From 251f99339b8d9930cd433ba0c994c7d01485a88b Mon Sep 17 00:00:00 2001 From: XProger Date: Sun, 20 Nov 2016 23:27:42 +0300 Subject: [PATCH] code refactoring; animation, character and sprite classes, compiler warnings etc.; fixed #19; fixed #20 --- bin/OpenLara.exe | Bin 109568 -> 112640 bytes src/animation.h | 253 +++++++++++ src/camera.h | 27 +- src/character.h | 137 ++++++ src/controller.h | 541 ++++------------------- src/debug.h | 20 +- src/enemy.h | 120 ++---- src/format.h | 40 +- src/game.h | 11 +- src/lara.h | 685 +++++++++++++----------------- src/level.h | 24 +- src/libs/minimp3/minimp3.cpp | 10 +- src/platform/web/index.html | 4 +- src/platform/win/OpenLara.vcxproj | 3 + src/platform/win/main.cpp | 6 +- src/sprite.h | 70 +++ src/trigger.h | 19 +- src/utils.h | 13 +- 18 files changed, 989 insertions(+), 994 deletions(-) create mode 100644 src/animation.h create mode 100644 src/character.h create mode 100644 src/sprite.h diff --git a/bin/OpenLara.exe b/bin/OpenLara.exe index de8cf3bab416e931e3617451bacb3f36f1175ce9..3d0e86e58169b64c9bdccad43488b5c825fb0814 100644 GIT binary patch delta 47611 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 delta 44929 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≦=Z zUT}4i%>7c)5H4|r2GTWEITTriW|Oy#0_dN?y#_m5kb4bQUvppEQLZfw0Jyc|ZW^F- zo%Wz(A`bhdLw3&GeZ(A8htQR9d50w71RPch(LSqedJpblO?y2XQ{nmm-G*nHuPqg! zY|>2&u!rQgP;?{4m#;&b-3K^36fOHn3P9x9;Ag_GBSxEckXT)ph`q0?cOv4cWBGc_r3`9EOt!qEwp=q*M(M9Pu2b z^iw31%@-Ov)&(TY zFA2U&KqkK)$&_8?Or~15h4C9{#n1ikjCbURkpap=_xt%KRK>gA!E}_BLlQbg5Wu|E zHz~;Ya%#vLB9W*@0#DP`>1{{Rx!?}bkv zzUH^kn)3v)pPD6+r|3$1sIPpiqKk)~TF5BByiHy+{o4vS%cS2qtuTs&jV=ppjbyTSeoKxww6lYX{ z6As1xNe&|fs&18E=MbW8c!|TJt$&p8CElrM%Tl3!x`d;fLJ-XML~qbig$Q*HYb3v( z`igc1_CBc?HW~YX1D#F$TSXf<^2p<9=THSUuX+bfAXvIpfc=D(gdnx*f^`vshoFl0 z$)62DY8^HX4uk{>ZN=Db3y8i`ZO7lD)Yl2$t>fB?0^y33Dj6dUQ)R4f`G=^H8u1RQ z1(D?#0qDpG(HQZhhgZ?C*|ANcc}{Pm(B{;Jz1AW5XVp+++4hPzW1rdG5F6Ty_;9|& zb|R<5rU94_V{DFSyP|G{#m<^{m2v`$dR36;`m`A{s_;<)?~_yUW1%aT`cq=oB886t6tGln!-Qpx4xm$3va z`5ooLr0_ogUSa^hXT{?k5w`DOa5I4241<<12;Z)w4iLD(OQc+a;b&$>7ikWzI4cPJ z!A0a9myf|@=k6kq%eh9f2SW!t9)gRKUqP;4Cno`q=S?b`aU0k60HDM-+3Lsb$TLmx z7|xi`Nb>d8U>TydG!l{8)j!8U|623@dA(FjsN z5YBU_s9X~)VsD=Kji&L}(BczR%02{YMiMYB!#AD=4~G_DD?WP^tiwz?Fiwu`R8(L= z{PEeCP>R6^+(vwvJ;GkyvBxBU|CN__V6kh6gy|%~Kbc#o9E8G+0CrImUgQCeNcq*( zjFS@qlnncFykeb?BjYQVd>r10j1+rJ(mJGw-Mr|_!wFNaP4^8M#c1=3`G;sx}flfWVW(5-mtr)-k_efHpij5 zs_`fT<6kf?Z5d0``r*u3g+1vQ>@sw!*5uGQ)EG_8 zkPYgU0t5In&bh@Sd3SK_?_v)NZC+_%+8NKj9iayDV-2K}3CTi_Sio7mtpl+Frxn5W zzamI|0pp$}o_nw_+)x;Rsx0x}B`~sNa3}UAs?ssa56(_7Idp^V;}P9I`D*?$HE z9jK+4#xjOR7c6uCy6<9-@YB^Wg_v*SaA^G|1&076#1Moe;X6o5aDf?y&OLY=L+L-h zVICT1ygx~7&epl6kE2X_muSbGO5AG(IQ1bJ9fwk&NyDUY8h(60k4kgnTko*7WK&mR zlmr|uW0ec}@)f_$xEl9K%@-U|fCfa+6@V6MmD_GYw{I&C zRm!(X`Mkw3VGL%Bl#}0vYwe{2CP(yDP$5kAZ^E#xz0U#0BhqGwNDqlMSENETdNA9F z6DpO019r}D5pEby_{6cA7~#vql>iIt!&aep4^di3Xk4 zj*U2n4%u%zR`cqKbf=cE=s`y*2#OI(I==ujMVbTmy?CuW{T|ADSGfz}=} zlePJz<1{$r!|IxN)eLt^t}O>CU(>Y!dabfk+EUi>!DMxC5fPB)1B$uSzgVykRL($> zS#O30!X85m0LK~-6<4;qW1>;L5HCA+WEs^ngaSq+jD7z3L^DM_uD$5jBKj$ZLKka( zi%ULCnPE3+Xlgr%kow^~h2J8Mz;Fh_U%=lEYsnaw`XDF3GfCq1M8-6prWeBf>}mtz zHnJ!}K^v9Y5=pY)1ecaEW#`~e5Cb|oB;(r>6?0{ETOuu#Fp1HdoBWz#sh$xkxsVR{{xQLLNfqTx^;*)4zTUOm!%R1m)xU+ z7-8BYyq8G6xzY*c`h~~5-HOfBa`^Y48Q7u;6vI&hi_yUmu3{*WfGqt93YxO(_7rWG zBUdqbdM{j`p|bPqiB8(-k~osxI1R3!ee zIK<=KTfB;jXQ)xLSyp0NlRR+o8@!Rq67_O_G4=!04SMk~OBx!3dwYc7Xtc}*yrue^ zVvLfRXK%vt+(VDS%u6lOatjY}54I{XTyk1ipywXCKa`0(E#2IMHHfj7Lcf=$3rfN{xt3w! z*AIAH#5YZ6*`A)i_nh~$Gb_~>yi3pCqL#fsK6}dzL%={bb#Pl#JWMieUxIlc`0$SE zcKO-0XljuWNW6bOyGw_5+SOjy-;!wO7Q03csl4Tcqln?aDZ_Mz#~JIX$byuU8{zuR zoUA+P^nncNil^PsVme9*SsJ;vGE7mq+M$O;t=r^0g`TM@fG>M~#MFAA2u& zHr912f?csikQh1kUT|sb`m>k-c5)jYyJ>rd&L`O=TiY67sQNp==oVfT`N))0Z-B*_ zX^DmgyG|Px9ovUk7v$qBU>!xbQcs!by2CkUGdKl1fnpfIB}S{PJ>1bSO?|6yAD}^g z5s}VL^T0g~vC;urbm5EeZqUtw?gJ#v+y_XvU|p-|Xm!-FC{AZkymBN(t;DU6BeyHO zg+@YQkUwHkoW_CLK1yO>r(~fQ8CTs|&FBs~u5@2p`X}Mi%s^uA^AxcQibZE-r$Rnk zfMLrAc6L;j2D>5poZV)^IA!c7(v$!#YeVuWUJk3B*m?!Ig-D#f>egq78R$3-Bo;CK=mCEEN~S-=RfMLvXz!40wH zhY_ksVZ%T_7_rzQ#=kV^G>Ea~!dbaXX&f;;GyfE55h78iNFF_~&g^Uq`6L-YWXdr}fk;ze*Dt_0R{t01)Ct{jUiKJ;{Q$E`EepK0b=I_4&* z?-3OLhSnh;`h}R;?PLxDR~ed#a<~N<!q zUUp^(A$k{{5r%}v)#wwl^^I%LbYvx}(*)Xi)VlLx2|@g8&>Vje7O(6uQ5;HQ9p@Tm z0!FIPV)o%g-2&ei=D-}$P4@K%X$oQ(@n0f5dKhzxP62VJD)SK6{vx86a^lon`!88W z0CzVQEZ07rc4)z&B`UhEz&-Y0ILEMHxgnIpGJZ(ExZx1^G56SQ2#bOHns9Jc#%*88 zVwKHFV!*~dN|$)V!0P77VxWq9G>yfoxyR;Hpf*c|*LAe+12qtD=z8>IPDoXWsud6k zX)2av$_Z&ImK3g?+yyXBmTa&W?9R_EXQPX&kd;2Uo<%|z=GY`7x!8o%tBesoc3&1M zNCqAG7Ny`^8}Whb!VRc^Zrv%CO_(g`tym^NNxdPxhpedfvmLui)TUJu*}{2CGE$Kf zYe~kS73^ha@;KOQ=Gvf$1edgc?&hgW+VJ5XJx?i*QLya;1TiqM875{njd0tSAs!m+ zo{nir>iGgWZ!zw&vxn$m5f8uY5OP4d_KKnW_Z;eyRle|J@!o%aL+cH5YavBm*)!Oo z8`-&qxKY#L3!PEJJEC=n2xN{&?kZD)xOJThW}x{UuO_Ld2xX>%h*s+VJA)ey@Co4# zX2TcZFm7|;UP=wx@*ikGFlOENx@xIPHJ9G$t?#E-FBI7iI~HHtUZ^p`z3XxwJlDYo zPCbr?+W$o5oTfVIh_sa#WngMWEd@?zfpd0&Gq!+he+Cc&K-;_s_zn029M3LCG zqima}#{VdCBMj%sYcIi$$hG|tnHgJgp=2EbBC|f?fVUJ8kjP8N;XkF}aUB}{P3HLT zQwC`8Po@nd+Kf2WaXr43Q1yScH$efMj06}LVW|8DUE>F#)Z~tjW~-+PcW2SU6+Q$} zyf&{L9WV$yWgc5LPOc$di8xgY$19Yt(7xV+zCoziu)hTrVDxI>2Azkq_>Gn zn1kCFD}71!OX&z6U>48eCHPyo*D&6#pAtuO|0`Pjcot;738+(8r68BOtQH=(AQGB< zvVR4xPl29PLZ4eJso9HTwR5_;eLjK)HSs6`-9hSZJPM>+C@kbpK=VI$!3b?s!4wCOWE`vec!vBLanJ$Cj>5b!f z7Z#6O5mt2U5ljoVC(KQj)w>m*gs;I?g9=v*kINhNuu^FJw@KZX2WI%@(gcpkr@HP& z)y^s3Lg4#7p8JC{i@$&-q*I482>%jT!xH4r2{qlGQ&^8DE-)a$$jp3j6$Gp53Ly5_ z4Tjw0PT57cu)|;{+u{QTHKyMK29+}ceh&NT$w8(Z)1AL9A(bLYSz-i!BG>*0We!;s zhTutBFJ`=vK*4MlG|I1Kh9)KczXzY(!eY=g447Vh%3?w)$>T`Bx|)Cwfdcx8Yz>z- zQt9R4(gLF7_lNs+zyp_Z!p4fAAanUEq^k_H6UL2zg&#J=h_#EcJf<=O#2GklO@wr7 z<`ZDz-L^-2`L{#gNJXGNifh^DIK^1 zj+%^36FWV}0?HRd z;yWuDI{6pC29`!uBx)7{2)i_fii#<9^f7=`U}vOO#SnHjg~%&3#rL#88kHE}Igb&u z1xg>3gQvj$)XFUUupJzFP-(2pWn42jygZL3Tr)TdtU;cLtnUtvQ9iyngo6g>`xton z4VHq@NKlmAm~2pqIfw4Y!UD$Rr=ekJg?&Kxk-|C{tPbf$b-K`r7Ls&kVl#33Rg(qgk+csNe8}|O)~{OxbcYbNgx2F`6XN(%6sgT|a97O{k0fRm4L80t zCY)%9-<2Qv9XPQ8Phn)ci?c!dVOG{VZmNOko~>y1 z(42pC(KT#klsj3%HEgB88stZmEaIEWML`~%iur*|HqR}KIz|`3Nhu2q#HI|*ZHJnw zr8!#-jJdK5zlO;S0=L8PQWOC}YeIblI}CM2LUhjnq|_gU8p(T-a@ zt+ZS6b4yylV8S%Vd?X;GaHHT`%lObgaY+mOEFIHrTyY??Il&pP%&<|gN-4cCRf2vW zWUNtbl-?IVj(2F*ieJUMj2X>57B+H=oq+%G?WgEoe?N9*4$hX@=UOa&lDOa_=Y0Il z68&1|6w$B8%A7E`8p@*My1}wI`bG>E(P#Rgg+4O|mv!8JlX`~sB*v$s?k4ptL51{7 zJ+!7z9UP&_5qGIaYvZspqyK4yJks0FJn%qxl57;O%yX5tpLDM5#-{tk<@d!;)Z%fI zdT6Nj}vskO_5yWPJkLTJxM$e-DB(9A-M?!mQPx*`2 zY$4vg2gI27J}vc0<~?c0793-cqp~iYxbN!T=ncGyitNxi)-gb{Jry=b0VLx=nuXnV z3XC>HIerLawnK^p%$D0b(F52SoRE8d9hOU%a*RpvTX=wvcU)(4)I_X;LmPYAVQku? zn=svpB@ze86pM%!ky(TPP$FtN` zunZ*W<;pM&3NaXqlBZZZXdV~C_XG{?56xdHMLkAKHqB&v9EE89_7NE=9Z#fU!cVBh z5J3(sz*@D0CSoH8+Or)~Y>q|b#^;Z4MBB&!Om3&G&BhkA!Zk@=_!(nM9fVnRkFqQ{ z?;;^zMMA!kg#4XiV7jx2q<;ae1G!icT-Tvy-A<4}gwcVJ;Fv=bKFj|)YE5!#9kKr1 zVj$KznMfoJcrk!CYaKEEryP_0J4nE!ID*WY22n#Y$YL53LT@|`;;eGSK`cMGPjCY3 z%SQRb7s#PhyekX;kI-$NAM>`1NUn`G!{XA}PSa{}X)0vF((hq)T6#TJr=<(AIxW?( z#p!Ph=ERMS@OLrsGlk%i7E19|B5H*zY2!^c@L?wfv97>^_VSLBk z9^@nuHDM%Z^ulA+hhr7k3!N^cS+YINsY-_kc&rPzBjhtFO!%@s!|}=sEXvPPj^Oa$ zuhA);eIVIV>@Q6XccUZG?CuyR_6ml@atd$T~nJNSb|llbgfLTrao{WUa@ zNp`Jk14xN?*5a31BbY~>QDY?VY<=_;(@2EaKD?c?VewFSl=Q){`0nNwQo4DV*mjt) ztvbxM-C$dSg^Fz90aT0?V1{rT76QDebeAYkBsVlc=bTvJTm*kK=gI;X<8fjSG$`aM z!2FRd1^yIKP9!(lzZE#=i82S%p$qRfUgZVFv49<}6c@0i3d6ums{SEbpda9GaAm^| zUQz!l8Kn~|C3aO5S7OSR8ZRFrS>c)mt3$`#>FN}LC*=ySD*YMDM#z4Ow^-3KaVP;7 zWUcZ-I!G=uz@ZiIqTvSHEW|<18Y4Vq5%zH(vf6?vF~X=qJ*RBX4B=8#36iLg4IgA5< z0z>k1mV$i|Qm|hJQ-&-zeXIxyGMf#gxt-3p;N*#4yh<`?!ODR|{MWFMxu^lQ(RjF; zCXnQ16PjNO7Xl`Lf$1KaSkpKQ`VOT@Vy#qbJ08qX&!ac{fHryXGgqVbQ@GL%v@X*3^Ido1|Dm0=ECB66Im@X@=S{xZ^erc z=>Keb;UhWpSL2@+-gvN_&3KdnH%m8bt=N&uoxrAa;EVI{-;A;DCk8=}ZbSk?*q;I= z*~VccFElNtM-R~F=mqu~0wVHbD;Q)I<^Uah@^qrC_-mmj+0|>BA}GMnCo_&9n>^Xv zA^!c)W9_;Ccn6xV&5{l%OZrt?2iY~typMdgKy%9O-S^pi;da=ijWAsZ*`i?3x>9l5 zss31@lx@6}r~bYgu2A=+<9~O-oTW!7d-DyX-~eZSvi#Q$oF+!2l+LNGbysMvIu^0h zpX~r-r#~RS6lCzZ#9Vh^2-*2Dj*jUJ*}LoqQ9|Wh*5sHhT3Q$OaSuNL-#r3O_0Xkp zMZLk3HndXQosKRmKw$M^`oDd7YHlB{`gY=bE^J2_K#ULFGqmG<0m>tQ0d&cG=%~fg zR{&T+J&V_51@`0(lRP-YK;yt@R(uNCn-X{f2;B<)$xT6Gil)9)pJn}+WSu5BwO<%$)~+gCVK zS8(n9NJN!0XVzejP7gDaSMh1&4b$tO_K+o zpyY)y*8fx7xkpD;T@CzRNML}3nP38uhXf5a6~t(G#RSX{5|D=-NC^o>M1eRCsHl?= z%*n)sX#EO6fTa@8Z5+AK#ZA+EqLQCz9Nozi#7?CpHZ=X9M5v}%* zeyg+Ao_p@O=dsT|`@Hwrn{L(qcvvuzESFfR<_h&E=YR?ftk6m{r(Go#*P*q#m4~po zR0|Q>!uB_(TA%PR`?f_R9YXjL&2H95*m!@$>Qp8VWsNw8R3HIiBt5mRr?zBWMq*RhBBJs z;>O=Xzy&BNM<*G6lj)I*<2MlT8@l;XZG-l@DY~!4zGoad>|%qx;2PPXWw@sDQ11b! z+Fufh4y?EgIR@2^eHy&E1iqgpO25M5kbxXG)l?+vw}f!KD&{ZhZE>JfVeDp5kjCA{ zSl(+m&X8k@D}AE>HramNtx^WI_)C~xCUhz9;h#I4dkDg@qCQGGf?O$#YS---FQq=8 z8glShD-Gyp2e`%1OM{s&v)9B!b}S^*+_O0h&3KnqJmjGFR&GX3;}9;<(n^eY&RyhghlWSoY85 zjAd@R))*!`KK7SGE{;&J*D#umB&;@Yx%q)4`*jS?ZswAI<6q$@C3UdW z87y$Huh`9h&QUg^=H0SnA(sb>B*+5$!&#_W?gpM+S1Z6czCQ1l$rC_>419UD2hv z>X?*MJd~D>l1+~`oYpCvM!C$&$1dWem0`{{iccoTK2Z41$9$?Au*i|n1uxs9bm&T# zNOFfkt+@nTrzY|jD_RKPWsVIv8?di>mvW(X3eE*fN4)Gdg_dolFxCo(D-dW#r)d4< z!)=N6d5LNjy#*pjS3NK^mB1{=?fJ*eN0{%RUk5KU8)q=7Ic(gLtKZO-od)%?Di^69 zB-fROsuf6DTPRw_rq%JW++x0{g<;7?c+|Qg6;i4fP-Yd{(L!z09=s2MuYWyDlvp{3 zpx2|jqpQSzuq5!7HSJ6}z%vCYii12;HLZ8<9CIC{m$)a1OU*?u5^x4g^07HA6$nZx zQ=F2}rP0koVn@-o!5}%ZOTr4~ht7Gj{_)?DY>aa0gMHgqMI!D*5UcO9LsT&>pJ(kg_N(ahwwsJ?ssbb=bCxb{IxCoNBp&C)MNfBt`nO;GcrqYfqZv;{z6ULFs?p- z5#L$*C**rs?`|pHvPIOODwS>HViruI`>eEN5K!FMN$lHJnM9z-<76D1B#p`vgFIuN z?59S-HnkmJs@HF-?f3})_N7B!3&|iXxhWdCRbJ1S>#WGPaT)N`Rd^}A;au6qsq}yx zXB{XZTTtvt$=@^{pT@}=OUanf%d4d>mYq_w6~vdl?CX@E&{&2JZbkTCmCJ41`}>-2 zsG>rO&%CiSxma?yHj$-^l)Ejeo4*NP5Icag8&9u8wc{m3o5fSfTfumb+oGpOe4!QvUjqC=ZHzH6ab%h4_+|taxWtZcFrqKIS z167ho=+(9SgW2t~&3Bn7G;5DP6U)ygqP z)y|)r3|#>JC&AXJ&T^&KsI0{!(G&!pu%pH z6gNp~8zret(RAYH@Dh3m{);XSnejscP>@)1!C5wF)XrNpOGj)8QSguMYso6RsLIMV zA^LPc5yztzijJzy3Z)`-!F^&5p>xKA9rH5&)fmiVD-F8%?-cB%gvSr1(g{EJw-86(OJ0#<-Z! z!xfg0I2nA?{*omX-Zm+2&nEPUBr41#yh<(Ey8VkN=W!gqXReg8 z+Sj~6XDjO-W91Cp?o(@&uCB-)!Pgu%nP@DR)Dk!Ng!m!o&>xi(T>>lR4J6S|$SjTP zeQEZUC+TYm?_w%dy$`n)sPPT)epyP3TBl;jiozwUkbp~5AaxAeYbl`&s=d~KCs zg&pcMC-JSlmsP84WdrdDG zihaGy?KL)WHAFI33PJ2^zT_XAnM*^A1XvxsQXm0Ivh9;*44J9_z{m7W& zu)WtRgPQIT@~7)ahWv1<1;3=@5~1S$V0KciU>jUC$LH3n?247#3LcJ?a*V(SQ8*FG zr@<(to*(h4WIzdhe~`s*iBD!v$g-)tbNFXb3unvj!*G%MURJA`Q(UN4s)0r&76uw8 zaD+<^YIL=dj<#7_^|>AG(QRbA zA;8>fo`@dO3{+U^ojslW3T8aRnd9wu%``i~-=QbAD!Pi2(sQS`$m8?sv~!is?ij2 zdX-b^BzkZ$J9dhi0XQZvq)wt@zI1NFS89cOjauT4-DAzGWIoCYGnvF$KNJ^JAkvk_ zXddZfJUC0g`ZDmPcvGZ!!E+$vZaHXdpQR7$6n@D#F$-aZ)s$F@*s@=4m!ICU*Tv)Y zt;7qqPLrm!#BJFGo9s3}d7y13C{qXK9p7P_Kq&s81=!}>dXdP@{(^VRM>s9v9-<6}Aea$xFc^{Iy;BMmj zP0sPeyv;MFo7NNGX@d`c? zd~ADgTYK<5-w6JhmAbt>xa{#pQ}?av7=HW94u!pTj(SU8&UtG;d(Ct! zt-~-t6w})Fw6Y~Bg<=%nM%kA4w~n)~>8u=?_7FCl@wwVw{i1TD`Mmd|aLQhLPQ6~g zd?;F`?6oq{^0$t*ulX|$4#{VbUvaP@BP<6GlS#|f_?XJ+vwz;On(IB2F+5PLCxcj4P(q$%D zNu;SHkU{7EVMso!1oo4)m4FQL9TWJWq%cvX@ZJuE#9v9sfB4cAmS1c9`VM`_)hKrH zW=8K-m#AsKy7**X>oYv->D!wTa7T?rJk{LG4 zJ2`VC3idFOYHSYVoxteUNW1?jdK~n8st&VoV&`K4?u*t<>J^xJ=8FSbC7oo=t%UF| zalOF2__^UkTElbHmXkO;~(+uwHiLoUKj~G|N)b)&o z(eNxB67n_>zF0+I$gdk0C)SUQZu9h^qeD|G%^?Hfs+E|g74;MA6J=%gL#Kad!hae}%RyDbZi_vh;a zlTI>gP}q4K(DpF8&i7XyGCJ&iN$K=QC7=bp4!f7<%-jsS|)Oo4!2N( zVwXpY&E3UURWZ%C?G!TKk#RPw-fMS@U!fR%$1sPnu~hHdY1UZdwNib6uYvPezYksQ z32lKQi=%ql{lAT5E>0$sOWH%`;xs%jX%3l-JK$)w2C1Wy()s{X%Us;wYX?6JVMG>3 z(G3PCD~irIBX$jS=-K(8%Y1%J2YRYnLm50o^-GTz3LRx~8mw>!Cc5d7m$hKhXQ;wHQX)4zJf~2yxyaPyb{`Y z&t6AgHprNAxBi2{74D0BX>!;!d0}AcdyQSsS?ZsgWy5mt!X3ubck55rJ#7dS_s?%dEI{c*g-OYojkxonvSw;qb`C5-pI@dQGFm(g4PhgGfj(pu%&QdW z2PdYAQI*T0Jq$Kggs-Ez^*eS*ixig__bt#kSNWnU!M4+)xhZ>XU8s76WbEz5YipkN;7klm*33>!7+p> z{wZdO8q8m$D*fkhVnrL1%#jut?KMe^D`mgsF%q;Eev4EP zN_iPTvC%|c%SH!l#ZIz0iA+E;j~>t5KmM?B%R)W%I_S`zXNN3P!_|CGQfAc4W85Fb%iEkvCo*ti_#0`+;2IVA=2Y{@DDe3J z!7Qe>T3~?Ky9w3F@E7c9R&c5o?BcJmNz0MBc!3~di&{9~L!5l5pn0kfad5oY#E~c| zGH%GcU1FrDabwOs`cN1kbdTbLZ9Iezu(uVNdfr}>4YLMERD-1Z{vP1_2X_n|VkF5c zKI4^-YW3#an27Pud-TU<_b)Ur+{UPS=4nty0l;T0$lbC5YyN%ng-FI(nQUE#uDeq* zwAVF9BAXV0TvM082zh!OW<9|qP)~Qvda!OVUnB*kCqrbalCK%LipK_!^Vn5`p9kx|GW@U=LE(9lIi4n$@pNA zK01BRdAwQi%gyXr9JmdOPiGckog)SFPALtWCifJYWD^-#5hM40y~r7+z^tI`)$19_ z(JxY;cbH|W<`M>Kkb~EPNYZ(5;A&pYY=XGgQG`%U8pD%|PjOYoiTm|}43SKs0kUCI z0;SakqmowPU5TZb!-){I1>$A*5zXA2<{UEad_ccD=X)bQDbyt5yl6zXJZn~y+*Tbu zhenSF^^p_Ss&;SffwpDI0g-(|3&*aqw`!m8Ma@~R3bvoQR&Tfv8H7c}8Lh=^Wh0Vp z9_VFMJ*W@7Y(uXIUt0O-xv7}%=9*r{t_SrS6I;lI>*vz9u!10>>Fj0bUUcg{!PuPZ z|El}aPn!B=gjWyv7_xf07VlSktuO2ssZn%BZ?ih(LzQFxEM)iIGjsoDSmsEqdYu_b z%7M_H6n8qFkr5U0pLE9Za$Vxp_Sse?L48)(R>DR`q!HBo^-p1IQXG#Lk8B>d%W`^J zH1EQh-f30K;`YJFWnImMwA3c2LMDm@#<@Ky7+9cZ5De%!{vu2Gs8=&yZEg(=e9@e4 zpN&T9MByj56fBZ7k$`Ucb1mb80{4|(Ub4ePjkqm@TlmZw-5qXiq825CZf+CQJvqVF z*M$xRg@G}uL`fTEavc|n$*Rwf(NgKzFre-#bh#kK5GisBg<#4VdRjhKU697OEHS7e zH@&%ffY3NFAnkzhdYOLLZDiay5q9>VTF$+JPv#8xf%NywMtersV=F%WLVFGJy8lUr z#?vn}$|*5=P3(0C4m3J8N#78Nm!6{W3mS$r-*uJ7igNvGz0!ETT<;hE23^^*_fEe+ z2Xdm)I99GF+Q^(K&DKieZ{>QQEZ%~7Cx_%6m&3=r6GPCS6e&9%DSM9?+Xc@_B;4b8 zfu{wJ<9N6=(!~8_rE$|@J-PP=R={3c4tkv*_}`|^=o)_Hcc!zaanE8sOMlLIez9KI ziC4WszpBsbc;*c6dV8pf-Ar0d*^#8(l}1*Dp4O9s3um8awBM9-$hg0PTiMo??Na-F zv0<2>O|5h)H{*lXHSHa$h8c>Fz2r#J4k-k#A)GB2@N@A}#EZ*SP*CWOqc3(R^GAnq zR6-sv;Qlu`pOTjrwpZQg$z)n2m&RW3q!r2iTBb=k^`yRVFrAQic*x!DD&+A0p_C)l zsjKj2%+^SP9Vz!T7Zi!%g~yM1xC)6v7H-^T>=%)Ud6e$fe2z(gdq^2PcphYgnx5`B z_DQH?Y%>L0G9sa$nluYAO!4^inKNgSZwdAFHencA`tdr2*ORbN14jtYdA*kR$A*r4i{S_Mp>z zsvXG$=d}Hk@Bwq+=k}tQUrRqk*6CJXR2*vwNy&yTo>9G!>V+LWyi%-=h5We089!Qj z!<8Ztf(K_PGHps#-6uhL<9{e!C+YVOj`2w=itb#mN}()B6^eCchmC0JQlgTNjS4v@ zs34g?li_T6TE^Mnntf7t{_N``=oX!FeP3mSi4oDAD>vIzxTWt~L!}IqZXt=BcCaK? zd;Jcdl3x_5T0u36Jvb_F{`=ckO_x~)b(Il`_BrFJ4Yumj17jT0IL9RNZ%#NvFxV@S zB(2Z9VdBK8Ia?swsu`$!!NkA2@I6tdp>2Xo?Z?PkI438vLfQMiNs@d>t z1Ft;R^SFI>Mp!Ozaei-a`W~n|nFE#pU!PL0Ufi4o;EVohWE{s!AqXqtDakTT` zpS=Azv^P&bVIiU<0bJlQ!4|1ow^U!KbCWQ;PS>sh`UCQ-eMQ%n0`~$Xz$9QKkOuSy zx&s$>>)PkQ`@lh98&C_#FT7LN-UCeFb0Be-uB8IQfqb9{C}6Nv5OxlMq~_X2MN9|E5P8veb2tAQJUalns(QlJ8;1)c@u_a5mt1LpuQ z2=^)FuMijy3w~S`lGg_=~%w%Bh6|mU}qqv=ZNjzvFSjN8M`)Ur9Lecj2Ey_+bu7 zhzLcs1Qd4&_gc&CScN-ixz`dQeMMcNUw^Cjn5S?9*}!<92$%;f0xEz?U^TD~*Z^z? z_5uySl&9)u|Bv1)-uH29j`qaK9PRg~a^$l?$&;%0y3Iw?;2=lM%_1nlS8EWIu z;Q%z}JGEyfa9iy8v$?fP>$+6}ilwO-TV4f>%G zJioOcDkRhIuFvnU&L>D*TFUNXr?z5sj+VDN$C$fa&onk{*Lxc~x9dy0Z-9Nn0Na1* zFY2>nweebbl=1Y7`c0i$4j3n1)CW8|%w~M~qJG!h)|agzM*<&VxN%kL+Pa62pXV)p3g_5TCpJ*71O diff --git a/src/animation.h b/src/animation.h new file mode 100644 index 0000000..49c20cf --- /dev/null +++ b/src/animation.h @@ -0,0 +1,253 @@ +#ifndef H_ANIMATION +#define H_ANIMATION + +#include "utils.h" +#include "format.h" + +struct Animation { + TR::Level *level; + TR::Model *model; + TR::Animation *anims; + int state; + float time, timeMax, delta, dir; + int index, prev, next; + int frameIndex, framePrev, framesCount; + + TR::AnimFrame *frameA, *frameB; + vec3 offset, jump; + bool isEnded, isPrepareToNext, flip; + + quat *overrides; // left & right arms animation frames + int overrideMask; + + Animation() : overrides(NULL) {} + + Animation(TR::Level *level, TR::Model *model) : level(level), model(model), anims(model ? &level->anims[model->animation] : NULL), time(0), delta(0), dir(1.0f), + index(-1), prev(0), next(0), overrides(NULL), overrideMask(0) { + if (anims) setAnim(0); + } + + ~Animation() { + delete[] overrides; + } + + inline operator TR::Animation* () const { return anims + index; } + + void initOverrides() { + overrides = new quat[model->mCount]; + overrideMask = 0; + } + + void update() { + if (!isEnded) { + time += dir * Core::deltaTime; + isEnded = time <= 0.0f || time >= timeMax; + time = clamp(time, 0.0f, timeMax - EPS); + } + updateInfo(); + } + + int setAnim(int animIndex, int animFrame = 0, bool lerpToNext = true) { + TR::Animation *anim = anims + animIndex; + isEnded = isPrepareToNext = false; + offset = jump = vec3(0.0f); + prev = index; + index = animIndex; + next = anims[index].nextAnimation - model->animation; + time = (animFrame <= 0 ? -animFrame : (animFrame - anim->frameStart)) / 30.0f; + timeMax = (anim->frameEnd - anim->frameStart + lerpToNext) / 30.0f; + framesCount = anim->frameEnd - anim->frameStart + 1; + updateInfo(); + framePrev = frameIndex - 1; + getCommand(anim, frameIndex, &offset, &jump, NULL); + return state = anim->state; + } + + void playNext() { + setAnim(next, anims[index].nextFrame); + } + + void updateInfo() { + ASSERT(model); + ASSERT(anims); + TR::Animation *anim = anims + index; + + // framePrev = frameIndex; + frameIndex = int(time * 30.0f); + + // get count of real frames + int fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1; + // real frame index & lerp delta + int fIndex = int(time * 30.0f) / anim->frameRate; + int k = fIndex * anim->frameRate; + delta = (time * 30.0f - k) / min((int)anim->frameRate, framesCount - k); // min is because in some cases framesCount > realFramesCount / frameRate * frameRate + + // size of frame (in bytes) + int fSize = sizeof(TR::AnimFrame) + model->mCount * sizeof(uint16) * 2; + + int fIndexA = fIndex % fCount, fIndexB = (fIndex + 1) % fCount; + frameA = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexA * fSize) >> 1]; // >> 1 (div 2) because frameData is array of shorts + + int frameNext = frameIndex + 1; + isPrepareToNext = !fIndexB; + if (isPrepareToNext) { + frameNext = anim->nextFrame; + anim = &level->anims[anim->nextAnimation]; + frameNext -= anim->frameStart; + fIndexB = frameNext / anim->frameRate; + } + + getCommand(anim, frameNext, NULL, NULL, &flip); + + frameB = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexB * fSize) >> 1]; + } + + bool isFrameActive(int index) { + return index > framePrev && index <= frameIndex; + } + + bool canSetState(int state) { + TR::Animation *anim = anims + index; + + if (state == anim->state) + return true; + + for (int i = 0; i < anim->scCount; i++) { + TR::AnimState &s = level->states[anim->scOffset + i]; + if (s.state == state) + for (int j = 0; j < s.rangesCount; j++) { + TR::AnimRange &range = level->ranges[s.rangesOffset + j]; + if (anim->frameStart + frameIndex >= range.low && anim->frameStart + frameIndex <= range.high) + return true; + } + } + + return false; + } + + bool setState(int state) { + TR::Animation *anim = anims + index; + + if (state == anim->state) + return true; + + bool exists = false; + + for (int i = 0; i < anim->scCount; i++) { + TR::AnimState &s = level->states[anim->scOffset + i]; + if (s.state == state) { + exists = true; + for (int j = 0; j < s.rangesCount; j++) { + TR::AnimRange &range = level->ranges[s.rangesOffset + j]; + if (anim->frameStart + frameIndex >= range.low && anim->frameStart + frameIndex <= range.high) { + setAnim(range.nextAnimation - model->animation, range.nextFrame); + break; + } + } + } + } + + return exists; + } + + float getSpeed() { + TR::Animation *anim = anims + index; + return anim->speed + anim->accel * (time * 30.0f); + } + + void getCommand(TR::Animation *anim, int frameIndex, vec3 *offset, vec3 *jump, bool *flip) { + int16 *ptr = &level->commands[anim->animCommand]; + + if (offset) *offset = vec3(0.0f); + if (flip) *flip = false; + + for (int i = 0; i < anim->acCount; i++) { + int cmd = *ptr++; + switch (cmd) { + case TR::ANIM_CMD_OFFSET : + if (offset) { + offset->x = (float)*ptr++; + offset->y = (float)*ptr++; + offset->z = (float)*ptr++; + } else + ptr += 3; + break; + case TR::ANIM_CMD_JUMP : + if (jump) { + jump->y = (float)*ptr++; + jump->z = (float)*ptr++; + } else + ptr += 2; + break; + case TR::ANIM_CMD_SOUND : ptr += 2; break; + case TR::ANIM_CMD_EFFECT : + if (flip) { + int frame = (*ptr++) - anim->frameStart; + int fx = (*ptr++) & 0x3FFF; + *flip = fx == TR::EFFECT_ROTATE_180 && frame == frameIndex; + } else + ptr += 2; + break; + } + } + } + + quat getJointRot(int joint) { + return lerpAngle(frameA->getAngle(joint), frameB->getAngle(joint), delta); + } + + mat4 getJoints(mat4 matrix, int joint, bool postRot = false, mat4 *joints = NULL) { + TR::Animation *anim = anims + index; + + vec3 offset = isPrepareToNext ? this->offset : vec3(0.0f); + matrix.translate(((vec3)frameA->pos).lerp(offset + frameB->pos, delta)); + + TR::Node *node = (int)model->node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model->node] : NULL; + + int sIndex = 0; + mat4 stack[16]; + + for (int i = 0; i < model->mCount; i++) { + + if (i > 0 && node) { + TR::Node &t = node[i - 1]; + + if (t.flags & 0x01) matrix = stack[--sIndex]; + if (t.flags & 0x02) stack[sIndex++] = matrix; + + ASSERT(sIndex >= 0 && sIndex < 16); + + matrix.translate(vec3((float)t.x, (float)t.y, (float)t.z)); + } + + if (i == joint && !postRot) + return matrix; + + quat q; + if (overrideMask & (1 << i)) + q = overrides[i]; + else + q = getJointRot(i); + matrix = matrix * mat4(q, vec3(0.0f)); + + if (i == joint && postRot) + return matrix; + + if (joints) + joints[i] = matrix; + } + return matrix; + } + + Box getBoundingBox(const vec3 &pos, int dir) { + vec3 min = frameA->box.min().lerp(frameB->box.min(), delta); + vec3 max = frameA->box.max().lerp(frameB->box.max(), delta); + Box box(min, max); + box.rotate90(dir); + box.min += pos; + box.max += pos; + return box; + } +}; + +#endif \ No newline at end of file diff --git a/src/camera.h b/src/camera.h index 22c96a8..6b40612 100644 --- a/src/camera.h +++ b/src/camera.h @@ -56,7 +56,9 @@ struct Camera : Controller { } virtual void update() { - actTargetEntity = owner->target; + int lookAt = -1; + if (owner->target > -1) lookAt = owner->target; + if (actTargetEntity > -1) lookAt = actTargetEntity; if (timer > 0.0f) { timer -= Core::deltaTime; @@ -65,6 +67,7 @@ struct Camera : Controller { if (room != getRoomIndex()) pos = lastDest; actTargetEntity = actCamera = -1; + target = owner->getViewPoint(); } } #ifdef FREE_CAMERA @@ -91,28 +94,28 @@ struct Camera : Controller { angle.z = 0.0f; //angle.x = min(max(angle.x, -80 * DEG2RAD), 80 * DEG2RAD); - float lerpFactor = (actTargetEntity == -1) ? 6.0f : 10.0f; + float lerpFactor = (lookAt == -1) ? 6.0f : 10.0f; vec3 dir; target = target.lerp(owner->getViewPoint(), lerpFactor * Core::deltaTime); if (actCamera > -1) { TR::Camera &c = level->cameras[actCamera]; - destPos = vec3(c.x, c.y, c.z); + destPos = vec3(float(c.x), float(c.y), float(c.z)); if (room != getRoomIndex()) pos = destPos; - if (actTargetEntity > -1) { - TR::Entity &e = level->entities[actTargetEntity]; - target = vec3(e.x, e.y, e.z); + if (lookAt > -1) { + TR::Entity &e = level->entities[lookAt]; + target = ((Controller*)e.controller)->pos; } } else { - if (actTargetEntity > -1) { - TR::Entity &e = level->entities[actTargetEntity]; - dir = (vec3(e.x, e.y, e.z) - target).normal(); + if (lookAt > -1) { + TR::Entity &e = level->entities[lookAt]; + dir = (((Controller*)e.controller)->pos - target).normal(); } else dir = getDir(); int destRoom; - if ((owner->wpnState != Lara::Weapon::IS_HIDDEN || owner->state != Lara::STATE_BACK_JUMP) || actTargetEntity > -1) { + if ((!owner->emptyHands() || owner->state != Lara::STATE_BACK_JUMP) || lookAt > -1) { vec3 eye = target - dir * CAMERA_OFFSET; destPos = trace(owner->getRoomIndex(), target, eye, destRoom, true); lastDest = destPos; @@ -138,7 +141,7 @@ struct Camera : Controller { room = info.roomAbove; else if (info.ceiling != 0xffff8100) - pos.y = info.ceiling; + pos.y = (float)info.ceiling; } if (pos.y > info.floor) { @@ -146,7 +149,7 @@ struct Camera : Controller { room = info.roomBelow; else if (info.floor != 0xffff8100) - pos.y = info.floor; + pos.y = (float)info.floor; } // play underwater sound when camera goes under water diff --git a/src/character.h b/src/character.h new file mode 100644 index 0000000..7b34d40 --- /dev/null +++ b/src/character.h @@ -0,0 +1,137 @@ +#ifndef H_CHARACTER +#define H_CHARACTER + +#include "controller.h" + +struct Character : Controller { + int target; + int health; + float tilt; + quat rotHead, rotChest; + + enum Stand { + STAND_AIR, STAND_GROUND, STAND_SLIDE, STAND_HANG, STAND_UNDERWATER, STAND_ONWATER + } stand; + int input; + + enum { LEFT = 1 << 1, + RIGHT = 1 << 2, + FORTH = 1 << 3, + BACK = 1 << 4, + JUMP = 1 << 5, + WALK = 1 << 6, + ACTION = 1 << 7, + WEAPON = 1 << 8, + DEATH = 1 << 9 }; + + vec3 velocity; + float angleExt; + + Character(TR::Level *level, int entity, int health) : Controller(level, entity), target(-1), health(100), tilt(0.0f), stand(STAND_GROUND), velocity(0.0f) { + animation.initOverrides(); + rotHead = rotChest = quat(0, 0, 0, 1); + } + + virtual void hit(int damage) { + health -= damage; + }; + + virtual void checkRoom() { + TR::Level::FloorInfo info; + TR::Entity &e = getEntity(); + level->getFloorInfo(e.room, e.x, e.z, info); + + if (info.roomNext != 0xFF) + e.room = info.roomNext; + + if (info.roomBelow != 0xFF && e.y > info.floor) + e.room = info.roomBelow; + + if (info.roomAbove != 0xFF && e.y <= info.ceiling) { + if (stand == STAND_UNDERWATER && !level->rooms[info.roomAbove].flags.water) { + stand = STAND_ONWATER; + velocity.y = 0; + pos.y = float(info.ceiling); + updateEntity(); + } else + if (stand != STAND_ONWATER) + e.room = info.roomAbove; + } + } + + virtual void cmdKill() { + health = 0; + } + + virtual void updatePosition() {} + virtual Stand getStand() { return stand; } + virtual int getHeight() { return 0; } + virtual int getStateAir() { return state; } + virtual int getStateGround() { return state; } + virtual int getStateSlide() { return state; } + virtual int getStateHang() { return state; } + virtual int getStateUnderwater() { return state; } + virtual int getStateOnwater() { return state; } + virtual int getStateDeath() { return state; } + virtual int getStateDefault() { return state; } + virtual int getInput() { return 0; } + + virtual void updateState() { + int state = animation.state; + + if (input & DEATH) + state = getStateDeath(); + else if (stand == STAND_GROUND) + state = getStateGround(); + else if (stand == STAND_SLIDE) + state = getStateSlide(); + else if (stand == STAND_HANG) + state = getStateHang(); + else if (stand == STAND_AIR) + state = getStateAir(); + else if (stand == STAND_UNDERWATER) + state = getStateUnderwater(); + else + state = getStateOnwater(); + + // try to set new state + if (!animation.setState(state)) + animation.setState(getStateDefault()); + } + + virtual void updateTilt(bool active, float tiltSpeed, float tiltMax) { + // calculate turning tilt + if (active && (input & (LEFT | RIGHT)) && (tilt == 0.0f || (tilt < 0.0f && (input & LEFT)) || (tilt > 0.0f && (input & RIGHT)))) { + if (input & LEFT) tilt -= tiltSpeed * Core::deltaTime; + if (input & RIGHT) tilt += tiltSpeed * Core::deltaTime; + } else + if (fabsf(tilt) > 0.01f) { + if (tilt > 0.0f) + tilt -= min(tilt, tiltSpeed * 4.0f * Core::deltaTime); + else + tilt -= max(tilt, -tiltSpeed * 4.0f * Core::deltaTime); + } else + tilt = 0.0f; + tilt = clamp(tilt, -tiltMax, tiltMax); + + angle.z = tilt; + } + + virtual void update() { + input = getInput(); + stand = getStand(); + updateState(); + Controller::update(); + updateVelocity(); + updatePosition(); + } + + virtual void cmdJump(const vec3 &vel) { + velocity.x = sinf(angleExt) * vel.z; + velocity.y = vel.y; + velocity.z = cosf(angleExt) * vel.z; + stand = STAND_AIR; + } +}; + +#endif \ No newline at end of file diff --git a/src/controller.h b/src/controller.h index 4723dde..76819a0 100644 --- a/src/controller.h +++ b/src/controller.h @@ -4,51 +4,25 @@ #include "format.h" #include "frustum.h" #include "mesh.h" +#include "animation.h" #define GRAVITY 6.0f #define NO_OVERLAP 0x7FFFFFFF - #define SPRITE_FPS 10.0f struct Controller { TR::Level *level; int entity; + + Animation animation; + int &state; - enum Stand { - STAND_AIR, STAND_GROUND, STAND_SLIDE, STAND_HANG, STAND_UNDERWATER, STAND_ONWATER - } stand; - int state; - int mask; - - enum { LEFT = 1 << 1, - RIGHT = 1 << 2, - FORTH = 1 << 3, - BACK = 1 << 4, - JUMP = 1 << 5, - WALK = 1 << 6, - ACTION = 1 << 7, - WEAPON = 1 << 8, - DEATH = 1 << 9 }; - - float animTime; - int animIndex, animPrev; - int animPrevFrame; - - vec3 pos, velocity; + vec3 pos; vec3 angle; - float angleExt; - int *meshes; int mCount; - // TODO: Character class - quat *animOverrides; // left & right arms animation frames - int animOverrideMask; - mat4 *joints; - int health; - float tilt; - struct ActionCommand { TR::Action action; int value; @@ -59,145 +33,39 @@ struct Controller { ActionCommand(TR::Action action, int value, float timer, ActionCommand *next = NULL) : action(action), value(value), timer(timer), next(next) {} } *actionCommand; - Controller(TR::Level *level, int entity) : level(level), entity(entity), velocity(0.0f), animTime(0.0f), animPrevFrame(0), actionCommand(NULL), mCount(0), meshes(NULL), animOverrides(NULL), animOverrideMask(0), joints(NULL) { + Controller(TR::Level *level, int entity) : level(level), entity(entity), animation(level, getModel()), state(animation.state), actionCommand(NULL), mCount(0), meshes(NULL) { TR::Entity &e = getEntity(); pos = vec3((float)e.x, (float)e.y, (float)e.z); angle = vec3(0.0f, e.rotation, 0.0f); - stand = STAND_GROUND; - animIndex = e.modelIndex > 0 ? getModel().animation : 0; - animPrev = animIndex; - state = level->anims[animIndex].state; - TR::Model &model = getModel(); - health = 100; - tilt = 0.0f; } virtual ~Controller() { delete[] meshes; - delete[] animOverrides; - delete[] joints; } void initMeshOverrides() { - TR::Model &model = getModel(); - mCount = model.mCount; + TR::Model *model = getModel(); + mCount = model->mCount; meshes = mCount ? new int[mCount] : NULL; for (int i = 0; i < mCount; i++) - meshes[i] = model.mStart + i; + meshes[i] = model->mStart + i; } - void initAnimOverrides() { - TR::Model &model = getModel(); - - animOverrides = new quat[model.mCount]; - animOverrideMask = 0; - - joints = new mat4[model.mCount]; - } - - void meshSwap(TR::Model &model, int mask) { - for (int i = 0; i < model.mCount; i++) { - int index = model.mStart + i; + void meshSwap(TR::Model *model, int mask) { + for (int i = 0; i < model->mCount; i++) { + int index = model->mStart + i; if (((1 << i) & mask) && level->meshOffsets[index]) meshes[i] = index; } } - int getFramesCount(int animIndex) { - TR::Animation &anim = level->anims[animIndex]; - return (anim.frameEnd - anim.frameStart) / anim.frameRate + 1; - } - - int getFrameIndex(int animIndex, float t) { - TR::Animation &anim = level->anims[animIndex]; - return int(t * 30.0f / anim.frameRate) % ((anim.frameEnd - anim.frameStart) / anim.frameRate + 1); - } - - void getFrames(TR::AnimFrame **frameA, TR::AnimFrame **frameB, float &t, int animIndex, float animTime, bool nextAnim = false, vec3 *move = NULL) { - TR::Animation *anim = &level->anims[animIndex]; - - t = animTime * 30.0f / anim->frameRate; - int fIndex = (int)t; - int fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1; - - int fSize = sizeof(TR::AnimFrame) + getModel().mCount * sizeof(uint16) * 2; - t -= fIndex; - - int fIndexA = fIndex % fCount, fIndexB = (fIndex + 1) % fCount; - *frameA = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexA * fSize) >> 1]; - - if (!fIndexB) { - if (move) - *move = getAnimMove(); - if (nextAnim) { - int nextFrame = anim->nextFrame; - anim = &level->anims[anim->nextAnimation]; - fIndexB = (nextFrame - anim->frameStart) / anim->frameRate; - } - } - *frameB = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexB * fSize) >> 1]; - } - - mat4 getJoint(int index, bool postRot = false) { - mat4 matrix; - matrix.identity(); - - matrix.translate(pos); - if (angle.y != 0.0f) matrix.rotateY(angle.y); - if (angle.x != 0.0f) matrix.rotateX(angle.x); - if (angle.z != 0.0f) matrix.rotateZ(angle.z); - - TR::Animation *anim = &level->anims[animIndex]; - TR::Model &model = getModel(); - - float t; - vec3 move(0.0f); - TR::AnimFrame *frameA, *frameB; - getFrames(&frameA, &frameB, t, animIndex, animTime, true, &move); - - TR::Node *node = (int)model.node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model.node] : NULL; - - matrix.translate(((vec3)frameA->pos).lerp(move + frameB->pos, t)); - - int sIndex = 0; - mat4 stack[20]; - - for (int i = 0; i < model.mCount; i++) { - - if (i > 0 && node) { - TR::Node &t = node[i - 1]; - - if (t.flags & 0x01) matrix = stack[--sIndex]; - if (t.flags & 0x02) stack[sIndex++] = matrix; - - ASSERT(sIndex >= 0 && sIndex < 20); - - matrix.translate(vec3(t.x, t.y, t.z)); - } - - if (i == index && !postRot) - return matrix; - - quat q; - if (animOverrideMask & (1 << i)) - q = animOverrides[i]; - else - q = lerpAngle(frameA->getAngle(i), frameB->getAngle(i), t); - matrix = matrix * mat4(q, vec3(0.0f)); - - if (i == index && postRot) - return matrix; - } - return matrix; - } - bool aim(int target, int joint, const vec4 &angleRange, quat &rot, quat *rotAbs = NULL) { if (target > -1) { TR::Entity &e = level->entities[target]; Box box = ((Controller*)e.controller)->getBoundingBox(); vec3 t = (box.min + box.max) * 0.5f; - mat4 m = getJoint(joint); + mat4 m = animation.getJoints(getMatrix(), joint); vec3 delta = (m.inverse() * t).normal(); float angleY = clampAngle(atan2(delta.x, delta.z)); @@ -233,16 +101,17 @@ struct Controller { bool insideRoom(const vec3 &pos, int room) const { TR::Room &r = level->rooms[room]; - vec3 min = vec3(r.info.x, r.info.yTop, r.info.z); - vec3 max = min + vec3(r.xSectors * 1024, r.info.yBottom - r.info.yTop, r.zSectors * 1024); + vec3 min = vec3((float)r.info.x, (float)r.info.yTop, (float)r.info.z); + vec3 max = min + vec3(float(r.xSectors * 1024), float(r.info.yBottom - r.info.yTop), float(r.zSectors * 1024)); return pos.x >= min.x && pos.x <= max.x && pos.y >= min.y && pos.y <= max.y && pos.z >= min.z && pos.z <= max.z; } - TR::Model& getModel() const { - return level->models[getEntity().modelIndex - 1]; + TR::Model* getModel() const { + int index = getEntity().modelIndex; + return index > 0 ? &level->models[index - 1] : NULL; } TR::Entity& getEntity() const { @@ -259,64 +128,6 @@ struct Controller { return getEntity().room; } - int setAnimation(int index, int frame = 0) { - animPrev = animIndex; - animIndex = index; - TR::Animation &anim = level->anims[animIndex]; - animTime = (frame <= 0 ? -frame : (frame - anim.frameStart)) / 30.0f; - ASSERT(anim.frameStart <= anim.frameEnd); - animPrevFrame = int(animTime * 30.0f) - 1; - return state = anim.state; - } - - bool canSetState(int state) { - TR::Animation *anim = &level->anims[animIndex]; - - if (state == anim->state) - return true; - - int fIndex = int(animTime * 30.0f); - - for (int i = 0; i < anim->scCount; i++) { - TR::AnimState &s = level->states[anim->scOffset + i]; - if (s.state == state) - for (int j = 0; j < s.rangesCount; j++) { - TR::AnimRange &range = level->ranges[s.rangesOffset + j]; - if (anim->frameStart + fIndex >= range.low && anim->frameStart + fIndex <= range.high) - return true; - } - } - - return false; - } - - bool setState(int state) { - TR::Animation *anim = &level->anims[animIndex]; - - if (state == anim->state) - return true; - - int fIndex = int(animTime * 30.0f); - - bool exists = false; - - for (int i = 0; i < anim->scCount; i++) { - TR::AnimState &s = level->states[anim->scOffset + i]; - if (s.state == state) { - exists = true; - for (int j = 0; j < s.rangesCount; j++) { - TR::AnimRange &range = level->ranges[s.rangesOffset + j]; - if (anim->frameStart + fIndex >= range.low && anim->frameStart + fIndex <= range.high) { - setAnimation(range.nextAnimation, range.nextFrame); - break; - } - } - } - } - - return exists; - } - int getOverlap(int fromX, int fromY, int fromZ, int toX, int toZ) const { int dx, dz; TR::Room::Sector &s = level->getSector(getEntity().room, fromX, fromZ, dx, dz); @@ -391,15 +202,7 @@ struct Controller { } virtual Box getBoundingBox() { - float t; - TR::AnimFrame *frameA, *frameB; - getFrames(&frameA, &frameB, t, animIndex, animTime, true); - - Box box(frameA->box.min().lerp(frameB->box.min(), t), frameA->box.max().lerp(frameB->box.max(), t)); - box.rotate90(getEntity().rotation.value / 0x4000); - box.min += pos; - box.max += pos; - return box; + return animation.getBoundingBox(pos, getEntity().rotation.value / 0x4000); } vec3 trace(int fromRoom, const vec3 &from, const vec3 &to, int &room, bool isCamera) { // TODO: use Bresenham @@ -439,7 +242,7 @@ struct Controller { int maxX = minX + 1024; int maxZ = minZ + 1024; - pos = vec3(clamp(px, minX, maxX), pos.y, clamp(pz, minZ, maxZ)) + boxNormal(px, pz) * 256.0f; + pos = vec3(float(clamp(px, minX, maxX)), pos.y, float(clamp(pz, minZ, maxZ))) + boxNormal(px, pz) * 256.0f; dir = (pos - from).normal(); } } else { @@ -472,7 +275,7 @@ struct Controller { if (rand() % 10 <= 6) return; playSound(TR::SND_BUBBLE, pos, Sound::Flags::PAN); } - + /* void collide() { TR::Entity &entity = getEntity(); @@ -510,7 +313,7 @@ struct Controller { } } } - + */ void activateNext() { // activate next entity (for triggers) if (!actionCommand || !actionCommand->next) { actionCommand = NULL; @@ -555,93 +358,27 @@ struct Controller { actionCommand = NULL; } - virtual bool activate(ActionCommand *cmd) { actionCommand = cmd; return true; } - virtual void doCustomCommand (int curFrame, int prevFrame) {} - virtual void updateVelocity() {} - virtual void checkRoom() {} - virtual void move() {} - virtual Stand getStand() { return STAND_AIR; } - virtual int getHeight() { return 0; } - virtual int getStateAir() { return state; } - virtual int getStateGround() { return state; } - virtual int getStateSlide() { return state; } - virtual int getStateHang() { return state; } - virtual int getStateUnderwater() { return state; } - virtual int getStateOnwater() { return state; } - virtual int getStateDeath() { return state; } - virtual int getStateDefault() { return state; } - virtual int getInputMask() { return 0; } - virtual void hit(int damage) { }; + virtual bool activate(ActionCommand *cmd) { actionCommand = cmd; return true; } + virtual void doCustomCommand (int curFrame, int prevFrame) {} + virtual void updateVelocity() {} + virtual void checkRoom() {} - virtual int getState(Stand stand) { - TR::Animation *anim = &level->anims[animIndex]; - - int state = anim->state; - - if (mask & DEATH) - state = getStateDeath(); - else if (stand == STAND_GROUND) - state = getStateGround(); - else if (stand == STAND_SLIDE) - state = getStateSlide(); - else if (stand == STAND_HANG) - state = getStateHang(); - else if (stand == STAND_AIR) - state = getStateAir(); - else if (stand == STAND_UNDERWATER) - state = getStateUnderwater(); - else - state = getStateOnwater(); - - // try to set new state - if (!setState(state)) - setState(getStateDefault()); - - return level->anims[animIndex].state; - } - - virtual void updateBegin() { - mask = getInputMask(); - state = getState(stand = getStand()); - } - - virtual void updateEnd() { - TR::Entity &e = getEntity(); - move(); + virtual void cmdOffset(const vec3 &offset) { + pos = pos + offset.rotateY(-angle.y); updateEntity(); + checkRoom(); } - virtual void updateState() {} + virtual void cmdJump(const vec3 &vel) {} + virtual void cmdKill() {} + virtual void cmdEmpty() {} - virtual vec3 getAnimMove() { - TR::Animation *anim = &level->anims[animIndex]; - int16 *ptr = &level->commands[anim->animCommand]; - - for (int i = 0; i < anim->acCount; i++) { - int cmd = *ptr++; - switch (cmd) { - case TR::ANIM_CMD_MOVE : { // cmd position - int16 sx = *ptr++; - int16 sy = *ptr++; - int16 sz = *ptr++; - return vec3((float)sx, (float)sy, (float)sz); - break; - } - case TR::ANIM_CMD_SPEED : // cmd jump speed - case TR::ANIM_CMD_SOUND : // play sound - case TR::ANIM_CMD_EFFECT : // special commands - ptr += 2; - break; - } - } - return vec3(0.0f); - } virtual void updateAnimation(bool commands) { - int frameIndex = int((animTime += Core::deltaTime) * 30.0f); - TR::Animation *anim = &level->anims[animIndex]; - bool endFrame = frameIndex > anim->frameEnd - anim->frameStart; - + animation.update(); + + TR::Animation *anim = animation; + // apply animation commands if (commands) { int16 *ptr = &level->commands[anim->animCommand]; @@ -649,50 +386,24 @@ struct Controller { for (int i = 0; i < anim->acCount; i++) { int cmd = *ptr++; switch (cmd) { - case TR::ANIM_CMD_MOVE : { // cmd position - int16 sx = *ptr++; - int16 sy = *ptr++; - int16 sz = *ptr++; - if (endFrame) { - pos = pos + vec3(sx, sy, sz).rotateY(-angle.y); - updateEntity(); - checkRoom(); - LOG("move: %d %d %d\n", (int)sx, (int)sy, (int)sz); - } - break; - } - case TR::ANIM_CMD_SPEED : { // cmd jump speed - int16 sy = *ptr++; - int16 sz = *ptr++; - if (endFrame) { - LOG("jump: %d %d\n", (int)sy, (int)sz); - velocity.x = sinf(angleExt) * sz; - velocity.y = sy; - velocity.z = cosf(angleExt) * sz; - stand = STAND_AIR; - } - break; - } - case TR::ANIM_CMD_EMPTY : // empty hands - break; - case TR::ANIM_CMD_KILL : // kill - break; - case TR::ANIM_CMD_SOUND : // play sound - case TR::ANIM_CMD_EFFECT : { // special commands - int frame = (*ptr++); - int id = (*ptr++) & 0x3FFF; - int idx = frame - anim->frameStart; - - if (idx > animPrevFrame && idx <= frameIndex) { + case TR::ANIM_CMD_OFFSET : ptr += 3; break; + case TR::ANIM_CMD_JUMP : ptr += 2; break; + case TR::ANIM_CMD_EMPTY : cmdEmpty(); break; + case TR::ANIM_CMD_KILL : cmdKill(); break; + case TR::ANIM_CMD_SOUND : + case TR::ANIM_CMD_EFFECT : { + int frame = (*ptr++) - anim->frameStart; + int fx = (*ptr++) & 0x3FFF; + if (animation.isFrameActive(frame)) { if (cmd == TR::ANIM_CMD_EFFECT) { - switch (id) { - case TR::EFFECT_ROTATE_180 : angle.y = angle.y + PI; break; + switch (fx) { + case TR::EFFECT_ROTATE_180 : angle.y = angle.y + PI; break; case TR::EFFECT_LARA_BUBBLES : doBubbles(); break; case TR::EFFECT_LARA_HANDSFREE : break; - default : LOG("unknown special cmd %d (anim %d)\n", id, animIndex); + default : LOG("unknown special cmd %d (anim %d)\n", fx, animation.index); } } else - playSound(id, pos, Sound::Flags::PAN); + playSound(fx, pos, Sound::Flags::PAN); } break; } @@ -700,21 +411,20 @@ struct Controller { } } - doCustomCommand(frameIndex, animPrevFrame); + if (animation.frameIndex != animation.framePrev) + doCustomCommand(animation.frameIndex, animation.framePrev); - if (endFrame) { // if animation is end - switch to next - setAnimation(anim->nextAnimation, anim->nextFrame); + if (animation.isEnded) { // if animation is end - switch to next + if (animation.offset != 0.0f) cmdOffset(animation.offset); + if (animation.jump != 0.0f) cmdJump(animation.jump); + animation.playNext(); activateNext(); } else - animPrevFrame = frameIndex; + animation.framePrev = animation.frameIndex; } virtual void update() { - updateBegin(); - updateState(); - updateAnimation(true); - updateVelocity(); - updateEnd(); + updateAnimation(true); } void renderMesh(const mat4 &matrix, MeshBuilder *mesh, uint32 offsetIndex) { @@ -739,139 +449,40 @@ struct Controller { mesh->renderShadowSpot(); } - virtual void render(Frustum *frustum, MeshBuilder *mesh) { - TR::Entity &entity = getEntity(); - TR::Model &model = getModel(); - - TR::Animation *anim = &level->anims[animIndex]; - - mat4 matrix(Core::mModel); + mat4 getMatrix() { + mat4 matrix; + matrix.identity(); matrix.translate(pos); - if (angle.y != 0.0f) matrix.rotateY(angle.y); + if (angle.y != 0.0f) matrix.rotateY(angle.y - (animation.flip ? PI * animation.delta : 0.0f)); if (angle.x != 0.0f) matrix.rotateX(angle.x); if (angle.z != 0.0f) matrix.rotateZ(angle.z); + return matrix; + } - float t; - vec3 move(0.0f); - TR::AnimFrame *frameA, *frameB; - getFrames(&frameA, &frameB, t, animIndex, animTime, true, &move); + virtual void render(Frustum *frustum, MeshBuilder *mesh) { // TODO: animation.calcJoints + mat4 matrix = getMatrix(); - vec3 bmin = frameA->box.min().lerp(frameB->box.min(), t); - vec3 bmax = frameA->box.max().lerp(frameB->box.max(), t); - if (frustum && !frustum->isVisible(matrix, bmin, bmax)) + Box box = animation.getBoundingBox(vec3(0, 0, 0), 0); + if (frustum && !frustum->isVisible(matrix, box.min, box.max)) return; + + TR::Entity &entity = getEntity(); + TR::Model *model = getModel(); entity.flags.rendered = true; - TR::Node *node = (int)model.node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model.node] : NULL; + mat4 joints[32]; // TODO: UBO heap + ASSERT(model->mCount <= 32); - matrix.translate(((vec3)frameA->pos).lerp(move + frameB->pos, t)); - - int sIndex = 0; - mat4 stack[20]; - - for (int i = 0; i < model.mCount; i++) { - - if (i > 0 && node) { - TR::Node &t = node[i - 1]; - - if (t.flags & 0x01) matrix = stack[--sIndex]; - if (t.flags & 0x02) stack[sIndex++] = matrix; - - ASSERT(sIndex >= 0 && sIndex < 20); - - matrix.translate(vec3(t.x, t.y, t.z)); - } - - quat q; - if (animOverrideMask & (1 << i)) - q = animOverrides[i]; - else - q = lerpAngle(frameA->getAngle(i), frameB->getAngle(i), t); - matrix = matrix * mat4(q, vec3(0.0f)); - - if (meshes) - renderMesh(matrix, mesh, meshes[i]); - else - renderMesh(matrix, mesh, model.mStart + i); - - if (joints) - joints[i] = matrix; - } + animation.getJoints(matrix, -1, true, joints); + for (int i = 0; i < model->mCount; i++) + renderMesh(joints[i], mesh, meshes ? meshes[i] : (model->mStart + i)); if (TR::castShadow(entity.type)) { TR::Level::FloorInfo info; level->getFloorInfo(entity.room, entity.x, entity.z, info, true); - renderShadow(mesh, vec3(entity.x, info.floor - 16.0f, entity.z), (bmax + bmin) * 0.5f, (bmax - bmin) * 0.8f, entity.rotation); + renderShadow(mesh, vec3(float(entity.x), info.floor - 16.0f, float(entity.z)), box.center(), box.size() * 0.8f, angle.y); } } - - quat lerpFrames(TR::AnimFrame *frameA, TR::AnimFrame *frameB, float t, int index) { - return lerpAngle(frameA->getAngle(index), frameB->getAngle(index), t); - } }; - -struct SpriteController : Controller { - - enum { - FRAME_ANIMATED = -1, - FRAME_RANDOM = -2, - }; - - int frame, flag; - bool instant; - - SpriteController(TR::Level *level, int entity, bool instant = true, int frame = FRAME_ANIMATED) : Controller(level, entity), instant(instant), flag(frame) { - if (frame >= 0) { // specific frame - this->frame = frame; - } else if (frame == FRAME_RANDOM) { // random frame - this->frame = rand() % getSequence().sCount; - } else if (frame == FRAME_ANIMATED) { // animated - this->frame = 0; - } - } - - TR::SpriteSequence& getSequence() { - return level->spriteSequences[-(getEntity().modelIndex + 1)]; - } - - void update() { - if (flag >= 0) return; - - bool remove = false; - animTime += Core::deltaTime; - - if (flag == FRAME_ANIMATED) { - frame = int(animTime * SPRITE_FPS); - TR::SpriteSequence &seq = getSequence(); - if (instant && frame >= seq.sCount) - remove = true; - else - frame %= seq.sCount; - } else - if (instant && animTime >= (1.0f / SPRITE_FPS)) - remove = true; - - if (remove) { - level->entityRemove(entity); - delete this; - } - } - - virtual void render(Frustum *frustum, MeshBuilder *mesh) { - mat4 m(Core::mModel); - m.translate(pos); - Core::active.shader->setParam(uModel, m); - mesh->renderSprite(-(getEntity().modelIndex + 1), frame); - } -}; - -void addSprite(TR::Level *level, TR::Entity::Type type, int room, int x, int y, int z, int frame = -1) { - int index = level->entityAdd(type, room, x, y, z, 0, -1); - if (index > -1) { - level->entities[index].intensity = 0x1FFF - level->rooms[room].ambient; - level->entities[index].controller = new SpriteController(level, index, true, frame); - } -} - #endif \ No newline at end of file diff --git a/src/debug.h b/src/debug.h index 4d7b0be..d8bb5ff 100644 --- a/src/debug.h +++ b/src/debug.h @@ -449,13 +449,13 @@ namespace Debug { TR::Node *node = m.node < level.nodesDataSize ? (TR::Node*)&level.nodesData[m.node] : NULL; if (!node) continue; // ??? - +/* if (e.type == m.type) { ASSERT(m.animation < 0xFFFF); int fSize = sizeof(TR::AnimFrame) + m.mCount * sizeof(uint16) * 2; - TR::Animation *anim = controller ? &level.anims[controller->animIndex] : &level.anims[m.animation]; + TR::Animation *anim = controller->animation; TR::AnimFrame *frame = (TR::AnimFrame*)&level.frameData[(anim->frameOffset + (controller ? int((controller->animTime * 30.0f / anim->frameRate)) * fSize : 0) >> 1)]; //mat4 m; @@ -495,35 +495,39 @@ namespace Debug { int offset = level.meshOffsets[m.mStart + k]; TR::Mesh *mesh = (TR::Mesh*)&level.meshData[offset / 2]; Debug::Draw::sphere(matrix * joint * mesh->center, mesh->collider.radius, mesh->collider.info ? vec4(1, 0, 0, 0.5f) : vec4(0, 1, 1, 0.5f)); - /* + { //if (e.id != 0) { char buf[255]; sprintf(buf, "(%d) radius %d info %d flags %d", e.id, (int)mesh->collider.radius, (int)mesh->collider.info, (int)mesh->collider.flags); Debug::Draw::text(matrix * joint * mesh->center, vec4(0.5, 1, 0.5, 1), buf); } - */ + } - Debug::Draw::box(matrix, frame->box.min(), frame->box.max(), vec4(1.0)); + break; } +*/ } } } - void info(const TR::Level &level, const TR::Entity &entity, int state, int anim, int frame) { + void info(const TR::Level &level, const TR::Entity &entity, Animation &anim) { char buf[255]; sprintf(buf, "DIP = %d, TRI = %d, SND = %d", Core::stats.dips, Core::stats.tris, Sound::channelsCount); Debug::Draw::text(vec2(16, 16), vec4(1.0f), buf); - sprintf(buf, "pos = (%d, %d, %d), room = %d, state = %d, anim = %d, frame = %d", entity.x, entity.y, entity.z, entity.room, state, anim, frame); + sprintf(buf, "pos = (%d, %d, %d), room = %d", entity.x, entity.y, entity.z, entity.room); Debug::Draw::text(vec2(16, 32), vec4(1.0f), buf); + int rate = anim.anims[anim.index].frameRate; + sprintf(buf, "state = %d, anim = %d, next = %d, rate = %d, frame = %.2f / %d (%f)", anim.state, anim.index, anim.next, rate, anim.time * 30.0f, anim.framesCount, anim.delta); + Debug::Draw::text(vec2(16, 48), vec4(1.0f), buf); TR::Level::FloorInfo info; level.getFloorInfo(entity.room, entity.x, entity.z, info); sprintf(buf, "floor = %d, roomBelow = %d, roomAbove = %d, height = %d", info.floorIndex, info.roomBelow, info.roomAbove, info.floor - info.ceiling); - Debug::Draw::text(vec2(16, 48), vec4(1.0f), buf); + Debug::Draw::text(vec2(16, 64), vec4(1.0f), buf); } } } diff --git a/src/enemy.h b/src/enemy.h index ad29d4d..c588cf7 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -1,26 +1,11 @@ #ifndef H_ENEMY #define H_ENEMY -#include "controller.h" +#include "character.h" -struct Enemy : Controller { - int target; - quat rotHead, rotChest; - int baseAnim; +struct Enemy : Character { - Enemy(TR::Level *level, int entity) : Controller(level, entity), target(-1) { - initAnimOverrides(); - rotHead = rotChest = quat(0, 0, 0, 1); - baseAnim = animIndex; - } - - virtual Stand getStand() { - return STAND_GROUND; - } - - virtual void hit(int damage) { - health -= damage; - }; + Enemy(TR::Level *level, int entity, int health) : Character(level, entity, health) {} virtual bool activate(ActionCommand *cmd) { Controller::activate(cmd); @@ -38,66 +23,40 @@ struct Enemy : Controller { } virtual void updateVelocity() { - TR::Animation *anim = &level->anims[animIndex]; - float speed = anim->speed + anim->accel * (animTime * 30.0f); + TR::Animation *anim = animation; + float speed = anim->speed + anim->accel * (animation.time * 30.0f); velocity = getDir() * speed; } - virtual void move() { + virtual void updatePosition() { if (!getEntity().flags.active) return; vec3 p = pos; pos += velocity * Core::deltaTime * 30.0f; TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.z, info); + level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.z, info, true); if (pos.y - info.floor > 1024) { pos = p; return; } if (stand == STAND_GROUND) - pos.y = info.floor; + pos.y = float(info.floor); updateEntity(); checkRoom(); } - virtual void checkRoom() { - TR::Level::FloorInfo info; - TR::Entity &e = getEntity(); - level->getFloorInfo(e.room, e.x, e.z, info); - - if (info.roomNext != 0xFF) - e.room = info.roomNext; - - if (info.roomBelow != 0xFF && e.y > info.floor) - e.room = info.roomBelow; - - if (info.roomAbove != 0xFF && e.y <= info.ceiling) { - if (stand == STAND_UNDERWATER && !level->rooms[info.roomAbove].flags.water) { - stand = STAND_ONWATER; - velocity.y = 0; - pos.y = info.ceiling; - updateEntity(); - } else - if (stand != STAND_ONWATER) - e.room = info.roomAbove; - } - } - void setOverrides(bool active, int chest, int head) { int mask = 0; - if (head > -1) mask |= (1 << head); - if (chest > -1) mask |= (1 << chest); + if (active && head > -1) { + animation.overrides[head] = animation.getJointRot(head); + animation.overrideMask |= (1 << head); + } else + animation.overrideMask &= ~(1 << head); - if (active) - animOverrideMask |= mask; - else - animOverrideMask &= ~mask; - - TR::AnimFrame *frameA, *frameB; - float t; - - getFrames(&frameA, &frameB, t, animIndex, animTime, true); - animOverrides[chest] = lerpFrames(frameA, frameB, t, chest); - animOverrides[head] = lerpFrames(frameA, frameB, t, head); + if (active && chest > -1) { + animation.overrides[chest] = animation.getJointRot(chest); + animation.overrideMask |= (1 << chest); + } else + animation.overrideMask &= ~(1 << chest); } void lookAt(int target, int chest, int head) { @@ -105,11 +64,11 @@ struct Enemy : Controller { quat rot; if (chest > -1) { - if (aim(target, chest, vec4(-PI * 0.4f, PI * 0.4f, -PI * 0.75f, PI * 0.75f), rot)) + if (aim(target, chest, vec4(-PI * 0.8f, PI * 0.8f, -PI * 0.75f, PI * 0.75f), rot)) rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed); else rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed); - animOverrides[chest] = rotChest * animOverrides[chest]; + animation.overrides[chest] = rotChest * animation.overrides[chest]; } if (head > -1) { @@ -117,14 +76,16 @@ struct Enemy : Controller { rotHead = rotHead.slerp(rot, speed); else rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed); - animOverrides[head] = rotHead * animOverrides[head]; + animation.overrides[head] = rotHead * animation.overrides[head]; } } - virtual int getInputMask() { + virtual int getInput() { if (target > -1) { - vec3 v = (((Controller*)level->entities[target].controller)->pos - pos).normal(); - float d = atan2(v.x, v.z) - angle.y; + vec3 a = (((Controller*)level->entities[target].controller)->pos - pos).normal(); + vec3 b = getDir(); + vec3 n = vec3(0, 1, 0); + float d = atan2(b.cross(a).dot(n), a.dot(b)); if (fabsf(d) > 0.01f) return d < 0 ? LEFT : RIGHT; } @@ -135,6 +96,9 @@ struct Enemy : Controller { #define WOLF_TURN_FAST PI #define WOLF_TURN_SLOW (PI / 3.0f) +#define WOLF_TILT_MAX (PI / 6.0f) +#define WOLF_TILT_SPEED WOLF_TILT_MAX + struct Wolf : Enemy { @@ -163,7 +127,7 @@ struct Wolf : Enemy { JOINT_HEAD = 3 }; - Wolf(TR::Level *level, int entity) : Enemy(level, entity) {} + Wolf(TR::Level *level, int entity) : Enemy(level, entity, 100) {} virtual int getStateGround() { // STATE_SLEEP -> STATE_STOP @@ -178,9 +142,9 @@ struct Wolf : Enemy { if (health <= 0) { switch (state) { - case STATE_RUN : return setAnimation(baseAnim + ANIM_DEATH_RUN); - case STATE_JUMP : return setAnimation(baseAnim + ANIM_DEATH_JUMP); - default : return setAnimation(baseAnim + ANIM_DEATH); + case STATE_RUN : return animation.setAnim(ANIM_DEATH_RUN); + case STATE_JUMP : return animation.setAnim(ANIM_DEATH_JUMP); + default : return animation.setAnim(ANIM_DEATH); } } @@ -191,7 +155,7 @@ struct Wolf : Enemy { switch (state) { case STATE_SLEEP : return STATE_STOP; case STATE_STOP : return STATE_HOWL; - case STATE_GROWL : return STATE_STALKING; + case STATE_GROWL : return randf() > 0.5f ? STATE_STALKING : STATE_RUN; case STATE_STALKING : if (health < 70) return STATE_RUN; break; } @@ -215,7 +179,7 @@ struct Wolf : Enemy { float w = 0.0f; if (state == STATE_RUN || state == STATE_STALKING) { w = state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW; - if (mask & LEFT) w = -w; + if (input & LEFT) w = -w; if (w != 0.0f) { w *= Core::deltaTime; @@ -226,12 +190,14 @@ struct Wolf : Enemy { velocity = vec3(0.0f); } - virtual void move() { + virtual void updatePosition() { + updateTilt(state == STATE_RUN, WOLF_TILT_SPEED, WOLF_TILT_MAX); + if (state == STATE_DEATH) { - animOverrideMask = 0; + animation.overrideMask = 0; return; } - Enemy::move(); + Enemy::updatePosition(); setOverrides(state == STATE_STALKING || state == STATE_RUN, JOINT_CHEST, JOINT_HEAD); lookAt(target, JOINT_CHEST, JOINT_HEAD); } @@ -244,7 +210,7 @@ struct Bear : Enemy { STATE_STOP = 1, }; - Bear(TR::Level *level, int entity) : Enemy(level, entity) {} + Bear(TR::Level *level, int entity) : Enemy(level, entity, 100) {} virtual int getStateGround() { return state; @@ -259,14 +225,14 @@ struct Bat : Enemy { STATE_FLY = 2, }; - Bat(TR::Level *level, int entity) : Enemy(level, entity) {} + Bat(TR::Level *level, int entity) : Enemy(level, entity, 100) {} virtual Stand getStand() { return STAND_AIR; } virtual int getStateAir() { - animTime = 0.0f; + animation.time = 0.0f; return STATE_AWAKE; } }; diff --git a/src/format.h b/src/format.h index 746e34b..f81b74c 100644 --- a/src/format.h +++ b/src/format.h @@ -5,13 +5,14 @@ #define MAX_RESERVED_ENTITIES 64 #define MAX_SECRETS_COUNT 16 +#define MAX_TRIGGER_COMMANDS 32 namespace TR { enum { ANIM_CMD_NONE , - ANIM_CMD_MOVE , - ANIM_CMD_SPEED , + ANIM_CMD_OFFSET , + ANIM_CMD_JUMP , ANIM_CMD_EMPTY , ANIM_CMD_KILL , ANIM_CMD_SOUND , @@ -106,6 +107,15 @@ namespace TR { SND_SECRET = 173, }; + enum { + MODEL_LARA = 0, + MODEL_PISTOLS = 1, + MODEL_SHOTGUN = 2, + MODEL_MAGNUMS = 3, + MODEL_UZIS = 4, + MODEL_LARA_SPEC = 5, + }; + enum Action : uint16 { ACTIVATE , // activate item CAMERA_SWITCH , // switch to camera @@ -419,6 +429,17 @@ namespace TR { bool isEnemy() { return type >= ENEMY_TWIN && type <= ENEMY_LARSON; } + + int isItem() { + return (type >= WEAPON_PISTOLS && type <= AMMO_UZIS) || + (type >= PUZZLE_1 && type <= PUZZLE_4) || + (type >= KEY_1 && type <= KEY_4) || + (type == MEDIKIT_SMALL || type == MEDIKIT_BIG || type == ARTIFACT || type == PICKUP); + } + + bool isBlock() { + return type >= TR::Entity::BLOCK_1 && type <= TR::Entity::BLOCK_2; + } }; struct Animation { @@ -463,11 +484,13 @@ namespace TR { vec3 max() const { return vec3((float)maxX, (float)maxY, (float)maxZ); } }; + #pragma warning( push ) + #pragma warning( disable : 4200 ) // zero-sized array warning struct AnimFrame { MinMax box; - Vertex pos; // Starting offset for this model + Vertex pos; int16 aCount; - uint16 angles[0]; // angle frames in YXZ order + uint16 angles[0]; // angle frames in YXZ order vec3 getAngle(int index) { #define ANGLE_SCALE (2.0f * PI / 1024.0f) @@ -475,7 +498,7 @@ namespace TR { uint16 b = angles[index * 2 + 0]; uint16 a = angles[index * 2 + 1]; - return vec3((a & 0x3FF0) >> 4, ( ((a & 0x000F) << 6) | ((b & 0xFC00) >> 10)), b & 0x03FF) * ANGLE_SCALE; + return vec3(float((a & 0x3FF0) >> 4), float( ((a & 0x000F) << 6) | ((b & 0xFC00) >> 10)), float(b & 0x03FF)) * ANGLE_SCALE; } }; @@ -483,6 +506,7 @@ namespace TR { int16 count; // number of texture offsets - 1 in group int16 textures[0]; // offsets into objectTextures[] }; + #pragma warning( push ) struct Node { uint32 flags; @@ -715,7 +739,7 @@ namespace TR { int trigCmdCount; Trigger trigger; FloorData::TriggerInfo trigInfo; - FloorData::TriggerCommand trigCmd[16]; + FloorData::TriggerCommand trigCmd[MAX_TRIGGER_COMMANDS]; vec3 getNormal() { return vec3((float)-slantX, -4.0f, (float)-slantZ).normal(); @@ -734,7 +758,8 @@ namespace TR { bool secrets[MAX_SECRETS_COUNT]; void *cameraController; - Level(Stream &stream, bool demo) { + Level(const char *name, bool demo) { + Stream stream(name); // read version stream.read(version); // tiles @@ -1039,6 +1064,7 @@ namespace TR { info.trigInfo = (*fd++).triggerInfo; FloorData::TriggerCommand trigCmd; do { + ASSERT(info.trigCmdCount < MAX_TRIGGER_COMMANDS); trigCmd = (*fd++).triggerCmd; // trigger action info.trigCmd[info.trigCmdCount++] = trigCmd; } while (!trigCmd.end); diff --git a/src/game.h b/src/game.h index 81526b1..e29cb48 100644 --- a/src/game.h +++ b/src/game.h @@ -12,8 +12,9 @@ namespace Game { void init() { Core::init(); - Stream stream("LEVEL2_DEMO.PHD"); - level = new Level(stream, true); + level = new Level("LEVEL2_DEMO.PHD", true, false); + //level = new Level("GYM.PHD", false, true); + //level = new Level("LEVEL4.PHD", false, false); #ifndef __EMSCRIPTEN__ //Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), 1, 1, 0); @@ -29,7 +30,13 @@ namespace Game { } void update() { + float dt = Core::deltaTime; + if (Input::down[ikR]) // slow motion (for animation debugging) + Core::deltaTime /= 50.0f; + level->update(); + + Core::deltaTime = dt; } void render() { diff --git a/src/lara.h b/src/lara.h index 73f8bfd..2c67f38 100644 --- a/src/lara.h +++ b/src/lara.h @@ -3,20 +3,23 @@ /*****************************************/ /* Desine sperare qui hic intras */ /*****************************************/ -#include "controller.h" +#include "character.h" #include "trigger.h" +#include "sprite.h" #define TURN_FAST PI #define TURN_FAST_BACK PI * 3.0f / 4.0f #define TURN_NORMAL PI / 2.0f #define TURN_SLOW PI / 3.0f -#define TILT_MAX (PI / 18.0f) -#define TILT_SPEED TILT_MAX #define TURN_WATER_FAST PI * 3.0f / 4.0f #define TURN_WATER_SLOW PI * 2.0f / 3.0f + +#define TILT_MAX (PI / 18.0f) +#define TILT_SPEED TILT_MAX + #define GLIDE_SPEED 50.0f -#define LARA_HANG_OFFSET 735 +#define LARA_HANG_OFFSET 735.0f #define PICKUP_FRAME_GROUND 40 #define PICKUP_FRAME_UNDERWATER 16 @@ -28,10 +31,7 @@ #define FLASH_LIGHT_COLOR vec4(0.8f, 0.7f, 0.3f, 2048 * 2048) #define TARGET_MAX_DIST (8.0f * 1024.0f) -struct Lara : Controller { - - ActionCommand actionList[MAX_TRIGGER_ACTIONS]; - int lastPickUp; +struct Lara : Character { // http://www.tombraiderforums.com/showthread.php?t=148859 enum { @@ -165,6 +165,8 @@ struct Lara : Controller { BODY_LOWER = BODY_HIP | BODY_LEG_L | BODY_LEG_R, }; + bool home; + struct Weapon { enum Type { EMPTY, PISTOLS, SHOTGUN, MAGNUMS, UZIS, MAX }; enum State { IS_HIDDEN, IS_ARMED, IS_FIRING }; @@ -180,38 +182,32 @@ struct Lara : Controller { struct Arm { int target; - int lastFrame; float shotTimer; - float weight; - float animDir; - float animTime; - float animMaxTime; - int animIndex; quat rot, rotAbs; - Weapon::Anim anim; + Weapon::Anim anim; + Animation animation; } arms[2]; - int target; - quat rotHead, rotChest; + ActionCommand actionList[MAX_TRIGGER_ACTIONS]; + int lastPickUp; - Lara(TR::Level *level, int entity) : Controller(level, entity), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos), target(-1) { + Lara(TR::Level *level, int entity, bool home) : Character(level, entity, 100), home(home), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos) { initMeshOverrides(); - initAnimOverrides(); for (int i = 0; i < 2; i++) { arms[i].shotTimer = MUZZLE_FLASH_TIME + 1.0f; - arms[i].animTime = 0.0f; arms[i].rot = quat(0, 0, 0, 1); arms[i].rotAbs = quat(0, 0, 0, 1); } - rotHead = rotChest = quat(0, 0, 0, 1); memset(weapons, -1, sizeof(weapons)); - weapons[Weapon::PISTOLS].ammo = 0; - weapons[Weapon::SHOTGUN].ammo = 9000; - weapons[Weapon::MAGNUMS].ammo = 9000; - weapons[Weapon::UZIS ].ammo = 9000; - - wpnSet(Weapon::PISTOLS); + if (!home) { + weapons[Weapon::PISTOLS].ammo = 0; + weapons[Weapon::SHOTGUN].ammo = 9000; + weapons[Weapon::MAGNUMS].ammo = 9000; + weapons[Weapon::UZIS ].ammo = 9000; + wpnSet(Weapon::PISTOLS); + } else + meshSwap(&level->models[TR::MODEL_LARA_SPEC], BODY_UPPER | BODY_LOWER); #ifdef _DEBUG /* // gym @@ -261,33 +257,38 @@ struct Lara : Controller { */ updateEntity(); #endif + chestOffset = animation.getJoints(getMatrix(), 7).getPos(); } void wpnSet(Weapon::Type wType) { wpnCurrent = wType; wpnState = Weapon::IS_FIRING; + + arms[0].animation = arms[1].animation = Animation(level, &level->models[wType == Weapon::SHOTGUN ? TR::MODEL_SHOTGUN : TR::MODEL_PISTOLS]); + wpnSetAnim(arms[0], Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 0.0f); wpnSetAnim(arms[1], Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 0.0f); } void wpnSetAnim(Arm &arm, Weapon::State wState, Weapon::Anim wAnim, float wAnimTime, float wAnimDir, bool playing = true) { + arm.animation.setAnim(wpnGetAnimIndex(wAnim), 0, wAnim == Weapon::Anim::FIRE); + arm.animation.dir = playing ? wAnimDir : 0.0f; + if (arm.anim != wAnim) - arm.lastFrame = 0xFFFF; + arm.animation.frameIndex = 0xFFFF; - arm.anim = wAnim; - arm.animIndex = wpnGetAnimIndex(wAnim); - TR::Animation &anim = level->anims[arm.animIndex]; - - arm.animDir = playing ? wAnimDir : 0.0f; - arm.animMaxTime = (anim.frameEnd - anim.frameStart) / 30.0f; + arm.anim = wAnim; if (wAnimDir > 0.0f) - arm.animTime = wAnimTime; + arm.animation.time = wAnimTime; else if (wAnimDir < 0.0f) - arm.animTime = arm.animMaxTime + wAnimTime; + arm.animation.time = arm.animation.timeMax + wAnimTime; + arm.animation.updateInfo(); wpnSetState(wState); + + LOG("set anim\n"); } int wpnGetDamage() { @@ -333,21 +334,21 @@ struct Lara : Controller { resetMask &= ~(BODY_LEG_L1 | BODY_LEG_R1); // restore original meshes first - meshSwap(level->models[Weapon::EMPTY], resetMask); + meshSwap(&level->models[Weapon::EMPTY], resetMask); // replace some parts - meshSwap(level->models[wpnCurrent], mask); + meshSwap(&level->models[wpnCurrent], mask); // have a shotgun in inventory place it on the back if another weapon is in use if (wpnCurrent != Weapon::SHOTGUN && weapons[Weapon::SHOTGUN].ammo != -1) - meshSwap(level->models[Weapon::SHOTGUN], BODY_CHEST); + meshSwap(&level->models[Weapon::SHOTGUN], BODY_CHEST); // mesh swap to angry Lara's head while firing (from uzis model) if (wState == Weapon::IS_FIRING) - meshSwap(level->models[Weapon::UZIS], BODY_HEAD); + meshSwap(&level->models[Weapon::UZIS], BODY_HEAD); wpnState = wState; } bool emptyHands() { - return arms[0].anim == Weapon::Anim::NONE; + return wpnCurrent == Weapon::EMPTY || arms[0].anim == Weapon::Anim::NONE; } bool canDrawWeapon() { @@ -396,8 +397,8 @@ struct Lara : Controller { if (wpnCurrent != Weapon::SHOTGUN) { wpnSetAnim(arms[0], wpnState, Weapon::Anim::PREPARE, 0.0f, 1.0f); wpnSetAnim(arms[1], wpnState, Weapon::Anim::PREPARE, 0.0f, 1.0f); - } else - wpnSetAnim(arms[0], wpnState, Weapon::Anim::UNHOLSTER, 0.0f, 1.0f); + } else + wpnSetAnim(arms[0], wpnState, Weapon::Anim::UNHOLSTER, 0.0f, 1.0f); } } @@ -418,26 +419,24 @@ struct Lara : Controller { } int wpnGetAnimIndex(Weapon::Anim wAnim) { - int baseAnim = level->models[wpnCurrent == Weapon::SHOTGUN ? Weapon::SHOTGUN : Weapon::PISTOLS].animation; - if (wpnCurrent == Weapon::SHOTGUN) { switch (wAnim) { case Weapon::Anim::PREPARE : ASSERT(false); break; // rifle has no prepare animation - case Weapon::Anim::UNHOLSTER : return baseAnim + 1; - case Weapon::Anim::HOLSTER : return baseAnim + 3; + case Weapon::Anim::UNHOLSTER : return 1; + case Weapon::Anim::HOLSTER : return 3; case Weapon::Anim::HOLD : - case Weapon::Anim::AIM : return baseAnim; - case Weapon::Anim::FIRE : return baseAnim + 2; + case Weapon::Anim::AIM : return 0; + case Weapon::Anim::FIRE : return 2; default : ; } } else switch (wAnim) { - case Weapon::Anim::PREPARE : return baseAnim + 1; - case Weapon::Anim::UNHOLSTER : return baseAnim + 2; + case Weapon::Anim::PREPARE : return 1; + case Weapon::Anim::UNHOLSTER : return 2; case Weapon::Anim::HOLSTER : ASSERT(false); break; // pistols has no holster animation (it's reversed unholster) case Weapon::Anim::HOLD : - case Weapon::Anim::AIM : return baseAnim; - case Weapon::Anim::FIRE : return baseAnim + 3; + case Weapon::Anim::AIM : return 0; + case Weapon::Anim::FIRE : return 3; default : ; } return 0; @@ -456,21 +455,25 @@ struct Lara : Controller { void wpnFire() { bool armShot[2] = { false, false }; for (int i = 0; i < 2; i++) { - int frameIndex = getFrameIndex(arms[i].animIndex, arms[i].animTime); if (arms[i].anim == Weapon::Anim::FIRE) { - if (frameIndex < arms[i].lastFrame) { - if ((mask & ACTION) && (target == -1 || (target > -1 && arms[i].target > -1))) { - armShot[i] = true; - } else - wpnSetAnim(arms[i], Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, target == -1); - } - // shotgun reload sound - if (wpnCurrent == Weapon::SHOTGUN) { - if (frameIndex >= 10 && arms[i].lastFrame < 10) - playSound(TR::SND_SHOTGUN_RELOAD, pos, Sound::Flags::PAN); + Animation &anim = arms[i].animation; + //int realFrameIndex = int(arms[i].animation.time * 30.0f / anim->frameRate) % ((anim->frameEnd - anim->frameStart) / anim->frameRate + 1); + if (anim.frameIndex != anim.framePrev) { + if (anim.frameIndex == 0) { //realFrameIndex < arms[i].animation.framePrev) { + if ((input & ACTION) && (target == -1 || (target > -1 && arms[i].target > -1))) { + armShot[i] = true; + } else + wpnSetAnim(arms[i], Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, target == -1); + } + // shotgun reload sound + if (wpnCurrent == Weapon::SHOTGUN) { + if (anim.frameIndex == 10) + playSound(TR::SND_SHOTGUN_RELOAD, pos, Sound::Flags::PAN); + } } + } - arms[i].lastFrame = frameIndex; + arms[i].animation.framePrev = arms[i].animation.frameIndex; if (wpnCurrent == Weapon::SHOTGUN) break; } @@ -486,37 +489,35 @@ struct Lara : Controller { vec3 nearPos; bool hasShot = false; - for (int i = 0; i < count; i++) { - Arm *arm; + for (int i = 0; i < count; i++) { int armIndex; if (wpnCurrent == Weapon::SHOTGUN) { if (!rightHand) continue; - arm = &arms[0]; armIndex = 0; } else { if (!(i ? leftHand : rightHand)) continue; - arm = &arms[i]; armIndex = i; } + Arm *arm = &arms[armIndex]; arm->shotTimer = 0.0f; hasShot = true; int joint = wpnCurrent == Weapon::SHOTGUN ? 8 : (i ? 11 : 8); - vec3 p = getJoint(joint, false).getPos(); + vec3 p = animation.getJoints(getMatrix(), joint, false).getPos(); vec3 d = arm->rotAbs * vec3(0, 0, 1); vec3 t = p + d * (24.0f * 1024.0f) + ((vec3(randf(), randf(), randf()) * 2.0f) - vec3(1.0f)) * 1024.0f; int room; vec3 hit = trace(getRoomIndex(), p, t, room, false); if (target > -1 && checkHit(target, p, hit, hit)) { - ((Controller*)level->entities[target].controller)->hit(wpnGetDamage()); + ((Character*)level->entities[target].controller)->hit(wpnGetDamage()); hit -= d * 64.0f; - addSprite(level, TR::Entity::BLOOD, room, (int)hit.x, (int)hit.y, (int)hit.z, SpriteController::FRAME_ANIMATED); + Sprite::add(level, TR::Entity::BLOOD, room, (int)hit.x, (int)hit.y, (int)hit.z, Sprite::FRAME_ANIMATED); } else { hit -= d * 64.0f; - addSprite(level, TR::Entity::SPARK, room, (int)hit.x, (int)hit.y, (int)hit.z, SpriteController::FRAME_RANDOM); + Sprite::add(level, TR::Entity::SPARK, room, (int)hit.x, (int)hit.y, (int)hit.z, Sprite::FRAME_RANDOM); float dist = (hit - p).length(); if (dist < nearDist) { @@ -525,7 +526,7 @@ struct Lara : Controller { } } - Core::lightPos[1 + armIndex] = getJoint(armIndex == 0 ? 10 : 13, false).getPos(); + Core::lightPos[1 + armIndex] = animation.getJoints(getMatrix(), armIndex == 0 ? 10 : 13, false).getPos(); Core::lightColor[1 + armIndex] = FLASH_LIGHT_COLOR; } @@ -551,7 +552,7 @@ struct Lara : Controller { } // apply weapon state changes - if (mask & WEAPON) { + if (input & WEAPON) { if (emptyHands()) wpnDraw(); else @@ -562,18 +563,18 @@ struct Lara : Controller { bool isRifle = wpnCurrent == Weapon::SHOTGUN; for (int i = 0; i < 2; i++) { - if (arms[i].target > -1 || ((mask & ACTION) && target == -1)) { + if (arms[i].target > -1 || ((input & ACTION) && target == -1)) { if (arms[i].anim == Weapon::Anim::HOLD) wpnSetAnim(arms[i], wpnState, Weapon::Anim::AIM, 0.0f, 1.0f); } else if (arms[i].anim == Weapon::Anim::AIM) - arms[i].animDir = -1.0f; + arms[i].animation.dir = -1.0f; if (isRifle) break; } - for (int i = 0; i < 2; i++){ - arms[i].animTime += Core::deltaTime * arms[i].animDir; + for (int i = 0; i < 2; i++) { + arms[i].animation.update(); arms[i].shotTimer += Core::deltaTime; float intensity = clamp((0.1f - arms[i].shotTimer) * 20.0f, 0.0f, 1.0f); @@ -593,23 +594,26 @@ struct Lara : Controller { for (int i = 0; i < 2; i++) { Arm &arm = arms[i]; - if (arm.animDir >= 0.0f && arm.animTime >= arm.animMaxTime) + if (!arm.animation.isEnded) continue; + + if (arm.animation.dir >= 0.0f) switch (arm.anim) { - case Weapon::Anim::PREPARE : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::UNHOLSTER, arm.animTime - arm.animMaxTime, 1.0f); break; + case Weapon::Anim::PREPARE : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::UNHOLSTER, arm.animation.time - arm.animation.timeMax, 1.0f); break; case Weapon::Anim::UNHOLSTER : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); break; - case Weapon::Anim::AIM : - if (mask & ACTION) - wpnSetAnim(arm, Weapon::IS_FIRING, Weapon::Anim::FIRE, arm.animTime - arm.animMaxTime, wpnCurrent == Weapon::UZIS ? 2.0f : 1.0f); + case Weapon::Anim::AIM : + case Weapon::Anim::FIRE : + if (input & ACTION) + wpnSetAnim(arm, Weapon::IS_FIRING, Weapon::Anim::FIRE, arm.animation.time - arm.animation.timeMax, wpnCurrent == Weapon::UZIS ? 2.0f : 1.0f); else wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, false); break; default : ; }; - if (arm.animDir < 0.0f && arm.animTime <= 0.0f) + if (arm.animation.dir < 0.0f) switch (arm.anim) { case Weapon::Anim::PREPARE : wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 1.0f, false); break; - case Weapon::Anim::UNHOLSTER : wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::PREPARE, arm.animTime, -1.0f); break; + case Weapon::Anim::UNHOLSTER : wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::PREPARE, arm.animation.time, -1.0f); break; case Weapon::Anim::AIM : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); break; default : ; }; @@ -618,66 +622,64 @@ struct Lara : Controller { void animateShotgun() { Arm &arm = arms[0]; - if (arm.animDir >= 0.0f) - switch (arm.anim) { - case Weapon::Anim::UNHOLSTER : - if (arm.animTime >= arm.animMaxTime) - wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); - else if (arm.animTime >= arm.animMaxTime * 0.3f) - wpnSetAnim(arm, Weapon::IS_ARMED, arm.anim, arm.animTime, 1.0f); - break; - case Weapon::Anim::HOLSTER : - if (arm.animTime >= arm.animMaxTime) - wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 1.0f, false); - else if (arm.animTime >= arm.animMaxTime * 0.7f) - wpnSetAnim(arm, Weapon::IS_HIDDEN, arm.anim, arm.animTime, 1.0f); - break; - case Weapon::Anim::AIM : - if (arm.animTime >= arm.animMaxTime) { - if (mask & ACTION) - wpnSetAnim(arm, Weapon::IS_FIRING, Weapon::Anim::FIRE, arm.animTime - arm.animMaxTime, 1.0f); + if (arm.animation.dir >= 0.0f) { + if (arm.animation.isEnded) { + switch (arm.anim) { + case Weapon::Anim::UNHOLSTER : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); break; + case Weapon::Anim::HOLSTER : wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 1.0f, false); break; + case Weapon::Anim::AIM : + case Weapon::Anim::FIRE : + if (input & ACTION) + wpnSetAnim(arm, Weapon::IS_FIRING, Weapon::Anim::FIRE, arm.animation.time - arm.animation.timeMax, 1.0f); else - wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, false); + wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, false); + break; + default : ; + } + } else + if (arm.animation.frameIndex != arm.animation.framePrev) { + float delta = arm.animation.time / arm.animation.timeMax; + switch (arm.anim) { + case Weapon::Anim::UNHOLSTER : if (delta >= 0.3f) wpnSetAnim(arm, Weapon::IS_ARMED, arm.anim, arm.animation.time, 1.0f); break; + case Weapon::Anim::HOLSTER : if (delta >= 0.7f) wpnSetAnim(arm, Weapon::IS_HIDDEN, arm.anim, arm.animation.time, 1.0f); break; + default : ; } - break; - default : ; - }; - - if (arm.animDir < 0.0f && arm.animTime <= 0.0f) - if (arm.anim == Weapon::Anim::AIM) + } + } else + if (arm.animation.isEnded && arm.anim == Weapon::Anim::AIM) wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); } void updateOverrides() { - // update animation overrides - TR::AnimFrame *frameA, *frameB; - float t; - // head & chest - animOverrideMask |= BODY_CHEST | BODY_HEAD; + animation.overrideMask |= BODY_CHEST | BODY_HEAD; + + animation.overrides[ 7] = animation.getJointRot( 7); + animation.overrides[14] = animation.getJointRot(14); + + /* TODO: shotgun full body animation + if (wpnCurrent == Weapon::SHOTGUN) { + animation.frameA = arms[0].animation.frameA; + animation.frameB = arms[0].animation.frameB; + animation.delta = arms[0].animation.delta; + } + */ - getFrames(&frameA, &frameB, t, animIndex, animTime, true); - animOverrides[ 7] = lerpFrames(frameA, frameB, t, 7); - animOverrides[14] = lerpFrames(frameA, frameB, t, 14); - - if (!emptyHands()) { // right arm Arm *arm = &arms[0]; - getFrames(&frameA, &frameB, t, arm->animIndex, arm->animTime); - animOverrides[ 8] = lerpFrames(frameA, frameB, t, 8); - animOverrides[ 9] = lerpFrames(frameA, frameB, t, 9); - animOverrides[10] = lerpFrames(frameA, frameB, t, 10); + animation.overrides[ 8] = arm->animation.getJointRot( 8); + animation.overrides[ 9] = arm->animation.getJointRot( 9); + animation.overrides[10] = arm->animation.getJointRot(10); // left arm if (wpnCurrent != Weapon::SHOTGUN) arm = &arms[1]; - getFrames(&frameA, &frameB, t, arm->animIndex, arm->animTime); - animOverrides[11] = lerpFrames(frameA, frameB, t, 11); - animOverrides[12] = lerpFrames(frameA, frameB, t, 12); - animOverrides[13] = lerpFrames(frameA, frameB, t, 13); + animation.overrides[11] = arm->animation.getJointRot(11); + animation.overrides[12] = arm->animation.getJointRot(12); + animation.overrides[13] = arm->animation.getJointRot(13); - animOverrideMask |= BODY_ARM_R | BODY_ARM_L; + animation.overrideMask |= (BODY_ARM_R | BODY_ARM_L); } else - animOverrideMask &= ~(BODY_ARM_R | BODY_ARM_L); + animation.overrideMask &= ~(BODY_ARM_R | BODY_ARM_L); lookAt(target); @@ -687,7 +689,7 @@ struct Lara : Controller { aimPistols(); } - void lookAt(int target) { + void lookAt(int target) { // TODO: character lookAt float speed = 8.0f * Core::deltaTime; quat rot; @@ -696,14 +698,14 @@ struct Lara : Controller { rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed); else rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed); - animOverrides[7] = rotChest * animOverrides[7]; + animation.overrides[7] = rotChest * animation.overrides[7]; // head if (aim(target, 14, vec4(-PI * 0.25f, PI * 0.25f, -PI * 0.5f, PI * 0.5f), rot)) rotHead = rotHead.slerp(rot, speed); else rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed); - animOverrides[14] = rotHead * animOverrides[14]; + animation.overrides[14] = rotHead * animation.overrides[14]; } void aimShotgun() { @@ -730,12 +732,12 @@ struct Lara : Controller { if (arm.anim == Weapon::Anim::FIRE) t = 1.0f; else if (arm.anim == Weapon::Anim::AIM) - t = arm.animTime / arm.animMaxTime; + t = arm.animation.time / arm.animation.timeMax; else t = 0.0f; arm.rot = arm.rot.slerp(rot, speed); - animOverrides[joint] = animOverrides[joint].slerp(arm.rot * animOverrides[joint], t); + animation.overrides[joint] = animation.overrides[joint].slerp(arm.rot * animation.overrides[joint], t); } } @@ -745,13 +747,13 @@ struct Lara : Controller { return; } - if (!(mask & ACTION)) { + if (!(input & ACTION)) { target = getTarget(); arms[0].target = arms[1].target = target; } else if (target > -1) { TR::Entity &e = level->entities[target]; - vec3 to = vec3(e.x, e.y, e.z); + vec3 to = ((Controller*)e.controller)->pos; vec3 from = pos - vec3(0, 512, 0); arms[0].target = arms[1].target = checkOcclusion(from, to, (to - from).length()) ? target : -1; } @@ -759,20 +761,20 @@ struct Lara : Controller { int getTarget() { vec3 dir = getDir().normal(); - int dist = TARGET_MAX_DIST;// * TARGET_MAX_DIST; + float dist = TARGET_MAX_DIST;// * TARGET_MAX_DIST; int index = -1; for (int i = 0; i < level->entitiesCount; i++) { TR::Entity &e = level->entities[i]; if (!e.flags.active || !e.isEnemy()) continue; - Controller *controller = (Controller*)e.controller; + Character *controller = (Character*)e.controller; if (controller->health <= 0) continue; vec3 p = controller->pos; vec3 v = p - pos; if (dir.dot(v.normal()) <= 0.5f) continue; // target is out of sight -60..+60 degrees - int d = v.length(); + float d = v.length(); if (d < dist && checkOcclusion(pos - vec3(0, 512, 0), p, d) ) { index = i; dist = d; @@ -801,7 +803,11 @@ struct Lara : Controller { return false; } - bool waterOut(int &outState) { + virtual void cmdEmpty() { + wpnHide(); + } + + bool waterOut() { // TODO: playSound 36 vec3 dst = pos + getDir() * 32.0f; @@ -811,15 +817,9 @@ struct Lara : Controller { int h = int(pos.y - infoDst.floor); - if (h > 0 && h <= 256) { // possibility check - if (!setState(STATE_STOP)) { // can't set water out state - outState = STATE_STOP; - return true; - } - outState = state; - + if (h > 0 && h <= 256 && (state == STATE_SURF_TREAD || animation.setState(STATE_SURF_TREAD)) && animation.setState(STATE_STOP)) { // possibility check alignToWall(); - dst.y -= pos.y - infoDst.floor; + dst.y -= pos.y - infoDst.floor; pos = dst; // set new position getEntity().room = infoCur.roomAbove; @@ -840,38 +840,16 @@ struct Lara : Controller { if (abs(item.x - e.x) > 256 || abs(item.z - e.z) > 256) continue; - switch (item.type) { - case TR::Entity::WEAPON_PISTOLS : - case TR::Entity::WEAPON_SHOTGUN : - case TR::Entity::WEAPON_MAGNUMS : - case TR::Entity::WEAPON_UZIS : - case TR::Entity::AMMO_SHOTGUN : - case TR::Entity::AMMO_MAGNUMS : - case TR::Entity::AMMO_UZIS : - case TR::Entity::MEDIKIT_SMALL : - case TR::Entity::MEDIKIT_BIG : - case TR::Entity::PUZZLE_1 : - case TR::Entity::PUZZLE_2 : - case TR::Entity::PUZZLE_3 : - case TR::Entity::PUZZLE_4 : - case TR::Entity::PICKUP : - case TR::Entity::KEY_1 : - case TR::Entity::KEY_2 : - case TR::Entity::KEY_3 : - case TR::Entity::KEY_4 : - case TR::Entity::ARTIFACT : - lastPickUp = i; - angle.x = 0.0f; - pos.x = item.x; - pos.y = item.y; - pos.z = item.z; - if (stand == STAND_UNDERWATER) { // TODO: lerp to pos/angle - pos = pos - getDir() * 256.0f; - pos.y -= 256; - } - updateEntity(); - return true; - default : ; + if (item.isItem()) { + lastPickUp = i; + angle.x = 0.0f; + pos = ((Controller*)item.controller)->pos; + if (stand == STAND_UNDERWATER) { // TODO: lerp to pos/angle + pos -= getDir() * 256.0f; + pos.y -= 256; + } + updateEntity(); + return true; } } } @@ -882,7 +860,7 @@ struct Lara : Controller { return fabsf(shortAngle(rotation, getEntity().rotation)) < PI * 0.25f; } - void doTrigger() { + void checkTrigger() { if (actionCommand) return; TR::Entity &e = getEntity(); @@ -890,7 +868,7 @@ struct Lara : Controller { level->getFloorInfo(e.room, e.x, e.z, info); if (!info.trigCmdCount) return; // has no trigger - bool isActive = (level->entities[info.trigCmd[0].args].flags.active); + bool isActive = level->entities[info.trigCmd[0].args].flags.active != 0; if (info.trigInfo.once == 1 && isActive) return; // once trigger is already activated int actionState = state; @@ -903,14 +881,14 @@ struct Lara : Controller { break; case TR::Level::Trigger::SWITCH : actionState = (isActive && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN; - if ((mask & ACTION) == 0 || state == actionState || !emptyHands()) + if ((input & ACTION) == 0 || state == actionState || !emptyHands()) return; if (!checkAngle(level->entities[info.trigCmd[0].args].rotation)) return; break; case TR::Level::Trigger::KEY : actionState = STATE_USE_KEY; - if (isActive || (mask & ACTION) == 0 || state == actionState || !emptyHands()) // TODO: STATE_USE_PUZZLE + if (isActive || (input & ACTION) == 0 || state == actionState || !emptyHands()) // TODO: STATE_USE_PUZZLE return; if (!checkAngle(level->entities[info.trigCmd[0].args].rotation)) return; @@ -925,13 +903,13 @@ struct Lara : Controller { } // try to activate Lara state - if (!setState(actionState)) return; + if (!animation.setState(actionState)) return; if (info.trigger == TR::Level::Trigger::SWITCH || info.trigger == TR::Level::Trigger::KEY) { TR::Entity &p = level->entities[info.trigCmd[0].args]; angle.y = p.rotation; angle.x = 0; - pos = vec3(p.x, p.y, p.z) + vec3(sinf(angle.y), 0, cosf(angle.y)) * (stand == STAND_GROUND ? 384 : 128); + pos = ((Controller*)p.controller)->pos + vec3(sinf(angle.y), 0, cosf(angle.y)) * (stand == STAND_GROUND ? 384.0f : 128.0f); velocity = vec3(0.0f); updateEntity(); } @@ -973,7 +951,7 @@ struct Lara : Controller { vec3 offset = chestOffset; if (stand != STAND_UNDERWATER) offset.y -= 256.0f; - if (wpnState != Weapon::IS_HIDDEN) + if (!emptyHands()) offset.y -= 256.0f; return offset; @@ -981,9 +959,9 @@ struct Lara : Controller { virtual Stand getStand() { if (state == STATE_HANG || state == STATE_HANG_LEFT || state == STATE_HANG_RIGHT) { - if (mask & ACTION) + if (input & ACTION) return STAND_HANG; - setAnimation(ANIM_HANG_FALL); + animation.setAnim(ANIM_HANG_FALL); velocity = vec3(0.0f); pos.y += 128.0f; updateEntity(); @@ -1009,7 +987,7 @@ struct Lara : Controller { if (e.y + 8 >= info.floor && (abs(info.slantX) > 2 || abs(info.slantZ) > 2)) { if (stand == STAND_AIR) playSound(TR::SND_LANDING, pos, Sound::Flags::PAN); - pos.y = info.floor; + pos.y = float(info.floor); updateEntity(); if (stand == STAND_GROUND || stand == STAND_AIR) @@ -1023,7 +1001,7 @@ struct Lara : Controller { if (e.y + extra >= info.floor) { if (stand != STAND_GROUND) { - pos.y = info.floor; + pos.y = float(info.floor); updateEntity(); } return STAND_GROUND; @@ -1044,7 +1022,7 @@ struct Lara : Controller { if (state == STATE_REACH && getDir().dot(vec3(velocity.x, 0.0f, velocity.z)) < 0) velocity.x = velocity.z = 0.0f; - if ((state == STATE_REACH || state == STATE_UP_JUMP) && (mask & ACTION) && emptyHands()) { + if ((state == STATE_REACH || state == STATE_UP_JUMP) && (input & ACTION) && emptyHands()) { if (state == STATE_REACH && velocity.y < 0.0f) return state; @@ -1071,34 +1049,30 @@ struct Lara : Controller { if (state == STATE_REACH) { return STATE_HANG; // TODO: ANIM_HANG_WALL / ANIM_HANG_NOWALL } else - return setAnimation(ANIM_HANG, -15); + return animation.setAnim(ANIM_HANG, -15); } } if (state == STATE_FORWARD_JUMP) { if (emptyHands()) { - if (mask & ACTION) return STATE_REACH; - if ((mask & (FORTH | WALK)) == (FORTH | WALK)) return STATE_SWAN_DIVE; + if (input & ACTION) return STATE_REACH; + if ((input & (FORTH | WALK)) == (FORTH | WALK)) return STATE_SWAN_DIVE; } } else if (state != STATE_SWAN_DIVE && state != STATE_REACH && state != STATE_FALL && state != STATE_UP_JUMP && state != STATE_BACK_JUMP && state != STATE_LEFT_JUMP && state != STATE_RIGHT_JUMP) - return setAnimation(ANIM_FALL); + return animation.setAnim(ANIM_FALL); return state; } - float distTo(const TR::Entity &e) { - return (pos - vec3(e.x, e.y, e.z)).length(); - } - Block* getBlock() { - int x = getEntity().x; - int y = getEntity().y; - int z = getEntity().z; + int x = (int)pos.x; + int y = (int)pos.y; + int z = (int)pos.z; for (int i = 0; i < level->entitiesCount; i++) { TR::Entity &e = level->entities[i]; - if ((e.type == TR::Entity::BLOCK_1 || e.type == TR::Entity::BLOCK_2) && e.y == y) { + if (e.isBlock() && e.y == y) { int dx = abs(e.x - x); int dz = abs(e.z - z); if ((dx <= (512 + 128) && dz <= (512 - 128)) || @@ -1119,39 +1093,39 @@ struct Lara : Controller { virtual int getStateGround() { angle.x = 0.0f; - if ((mask & ACTION) && emptyHands() && doPickUp()) + if ((input & ACTION) && emptyHands() && doPickUp()) return STATE_PICK_UP; - if ( (mask & (FORTH | ACTION)) == (FORTH | ACTION) && (animIndex == ANIM_STAND || animIndex == ANIM_STAND_NORMAL) && emptyHands()) { + if ((input & (FORTH | ACTION)) == (FORTH | ACTION) && (animation.index == ANIM_STAND || animation.index == ANIM_STAND_NORMAL) && emptyHands()) { // TODO: get rid of animation.index vec3 p = pos + getDir() * 64.0f; TR::Level::FloorInfo info; level->getFloorInfo(getRoomIndex(), (int)p.x, (int)p.z, info, true); int h = (int)pos.y - info.floor; - int aIndex = animIndex; + int aIndex = animation.index; if (info.floor == info.ceiling || h < 256 + 128) { ; // do nothing } else if (h <= 2 * 256 + 128) { aIndex = ANIM_CLIMB_2; - pos.y = info.floor + 512; + pos.y = info.floor + 512.0f; } else if (h <= 3 * 256 + 128) { aIndex = ANIM_CLIMB_3; - pos.y = info.floor + 768; + pos.y = info.floor + 768.0f; } else if (h <= 7 * 256 + 128) aIndex = ANIM_CLIMB_JUMP; - if (aIndex != animIndex) { + if (aIndex != animation.index) { alignToWall(); - return setAnimation(aIndex); + return animation.setAnim(aIndex); } } - if ( (mask & (FORTH | BACK)) == (FORTH | BACK) && (state == STATE_STOP || state == STATE_RUN) ) - return setAnimation(ANIM_STAND_ROLL_BEGIN); + if ( (input & (FORTH | BACK)) == (FORTH | BACK) && (state == STATE_STOP || state == STATE_RUN) ) + return animation.setAnim(ANIM_STAND_ROLL_BEGIN); // ready to jump if (state == STATE_COMPRESS) { - switch (mask & (RIGHT | LEFT | FORTH | BACK)) { + switch (input & (RIGHT | LEFT | FORTH | BACK)) { case RIGHT : return STATE_RIGHT_JUMP; case LEFT : return STATE_LEFT_JUMP; case FORTH : return STATE_FORWARD_JUMP; @@ -1161,30 +1135,30 @@ struct Lara : Controller { } // jump button is pressed - if (mask & JUMP) { - if ((mask & FORTH) && state == STATE_FORWARD_JUMP) + if (input & JUMP) { + if ((input & FORTH) && state == STATE_FORWARD_JUMP) return STATE_RUN; if (state == STATE_RUN) return STATE_FORWARD_JUMP; - if (animIndex == ANIM_SLIDE_BACK) + if (animation.index == ANIM_SLIDE_BACK) // TODO: animation index? %) return STATE_SLIDE_BACK; return STATE_COMPRESS; } // walk button is pressed - if (mask & WALK) { - if (mask & FORTH) return STATE_WALK; - if (mask & BACK) return STATE_BACK; - if (mask & LEFT) return STATE_STEP_LEFT; - if (mask & RIGHT) return STATE_STEP_RIGHT; + if (input & WALK) { + if (input & FORTH) return STATE_WALK; + if (input & BACK) return STATE_BACK; + if (input & LEFT) return STATE_STEP_LEFT; + if (input & RIGHT) return STATE_STEP_RIGHT; return STATE_STOP; } - if ((mask & ACTION) && emptyHands()) { - if (state == STATE_PUSH_PULL_READY && (mask & (FORTH | BACK))) { - int pushState = (mask & FORTH) ? STATE_PUSH_BLOCK : STATE_PULL_BLOCK; + if ((input & ACTION) && emptyHands()) { + if (state == STATE_PUSH_PULL_READY && (input & (FORTH | BACK))) { + int pushState = (input & FORTH) ? STATE_PUSH_BLOCK : STATE_PULL_BLOCK; Block *block = getBlock(); - if (canSetState(pushState) && block->doMove(mask & FORTH)) { + if (animation.canSetState(pushState) && block->doMove((input & FORTH) != 0)) { alignToWall(128.0f); return pushState; } @@ -1195,14 +1169,14 @@ struct Lara : Controller { } // only dpad buttons pressed - if (mask & FORTH) return STATE_RUN; - if (mask & BACK) return STATE_FAST_BACK; - if (mask & (LEFT | RIGHT)) { + if (input & FORTH) return STATE_RUN; + if (input & BACK) return STATE_FAST_BACK; + if (input & (LEFT | RIGHT)) { if (state == STATE_FAST_TURN) return state; - if (mask & LEFT) return (state == STATE_TURN_LEFT && animPrev == animIndex) ? STATE_FAST_TURN : STATE_TURN_LEFT; - if (mask & RIGHT) return (state == STATE_TURN_RIGHT && animPrev == animIndex) ? STATE_FAST_TURN : STATE_TURN_RIGHT; + if (input & LEFT) return (state == STATE_TURN_LEFT && animation.prev == animation.index) ? STATE_FAST_TURN : STATE_TURN_LEFT; + if (input & RIGHT) return (state == STATE_TURN_RIGHT && animation.prev == animation.index) ? STATE_FAST_TURN : STATE_TURN_RIGHT; } return STATE_STOP; @@ -1232,37 +1206,37 @@ struct Lara : Controller { angle.y = dir; updateEntity(); - setAnimation(aIndex); + animation.setAnim(aIndex); } } virtual int getStateSlide() { - if (mask & JUMP) + if (input & JUMP) return state == STATE_SLIDE ? STATE_FORWARD_JUMP : STATE_BACK_JUMP; return state; } virtual int getStateHang() { - if (mask & LEFT) return STATE_HANG_LEFT; - if (mask & RIGHT) return STATE_HANG_RIGHT; - if (mask & FORTH) { + if (input & LEFT) return STATE_HANG_LEFT; + if (input & RIGHT) return STATE_HANG_RIGHT; + if (input & FORTH) { // possibility check TR::Level::FloorInfo info; vec3 p = pos + getDir() * 128.0f; level->getFloorInfo(getRoomIndex(), (int)p.x, (int)p.z, info, true); if (info.floor - info.ceiling >= 768) - return (mask & WALK) ? STATE_HANDSTAND : STATE_HANG_UP; + return (input & WALK) ? STATE_HANDSTAND : STATE_HANG_UP; } return STATE_HANG; } virtual int getStateUnderwater() { - if (mask == ACTION && doPickUp()) + if (input == ACTION && doPickUp()) return STATE_PICK_UP; if (state == STATE_FORWARD_JUMP || state == STATE_UP_JUMP || state == STATE_BACK_JUMP || state == STATE_LEFT_JUMP || state == STATE_RIGHT_JUMP || state == STATE_FALL || state == STATE_REACH) { - addSprite(level, TR::Entity::Type::WATER_SPLASH, getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z); - return setAnimation(ANIM_WATER_FALL); + Sprite::add(level, TR::Entity::Type::WATER_SPLASH, getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z); + return animation.setAnim(ANIM_WATER_FALL); // TODO: wronng animation } if (state == STATE_SWAN_DIVE) { @@ -1270,7 +1244,7 @@ struct Lara : Controller { return STATE_DIVE; } - if (mask & JUMP) return STATE_SWIM; + if (input & JUMP) return STATE_SWIM; return (state == STATE_SWIM || velocity.y > GLIDE_SPEED) ? STATE_GLIDE : STATE_TREAD; } @@ -1281,23 +1255,23 @@ struct Lara : Controller { if (state == STATE_WATER_OUT) return state; if (state != STATE_SURF_TREAD && state != STATE_SURF_LEFT && state != STATE_SURF_RIGHT && state != STATE_SURF_SWIM && state != STATE_SURF_BACK && state != STATE_STOP) - return setAnimation(ANIM_TO_ONWATER); + return animation.setAnim(ANIM_TO_ONWATER); - if (mask & FORTH) { - if (mask & JUMP) { + if (input & FORTH) { + if (input & JUMP) { angle.x = -PI * 0.25f; - return setAnimation(ANIM_TO_UNDERWATER); + return animation.setAnim(ANIM_TO_UNDERWATER); } - if ((mask & ACTION) && waterOut(state)) return state; + if ((input & ACTION) && waterOut()) return state; return STATE_SURF_SWIM; } - if (mask & BACK) return STATE_SURF_BACK; - if (mask & WALK) { - if (mask & LEFT) return STATE_SURF_LEFT; - if (mask & RIGHT) return STATE_SURF_RIGHT; + if (input & BACK) return STATE_SURF_BACK; + if (input & WALK) { + if (input & LEFT) return STATE_SURF_LEFT; + if (input & RIGHT) return STATE_SURF_RIGHT; } return STATE_SURF_TREAD; } @@ -1307,7 +1281,7 @@ struct Lara : Controller { } virtual int getStateDefault() { - if (state == STATE_DIVE || (state == STATE_RUN && (mask & JUMP)) ) return state; + if (state == STATE_DIVE || (state == STATE_RUN && (input & JUMP)) ) return state; switch (stand) { case STAND_GROUND : return STATE_STOP; case STAND_HANG : return STATE_HANG; @@ -1318,78 +1292,42 @@ struct Lara : Controller { return STATE_FALL; } - virtual int getInputMask() { - mask = 0; + virtual int getInput() { + input = 0; int &p = Input::joy.POV; - if (Input::down[ikW] || Input::down[ikUp] || p == 8 || p == 1 || p == 2) mask |= FORTH; - if (Input::down[ikD] || Input::down[ikRight] || p == 2 || p == 3 || p == 4) mask |= RIGHT; - if (Input::down[ikS] || Input::down[ikDown] || p == 4 || p == 5 || p == 6) mask |= BACK; - if (Input::down[ikA] || Input::down[ikLeft] || p == 6 || p == 7 || p == 8) mask |= LEFT; - if (Input::down[ikJoyB]) mask = FORTH | BACK; // roll - if (Input::down[ikJoyRT] || Input::down[ikX]) mask = WALK | RIGHT; // step right - if (Input::down[ikJoyLT] || Input::down[ikZ]) mask = WALK | LEFT; // step left - if (Input::down[ikSpace] || Input::down[ikJoyX]) mask |= JUMP; - if (Input::down[ikShift] || Input::down[ikJoyLB]) mask |= WALK; - if (Input::down[ikE] || Input::down[ikCtrl] || Input::down[ikJoyA]) mask |= ACTION; - if (Input::down[ikQ] || Input::down[ikAlt] || Input::down[ikJoyY]) mask |= WEAPON; - if (health <= 0) mask = DEATH; - return mask; + if (Input::down[ikW] || Input::down[ikUp] || p == 8 || p == 1 || p == 2) input |= FORTH; + if (Input::down[ikD] || Input::down[ikRight] || p == 2 || p == 3 || p == 4) input |= RIGHT; + if (Input::down[ikS] || Input::down[ikDown] || p == 4 || p == 5 || p == 6) input |= BACK; + if (Input::down[ikA] || Input::down[ikLeft] || p == 6 || p == 7 || p == 8) input |= LEFT; + if (Input::down[ikJoyB]) input = FORTH | BACK; // roll + if (Input::down[ikJoyRT] || Input::down[ikX]) input = WALK | RIGHT; // step right + if (Input::down[ikJoyLT] || Input::down[ikZ]) input = WALK | LEFT; // step left + if (Input::down[ikSpace] || Input::down[ikJoyX]) input |= JUMP; + if (Input::down[ikShift] || Input::down[ikJoyLB]) input |= WALK; + if (Input::down[ikE] || Input::down[ikCtrl] || Input::down[ikJoyA]) input |= ACTION; + if (Input::down[ikQ] || Input::down[ikAlt] || Input::down[ikJoyY]) input |= WEAPON; + if (health <= 0) input = DEATH; + return input; } - virtual void updateState() { - doTrigger(); - - TR::Animation *anim = &level->anims[animIndex]; - - int fCount = anim->frameEnd - anim->frameStart; - int fIndex = int(animTime * 30.0f); - - float rot = 0.0f; - -#ifdef _DEBUG - // show state transitions for current animation - static bool lState = false; - if (Input::down[ikEnter]) { - if (!lState) { - lState = true; - - static int snd_id = 81; - playSound(snd_id, pos, 0); - - LOG("sound: %d\n", snd_id++); - /* - LOG("state: %d\n", anim->state); - for (int i = 0; i < anim->scCount; i++) { - auto &sc = level->states[anim->scOffset + i]; - LOG("-> %d : ", (int)sc.state); - for (int j = 0; j < sc.rangesCount; j++) { - TR::AnimRange &range = level->ranges[sc.rangesOffset + j]; - LOG("%d ", range.nextAnimation); - } - LOG("\n"); - } - */ + virtual void doCustomCommand(int curFrame, int prevFrame) { + if (state == STATE_PICK_UP) { + if (!level->entities[lastPickUp].flags.invisible) { + int pickupFrame = stand == STAND_GROUND ? PICKUP_FRAME_GROUND : PICKUP_FRAME_UNDERWATER; + if (curFrame >= pickupFrame) + level->entities[lastPickUp].flags.invisible = true; // TODO: add to inventory } - - } else - lState = false; -#endif - // calculate turn tilt - if (state == STATE_RUN && (mask & (LEFT | RIGHT)) && (tilt == 0.0f || (tilt < 0.0f && (mask & LEFT)) || (tilt > 0.0f && (mask & RIGHT)))) { - if (mask & LEFT) tilt -= TILT_SPEED * Core::deltaTime; - if (mask & RIGHT) tilt += TILT_SPEED * Core::deltaTime; - } else - if (fabsf(tilt) > 0.01f) - tilt -= sign(tilt) * TILT_SPEED * 4.0f * Core::deltaTime; - else - tilt = 0.0f; - tilt = clamp(tilt, -TILT_MAX, TILT_MAX); - - angle.z = tilt; + } + } - + virtual void updateAnimation(bool commands) { + Controller::updateAnimation(commands); + updateWeapon(); + } + + virtual void updateVelocity() { // get turning angle - float w = (mask & LEFT) ? -1.0f : ((mask & RIGHT) ? 1.0f : 0.0f); + float w = (input & LEFT) ? -1.0f : ((input & RIGHT) ? 1.0f : 0.0f); if (state == STATE_SWIM || state == STATE_GLIDE) w *= TURN_WATER_FAST; @@ -1411,12 +1349,12 @@ struct Lara : Controller { if (w != 0.0f) { w *= Core::deltaTime; angle.y += w; - velocity = velocity.rotateY(-w); + velocity = velocity.rotateY(-w); // in-air velocity rotation control } // pitch (underwater only) - if (stand == STAND_UNDERWATER && (mask & (FORTH | BACK)) ) { - angle.x += ((mask & FORTH) ? -TURN_WATER_SLOW : TURN_WATER_SLOW) * 0.5f * Core::deltaTime; + if (stand == STAND_UNDERWATER && (input & (FORTH | BACK)) ) { + angle.x += ((input & FORTH) ? -TURN_WATER_SLOW : TURN_WATER_SLOW) * 0.5f * Core::deltaTime; angle.x = clamp(angle.x, -PI * 0.5f, PI * 0.5f); } @@ -1443,26 +1381,6 @@ struct Lara : Controller { angleExt += PI * 0.5f; break; } - } - - - virtual void doCustomCommand(int curFrame, int prevFrame) { - if (state == STATE_PICK_UP) { - if (!level->entities[lastPickUp].flags.invisible) { - int pickupFrame = stand == STAND_GROUND ? PICKUP_FRAME_GROUND : PICKUP_FRAME_UNDERWATER; - if (curFrame >= pickupFrame) - level->entities[lastPickUp].flags.invisible = true; // TODO: add to inventory - } - } - } - - virtual void updateAnimation(bool commands) { - Controller::updateAnimation(commands); - updateWeapon(); - } - - virtual void updateVelocity() { - TR::Animation *anim = &level->anims[animIndex]; switch (stand) { case STAND_AIR : @@ -1482,8 +1400,8 @@ struct Lara : Controller { speed = 15.0f; break; default : - speed = anim->speed + anim->accel * (animTime * 30.0f); - if (animIndex == ANIM_STAND_ROLL_END) + speed = animation.getSpeed(); + if (animation.index == ANIM_STAND_ROLL_END) speed = -speed; } @@ -1509,7 +1427,7 @@ struct Lara : Controller { } case STAND_UNDERWATER : { float speed = 0.0f; - if (animIndex == ANIM_TO_UNDERWATER) + if (animation.index == ANIM_TO_UNDERWATER) speed = 15.0f; if (state == STATE_SWIM) speed = 35.0f; @@ -1524,32 +1442,16 @@ struct Lara : Controller { } } - virtual void checkRoom() { - TR::Level::FloorInfo info; - TR::Entity &e = getEntity(); - level->getFloorInfo(e.room, e.x, e.z, info); + virtual void updatePosition() { // TODO: sphere / bbox collision + updateTilt(state == STATE_RUN, TILT_SPEED, TILT_MAX); - if (info.roomNext != 0xFF) - e.room = info.roomNext; + if (velocity.length() >= 0.001f) + move(); - if (info.roomBelow != 0xFF && e.y > info.floor) - e.room = info.roomBelow; - - if (info.roomAbove != 0xFF && e.y <= info.ceiling) { - if (stand == STAND_UNDERWATER && !level->rooms[info.roomAbove].flags.water) { - stand = STAND_ONWATER; - velocity.y = 0; - pos.y = info.ceiling; - updateEntity(); - } else - if (stand != STAND_ONWATER) - e.room = info.roomAbove; - } + checkTrigger(); } - virtual void move() { // TODO: sphere / bbox collision - if (velocity.length() < 0.001f) return; - + void move() { vec3 offset = velocity * Core::deltaTime * 30.0f; vec3 p = pos; @@ -1560,11 +1462,12 @@ struct Lara : Controller { level->getFloorInfo(e.room, (int)pos.x, (int)pos.z, info, true); // get frame to get height - TR::Animation *anim = &level->anims[animIndex]; + TR::Animation *anim = animation; bool canPassGap = (info.floor - info.ceiling) >= (stand == STAND_GROUND ? 768 : 512); float f = info.floor - pos.y; float c = pos.y - info.ceiling; + /* Box eBox = Box(pos - vec3(128.0f, 0.0f, 128.0f), pos + vec3(128.0, getHeight(), 128.0f)); // getBoundingBox(); // check static meshes in the room @@ -1601,12 +1504,9 @@ struct Lara : Controller { */ if (canPassGap) switch (stand) { - case STAND_AIR : { - int fSize = sizeof(TR::AnimFrame) + getModel().mCount * sizeof(uint16) * 2; - TR::AnimFrame *frame = (TR::AnimFrame*)&level->frameData[((anim->frameOffset + (int(animTime * 30.0f / anim->frameRate) * fSize)) >> 1)]; - - f = info.floor - (pos.y + frame->box.maxY); - c = (pos.y + frame->box.minY) - info.ceiling; + case STAND_AIR : { + f = info.floor - (pos.y + animation.frameA->box.maxY); + c = (pos.y + animation.frameA->box.minY) - info.ceiling; canPassGap = f >= -256; if (canPassGap && c < 0) { if (c > -256) { // position correction for ceiling step (less than 256) @@ -1619,7 +1519,7 @@ struct Lara : Controller { } break; } - case STAND_GROUND : { + case STAND_GROUND : { if (state == STATE_WALK || state == STATE_BACK) canPassGap = fabsf(f) <= (256.0f + 128.0f); else @@ -1647,7 +1547,7 @@ struct Lara : Controller { default : ; } - bool isLeftFoot = (anim->frameEnd - anim->frameStart) / 2 > int(animTime * 30.0f); + bool isLeftFoot = animation.framesCount / 2 > animation.frameIndex; if (!canPassGap) { pos = p; // TODO: use smart ejection @@ -1661,7 +1561,7 @@ struct Lara : Controller { } if (velocity.x != 0.0f || velocity.z != 0.0f) { - setAnimation(ANIM_SMASH_JUMP); + animation.setAnim(ANIM_SMASH_JUMP); velocity.x = -velocity.x * 0.5f; velocity.z = -velocity.z * 0.5f; if (offset.y < 0.0f) @@ -1674,11 +1574,11 @@ struct Lara : Controller { case STAND_GROUND : case STAND_HANG : if (f <= -(256 * 3 - 128) && state == STATE_RUN) - setAnimation(isLeftFoot ? ANIM_SMASH_RUN_LEFT : ANIM_SMASH_RUN_RIGHT); + animation.setAnim(isLeftFoot ? ANIM_SMASH_RUN_LEFT : ANIM_SMASH_RUN_RIGHT); else if (stand == STAND_HANG) - setAnimation(ANIM_HANG, -21); + animation.setAnim(ANIM_HANG, -21); else if (state != STATE_ROLL_1 && state != STATE_ROLL_2) - setAnimation(ANIM_STAND); + animation.setAnim(ANIM_STAND); velocity.x = velocity.z = 0.0f; break; default : ;// no smash animation @@ -1689,21 +1589,21 @@ struct Lara : Controller { if (h >= 256 && state == STATE_FAST_BACK) { stand = STAND_AIR; - setAnimation(ANIM_FALL); + animation.setAnim(ANIM_FALL); } else if (h >= 128 && (state == STATE_WALK || state == STATE_BACK)) { // descend - if (state == STATE_WALK) setAnimation(isLeftFoot ? ANIM_WALK_DESCEND_LEFT : ANIM_WALK_DESCEND_RIGHT); - if (state == STATE_BACK) setAnimation(isLeftFoot ? ANIM_BACK_DESCEND_LEFT : ANIM_BACK_DESCEND_RIGHT); - pos.y = info.floor; + if (state == STATE_WALK) animation.setAnim(isLeftFoot ? ANIM_WALK_DESCEND_LEFT : ANIM_WALK_DESCEND_RIGHT); + if (state == STATE_BACK) animation.setAnim(isLeftFoot ? ANIM_BACK_DESCEND_LEFT : ANIM_BACK_DESCEND_RIGHT); + pos.y = float(info.floor); } else if (h > -1.0f) { pos.y = min((float)info.floor, pos.y += DESCENT_SPEED * Core::deltaTime); } else if (h > -128) { - pos.y = info.floor; + pos.y = float(info.floor); } else if (h >= -(256 + 128) && (state == STATE_RUN || state == STATE_WALK)) { // ascend - if (state == STATE_RUN) setAnimation(isLeftFoot ? ANIM_RUN_ASCEND_LEFT : ANIM_RUN_ASCEND_RIGHT); - if (state == STATE_WALK) setAnimation(isLeftFoot ? ANIM_WALK_ASCEND_LEFT : ANIM_WALK_ASCEND_RIGHT); - pos.y = info.floor; + if (state == STATE_RUN) animation.setAnim(isLeftFoot ? ANIM_RUN_ASCEND_LEFT : ANIM_RUN_ASCEND_RIGHT); + if (state == STATE_WALK) animation.setAnim(isLeftFoot ? ANIM_WALK_ASCEND_LEFT : ANIM_WALK_ASCEND_RIGHT); + pos.y = float(info.floor); } else - pos.y = info.floor; + pos.y = float(info.floor); } updateEntity(); @@ -1724,11 +1624,12 @@ struct Lara : Controller { virtual void render(Frustum *frustum, MeshBuilder *mesh) { Controller::render(frustum, mesh); - chestOffset = joints[7].getPos(); + chestOffset = animation.getJoints(getMatrix(), 7).getPos(); // TODO: move to update func if (wpnCurrent != Weapon::SHOTGUN) { - renderMuzzleFlash(mesh, joints[10], vec3(-10, -50, 150), arms[0].shotTimer); - renderMuzzleFlash(mesh, joints[13], vec3( 10, -50, 150), arms[1].shotTimer); + mat4 matrix = getMatrix(); + renderMuzzleFlash(mesh, animation.getJoints(matrix, 10), vec3(-10, -50, 150), arms[0].shotTimer); + renderMuzzleFlash(mesh, animation.getJoints(matrix, 13), vec3( 10, -50, 150), arms[1].shotTimer); } } }; diff --git a/src/level.h b/src/level.h index bebcd31..a813abf 100644 --- a/src/level.h +++ b/src/level.h @@ -30,7 +30,7 @@ struct Level { float time; - Level(Stream &stream, bool demo) : level{stream, demo}, time(0.0f), lara(NULL) { + Level(const char *name, bool demo, bool home) : level(name, demo), time(0.0f), lara(NULL) { #ifdef _DEBUG Debug::init(); #endif @@ -44,8 +44,10 @@ struct Level { TR::Entity &entity = level.entities[i]; switch (entity.type) { case TR::Entity::LARA : + entity.controller = (lara = new Lara(&level, i, home)); + break; case TR::Entity::LARA_CUT : - entity.controller = (lara = new Lara(&level, i)); + entity.controller = (lara = new Lara(&level, i, false)); break; case TR::Entity::ENEMY_WOLF : entity.controller = new Wolf(&level, i); @@ -71,7 +73,7 @@ struct Level { case TR::Entity::ENEMY_CENTAUR : case TR::Entity::ENEMY_MUMMY : case TR::Entity::ENEMY_LARSON : - entity.controller = new Enemy(&level, i); + entity.controller = new Enemy(&level, i, 100); break; case TR::Entity::DOOR_1 : case TR::Entity::DOOR_2 : @@ -108,7 +110,7 @@ struct Level { if (entity.modelIndex > 0) entity.controller = new Controller(&level, i); else - entity.controller = new SpriteController(&level, i, 0); + entity.controller = new Sprite(&level, i, 0); } } @@ -232,7 +234,7 @@ struct Level { PROFILE_MARKER("ROOM"); TR::Room &room = level.rooms[roomIndex]; - vec3 offset = vec3(room.info.x, 0.0f, room.info.z); + vec3 offset = vec3(float(room.info.x), 0.0f, float(room.info.z)); Shader *sh = setRoomShader(room, 1.0f); @@ -251,9 +253,11 @@ struct Level { TR::StaticMesh *sMesh = level.getMeshByID(rMesh.meshID); ASSERT(sMesh != NULL); + if (!mesh->meshMap[sMesh->mesh]) continue; + // check visibility Box box; - vec3 offset = vec3(rMesh.x, rMesh.y, rMesh.z); + vec3 offset = vec3((float)rMesh.x, (float)rMesh.y, (float)rMesh.z); sMesh->getBox(false, rMesh.rotation, box); if (!camera->frustum->isVisible(offset + box.min, offset + box.max)) continue; @@ -343,7 +347,7 @@ struct Level { int j = room; for (int i = 0; i < level.rooms[j].lightsCount; i++) { TR::Room::Light &light = level.rooms[j].lights[i]; - float d = (pos - vec3(light.x, light.y, light.z)).length2(); + float d = (pos - vec3(float(light.x), float(light.y), float(light.z))).length2(); if (idx == -1 || d < dist) { idx = i; dist = d; @@ -360,7 +364,7 @@ struct Level { if (idx > -1) { TR::Room::Light &light = level.rooms[room].lights[idx]; float c = level.rooms[room].lights[idx].intensity / 8191.0f; - Core::lightPos[0] = vec3(light.x, light.y, light.z); + Core::lightPos[0] = vec3(float(light.x), float(light.y), float(light.z)); Core::lightColor[0] = vec4(c, c, c, (float)light.attenuation * (float)light.attenuation); } else { Core::lightPos[0] = vec3(0); @@ -389,7 +393,7 @@ struct Level { setRoomShader(room, c)->bind(); Core::active.shader->setParam(uColor, Core::color); // get light parameters for entity - getLight(vec3(entity.x, entity.y, entity.z), entity.room); + getLight(((Controller*)entity.controller)->pos, entity.room); } if (entity.modelIndex < 0) { // sprite @@ -482,7 +486,7 @@ struct Level { // Debug::Level::portals(level); // Debug::Level::meshes(level); // Debug::Level::entities(level); - Debug::Level::info(level, lara->getEntity(), (int)lara->state, lara->animIndex, int(lara->animTime * 30.0f)); + Debug::Level::info(level, lara->getEntity(), lara->animation); Debug::end(); #endif } diff --git a/src/libs/minimp3/minimp3.cpp b/src/libs/minimp3/minimp3.cpp index d76e499..cae9609 100644 --- a/src/libs/minimp3/minimp3.cpp +++ b/src/libs/minimp3/minimp3.cpp @@ -2504,16 +2504,16 @@ int mp3_decode_init() { for(i=0; i<512*16; i++){ int exponent= (i>>4); double f= libc_pow(i&15, 4.0 / 3.0) * libc_pow(2, (exponent-400)*0.25 + FRAC_BITS + 5); - expval_table[exponent][i&15]= f; + expval_table[exponent][i&15]= uint32_t(f); if((i&15)==1) - exp_table[exponent]= f; + exp_table[exponent]= uint32_t(f); } for(i=0;i<7;i++) { float f; int v; if (i != 6) { - f = tan((double)i * M_PI / 12.0); + f = float(tan((double)i * M_PI / 12.0)); v = FIXR(f / (1.0 + f)); } else { v = FIXR(1.0); @@ -2522,7 +2522,7 @@ int mp3_decode_init() { is_table[1][6 - i] = v; } for(i=7;i<16;i++) - is_table[0][i] = is_table[1][i] = 0.0; + is_table[0][i] = is_table[1][i] = 0; for(i=0;i<16;i++) { double f; @@ -2540,7 +2540,7 @@ int mp3_decode_init() { for(i=0;i<8;i++) { float ci, cs, ca; ci = ci_table[i]; - cs = 1.0 / sqrt(1.0 + ci * ci); + cs = float(1.0 / sqrt(1.0 + ci * ci)); ca = cs * ci; csa_table[i][0] = FIXHR(cs/4); csa_table[i][1] = FIXHR(ca/4); diff --git a/src/platform/web/index.html b/src/platform/web/index.html index d8cee04..6cca02b 100644 --- a/src/platform/web/index.html +++ b/src/platform/web/index.html @@ -3,8 +3,8 @@ OpenLara Starting... -
- OpenLara on github
controls:
keyboad: move - WASD / arrows, jump - Space, action - E/Ctrl, draw weapon - Q, change weapon - 1-4, walk - Shift, side steps - ZX/walk+direction, camera - MouseR)
gamepad: PSX controls on XBox controller
+
+ OpenLara on github
controls:
keyboad: move - WASD / arrows, jump - Space, action - E/Ctrl, draw weapon - Q, change weapon - 1-4, walk - Shift, side steps - ZX/walk+direction, camera - MouseR)
gamepad: PSX controls on XBox controller
FullScreen: Alt + Enter