diff --git a/data/tr2/icon.ico b/data/tr2/icon.ico new file mode 100644 index 000000000..49c118411 Binary files /dev/null and b/data/tr2/icon.ico differ diff --git a/data/tr2/icon.png b/data/tr2/icon.png new file mode 100644 index 000000000..ca583a04f Binary files /dev/null and b/data/tr2/icon.png differ diff --git a/data/tr2/icon.psd b/data/tr2/icon.psd new file mode 100644 index 000000000..aef77a698 Binary files /dev/null and b/data/tr2/icon.psd differ diff --git a/data/tr2/icon.rc b/data/tr2/icon.rc new file mode 100644 index 000000000..f4e46e564 --- /dev/null +++ b/data/tr2/icon.rc @@ -0,0 +1 @@ +id ICON "{icon_path}" diff --git a/data/tr2/logo-dark-theme.png b/data/tr2/logo-dark-theme.png new file mode 100644 index 000000000..16c9fbc76 Binary files /dev/null and b/data/tr2/logo-dark-theme.png differ diff --git a/data/tr2/logo-light-theme.png b/data/tr2/logo-light-theme.png new file mode 100644 index 000000000..fe050d2ed Binary files /dev/null and b/data/tr2/logo-light-theme.png differ diff --git a/data/tr2/logo.ai b/data/tr2/logo.ai new file mode 100644 index 000000000..3791eaea4 --- /dev/null +++ b/data/tr2/logo.ai @@ -0,0 +1,1489 @@ +%PDF-1.6 % +1 0 obj <>/OCGs[24 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + logo + + + 2023-09-25T22:02:23+02:00 + 2023-09-25T22:02:23+02:00 + 2023-09-25T22:02:23+02:00 + Adobe Illustrator 27.9 (Windows) + + + + 256 + 104 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAaAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4qoDnMxoeKDChv6sP5 jja076sP5jja076sP5jja076sP5jja076sP5jja076sP5jja076sP5jja076sP5jja076sP5jja0 76sP5jja076t4ORja01xuE3Dch4YqvjmD7HZvDAlUxVa8ioKn6BiqkGnk+z8K4UN/V2P2nJxtad9 W/yzja01wnTdW5DwOKr4pg+xFG8MCqjEAEnoMUodVkm+JmovYYUNkSQmoPJO4xVWR1ZajpgS3iqn LLw2G7noMULBbs27seWG1ptGeOQRsag9DiqtgS7FXEVBHjiqhA4SqNsQcJQFbkviMCWwwPQ4q7FW uS+IxV3JfEYq7kviMVbBB6HFXYq1yXxGKu5L4jFW+SnuMVdiqlPHUc12Yb4Qgr435IG+/AlSjHqy F2+yOgwoV8CXEgddsVcCD0NcVdiqjcLSkg2I64QgqjfHFt+0NsCVlu448Dsw7YSgKvXAlQZWhbku 6HqMKFz3ChRx3Y9BjS26KIj433c/hiqrgSoE+pOvHovU4UK+BLsVdiq14kfqN/HFVn1aP3w2ilio EuAB0p/DFURgShxGrzOD2woX/Vo/fG1p31aP3xtaWxqFuCo6AYqr4EodY1eZwexwoX/Vo/fG1ppr eIDqR742tOt2Y8lrUDocSoVsCVCCpjcD6PuwlAWxy+mvEqa1xVf9ZH8pxpbWsxlcVBCDrirduP3j FfsYlQr4EqdwQIiPHCEFdGKIo9sCVksXL4l2cYUNxS8vhbZx1GBVTrilYkKKxYfR7YrS/FVB3aRv Tj6dzhQqxxqi0H0nAldirsVdirsVdiqif96h8v4YUK2BKjH/AH74UK2BLsVUV/3qb5fwGFCtgSox f30mFDpGd5PTU0A6nFXC1TuScbWlVVVRRRQYErZnCIT3OwxVqBOMYr1O+JUKmKtM6qKsaYqo/HMf 5Y8KFZVCig6YEud1QVOKqKK0r822QdBhQr4EuxVQuOIYEbSe2EIKsnLiOX2u+BLeKqVwX47fZ/aw hBXQhOA4/T41wJC/FXYq7FXYq7FXYqon/eofL+GFCtgSox/374UK2BLsVUV/3qb5fwGFCtgSoxf3 0mFDVfTnPLo3fFVfAlxrQ03PbFUMn7yX94dx0XChE4EqckwU8V+JvDCi1qwsx5Smp/lxWlYAAUHT AlZJMqbdW8MVUFZHblK3yXfChXWaI7Bv4YEr8VU5ZeAoN2PQYq1FEQeb7ufwwoVcCVqyozFQdxiq 7FVBlaJuafZ7jChWR1dajAlvFXYq7FXYq7FVE/71D5fwwoVsCVGP+/fChWwJdiqiv+9TfL+AwoVs CVGL++k/z74UKrorrQ4EqKu0TcH3XscKFfAlSnjBHMbMu9cKC4SH0OfemKut0AQN1Y98SoVcCXYq t9KP+UYq70o/5Riq14I2GwoexGNopbDIfSYncrhV0CV/eNux6YlQrYEqEkjO3px/ScKG2twFHA/G O/jja0uil5fC2zjFVTAlQI9KZeP2X7YUK+BLsVdirsVdiqjMrK4kUVp1GFDvrSU2Br4Y0ttwI27t 1btiVCrgS7FVGUMkglUVHcYUONytNga+GNLbcCMAWb7TYlQq4EtOiuKHFVAM8J4tunY4UNvNzHBA anritqixgRcD4b4EqSSGL4HG3YjChf8AWY/fGlt31mP3xpbd9Zj98aW3fWY/fGltprgEUQEsemNL a6OKkRU9W64FU0kMXwONuxGFW2laX4IwQO5xVVjjVFoPpOBK7FVOWLl8S7OMKFq3FBRwQwxpbaXl LIHIoi9MVV8CXYq7FXYq7FXYq6grWm+KuxV2KuxV2KuoOtMVdirsVcSBudsVWGWLoWGKrlKkfDSn tireKuIB64q1xXwGKu4r4DFXcV8BiruK+AxVsADoKYq7FXEA9cVcAB0xV2KuxV2KuIB64q7FXYq7 FXFgOpA+eKrfUj/mH3jFXepH/MPvxVdirsVdirsVdirRZR1IGKtGWMftDFVpni/m/XjS2ta4B2QV Y4aRax0pRpiST0UYq2ptj1FPnXFW/RH2omofnitNrOynjKKHxxW1aopWu3jgS0GU9CD8sVbxV2Ku xV2KuxV2KuxV2KuxV2KuxV2KuxV2KqEv7yZU7DrhQv8Aq8Xh+ONrSlIsStxVSW9jircbzqvEIT4V xVsvc+AH+fzxVvjcn9oDFXelMesn3YqtMSVo0u+KrhbRUrUkY2tNpFARUCtPniqz1IQdo6jxxVUj kib7Ox8OmBW5Yw607jocUqQdR8Ey7j9rChqIAzVjrxHXFVd1Rlo3TAlQ9OEH4pKjsBhQ5GjSaqn4 KYqrevF/NgTbvXi/mxW3evF/Nitu9eL+bFbd68X82K2714v5sVt3rxfzYrbvXi/mxW3evF/Nitu9 eL+bFbd68X82K2714v5sVt3rxfzYrbvWi/mxW1ODaRw32sJQFfAlRTa5avcbfhhQ3IZkfkPiXwxV YFWZySGGKrkSRH4g1Q/hirXGWInj8SntirUaKzEGMgeNTiq4RyRuOHxIeoxVzQsrcojTxGKtB5+g Sh8cVXvCHFacX8RitLow4Qc+uBK4qD1FfniqHrKZDGCF+W2FC4WwO7sScbWl4giH7OBNO9GL+UYq 70Yv5RirvRi/lGKu9GL+UYq70Yv5RirvRi/lGKu9GL+UYq70Yv5RirvRi/lGKu9GL+UYq70Yv5Ri rvRi/lGKu9GL+UYqsk9BP2QW8MKGoYSW5sKeC4q6b4JFkHTocVb+tR+B/DGltTkkRm5KSDTw/txV ckMjLUuV9sVXejL/AL8P44rTvRl/34fxxWnelN/vzFXelP8A78xV3pz/AM+Ku9O4/nH+f0Yq7hc/ zj/P6MVdS6HcH7sVd6sy/aSvyxVtbmM9ajGltqVQ4DxmrL4YquSdGHxGjd8aW13qx/zDAl3qx/zD FXerH/MMVd6sf8wxV3qx/wAwxV3qx/zDFXerH/MMVd6sf8wxV3qx/wAwxV3qx/zDFWvWi/mxVo3E Q7k/RhpFrfrBP2EJxpbdxuH6ngMVXJAib9T4nAmlTFXYq6g8MVdQeGKuxV2KuxV2KuxV2KuxV2Ku xV2KtMit1AOKqTW9DWM8ThtFLGSStWQN7j+zFWjGzbCPj77/AMcVVlt4wBUVPc742tO9CL+X8Tgt NO9CL+X8Tja070Iv5fxONrTvQi/l/E42tO9CL+X8Tja070Iv5fxONrTvQi/l/E42tO9CL+X8Tja0 36MX8uK0uEaDoo+7FW8VdirsVdir/9k= + + + + uuid:9d823f4c-4b4b-4823-ae38-c33bf13c4295 + xmp.did:c75b5b7c-47a6-ef43-8012-54e10b5ca555 + uuid:5D20892493BFDB11914A8590D31508C8 + proof:pdf + + uuid:85b06774-401c-479c-8001-7d3b0b129933 + xmp.did:0fb69b92-9296-f643-8766-fd53738d52f9 + uuid:5D20892493BFDB11914A8590D31508C8 + default + + + + + saved + xmp.iid:0fb69b92-9296-f643-8766-fd53738d52f9 + 2023-09-25T20:39:44+02:00 + Adobe Illustrator 27.9 (Windows) + / + + + saved + xmp.iid:c75b5b7c-47a6-ef43-8012-54e10b5ca555 + 2023-09-25T21:59:35+02:00 + Adobe Illustrator 27.9 (Windows) + / + + + + Document + Print + AIRobin + False + False + 1 + + 625.000000 + 250.000000 + Pixels + + + + Cyan + Magenta + Yellow + + + + + + Default Swatch Group + 0 + + + + C=4 M=2 Y=1 K=0 1 + RGB + PROCESS + 241 + 244 + 246 + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 35 + 31 + 32 + + + + + + Grays + 1 + + + + C=0 M=0 Y=0 K=100 + RGB + PROCESS + 35 + 31 + 32 + + + + + + + Adobe PDF library 17.00 + + + + + + + + + + + + + + + + + + + + + + + + + +endstream endobj 3 0 obj <> endobj 5 0 obj <>/ExtGState<>/Properties<>>>/Thumb 29 0 R/TrimBox[0.0 0.0 625.0 250.0]/Type/Page/PieceInfo<>>> endobj 26 0 obj <>stream +HtIEuKgF[ BxF$#nGjETdd  K +}?Э~Kx1!UcGd~3ˡWNr'ӡWKm?/+^CpE+[ 'gVbͥE^=SkaRR̙Rq]X.(ks : 0cS)Yt#Gm1抩,#vfg\'pKc[5ơ}e}.!z6l-S42 BѦ"uˏ݋+V["{#oD8GKY-ĚaL`Oy]ku1aEeq@1Gbl𰭸F~B YpYh 92[88閰JbHcŐMԬ(Cqg$e?)dVܰSMX~нF(oɹrf7{[`J@$DzVB|e5fAvg/)Xodw: K^Ҩ/<)j`6i&'O%6-7Q +ц;rmumF'+- +M99zj[:4Ro]vNnu} y4μu9WZN\qiy0-'43_|~8= +endstream endobj 29 0 obj <>stream +8;ZDm9+HIp$q0Q6Qe??qY%%kUBPmQE#ReZi85U4q[)TUZBT@eEa4i\9f/'&[RRDHl +NjX&<7Nq4?7_a+8n'pa#`t4f+\/0!^a_X1)b@]V*V[/!!!$!rr<$!s8N0$XN0%'~> +endstream endobj 8 0 obj <> endobj 9 0 obj <> endobj 10 0 obj <>stream +%!PS-Adobe-3.0 +%%Creator: Adobe Illustrator(R) 24.0 +%%AI8_CreatorVersion: 27.9.0 +%%For: (Marcin Kurczewski) () +%%Title: (logo.ai) +%%CreationDate: 9/25/2023 10:02 PM +%%Canvassize: 16383 +%%BoundingBox: 668 -724 1280 -478 +%%HiResBoundingBox: 668.8522925662 -723.82957172635 1279.61121764318 -478.292207216795 +%%DocumentProcessColors: Cyan Magenta Yellow +%AI5_FileFormat 14.0 +%AI12_BuildNumber: 80 +%AI3_ColorUsage: Color +%AI7_ImageSettings: 0 +%%RGBProcessColor: 0 0 0 ([Registration]) +%AI3_Cropmarks: 661.731755104687 -726.060889471573 1286.73175510469 -476.060889471573 +%AI3_TemplateBox: 298.5 -421.5 298.5 -421.5 +%AI3_TileBox: 565.526869621899 -886.698676336808 1382.96686252656 -315.378669012589 +%AI3_DocumentPreview: None +%AI5_ArtSize: 14400 14400 +%AI5_RulerUnits: 6 +%AI24_LargeCanvasScale: 1 +%AI9_ColorModel: 1 +%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 +%AI5_TargetResolution: 800 +%AI5_NumLayers: 1 +%AI17_Begin_Content_if_version_gt:24 4 +%AI10_OpenToVie: 656.148966433662 -393.435617140419 4.59 0 8865.48883571471 8369.3179700816 2950 1902 18 1 0 68 181 0 0 0 1 1 0 1 1 0 0 +%AI17_Alternate_Content +%AI9_OpenToView: 656.148966433662 -393.435617140419 4.59 2950 1902 18 1 0 68 181 0 0 0 1 1 0 1 1 0 0 +%AI17_End_Versioned_Content +%AI5_OpenViewLayers: 7 +%AI17_Begin_Content_if_version_gt:24 4 +%AI17_Alternate_Content +%AI17_End_Versioned_Content +%%PageOrigin:-8 -817 +%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 +%AI9_Flatten: 1 +%AI12_CMSettings: 00.MS +%%EndComments + +endstream endobj 11 0 obj <>stream +%AI24_ZStandard_Data(/XgY ,M6dN>L1{#SJ{I{ +{| $ q +qWXDb CAͦÁH@ :=+1Y +~aϊL^(.oÆ"OqQ +*R\ҳ{aLeƴg~9/#\$]bAiՇ" Eo 욆"@4I D6BFMaELQ{4G0EPµ kM$H6狃HWx,mzDYW$ͪ9er^fg7eDTE91~y %PhWL0 +iHp8Cw #qd8^4htՑ2S|!YZ{(_pb({vR ͑06D%a׳Uv㌎ ]u/bբ.sf'*2YN$sqfΛtgVV7Ѱ4sdkV*C + mgLVuK^Ҩ6KlJbC+C~,SoQ" +D$LDa8$ʮ!pa8բ0'0NpMqx QpB(n)/PwnGE)y9qELsBaGQQeR2v-ej +ozڵJ޲ 1QfCcE.:7b8pX\Qc8ʇp HLQJТ®wP<~i Oa7b "QGeH!Cғ3 zySnKp0D&@XNT  t5 0q(|^% ;CcZXVZ &1 b@xa%H @Y$"}f.C<*1i Áʮᰠ* D<g8o8gVL32m=D)NQ… 0f +ÍtqG}15JTp"H= B/EzV5d6f8粈hFCp2(A +f8(pȎ1^:) ["")#r1I0X2,U pH5ƊL$:hfu.Jx 6eHStٍu9:[q?`9$dBVKı3 ЎUs%df=Ϊ pـ#S3E%c H LGp"[ ;'8 R(ʆ)"q, @phb8B@(aEp EBqGaD"a8$dpH ^pHY-hA Ápw=+Z$DE) áLAXFoet! 'R-XBBA ZT5+G%J1n7:kHtEа# \up‰9n7 iP -p "3Iϼx2즚Jhf6{c,E+9YzyeH9 ɜeM'ӈ\q^ګV9YVKarew0U)?΂GV|^!CTh£vK2%!͔!o;8fP0  +$ Tݦ &QHE*P*Nq )F1Eb0D"@$#a$O8L`%*A JLb @$ @GaD 6ExxQDqCX#q 0>0qu1P #-*l8$2h0 qP$09qq4;+Nc bR`2A +"v;5B')yd"I.nqpHXGp?#U#C* B a8ppÁ + )AB?,4ʐsJ2e:_arxSK. 1ͩ)P8P@p0 {ASTU`߆VW-,˨(E-j쬆fvူ)La ~)nqSjzmY5zD]tp0<,"&*p&fi䑈L"9&Co8+da5H 8Xsta=8đ8! +aCDq8D@,D%,q&0 ' 'đ@$ Eb)Ja +S +TŠ+ š@( +b-ja[t\x8EbX,8~:F0 hQ\÷ᐮcEf$D[f8vD`Y58 4%$Wp(~`ᠨp0@p4fѰHu@(WV0J7F30(cH 8 07l8 4fXd1+x#q ~ +FN\.E*244l^ZVRN.p +L&ͶZ5S\wq<ޝ0vDe땅պ2 ! N'SґQbc|<98΍M 06`8`p"C +2 ,lA XPAB PH$`&D@6 : (@R | L "t@Ba < +$ +  h +"@p h +(@`RA"\BHH.XA"@0X8A +*\pDpHX`*\ (,H +\"@!h ,@b +<@`„ +T`T   +@ +DB *@ HPP! +"B +$a$,@L 2@ <0 $D ! H + `(H@B 8 aa & 0Á(3@<( *@`QG.aI%($A @L $D ,dA$ "\,<@_A +D&Rs"AL>L(fJ&Crts+v3;`X* aAx0tʂ +<DxPAB $D`x@p@:` xPB &*ULvTDXE}X%H!^U&AP`88d\DĈĂL82 L8(p0`8$@xHCjXPBB„DP&d0A 4P`8TRAp@\@ . Px"H + xP`8L"L &T@h„ +( (p S A +HI8J*[u,uJ=zirI&"tK{:txhЦKڑV!l&<+dNjw%U K+Qz?*3r9Tx̆lISᝯBG$fg}>5a[DşL,bm,0TUƥ:Ye5oW4yY^ʓ]n{eNȌwL0f[dwnSn'JO㕺3tw'sOi#ߋ (-7!. &و|SQ!2#{s.}7"sߎ|~t·ԎT4gs4Wi툕fr"rhM3ʣzac\ʤOr5+TGޙ2&b)=O1V~۹Pof$&*)sj8rTsEEnXJD4heUBO YOŬrU1^]*AjVSBd]HX'g#+twUWXN[*]Xb9CJh室.IMDnKաJOBg~z]n[>,NzZnRd Ut\1kV>V_=UJz"Eܫm$x5̻24-ʧIttW ^`VX>TUޕb1*1,!bHv%Yߓ)9I_/YR\%H5gs4تSٕ.IO魂%D4`I>-ݴ+XrJڂ%4',˻Y>.L*D%bHVU:%xyteBGnNMIǬ̯R*reF/ֲYlR2xr1CMN*U%wfwT!2G%u^)+I"VzܔPf2C䆊yZJfsxu|Sr8 \ zܓIX%K5h*bK9UN =3Xqj& }3Gy,t R\: "d 먤;gЖbѕ24tPK +eASeZ,oSqzWd{MU\S&$3%3O'Q+6 hUJh*q#VVw+nR#6eC\eS6]ksrHl +\u>*=*3ٗ%d;)ɼ,#Ғ.+#=< HMxUzĪPIٰ{Bw1щ^ƨˎXMdZ#DN}tSԕ]MG-XG4WBuBBf3Ւuve4&$c13KaK˔deK"[eMKS|&J`9mkdK f1RܫFNIx7R_fJvTjޑXXϾ2ң3}K呼ֲ-.b3OXY\e&"iύ+9"X7Y!:>3f=HMgʝ)wsKG6C ;"jS^^G9,Yi-Y+YI%2]S#GڍC7ڰ3~ĊurH$uUF$R7zY}|?4X㲑JUiTч^_c\> ض2]fK4&;LI$RBD´TG0o^)k,>"Lb!?ptIy#ٔi4t-IDlȷVli5G RO#*y%T83R4:YZ*N)Ϗmk5UfyR5bK~jRi2%r,J|gIv7+yN+FnJyʥ_GTZ"?"xӱ;SYڙ¢鎲h4!fX牖Ҳ~_5/.L](4-Y"ds#R֦ o)wDǼ9_0ͦ̍ ~,dDzXfăstD$tԫ!`Iw])Y):Ubͭi;Ey94e_EElRR#titSΈ z™RvVB_̈;k^Ȏa|i){Ht[zxWWC}<97֯Y=ySJ<#QVɁ)krݓ<+:w:Z@K(0 axA +<@B\x`F& b +$LP +(sSĻȊDM |Y"XBy%,g$Oҧnnc#&讆7ba >7$yXt7X?7﵉Zsy:ߝ,T V<3O<ݼB%g[#"9}nUXnJp*am}f-'kf ]55XYjp۞iR3` xj\8V)ڮy^W8XZC_*.ꃆFWz)VdTBBUYYh5ݫ.14d2Zi +{%IvMtiT^~'Kz:#eg8<sV/*֖fnyC)3V<v96bNK{&]U3LI)˞Д)_W*D-Չ,k政*Ty,I9FǪtY>#`K)6C%KM%sOk⫌|):bE +,xFV +KǞKH%aI+gzѝ8e9en='2=q+,W^iRE|]"Y*m\ OSXVt{nͷ7#;+uY# h5'+֮8eKgelĮX6ʘ\Ty^HkHU=7''w+W4r)6d%͟6vds]`"R'&ogugRiFQ3yCWN+ʵ9 5s֗Jp9n.1`+d"f24j^OIts_w6S+fOQyZn) )5|,,}#)fyj\8;ͼ0iR)Z7R?J*즡ۦgoa%fJ6i߳r"2aN~SD},!$C+rӆwXgߦZdJVyźŸI6YP&]gK. +MB"CDCTv3o3#C:$3B칈n-|FŒ#aB +*L\pq H@ +@*"֚B$'n}ڧy^=1:JJ/{ˡBs{j5=޸Ţi}6Ep;!oq>iB|jO<#ϕ"ıqPy*c}{t.~/[3S<#*9Y$Ek{/vQKY5KE|շS˥'-\Dd]x͛m^9;sXD/>c Lxt!yא_*JS|4DsK}ЉR5,Biz[BOߣ3kCω fɖ\FweahfZj_D|zɹ}U6*y2J8Y} +烦YfObԏ}`q;4|%x]UbJ S/E"m!窼J.3<|Ib*N|?+>ěK9T?ybf;iS ˝*m&Jl'^N⭬ dLJS*5w5y4; +We{Ouj26pbcyŰe\+-{XrҴ{XICrmrVR薼^u*<{2x5flf7**v_!2 'kNXw|fa^a^.f.<,9u8uaf.;Ygp[I2zl:Y;Hepެq}^]܎eu{n|mʖD*1Uiut镯JX232VN5i²F*6ʛ(e?$l-bmm5?[nln6ټlx;tto.#͍m>tmќnvݮW->Wi>U\}~6W3vDWUl^Gוc,5^Xu©=[wgeʬ^<_ؚ{jrΚٕ{sяyşɪLVR*/gw2tL5rP3n'O"HD'3i%=.?1/$EvLWMTt6=3 (C:ɎkRyu7fڙhLJ:itsx%}cgٕIe>e$X|` 1.}$1\+IKR$etGyru:+K +T' +L^YJf!0ωxv3dezR|ĖLE-mhffR-hB3b3f?xAJ>8F8vVIxjoJ4*sևcovH*i>S9G9h.&BIG&hW%:* ]C"7 ȩu5"y+l6d"O&*sgDu92Kh=|YXdj*j|PZbU>:W.7)ggehlVӕ]eAKaf ?d.U v-cOz,&\~eItѨuYzaV=~Xcofi؜zhnm+#TY&~98IJ R&k,尜Fw,ˇvY/d25u峕vcCY5{]}ձ[uWo Nm*t/5j:k]Q +_q>s$ јL. ӪdIVslUm*皕}3O'Χʼdeu# >d I(GqT$hPHY-'{t2 pv +'gvÇf51}QPe=r7O|)b=X@\{M8iFɿYa nd8Ķ~;!z7+ +7)=}5Kܰ㊡KbA8U8*͚W]MJ geE,L[g=)DɍB)sYy?e~2J +ȉ@FP8M^d$,?ף|ĥ΂NъQ;XѐY""Lr=Lep@^羥^gr 4Op⥖[#FSch7P!MR4駏ߚ?j|n9K7&?; 4 +-vC؛8 ݧ78w,:bJ8 -)R)SEfчyð[ԛEjdD7XfܜWVofqij=Cfg]&YY&~|*a'@ +DKgkx]Yyr*:&^kBь5!Os:UYZR!o<]8kP]VwGw +B8u5F2>v5*TfIY-hs+K$#%Q!Y/gmGK^8L CjpqTYy9,qָFęԞH,A3z D ڪX48B sE8nSIpE_\ A|;Β?N<,kTȎhJ鼌}Y,P,/5cj>'ިyrhƱ^n gb딤Ym88kZz' ^8+^X|1 xLzI8R"'Ί5D7LXXI<ϋDp\,QwB 1&ٌeI7i<~Qg +/>5!SPKZ>TZɑ {t5Keq+i +]!f(/ J`(J0ҡ,"['V`"Lon[d@ڽ^@Ad$gm&zɣX-RY$ZC3BjV4裲3Cc78[S,YzzIغ.@ӟ2 +zJn,Bj:XUah˱UiLg%{{=63 qVybmVv:-q%Dʗoh8,HҫdB.YT𣏕Ec+=drQ(|qi`A!EyĶ&k.=ߒ 'S +||G€$u *;5 !γ>{Qm ֛ +1Zv1 vod4aj]_ L{A)M",8"c2詢Yd9/zo\;{;9VtB|(VFA$u%9ɄsCXȣ5(=5~Īo?|Lk |Ha%+HӲ^*Dt={>N!.o(MQkPYZ#Xei$Wm̥Q'?^s=ir;[F +GNw.p ~rW \`!˓/+QgJ;]s|Hss*w˦RnDx.ck- *FT,x%|= wüA>`mskdS- +ǨLUڀO:/2~Yrrnc/1IadjO -]=hm5W)*٨h29%F-nxQF3ѥI~rlxհNd7У\r(@,(t9Y[!{I +hC.Ym0&)y[ڤ \H90f"]Z# =]T/߸Oo̽&fLjXC+ìF#0E~x⚤1?>$o|uMJ"p1& +cv8Vv9%p\P Ed_`Y0o*đm&? "~҇qJdLd|B_[>ieU{Бu!}AC A-#/VH(A 喣Ɗh Ycűp6U9Tn#v,ܤ4(QJO V&NJ uO 2d$l(^şU2o槲>щ)F:6ePVxo8AN +5x\Sn W&% w~;+ ~@LW4- kDc K~*vѾ׍U& 'w߉b8EjŶض,WF6_Cfxz8WKC=Qv=pP)AA%5J-ܐ:a$>bEF-@-pcâ[Nc Sw&t=Wt1VUgs\5`wlIQz~ YB-S:k AuA4 + jeM ==cE1l.#.YXYF@B)$yy4 TdD%Ss,A@κSMd0U ) ˳Kw6@Qp +͂1ȺdWd:!! iSݛ#m$4ʔԈЈ**B]բ(23+|eQw ׀~q94gҏ |@@>vsQ%ais?FwCSJ-q( X$``6@$jA2p7ˆwzlYK/{=*i`  /YD%Rs)8d.R艞(ߔu;$}Q)ukaT~_tIe׈0X[*⁒i=趩c\2+)g)gqy{EDQ5S^\/v$H{ctDɸ5OMiO8kPZ~P+EȯO @:fa ӈzc]`!- arQOQDۑn^ɵT8Kʳ_qc5)8#yUzaϸ۠4T˜:|J̋yT62X|#3Đ/~QM(KuOOWRbi< +]0iF[ h`ܬLvK +@' +:3uI /F;Lx'xs8?ʻws Q/4>,%lg>r[]@Cge VzQY- ͒܏Ǯ)/VGBD3 / FZ& ef +X̨&VKJboӝpArW-[q\(XF殤B|^NSzg]bKcK f_sxK,V\b,9lDߨk=q_II.v- 2^>S ;A( ;ҭBS2gixNٜT/x̢R&}Qb) #.K, | h撜`R50"NU窣kgVd'`=)Y5-ڥ1wڤ_f>8^zx7dEWuNVcƪ8\A-:gWl'ck)PքK oRCڀlnPlFξ\G 6(wB'㝘5(&45Ze% KRM;7#,ZaɃ, vE7Z3A,S0W8>ˤ#xc^E!Px +6dyl4 +?RHB5oqSXe--Dr]=ҭ({m"F𩲌/V'#9DoH&t$#VɪEq>y#FJ<0hPT3+HK7iO>*7K I#+3Bn?XΎˠ@iWX$h()%f%043JKuee)bM1՛xL!O6tKU4e +[Gu6IudM `\hv8,8sRUXafl&Hw:sW,&T MryoEYx!k}![3)fYWM(€hbEHO$u7bϳ݃l/?-–^'y|hH}VS {"`d֢؁A4(c"e.Az8eHL74} O~5pܫSgy=,"eCyF3(4 :%޴`7W֤=v#Pjf#1D\Iq ޼=3fqSYeƣZC9awue[uD[,WSp7-.Lw:O0g혦VdJ`;E&P:1:0 䦠;qn#z9ի ~@xB%X$`^?|n!@>+ +hnp~HZ K.KRp6щ +ʒ7.Vj Q\4oPΉ~$}vxJQ*+;ۡJ7G|605AaGpUk,wfӾ|Jk|G(pcP-Nj@b7`ʼnoprbZؔ3)m,,wVaK1OI:\4[k(D&1FH긕oi`q|>Ɋ>x\d|~˛؈З{ +9l0NI:Iܿk I-ͳ"_\a +/:~al;78t +⮒P{짤YAj-jx,z:k?^: EPO ~˛ ~rj>bFҩ͌( y ` k_O;9~VXJ^ce0r;lZXo/HI<'$ƲDyfVP KߏdY*K{YZ^0_"k5r +9,)-0.9TOO)D5pv JV.U\Hd*.@Fo< t7nIA0؉`~'l+iv? +Zk~ǜ:y:1@ H& +[ zy l&fu'*pi{9 4]_P$3"HIQ\5CBLhʢۊrd;J!/iFs6ڷzQ<W-}{8U(CIT-;>J&]P`!6=w-> zF`.^𡇁v(uږz O\ǮM}?Rg=5iVif[`-T5edc\5X6QCSH7QD,8ar NTSb߯Toڞ<ھbe[#2]qLT Bp62RG?4 1%#,qFRV2TB}moï@!s6%'t|Jv9Mb#WyivP"0~gF2&gzJ/*0BSȢbVf©s/nE@:A+OP_bfۿJȹO*~ClABzGqΧh:J%9eBe$S +lΓ\5-u7$ˏCzq]bEHixD]T1mINԇ@n4b{őv&cñRwC +3U\8}P[ZnƤkh+|;Wөi5'4VSN5A@uw~H.T>cB91DWF'kNw]VDrI(Z.ú$\ʑ(֠9ѓЅĎe< 'L+]:VAX|c  1S>ճ)(0+5t>x./NQP"2 Pj-!b%=2T {h Ro9.|rfCXJ/lo`:%m ޟCBۚ "FhQXk3WF ͢UqLlg'PMq818U/,AVaش}0fX)֣r>0I(z_\@e_.4)0KbDaq0sgp]mD>/NGXQ%(Dw_Rd[ʪH3\n{{uSc~}효LAJԟ?%շsǪ#38@$9rIbk U!$t0G ;j?gF\):+Uè;{R#Bcz O\RE(h@.p ΞA +bW&V`r^DnnxwFʩ0@$fDΫ;QuKiߚ& 1-0p nNlX \bNe 28Px?ڢ+,8@2ǵx9qR"s{vΫIzFĢa\gv' C:+e3o+8#X؁GO`AiO8p\vK t@ +W?JrL΍:-Yx^QrnY6gHn'.*#Cir(gOgşHnuJf 踷d+x;9o,3lv|N` r|+M0бq6@xP駺\Np?[f.΂(Gdz,Ɣ9d!i熁dV\(xp n\ +;1t*rf-87zׇ +O,<$)տŀ:!=22>K/RTGs`/UYwWǎA2?lRcCT,'NYB7&>Dcy 雋983oNQ3w&1ǻDNNAjXTaj'(_W2|k]c0r;yjaQZ̘\<2>pTC-X'\7p`Z<'4-ac$]%cVv(SNELx?Pz̴VYVJ3eXĢ .h +v˸!5Mꝯ5q*N<+c譄y%;Ϛ>d/T3L]>TbI,ٍ`y +m g\n#: B*onz.TI%iP YajQshO''bڤFqFja+%zdŋ >P# #y`L@ww ],?ҡR9(jp2i3dzUZRRQ$UW%| .Hg *YZ7$,g +ZRV lUI +k[+/ĄۢXQ@o$gV+m9jFiҳr騫7尪^=NJ%рj*Eݦ dV'gpp$h[cqiz0ge`u#wxV\eEjhBzϓjfP}ڝrtD7G\h0QW]3BG)ʼ]â7(V|(.KV_yR/lԞEA20D_b4y[9TR|YY㘆tp +d'j4 R~ϨmPpG['Ose'Y58ߓA+U8SAV=R |?NAov +ZDG%7΂D Fp}%l +Bh)UEp9I?Jḿ' R"2"lR;Yx_ն&LOs üu&׾!ʤ|" "tOKČ hs~]}V3Ux2v&&X5-s$Zh:UBSOW$J# VIP%2>$7^/NjNE;o9r.F/\$Pׅ27`KE=r7/@j9eAdPǖ;&c^"ѻp}=IJ?m͠qH%ͦI@>tVtl)޹ z\F{4k +C-j51eZ*wu2J{$p&{M4 Mh0w EN>?0*?{mL0!0_}X\J+y1Le!l8."P@" 9 J:.ai;r`sh+)ʄ n,Tr@K%M' Ә jڭ|CO;d]"1>yJP +4t@ίyFSܔhy$}E=HY_PZr$\0% +M}-AK)# +^o&_7 tBo;$R'ӎh7 +Z+VIWEW t颿W6Q3|@,>L3qi$+BCr´qpqm~yQ2=?}bC!4޻[y۞F=spZ;nF|i"*2okmQQvAzƮjezz Wbd 1)i/gzNV![+I\K6JUL}Z' +^ke_=Pd(냑3.uW{U_[(l~@8!O`Ɂ:cU¸,EYR5`@d߫΅fZ 9ۅ~/3WT L8|ə1]R&nl9{+ʶWuj%/Rs ҉"M7]#3) [c?xFn{|7_Wr-@b$K asܧG%΍0b2I5ɕMw^ftc٫LOoEg\ًB+|+k@ hU7 D//%"Gn|R旆nXA +ŋADu0_E~W̕TwHZ)c%D cl1qDlPXnbP|`C!&hh@eb+1%6ɱhR%u d5Y蟥SL^F_M71{X!fwxpRW,U>$0r߲RlyysbƊumV{K~'l-s)[bII$ߝRRLI{ԙ+XK YNy%A]Os_$\ҳ WL_]>86fWm8F'M!fE ean/Spoe8TByOaQ_o)85-շRپd:jtqXԦ_TTSj+Rҋ;oێb GIɎ}~<SH-1JXVWq2#X' d0z=" eDNWb #aw4wm/U H_C6 3GA#;B꟡ "kz +ߟE*AjCDEC;2߁efBqyMnIl3_o2y-o;N#w[ۋD!\+jիg7#,͑X2~UtyT ]ӹNjR2xr$iIybˍܝ#Gn$AU~QưNz6υfwz9 ^],!J)Ӏn/Z ߫ɷBryӇH ~ /J˺p?JB."E.2Y.!o $xXAʑ+Ij?W|ȼ| +)>ۢ\ ]5d{ΐ|[^bq +Ѫn3|x)KN2Ny]Nޕ{)jB3e}cFZX*-C.wz/ ў˟o +.ոY 2$PmҥHh: apN`>g; ఺n ݂usӸG{3H#VK-ږPvzR~Ş6;jv4qwhQ<=/|з^aȰ͋"Srhts zB%˃^8P>zV̒k@I2nŲMh5|M˧R>kR{,{<9TɌ2H|ipTG{QhlɯdpҦu``nzHy1h~>׀pqd غc +oMIkIZN6E|Mtgt3(("`bɏt)fLL6Ud @bܤ:9}7ΉJ"[h8Em.%"=b yL'Q*|imG;‹kRk&gdeExxpZl/Yc j/0,$d6'i]'&L4:;Җ{-ppnKПgsEX~MWF'Bp>Lk-̃ZO)vxQc}& E}"'ho,"J}EQ8hF >9Q\mj_p/I/dyt4jpbxX't=Lz݈L{-3pD)r WԣY„֐AO69"荸+|vÞ ylRy{p?/+ƥ o9DYOP{B3\g\g !e{X  $m `x{,XtC wY"_(@QoorGJ':␕9f, :d_~Ugz̦ ~P0}aY؄#5"!]<I BH|1CN͚(\9 \7sU" wtr.*A`b!q LLWwf +/>' {\=@'bUlb(f?c*Q !Dz9q?$|[~~rxIЏp/L9c-v̉AY[8q7PG?[$!]+AFAKߔzWIfJ#G '+@ WDvktFܸϑG?Z{w:Gu)^C-qI7|67z>[g~m܇מAb0qS>^XMd4/NQɯ,"$]L oK9 .ͯ+R/h`|@pq^#YB`lbYi6!V=xt0[Oǒ + `HpiFHz52m=+Y9~8]p#čȍ"ltIʍ ^5Jc-]^jT5=C6 Hl<a+3`W/@vu4Y_ 6D㈹S"dh{Y2l{D@6!nYl޴8Qb}HcYw:D꣝e{.h›xȹ3 ċ\ '^ȩ~lC νeZY.CK]lG mf(}Z2 1A{& `6@pڢ $WZ 0sJ }y蕹̎04=OQ qtGSNw~V$ ` N4- Ҹ qa5JNpxI7Za$ 0z|腇x(sPnz:!&KlRO w2n!ъJqh\rcx6olՒUjY9YRۈ4$L;skPAeԩXs[N֎%H^V'E"+at2r}bcbz]~3zc [(("r-@coLjf鳂)& 3NY2p}IUI⿡^P^՘6s!6|`VSc~  DA! ٝ|=r S!Lj(2b$Tz6SEQy,&Շyh]ƳCF ?yYU* +ߗ:XDDk+5pלs7ϑ ETx@KEچ?;Y4gl)}ϸH~~?ً²Eܘ=X̎gOw$%g ,A\TpY3hVJmCJn1q (\6€[O9~ߚ?;rO}ĿlgX6wlToydp>2.$hRZdԚn0\An\M:=kzBU,Ȇr[94o$b^¸n3./Vtu[xȎfZ:˘p/L~<4kDG@69XIDQ(Dhe +I"z.q"uazn/QA" gACI:Qn%b.@ޕDE1gy0@QEQ-L?>q| Q>NLmQEt b)^@~;YzhY,&8Yެ`C^< I6J(А>ZY,5:M G>9HB3^ MWG]gLJ~Qs (jiGնQNN:.Ue~=b2V-Ly 4aCTLHoh~k4Er$`֟^/kijmo Ei˞T&1ry;޿_V zߌr Re +<*\} vzA`ր+ ;_0qR5$KV,6#-fk co zK$V{1[P^('sh!p3I]sMZ94Pʘ$#)Dg>\;!\+-]A~j0c Q Krz7a"‚2Y^bQ&լ·RC;4cȊ*'97畜ʴČC#ġO-q |%Ϸ.=ҼF,o QiVs_\sL8Mr 8WVQ_?d oPTG.A9Ĩ#ɭVDEo2X/;!E%/ۇ)ˇzV*&xi֤ڴ֐]Ɔ*γle( )MBŮ@q=OzB1(ړAQ765 /~Bf)ŻbYPܐ#ݛ#ߝS\<"E﫽W"i`)k"V<}R9MvT 3K5~~8,ȪYE0;is &]ې: d֬8#tUt6uÆYktgPUcn. B^$W 䏹USri[ŧHkT2eZgDx/##1 xh[ss3O[h )9b $fx%nl}HgADg확W I32:,K73jVkU4= RhA4k^4c(})5U<9_6K ̩ iU&!6Ҁg:Sfa[#Rè!!OЭ;}H{GeH 0'W2h7􈈐_BK'nG6 Ǖd*c`4+#}lj E%t} F +}-HOS}aNLFnZnޞ;2b;zKLsaA- UBnۦp;S^ڐy;@:TMLlE]VK|1Z[ܷGY^'Ӵ?4zMu`9ޡvsXreJC_%xM R CտP0+hB'Vf>Ouai/` Z pVHG.R0`[Z,UPbp{(o$|ɪUUl[ `]X$8SZ-k 7D[H-_x<^69&9Si*' ZlMIZ~q4HݎV?:^R!V4L=ɩduZFgnWT^Bއ 'W";Ye!/\(9fc{93W8|^#&Ew)(%$;&t[ew29\AMx-t3rbƦ؆{7<=lGX/S 5ÆPJvUKI=;lЙ+{DQ ~7_C̾>u{wWj=:dbh۾k!f9k,nˆ-QQG~ %= tz(A[$2KEag?E a;Ff }7 J_rDff,F@`k#Dc(i,Ɗ[ 6WwCYy28نg(u,>(5rоL CU5[4 3m[q;EGz{Na~k SÈb=2>+TPq`)F=i)8&`2i։?qF=TVM\v11𭪰7zlM0h&p?J@ŒaWрJ Ine_.P[? 146xo4kԸ!nG\{`٦WhrBNpF6>Gw$yP9ч*=W[;t>Տ9ޱBrm2?U(:I۸^Qɫ +֡\Jm1"af?N4S6PJnP~ **P@'61k*Әd B2u}}uȨ8zMہ~!95P[QTfS'U z\l6 XgQ%c4 eA% tՅJh7aK9a@{GZӣ|pʕO^?sukC1~Ilpwv^]m޸?pQ "$!F>;-JWH0zo劣Z~Bl_7gq?oaRӴ9*Pϊ] XT_\2iI7$)qOI~M5`,?$A^^cZB1]ǰ?($,Wp*m=-o>^n#O\lQM IksЍVB1X4 W@=LI̸^0Uh-:鑝#HތD#zFs`W&R53( *#Q{ COEEiz %&]֯qB"XSҨ@EL՜,it[I~(rpdx_ԥg\?ZϪS{o]Sr Sߓٔ_a:aK3kq k\:c} @,y[^nt4):Pk ;dHa&ף-B(KI_&vSܥƵQ)j].#2n_,{e[jsi@rON&r-WJҕ4 n[:uc4_"j|Eu hJ<ЇY*o<WQ,*~57Ґ(<iLiFe]hE"70WKiGhs/L igեTR L1*3H恰rthC-(Ϻ\dvVde_]Ik&5:#TL[jIk> (2^@o3lٵ`zVpsflDž߇xysNrgN<'EADT(3zUCitJb+kO?;7m0?/=z]&VhW% nh$IzJ$)%y zaSINqd]RLF"/JǬJyFgu?Z{gmn(-D-e>~)i:/u%ZKiiz!7ሦEX'i]M)RB)yE=tIjvw]yG[NnΛS(F?Ķh% ʇĠM$< ѳ*!̈'ď"$(+iP,rD>܋_4.JO0=]LЫ;j/zO¾?<8喾!Sg&)K HjR5]ZKyHLt$(7+ #AetpUH0J k yfY=R\{xƒMHRW8 G#%{>M~!̩DCa7!1RiOQ<Ky~L 5lB"qTED"BcG>Hk! +$Yn&s=HOvWtx{ׇqX.qy:}3|EȞd%=%W͕+4D#G;x~8?x8ͨqLc>T3_EGHi!jܾȊk}G84}ݗp19}u} 'o[w(~E?D)R'QXKqDA}nP!ihBFPq$HӜ/bDeM#֗`?ssxaI_cP]zF%׽PdfXM(4+qSfFk>?ewab=>Ri)❚虊sZs❖#VS9QRu>Tg [ƧJ i|gZUW]tx"Y%jH\!wŢlR}HԕIe]K +wq]uqG&.eh*u#; I1zHƩ~r*!-㾽aM~͔g<_$ҋpRGRWI$Jc-Sf5Š 2*N6'9p,΋[Jh<B CBC vբ&kqP]K+*nܘ.>[-MtqG21ѹmGlhbl'lfj"H #3>+˖`QAR_j rLɻ؍iui`LtT0 IQQK(d"둊!_Lmh$i ˣhS4hnC'·&Bx!ͳtfP'|EJ!sPj6!(d&tffCaffs!:*>9 (F[h3њCFIJ5>b _b3|Ƭ9_EQ4bBmmVYptɐ*xDn:SF_E7\ f^͹yݻ^y]_승e%H:AA5AAl22bC/ˬGW])H%[H\JNDg䗷)DlDf8HKF:qitCyy;ԸFJHfju_8I^LMC$|UB$8}e$C;v"GSB4GSh v4SJ^> JPdkD025C\Z!:hKVء5>y!:FHJgAj*WrJS6,3S$+kf$4`>g4-FLQ?3!qR65(~XGQFDhx=^Hc}#d>g>voS7ӎƙbBvߥ6pw xX\)P8P(O)pJaeH4IU*ipѩiꤤ"n(2ǬZnxY!٫%LF%# +n䢬˳gw*s{zD(}*CS."7+#"@6d a([lr`q@%J@*y bzuzJ%"'(b%gRRGn. +|WvaLUxdHV5ĢM|T*g&iHѩp Uc$60b:bjr%N1|DTz4h153ェuWV:Yůؐ:bOeD;բXk,'?EfHQ]*E%`Xl yRòVv`YָH+-=Zzhuf_Zg_>H!]mkt!ϴKҴIJΐl\\)LuCgfR~џҍ1_>'iϡ\ƅT_en[_Ή_SAvUBZHIvU$N .rh~)͏2h~b vQG1j( 4RR"6="5_>ΉEe Kdz K̄ӇPzbl>EEQ=KFGGV(Yt_Z|H5C9GY!BV4A|dkؑ}K8)*;0$ʢc +DNxBZIkQPİ"ƠUp9umIZI'CNjɘIg$PPѝ7d +fdDG)4l>Y5i-N? u#e"b,٦igabz}#G1t٧ۃ6/ʦ!wlK$z|qc7c֠Wo~arr1*|"g*}b39HPjU_!X$a]6J+}'3;+Lg+R}~_'٢bc?ZN,ф%ф= M-hh%WJQ'f[žUUE/#2u!ϞL/mtZDǔiZ8><+]&QE'F$tO!G6g<^BE+2)u踈uV!"j:=>>Vz~B֒ZDVlm"EҋrԸR9*Ʀd7rw6U1'yҢ]62:PJrPPBIIIHJ< r&l⽔^jUE$Wu411Ux8B.p0BJL&2tՙd68i]F#B'`P*r|ҵD~m]%W2"aWݪd^fdCxbeIl4ot4УIcu5>uJ]$1ԑ_H'> R67dF7 E^Q }MBҏR(GX +)X++C')x~B&tdMFФ9M!okS3l!)j}/u=oVj"92E.FQ+`gd43##epм#'WhsьUW.C&#D62as\dF<'jr3#4:8$ʥ]}xZќK͉`EzaG# =XУqf/RnpoWO&HEb\\ o^._:iR* c,I1e"a3QBGk#D+)ӂi1B%HtIW>Ō,ukH:GygF!sVG7G|&9ňv)e/#PQBIΚMOZ^|QڼZQ/f*9 U n.1&hfpU¹5p.ͭC7n&ֳfV3ڌ^n^&֩P#F<cďyH nGiIZFEND2Th*e2 eI+ȡ)&HB CK\^y8Da%1U)mP\{[%%CV9 +Y$ vBs[JCT6SHP=kD,)G#+4-05=("p(ȫ[Q}]=h +C(VZ9IHIꭹED3ݘnz@EEڄEؑ5$G5Zr^q&SHNY~}ڡ'Sq~B:ca +.s&%S[HLG8WE>9" +Qz˒U%hw*Oab$GhE,4%r=9(O2ǝ,VZi+CX#o/mE$5V +fdƋ7ZQ;j5#U\KA +dߦj(%ũ"V4#4G#՗!,FbB:7XiDY()>JtIf.;Sr +m$P|1 O4$Wp(3VMd +R?2eW?6CIe/[jЋL1b"CXo@iGYAEArΉhA VjfgK*9#슯, z8"1C: bʞȶ3DlQYѷĝԯ#LCbÝ^S&r)dN̸h"l5r)H"bvexe1|qt8Y-'UxAD"Qgg+ ,}+^]^bZjǣsI-[AY Iz br|厕 bQ!Via' qy([Qۣ +CoU=X1E8x1GkݪRmWU"VL˒ƙ4r%IOT)Qb뒤-⍡lrQh9JLFVCX(nK$]"T.Gcӊ8Y!xp"iL̼Rٙ]>OJMŦ2 +<nHΩpgʹ'Nk])?HsEmzтKG2iJKO*Vqœ^"ntAESt/ iZ$nfit8aivG"X]Eȉ~rl_$E-٥M6mutM7ӕ[GvבQkE)\NJFP9B-$HX ]&10Eu3,+))^-!+[èYNC7t\B n"ʡpPeIC_paƁ\Ύl^}拠EE/hNJ`e2Na~DD8a5A2F""(cϾ \-""aw^̅qab r  +CW !_p4,dS't/$").ك` +A +7>rp`w0)O#<"{  TY@Cq`A c@Ș&JL,Q?2#([WR'hAN ?3 +P7#ihUI_H)Jq $̴_0ZGbكS.AJd :z f<|,{AxSǃ5R/cvp<`W + ~A2 ,ځ2|`ax0fP㌗Du!"x`œ*M85`B} `po\|6sP r'r 2\r 9808A;ā0P-`pg "p`oM`0u#7ع66hڀmZ0ld408l _Av A5hf2As[ ˀAsՒ@>P a>WԛJj6s))x)ąbC-ΰ KoX;V3Y13(~3h\ `9(K  'z ^q LS*sXȋ RM 6`p% v/@VȾ78^8d(0ƿ s/uP) T) nl0GWX!&;@ +[=h(yș8 y.8 AD̙v\ `8XRp\`q)pD[`XYb0Ho0H` ƝQ=w ģCT:ETlLL%Ax: [,o { 6vنߐ@0PIVɌ+_a&j݂gĻ(sFu -H^m{vaP9?kݞ1n [Z وg@8a{ζp$`pWT=E!'J5qHt=)T/vR! %2ٲp({H} BIFo.BZ}ܚpA~z1 ڂiΞ· ,ǘw& ?j0;!wSQNPkctWxqWM0o]zH5H!^B $RC>aT&&. S2NLKpBeKK +]\ 6|+`-JI6rAixV: =p1[*vH Qgܟ'$ȥؒ@w%a|I  r GThx#ie= 𔦃{p:ܣ?+m}Gznk7oLZjD2F~U>gamE[#HGZgTMJϹh5@v.`Ġ6@D\q4S|sob_%X/Q[F:D*32)}Ak !o!${v= bƛMR6^ EYh ޣPK9 u攔i'gBpyJ'J!`X2 p<wa/rd h.@cy 5@V@AЂw +AH +xe=Gl)7:I`@g?_C&,^k.KVנ~ W˕g\WH'́ x&(EF-ҤĪ4Ýi%z%A=0U¡ XZz 2B4&yu%x,-8gƇ"Mž^~z`hЄs6 +lI 5sR߬ I÷:`/ YAyu@-+8.<&p9`}L52.yHSzbqUIt^tqGqἄۺ++}_7iXш˄(?fFj@81WZa{\4SpvN& +ȉz=I`BdG8PaSG}wT^ {RWNiӵ}R?Ԙp_7ptYEsnj8@R zBY JP>$(%w +&q3Z^v_~䱁7%%J> :@3[vHUeRai`Og)0m5m5@3}5ЬDPHf H;WH 2X͸h .ȇ O6l;% D>$g&*rO~Qwp*20@3S%׀ThO'!)'|I.@у1),'~b@|q5Op_dPLw%i[ Bao&?js;3 +*h  y n43wJ XN.PN-h[i@gi4[xZȡ=4ꬁ'+R{0U@MR. bXKy&B jL&(x^vh%>\ 5@4X})6\prMݟA˧2$^2xp6 iiPfv+q +i^; ]wh@T> @ 7>8')@:DMbz8\WӀ#hrr:= D r'.yyd̀$(mlf #s(S# bf4F 5 |a: p=41,*ւm"VV. qem V<2!mTJ^0OxV,zߺ.U60fƀy-Z)'1 -LtmOH:@  `@/Gy#Wn0QY0@ WW?%ḵG4/m!$bEEZDDg@s BEٻh'Gg:LMb=1[+ICɩ|q+`)4 zqHK +g w0/Ԡ}W9CT'8FDˡ5s74"+`{P4_5 +85xB +zwv?@]n +%DzQgw&wW[ +X_uPoU + +ʌַb̓AU@)5U\YU@rTc-O`4K0D y$ +vT +mU0Fp`2 +(j6[OFa +:MTP_U 4'jZo= +6t3HO MDRZ4_"+Ȏ pA}*`aDܸT2YQ<~[\Lr"k* 2ǯ] +?FfduU"S  Z +h6ZBm^Ӣ?JWw5 [S,,8Y +8e*U%^ +Zb {ץVΥU9r*1ʠ +Z;<*Z*iu0SJEWvAs +J"hqN9F8kssA.) +H|  +oجI< p&dޥ #P H>ׄFJxJdV ПJ>+yeIv4}S.[\^OcaBg P=@|ݶ$foV~#(e<a3$hZ µԭ'yhoZs |@c(R'`B )lQ_RuOюq&G+Ym%@EH.kdfS +"=='(HwWB:6#N?H`p Ҷ6a7Ġz]PR3,~]tֱ쏬~^N. ^^i4_:཭[~y9Y<=ӄ{]&[a $}VAGᲥ.6r*>?s1u9,oNMTV-xVP_iVCXSOڝW&m10ONG,t2됋N3=בTVھ3^Q @Z;{Bå',))A OT?N=uE_\wy//mZT$L<ǫN̙ `j1ϤaO@r;lv޸ݡqLق̩\}qhv/ Tt74:mYIowar(LX? ˍG_I(:#/V8ٍ¨̪M:;4W k|#g)6)$$sI-2=G C:H4,}I$U+&˵p"TF\ͳI)1U]ArAك"4%kzQ4HC"'nzsAeٟ% njjvDݤƆUG4n /(מAQc1-^ "[_~k"oqAOaIO$glSW" 9-~`*0l$4=(ELH>$v[FFV{F`C&r "SSQB_ *e~EO\͎?J wyLCً&aOxiG +ZW"!WДA':ISk_W[k2iK t=CI?4?bJO\ִy+Vy2}P$'u&dQ]XJYow w'#f=3Y\zg@g:c*hVI ):'`"4,¨3ek;c嚝a=bL)g!zrb@~R> mBUNGvlf q{i6$R MS}bW 足o, +Iu2,V/=I~țzKM2Ζխ_CZlHwfA~Kt‹XS?^2V'S##{ |"\yWooi @|\i C Kh"#z>퀲AF8举s[!k9' h?4.ь_Q v1Qy"vlZ+nSWODXzHXXx |}i󕪞݂5͆mCWV aGMɟ4.BI ; FToCmh)6G5!+ɢ1|^D\*pÝe{»+ )l83l`ޱoe?Yɸ-}c]˶~nE¤cVy!;>5Q7N.//|Gc$o37, MA Yv(B/ + +{V<I@gbC(+g L[jX=B_(\W`a I #F`Q`{AJ'nΜQFgcoijMU*0\aB&3⏚4c%ߖ(J;WLɶ +l)!Lm!\ðTG' +¯ ?q~U_~]ʚl1rnaaPhЈH#'¼$ڑTDaWwɦ?zOԩ)ҊJ12Ѕ<>|sLk|}#U2qI{Yv߿=]<>+o DHtu%*mZvӗk0[͉#Ȁ"ݒ#s/9dJhM]\ (QH'J쟾Y󅣭HN8.z-@f%?]KFMi͍p9Dszd3~Cia ~#} kvuCb}gefh.K;ie^78UZ:c+a\ꓪB;7?<PW~\fư x,D 8( ۈⶢ_+yҿ@.7 +J-#SU@ 6P!6!$<D&F/4a}z[_9u&& aC䯃,Dj({CX&$X &3VW7?G(d'=\*鬚׮k0?KaP !8? i2aG›b6 [Rv/n8*j[XD 6ǒ0#-oK2 3UBM[ d1_s[.0Z+?TJƎ/7쨺qw-ߔ$폚'p9XȰ>ojb:_l@and|ZOݚb +zLLb ʖiN[RǧPY@iVOZV +o +(кi~N$faΌ0>`v(Sa+ƂrOm-ZǡIK|š ֪ +кlSCO1|j9|C5|PGNb@^L0F&,#D~UrhF*P1z[u8R4B+m]ho& +Rl6X~,jăXOSA8.~4~d~m^j-]eJ oC#}[ws.48i|Ag;T$3/TCd <%%S&ƴj__}QUR?tȼ7GQ`WQ~FG`ɇ!o$Cl=簷{gMev{W/{x{aet/Czգ_ ݇e 9C1[~nx qrn*&^H!~zi(6W| ~|F`YZ2wJ +y Wom!1|QgC\ۻ޻o-/a!uzTDqoxGa ]&η@fePX5\]*yGz>!ߺi>6BIsaע_s +!Q_1 7}wPd+)m4y{rVT7^ KX8f`+ ;c۾_ql{B +jM`5mhBnwWv\iS%d #pmEfd/3,#E;<>t„VibFmlpc^ +QmjmPH1kX\ݳ R( _/S!= +C?ыm^x!'Gq7}\xv߀܌mϳRq.V@ +PߗBھUΨRab։ݫ? ~eseR° +f?':]*-Wm"Rquҟ?mOp(Ъ% Qߣb3tYZ:9ufuj{@5}븥>z(o^q^Q3j{ޅ߳WfU7&Nk]ʁWC Zg/7Njc%Q_q<0T 㶸JA {[-)@8b"~BNvkzV'~+³++nd+:y*=[1lZN։`7eo;(fk($Ã6NJE%u^чѭ~SRfjNt<(h;qVSl=X{R 3ԫ8J A #\!ݗ-Ct&Z.FNJɤ<&x?!B;d[@CI/\:HM%lܕHk)U&Q頑 Kmhz(uq'ZYGBd+(OX:=/y0HEChgPDӖRSTH}Dd:ugrL8PzO2*qgaϛ9tߎERj7̑`0;f*g\e*M! K\ҎtWdǤ9(4b?4d3})WRyq;bHN[sO6/3CҎޠk K#Uje/pU[Vqƍa`_Cޝ5zޑ~w[ WZ+vW\-xB՗Wx?OZDjL@k[xmҕ&8-~لڟoeY3!i^ < 2BrgL#8ހc1\(M//A z{SNb4!`N~ \Ҷ@?dS=;Pg J$2;–ksIq' +&7PDm :pt&TKܔȠp W [S*NAExAML"כ Р\)  Ҵk*]!@tۄFe +צev +J7$4pt4rzSZ&2讪 U tU`ֹkщGQ t!F*s%.VqPA2ӎM2NbIiHq:#!9ঐsU9%4[L&7a0%JcAlƷ ߂m;:"i7%Rhk(2mQ~pk:54Frcڔ\ ]#úlqchН)cT]g.YuD_'. WxB%1Zً'#=jA(\84iA22Ulo2WM +hAh%ܗgEAh `qf;ɦ8E>*ڴf)%QJ-8 lQW=/g +6,* W:; w (k/nAC4 p'0@$}tUghxoU0/hȞ.EdS +$$pŦ.Ґ~V7@lCQd`GA(Cns3(Z'Ĺ®-H%pZb%}U:U V9j(N0L~$EٍcSd=5Tv *(MD +ɢ|O~φK +NcfeQMn/,9Oj|MNy#a4 ZPRʁZEz xF6f_sq 9h[ u6%8tg 9D @Y6-kCB@aĻ ¦R(B♇xo7ֆvihAWJ R{ޭ4MeŌ..hH] G>𗴋'anڠI>Dj:;gAuH3dAǣW$6FYDF A 5ậ5IқrR,D)|h{A3glJCC6m"6Ճ*SӉqOrV^< ]:s̃ʍyЯyM<&=6faҬQ-Ώ?Kp +X (nfdM( zxl0,Hƃd1nj{l?HW)@:')1@7mJen`vx=EH +1:(60uIٷ& ܴPnR?;[a᦬ kƔ<>}:AB}!jYedb5DVq-tkޔEdj@cޔV&BySIoXw.j>+g,V.2&<'^rA.:PrMJF(.U =+BgNkM,=>͛rQTEHG*BB+ !ڶ "<}EtN$>5]z2m7i*BE7K[9D;"tBcgTWax;W5Mcܲ2=BhhJ5.Ʌ!Gguu!*v +nj,*YM025D[+Z;_M !e)BdX NODv,j,Uju/5@"C@[ ! !e +@K&x+KMf0B3WN\#Xj,@$B^CAZ`!y&1܎8ZI ^Yz!hyݧRBʀw0"̥璉L@H@慐{+ohax7y5B|Z⺛܆] J>ca9B`#R;%$ClؔAEwSьq c }PkT:R1K'3˨jcrI|9 C+䣥 C讖/B򀄎Rt .KZ*KMlSy!b7AMPߞxP3%TFLNJR!2F`'BفH5ebvT֚`JERB8fhĆ7+#XXJzUDt K'!xM)w6L(<'D}[jNڃbBYbH2!s&D|dBu$hʐ2|A` +YgB#nX΄ J׼N,A}FBOY._~OI\ Jx7+z-JHTkjPTIХ8 ;w]Pta(cO.OO'w"`0Wbve=pwN^TE +Ņ M\EER.#($5Nq~nw}k;$12HD>C=$?]sϴ g$#Xߠb@|@aos` #FlӁ"s#k|9x.V82/BAMsGc2B%5"fc5y4wflE*<$di%d Oe/J"r;"<z*kc6 ";* ICu@-|ǰb1߷~Zҕ'cʨӹvIf($Qy\*d0IOPVGqڂAI"(e +@<7dR qfA*ٺ@~:/PQ S)MyH^au'l"u֔HWXpƫ$[=qռJ: -Wo]v+%Ifl J\H-eJOwJg$|gi +TU͖ml"+Jttd%1q3,prSZ +BRXč۷AB'PА +HBR/ +y%ǧ4=rnH)7H +X*Iې޲r俨T[`i.a|[,LYiIe~ RNT(t'Fzo7L)*iU}XI$*AmܵW*d;}I2 t 3ɤ!%L!;Æ)r6\_ +SX+HfSڭ ֦vi~cj]R +S02A$Dfʓ`d/(H3(!L;4Ȕ7 )!MFLQH~"|H da\s?X -PJλIk?L9{_^|jFTXTj0U͈UVlNe/7I14fV`*No(&D71^g1`r032oM~jSHS1cǻ=n~̴*c"_+fjL}\-㊩)|$EZVLl>^K=/(z6uwӘHaHmYs1UÉ'qd,I7,9c>:$1I RgfQZeLG適uHM;E|5)-p$E„*(-.KKe".epnڤp&)'oSaRlLbANTRGK[ +d;F@L`&Q~#gXy'sT{)b& SaU.dRzL4beϽTbdYBR_L2AbXo1aS-nH7pPŴͪFV ] ,Sf"-ػC8쯂)Hn)a1)nbYbJ Tҹ6eyp+n@b2&CwN;:0b Ku>S"LH;nǠv0=߀:թLRu*<\JZ<1paŤpl07~xNE^.8Op蹘 TƸئ`1u݄x gTr/}r1В-I1fɘ $2)&WW= c"_d0pk!gi}?>f*띩"2xTsClʗX4r5ML挧Q"g5cS2.̩@˲_Mue*#*&kL`@dT INQi󀲡+?u.׏(@ɐ{jdedr[n!Sa5(5~m2O!|]iKmħ4_֔hoO",][IPRs88,R6conRꓥ+ +dЬa._>k6)jj +endstream endobj 12 0 obj <>stream +U%Yz! "'9&+y

T5 NQ:ż*B=GqNQ{!Lғ;$S0JgAeb#?y"{T]1kcp xVf$ nWWm€ +~Rył$n`8&3'˨KviVrGpM@T_.Ww\2[},vI`9nh\oGao*14 HbZw ӎ:>5UMRǘ/ + '{aȲGb%#U/J%_YiRUrRďnge:tY9xh{kcֳưhJ2(5TTFOr -JMX^@gџ ̕ԦŠsL㟪vg +Aj@#IʁX6Sb)WPL\#䗥Zr\]y,PS3Ti?*5b,i,W/| HuS*ը*rgF;bVoW$K_>,4LU*l=gyWAD HU4_TTzj*fTً8/0E*dMc'kqRCe:N*n+.IuFJ=3~AD!T+Ld]L%qlfvAMq +Ojo`U~{.fWx@TDMB0 G $oVO +u[}*?AQjaauu@MJV +L#(SO^m'=שr:ޜWh-/P:Vt1 +[#[W 8rfAPJئJ~51~=)b+ f +=y`U b%GV@\0RU+=aztlVZb)ðGW\ MQ9ys%`#K!m%7Jt+g9:̽ZJq +4ܯ@ɼXu,k- ?uq$r-6u} Yu|%X%GTiNV21Vxe= ! 8Д*k]YçmHnRNpC_Ub!?/ZVEfS/BqҬf ^xf_TY/(\uu֭L6$ϕ.k uօ(բ`Y:FkRiSLWۆgQ? <2YJYe^1g,w-| @:SHZЭ +oS唇jY@5CӮ ®gV肠΢  (Y8Βf:|׶~^u_7d}H[tuu8` Mko#{}2³ +p-@8E'?xvgqut +ς78_ZĻ'C`ljc/dgOI3r=$U!x,lQ05/R-Y5bU1L !YMWKSd,#KHc|,y* g5yP_=WΪ +V ۃY!*`kY>:qwVQSEBu5Nb4fxo<+ߤ5<6E0`7ֱH'D)|!349(gI:x9Vޞ@ƝH[1|r\owO-̖urW.A^2r kޓ-c:L!N,(J,,E\MsC-PsgPT;P{C# +'tg90 +,?*wm6 rg)i #MG(rB@æ8{,QDT"-E};򀚺Il\z-;BFJ:f8dg9'.PLKP Yh:5U,tɺ|NgոYfͳ[/R= !.9XIgoMi.?Ng]A@:r,e)nSg]Y8f,4S Lud&nl]3I 2;I_u:@w|ꬤai qф,[ 䥗!'d;Pc04H͂aH!m@NsY-ԹiP@מ-"hjs`W3g%AK/.YeqnTYtyՂ05Bn خkZrBA5(ظsۼIe(e@.`E1TAwBe%+L7 ;ql7BeIÉa $6[@d<FW4qpDh8_mr@xq`غܗ5Y xq7+O94M +Y(@E1 +8 & .)Ϊ ޏ;n{.Yľ4L~&κY-o ;,j08/WyTXDY^%Kn +i'z0_$[t8ijbLY 4*B! ݂8\+"?OWYjTj~Pqc:%.N{7R.RM)KwLUdIC#J1*"Z*8v}WC ,0JK1gIt \ł +}g!<⬿qqV"UШ%2G} Y#*1^~o?;ꚇX pVm2*}2Op8k栊qU(+g. +01#Y|x_ho YXlS66jэ$Bfݽ&zf' +>ð=Ͳ֤URI{'j6XC@ 'r"y@kf5 e,)D-鋬YpM>+7R1H좶fŒLhmլ6 pe;'w:BgkLƪxYEaMɰB`5F5&#`%Ԭxj:>ѣ7ZPf!JfB65KP1Y s_j1uVb28Ue^+ +vfS$MKÒF,H=qp>ݯf4R>hkA6R/k4xzH9^,%@!QuiۢYW ۤYd3d>;JD2ODc5]0#Y[at4LwtY'H1s2+f'' =&]j2o4 b |iVd.bGg4댵}Md5 l焔90$w1cK5oY3JYRCMҨp^4_ZM|UzjgawTia3팊OJc,vUC\sIS.4_R<#YD1 {h>yVT5-YjVA9r!Ii"===YM!SmëfOjވ4T2M]SerUȋcRU˛f+_ŧYL4Kzc ~xi[ri3 +RfݱԋcfeYf  i1,D"Ĉf9 5Wn0B0sfك6^4[Y4I75K^p 5 2Ca9&LzDE Af5ȣ@y-i)'YTiG0apR,G@lft;a+7 tIcUzLSQ#U*F\-I´x⇧!*AYP4y4\n -$;FLOF0RMr73f9ԊnPıfe:/f!#M⍿,8D ?5+<5T>~eVfՁ X֊(PY@FQjF4n/5bc^ 27E8:]M^4⏿鬻i s3YN%kf1)j۠8-ÏJd/BD8m\CCndNmAF;@Li,<|ņM!|Yeh6 +4h_=fa5p$궑hDhٓ!c,Ma4-9Ufj2PQ4  Oڋf}d,: h<p;{4+estdf ZjzSDenGa:mb@ A[ .iȜRi ̼2r S}c8znYWl*32ONR ͂k G3+9@"2j<^:?Di2d?!YhV3Ft"`Bi#*YbG>F֢oG_ Vf9j2((g@l0fl^sS..t՟%5@ W؁ø0c'\iV!N iT.([ 4k'!f Sd|(ӒҪ` '*f2*a5W)%/!cfiSb5,r`kr`iiֽ_J)ҬRiVXb`)Y犈/lgGmUm1R9Y[óT}(9j"f_ .YrYt)C$u\X7`.Bf즱]15 xMAa"Zb秾_?9g@vYWb҂|5X훿YH2NPS #Izϊ?Y>(wɥ+Y,ܨuoo[{aX@5,;#C䩭Y+]Wǣk\h4֠#ʳ?fR dZ +wkVO߯S^r?a@ *B5ϘXlId5f9L i,AC=͂ @VQԧYy]2\,Sv|JܦY.4Ku| U\?50Yn<-D^ʣl],MYamAr^VŸg͂M戚/lfXjVͬ@C͊`h OqOXfoNmF=g N`604+jf7LaaC7͊iyVxuh)(1J \ȝO0ݑPL0ks@4~_{f}kVMno* :0 +QkVfnB A _SWd5(K ՈCjYEΫDG'pܚ%Z84X^kV5 t:֬zY qC) oRY3شUk\|SZa1fΞJ5+{0^|i=q֬wLi%k5 s Gf~Z*[u=٩{PJ {Y@Sm8}Z@pQ6pÕRHͪvU5PAլ*&+6keͲ/ri,>kDY؀`2-\ +?ci8լHeͪ%if n)B5 ŢSdlYr,+-jV"M5 fLlĞԴ X>p/hY}=(EmhrQ,@/JG˵ ;Y _bi5=Ⱦ=^L.g 3F0e>N7l;gгo s2cg/w)9T +vm8X]LC,0oeEH U3 {ϡ4a'd b\Ab#,L0$B5)G?s\1T:xsH=(<ў&ѧ +Qf/:%LA x6ND)*f"mph>R!Q {+z-eo +͝O{&@&BT$AGQcx +tL($ ~xuc443̘mb9#s]ۥ f>a|EiE\? C/ ѠE2bI%>BCs~ Aaa e0eX{,8_4{g|¾!A9C 5>N~6~>pCGVc{ *~»㷐"}6DCp#`8 !ЉC`pZ ]0 :v8{$)&# +u@0pRBo{Z!J EMfLD)qUBNlN  ȃzQ +H70};J2σM'fC +aGHf23gY)Hߣ6,Ub9C+AW6JPxMnW ++m!3BO\jyCG;#Q<>CC#X.]L#SEKj9ChV~2W`y Ä)j 1͢>~MgD7s(0( +UxJA2l䃶?KDYvf ?eoUF|Gs3 'n;c7._PrhiZϥy4B'-HA6qž,Ie*/[pף+ahgFr_׊x; Iz4dWm,4 (,eQwѣa{ܟơ*Gc%`h=084s,PhXi@Ndk<;IrE|Hew_:|9*~~Ě`IP,"Hx|4pov,:B ].ۥJ"=?? RyGC$:q3}=!wSCiT *ЁfyM! +4T {1 @񰇮d"r zQJe +hJi)FxSŊ "(GLar/H1gvËqRLFN SƓ Z*  h/8l"l AH=65fuLG8(x)E!'qQ4䥿6itH!u/SS^1# +%] /R:HC:G7 N1XJ! 8S+ {U t $ aXZA/nY\A& }<8vGH1VnVy a1bO \ĄAyCbqEҘhei~.(im:hPA<"GA@|Ý + 2H<=]h4f]gm*Q3ܛ]E;ܡ +|K.%yL-*;\'o +4&\  Q N+Q\H#\%{B6hRZp4$XMR^}qtD=>*w=hl qVcyBuHC4h`\vE!{н%Ln ~wP`4=tҠ/8h!sR9JHF7C BU.C>M4tѠt4. A`?A9E +'EDV҈2n)<1a!yq/IZhxIE4[d`tqj#Mv%^N7l㏏A>;! 2)u.r0N||K8Z}m4 +SaerԌGxw|*bl Y7uG[ d[ldBرBL(tvxx44& 4,QHMt’Ґ13l(ۡE2l i̯o4#LYVPJ%߱tG C5hz`H#` N\v(R}t+! >e|ӐTc4bDOm! &-my@~/RzvCUp(>i!0._) ,Fad2Vi ۡZ ii܆L0f%?Sp4۾֡C8`)SdC\(Đ)`E.E( nwnv\%gKe2KA'ɐv@x!;5we*ہ=s({U=~_X8 YʥdS䠻BBG H&VC:E/{ ɚTJ\x! }D/7 P{$4)/+%9= \m/`^Hc%(KLFK +x4Puq.gbv:h"@U$**HK^^HAXtT,1u4~4Ņ F"\=pk֚3 8dv*?Α.d'H"ca@Ai7i0{J7bA%mXT +] +P9i463a#I E)ܷM\QH8\d;~nN%qny5\1vkE.XJd (o'ۡ:,Mp&н[+,ЊȢd˺0TK[/QI9 ̝%1ˊ食b *ӿ ALV S @Oh@)F}6(H6TagӆAxyx# ϸ  z BXJ|LiW3FB:D#T`1,hs_]s:8pIioʞMl p=[{  U<Ǭ=EҰ@P5_ <AL1L.49|x}KAM}f%n|:J#7☩/4919H#$ 4enEN$Ҏ4L 5% +Ҡ.ń4~]R"_8}g4~zC]?Xgꒃk4q[dKp". iL&:a?Lttihtkb;]2&^q4f]mUXУ-+#N18Sb2׭X]0 +ŠQ.YB!Z('Tg?yBkv[ BI(J~4Wfm-k8ɥǖ4,Kie‚7Dp Go(bHH-=PҀ+IPH#35K ݖ2m! {}-Bo=.Ƅ4ϵvC |! \BvZ.n@G ! ډGHk$}c73(av)M7caB% J6 +riDCnN$ LCx 8 iBO=f{X9-M{&sĘ2J!@(1ߣM)q.nUWזSmS_4nHf>^1N'Ql*TU&F[k5G GllzB{pw˫8?W29{7zڧ[Je{ #0dl> i\pb +`{C7*R +i (JTd"z8e{YuЭux i|);B-!3sY̪o4 +ϲ6¨."(hbu:HC) C2! sD!z.+S ]f|A&\4^ifKoi0 RMFhGiQWTÒ j"f74i]Yzmi%"o}n)4TViE iӫdĘg) ߅[fCt77éy iЂ,U'Bs@'8`lCgn?^0\OyqnH·xHHN( 9d1xO\9!3Ђ4lr=FC4[7h2\ʮq![mF`R75E]<(u}V/*!@, +z4pIeeoְ롭?-z2r AfTjU揆 !5@^X}_%P;^ +='5"Mnt/yWbp,{{h'HJd,/&7t5⬬x{4#b hX ֳKNnHsOC)\aݭ"sFHpQrL|4Ν] ē.M/Q¥{4Pxա|4|뜶qwټwhGc$(c\ G) T>YF|4&vč'Mk*,ډ68SX7cVŽ( n#3SHBf>[B.*FXs+g51`S_$+\>s|0-np[>^6ۈ}4ji3殲p!nQ ~Z~4_!һQhJ1 g 8Vj]9t90gv˪&ޏFJGct^ZoBp5+fB8#-(h&&H<ˎ=4uE8<IP6Wb/ZVRhF󀔙d y$'1h`!V?]y3#FeEdz61;{i8tl.w.1!Gn- G$M"heS.hXxސ6pјG=^#9!2 GYKg+:"ڿ|m4GȦh<`BJ &qfeN|D K +RA:HC /ͪg Ou Afj5 42ren~4YarXpe~4JsGC|Z> űJ.½iMGMб^nԢ> 6(lLΰH4Phx$ YhWfQמ6 +WsJSH*,c aEK|4lDCTlG34H3@c:BVU5~4z+*49CCYTFN88aAs8??u KSekN@HoMb_ae1Mn:c|"]ٿv4T龶 oq^K폹qa %h\/\303Z@Ex4v5x46L`38L`hȡ8Dlӎ0upEF:d=k(0䓿ТGƨl SYh@p%\ MTGOQG#^5FMD6oq+5fy]AE!O,I6 I[V78nrwLbȥ|4=:}xhgـ|4ϳ +sҠ,CoP<Jo%R..#B`pH^ G P=peJqJӾXXNzăhDx!`!6H) cCyxG_W- Kؿ3% }4Gh`PeAe|G!/+bWJ2D&6cO 7N+^>\J6MX`PZ|w7GhJ@,ê 0b1(VXX[hX}`G#Ҕ+gufw$ra`ϖI*r |4ƧFep2G +,wJ5OB>lw< = dc릻91&"IuP^KˬLmJ,52ՙNz}6 !~U3VMy4\6yҢXMGHL0t1khLKWzSNW +2m +DƖ~rHCrmhj~-/ Hg{go]`7Nz? +._4?/"Q(V[]ytN E$m92#Ђ@vP}4$\Lo;4qbd`e 6hD@L XSZ"N)CΡ8<邾Jr2ՄBx+0r{4xh`Q SO@۱zAB)tmiZ-jG'0 |4Nk,ؤi Yᄋ㣑ѓdSóh DlQ z?GcVأ,VFo3e -{7z [y4 SzI: *bṛ?kѤl#%QʰϩxiRr=CSLee?&K#Av%ţU$L&6Eg:'m<[:"B (e} WY0£PohL>~VBiyh&;5_hYvG3p)UzF%bl$*Od@БthuN& t7&= n]"Åɨaԭo:VG'cp-FP!^n#Xh]7 nlbvkD!d=ߑoP2)._ ,@NWghtWVk4)h໱Yg$Ϲ:.'c>hT1:?Kjv_"CWrDWd"GcJhT}S +0vGUڱjFҴ,:׋pdW&ǡtp ؛I u =z5 +R40gJE=)toh^}_L6aqo2y4+vcAGcޫ]<ۛ> =d(I=ݢ0r9_[/;CCx4F޻qRlo0bGM=v4Vdc<AFX+QJK؝FcM@Z^(=Lա72= yq9,FCf=}" + }p-kdk1VUАFC’;. 3J^͋0ZJxP!gQSL oq#ShdI:_Gq檭,+aA/ v:/A)CPC(fIjB!NbN[$?sk4mɠf1g0 0KGZPGrW%~"dbh6`v$g h?whCu 6>Qze,+t x0a1w`Ƽvٰj} nAuiA.@|$+Ϡ}^dCJ _O+iج͹u !Z{(6bca8=PLE.z8Pb-LX6o`wS} P:GjHS]~?xĹ;QӂBhۢmb;`~!aXor @ eFc{4 mÓPDZƯ, G03'h9m/?x!bU# ij{j&UuT s LJ͞YLh̜.pdܖuh nhۀ6BPF?t {ȟhf8giRCc$PCf%oeP-FAnptcfk,-܎F]8+XG hRh$J+DO010h4=pZHTG#pw4~(sO 4 3bNe*'>3VZaF((+uU +5؅ɞ' $Mˊmv} 4w8NZ!m7ϻ \eY|X jPYɥO"tKb$P''zM%HܢآȖ8v +ި (A܎F}; `\_#J7͢Ć1tGNA:ގF&L.Nv4ϓpFxByS9>Za.SĠ(G Z[JѸA85K +_ݮG+tq?1};GŴ^v4^1‘Рh|:\͘zo0po6 K;U; +ŘICV[L ~Ŧe;*X) 4fNdcnb4O4'Y4pB 1f}h б^CC Jh8h,4Hh)3c |Sg>o JpՑvL_Bt6gH ƘP0VG0"ls􁱟 ,Y#6rY0 >ɹT!0P0`39:,FЄ/7FZdbC+}uDCRWlV)M_kh=HӸ4$j;v[eC/޴vN;MԦPƒn6q <@Jj ХvT`S@76 ?ZO.jQP??R iE)fJk)6 RMIT\O!cnZVGxq,$,rRz4&@]9_iD +D mqYk/+5"m/._8bȨй$"`&^v/֏m/ً I/tZ^ drb&2n^XZ0j/7qxqPM-] zw"k'.:&.Dn* ?E[]0J" X')TM\8Xjl1RK\$5 WO @rZjシŴ|YWjBP: X& \-R@6&Jv #J~n&[Zmhjmjhfj#vb4~-JfkQԱjFxEEPըrkj[cj˯P13$-h-B'ۂ&gj{FQ΂tb5ABa҇YX(d, SndUadqud!yXc8uVj:"l&2WRlXZMsYWstaMQ/8XtE]amh,ntB"#e@4qƚb֊EQjbQXXP ScD,  ରX m\2XkYU1X"ŴkgWGdg_ +R{i +IɆ^g +lsfkmTG.|5MxC,{5ل5kCXīW08"mLP]+8^]wsPxY3WjC8JjmXK *';@hDkx 3E96נZ:^nY[i2^!ETU9eaUk߼Z8㮑WWEkoZm\WAikg[<͵\Búĺ׊+>ZB'2ۊlmŊEB?l +VV4;icZ!Rv~̊dœHwre!+ r2r$Cal*R͉Wam]E[ yxA"Xjf{jˇUT6b;fǪ¶.:* +Jwpxع-"- }3p#TnɗfX3݈g pT'nGEʨ972E *XtkYhޠb}MPfh?~v7"=pS[xS@ީ)ڼqoblu"!-o@4ߎ@X7f@L!/|(p]MMp,{\hwR</T:QR=)IE")F jcε(8:aĠ2ȭAJ3]$oK.E]LR݆As|)|H ([ +ɸmuS8OȢ~n}IO잸=~uU:t'J5'.}'t'R[?j'N'A"B7jVg{VU0v"K|:vꚥ0N0XUw.Vu:Sյ}0Su +If꘮0^'鍨d˟NuӽmI w%u9׉` u:؉x u3©ìI]Npx-[ ©W_'ೌ+} o!8؉3`鄷F'zNĐ +3z%YyJɜ@5/"{DUhT330'0R]܄(y.0'=T7sV,^]uUmquvp2Sumu9$v7vz8NǜHig 52'Ff >-ao4'̱N #{;ID +w͉N|ݹ9!Dq意~ޕp\l;|VsBaBC!6'v +FUIS|SVt+Kys7={w-ZpNWйջ r9$>%׫,]62!_8Dw!pbU&].M;%Mx7x41UȫMhG^nhD Ll<̈́ЋX .^2!S&ҙg(L:$2L@yEGZLzĄ7 zZg/wLdp k/a_b^K/q-D +z1@+QTe%"[-j%60==%(r%x)@{!&,%m ~glBS"UBU +%*aIGS₦SŔPORTKgJ bgPO)5O""T8F= N¹&1o$D<&1%%qHQ8WIQLWH+EˏD$eա#  Q]rI#rH$H ɟRt_|"#m,"n=iD둆iP"x# @D}繞CpCteUO!`  =kSJ"\d BV.Cz!Rts𽷳 +!(;bEϐ !$|EJ؆/ !!_χ!Q" _i>}V8!6|e:6vc[+DNtA1AH5} Q_'ɂpw=K +BtK + A4}4"uqBh@!Q~;?Jx=I BaoDXH O ua1 ~Oc{z)#~xߘfT!KN~>{R = xJ<~W(tDh3ph*#5iu@??Я5c7gh'=OC;PKU!Ĭ_A}[$G6ڰHX@faLÝ7B4p2]J! {z%` +X wsc~%x@ +d4 T? 'I@X[ s^Ni )@}D}d6Rn"$ɇIqU+d1OP3{`g&JUN,旇F\yh!< <旇l+WMpKvGv4\u OB2H0)^/֡ V2Tu:0rC NDL=t=ay=En'ecs%<)N79ħ9<4? `U48pFK9pɁ;Ɂ bA Jl B2xp8lf!EšrBTs8D`89 Qlb.% >2n nA\Z2؟`؋dYݠU0r`1BR%` I'|VDL td͒" & $ʹ3с4( uÁ,0x s l^"=Q0XASc1(A`g)& %u\GLryt霟r:h1( b(  7DJx`Z V1 1%ĠnRq47+!* l ~/ap2 04 'pA ڂIat\ྃ6F5ٳ+LZNK`K% X2Z2`pCjAq\n  $c!uJ qCs0XO' \/ m6nE݆k6$ &_ ޣ ` gyj6Y65!ႰpP7k>޸w:m AlFk A2e~ ~5A(Dnj8$5la=/wNCpua04@ ֖ + -K\j1Ne(Z5hjD_J+xv?C>Ý=f3$:h3t 383 x E䋵 \B V/"e e wA2aauXv (2 `œLJt̐ #2#C "}em?=t]0Z0 Fb6 +h `tA(*-_ Mv ?oIz|'d2V UQ @4!bxR^02 .( 炙K0$  M0\t`Ћx\ [ Ku.:.u<8#L:ȥrlO pdKmY-._x({/t. ɀ]p. Y}]Y_`5_ \d5/O`_^ezӀ,]0&#A 0Hzw /\. ]B-낉u. +u k 8D[x2pP N]p@…$GH U,v[taE8jŮt!LhQ^$W.NpaQvX~f&?r@t"  U; kot] |`5˰,=Bs ʅC-\p2.l0pH[ cd--DN-\0BvjABN J \0_@TЂX,D3wY * ,<GȂ H q'yXx0X fW:悕+Dl$+tҭp[V"rAP)xVxq*+fP?X*WAv=W+ + uPJu*O> +٧`I +R!**PTHՎSAO!炖u +,)L Ma38]3VL!B<° &#QPGacp3UTd$E[:d Bh܅-$nP8$悦(8'̐O؞0\zR'wDmN0k n' TƧY `1W. .\]Vv%Bu'p'Er'${[\HwܨûlН`k+wYg`t>vK.'g ,@< ~*f@Ci86Lo]E9nj G`,8Ap P yM낮4aoh q̄UJpd8Lt &~ \/ r./!Uo8]K%9 c}%n]Pyu.A^%[^p%X'-0pt$^PKSv\9'%@H K_h%.]Yn]p% Ȏ t G0WIٹ]0%/.}7%94k-gg "0:wݓ )_WJ)˕vE..p%(EVB]DN ZMĕILtwK{`Mw_pr%>ɿ`AWy2.WN═^ a`W _ <_ {%` )`PɈ.v╠Վ+tС _TjWJ@$&GY9  Q0 +60HN&c[ s_[%&lݬ$/e%l*Jؙ:*AA.=%t [Y? +wPP"t$$\G %a6݌(誕TzPJ1T,o:JBx= * +t+h u$LN0( .0R$ \u!!T$`!@{GpAގ0 &!A?Ct96`{oumk[E4# ~FFz}ʷ!fJ #(&6 +8!лtwz'igJIݗ*M)! =qYذ赋bӜL9B<8X@ @,!  d A$$@B^F+Oz7CQ6tr BzȆ&J e;Nnhp bHA (48Bdl)fßSd2~S%4}~S7ȼqN(Ұi|kf3UXԣ^>Eޓ;:SL"h2o@>߁dl Ul'VXGu7ytR:訵UDʫhD7D:;>,ڐذ)MM)> #>1Ԝv*LۧݼV>+0C}^"2Xlue"B&L@Dx=&T\#" ` tB +D9^B)֌2$:3Kt1Q8G2Dfqfߏ=4s36:tqL3J~=RԍiҸ:^5UϢS' *qd*֭L+ +UYNU5LSXuHV%E#k/;aE uSF s):e o^J-sNu)Eb =idȢ&͂z4i>-Ȅ8CEetABws-^ZTY->m,B=ґcWU(Pm>tRot|wao7~cKɈȕSZƲEt)|"S?]؆<V/^M]dA + +RA4~8S4)N2?SzMD< +EۦʯXIͤ\hNAvd,z<̼`t4`1QtCg͇Mw3&şb\t 8݉TWq$mbELDCoکn>iu9׍5|u7+}N1ewGڜ z\Ꮈ1}VۉЇi=G*ܐCr87&5#Cu2F\APY7z,tmW8V77>^dr%U}=*L S"WuTLzLUƦz9m%dq^"dQ ~fA4@cnXʮDeP2}tړ౪s` @SU.j<~b@sË \O1:5.ad +(%.īe}7tc{Ū|۬ Fiiu"<1O[D;J.kt]Va{2Ei)b`~h:礊!ndQ -El: n|jҏ1l&ܸIiC5gehZ8p&'eRɡ'O\ܐMw2sZNn9d +I8Q+_q(f~s,P^_(kȌCj^ -yP *.ӤɑCK+VP Č\JcͰ2i1B|XA4f㡯NQ& @)1q; +zUB%ܷf\F`@"Ked%XQ){4}F+*{D;""=q#K\#Ӕj;ОȊh6g,3͛+"~ZdxꞾ^S +U4['Qa/nm~SYm_Liܔ*! ̥#3c~M(Q֩t瘟UӔwJA%ZĖ-r Ӫa-w` (;R)ߵCGndq(Q 4zp(>.(3Fn dM{*W(FQoS6UClָ3 DMooLWkjU+'Εf(sg >+DxvKuVx2D?#D`b m)]BA#c>mFj ;CQZOITJhlBO0(גgIJE3ge]s#o if3)ffFy`|8KHN2蒾 |2ժLaKV#k̈́%ZYezod#Ƈ\޸jQ,!cXj M衄;'F?`6m9|ܼ-ԋ# "p3 +Mh:I NsIz +(b Qui{u yp#2ɪsn8ޒmqʓ?N|X ʑ=*ﰔQ3'(ʛJ `ّ) Yu% B.7"xy>vZ' +heYF'ly;Tޏk&sX0v;Q]A:# FI{GSh8ϯP(d';CxbO}T, `dwzURܒ&\ G9j􍆱K3(K+{Rj$ĻB&ude7V B-05WI.>Cf-#JLY"ߪ@Z$jU蚵N9 |/(g>m>z]waCcLʺUl q[ +J1i5*o(C(]LG7 8^`$J/LJl4G$`)xF0Ir0墈@6bA[\TkrbKp;8xq8V:ގqg2I0(fm=hg˧[X]Ex6zktJ]ӑ56Ϧ_f/fFi):dKLFiǪ̃ڱ^S㾻vÑt}㮟,Akǩ^,%:=U_ ue11%##5\yUd\؅E%D +4Jp +X3VF5}֧D^Oq݇b23~/ƅ?2 &Wݙc`4I5[]\,5]0"!lY @DiWiԳhNJdԺb#2 xPچD#xk}/UH12QȞ%p~ilx[ 7^gݥ.C}R4"pmMLОз0öe +UalZz6y)E7T7txzQy:TYF7BdcB| ?lC/aF +<ɤ Q\Bl~}B;c>z}h` 6q:_ E)j P*( %~YXpq +hUN1J~bC QXl!NhMis=dw;׼5iF P"v'@JiB$ަMVY4Y})\__ޝc)]g|Ȇm̈́ 1t~)}™I*\CP_@?{fs?}xĜ] O|s Pre ,Bn$$0s +=`,P;``:mě͓!ǮK}6 DF3r,Ћr PƌQ7w:'kVGOCrSob1f= {0{" yYq`A@&V‚HfKBhS_c!yHܫܝG@rƥVB pP@FQY"AFS*4z0d\o~k4WS*,_W *LPjemo`x+vߝf + _*ۀ[C j&o5(ygPhd|J Ć3`@W:DJUdl ЈtbNzǒn?OcG-9ލ?A]h4HPLTt3 w=9 cvh˨ Zd08fO9a+]Ѽ 4vf̣]}m֥=_iX 2r@HjДj:+u~>YUy|m8r57Sָ(dȄ/< +Kt 1ozy- l- W8NpP.Uf6-!r֙HHE,*ԙF#UYyaN8 ugW/u+x<}V?Hua6!u& +P|zYX< %jch +IO(.4Xc!Ttګ?ȴ"se5s. +uϝjA1lsP`j2԰lc +66݅ZIY(k@+k[b'TaKr Rb&C-\iUiA40F t45P4C +P 4BP 3hB0i%A=;J ZeG:C hNp+_2yN+zx&~UFxڑW{2_O¾bHW6IFT (ywyPHX:QJWY_1ta_8 ehε}୺ZuK pCG -M/3)LY MjrdLq %JՀH=7'$L$GlF[GDG$aE#QmrraFee/YA_=K\AiiVyf<|g8>H+c= C[>d$.V"9KФ~g? p-hHaI#26RIG0b+P\cA -ho">1GjvWӨlWE |oϽX7E,bH=JǏK `i)ʹ?;_)y&=3,O}mdYm|>U8䉾闧,ZxY)|+h2'Xb?pENA<勔̤&%Iϛ)u߯/_#4SxG>"@22+OR#"PR0 ru +~S3J@!DX hi@U¶F/6LW\X%}8Εz,a`i饂R1({=Gᘝma +gqG67n}:l> ߱:ez;cuQh;  + $h鬟"%=M@yaVk$AȌaD;;'olaZڿoȮY Z-j*Nl~Ϧ[V ܴ߃ h^InS)l+) t(3$<.PҐDWzWxX<Ħ&nWDĎU +k}C6{Y2"><~ +J$D!rLF.n&~,ҭ\ ]kFzfkJ(FC^j +=*|y3,) +*ͿP:-0uRڱ9w|wZg+$ g# B( {+@ +GS:L_oA(7>m{$jZ>vrtotqЬ9j4aX&wVRCbzb,vf%*gZH|&ą/܁ .=&45Ա>B] Q + +# CkmX`b%wK .`4܉ +%?2:{tC;GcdԼ1մ:^/-~*,.wއ~#Q00i1HYȊ+ZL V}ꔆbRCQY| 7? Uop M|` ?/Qg}- ZG j7Â/bd/0_ANd]׼Þ]-1MyG^JWL tt5#bWR~,A(ͫ.oÈeydJ"%G-xtZ"ptpĉ)5 yw(`E9L;}{I_zmy5 )-aq@}NoN1n0KRB!RTHIFJkQ!y?Y#>u6~2O5s&bӮl4J̪k{YʆO1p4q1DIF_F2kJiِDʍiͣ˚q (%}`h8⪫#=c퓶3h3c>sȵ{Vn.DR*d,[=6Y0pp-A ~f N)j)gѕ lְ8` +}QZiґH 5:MZ^A;\1īĕƠgvwH~h`!|O^n MS" @JD+Ve`8|2pG#jo%Du}ns7 lQ\`;iC πҴrsUiEg9.mN@-I;,_eS'oI,..Q "FiB9c#na\ AusLk&D-ւ^q‚M>=\W!@ Z堮;lPv| }Q[dtN_O߉]hQ/Z`)3߈XIОd\ #`wJ+C=ml!b@Jن&\Uh/kDzʓE Q@u=젍>>>\#k{Z4QZ,Erk;$3>J)mA숩O}~IMv/Q71iqi(NGhJH݇o5zI{,%E}4HJYӧ{P}$MN w9d@cM(X#)oN@)vBm0V$/71$y-\"DM T8T,|Nu#5@<=\׳N[V-+Dz +FkIU;3fС+c~as/3^0^b @]  \) 5,!{-lPWCq}ΒٙiCFD;q@,Pvs"JO3WT!pF = +w\3l,aAsS.1/vS0ghu5>9VmLbΦD+`4շkz[tO[r$%/A3@\njo`mTw/Q"b!|r8it~ty4(%ʶ20(-%5=M `{w$mƑnJ&g=\s{2E5(RMnnj#uחL+.a^ '.E 8GW5tAG@Gqdө$ʤ(pG7SWZ%:+gO`}Ew&Z@StjY^Cw 0H+6AB_4V!Ү'!4 xN#߃u7ɈICܝ$O뛌qĭܬ"r<4 )ҫo-!s_jm!ZQw: !PF. ȃ>>N-E*yUQ +`v*4ڒR!]|scN|C:іv').B\-ݧ&RFV O91(׭/Ľ6`p ~k9Mk pحlZ:j'v(ie@ff&ԕ":v?*5 @Xq^[u Ee]:Vw5ro`:qX [_ +|7]@4](Z +U4@Y.0ÈmdM:>m* j{_MWbcb#V&?:l N~moW[ @̆OBĂ$U-Y)|v91c5NiLD\`fwqeN;1;ۘޘ|.R׿#U/ڠMW MX1}m|pOu9~g>X,;m8f79őƙV̘apnqCh~mvӥBF7( ~Q+{2{]m},ǤUm ? ){!Jca@xzJ B>OAdFc|phԱ-qb gnCcc;Ҷ1c`R5t;֗B sXFPȥ4ᝀßq,^+hJzͅXv-X; S?Fsotq) X>c=ƃֆ dyҰ`?1ȄǬV2]V?f(y  dǾcAM1cѩ1r@zF~L؏4nK>~~Xhڏ+Jd%=>6MC7 ;f6"ӞEzƴ=9(Y4ޒɰL&Mnw2IeN)K)uhɼvگYv*CfeΏ˒˦s!^{a&Y!̲Bˬ!63÷LF}^j6SSG #PG)koXI4oipXg&g}RL\g-{jݒݭ2A'=Uiptsh@* .JCJJA*Rd.AD ::*.A[tSgFҽeM!H;6\Cit!Q"D]+#udFTViPcgA +^r 3m%($pHiBpݏ躀tc) y\|l )X4':>loؤy뼺uuE4;їuM<*벑) g|J(Qi4uc*YG_!:ذK@>uuJ㱫 -;dǍ+c\VE.Li4zV'U) zgoJ(c]{u2qvõ]Jzg$) dS7?P) kYشŽ.) Rn;yoas Wr{֑h(KR7JS'Ҹc묋J#k]Ou&qbO6κ*k "S-wΪw]JaN}AބmOb 3vY ͮgWj.cvTjWCۉ4_@ղq4IS+vKR [Rww9x'ʻޕ44vsZ4}g +rwJC`JcbXR)Ҍ]O+}]THR_p pV > 0חXU u(V5H6M5J* +DE ywXv8x7xR4X(N(_mOҘS5Dtg__AZ;|@i(nEFFi| (̟<ZϱLZ!(Bx`~F?{xbWB\) ?=JipEG J^E^-IɣДGW^M<) K-e^i( }<+|@} +GԹѣGҫҸɦORԎ (@Q~7Ci~.oX4 !DiB',9JcclD <֋NdmAv:y6zlJ +|q덠FlKJoY/a͉#c} ̩Q\"1(I|Nqϧldr|T4!ڤn4^ԔI#pizB2Ӥ(N1${14AnI6{8h}4{'1JI#|A籗hyJ+VxT̞wV2mՒb\z@ICA{xv՗4@{p?{Н]/i8F.=%Q嫵ƙ4D>gq]Q05e(2&/송Pdmo_@h4&od6ܤ!QdQ|Cn444G>' >lC*הm/m#ePJoF* JsTTQ}P> ?T9yy4UȾ̯ǜ_,Gwf;l̏؝_ 'iS7[T~@icdZ7E? fk.MoF~J*!w7<{:N(؟( B.I-OTҨVP쓆_j(@_H`P*ն4 iiSܫ4c 5d>4 74W(`D )βor+dbHòQ@0MCd<:j7a0A߆CfeBK< .+?fh4 1& ȰiדA]ii 40 ocE  #~3_6#_+0H|@B.)[+X!JȇAG"!(.تAϷT!n#X$Շ,Ɠ*{س ªbxQ. J BpN +h~ld]P vA~uA'kr i`>~BZZPF!Y V!6$ưWѐF|$51R/$LRV +B]0 !1AÅ.H XҘP/IbA*1 %.^H%! m\il-zgEp_l F +iTf[=^HwAq󨓫u! :BE TH#AHC]m.ᰲ`킾 >i+q, b ^y]Q_42TT]V<PJJJ7.8пDU]Ga`{1]K4^8 8ѝ},BnE~4L.hݠLa.xß |F,O]hLt4)wHG4 +HFT5Ԛ 6N̘ j]*"Ұ(H#g4B T5h۾Ud%R!Y )H#sg]` e4/YpL/A6`Y4<@+Ay Hc1? i(dHCW(VA纠N ^ڶF FQ> BiuAH ~]r(Џb#hRҸx VGC%}0^hFM s . +e Q|ܿ.)~3MX ]hPb1{4ѐ= >v]0qR|zÀF~`A! ~!)/hݤFjwx n?xAX ;pd +1i玈]0DUnGż qh{4~Gd&4R%r4|4Zr=9q7!*@k&1-qhT/Dq9bbfN>b죡 r~42 K [O/XN +H=M5 K>&#I1_P0ˑ8ok + Z Gz4BA=?G= + F`0 + L$ > |4ߣA T&hy^chE*; u&ի. GoʀAJ&g vѸC;Lw:v1)WZhh_\)%£Me#uQIң19`MuP#0hׂh 'Bha`P`8 j9 u bq1wHƣq 6$ YU]x [DK!;Gw4@F֦ +Tq#9ukח=|4G`HxPP  =j |) t``A ^y4^( GK?C@M Sl`@hPo~79jSTdr Uh(*G.t8cOV@h t7maQ@, 1O!`7 TUvFml45߄Ik4┄W$ pF0XeFC?m4$L`FyM2`F)081Z0` & Z?,/a6E$_q apWaPS7E-gJ\ RTiN&0j a7n48qՌ +YLpy IGԴq 'N6K!?x4;GCF1( QĔl6Mhdu4aY@T|;0bzeGÂ0h`pMIhĈCQ# + :G0d;[ .NƢ ' xA aWb"x4NP"A;< +% h +$^׎x逖cҁ qG$ VWE⎆qЎF\l`0E,:jNWFq.ԤYQ+f`cpB u ]q<˵"ݷ Mx:ü4UNHBv4B@%./"Q,}ŴeGcJo40Loh輣 ,~(u?,ʹ#|F~%*NBS  LGx O׶hׇsˇܥMDQnYS)9`FꞂr @(ҶR +-P*5P"}T֎ƥmê"aF0݃]lH *&4E< _Pl/d%v;xh:R衽A)`jTP} +Q9X\UZKGCT4;G?cZ?#Pјkh FQB;i+3_pZ0}r(ŕ(´g +p]sq0u4v1#[XG4(x) 1ANq^F2P 26T!RT@Pc5lRSbs+hi}X]Spb9y" ȧ,j!ETB"lB1RGDt֌-Uam;WzyY_F`/R_ +23+QqFBSIIojYau(W[xw&'#3Z>ad244[y fG࠼Ie'kJ)B&Aݜn^̓`YPa% iJ4[y\ 38̙N]V>442#2HmMPUI?Z>+9iyD3;>JU "qt8zm1؊h_! Eٴ6&(& {ef:,h:!Rp-8hMoC;WdaHNR6K +qdT< U]=Ab +W$Rsu\ m;Ƞޒ&11\⾺]"nvd8$B+{&X{2-QD>:{oPA1֓㷆k +ݗ|].JUdP'1EqI+#JNu Gxy=oNT4pT4R/ah5QPO@EGrL%d 0KuJgi:2#Q@AVy*˛ +:f\#Wk]cuS<O U, 1qĴ8]R\Cln-4&q7K/CxHԇz+98XO/L%, cBťH.(rH| )U1YLdATLwAc'S*)R1I|$2EsgEІ]0k_x2H dD ӄa;KdwKBrKRSOڪg5U E6sWA! +LM\Xg}CEd4垤DsNԨ._7& E?l 8?H/pHHrRMy;] )vKL24!!!OL*UOUOb"`۲ȇ$ڗV/!bpY{ p2 I5\aM/?;FU7eEJqGCŃk\I"3CH%-j'e{Z~YlI竪GO",iMHmb9QUYͼ vnop"E/}T#w82APHkTHQMJbL(>9MD!.($0SiLˤtbH)BjT1R[B*GU-8(ƂXoi3 CQbNq% GT (^F EPa5* 2 zysdvtjr BS 2SKafJB]0:#)V2PaW${k&+ n͂;Ш-&@%kC,[}lBe)^0jFa-.3a^WULv T+KX= +kV>0@ x(Pm"vC#vZ3TC+D'MhH)^!!)|xX$~gLe $7LH.!ʘ1T'<JAP "VUtdR5SDS'8DT^HH걑c.Ҙ2ȴޥ&ލ ni9.9ifώpCX!2R⪼ȕep9)نx yR)]:W)1|j#/`6i & +o9AI$pLj(=/8:8[iYYgc{; +\ā kۚuڳA^G>'9wb=A-. {:/% v_Dq2)*O!գ2lCn s>:!hd;Z|".Бt~ݠ gKzv|z?M=ehF*bl܀s7 +,pWK$1 v>[,gݴLM{Ezwi$y5;\TȌ6P C$h.E]L6WtQA#1aOz 3*HHG槒iO/ +a tTݖ46F<_F7qku]#(g,YӄjB{,' m ]g|"XF# 1dB~/5Vt'?ϖar kCvD3Læ -0vAB2dQ~p*=⌷ѸN;mT@??X,P`n$:bN<ı #tZ(m4u{dܚ>o0gWi^׆Ƈ7`k+[hC)N&nM\meu.vYlCUJN-=c@ {AC36?֢{^& 2xH+ITU +t5wCDC8dGd1#o+^36T~'9m.&*4IAsjOdas>E a{ ADDWcF*ցF>=Q>o)u*EΔBv}~ EnS~3 +9ULk(Ϝf .ˋ mgf1;s36{gҲA?ov'޶Ky\(?ޫSy甈>M:GqP桢T24t LX0z6A]>d#} >D! *lU`oh4QIӭ]jp.IC"U@ eBRеuHVX/0>&f|vq6yfAp 1wSUz0cx(`>oZ-Fyq^Ig:1LVDt +Aƣsґrft^jUc3L"hhXhH <0^oX%OΝL[! ++IGʪԧl/ O[dp;d,'edJ)F.q/kKUORCHXĻ}͓#gcD uv# +t`1AstĄwSEM'q7 %!}I%,X()W5"ys +k(7^D,H/ X5ߘ D{e:ݳ3,_-.q+S0׮;AP$_=տ0#fD|r&WRXF(Xqp*]Y Ρ$NG#zYIT0 QkP֠ #u=ۜP U7XȻpC[O+f_l0OQp^_MrԗEЧ[$GWcUMl?fH0ڲfJ.wM$6ŗ!alΜ53F_&{W + +`yP.ɬ. ,tUI.+ !B1>YꞳ uE7ʶep)Tf64}ŁEKȊz3:$ӧlθ=:E.>4 )ԥkfB[@+ ե5'0vÍbA/J +Y_ VFןOLT3(z?c-; ǟo}l=Gzpڭ("nFFT8Bk~.@ݶ2qJWXnpZO0l?rM^3.4}L.8Y +U4*Er jS:fNHB$r +9?p7q=Rg:.<3I.\Dn6JXi\}UZ'<P C2iVa* sW1)sIe@v3(]o+/ѫΈ.,B^ogb]=GomvWs<כ2KBH݇wh!z-]3E/jU6:  8E#$ f㔋xZpވS陸ff|KUSpubBi{5m?4 +meKr9`T3\?F q&0EkTeC0/xZ DXO0N`F-qDd@;G4L1 .hPAZչRL)f=fcBH +.(f "81&>B 0GBK`M[0єDP|_z{0U갇Ӭs'#)¯Nj|Z.$hۼ> h+|ڏ8{G\D y5@_HHL +ßC cTWSqG#)<0?5N҉GYCӋ5W{s!B[-DG!RDR:hK^hO O*`@mIPюyT-_F[18`mȍvѾ`oeR4L)2No"hZkUechՔQ-ZͷH-iԛ\o6fĎRoRZo0桜괿9j iVϲԄ%ή⺕ ޯK5.JM}7pR;"ZIMBe"PU +J9.lX,03tA]4AF3 +At Td-|5S֮X<n6 UCq/OW4 %]\ UGҋȆJ D97s?.*7 N~:Ppxwo{xbeLDC'Uhn'"Av!PxY5@ IE<#Bw EJ#^shFYAl-?8kQpaRx0UM5IY;du##$#oQw CE>ӽG['*)4xz`D{0SrF@ARJCipeY< 9҃6;zyEg F*(Jvh; tK$T)aav'CH C͕uuy4T}_{˴]KxwAx' n7a1t۲jV2-lqYfjjT֢AN}94;Y$1;bBoXNp]ȾzQ:|R%?q7RnT:!o0(CԔ@0K 0]yuzĬIpvg;gYley[B{ٖ,ŨQ(u U 'C ʇKH#T :| +4yZ8Vh(pw"w +0|G +jls*F+ĠHcp!K,i[ /ͅ]gPp#痕dܿ[m9W\ak)Gb@@1x4XgGguc4D5ОՅނBi5]نq Dd'HKD=A>pDQ$О t $9dw.l7;Mz9My_딜yy.1˒g4;Xꙛ@QHltzC ]҈niRNzH2KDBn#kW#a(h! iIw@^ց}u=Ou|pP"]RݐTLtit;udJw'F)UWwzPQX uеbpp9^k)Qk&kLk10_BGW +c3YlNJpaȈ w L\pIpg۝ZDgAXYpƻ dQRBLs)&nѩ wuHlKWT?!~+S惜Iov0j7:Kf`fƠ'~Q_/3ًHKu, 0.]>a(lGzNXMxaL _ !`#a(\XAaVr΀r$+ǚ=(RvCaJ)ni: O * ϾςӅQ:D>4',  +AMa,4 w 4{BNC'[S`&9U~pXBn-M2fw (P5 ФoH +#k⯛Dh/~ /?eYH"JG?>8::6b @l/Lu0h5"z%SQo?f(z $OV$bdKDP?AdG?ѐ>?PJ?b~r}J+cM1Е5_;&Z_b9h繎_^@b&Glev-ڵ44cOD;β5ߪc2v8=AdpHܮev&2v& :p܏xmm>Q$5y^US#N[uNg dnJu^0DoD(jԢ s,>|pss10͖& U<?ylDq~#Ϳ8t-^ .=y}jdg݋%=-$A-ٯ .aJ">jG:T-ŪM/ڷv˽d%3ꖗ(QC{.u~-kYtcǛkmb,e@#y昕vDzlO吐` 98Q9WSyX F@9A%*$[<zvAVT#}v̈QBϸh ޻n6vKK]f(W#欵0˒l8qZK!f4.5\ę_B lvnf P;y^.˔rBH} znzuXY۠46у!sql^Mؘ>M{"oS|KyNyQ @/[{tVJ`l'<띗lm)`@Yc6{˿'༫7@t)z^D 39Jj50h(,QjM @[QgD 65&; +9Μ|Bn YPcjF#}p hܤ=`97CFpߔPV3 T#X`ˏHVa4֟`CPp/ac53QL,{jTHTy 1f\y7$Df҄" l- /u- 3JI?M׵L&}USrҔ95?m!NNozԶ=Cө\g0eivJF?N"6s;m!`D*f@O^T^kԖV.\Pˡ#ϢL@kABAo([Nyq5X z^W ^|aCa:S(1KM8p8^y35U10P]i-u>EW'cÖ`Z0PRJ$k[h“8̉ͼH4]×6WSq*?g qI(eZ{M*!.Md-dBZZ}K2™gqgӱ͞b_Lׂ{9@zb3kÞg`i\E#)\{=|22{?A%d/c#3J)"3D }$#[ǐMA痣f(lb+TG4m{]dD&B9,t1%Gr(I +26CŠݕenAx*pECtƑp۶K6(, 0fuU [M?S@~X=8omgJ,j<3DcQ0mA2~,zL 9'\f(5<>HS!l4XVTw%nLYRGƄ6 +d]~Jlߦwa"͟m7\phew~*CҀ~Ltf9c3g `fſ:ckf>m+ѥNV}y +b&nUqtb-UEq@Ue OUNQF#n,Xqԥ؇2b?̤);QȨ-/$**\׸FM$(50??Hi|"BʾF."~&:6efA;"F.n{iK*wʄ lǷ˭lۋ#PЕ?Q'eG ܮۼ +-ȰĽ +2Ik6Ep { /9Ϋ2w]ݯ}9%M@*+Y)e7L^N*NPi:f?gsPʍL'햻opn2̖T7{J(T I,}_'x"5Beh;;͚s)r|Ǵoh]J ƴ߀1K1K_tH:.<"'tV)zʪ{%:j!0:-m$:?P10Q5Xy$ AJu)(pŧ)c&R咀XZ 63仚c6d<4hg4ծC=-/xhHOl_eɖ"jٸ; IS f*~NȂMNY-.LߞrOGGp2/Nx2Jt疃Np=gY:=da`ziucτBlS{ce-ø24)QըuI,@3YO@:O1-s|c$cܶ‚1DI _̈ Bps)"ܕŰ09^ѱkk]cA #pʞ@Xm%5#E & - bVՏ@II+kZJEs%F j&S$Ɖ^S:e%7͓- \~DTmm0=Cv( 2-jpEkV x- DP 0DB\=_0Lu4iw9PaWڙ' em3]4ңfs0bmi-BZO{zLH$ҹtwu%fM?LH0t%;7ڌ^ -h'8zc StdXlќn)֡/Y~=z䋶_ Y=kW zZq%lܕ9VQb'pdMWNrXCFp[wT5ͷhkG8hݭV|ez%dW[I8)sx<wbj2S4x 7oΦ|qcǀkWhn Txxb99 KIŌf!u/bq03l\CLPot?F%htĝrF!u9Dw&(G?F\;/  e{& L³[SFzfD +dKb0( @ois">{vtrФ_"}u6"@CXa/B|^ +;euQL :!:t/č"hͬ)$me BA^WuUְV3 +Kn8BN}¦ZrJy)aUYEjOѩtot +Jj1)>}<5wBc4:CmZfmu:e,PF=rfEjYW:weQ=j +5])^przȮ5Te"eh߅SBVhr᧢ipsyn& /mI2f^GJ:M|L@E6f0ў I4$s*~ḨDt{Vx6&q~CٲRNCq:l]bY +endstream endobj 13 0 obj <>stream +xdF_ӏHxzH4 ($-vi GL)|wg.ścK;\F@ˆPi觎dLdǽݩ,۷C~ gp^E5N{rӹ#y=D!\=lAplQ>m=nnyjGhA_;&Kf٪8Ѡ ݝ3\;F4B3/4k:&M.V]ٜxXH/dpl:IXJ+-:Anٟ52NH)~&uaݰ`Y}rޑӈٖSfs7T!|=gT(E)'?ì +.@ G :#WK"6BJ& a-LXZd(LFR%q䄉-< +s/!3n@7>AJi xxT.֪NE$祃)CBڜொ63Q7tnV?dVtSp&7 +P'|G35dbnX9ܨ pIȳi81Vqr`Vzİ#B_ Yb ڦRTUF(^#$E<̦VD񪒞.ʖVhX2`1有fѴޯ5H;i SUQ@tFsN3r2ЫvYT ZߤCɹW>IE)I,^@i`4;pR$6h#[͑1±WfVd.8Ϟ"[qٗ +ӥEBL(uIo/jMz,Z4KV6)ݒTMGRQaB>Gz1 @'>%I-U%w΃G ^ӄ^>Ma< qԡv߇ D-:c3. ]*ZPBuPѠd!{Cnn#o}=HBYE<㠚0UAh*&Z4*=h 3|Dm>?bokx?Y>g-jധ1ljiZ/o ~j1wsp!N뵼%E[ ݔ\8'2N7Ϡ#$~ը0&4.dJ9<@-SkyۮF75=^%"(;*fb;4m8'CԭmQާl^+Qh|ۓ\YҒ K4Z%RVоU>Mb+Ul$M"n?| 97f-ٌD#䑰 +_Q<~@{-܅o[i&dVKh +E{'ˆ\v ]қq㺍CdZag0l>7dް5Kދ,lr MzL.8 ۱9Da+V^M7p804(R& <2؋) |rĶR݁[,T6,w%60um)dl`-Q̷>Є Qvzs"%/{kiS\ELGRp4y8p$[:3T]+Cwli(r^/a#2 YJaӌ#5_' YqIvb_M1mѣƏ?þVi2dШN󗬎zb #=h%hjW'fShT*3|ot>0'?OEp٨XM8`NohG/BA#q Q3$$h +0%m@} q^FXÃi)14ev2XlTZ\qƛCNpG+q),.4C~bJ6Nj*mĄ} v9.THK+Mh;5<u_>(qy]A0b ݍm[݁FQ Vũa1a,ꮕSBK 2:vSg>`HK{ZpŲ.HsL/xxJNh?4րO5 ӧDh`tʾ`F{ZcN@y}/%;g@}W4px&zcO뵶di~Z/|geSs\êO{: +on 1Ş>\㔽){Y%$inm<}Z5Fseͯi\D`yG +(78 NjB=;#o}eX]"ih >H(7u"(rxHRbh߀犫\<-A2H^4@FWrׁ LD0y hC .aneC>4!/M+}.l+ӣhZ!~C924 +BQRNJ)R+S(bCbF6I4Ks0+V"D a{bQpϧ7 +ٺ^O F)>Ga'x`tr~5fQJ0X!;nʒ(S֋(EJ}2zOBMQTH(cOGߺsFE<>JBeIrw7ŒptoxnwhB .*z>lѐ?,4!exv6m9cR^VudT̸gsaKߦ4hVJ2#l$(130bWC;kW̬-U"Akaz[pNM;*}i`%CDlO9)}#52#bGLmzbZ'c ꅤIiiªZ=[\A*f S.+~jw;p$&!j Faj>bTx0okǃ/^ΩlKp~lIJC=C GHjW'J5#J{8_󏩥 S1bR,HMhf,Ut1i^0z bX؁ؕ>4x@+H"Fh5+EH?{bv!bLɳRGRpפ-Ui]3ʞթ(epDY52G(KPv;l7xiLjn81yiIU^5`]_PL?Jk@ _ -Y(KKX.{7.|Ԇm* өJ1lh*]#QAi ;PNO8e4.5Bi_Ec}<1R08C U˕pi< 5ʄ G +F#}"([BZERGfL?K>@]|~%XPZiWfx\rl!Kd SrXhDzyKqȞ =GBȺa!FxTQDAZ:.`x4_t>idv wWyq5Co?ױ<ōBEXsO2kXRX&g!EjI8@)a+F':w$N vMʮ/Cd3tb/y$MsT ${u#r dETpZX>w+kc$CvN, ]R,:-pv$t} ᮨp5X7 7&ɵh-nKFKz70䵲' zPK +qYJXuL}$zԖk4*b)APUJ$VR -(+XoOJ#~0$r]y_ki~xTL0k~C$gxQ,]b&OT+AJ~3U8+Whcy諄G({aT}m5$؍d@zY5RÒdp ./K\ѥI>}T,{6A0ZDZU"(N1r)> J(QO3"v$Tx  qꐗ٤1GeiOMCEZJMr3Nxx+g۶L +vt*cb#: =`ie,uuL7BS;t6}4;LR*c$`_Prr Ə \6Aqcڬ -Ew1$}BOE78~O AőI B@yړ暿YENL`VRjp {]O\Dn.?&\F +ZJW@Gu5TZZ }5"N$]F~GŹ\܀r}֑^šdM™Lg.Taڬ)uz |)b6^8h$1R l.G5/ +Dh::xBPL5_5A; {C=BD"sGthA BjuDβg^&/̄KZ)琐Twf#41?N9&f&\̺ h՟셛Q +~ԒD-+;05в")Ƃ71 ˼y5OEM񠹦.](A%.>U[ %&]%Xs4roNoǩ7yJސK`Ga!1:C; G3K>r̦2?(+fE7fA54[ a4$->YHwʪהqEִ{M9~X⦩5]Ol^SXI(V0 ).RtkP}*Ftpdwun^S?mɪ1U5Pg6UNjY m|M; + ѦJRD^Mq~V97w}(Oo*؍${jyS`y5ʛ"ef3) /8yS/8>7:]-FN +o:ܐ-;'n灛XinzuȑsA")ѦMP*qS|=;mF F\7eЦ㹥yhS[JZ&RGw;[tחxDg/2.[6.RA܆oؕJ:SMw";JAtrp*M~JZUߝ>րI'fjo^WbPtt⨻t(N}=iKkbñܒZ=J,gS,u&k̘œu{ciԤ Qظۼ&54( Sgk1WG+H,wA3tBQ˟tޙKv-]\ZEդivظPfT9X%s/1!}g'Ru(]gS ly;[Ʃ~IԿRZLO=wA)xHIj 2Fè@(7]CT@yac+!hjNn~+p(p`} > O Jq-F z,k|_,֠`{=["Ss}UufQs>!pHKS Im͔Vz}Nfa[LgbZLKCSFU*bޟj)7hb6.{۴7GRHJkZ&$ƛEUOdK +l!9)w^(O.p5^ZIRNUЇSjJ)RVR,rCB)ITɀ$?GPLS_l, J-hQ}_Sa>`ܺZcspJȶPEv`0dUy¼uSb6FCaF*`v#1HG!! M'gJsW +]@.y-ݭYqg px>?jhRvKvh)[GYP.hB>OwM$œu%orbbD.c[G[o#!^"kъ" f|x+NnG'pEcͮ4j#Di!mۭh6; -U"_-u+-U$v'ūq/uC/ɮ QOI=#4<sS3UX5Y%14ye]|Aʆ r(O +3c?̶0CQFcʾWX☎Q/x-ƫktʘkGGBöG+QsJ5l0>AOLnv_0T*l="*:SfAJoz1;[;FuQ0R2J Vu!`AɦLZ ^B'iJva*XBaʹfОNcvKeX__YʃR}o|V?89QKB핒FV b4~ATH3J188{(oل~c{Td/MBPTهuQDBe\p#5/ml['CJ5qY=$@TQ ^?ҡ&6*bD]niʡS-‘ÛnɎ8ٮ5hi-by$\4}”[fqюBjuJi RBLyj乑%ZO +̛ %)bnٞ"˂^{K7/Ʈ 2bh:3f!ػb nlS8(\ceSLK?A]\LՕ>-"c]ˋ/EjQQpu>dx1@h>~pä`Pw*Hxn$ZxTAܩC.5B"tneU-gk Ǖ*w I2;dF -:c('?fz m lS0ylg(ƪ)`%*C3g` c^ '_( v9t.̗qaRߛ,#!^Ej_>*ctѹBϡvtF7hޙO$]k. J蒰 + N|3j mCVJi8f,,q/-;vuu.=KV SrAI] ƸS9v܍"47C,7LT"a>'au:*W=<*ͦX!Veʏo2gulqYW3OoLJ15njV#Mory1҄vtrћ;ua +k.3x^$͈_̕l%+`!<PBBe1>]D%K. ]H]Wnjh,mnJ9/v@1^uдN36K~jL[i GXWg`+Q}%VTD(BNPV2$NXMbT8Dr@J+yYjX)&][h;%uhtR0suxI% J3yG!a@\$S +JJN.$4njBu6X$f*LS^j*ϔ#·"]#Jkk䐚in(b]a,sU7͙5p +(鬪Pڳap%z8h>Cbg(Ϝ1 ˖pe0`+2$gyZw|a zo;+Rf&;4@E5}KMR\ĀaIKTr񖵯xW%xzbxɦ ""cCBdh ʌdH:{_e}zIKa^ԿX$$`-d.T + +NÕ<1Bp8C=H#pp둛R,|qzD'F1Wd)f됤*.kS51hsOj( f#N:)\iÐwCL~sȜmYh|%[8U0<<wwwwwwt'IwRT"-bC꺮뺮뺮((((⸮u[X k69;SIɕ*+$%[m[ +Z_E5#aZ8IFA%$WGz_8&bgjar6Qm/qr+Ԝ#]=͡zLP4Z\'n%¢_Z5Md^ÅF#kjnj``;+OćA܏YX +[~BVV^]y+ Z)&)}Yy^n Vtjp$"7:uXuPIj, +ZӢ[y]H( ōi:eH Ԑ,?O\)-=%%ؕlfbyKLz~_Jg^ , jnI-'8NNs6(2"#&6ڥm 1͠ɩ6m?WF*r#Y3(9 GQRY+JJ0PV0<糺Q"vvww6ZuVdqC{2ۭj>xipaЙXVL,Ǖ:)t/v(ºWPmWM. o+*t629OV-z)z^yW/WO GGkG=y娋.S0ثeգO_^8ۈR$b6;}:9ű)m_KFnߺjW~ 9A8`j+-oQJ +}aͲMN +͜U+* +_8LA5JJ61ߡhiWOP2SV#liWKLo ,םҢSZ5 `Z )1,J,R5+_SXIvdǫWYcMXl_ÛgƯ&%_Sjf7I`5)љnȍxXQ"ťBG9R\GaȿZhr?4\3ȡvx%$^[u3))֯~-Da-Ȣ5nYE면\iE$(-\fJy/[WZef UJz=1)i٢ OT ))xƯ=[ϕ&(zֳ0ōZ=\\/;Z Ð`O?5ڮ(zvX&v7LW(~;ZdJ0زɔܒeZv ˎݦ(-b ӢcX 7a#փkOR4MWI7zOLj6[ْCz+:o%4}Mh(?Ir">S ~Jb +{: ?(MGYq"> fR.^v#"(ɈcZzh>fH?!HxE qA&ZG1!~ UC!H )Ɉ7v4tH$ 3b/?( drdTʢ\uJRI; |H `!$B,zb~>>PP ՜a7C+E^~Vχ#ڏ aA&у AH#{ rC*{HR"(IF#4Po@D%-,gGL!--Ϛ(SQ6Я~7IBS#ЩRPt#BFNSz(<دGP $NN 8BU[Cl#u}kQ=nY_vtqD'IzUYg(u(&_Ü"^ nh̏G,R%#"+*цJ#3ç^CأI^JBy#(vC g pO "1 +H$l;"p?%tԓCF$$ QX -xEQ"-oGGA3b!0P`*h3S .5t$K` z׿;U[wIA!p5V?ԥGG?Gj.QN^QSC+LqEQd:3">O&9qAo( ԠH)@l##h O(?sS$5PŊ# AfCMiOXz$K)TOC;z CN0@h"DPSf(Վ~ CoGC*I+K~XrI@8h]hу|sbvj0=#|{)K.QFKi>H}, +A;# 6("Eb { GjENoCJ~)Qqd?;)$x׷7G$LOCvj߰E>tӃZC5tJqث\Ӥqd/I3:;$5DjcWp0W?8~UalWWK*N!*8OKښ|:' +M`T4|rYI+{$qfngg9 Ð}ldp6pĂ^PTJF_J%ESbGPx q?H 3(b E~>JJZHD73|E!-[͑[_")2}QQ,Q;$Nc ǣ}!3@P;ZE*<߿~oDxO=;nE7=Q򒃗$鉿hcJ?0y\G!zL96Qb)g?9Dd~Q\Q6l~zjdG=$_z^'߮Ў=zJmxZE4z+OG^ }jV&kA&L*:a ~gG~&Vov`2g o~PԮp1&o=E +bUMSV69.}I'?=}$}vCddZt?_bA%WEq٩mKCrcBdH>vԂ?d pC'I C/9I9 +`_8$B#2{Q';HzJP٦`b=Q$%E_C0q[g5fZoWFg.IJmCv2NX<^ OEdR&=^z$e+0 oU09RE(VH=+'L^IH":fnUp  hH [Ʉd17Amr\ 3u&SI֏G`crޞQ$5#g Gn#)ApXC )X"shA ,D!ц>T7۷d@acR$19$LN %X|EoD!Ɏz-:mh{\j:ȞQoG=$ zN7Ls3Tc&?si?(gy䩇WA.*) ŢA(@ I@Jeۯ_ojC<$i=$ψ3BG98R<Xt٧ "SaW+$NK%,(BiqqGi^XIzEW Hb2%~r?CqȄ%d ߮ĊT#v) a)b5;YO +*vg*V'+$񨎙ɉnI ` hr5 K"g"Р8=G췢ih7C7~

fqE9? h7?E"x>zĚOSGc+=Kbg)Ef`A%꒳$a#FyӳvjxC&C#=",bSՊ@p7?SЮF; CWIRPؽ+W"+29?+# EdzI94ц&d#$e0,D''Ih0$y%ILgqCSǛ?;ϑp]jݚ>%)nd1}5WBx6T&k]Ҫe{#''wCz${$I%1Aͧ6 W^j=zя)a<+VMu?A1r=N )a=vs9 qr ~nokFiHFi>uԃ?;-!nvڐ0& ,q}\z +ʢ3$)FEРH@IF"\2A xIr⻯ENĞ |$-wH9`ȿz1-j +~\H} +KN zD,NNr`cvHT.$OV/A,Ȅ1$Ԡ+䂪.V]yz64bGGrJٻ9*_RrcX GאLjĂUԣ Z ?bRWNnrӚC +cbaN/nOS{(jQJ* eWd1'XTz==^^&EbxYNkIm?HǶ,K+^I1hжzSlgm;LfEh_C($FrHbbD켞 cmwMz<>P?#nF;MOxC&TOPȏzyA&JL :Y Oωz4!釄~zp{{[z) K&On X~Lx&@͎P~JGJC%Uԛ( ?Cq04_Nrv$/;]ɦ-Ģ{$I}egQ>rRψ3B퐐U;$WSzsHRd -"Sjc8ꧡCbOA%E9ٙ?W?? +WP𓳧<5*3 cJ`+KN^rxsXC^3XE iK*~N/ގvl}+^EY-TR2  +jN^h;Q[߲3BW Sȉ d2,Ԃ,$릇D-=%)^>J04]jVW;C#DN0vs#C^v3JAiC&D7Aa {c#|ɡ٩KJn%YG6! b:A# &Iy9^v\qxMW,VzvHz iDOdy,8E8F IjEɑŏ$ !H``]I2JP+(فhK&MǒE\Fo>]Gd0v^V3 *fuL*?;?ĊpZiZ7 jn" VKJfq36Bcf^N6{͑XhbWN~~`9+],̫/13u٦.M/xVJsh6% 6RsښɓX[WCL!{07j&Wû_|^Ԋ8١Ñdy D)IjE!h@od'=68{"HDDnq%WO*SoؒILg䂨7Őltf%5< O9B Ǒ'(! M21BA3{mɨkFe7{zǻ0;#P\FL^OisC\a%< QH끸"KWoIr4v;*? ϟJ^.m{G",3((:7bQ]LfrĂl;#l۞򴶳2\mvF( +V*&4mnz\3IQX=,U{-RVBMH +Ys~,+&l>euԅ{ɩ.,çy4J]5KP(Nkd߂F0(G9lvFFg_"[YݹU.e6*M)(?=%ka+_?fJuONi9{rbѮ_ixcӳv|~ +~JC|UK-!Dpç!񩧣 $Kl?RbrerE* xBjR Ƣ39UzAAX1ZE0;5+o\fvXZ +*Ƶ`R s$Gbm#4>R#ـ±;,YЉ.GmH$sda2b\0??1z9߈TqXRC3d9)X%ӑb&lQMlRee,_]V#\O/F64 iᨗ|?*{q6!CZ(&IdY3Y~\~J FL5lTNyF]L f1ѿd.?q[M/Q߇z#"WEzQ=%HM\(z$7;vd6ɊC$A +Zz'7ĢA=bWCnygGCziA%fQ?=^vJGIN2nȽK qђ{է&m:9Ӣ]\W\KLEt…VS cXQ"5dbCD~psgO=$I"vĊFH crzÿp)'8u~wyޑEy$KdfEp&) GVt$afYy$I?MGEDZIΌ_KJp-WKH&;%{f* OazELR }РMzVX4[$UԆWO~~$ *#GErH[\WAX$>VψS>i~~~x󫡑^GC,|gK7JC9IRY.e9JA=Q^zVO qѨ~ra.{SIÒKl&;ّ͇c"£$k92+}*ythJavrRo f4; ި"5)⯡,rO9.;܍Β_Z rX3|Uf=爩&{#v^x+)#GG?OydI'.UqbɡM7U.(KT}Bq;[r׾F1n q<גߍj)&gBVQ~JxUfܬmHKgQt2x ^ۤ[$6 [yt8@eFJo'%5Bz⸗i{a *x'':x!, w~:Cgyy1I i~镤ix8@6թ$(f74MI) XY聊!9(gaa Ǐ4M O3$cy>%I$K$K AEC2$Eð>7a/-8,0<3-ɒ,E42 GCh((iz!zg8dIahz&&y^9^x~W~^eIHz# b>$sHP$$AP$AA ɐ,A/I Ep@Q3oERܿDCC2<3?,=sDTD1D>D=T?G/p Apq1GGeQΉ^)4INY'hz mSmJT-^WMN,WYsiT]ڨ$GSTz8!IbӓCώYLOBCzG?bt`IЁvdu/9-5P (`Z`\@Sf~*ӔF_Dϑ[hXH^2ETfl8^f)j!G=.VjԎZ|4&=YȽvnfre`Txg` NbXTe +go$9W1OAUl:$KlT]һ(8vW32 +7]v()0^т4;GO2>o3ԊDL XBc^3QFvægjDO+`OCL2-1XCl:(l`4)9)%(|z~<XQ IZtJvcCq+FQLz7uAٻ>`}n8ZIQosHq$E u̅5ZZWSe/\-9*,Ee_׽G;tHǕ_ɆJb -3Gj[X tX "A7"#rBe_D,Pf.Xj]Y󨯞}zbqepw; &CU#gIը٤KQINs&|ԆXLJ_wK5%/ޡr(kSݻ]K{ +:I$#JRvOOA fHO\r'zEn䲢QeD.3Unmt\Vף^^D&FٍZLѤƸl!N vVLc~XOV +#5Z ++\Y* +iAؠCWqE4|8"flBȀ)d+|O9.ee/+?=blȋ6cz_\.a 1[MaZgi>[[l!*Xw%EștJ8Z)8jRj$wƟ11u-D (Pd&+\EI&fmd1OlGUWC&84de' yae kn@ 8N(I\eV70|6(7P@piQd>̺NN+gkgc<]m/0埨l>.KEJ0d+NZ.Vn.CͭnU}.ânyWE̒Qmxih_=e?è,rp>;m`1LL4RVF̟.6O,2xlU4)"EH\U.դ6A}+x4yꇂ3hHgEX$#j{f&) a#FCEIoW#KN1%#W?(z-UrHѫl`it pd20@p\5VNty8!d "3"6{䰤),"|1d +rFrkh=   35׾âܩF̭o?=wwZGAH qKkޮhZҼv%(Lu ͤbOJ4aAkjvh"nDťhsANY;xOĊ)F$,'?՝9bdD@IAsXU8"$(-FX/NT0PV6Ӌ +\F 7bp&u؎ºeLRߎA= r(9m gYQv\P8^p7Xj))͇a- +K@7Kq+pXwꭡXj( V74SoA'D34QY5~Ͷ6Wk{ˢ}F2Vq˒KSB7=.p)UmWzQTΧH<~aBFmȷLrz% +WCl)lgoᤊZOA\#J-a}" ]zvpoAnJ2^KM'p7ZX4XV6F.G +Tm@a]` jv2]&;7B4r(YfG-HS]^[}z8.^~MvR3"542 ב`+vOD%UJ@1t$pi?;Nt"gra:A%ֻSYg6[`~ik=|KxgE$;eZ7K*2F4CMzBca3-A8ҬO$Jw݇K9d~Ra5*"Xtkn荑Kv?l69PA RXZ%()`b7^`.TX;Wr*JVQ͚Ζ䔠fD|e,1%frh GlFַYn׎HV\b>?ACvt o_`$0'=3!]z$\Z:^D9hvZG+JNmĴ~)?Wq;,%'*}e+#SX3aUv6CjD-}ݫφ*P̟#-ߧ3ŴEͧς)6ewlٟ p$CЊBLkhgEcrԎGL'uļPV7Lo]xۊHN,rCCue$xd٩ &%f]v]y_^*F|H(u6>~E 5iᬪFI-hr>gֻ3ӊ +d]vҐUٲ=!I oIm8$'d\z"Ί#I 7Bdĕ58XInW͗O2Z;\q$}E/&Jq WtA~8㖽f(,Mw[cd̘pbNE غUldg?Y ɕ`RZ CVϽ U/$ؙ?`rEyU9&O_ yB_WWTv[{+c+`z1ܬIN*2U0K>)?nle$n~X2 Ie55-[րQiAOAC덐Fc Y 5\K.cGA/;}(٨kNUQ- izEխAխ +\?О\yS娻׏楿rV}kh䂲!GJꅬ^%_Thoy$$lcY#>F12#vlH,X=#Ho8zYu'WD{kCe h+{-* ~ | 5x +./G?\E _wd*hLt$jUGE +`$l&;0H͵LELzvIbzp[T CEtu lXg)S*`t&_Ay̰uj.19T "w?T+%#Wק3TC!`ܶPͦXb;'l GC"TiUoA~3RΈTdC:` 6\yiIJ5wS$z@Q5֬d8tQ h@B`'D B(Rh"`)ffPp4gקW?.,lfcgɭub;E 8| B@)H ^ ?(TZ.cEOH%:SQnǜ`X l`&8A +XMp#58F D&D;`8KuvY'fcnj`HXBp!0A f +8 "GR`1CMz(4Jt vrC PC↌-N`B%e^0OCmqޖ:pXe;|AjTpIA¥ńsf*9X2=p&P!D&t [vЂ, T 4pĠQڊ͚)z 6C"v !?؁x*Ta +X!@ H',;A@X.j^rR-L;~^[D82p!?8B ! +RxB0%(E)` +S0vt &,Ѐ.R\4%X?\2GY*Ph@jD Jp EЄ/t! C\ 8< H'`TjZv%щǙ?j4y"' N0"Q!ta X@ +r0xVH`tì%(4YA5b F@%$ OhB H"Cx P <Ԉ-tB̊-vmhhW#r(Da ZB%(?h|+Da J0n00p vء,lZШYN& U"~Fe#Ŋz)A-Xa +; h7r0h A0 +mf#[&Ty'0=)A%(V(/>Î4d@1#b/MT~Б x` X&< +$$?` >P + L Sa#yA|*:I*JV,ZlJ0B-(A GxB.` +R`Bp!I hl`CL`pIL_Ú^ oSh!pt MP0+PA +H($< F N HA@p5Z̨V7ݺU msM u` @A1| W"4>pB!, +DP\`:h…fY`|͏UR͗ilb%% YP# aHBtACp`C4 l@Rb{x5MqBjaMŀf 0! +QB@#A1`V0 h +B Hrxq L  wY4wR| h0'( YMȐ*L?l#aR`xA.Cl5K2; M2+C :]Bz`ܠ#x|! T 9A +6@ nF +.BF.RZ9Đ9A&` +OP& I0Є$,B R HtP21&9- +hA2_!롆M. +c+ /NP*Eݫwmu|N~VR15†LX%Ѐ"` +Q)4!*|A`p 1fPP^%j_pKPfkT_lcE jHCxv @ D)5KB#f?C<z7WwZ5hP N@Bx A5(5*3˄fҹ|Sygo#:ukسt``7h:`!x@7|1KH1%,2/8/cD)t.$*燣 +%j@%p!`@4hh@aqb7Rasb -VBVʩF}ҩȗIXiE +< BA p `v@( <Ѐ La,`Aa)yJouK1%,-dnxA @PCh2a`#ؑ .fARB`)UfkS#6e!ra1BЅ,X +I0n "@xB쀁L -'6VkDNvnfBFq3xVI1 r~LŕGp lmHƣ01;.ܬqQbbI)%`ESgſk; SNI ~Lq7\Z#Vb涃 +dj-RX" ^q3joWHtfA8^e=FMMA\ɡdn(9P~00_Ҳm>K"j%o%oD jVMH5i(v*^ȬlWG]YZ+xfzw-䕣tC1; )\p(F_<[E m4~!:Ua'@TL-l&1lTE#0`PX;LQ%&GF/-RvU. W_z٨r5Bp, [E: $<jKp" XʶL?u ~i/.濽+eşg?瀱0ivp4mmWS1xrQqB.X%G  6rG/HcKH.MPKn;xJL*&;IqTZ  mGQ),eV:ˏUʫު.av9OzUU~ԭl}AUMqC0~>zWga  IC&XRGСH).[܏{ mq"EW|b_?3oCnCJ "NrP|]IΕdپ\w`={A8!EIZK[Iip'YVEunČ*+>%6Zn-֯+l65;8 &W`!hj]|U@!&6TCmSas+W4#ejp* ݋o?]rg< őXLlŏ^C0V^,R`jJJgL+oQʝj[`ZËF +L# +hIrWVa#nU0]Uݹ !> rwCEiZ6_[K}P AlDH1v+ TE 12(t*ՍhŌEb}^79^@.VZ(+ KMLED %+ ٘P^> ?]6IQ[t6zSrHn"h cςRv3:{#)-e6z K,ڡ-_sy~+/5b(*jqJZ6uY̆[ W6UVuφKi0R^+v*(* V @åvmƄۗ.:hqؕRH%T$'0XhZhjoaÿaʊ + +i0ƫ*dq%,r(+._;J;8-CĐaQ*!^EI4>YkJ + ) +֯<ώf',4--++-T0$Ƽ_6>G,,E 6 v*~ʬ Z8*0A2dON(|u=+ɿ<Gc;8<#op@n@Pkn4(p>f~X +ɄԢ\I NE$%y4U#S|]^ElD+EfB;䚜m"żhd 2#UCkf<RQC%hi阙8YН^XL*a6`!T pCF(CUlTj$ -  Wi00=4#HNU[Epc`7DK b8NJnEx9 ݊fesD6jglTb.Vd/P\'pҭgC4Vn:UQmPA퐉8aSoƫ=ؑX|%$r,@XQϴua/v''rz!lL(*,/1%ygU|֍}*t)h~ a&(T-lQa;^^;lRJp&]w)jnh@7NP `@H$eCE t`W+fS]khťW(YE"2[8bjzbjsЀx +idu2/?1,Kт!ŭE Jl?k@PĎ*2efLdWJK.cHŕ◞LJk?QEJbH)EEt.[3':T 0#FGzi,,gk#+:8`2 !\@Iء^rJJxW2Zt\fb  +0\Z)p=piP3E [+Ge<Ѐ!lLb4\5 jpAR\,NV5Zv"W)AYUhC%uce6ri9lOTmڧ'W902^T#WØٿ&;K3a`^Q4Yz'L\58U lOq؍^F:~3 a)942)r7Tl';Ù2bZZy4!34)G %7n$FA7@av%&/ɄuLK3Pi\&(ڏ]LGQ8\`*-7]s+8Y~ vq;Slr[^d= *.;F* OO/y,-Ev=]Zjf2PPsS]hRJ-]a .9S"Gq1s[Qb\)D1.Vb,vQL򮵗r*(u쓓|#uMe \Z+gy)[֧*,$5(UR^  TMMOa_\tI5b|тj]؋kqb MD`I +p:fh6Z^5VZ.v)WO%&P`pC`!EN46HWr-2h^rld`^9sIVf,k\5M Bۡy y8hl(=w&\O==nW;9J*-?[~hf.(9E;1 XVZ#-Y8 JK&Ƴ4XT'h:++KPt5b`nl(Anx_C,S[Qb#SI5LGOt  *+RoHE9NV(|c44H,+U%Xuږ%E~}٦LGMRr߂NX8 d_Z6JJU^mJ+gQcbSݪM0%6aV]2F 3x5S+O,vhc6TpGlCRɕ\tեκg?䠠,)z*'7e^XNI 7]Z GWBr[T2jΑ\U]`LgOU"d5)%UTJ)h#TK;Fq +סt}J>9]HhؐU21݅ҢUL /Q5ϙY8O?9ކmja56J8PT)h)$3 Ƌ 0IVY**ƫ%?|ri˺MRVW ]un䏇*,/thHIB7 lp6TP[x333~݊soMڮ2*XLqWRߊJkhV~rexU-KJ4_uÊk7rMnOGzk0lhW.#_b=.]Y).԰yt]}a/-ƅͪ V9iφ&xo5zҤqƐVPΒ%'}Ff.ݥtUGJYήܰ,kk8d"El2-zEmFjG)dݩ4[Fmg(6A>`xzW]4Vx*qmoawza[~Y5< ɭ~+EʱmnOC$71%a:[_jr&?BwR+f<~8Gq4靈-|;6; Ԋ^LWoybrw(%UPN +ŌIc)4TײKeifMV$qɩna.t+Z!'?CѬi;VJ&MRvU7_q^`aWSw-u#դ؄Co\dS(aZ7M5UojFc}ٍ@?&=d^%=R4տﶌnğF,0tyFQx;]U&7,# [uY#-ޭ֎39c0~fUtVC:Q~z&V4 .g+zKQ`E,W9%9 N0VMJ]v̏JrfRp>56ӊjQǥhWOe_ӊD.~TGtͻ4{Ni]ٙeY pf8~^{ORģ@~;q+/!x[4;ҍ;&-+Mśdu7*(gwMp~G/|maE,W3UCӨSiR(cuxүoYjU"Zƌ--ۆllP<%*[Gt0263n0DBNlX${M)1 +\f$5`Hfc HK򔒥^$V"B3CF#erH'iF z8"~q%]GK|E8%`T|v*^t))3I5YےlXF_fBA+9ѐNI$Y:^V&eo֌B|*UN*gc#wU4⒝}.w:uOQ.>2bm}}wqhYۙQ7*0~a/: dpW +x-W4ݟdz84kgKq X Rlk( 2?]궽Q` r.xk/Gɕu 0[ ffC p 6C'@ NvT5cb^;KP+s-+̷c&Vd\f"#6P! jP!Vh F79^f3o`p\N56@df ATz 8@` @'MSWKzp" n \H0pR, r@TD7XdC%F*SS6sUzFI +4;` thd|<`#萂@ p|@1*0؀_-7,E"sRJ%ʾ/Y%% hx:D `%\M7:Bjr(2VV +Y(!z:7]'X–L(%x# ?# +VC +C:Pa8ˍgSv}JB|T\ZTt5.0A H@)HE@ #L&$(b9`Pض/J~*86 +=ApP/YaԸ +`&P"xP<5:0!<ʹPjp*2h*V!Zp;@ p&HH@8v``(EدR{(zѾm^%ٱRЃX Z +  @% n ( V@ 0Uf2d[g>iz7h/)vT@ xAnA 8! +^ np(*XAnJG0VYRJ0pf0@C|,6jiQCa#Vs^~u邥:f_dz|X tC%0!vP$p! (`A>! P |c)BGcC#͸*캎ϷZ?ŪaCz$(@ p "P"4`->T GJFT[-k!mʭ.{r4w5pj !@GBd 8 D8p r`bʋ NhaZypm~b TmC d7x riBIx@UO7)$"E`E6dA& "Rj63n-l@xm1+m;a(?ώjRcc&@\Ё0! `tp@@-7x8dž⤩#Hv>B $h6}R +&bs$|+R,q=Ї<%VԡP쌔I8Jr4`"l:Ȩ(1'bJ ꖂ`'VVOd)۝OPFKXE\r0*Z&AR"pf@5;DLI #vJ۾ z0f*Jұc C*vcIn/ 6OtbԪzuvUi8|P5*dP^%%gÍ3ۍU,UTrh)G$)?$Yۊ*&0 81%+%MQ3JeN+VɦO/OzmN 4v\-vB\lٮ%FkET;aGsyY5KHb+rh Hhuqt_I|FȴJ|q'fx>HʤP6[J(n8I!CӍ`UyY%'βDJ!*APl~״nSY봚~ $ Aj8CHĞ퀤#2ASA"ԧ:_YwkuQӰ,**("P NIsגYR$"EK E7(I۝=SSrzgy-ȸJJyf㘅c.F$KoQx_^-۹Z$LZ|<+ݳS{nU4ըE}]Aϋ>Ĥh>_EbBjuW?чGl/J[XEYNbY+aĥ[ڟlXeQюW҆Lġ.Λ|WDE 4-^d1>R$GPw}ZvOQSg􄌠2$"sR\ffR +:J}ѭliO5:q5;ODb;}'$QQn0Tii|X$̅as$wra5 #qJHLls\;#bwFM;̾z 1!|淒S%~3zEW_|!&B&jHC]6U A3NSb(҈tiQQD " m; ; 7+?]J`'pQ|ͱԞ ge7>JH2r-`,)z)p#~EjB_5^/8[~H +ds"S}VzB2a7lxNrxlO(m5SJOsmzhHf,^pт1ը*{  Tϫuu/xŤq"E #}#$>&`~LקHW>mzHHuѓIJ}}۲gKJ̰H44kϚ,-jz}=Kd%?A+װQ5}ȑ$i?+J00^Y3_VEAQU]^u_fQ|TκY} ؔt&?u*j >E|?$=lz$\a'r-IgQX;+gi JklK+,|XH<ԏ$"!EFkh}~zvye\p2i~rrFC1hzWo$=x +wONZ1jmU3שivD2~FNoA^R^>NMs*[FA?Fm(]erJx"l~@jH=;*dNCԏǩIO<7"c!y$@|jT^ ,Ď "inOUҨ(.85gӇ<-s q7 +_$Iͦ굏$ReF>^SxV?l?:ר嶲U\s_EY\G%y>_H +YΖ)-OZ^SsIrԌds2vLYjtf5!IJ!5+!Ύ45W ?(v^ؤ1j23RMN5kzY q}iһϰ<cvYM:th}xW_yk׽5sŨeh&VQ)1č5RиF{Iv]ܖٔVMM(iF]|< m9WkM\~M7&YiD;05OSSpv{=ͧ OkڊKX$zJUʩN=p˂!RxdU#ݚӕ,՝+/%}DIդIIFI9jŕަ&Al|VMu(JRO׳/oLnVl^lǮ5l3bH>d(G|6nYeuDq]}sSja9[&6?%wtݫ{OѮɡzhOEo6_hTŊVr9zI)H{ٜ؄5Xj-,gq:q{\Tfm)>z#mh&A.5&4&)sԠjݲf*>+=ɪ} \gbFT2{Qât6eQ10|ʪn:u{Êz%Y\&$~4%/4,^k +W [  +_$95yP,_ءJF1^̑1xH(+(l9SWS.(G}82 5RXF6f[uԛ-^lHk+oaf>dC LˋEnĢW)\ .s3Ri~HDUiNkfQZ; $S;! +T-RZ:H!Ka)pWC(tNǜI3_vYI4ogx֨j +*ַ(Ld(Pp։VW]ST.UT(~u~?|wXذfdt2ҽ㓝HC 38~fYcەj*گ"iP:c6K1ڷ!ކKL(ٜ[jǽ*d'7?-$mIZ0R`FL,fr|p4'8( ?ԒQD˿Q8"z(gE|3H>_d.XhTL,>L/ySY2&7MVFIʶ [[x*DGj8@C knB,ܢ~cq@dQd%g@|@q` +܈A!e\wM 쬏mp ̯uHrⶢok>vL'n* + ƵP5 `#Ɔ +LwwWPkNv֋ -Xt$~> "x8ijnbGLM)uϠA# ,`OĆAc"j8톕m1C& >a0Q,RPC-3R^'d}E)(F<6OP6˗VV< OX;ɵTj_IvXar`x٘l0IL(1Wl .Ƅ+,Nl,y-|nLrnK(T~Lʢm䢰v5Q_N+pN*|z:a5YN\}l4rE /ᮎޏ|T仠g.A#Wp JPMb'_Ps E V`R}_WINa-1]fƯS %(D1Y伢7@p&=/J2D4OI~T^~VG+KHDdƳ3J^^L-PbKeHG4rG#7bl+ 8NXK_IsWHfn g#.TZ;U}#ū̝).-4T`))A;jt6l;ۢH~vXÍ}@:^l&?(*1Zf"@>p T.c0Zbdh}G;poٌ %Uz7GZĎ&$x@;Vi+v+5&BhB h v@BDX p@@ 0|1cĖc8;p.T3mJV;+B/nPw +GSY]۩VX9w(䄣l5,SmR3@qR[l" bX~&r=͎HBܧ>3E "v8*,{FrTxjTxXfXZ| +wy)kNJE*NZ'.޵U'ѢH!G=*OmD=%SI>Szblok&*7"*_kk[,_36޺"o&ȅ&0JR316L@^44A@4n͈حVb<q M k6^&`#&Eoa205AF~ȱ2SVJ٩t|pт _yDЂ$vq5  P,@Æ )fQչ 7ܧhe$8r#%*Ji0LT6SDRhSX&2i4RcfR`n*>J46VMey եGEPO Q8Х4Q^];<Fe# AJ$:aGV+wMP w[:~IOLOgf Z*PZH0LpS]3ŦJKI>tEO%# +WKR6(6bp)/_fy[TH ORTv]ɏ +QUK?C& wk:W^Kd'2SNuߐ)lYu+)4f-;=6lP`s%ȏ&C6LQ,JX0?Yp|!4|dէnGE'AF LSo%*;MЭJ3U!xW]ʴj9kߡh'v!*^fb} CƵmFWAzqcD<ŋ Vw8a)"pFuԙ;׻'wI8ćdgODX?"ǙOpW׭@‚ 6&+~܉Fˋ:PHu;#ܗξFapz&=> vTMY$iA3+/4΂-kG*18ۺU c0jJ+ō+,I9qC,J\ wK [Z(t=VTtp6VДdy'+ cm鱽mE#/\rءL-uzC\ry̍|{! JHovRbd?I&mP ]TQP)tɅJLFƤ*jJZ2nlzU'GwUJ'\9J)ꍆc8 +ZIG̭ƊL$7Oo>/6KL ,Tc4bCc₹fn&}q?&(A+b"gra٨%bnxZ\6*pB0ɎU.U`L^^Q'8E5F.dT%&EUQ/?GđfCg^9$H+(uU4)7 hk 2@Yfb<9K-Rrذq"N*3RV9ҋ/j ER*e M(9pq.6\rOE%xlHj PWt*U\% jV ]7 +m~&g2'?oXiP@L2&Gw|f@aF̬DT$?$3&kZ6v-!TPfN^J-ކ GMI1b"`cf(+v)&YnQn;Ltj.GYiu+y~p#T.p4D=! +ЙtZgOzҳŏ=zP3ڲSL^kVZ0W^D 81(å$Vzsɸt>s \hVEͶlV5%WD"i}UvlАAAq BJAѯzDA(~޵7`)QQE9$2tOƿ7VhISKVzaSKdR*ƉjnO4*DboXB %dɨkFYV oONR'l(IN *[y0Il?X, +W4ZJހ ̌2KN3,OYuX9=5\mi\^T, ID%PEV @6˕f)+7d`~mwO9rEZaElDJnXLOi'l>)U2Vfh?E$vcxT>lg{Qپ|u;-3dr(?.ը +gܩ|HNA0[Pn~)I ig/#Kӑʢgz^-K(pUSllg\ }d=#M2fx"ċTT`>d~?l_ExS ͥw۹ jxӒ~q(p(WUT}\ + U_p9 GΌdr7BҋHZZU6ʷIsbm + +Kɫu(FK,E+*yIЏW$C\n?CկGC<d(1OehVq$'9Ta80Aj̜(r+:dj4TZ1>]tP!eߪ'Ԑ#T㉉]rcI,X j~1?R8w_\zK790NV.kWQ\`n&?#="UELIn#+9!#ȝ0id^φv gh#04*2(~R;A1"KЙdhjnF!tHz%fnIBC';6R뤟W|bnߎE~?F!Ѻ]yA H# k&ozb;ƌ6h\P7,:$`=49؋WR7+ok?Kcxhɵl6bsK oya9BE EN_ 3DZvTT[~S ^#Y: ',( M_]r!$3==~PY.(\Tg?W$Y06(n>Y2*TfBРE WT lh-7^ka"$9bU⧧)y%upEy]U}beRY9bp<BF +,VI+5CKƋm +l|8E1?~ˢBdӎ#rL-/ƫ1-ƕ|T4A +XPxF1$5|o?o!(E)W"QtJ(1*Ou~BXC?uWGD? *ĕR|zG{բ'?C;pV|(/Q&9TX&Mo8;%UϯqtI\bl_#al0Yt(|.48-;-)/U'} aŋYYl7A7*jC]F@G؊O=ՌecfVTzH6hѩ|؝v%FLzkhD!1jG^_?Mv,JVZdŧ(qpLP-Tpb01g|kr=q"W_zȤT&Al~&{x~XOSV\PsHG"GZ=>,:I2n$W>*K*C|552=ًngF|ݨ”ܕ[? $#RM';)Vם٨+H?;jeL}sCːTRר練fbnԒX_E-lgB(g~jrԂy)!^~X-rf7[E)%%+ +R6\1';|, +K-觟, [ d-$}+J# Og[^/->%~ *+)+QIɭJṪS5ʳ`kO~$Cg +ڐNu%/嫎r" =rY85pu8OV$Bzc}yͼo2eU_E' bOۖ#+d#&e9ڲ=gGClGL(WG߇Ʒ.EU\-~ՓE 7IdF;lHyJA9ӎkiv8a&krՌ*P1pBySPy`ȮNJq^Ys~H6ѤvOAڔ*6}T4@Nˑ*b9#/dIh^чǻ(>ʑJQ=&damg$,2Q$6فإd`rlzvַYmP2RE0jטmGӌ +"1R=pl.K7b= ^ߊ +dEWfj&I/ה|\V8~N_lO=v'xYe/;=J&}TjE*Y4"-z-ی"qou#D~uf>Mث\P`QsC KFŦ\O/;)ۆiV2 OvRI1/%!ArG ϐӂ_L7]G+6uGH"#hç;SD'2]f?koE89R ncAf^.]Xj!FqlwcsVfF=E9Ŧ7+5vRtPF7~cM_G^5 ZVD2W(`7Arۺ͙8N}z7-OmYON#o˯ ,hUkzڢWמl$0DL/!(٬IMVW:$S,O]P&$G6{ڰ=aVOI>iv\Їnzy$#lb#8&'`^^$#zTD^w>Y}vwg_AY޲Yag˪hK- }ՌX>j1o?$+Hf,Ðz;Rfj JMʋŵeGVٷ͇_^}ϊRH%ÄŢA~f:ˢS$*a%)1*Lʨ(v4M?HSx w'7L_OPF[9!_ ~,9|loU4*)_l rzVLͧwS8zRZVfuoFr._xBƫ(aL]٪)&[bֳPToIqɳ؁EhQo~Dfw]iE#7D U+r+ga3H+WMrIjAX]ޮ7zeګ-/miϒ~5_-_O$_,oq|+!QFM,wO9z KPrTH.ĆcSԌt5&VA H[/J_H=vc%IJ~X +nZ=IңbWcկ@eR3&IgGA2(R#,D~*RI:̢~z4fX .j%gX'*׽}\6?qbUiXv + !ѺQoIU,VP)^6Ah _ 21,[ƥd?=)ǩ( +O)4-ۅm4JAyIJݪ,oa9OcMx75=OwOay[5DCzNv)~.G79~~`zUrC|$Qh[Βǰ[端:e j3.05),bW]rjj;bf/.\-le +JpwSwUF0^isaL%իpObgpiͮYVN_[RujIIn +ǐS|Bƿ~$ ]1,~\ ,դɨ-;K ?u+is'ƿ ȒI-dp09ݪ_M[7C<;N1VJh~<գ3t&-OYUgW |77zUsbܰa|`WCIu=Kb=! }$j_l1W=y*L/ŧ$XVlS%2M!$5nSoVޞvUq%EjM?D[я 7wYNvrPiG\ }3ǯ-'y75"^֧%Z$%HJkgMHI}4k/boi^IFaPۗ̂_Xp3WgʢM*gWw;4eŗr__6W=,#ѷӍ3Ѭ;5$WyE $%nf=]PMKI6ϑYR~ }0qT$JNwԍRZIi>Nu:s[rAr%ejWFL v`P M^5$#C7wIVOwO~S(mj(êḊS^^G1<Ť o>lrCKtV׿8Y)&wWSǹ +4}aŨ]@oDWgz&vCQmi1Kp"lv-fI)ې_վ՚$܎bQP6>stream +nVes;j44LWYr=Mz +ا'v/)YFΤb֣`X滿lTw*&'[>gþXpu,%l1}׏-*Q*UP qIȍ( +eqVX2_0U'5Gs2KIͲ]5h1rwRD厠P؎Cٵӭ"t`y0+%妜^(ƕ藾Gƅ QChYa+ڨEN #''=b,WAn[vNǗ 9^zD@_ wy,|uh>M竟NLJߎ,lΏSYSƻwdզJNw_+ +,r%Нh=;Lw둓}^㤥p5.*{v8%"7rev)=ynѥ~~t-[pFlPhR(KbC4e赡BE //ܳӌ"h+b&%(] rFrF[Z9 ,* s#oQ宍twr?ֿj=Q2"lwWzj? (Rbhߎd'e5`FӜ3*|sҽz7Xn]UPIE9A9nߊ.k^ jQ}u/ Ԑ5R\:;ǘ/L&MP n`f[)jY)8/81lp6TV13Fl˯-=ՍJLl_Yٱ=}+>Kn<Ҫ[I΍ڔ,r7rK[SLsj6TT WY?^Q4{",!%|ZQR',~Vn~H#ɟf{_9QlGǧޞvCzC־$f +ii@Љ`8Z`3ucˮrH$g[R6w;^h%0: s<ōdl]klv픠¬n +v'-d5)_k:$2]-†J eMپU,ϗ^E5DQW  ,-ΫKȣ L~MsxXy|Ƴ6S)0053DXp%,ە=DUcWrq9 JǙJna'Yl]r5*hvX JM$=H .+/w&z>plgE.q9}k~*ڌ=UTâYV5 +F$ +&W3)U}b淹m#Alws%.C%wq2/% EVbf9!bX_2jW2H#%MdV{>m#&棔Y+8G'+ h0.8`h H05$A}*\x참$OC)Y]5 ]h|Ֆli6='}K5gBEvR)ɊL Oi8ZrC,-2#(hh?=+zDAQpVrFSXӼe4|~~#vJTncr_=+TY(vB^]Rwn`v1}=i4:Keyʃ l*ƕnM0HjE}!cԀpiɊClE#;(pÉ"bN0O+0;lHkG91Gˆ +kwIU`e%WEm6Zq#H +Fpg_G܈ݙxſxm +Y0IpSnr5\A ' ^CYYUV9 淸q;3=^zbч~ZnRV,2S9fcfeOC?b8M~`i`x)ڄ4dvPLJJ' 5z𘘏ei.U923F3J&\ǩSWEwmzcES斒W3٧"㑢M\wG?5|QշUoE#xM+okfU\ +wnj )gg>KE1;*&%llwY/?$Ɏl~NX;ʪGh +GQ(֌Ws \ (A~r_T2Vd15[K .UR ikoEI E)nԲhSp)n%/&+]Fn^y]xOǑ)3AêQN1K+?d}VYKqja,FIEu|Y3糺M0ǡGP cH,'=MJR neHmț jv~Wi(t8Z\66Lݭ + |+waX5) OcPT^~VEC-FR)OV1RhEbkf} +Eunul20$x$φvئfq2r&͇U3ɲѓ\F  +B1i\8N_MG$4)R?~5,Np|#WC+*ֻ?H9hn8ht59TjD--=#c ubmNf(8:ͤTf/2^FBQ5ݝWf'68U׃q)9ƷCXeE]). noC8\M5+~'o]zn4^|٠Za`{.oE *ֻd_QHݙdV*!3hI 6I.1>"KC o?GMaZ`hPy<5`j6S΂tBGVggC&x%E泹1NT.|YLO]Jvڝiv6[RHtd uq'n$+/)l~F淬p%-1=_%+ڝX-T\5mBgn&z+:fj6)2=-eZSTtFUF*~{IbZE}*B-u2)7^P ^z^5*C3h6A.##fr#gC;8@ikݩo]F5r#׌[= #'n;^Q%EbfNjκH(r,5Eסfwvݖ׍QT8 WI)&h$HpA$fL1bhfILGa)WM(E'9`Pm }Ibj ,npg#MFlU0 0@ FV)l= ?54Ej(V^=F ޱrurdzC +5+7}QP)l6>6\*̈́feH6 +ZMxǝ $Ii1DJ Lbtɤ5J*Хjn(Խ3՝ؾSK٥*A&(EHE s'JM~iKKa'\C&\\ +\IGY7jmt+2cWz[^{Mmºg} drƻ7t&,OuszCoUH +I>Yͤ i&b 2z[Ξ4;ƋnWaL1NN4PH x2\J5o\P q/0NC3<;VEPrYVq*VqۃhaFI. +8qBǂ+,{ŅH;P@qY^ͭT#g,kKL2l@`/pRT?nPIHI()Lpjmr&(~6J#8IL+\(r+.u,\I)'WoX%BǪݑVHu&*v(C?,.9MvA(bMϡ\R䊐A3Xqr39\Z,l; +YgbltbRQ{"()(9jM!&P&.d(Kk&EP 5aSsK؆LFLJtzVq# +] l02};Uo_^ _I :dH2NR6RT;dRXv.u5CW+.݆K-BXcP>an(2,^.JR52^Jgb:.&%lMGK|JJO9'$F!C)lULH ]$QN ?Agd)k$)WZ/0Nxy56̠D`WMyሉ툑這F-4"q(^q|EC +Fo0bhf[Ϧjc^wً^`붉/rMoy5h.UR.v)IȄFcriɚoSSnq$erUC`z¢YH/JN,(V\PX8*UbboXA5@F,LXF20beôXUC6dpԽ+(b֕5_ōZC fzPF"wJY2\j1Xp/[p>ؠ(9M!`HlCKX/.;IioefbJJF1:_ݥ *W֭4(!#"{&V[tp7 n[AkڢIM^˫0ԃ[Ydd*dF]ȧN{vXqp1$%¢[Tn2jcnztbd{gF +Ewy$ջ2߆ayWRkf> +Of!2l:Vthf$0@xIa+ll啷H*Q#FƧZT ‚i2ܙ^-iNI6VZ h %o6JQ(U%I>[x#qB)sKA!Qr!VlprYsŅ!GMJIʞ0ڄS)EMFJfIɸQ+זWK&PYHY%%$-\OXwͭcbbh + *.Vrk=U؍d9ڲO gOC,p$WCT+rѥ*&SnxzCLsdPP*d|E7ˣ5͐K_ {qi)p'Rf`E ZIɦ (r+ ŎS`F + ]zU/EfC@#3Pf5ø6|+8r\\~ĊJ|ǰh +Oɥ^:d՟z1+F|+ɨyLQ6b\x6/m3YF: +iE?iVmoy4d&vfUQ/!N@ig~3Ű f@25N~`R3IN +O˗lcEC L +k*Wć+,Nf7) n+py 1C6Ȭvh6?EELrI)>I9+c#P*P%4H@ E#BѼ.>Bb 1F  !| ~5I߇{b~BϨ' /UDmM]Ӏ}dt)RZ[*ğ?Oޞ7^Nuziݡۏ"SNRmZ{w;*)QN~6.58&Ds%HDeȮy"O4W*QY'Ds%HDeȮy"O4W*QY'Ds%HDeȮy"O4W*QY'Ds%HDeȮy"O4W*QY'Ds%HDeȮy"O4W*QY'Ds%HDeȮy"O4W*QY'Ds%HDeȮy"O4W*QY'Ds%Hh~Gb. t4n}>VfEoXH$ǯ@$\'X`Sǽ@jߴ:OyTw]" sLrW- ֓ۺV6sLGiWe_Gl8ue3͹^hAPp}}T315XI<-04p`,"?%WYy`sX& -YWc#ʐmHA]B5 A1]#7 a9umFV&I%d4T<̑UD9[YO`kU",š{\C8^SP)9ۻ e$7ČºvN@E?z*WxQ3)BZ Z\8hӼ(0IhE-VD`U她F6@tsTMRV4W?![ G]`?6>=pXGAo̍2)7@?qؤp]G_Ź m'.H؟g(۰`o[xigԲ!>A _ {3%otM>R*HDj@:f/msKDy~y#LGA.zQ?˜Bu֯[l͓q)A+nYl\UD)юB^n3M3L>&zV<@Sp_} Q +c 6!N(|~x3YZ(.6N_h +YU@<l1+`Q*~\DxۆeŎ9"JO}hr_#D XF Aq)(b$K1f|9h?>5ΑS! H܄m'Ie&v4t*̆x8D;v܏o)or& <1q(o˷ &Z6]N8(7k*gnҔ!V'tƹ EFѥ@,SI@@iͱs9*P~#:FFsE(]>[TB0u)3 sGdMCJ [|?NQF&'7O,<ڷQk9oQOO^CSUΒQ+me1NVd=Y\h?YɗfA%&_[ɬ,4EXCŒ 5ʔlq~{8@Ցemie +<67J$jWH -Su_5hrm>ڦA( _))W:dLi^Ï gpY֓^-F9@qdf kmc.5Q95?I;EF)|(unqz`(4}=C*38] :$rx(A1IX M%<#޻#4ۂ1ENwr zs-ΧI;&1Fi=V #G:'[S%V f G-ZKof2sTZU.iWld_ D/ XQ4 DG᳜z6hலOJpPJ4ZߧuBy(< +Ex0ЏC!!h +pƸjUiHҸ ZM]NWfI85c{h:g vJ0խ=Pn>.O&hn22@*J 2hYrv(U/cK!ZŞ1,8M5j " 0HLXH;*&4N3pQm̡!mA %ii09DH2A~Φ] 2Nl+aRᘰϷ sNnC"H3:q-mاB /sʰ2LB&xqԯJ F4p33Dr9^74s)QV+Ty7mgKăyᔁ:[4l(ӐSpM[`mNET j]q_`X S'%{i&F&hKρ5q"]ZTÞU4N/ȹ9Y|?1 YCp-:}4?4J*;*++]DGQ22]!~99Aʒ +T&=0$2:)g|/=㳎3sm߾u1..d\׾v5т'/AP%MHNlޙ7<\ŖYg$jd!Jo>O,0EܔFiQgjUr(+M|6 +ٷ7JWN43x6ݟ*ڸUZU;p;RhJ4j% pi@N~x.lYoʳ^E?OK* + TfoG+Y,Sk.3p umyV^\! (bg`>8e-4yIqdטYT.1wFiqq܆FZ/ZE02hu`4;AҮ4G-Qy 2-DSj-Qfbd9 +k!RX{SG#6 :XXIEs1!kP8ko!mbv^=LR4cȰ*(*ފD"$KaT. S`duq!MF'N'NF5g"ǀPϦ voHm`hՕ@%ܳrHHB"~meo@!z(48fЅ~3HLx𡠉poi9J +h~Eo6NVYLkf|8zC3@l$]X$MA$6 U +ElmCDe{"Ƒ$`E( W/Ͽ7]˓<$ұGl;R\GK52PFY>nջAp̒ux K9<*6oxo/aK(]Oo{ȤD8 fX*X;gU . +W6稥\9 e@ Nv +% J$Ow"z[ai"@Nl`D}Y#'0Df;Si `Kx3@|:-n^38,xoW8,\x%4XETϝdkx,DK0_ zAѿ9{JfJӮF j5=~L1%b5}Gk\i[H[Rx aDdC)- 2o2t+5]PA@2CwIA\kR$#k,\bio"$ JP!rGVf{ oKxLJHW'BL泅M +UM)$7d!,̌\xZ+JVfb3Q3j\px6} 7wF}CXkLǤJfo(n9B{|o9oX ɞSA\J2AV|_QۓD0ӧm7ee<@I=2x cïM^@fi"&i>^|Ћ,]pj"~ߢ?S@ +^}hi&#7d )܎u%y3PeP^/@S< n9wS7`Ûl33t3PgJWZn*&,d%P`ҚT_L F/\8PL;"tFwzPb *ko1:O"15dg_=ī Lx*lŒ|]Aҫ#H!~$l>@|D VWe!?S7ZAGNڽfE e΍FCtnuk +R{" nr{ po˾Y {ت.Ȥ/S٢Tu@@ /p) 7"ӏEƑZ^1f*).` sjZ6djmHĨ<ۧio9Ykʋ׀ N^Cʌ;^w蝥y*FFjVVN˼>mС5QYi%Sq;Nͩm/0]Ԏ+bw+O2IэGO H k-#|VW%TRE +FQ fi#|BIkT^={Fbt{3d6j$d˒iL[XΤncqTĐO `ȍגD.v̘jR2m-[ +2@ +•hMؒ)(`5/yAJ蜥C.DI]tW^^5.zu<5Vv 5:ǹ;xS8MjTQ{|XCuTC5&?eKbxiZ՜3{9HPjNc&H#w XtUv[rl-&תci yNڄ3KѴk*Vh&8L'FFG-P:Gtޠ(jg6|{֚!>GR*#*= +PPr^PfqGk-.YM:1`%+23Yp` xchpNUVϐK}uચΪxь0=v5Q&@">Z$:!''_۷IU.Kȁ@6m*?fޭm| s(UP W,_} B<J~I+v)si)X +:c6WrWgj@uN!*pS">#DzSDJ*׈_g4: ͸o@[Z)kרk WCCd3"\u6 L̖(.e\'*K%mQn.^#"C4q2ia/l9;ͪvpAT"lʼP4MF9Tdzy"ʉy̐kKNynv vY&h$Zhls#{lI>/vy;lw2 5s@RaMV0864 QQXT2ޱ9n)*}]E@)"ĐrY~ŭTe :Av/%]łK,wi&xW;مlQ;#9ϗ5n4d*>AT"zT tBm +EP t@J5HEouR)`[`Nލ;n5Д.UˋjHjNM;臭VE=IFF8TH +(1/x=+Ҝ_,TDIkNPp;,Ft')_~W'Hzxh4DQ:n%8 H2W;OD4o4X ѾBk!-Py'H~MOv4]P1OQ A/UNzZnR>"R N5a]aPzO }DD+ Ê"2/,ё3O$ER4Q(Kr$[$i yh!:h +iE9[F3i!IC6 jDYxi 5QyߌvNC$x3C6ř8k3Vm l,4cEGZhBI(9ၮ8R#*U<ӏׂa75PɼP#(by&y] 'L(3pH ڤ}훩C|K&2Ͱvyqپ81[+?A9a~5CQb(nB妿lC-U jε`*ۨ|XBSOOIOݧڃOOϲtX8<\'q).RB~GSKZˌz9qp< ep^@Yͦrkjj-vCk1hf--ern6fSw^%RIImJrHN#R!TDi/͐}X C@9h"g44Ь}~"x o34%e⒢"j3\ _dZť&}dhI)Qp]don+%vs$6^\h!sJ\\`f8'ڬznvTl%`,[ ̌ۊJJIJID2ko'EeEE@aT*)0(0Zk)y)f[J j43YRsNNJPTZp._FSyQy!968Ңr"4[䖒23(ʭ6;Yq)/B4Z6+y LmBS9Qѭ2*tr4c86>r_n(K&-Ee%O@VNn' JIͬdN[r;Q)QUr%sBSSn*rrr!o.08\ɬ~GNF'Q-wpmғJNV\bTPn-~ zz|_XiSi[nbYDEEARj5VXj$SRR̈H$ "(T4@` PCA@ 4 1Hm93H6/(v'Q<-8:ʵd)&V|9 Z412 !6ad3X*M; 듢Xf4]\xfqfHW dyx԰s3)fpl0X7|pR+)KU0_v +'^MIEĐdmI6LzbQ.7+KH/^ރnEYpήlڀ eջpǣdHftW^F3?Yެ?3 B S=NkJ…;d.ϓ6.a +"$rd4Gp|Od Џ /eԛ.|DS<ٛ+DNq"M4ܓ }AV.w ܁f?eW-o{h9n8$:\b'8vL#="!7iGp)w!dw*;+sR TGـt +Fp9=Im]/q-678Ua0Xk(:tM +Z/b0=T6Gry$E #D۳_dbr4|a sCi1j<ٶvJEOU`]aP3=Iy5RM8OUq!'D=˨X0M >m⤦Gc4Mb+i@Y|pmb,xM(ˈh3!b- {\MtFDtf$Tj4S4/o.^11-lI!=)޸)H*bQ׶=)RlFaD( eb\.C*aȑZiO@iS-־ Db&꓅S8У.tBh! Sf1:b2ϩLEz&)).;UV$D 64TGjkC`ywg'7K/GrJ4)hAGm1ҶxE*_[؆ÂŲ'bR_ٲ8Ւ11Ņ勍_ƍu4*RGRסߞ WM)ÂF`RQg3fcV>~qJm!QS# qLca/H5RH('ِ,OHwd()sB!Zc_zшāyftlA1ǹ&Ҽxߖ6ɑR{#tx,A)\{HnGWVw}65f"k( +((r(UQx|GQ ;4 +ácCQH(;Pd R`f8(iaCEa!GZdQ? +p(nPlRy(W2a! yeJ ԬImaǍ 8DuqC+IfkHлMbI&ԽʴzXԴV9ɏ]DLy {*{]G +)YbP,O?n.uSbyg95r*K,DjLPl[25'i-3\59m5[!H$VfH2n)STS|Cn z˶]Q<2kC˫aClᅽTr|nl&@A8U wCGz<Ӑ{c0! +Gp(g'ݕU+i^=W4~z á0{wKZBdLAAc`&  $4# "P}JDqhXzwE}3^c'㮙*m oq>fǁN{.uJ*UoJ8ݱih~QڪWw6$b=:,Tm<QN&[C5I6I]<iWV49>v&eW|ڕM^vmnؐgy=/GT($J.?VaSzLaZ|,p(f/v{K5Gmn򧺃RQ{m:k3w &4їՔˆtPZVaAj|Jjv9#٦p gc1QK~ QL{qy,s9J51z,Y +d@@B 4Ѐ +8@$ @tC`p(F #ȫPn:myIj[ŐlʝcÂkؐ!D pkAPP~LԺ!gǂI:)nfD6A03ݝD:@udM_k%:j]ӮiTҦM)ܧY&.JK3 E9EhiڹT?Tu]= +Lg:WejCJ1t [g9X{4"ujWx+zMͅ? 1 [̈́Ź^qq]E#ٺV9~eM=ƩLWeZiK۲|DH9GܛY^^3l?naL $h \f@l2,˜14Y:v*r +N{!nVW(yg3 R МjCf,xF/9d32+Dtwu!Bn;Ja: +#+D]5?uǬ}hv![SϘ-~s9?DYdaQ`1 / +dT̻ :gE% ~%xhmȽEo< 0*Ǝ'tBgبS%XO3N9Q&tIܾ}D5#i*ޏ+3wWM6o0ĄFe2%xhMȞ) +@Wg#0 B ,ۻvQnX Ӳ匀0 A"hxTH@9Z>E#awخ4Gùt(? r~l7F(USI9sMF.ߏk8=j$a7\YߤL&9$iU}Dq5i ̰r|VHy*>[}@#=Po㡊,v"io(aEeQ9{q>pѥRH( -=<5: +Ly^ѨNPPžfB7o$3|$TRgo T0M+"Щ_蹛;AgH@ҠL{ ,^ Onp@e(VjKl6*)K% K?AkE7R|jas Vr:,\ 7EY!>W\DY0C_Pǩ BɀD@7V@(64!&+YSbJ ">';gv'dD0 S D  ! hݜj&jŔ.fɆDW} +.;#2`'"wu3e*52ڷUֲNc-!s m2^T~i &?ѿV(g&:0k29D6Q/@ {$]>b8bZy +2o՘Y%WTzHwO +E&fNF2kTUE_A.:qG߶niuNV*mK(Pvm-Q1X@ lj! +lMtL]Θq V$ 'v4R:u-ukU+Ժx9">(V/8Wǂ"M.Z!˴HtAzZa"Z+*Ras~Di8i̤Hh$e1 +k}xRg_+)֊rk]Z]=fxvCv6WP%fj/p_.|c˃ص6y rz[̙Z9$ae&/`@/(8 +O ) $<$jg9(kӹ'kŦI-Z&tZ筘Cx *k,"V"[ \s3Vmu` +Fc8ޝ'M>`A ްyЂ^ PQL*>$u<0nؚ B\D3s Ov(- N$d7 **Q}D&/G|"1#<sa+B*E`3<2~Āwޙ\-O IH0pVyi2,s@'&=^%᭭w宭<0mLfkA<~Nrp(xfTb̘5N;Lcn<Q&ٶHA+WΨW?j#̬sW̑M !̽1DL Է)¥2\`qb3I;OO9ޚlDx6EDï66 vi7pU{{#:Ly%a^)Ρ q_ٔ,5ݴšN#yC0'Gf t_b$t8'HXa$>IZ]\U٫Ij$:f=EBEEžsE\C |Z$HHTtժg +:HxCЇ_e7 6+o@F j#b1 +ʴ jv5RHL{#A" c \C@`$|M-f,ٔz9"% +m"yRO _R#'FWK4^4H*y"^8╸\ >Nj`cސ=1EYYy̓4/Wi_Dwú <Ţ˪EZcr+I >)!~ڈ sv<2ff* $gh3b2@(bW 1Xϲ"F0en.}J0B#sLC硷;B76JRt_B <,ګXgKQ fKx2bcAdnj4b0( n<`wZnm{gTz,'*.N%7_I1ϱx㵃H\ D1-iENqoF7.*M,;y_ͅ90%P늘aLzKP8*](Ap)di?URw%/Rl("osqڛ9 5yIJ#{ SQ#NKC.Ud{g }o: PDj 1L:Ŕ)^$SC%!')$רP +[#PiY7cy=VH H4A2θ+p0F ^ǬHA3JLkw:>dO?G6& >с|w̥G<ϼKKzr>/UjB"՚-yν`MSkK#VWԐ1ls90Oo5 27ݸu,B@ _+HÆQTGTPbk&L?`|A+$pԚ:p CSuKz%(Z'8v֘b0GI1ђf#(0%Oj"tjG&[5/3.\xA"678K~d,p$ a lbaWK +!S $E䭃*SwbEJv~@-V0ލmۏs+ќ*).UaQ!jjK312:YzŢIU߿ f +Z s<"N'#ulQ:؋x0] ~eO|Aa#eVD;`1r +:g@‘E>Ӟ2Dw.a(4,h +1V_kOcIa8@~1Hs4lN/^Z. ̋Zѓ"kv"w +a,88񂇞 Nd9HV'4Z<6Q,')w" hNq9B<*V_ a8Xq꿜u4IH3"x.'&2< @59sJrFm 44<1nZ0EAM_GDV +P{|VV$, hPP"zUyz=B +5ڹ Cqc-L.G$wD +^?ڢd$8CnT9Z(RFS ~Lo`QЗV0GC HHpPO֕\G\I7d')_ IICNV ?6 x 54Ja-r!֗שV?HcZ} Lu*'Nd[cnkXYYŠb>*k xr[1oHeqRJESif;D:έ Mļj5B"jDr&7[,Z{F9q2S(C7xs +X +j2q20}t2Ȅ-xMqcBT1tbV..bܴي'wZTLDjCP3ke 0R e_ql\fKOZ)9R:2\bqΤ #)4jklqXa}qp S=Udj>u֡φL!8O VC#elpjլf8KV}z#rVM jSU/(- ܪLjҞĪ\G`qV} +ט9(XRЫjɱ+7v9AXVT,*RՌ"ވ#nTKN+ᣮ%U^zK[1+jh]+zi^crkV!N&ұ (#m t(Q_lJ6^-SyG=)~%±ndL`Z6%`QUoCսჹm6.~,ذ2Զt#ե)6El-\|Ejyu$W$8 $bXmju1sv5O: X٪d:Ff(ˠSUj:x@Ud1ϸkOV}U3AwU i݄D֫3UK>[CތtAqvhjU +< EN M}ޔVM,ئz#32O2:e38ʎZCh^aqXyHjz15jyqw&Lٽ +ԅӔ:S\ ]XFuߥB#u =u2_"#K|ΌA*lORXw]߶ t?TzF*\92J=ЀUTmK#!0am#x0YmHdOr h МvIL(?>dDMJR=[#Ж'aۋj", 6hn4lΌ9pdC%Rc,5#I7nqDG#;Ԥ׋%ɞoE_ܚ0s+Jά3:qK̀pxapI;}鎖]JGAR[^3T>YFz#ˎl#8k%U| + {W4)Ck5 %S=e)QL(F dvjbE%j{R*oއl>AU=d_2yоB8u([U1 JSOF&fI#ďGDo%U&aТi!%Q%B{ ;ye4]Es0YUCkp[|bK6z,}Z,/׮,&'t!<ٷljY ( ?<,a?!f(FN'vsL~~r^K$W5̫n+b"U1. 9ir`&RCm)p;(eݰ6. e,-4c|EF׺zdY2 'hX$\ +Bng|CՉK"ɰj-Jx@X9ۺK̺kY"GԺnm'q_ |;rJ.>xDd&{/lxZK/'smjóQe %TAwPr 'q4+ |܍_a,^S\]>sznstd\N~xEBK VfI4fd<Q@Ԥ1ljΒ;Ǫ͊)Y*ذ2v[̺QE~86h%r99} JV3fĢb)̓D`H_s}Y]h]/3@,6 .:jEi9NU,5e{*yYEZe>[Dw.Puˀ"-R([rq2$h[v #Eb1X+N! t!28D73HGIMO(q @WYW_E]͔d0p)rՖ 6SIObaU) +y-ɒWmPB%,<1(tjsFo }"L k^D|r +\Ɓ1:K'^bb'֛<($-&抁T! ]Gt]ʗ`>>IWÊpN#֏gyXbSVd)چ3 x +AcC|㉌ѹ-#A7@]>辤Iqw hW>TVC> +sTG]u9 7]~%hܱ C,\(+12 9YJsȮsJ/sUG)ַZ+Yb4آޏߨ$zuIFmM0XoH?kgrz7^WԢnxl"C{RݜU:>޼@4!ZNsedt<ڋs:u$h1-<᠊&+:(ҟ{wXH1%B}HXq*a 0]T@0j yFcgwHH>vӕAZPH'"`ߑ;{yLǯE5 K\,P$w +tˀ9鿤(ɥ=+ e0۷hGKK>Zd(H~`/E'~?{<$`xۅ,8w n&`v!rYF(0d7"E ;ts JK ]}ZsޕQ~^Ar;4T,{%SjrsQ_vф3J{[!npCC,B]LS\Zb7J=M9ESi.VdV?*LOrLZf7eeIjР|@;#-5 I|Rlcy]ela  7і2g2ɉ$&_G'{Xyv@J`2t#ߑtm,ڶǠ~)9v1ԏr"h߉=aP7MPC.?ґ54YrTkr|u sJAMX)LY(rFa\.0 +ddè4}D3 +M>*նeG.V] 5r[3u?25tn1sI7jE `au!IAb)J"m0tlE@ꮯPr [C:groM\Aj䁫a?qb +Ömc{ NTV͖OCk @a)Ap'Y5Ӡ"cu LGwB!¬ %l44z.`l%W:Xu@~P8Mhp3HW#A8;"LG?҈ׄ&WbCZ}VK!461H4"i͇ yW[p>+FM,L=;F sedװkv1^lb.A9SҔ{_s@6k Kq$Mߔ'9ucS 3иju`&. poBE('%އ/''2fY~J:O<Ϭ|P9.2uu$òJu몺": c8pK͹8 =@qt L +SŐtTS}CIERrHqez>B{t +!]p')cqo$ JP1k$@ӑn rc#X ?p'V4֗Vꋧe#)<#xy&<B"ݸAt|6 *G0;gA=AA:m/zg&vڿ'c=R8$,_[., D]8~DZZXHr3&9ٍ>z~ܑBSvF[{r}O|1S2aV|>G6ԦXТmdZ4+,%!TKØNDjUZ&5Hv7qIoeH{8֤ F64#0N /օ~0*"Ve 1<\]-V(`EzOih9Fw[NC [vd5yZ?d8X */5jL TM` B$  4N^ u"&W7JyY#-BeݓVȓw[$τEz>>+$̯<|TVw#@'#y͸._8DIDr|N@Yx@ +E?RІX} ؙBKaBeg5KP f4T2A1A[paiX?Z%h 2lfSfME: +W"7NldI&?:ߏUeR4bE ˓*w!0@^bT:m2<) +,5,Msf7=z2*ɕѯh?k -Y*v!{WE}Mq$$+{' +ZY"+h^H +4@2hTtO`ݹ*=x;߈jlAD &;ߓ[ UG"v^sDAY)~ ѥ2eN!oq.\f`ʅf#=Hl]f9,Hs7B>3IMcHg(pSȡ.4z yW⺋NR}Ks?;H˦̭>Mv4Rhm# ei;1)`9J͊b=XSCO=w{߬or%mPdK ]iC. X9aG>vs@uȁ򺒩ʽ>0!uv*<=!۠`ɍp\HxoY+Sb@wSV:12k}ch&蝤U-|)˕Jʑu(h o\KL3c3 +Aꌰ=h +=3sH.Ok=VGdb$\ANMh V{:BS*<8@SyVRcDw08!ALpFFM]b@ET6" +JM8gHR7خEMDM"-*~/E 1qG{z˖;\< Xq<ŖIn6)*ۋ=H3~gu&K$Mɋᩓ9rF*pF$|▆[3.#(#{D`PK'uK(І9@|F1TpO]CG^Ds,CX!t04^ I?\kw+^ّ}k%IQKD(=UB +ۗiT;) +ZaxrgT6YΔZrx AP7h1- +1q|koEK/2uF괎SCλ"`QjP}!ttyr0O=N?l2DN)um E{p) vIߍm E&/Swq{[~m[xH_ݮSJy.ӕPͨ1(u{"w[MPsԉ##MY@bZ,v`B8@ϋ}"%I˒@lxwZP*<p$[j%fGd-򚑛Д6sEQȶTyGх,IM7D̽E}`%7CQs-=PBؐېYD̒"lܽ{Fq:aރL:vJ`+B+Ĉ#({ ++A k[t(mv͑SQ,G$c@L~TYZ^b LH$Hx0mJ^e˓#:4E^^jlESY0i ihnH3* ;O`hQۉe.NS7խ6 āҙ \LI,o8b8++灌\$H:Nd#kՊj +6\]- SPsO99.[h 66EE|$[w{t%4$! 6MpXhD:@!;Z mM[rwP tӮtTE1櫢fGz qI:<P7j.wʉ%( zWh(Dy+r +┡' Cd V` + i:ÿ:ig)#u˓k,ٟjI +NsNr!q e[\4CShANg~;YFn@v<}/cXd|ggcV 5PGRnWc`DbrP6J7%[hz@A܍dڈ>gk]#$ s#pzeq`Ӎ1G +~Am}c$ay }zpDxTY-c^\L"8 +-z@=0۸G gEuJV}ӖCEDdos@w_d:S3 ~Tɤnt4<>S@yPY v\FVO@$va !cqLE +\nfDK1'Z~ߐ0*6G,_\b%0\Z%Z?7kJCRhXy[qXv5'Q84183ΰޣ;r$ll(Qbm|,a~|Ҭ8W4NFjW \< 硠9&E vESmw-Va +.IF +zDD*QA*J>ᡩY gG;p,.0 +BK`3e~w&zێ}$t{<"LZ?I뤺TĴ-QhѨ +4$nmxEQ;K%H"h'Y; +>{Alw"!I)r OږbK!#n +B?Jw EOH=ydJ Q0bX"AyXƛ#ƭMҕUHBŘce/4Oڄ#^9#c|oorrgö*WNya58rv[N4l2oH:h|iCY}$^xvO>Ԣ%h8O3zM\u@ErD!Q`IUBv<YlG&;")eh]IBeR<Ή}"ݘnToK7T3D%6(AO8C#G [$9-WM8':P]|ˊ-ОJA@U{g=1}3!z3>N"ۣۤ$;"Ⱦ1<WjXy@Qx?.MPsPʅғ*07O xO9ay8,?p+UfN#BʥuJLPd~ ɼޜtW ?.iZF^;7ӛĩ$ LٮXxᑥAifC}ʙ^H/,z-F 2{aO YaliȤ䫪I\,b {:7RP9ײe/ww*\,8!Í H0 ]HN8)&+T"k҂xo}2CW*0%)V$" +?ŷikU)Ha`@~QS2Eg4@ 7$8G+;X@7fj*tЌHd#'zQ͑-|*1YDtsjsڴ掙0&]Ry0#f̴")2ɈѻC9d5\hf6UU8@Ķ_QJ^d:>Y?q4a/)(Dwe.\[: z|#q%4|ii]3'!Z 8HyѱKi5 Y:rKӘ3J$^!;Q9qX,rFPTќ@4t*X9**b4x`#!x?0>Z4pfG$+AY&'zO1TpwDYETFH\ 1sB麁u-yR&@c;ԙA2ALrlƍe]󇞠'S~x%"qBkQ4+V1UeB5\]j'dd&AXmK0Ġ`up{ +èN&8<玓a狘McǺJyf J XP`t3ArQcEGuu%pk!vDn.KmV/*]^ZEr.beHi?\2Vd>NF"^*bNH 3\/|.AGjG?XkI6% JْgׇʗT6%9礆Q3N 3'v`-J^^amt @"=- 6eXY]JħACƊ(2ǵNGSʘbzkL-UJœR}gy" +ʄ]O+rN4ROg$画Oj$L`9&թTBMl: "-ˤ@fl9c#!%5#nڌ~f=AϊpA0fqj*F(~{8repJi*΀ң.(bI%lh?Z#̲%|αŽĴj0;w`Njk2"qQXW؊%hdBY9a˼ +$4Rkcš@@FV҆,B #I +U$v"嵸;O3kMc!6`K*AFS. ņ2݀edϹg,QȃSp7'fxr8Z fp®s,セ`OHHGԆ-PЀ{KD\KEt|'|U2Ō3X$ m~ YFN;xdb STaoV6N͙iGfM)hFQW&hU 9![AI1;(K"|Q;ʼn6zY!a9y fs଍s oLl ΜSy$vYoS]v;r\rS]0DaBnQ2Dy^nĝd1EBËwaN B- Yő%Ǣ/",j ⌀(}xTZ)jrH:lPUF=!\nڧy! +xLzT8e'G)s]Ƶ + Jh TZ.oF V)IRNC3Jݰua't?^rӆa^ .xhWƑp_q1n)M͝֎%,~H0ɏ4@9, CVQYISP("s3n<.>N"eJ͐#Uď0RԮ D#U\C}ñMK^S4E8]m%Mrt^=|)v ;S`4gxmi;\8PYAuZ%h:G =A؛_hU7d;A5kNi~tDxF'oE&Fuζ\ur$E5\)ŚE*!ף GwwTӱ%xEF>>몝R>U*Sn"QܱuZI> Q~vHi>SfsBn8kXr- OKX+":B^xH"Llp3 +nX[P]jXpX3Hxh2E&͚[UF[ާq2`@*{!'k5u~0d#Gi=-1WV9^3*G@E M|`ͩ A1^TO>q>lUW_=%"{@~*BUPqPM7Z_4PT, ق8PNn vh%rgq O>6A^]YiAh鬱i%=5=]2 Ϛ}ԔT,~ZGzins:Yݥś$}n%,djAyXiҔq&I9y)<1cQӝF.bzٷA|<58ړbgсgm1p=d+pM$\SX\KE)m]"bh0 RǪyuՊYǩoV,1M~SP/>7$o1O9jo+6N)ݐ.J!wG}Ls5`M:9>ן% " +zU4rBX=hn[ϩ[%WzH9:ĽXc; XQm8RzB~v +XM+uVXا;x`b&+𚞓pK#ͻ|ռ!V쯘s|S7r y閎'\ËF XS~4{j%H- 7&MKJ1Cbvƒ&bmp`qltUdAQB 69 ty^Ҟy0N +=Y CB*5XD_i`)܃Id'Σ + +M]by(\xS݆+xRG:=cFmí`J3N+W XɁe\ZJ^&=hAd]SgwF`"`YɭT-AO7}ùQgpOAJ]:|Ѡ>ɁHFG>8֖ioM̨> ^ZtUܜvvt;J1hPܓ@zbXɠѺo«] 5aP}>7Px B3M9RCFjbkPSŶӱy۝qT:RR +f'6$8߭%߉+t-}Fg<-]H3gƭ3g#z?0a;ÂXѽY{"wj( RZ/ .#H`ػN܃ ?(DD`ÿ|2T'"N񞜝9QSbf@ Syyp\՘n=3MbZTfbZi釓dXc,URl-1fLi}[/oöԷŴ) W5:bzQdK_۶kYb[Mݵ约uTp;ܡl+KMk㸵5V*2'24sʪkkdd[9tuUh,m,KoKf5f~LquMϔEQlL}5#e^,cV}7 47e8FWZޜSTJ?[qRe;ZM߽ ~tD0g#tݕ^f//e隚f/sv/o@(5-uLs*ޠ` hoY]edjQ (xOUP#,GKop^~a@lqyW7ZCi-n~Sm^MEsckgkGsUeJKK;y#ei4ſC7?P.{ѳIJk;E4yI_ w,{?7I7ý"ΪMJvƢ& +hs*86W +fQu^i˪*+-}s;6E4E0y䖏i»w\PQw]c^t^n^۾A0ucSל-FGFAYL7?}Ý֚H-fWvdiǀ0iL +Ejf]Lr*rzrE,Ki4LNHW5ê:r䅫!9G(!6xEkOq9݌me*Zp2461VS̪؝# ƲJCCY1 ct{+7IqIqe)\ɐ$3YnzNhf,G/N$/=r3RdI~MOr#'rGQMs ɿ;'Y^<#(p{d'{G9/K/&/.9?r)fn￷(C9 7,?ؿGwX`6MsC2$7X$9X$}'G4H;{~4m4%(?8#^neI7h#_"{A/p̤Xb)Nރfap='/~f>""8 |ﲸy{q@ȕy\ K_[.w'Ew,=9 "m 9h~`_9?fo/;HqE/~,E2="E2{${]$("I#??)d.`MN=f=cM,Gp@8Ǟoq"YQ"(7 KqɑhaEL?\@8?\fG`.'{XYͱ99N2wq8IIER,(KPsIeY,җ=̾dI0o҇Q 4'Ar$}9 Hr s8]9.7RL}55V]Q(zɱ2ss9-#;Mr_?d;,.ľ ;Eqb9h"O9(@$GS4Ms9h} 7ͽЛ}oޗ{b!W&@[)ā [t +X$I<ĝ85j`f`L!N7, da }\@WK#6 +jjWjcFAK1Xcv@[7DtUI.<-Ύj1Å|i!,$}c!K-Ec㶬ۙ)d(@ؓBr@ +$Q-xPP#&\xq>ķi mJ +u 1FIIǒ`AHDJ #H ,"QDLC"@Dũ@Oj AQQ)>z RD9D p 4"'yzy:TqaQB-"g#4-Jra3@@L]la8aD%7 pʑ[h\AB΁ +^* +ApJŨKZK4D֧rZqe +,V/ &$PY c,ML0U:L9i$c_-~o@HpfK-Mw +Oe1hs /ȑG8pЃb0N/#!ԓ4LG%XiO0@;1`>R6Qzdqq2]Ut8D +1{,e0T-I.KW ހ0`hp`,ɬT),XHIhi!Z< Aހ֢Akp j O9,txkjg)E P)~.9nӆ wI.C Rΰ`2;UF+@O1 x|ٺZkT0 JgA5 'X=1Zkjh}iJZ7*aB BkMʊ@Ak]Ph]O*+DM @: HhVچФ,hIzIk߃֚sH/kKo@(tZ / p?Dƶ[,PF5cA)mD!@4%zhbnDHVeFkZYz%s*^U-9:SW4zT2@ OWW( l1왹ª뛺6,]V c)OWVQij̦3ǽ-ⷒkL˶ Q,Jpk_e['@_ec蛲*}5:c$SvĪ1^z']sW}epz! ű 6.(T8 &mUͬc56γ4e𶹡fVEk5VY6]+ 30Ue3YNmqN_4pj;)ji USt Eߓ-5tԞf,Za^bV}G)9=2眊ñk)jmEY@k3Ǒ! +"ݙ2eEAx\Nij5=n0*Fej)MekT9VU3V1@m%Fe.MgkKL54*fJe!| d?p9]Ljy'Ir+9Qd%@tsiv,(xWɜ"CV6CPugZ^w9on6ci-2so2yMN%޹N۷],.gpZlr* pUAƬ1 +jua}y6#vMhkǩiScrp11]\ S]sm dh"0@cn<8NMC ̠xy9ml92ܴvոi%6]iHƪl21Њ|ird%F7kjRdC(n]S*n;i X]rlj0+R&*[SH<Øi2[sYBJQ& ۗˏ +Y9($ʤxǀ\ uNBW&h- |&d~#xo@* nY",znD[w)\ a1l8{Ol~S9Bb f30n*ހp&b%f^9>2z1!ظD"k#OfQhS7t^(fkhMr^REJK)SH} XrPb`'a]w2,,=:aiCˉ rG0x@x? +lh\Һ3f +d8ͯZO:"7 (:h5tH-M5IZhӥԑWp@sb0`%pP*V˜-:qIS&*'|3"3"B`Vx`:k3 #%bql/S^ q>]%9TCж$ v؋ +YO_;[Wᡖqo@c<*x;EX}"of`An$K_Z1%b%0D]们}툥& o@HOs*˸y%1[O-κPt*nSMoko늆Y5l699M"#eόEiX{{W[ (WWYtvSxuY g,jm82E++k5 ̱t vcdi̪aN0ZKvC2 뺯뒞,eUZzQ4y7MCXYnpX{s5[[,|]]paqUWZ8{mjZC8w0ڛb5[ tM[h-{\ʪ+-ỲuB q^,n1a涸MC[@Ndo-thi˜@, h9&s c9sY+U/7 H`Ҽ!7 +)ozP΀K`:k]4r ]IMCFCƲx&LEd+3 PrKn$7GV.)lErtFJ" P U@\]zj[ Պ=B7 –nIEz'K" $jSiGD04e/hbeE`JT'RZQށW hʀ7 &bAR O EPOeH-D$09U'w!rb%ƄBSʕ&2U"o~R#?<)"LIZhǚpFG F'j +]$ +E@.2Pfl5 i*:-|lF%ax"[*>zyL4-yR..)<4el'S3NWc8Ź-sjpj+#`l ]6ܛw4 xZp45p,eP41mH $2CT4 Y0J F ^*% E Nu41ىc)OⲼXr;iUhJm5TWp-܌=dЈuɥA'+ސd;AppUW $%0|V*:";Do@آX>7O9-߱#Yet5!CWWL+_"{Ey=5ڰ=ywEqF9ܶ0Ec> +q ߓb{RKm( 'g@-PQ(v59~@s:("k;0+R tN5ܠ˰T\FNor1uRe㰜w9E6gDqg=}me_y r|2Ej8.:[ -pLV ,pk@5(P~XK) %h Js&P +wrnnSٹiB M]xq= G&xux':NN!>\G8Nn8?͹Tyө,F0%+O _Xqa 1.pqYJ(!aAKA\.Ż>E*Kgε΅E'6R& VJ#ݻZN)MP9[2~`L`%v/ `>zADKCwB٧)7[Cn^W7 ۑhfVivʎ*+9Fu]]PHMg3'0OGx6[!`+ժxx*\ޗ++P]b8mN]+wV, ?=C,baB]Sצ4m +u>?3NQ'4I1%&Վstt{&=&;ʨ3~Nu:|I')xdwlp' po@%qIO O}S%KjXFiHLf֙ĒOL8dNU@1Z#2m +\X6Vpqa+daj_ +Bo@`0 +Q/ +pr$R4|{^Rh Ap2@C)6][uP+D!^fq c]k!JcT +-k#$N|z4{5nQЧ٫MTC. !~%?8ɧM7[CB&#E +R0PZ:V?%XD>[(8VI?, @zA<{ؔFE +Jtj32x%v`K9r=DEA`@>6)==׀@LPSTgISf;fj%Zc>H$|(w 0PaE,: LJZ}wc'(X]aSŽ0/ $:qXSZ̎PCpYSՒM-Ǣk1W yPܗC8 ((V88_=YRC04 &͇(.F78t]I A0XceJ"!$.I#Yz +qG˃Eրvļ]Rހ:1ԣi2u  6&&+ 4Q' z%Bm%3"AU%N0-!aD\* `mK8i օ@mB}maS}Xwڴ-9 nA 9B%(mh_YeoG-z^a`%F!It!Xlac/{ulcEPɩZހ/MQνn%zBfW7NjHFx&DqAJ:nDAySדښd3DJ (yB/ 77 Bq/ GFzJPqIlw-maD fp, t蹠B## cXң{`8V4>"ıX 8!bdQH!8Aw6FHJI`RBg> 5Z>9@(vyK/$x :(*×K3 \Qvǁ !;p+{TQ3\P%ߦ08*aMg` +w%rp 8y13I|w gZB\h {N3O>]kw*r#Hn˲xH + Q]t [R8'Jn[)$tO""Q6sXWCE\a;Wt=-mKHފek%8EilBk -Bҡ]ܰDqIzJ;5H#0ocMDԸy:`4DelClT!Ƃ<uOD6ª!ƽ<&vf E#`GfX"9ė /` r0H% PޏMM6 ?Q䞈 +WQ)$CLvj6!Vܞa_B#ifbo¡?!VH%X؅\Zm!,f ٖx@˜W)NMYL.5L&R ))~/U0*8L'Pk1Ѯ2JSn EUB/a_(r%;Z|j;HiTY1+2+єEPMˆZ!2i~Ɉu`wjV`ٺV3FM625m"XS6;3,. DfȚֲN5|KqԧP79VT0R܋xPu8j2R|΄.OFe7\i⚷=:*J֑S([.A,;p[ ڒ +f_Ђ1xO3'h\ϩ~njy%#)x$V  +QOEl5HR*wH-H*, 0!iAmta09 K@K|~giGE NSx rjl`5cvC&^x3|V8zq,(+֟yӁOX^_(B ^ Jm,O Yx&Ccnc +w@~oC mvM>_&Tw{LQ@$e"KLSuUKE%k,r^ [(((Po2nH +$wwͬLc:H}|gS2` +S*La1`PƼ.#%xx9ߴC + +d Zh%m ұza#nuO ;Aеn(>̙Ǣo S*jZGpq=Gb-θQ TwTbQ +:1c|<U(cv 9*~DS<iG 3,GL +(bj\$NRw604FSn8cx͋- +཰r`VLgBM| ++NF+ QF +AU(ƒs~ Se,UtG-IK`rew>8Ց#gD"y9*n@7 o* +YgI2`&VANeMӷ]RYt\5˿`Oii;.ꩯh *B8n$>ƨ98:h(^7WI.Ҋ(j]p+wâ!dH*,nW$opQ݂E6Ww~JϹ… EoU\6ٔ͑9e^/?KJK!-B ӁiD0ޚ.cA]P6-o^hHwL3t\H u4Ỳ,Q?sk 4RǓ#!Csofm0t=sx99B츨t& qg|Skw~s5y;s6,~Y{V^t^(6~/΀'өYC]iW2@ohi`;!Lخndp9 ѱdPRTٜn݅c[:hL]Ƣ(va٘~) =wnMb)=|1R|)q%D9Giz/ 4S ]J' B^![!(!/ԛU$ŠtP/4+4qէp 4/tQ6nmFP3A#-U"xWPSj9 QPR|26cy!D׉xru2--|7Nʒ\ˑc"@x-qE'[~(d䶦6pʖ6d,7*R#8N1±-A&%CKXfkԛ䣊 ݱ<=.y/DvbpXhW?$>f~ Ml3瞷PXg'lQ>y4LZY$37%ۑr8[2m_/~H"&e7tÚF h/ NL-Ŋ &'PFi# tz5K8U]84cX/%9'<#0Oܮ?Yw]v!}ˮ<(ü2NVQ,U2"40hՋLQț5/ke44qyXǨ#+Ιb=|٣[ 18V 4͍m{DGi S)yY:tt}VtǸRpy3Թ[2PbV5"AC4,//^s˟6Ĕ>TOaf҆X8$ToFIT4PMkf1Es(ކ#k'vZBy'/@r~5dB猢> pQ&fvXCy}ݤrn` 8 A$ff;Bu,E!{~:8͖s%BN>{1u+)7{hg!dN7@>Δ2h^½u0,I +(^c?>3h3S`,JYa >u~m9w"2W'qo6 Ɂe.[HbVFl ^:" ́64~'޶Ugީ +\_ n~1Ӕ^CV 'P.@.Z.ck>cm +! 7(t,A)nþdBrd8S<s/6[̱ڋA3M2iy2ST!HmoDA%p,ɭ p Py1bP `C<6kzFzQ {9(Z=ߡ:8b8p{8m!%')bF?H^6Op|Afe͙t fSgU %`iT$Yg;d y?7!Ds[&ֲlot֞T?TSv `<,r WasQ\j G榝w[s_Bc#8ƐmYc hx|W6I7_YAŨ6@`p?|NwȜ:sCoTv1魹ޞ Ej,qg"Z 麫 +Y$CG +\[>C88؛*1ˠwcgM-RhQaĄ7xfw-RPR@6utMQy ہ[QDխ6sh u3@ :lB^$iT'ȿ@\F0ޙ-0Lkp`T+  yf#270ODdY$^ͨ$ɥ-UmVպ͝9h]6GΡ/b9onE~eK ` ^jU^SwoeERu5:g2q%SLd +-j)BE%5 ˿AμϵO%x([.GKjɼq:Ò3iΈԮ-ڬ,h7pVOȒY$lzszϫ,A&뉮q 4б7ʷKс%d}lO*/+9<%ʩl !J料 \||x7Id}{‡NB]5NȈqZی9@cʍT2r'@y.T eÉ"ҁJ9H+Swm3[ASwV="[}Z&5a]6-)Y|R D tuWRWM룓S֙r:_g#ʲ\IEc6ey!@>]!!i.r"j:4'' oƋnA9E{#N"DђѡJmZ;RqEcB4`p +i h.sÂs'Z(kIm&܍PԐZ-*!aΆ&"u#X6ļrԗ)R,eE .cJh@YnMlM"Xkq]pak}'<$%*U,/&F $'AN,)BIl$ +!>txOAhfN INUCvD7O^>pO{f4@ ߺHvQ*6Y Uj*}[]]?eFK:I볭M&JV!l͍{ޢClp@f5 C7VsJ +5  ԕK }x[>TJs|q}#n*dye +tt a' Al ~qbmy>w U2[HMMpq9u[ǣ}KxI*ѩ3>if])a_0>iQw~4-06n 6]hNY-;D%f= +$j3ԏk<+D.}%aqkЂDn5pHlQ/b^<8#,${gJAq~<-!Q6>M!> hзj $&t,?cz\v7Zj;{n[,F Wv> +$$:q!> 3AhQֈzzSJ Z擩,L8l8ﴸu@=Mt?,+3FcZ\]!HX Yt2X";yf3uCl&uT t p>~:qEf sȻZUбF٤?Rg-jbP,Vzq *2O&"[( Z%|HX۩kxU@>hLN梎co~`s%xr\ULuts+q%3)*Kg2V5dQ@({CGEGp7 e#Jy3}Uh͎:蘯;(~lɑ\Oev"_ RO au%ehsB'R{NtBd`9Z弘l70*=&' %lS^ߔ_b 5:[j {+WmDW]笔 +gA32 xbYwN̫6^;ufk*uf R)VκԒM /ZeVo )P]Q8HwMqRk&Oۧ- S0$>\DhGjdHvA^)~*M)xR>2ɕь5'6.'yP=2'xYA~x^aXɹRّ~DZVt0/T2Fwp^ri{k渡XN6$ХUn6 J+r9v e=W!^`׫)dM>*9z1?0'aj?A;>cb6 7wEL@o(Ԍ!9זNƮ_ ! PCē/[2"M?E MY8\L˳ź̬//rStRew_ou f{S(x F/R.#T,P"0U⇳UZ^HMbEKL^:!?a1r0V]`q bl] vyvb&BgmqEc8g9I)MHi +hˍҗSqpJ<38SݙhqD>j nnmY0aY94~ԐUOKS}'SIV  +۳Fiqۘ:Cgn)::ϭH/qڇr`$*(:ǘk1rO^Og0z)jj?`#fŢL IHSyqPna4yAHMm.ɗ~]TvrtPt睫 &iQFcӠg3z) q4*ӫq%鲿i{Q}%Z~Yti{y qh*/囮`qVh/Q: 4 Y Ҽi7(&""XAO6MA.zh ftEѳ>>=. %e/W'C*)glL 6ȼ* H*5`hO0}Wԭo7JOǩ~K\Е!EO ʛU(]<ۥ"Op) LK_N$~_n򟋣x8ޕ~dqON |nqL֦6=>Qs3!ϷQ=5n0 2C(!nx?ovb …&jcg3\wb ޕ1/86o4Vuur3[AE$]G[`xu M)"Lle.ruQh-K/. px&Te-:T"^"fY착ǩ.4Vs㍶ ZSbE/oXԚ&Ik>m?kxPa7錕 3Y"J!|NH5)FnzDzgD.MV#)I -:jkQ +&22JRH<.[̙6~('ڍMҶdP[5 XfΡ5I]7MYn3ݍ|zsRJn)WX=jԭąsnv26+}}Q: +C.5; nrԊ:h#M,I#էQGdrf<I P싗[KDsTs ]4(#@g`WwF/)&u$GFcY67Sz8볢9hEt\LeoH$ =ߩ3p^aE. Қa 6F` +1Ph({x`|RQ^'QL!c%]eݡCAaur4x6K;OL5e//^5N0 .QPJ5} nA #x]%v<W_2/qB%VdaM[~DWjV(&(kĝ5R5q &%$_k| J,IoYS_B"~ڧ??4ju遆P=ÒzW-FOV~X3*HLʀ ʍ%h{b?V UL]<2ZOQr9ЭN#^F1 +&>y@*aE뛟 Y&y(Bi=ILDצ!Q@eUDtXW6-O붘ݕ>G8; qDE#bhaF̽#I t%<\]*1^N|) E\d6 pЙz.f֌Yec ^FQ0-2O]mR!ijĩ~;N!7Փ1Xѫ6:;A#նD­B'roиra + +_VP]0 LtlBR6E/dYi~1V:7/2mldm>u'̫dlG)+A^1_eZ(]K^۸.+b:tGk`,uv]a|фF!~1ÂWqIx.i1Q[ +Ǽ$=iC0◲v(TQ5uGtva{'IJYJ6nU= |wcõVIWD<&g/U쟜@ȎM9We?,)q2m$Ad{YKa߉`B;`#a&_->ι*ʻ\7GFq-_Y2άdžC*>|¾Vn K ~@ :bC5#NX:%Qkl7Sp7nc(:6ہ֟ oCVO@TkPӦ\ + hts zUb*+ƈ),3Knt-e *lYDl!: gs >1 Rl㒟4唘=]yDb*lf0ag.?5Z LT_U~@H(SG˙#1 aQɧ,d +Gc_ +r4fcC1NiDy9>s-)Er[<: r'&z+cd.1vQ%mr_<Ŷ衠W;?jm,3D.q^$F,+ϭ;?KsdvOr{g~_,O6d#P?E`eόDjxUatQJdY +M<#4! +endstream endobj 15 0 obj <>stream +19/&wG/!۾فe*m::*X"g Z 3C^6ao܄?nÍݚr[2eG|IAOc{ChP3;IuDJo>Lg5~I Vˏt,kp y'P"1 9מҒj6?žP + (7?XJAP#"niqcȷuQT7FbTD.KFy ÿ +/(>k`!qR|'͝G + / +3B&z\Z9"s@BqفKیE:{FS D5RJ(p!2NY%?lwJB|BEЕ!sH:þ֊&J_ٴ+We$V)f)ՁS'n[V +="W%`ƢŮ=%MwRFvwF=GFSS ]@L(NKy\/kYL}+V/9fod;;gp+9R_([  +Z,!BK@9jx$C"M0νehim2 s]̧Άyh~K@yT6`IDyUrXb^R^@ Qeâ*K +gώ%ҩ|mL&X&LFL%ra) M`3,m=BIi,`WZ}SfHt*,zXE,e931L\Q9iC+{8ai_:kD4>G2fpAykL2m ȭV<` L/ˁ8Odɭ$έ0QJTOᏖg5~~wCS-`zԊ΁P9ËWZ6醭3G #*"cZ*⇏`,81iGlaC"iq¤ZKP,#]pc*"p[3"q[ С-RDkiN|;TTxZl'f,ly +ѶK +ͨ"RYTp6P-i~OIdt#Y(lHOuF@Jd.3WqC J,G?>"Ū)Hp):\ϒNA$=A]86Ú`ֆ KfxUtm(f-'\Oˊ lb44Wd"0o=# K #0ǐ 'j}ꌄ4-X +._.VA+!jv¨TKq#"d!:W} ix+dK S5E@T'dLH8!{yo˜8C:IM7]{LL`*u +r l{z'=^]&̨оϜ+vF%mPvd]xfiX%WI[_`%"栓f~>!0{"roY$D^afI!C_IuaDWM"oA L~J3J)i9i%0B._60h[;l.@К\w-8lek]ntFHrot}M0^߶%1Ok4~]'?x: -~FHKn"ڲR-/{a^# ]+'^C']s@F|XxD4P w~k''L*mHsBAVpPԿaHDR!9O`!FD + m᳉A)PQ{Ju>`-D]W}}{w:TnvHߥg\|~(v̰͌| +cscdBA6!r1g)nJ3aʈeciڰJlX<~DPoE{ŠJI:Hz9Q;Tf͋3EAtP ྒv#r1%u=5CXsF/ A@EN>T߂eǩrr7;U6/蔢ŷfsUЃ.Ƙ&5&ÙM<-Wٺ*ܢiu7vDţJixڌTQ=  +&5rGؾ zurŁC.Vr?}u1餓Qq-rnSԀ# Ͻ)}x)Zw(#B|tH5( i+v Z)M_W\km2 !ї%wd0A + fW+P"͌6fi;g'6~S:S|& ɸq[v'g.^71něr)id.AI \z#CF@ù=.}ئ>XJ.@ǦVGۉ%:d>).6CVJvK5;Xհ pJSgm(),KNȼ_r3{c?ܵmL23yp-tc>Q$с.x5M,bs7J~S^r39I  Pʉj7=U!#'~i7_l?2mӘ54_` MVi +G%9k j>#pOW55= ,=JvTLE V##ZOr,}i[nfѳGWk9 Պ4`UaJJZRt<TOĿ;Hd920"pA\r:7$4ajV6ǒY20¶j9;Qꈸ2˸tyw/:a$,Tt'-Q{&=^-X@GNsl|>4[bYJBk -$jPYl/Z#_jphw8K| PEKσMB`9%rn=Rstn5^)Z9۟ +wD@rndDRXGo EkbOAb8U˛ dp/KqEÏkӧ&/3z?RiЪE#&ٛRA+عrXvGoɠRZEtv\t[ň[PvbZC|ZfbFpMnOTݑTա<-iꡈNʛyoME VuTrz+`n&%<.;T>QPJh2OSCš[NeE1)re,3N5A28"VX@} ư @XHG=?F+X +&PDOL)WZ78A27Q_0?h"Ejf6u[eq^O@=FwbknƋ,?kjK^~aXXFGK@\z{ss<}U캌 ʏrbQ;%)@4r@'tJP-;{OUrz4|Si`g-c&L@ b91)Y9ThsW*,&BN;O<ʆ$HCzkKQ?0=těG;cw5?X$ :~RPrn aőalAEԜp`hKf h6[)N/\RZHv#om~45{r9( +PBӆ)KwDozD\=ޢwD$Α +m`*; eRx:JZ_8º/ߊ~Z^ZnڼAn/*/ 7ROd g{J*f,$cn|xĖ(މUX*X}U\ffj2f@mBɺS!(l*>o*V,q k!JBF{&~39P@`a!PB!gdXy(aQޜt {xq@rPIq3Po86mhHA.qH$kʎebZm\N T` sUN!s^"SqݣFF*myw8qmfRP- Q 48 Dȱ3m ,!'=(%Bt&U;hf9iB*މQ-QY,VV,0JBجhEadxCM=)3QQАƍCADnB-N@&)3œ(N fk@9NGAYHo&4Dl6p7fI!_RA6Ti.7cA89BeAКOヲX,1ޘ2QrDf+|'oB E6.BIȦQ`*Sa_ +&VF۸]sBh۸Yͫmܸ(U0oB+XĤ6N6RH0%Jf !fPEL:o@L&aQ.v (+Cr{Q?jgv,+ u(e);d +Zu cub`'ЀL> +vYDՇibB)QBIlb> ̄ψ*2:RASp/m?Ȁ؝*P*A\HHI0e O1615q)j&j)̍'N˻l4G pI0OR*0Qj630P +<N*T +p^*{0df6nA$B){d&.%-R`e&h_ڸTHHС3[1Pid'Bg00yx .2TH*-\T}q #]!aZ&`U$R: 2ueuv4t/c8c@k32iygAPXxo2J0RLxiB0!a`c!jE<T۸ {_g}U8D]+HA 11P"P ) ]&lI.`;\efB$*#lj]X9KHȘSP5q-r5"q3^K <+@;@$1"J&? DQG&Sx}MkFL&;xPrZ Erppƃ%"pw\Y3+L$X xg 0Z oW`@$6Μ<{| ^'K>~#@ˉ+/s +FH){W؜'!2OMBC~5 Qiu/P|T +D"N +IaYPX #'FxHgayl!Y(ef+_ UrJD@B _(@ +'OT1R!'TK(崄JPTl+HqDgƉQpZ!@LBV32G@Rz<( $`8i!4ֶV}12I`C%!tIxS&H WQ012 $R7Y1$AP14#-_C|6Uː*` I)1 a7H:Qu ;Z#/'S,zFNT#e! m,٘`MfY=n&! -;{6g8gCATb?ke`/QƁqqYV x7ڎÄBolNDYd28pG:ދ{E]Ԇ6:HP?Z/).#jtqD8wp G (5dhTR m&JD"ơBÂE&ÌF[vck;j딝 +RqLDE"*cH  ]R!6Kc?fvWXFMɨ0VS=LBGA 4`pi&L4^7 cvYM` +`ԅLq0dFf €GY&2$@tpY>' ƩH3n)\m?7oDFG#̋ᲸZQMbas_u +.}-)7J@LKCbX.A+H~%ayXފO=wtYFmap/R!?|@xH䔩 R㱌0l,9BR؃(%-Q1^0=-/+>cHC~لc4 +t̎iH*̘.&BHVDu4j^<.Aa@$BtYBnz + I(J@d% hu0@~b钟XNwtFSJ R@uR).DMBQ BI0ecE$>Yc|woƄK:@;YG2 I""bmqKmq7fF@*CeT0RSg!B$G_&3:L~KW\`QeJ^5H@Rɘ4`4h:RŽD؂gF<`'28"](aW‘Qf%h 4{L48FѨzˌR/CPz .Ma9PxH`Qx'Vw@𘄌-a&.jkz/kks:o-{ÅZ[EzQ,vqv7Z7LgOqԊ+~8c\7}7Op+T[/zmc֚u"7N/ޔ}+~z[,:mƗ]_cmfwŻmo5gzE }flo.okVgQǟū/E쪿 _kz[i^>"u$/H@tm' +h& <EFØ=#|G wXyzWRl-cUrؗ\2e $1"rb, R9,^ djL,hhUx0#"MЙ *VB@-e 03ȅ=FHi4I2)ȇXQp0QA#M rSWB(D +lm<0섊6EJ!E#%* u)<ldRQyF6M<ꁆ钖T(ftm(J-Xh}̋9i6>2:q@lb2"+HgXz%Q,!8R 90R/Z߈@fZPaBj#ـy00:y@JBL0cY +x e@]4F0@tC,,qaIVH JThDaf赐4:xЦh&SPX +9)bɆH܈o.ffH0 8ٝ*vuF*t$Z4IP(}B8))prp/ R,=Pڀg[K< +IȄ$۸Ϫq,Aeo1s53AFH@g#ҙCI@K۸۸P6B3Z+ƍ$REeG (J}0hKӛ\1qz6.Sn!ay&& *MLP`E801AC?)gʎ*7g.,]jfFF8;(*/1gS/i$LMo,4J@~1xm*" LbX`JXEb+Z{RR\)YBvXwb[_|3xjg|^[]׍5⚳ƹJmyni_ʹ*nqx/]޼H3B]9SMj\oՋk5jO);q/g;W8?v|+WTocJUi/檭 fRE{yk\Y/roivo/4;wW)_1cfzk\oMiv}i,V[V͗ޫf|wzug{޵ps~~1ޏt/lq8zvq + _8|5{m: ]zt.f>"uUn뜩cNgѫƮ;pquu1o-,pc{fzӚ{{[yWL^T\9ETzŭ),(ۺ}~JvVT~>^yؾu.t:W}녦׵~}\0_9U*ntۋ+.ֽۿz Tt}ÅV[\i]|]V7w㜫h3Kzu/tco꽠xgVVwvR֮).(^޺L{VK7*~i_|ԊKY;pqŝ~Mu[,nWZ}W׿\7NE17\썯u{5~L~wz߫ŕϿL?oji; ]7_VBk7ݸ^5/o?N-~M[{w{TV}]{[R׻.Zةaچŷ ]p+u WM]_JRx W*4lョ5W"^ YٟZ|k:zx]iqxcQ׭w/ںgn6ߌhoܥR}ZݙjKEƩ[{޶޽EWzhMmZkiմ]3/USOo~zݢMbhziv7N/jkWqϷ7NUojڬ7zӍ<7N~7n3x:tߧ{U]lW1k7Nڼ1Z{[YqjiTXbN)343S00 I%4iP(F26"@ +@ +b uZAwq"Bfn%)[⡛OHWͬ_Mkݍ{!;5QKEK+m NQj if ou Rc垧n-fDk:?]!,RnN!wiqulTx&(G޶=Ta>Ru!xB7{GaJivܝE#1nD#9m{n|B☥s'ˏ=Ba,pTnq}O➍k䜂-.C?::IU]7 +FZ8bU h B>a?;}Tqᅰ%lXfj֪EDr!|<Ikz`UmuwvKKb6$.iyBʊܟ V;J)Hzsqp>j:ҽ~$V+r=jdUWjM`'^r A_Lڢ'_rs> o{l۩M li 4}RL@1[- i#My+on3VR|r6!*anF7#o^:jQzㅨĚ%Is k?.[邢J!xH# cjRvVAnalO̓ ݓ,hSxlE* 5(Ձr@8`7/ &;KFG9g:w&76F}}Cs?Gbt*Sa@9> FyMGL[=MiqC3$kR^?K&jLB 0URlM_{o1ɪ]K<ߢ($64WD,qN`2ݵD!c`~nQ"g*k!ţJ]y)ƪ gN<ۑm)Sdlh+R髐؀ӡd;;F6^dr^.]%0lLIS53 5K7UGǁK eT@B9RNA x\,13^G#_SI Gƻ/d/[&E *k\T!auu>H}j;A5oL7BS5֤_{xۤ%a#ѪveȄo# +pCpjDb@[Nw~륉?f ;JF3;G(>!8SY}oMkt6_v݊e"㽐#2l{}hLan|;.}moQ $|V464@6U4}3@zPAaf=Y䃣Q*h¸zVO[ZB ,`{C_ }+z{*PUz6*5 +DF2oהQP2ʃIk@2]=*K$ۿL;@&'H<@)_~Ne WueɿH+0:E!C췌 `x `q(_(u)5ڠ@%22{^^9gYF08#Im@q2Uо<X;+(>}ݦ cΘȠ^4O+bbFʎ5f((9Q!S4 b`sBPfFo()r\8BQ'چܷ;0 fD*k16`x_Ug?2VK!WS0V ]4FwbH. )F>6%bWSj cp*uX׎ g<qem%*=&s RG`& + +?`t +Y^,5+LԖf"Z%H}e(Τo +4 +ƮG=2вWkZpң:cTs8>_HuV' +2UI$J}8T4&3 ޴߉lPꚭz<J#9u tfiFMei9zIDP_0$Od&wߗACIW ܡЉX+owJ1r҄@ g`cM)U:7 +H. /S-FTuq/]!Uۗ|n8 +K OR{%(2K'Ԗ!j Ѯv" +4ݕÄ^Fu,I'Sԅ4ZQY䮭[l@awHۣRcЗZ;0#RΦv|z2q1b 1%5ŌTYu$0dI6pRm羇0THzͫ=?Aa {aut*IDՎ"m/ww\ӑ̜ (W(3`MOؐNhV"ޘ%24碜6%شS4&|RM<DwI~3EP'~E!"Mm`E<9I,A +xS3H'n\kqʦo;^% 5wƑ-9S=Ƭ(!S_8/>;"u'^rb͊7@3t)'{7VI 1oQ!$K9M +"!IZZՁg)nj]7طKUw F ߃dQv ^P_o$^\͚#˽ˆos:P1w n|`C= +$FⳢUQ4 +{؝l|2N"#16! +jAnz#. k/YTm$h˾U+*27Ew*Tmd%S-БXjgn΄!c'GTj֢2*2K01p TvU*5 -H| jdVKs_f]lcND#aiBERw]X7 KqluHvXk=.~pLDl FIGװHGj&iNW V\ +пTleFc>N͔ +6]S~uELjWA +lR)Cy'U5|WPId;"zB&#9x"L `BXsnR@tA:77k?hZ\l=:=?%}D=yI>ϊۨeΕcC,[MܾBr=vl3I\p^~UU^OPhD=i +qa57/s?#^Y6epnSxd *258rRߵ-ENH)j L T+ʅ_2eU8pTiVy3psUۥvDglV'X#Ws6")mg.2 WVKA2o$TH ]aBxԗ0peMPa񦌕)_< hhD }K]8ئLov\l޺R*TXp}ywLbA EciUb:u0P>fnLXMWi)SkBd{|" =j}ؤГtZ TM}Gg +[Wb?NQ5cå1.oE4H0|!įI쩤wqD s0yr@~:ns+oF}%,/3HoeWnRr'{qҵ&5ֱBf̱8i6]b=}੖! +r;^T\Ғ]F.rZٲ +||EY$|)EA;fna7퐓7 [ ØŢoXb 7f*蟒mzlcwFѴfZfeˊf$)v6,ɼVlNeY'ijly kYv$_j+ ,kI=VwYExnYSԌ2P, 7ɸq@dްT3 6 ǵ3XF^+ p!v), (pF_Is %iHʫyfOp S?*E>j2$gj>ɷ%Xa4&?7Mx JrqLNTx=:IlUF] zj+ C/:l^+S;e()ͨsj}(9V*ƞWPvpŔG[4y7E 訔ڈm {cr!3bXX/R.T|6,?W?1*˒C!\0zy +<FNN|_he;#־ٿ`{]h(QY~b=00tJ*!?^+k "6'*W,¨S'DC| +*Aj͍ ]az_^ #)zUaf!p:Q4JRIL31bt/[%<dLk1&V"ĸWrULKS:kOg2&BAtF-p7SxWO*lu!62Xro$u@O$iL:`|E084./~PkS$Dž%6!w]%%nxlu9Q7'meq5Vkеpxg8@SiIU/Z+ZywPJѐ4 +cYw{&ID"5W$&Z5&xmJ)3~b2̲B}j[Y\W";!194*_ hckk^q&rZϸ)A02L.g pOAmuT'Rn Ub|HN8Jl7bvH! E|@ُ6JG):>.*6vE3o-}F0ӒdXнF>ڻй y:x D ȻZgJʗ5)Dwiv]GFwh_Btv_'ډN<13p@͞p-0;ha=ӣG\n(S .-@7% Oݵit_:0NB܌t׃b5&)61CX._ݕп*`E.d6z)"MPWR\Kd fgɍY ޅLI4V$suJ৓qw;IBe5SpP7HP@I?ӣ&ˈ 8B7}>\4zh. +n+}!{pCP"nFEI軅 <1($RMUH`-LH("SSBEf$iQ=80-^l; +f$uwnuZ=UxwqI,z5%4[`SeK[]+}C3ajF=ojNjӝA;ЗBn6,Yb^bx6t,41Z6DcÖW7ĺ_/@G ˥Ϋҝ`y{x5#}[ MHB"W H~1Ӭ;B62Tw!a 5$s?Իj(oi297>KԘn2Gݨ!!\tY:bl ɷM&or(0l + ͌N3,s"i?̰d ⳤl:؆4Sa4[P1V[q!@UVJ$mX8ykBjV6y_0&($0-pk҂Ɇ/U|+zo ' jia.Bs)кez-U~\HjC+DB+NmzBAڳ ۂ4Y l um0ıejo.lT*GEѪ91lDHG",(is_v Z}&G'`] +؀KoJKқJ +Rw%ɽ n.; V_٤ Qm{w0190^,?zJn & I|m/ 7*Ye ?^ +V@O!.޼G`@ 7Ԉ&uܼ(4hDSȒ_z Pg6%tᰫJeB7GɅ/rY+Eĵ9{j"J| +4pn8\ x5QpgTh* ЃP5Ǖ edPR&S+vuܻ1o)}n$<_j/qMp7eMҾ+j%SWۄΕ7#y-60> hDa Dq֚Aar'"FRu +)q䦣PmwW=]5[Dx74ͪ ېM4j@sFuj*ENK?0Bc`]ܛ %3zWGH} Af~naQ2f0/-jkNptlP;`O~B%(1E0)`P\*`pz]?0W%i7(279!ԫ2&9t>Izg 9^`1UFfV˶Amo(Y]BiXpV8ȄIg"7Ҥ+H1)?^v:&fi¥ԁt48FI[\a*~2hRaozPiD# +tOAl+/ OɎeI%.:h^e4sf B]D\w3!!Əz) +tUpJ~fmor=*Cx]췒VR#R:*TC9;7%vpfji@&sϐ)NXs/VYk[yCIr#㒻d\.CF]b2A/ q[2#=r,)D/HRlׂ.8|DvK~_o%N~57tEdcFM6H6=+,{( +r/D;fW[;l ,XxfMLj +endstream endobj 30 0 obj [/Indexed/DeviceRGB 255 31 0 R] endobj 31 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX$6Ra!<<'!!!*'!!rrmPX()~> +endstream endobj 24 0 obj <> endobj 32 0 obj [/View/Design] endobj 33 0 obj <>>> endobj 28 0 obj <> endobj 27 0 obj [/ICCBased 34 0 R] endobj 34 0 obj <>stream +HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  + 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 +V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= +x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- +ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 +N')].uJr + wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 +n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km +endstream endobj 25 0 obj [24 0 R] endobj 35 0 obj <> endobj xref +0 36 +0000000004 65535 f +0000000016 00000 n +0000000147 00000 n +0000013733 00000 n +0000000000 00000 f +0000013784 00000 n +0000000000 00000 f +0000000000 00000 f +0000016323 00000 n +0000016395 00000 n +0000016634 00000 n +0000018275 00000 n +0000083864 00000 n +0000149453 00000 n +0000215042 00000 n +0000280631 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000304029 00000 n +0000307022 00000 n +0000014175 00000 n +0000304329 00000 n +0000304216 00000 n +0000015941 00000 n +0000303454 00000 n +0000303502 00000 n +0000304100 00000 n +0000304131 00000 n +0000304364 00000 n +0000307047 00000 n +trailer +<<8E9FFD9F61D2424E89A75D8D75A99DA7>]>> +startxref +307229 +%%EOF diff --git a/data/tr2/ship/Tomb2.exe b/data/tr2/ship/Tomb2.exe new file mode 100644 index 000000000..eb9de022c Binary files /dev/null and b/data/tr2/ship/Tomb2.exe differ diff --git a/data/tr2/ship/cfg/TR2X_gameflow.json5 b/data/tr2/ship/cfg/TR2X_gameflow.json5 new file mode 100644 index 000000000..2db7371a1 --- /dev/null +++ b/data/tr2/ship/cfg/TR2X_gameflow.json5 @@ -0,0 +1,410 @@ +{ + // NOTE: bad changes to this file may result in crashes. + // Lines starting with double slashes are comments and are ignored. + + "levels": [ + // 0. Lara's Home + {}, + + // 1. The Great Wall + {}, + + // 2. Venice + {}, + + // 3. Bartoli's Hideout + {}, + + // 4. Opera House + {}, + + // 5. Offshore Rig + {}, + + // 6. Diving Area + {}, + + // 7. 40 Fathoms + {}, + + // 8. The Wreck of Maria Doria + {}, + + // 9. Living Quarters + {}, + + // 10. The Deck + {}, + + // 11. Tibetan Foothills + { + "object_strings": { + "O_TIGER": "Snow Leopard", + }, + }, + + // 12. Barkhang Monastery + {}, + + // 13. Catacombs of the Talion + { + "object_strings": { + "O_TIGER": "Snow Leopard", + }, + }, + + // 14. Ice Palace + { + "object_strings": { + "O_TIGER": "Snow Leopard", + }, + }, + + // 15. Temple of Xian + {}, + + // 16. Floating Islands + {}, + + // 17. The Dragon's Lair + {}, + + // 18. Home Sweet Home + {}, + + // 19. Demo 1 + {}, + + // 20. Demo 2 + {}, + + // 21. Demo 3 + {}, + ], + + "object_strings": { + "O_LARA": "Lara", + "O_LARA_PISTOLS": "Pistols Animation", + "O_LARA_HAIR": "Braid", + "O_LARA_SHOTGUN": "Shotgun Animation", + "O_LARA_MAGNUMS": "Magnums Animation", + "O_LARA_UZIS": "Uzis Animation", + "O_LARA_M16": "M16 Animation", + "O_LARA_GRENADE": "Grenade Launcher Animation", + "O_LARA_HARPOON": "Harpoon Animation", + "O_LARA_FLARE": "Flare Animation", + "O_LARA_SKIDOO": "Snowmobile Animation", + "O_LARA_BOAT": "Boat Animation", + "O_LARA_EXTRA": "Lara's Extra Animation", + "O_SKIDOO_FAST": "Red Snowmobile", + "O_BOAT": "Boat", + "O_DOG": "Dog", + "O_CULT_1": "Masked Goon 1", + "O_CULT_1A": "Masked Goon 2", + "O_CULT_1B": "Masked Goon 3", + "O_CULT_2": "Knife Thrower", + "O_CULT_3": "Shotgun Goon", + "O_MOUSE": "Rat", + "O_DRAGON_FRONT": "Dragon Front", + "O_DRAGON_BACK": "Dragon Back", + "O_GONDOLA": "Gondola", + "O_SHARK": "Shark", + "O_EEL": "Eel", + "O_BIG_EEL": "Big Eel", + "O_BARRACUDA": "Barracuda", + "O_DIVER": "Scuba Diver", + "O_WORKER_1": "Gunman Goon 1", + "O_WORKER_2": "Gunman Goon 2", + "O_WORKER_3": "Stick Wielding Goon 1", + "O_WORKER_4": "Stick Wielding Goon 2", + "O_WORKER_5": "Flamethrower Goon", + "O_JELLY": "Jellyfish", + "O_SPIDER": "Spider", + "O_BIG_SPIDER": "Giant Spider", + "O_CROW": "Crow", + "O_TIGER": "Tiger", + "O_BARTOLI": "Marco Bartoli", + "O_XIAN_SPEARMAN": "Xian Spearman", + "O_XIAN_SPEARMAN_STATUE": "Xian Spearman Statue", + "O_XIAN_KNIGHT": "Xian Knight", + "O_XIAN_KNIGHT_STATUE": "Xian Knight", + "O_YETI": "Yeti", + "O_GIANT_YETI": "Bird Monster", + "O_EAGLE": "Eagle", + "O_BANDIT_1": "Mercenary 1", + "O_BANDIT_2": "Mercenary 2", + "O_BANDIT_2B": "Mercenary 3", + "O_SKIDOO_ARMED": "Black Snowmobile", + "O_SKIDMAN": "Black Snowmobile Driver", + "O_MONK_1": "Monk 1", + "O_MONK_2": "Monk 2", + "O_FALLING_BLOCK_1": "Falling Block 1", + "O_FALLING_BLOCK_2": "Falling Block 2", + "O_FALLING_BLOCK_3": "Loose Boards", + "O_PENDULUM_1": "Sandbag", + "O_SPIKES": "Spikes", + "O_ROLLING_BALL_1": "Boulder 1", + "O_DARTS": "Disc", + "O_DART_EMITTER": "Disc Emitter", + "O_DRAWBRIDGE": "Drawbridge", + "O_TEETH_TRAP": "Teeth Trap", + "O_LIFT": "Lift", + "O_GENERAL": "Minisub", + "O_MOVABLE_BLOCK_1": "Push Block 1", + "O_MOVABLE_BLOCK_2": "Push Block 2", + "O_MOVABLE_BLOCK_3": "Push Block 3", + "O_MOVABLE_BLOCK_4": "Push Block 4", + "O_BIG_BOWL": "Lava Bowl", + "O_WINDOW_1": "Breakable Window 1", + "O_WINDOW_2": "Breakable Window 2", + "O_WINDOW_3": "Breakable Window 3", + "O_WINDOW_4": "Breakable Window 4", + "O_PROPELLER_1": "Airplane Propeller", + "O_POWER_SAW": "Power Saw", + "O_HOOK": "Hook", + "O_FALLING_CEILING": "Falling Ceiling", + "O_SPINNING_BLADE": "Spinning Blade", + "O_BLADE": "Wall-mounted Blade", + "O_KILLER_STATUE": "Statue with Sword", + "O_ROLLING_BALL_2": "Boulder 2", + "O_ICICLE": "Icicles", + "O_SPIKE_WALL": "Spike Wall", + "O_SPRING_BOARD": "Springboard", + "O_CEILING_SPIKES": "Spiky Ceiling", + "O_BELL": "Bell", + "O_WATER_SPRITE": "Boat Wake", + "O_SNOW_SPRITE": "Snowmobile Wale", + "O_SKIDOO_LARA": "Snowmobile Animation", + "O_SWITCH_TYPE_1": "Switch 1", + "O_SWITCH_TYPE_2": "Switch 2", + "O_PROPELLER_2": "Underwater Propeller", + "O_PROPELLER_3": "Air Fan", + "O_PENDULUM_2": "Swinging Box", + "O_MESH_SWAP_1": "Mesh Swap 1", + "O_MESH_SWAP_2": "Mesh Swap 2", + "O_LARA_SWAP": "Lara Mesh Swap", + "O_TEXT_BOX": "UI Frame", + "O_ROLLING_BALL_3": "Boulder 3", + "O_DEATH_SLIDE": "Zipline Handle", + "O_SWITCH_TYPE_3": "Switch 3", + "O_SWITCH_TYPE_4": "Switch 4", + "O_SWITCH_TYPE_5": "Switch 5", + "O_DOOR_TYPE_1": "Door 1", + "O_DOOR_TYPE_2": "Door 2", + "O_DOOR_TYPE_3": "Door 3", + "O_DOOR_TYPE_4": "Door 4", + "O_DOOR_TYPE_5": "Door 5", + "O_DOOR_TYPE_6": "Door 6", + "O_DOOR_TYPE_7": "Door 7", + "O_DOOR_TYPE_8": "Door 8", + "O_TRAPDOOR_TYPE_1": "Trapdoor 1", + "O_TRAPDOOR_TYPE_2": "Trapdoor 2", + "O_TRAPDOOR_TYPE_3": "Trapdoor 3", + "O_BRIDGE_FLAT": "Bridge Flat", + "O_BRIDGE_TILT_1": "Bridge Tilt 1", + "O_BRIDGE_TILT_2": "Bridge Tilt 2", + "O_PASSPORT_OPTION": "Passport Option", + "O_COMPASS_OPTION": "Compass Option", + "O_PHOTO_OPTION": "Photo Option", + "O_PLAYER_1": "Cutscene Actor 1", + "O_PLAYER_2": "Cutscene Actor 2", + "O_PLAYER_3": "Cutscene Actor 3", + "O_PLAYER_4": "Cutscene Actor 4", + "O_PLAYER_5": "Cutscene Actor 5", + "O_PLAYER_6": "Cutscene Actor 6", + "O_PLAYER_7": "Cutscene Actor 7", + "O_PLAYER_8": "Cutscene Actor 8", + "O_PLAYER_9": "Cutscene Actor 9", + "O_PLAYER_10": "Cutscene Actor 10", + "O_PASSPORT_CLOSED": "Passport Closed", + "O_COMPASS_ITEM": "Compass", + "O_PISTOL_ITEM": "Pistols", + "O_SHOTGUN_ITEM": "Shotgun", + "O_MAGNUM_ITEM": "Automatic Pistols", + "O_UZI_ITEM": "Uzis", + "O_HARPOON_ITEM": "Harpoon", + "O_M16_ITEM": "M16", + "O_GRENADE_ITEM": "Grenade Launcher", + "O_PISTOL_AMMO_ITEM": "Pistol Ammo", + "O_SHOTGUN_AMMO_ITEM": "Shotgun Ammo", + "O_MAGNUM_AMMO_ITEM": "Magnum Ammo", + "O_UZI_AMMO_ITEM": "Uzi Ammo", + "O_HARPOON_AMMO_ITEM": "Harpoon Ammo", + "O_M16_AMMO_ITEM": "M16 Ammo", + "O_GRENADE_AMMO_ITEM": "Grenade Launcher Ammo", + "O_SMALL_MEDIPACK_ITEM": "Small Medipack", + "O_LARGE_MEDIPACK_ITEM": "Large Medipack", + "O_FLARES_ITEM": "Flares Box", + "O_FLARE_ITEM": "Flare", + "O_DETAIL_OPTION": "Detail Option", + "O_SOUND_OPTION": "Sound Option", + "O_CONTROL_OPTION": "Controls Option", + "O_GAMMA_OPTION": "Gamma Option", + "O_PISTOL_OPTION": "Pistol Option", + "O_SHOTGUN_OPTION": "Shotgun Option", + "O_MAGNUM_OPTION": "Magnums Option", + "O_UZI_OPTION": "Uzis Option", + "O_HARPOON_OPTION": "Harpoon Option", + "O_M16_OPTION": "M16 Option", + "O_GRENADE_OPTION": "Grenade Launcher Option", + "O_PISTOL_AMMO_OPTION": "Pistol Ammo Option", + "O_SHOTGUN_AMMO_OPTION": "Shotgun Ammo Option", + "O_MAGNUM_AMMO_OPTION": "Magnum Ammo Option", + "O_UZI_AMMO_OPTION": "Uzi Ammo Option", + "O_HARPOON_AMMO_OPTION": "Harpoon Ammo Option", + "O_M16_AMMO_OPTION": "M16 Ammo Option", + "O_GRENADE_AMMO_OPTION": "Grenade Launcher Ammo Option", + "O_SMALL_MEDIPACK_OPTION": "Small Medipack Option", + "O_LARGE_MEDIPACK_OPTION": "Large Medipack Option", + "O_FLARES_OPTION": "Flare Option", + "O_PUZZLE_ITEM_1": "Puzzle Item 1", + "O_PUZZLE_ITEM_2": "Puzzle Item 2", + "O_PUZZLE_ITEM_3": "Puzzle Item 3", + "O_PUZZLE_ITEM_4": "Puzzle Item 4", + "O_PUZZLE_OPTION_1": "Puzzle Option 1", + "O_PUZZLE_OPTION_2": "Puzzle Option 2", + "O_PUZZLE_OPTION_3": "Puzzle Option 3", + "O_PUZZLE_OPTION_4": "Puzzle Option 4", + "O_PUZZLE_HOLE_1": "Puzzle Hole 1 (Empty)", + "O_PUZZLE_HOLE_2": "Puzzle Hole 2 (Empty)", + "O_PUZZLE_HOLE_3": "Puzzle Hole 3 (Empty)", + "O_PUZZLE_HOLE_4": "Puzzle Hole 4 (Empty)", + "O_PUZZLE_DONE_1": "Puzzle Hole 1 (Done)", + "O_PUZZLE_DONE_2": "Puzzle Hole 2 (Done)", + "O_PUZZLE_DONE_3": "Puzzle Hole 3 (Done)", + "O_PUZZLE_DONE_4": "Puzzle Hole 4 (Done)", + "O_SECRET_1": "Secret 1", + "O_SECRET_2": "Secret 2", + "O_SECRET_3": "Secret 3", + "O_KEY_ITEM_1": "Key 1", + "O_KEY_ITEM_2": "Key 2", + "O_KEY_ITEM_3": "Key 3", + "O_KEY_ITEM_4": "Key 4", + "O_KEY_OPTION_1": "Key Option 1", + "O_KEY_OPTION_2": "Key Option 2", + "O_KEY_OPTION_3": "Key Option 3", + "O_KEY_OPTION_4": "Key Option 4", + "O_KEY_HOLE_1": "Key Hole 1", + "O_KEY_HOLE_2": "Key Hole 2", + "O_KEY_HOLE_3": "Key Hole 3", + "O_KEY_HOLE_4": "Key Hole 4", + "O_PICKUP_ITEM_1": "Pickup Item 1", + "O_PICKUP_ITEM_2": "Pickup Item 2", + "O_PICKUP_OPTION_1": "Pickup Option 1", + "O_PICKUP_OPTION_2": "Pickup Option 2", + "O_SPHERE_OF_DOOM_1": "Dragon Explosion 1", + "O_SPHERE_OF_DOOM_2": "Dragon Explosion 2", + "O_SPHERE_OF_DOOM_3": "Dragon Explosion 3", + "O_ALARM_SOUND": "Alarm", + "O_BIRD_TWEETER_1": "Dripping Water", + "O_DINO": "T-Rex", + "O_BIRD_TWEETER_2": "Singing Birds", + "O_CLOCK_CHIMES": "Bartoli Hideout clock", + "O_DRAGON_BONES_1": "Placeholder", + "O_DRAGON_BONES_2": "Dragon Bones Front", + "O_DRAGON_BONES_3": "Dragon Bones Back", + "O_HOT_LIQUID": "Extra Fire", + "O_BOAT_BITS": "Boat Bits", + "O_MINE": "Aquatic Mine", + "O_INV_BACKGROUND": "Menu Background", + "O_FX_RESERVED": "Gray disk", + "O_GONG_BONGER": "Gong Stick", + "O_DETONATOR_1": "Gong", + "O_DETONATOR_2": "Detonator Box", + "O_COPTER": "Helicopter", + "O_EXPLOSION": "Explosion", + "O_SPLASH": "Water Ripples", + "O_BUBBLES": "Bubbles", + "O_BUBBLE_EMITTER": "Bubbles Emitter", + "O_BLOOD": "Blood", + "O_DART_EFFECT": "Dart Effect", + "O_FLARE_FIRE": "Flare sparks", + "O_GLOW": "Glow", + "O_GLOW_RESERVED": "Map Glow", + "O_RICOCHET": "Ricochet", + "O_TWINKLE": "Sparkles", + "O_GUN_FLASH": "Gun Flash", + "O_M16_FLASH": "M16 Flash", + "O_BODY_PART": "Body Part", + "O_CAMERA_TARGET": "Camera Target", + "O_WATERFALL": "Waterfall Mist", + "O_MISSILE_HARPOON": "Missile Harpoon", + "O_MISSILE_FLAME": "Missile Flame", + "O_MISSILE_KNIFE": "Missile Knife", + "O_GRENADE": "Grenade", + "O_HARPOON_BOLT": "Harpoon Bolt", + "O_EMBER": "Ember", + "O_EMBER_EMITTER": "Ember Emitter", + "O_FLAME": "Flame", + "O_FLAME_EMITTER": "Flame Emitter", + "O_SKYBOX": "Skybox", + "O_ALPHABET": "Alphabet", + "O_DYING_MONK": "Dying monk", + "O_DING_DONG": "Doorbell", + "O_LARA_ALARM": "Alarm Bell", + "O_MINI_COPTER": "Helicopter 2", + "O_WINSTON": "Winston", + "O_ASSAULT_DIGITS": "Assault Digits", + "O_FINAL_LEVEL_COUNTER": "Final Level Counter", + "O_CUT_SHOTGUN": "Shotgun Shower Animation", + "O_EARTHQUAKE": "Earthquake", + }, + + "game_strings": { + "MISC_OFF": "Off", + "MISC_ON": "On", + "OSD_AMBIGUOUS_INPUT_2": "Ambiguous input: %s and %s", + "OSD_AMBIGUOUS_INPUT_3": "Ambiguous input: %s, %s, ...", + "OSD_COMMAND_BAD_INVOCATION": "Invalid invocation: %s", + "OSD_COMMAND_UNAVAILABLE": "This command is not currently available", + "OSD_COMPLETE_LEVEL": "Level complete!", + "OSD_CONFIG_OPTION_GET": "%s is currently set to %s", + "OSD_CONFIG_OPTION_SET": "%s changed to %s", + "OSD_CONFIG_OPTION_UNKNOWN_OPTION": "Unknown option: %s", + "OSD_CURRENT_HEALTH_GET": "Current Lara's health: %d", + "OSD_CURRENT_HEALTH_SET": "Lara's health set to %d", + "OSD_DOOR_CLOSE": "Close Sesame!", + "OSD_DOOR_OPEN": "Open Sesame!", + "OSD_DOOR_OPEN_FAIL": "No doors in Lara's proximity", + "OSD_FLIPMAP_FAIL_ALREADY_OFF": "Flipmap is already OFF", + "OSD_FLIPMAP_FAIL_ALREADY_ON": "Flipmap is already ON", + "OSD_FLIPMAP_OFF": "Flipmap set to OFF", + "OSD_FLIPMAP_ON": "Flipmap set to ON", + "OSD_FLY_MODE_OFF": "Fly mode disabled", + "OSD_FLY_MODE_ON": "Fly mode enabled", + "OSD_GIVE_ITEM": "Added %s to Lara's inventory", + "OSD_GIVE_ITEM_ALL_GUNS": "Lock'n'load - Lara's armed to the teeth!", + "OSD_GIVE_ITEM_ALL_KEYS": "Surprise! Every key item Lara needs is now in her backpack.", + "OSD_GIVE_ITEM_CHEAT": "Lara's backpack just got way heavier!", + "OSD_HEAL_ALREADY_FULL_HP": "Lara's already at full health", + "OSD_HEAL_SUCCESS": "Healed Lara back to full health", + "OSD_INVALID_ITEM": "Unknown item: %s", + "OSD_INVALID_LEVEL": "Invalid level", + "OSD_INVALID_OBJECT": "Invalid object", + "OSD_INVALID_ROOM": "Invalid room: %d. Valid rooms are 0-%d", + "OSD_INVALID_SAMPLE": "Invalid sound: %d", + "OSD_INVALID_SAVE_SLOT": "Invalid save slot %d", + "OSD_KILL": "Bye-bye!", + "OSD_KILL_ALL": "Poof! %d enemies gone!", + "OSD_KILL_ALL_FAIL": "Uh-oh, there are no enemies left to kill...", + "OSD_KILL_FAIL": "No enemy nearby...", + "OSD_LOAD_GAME": "Loaded game from save slot %d", + "OSD_LOAD_GAME_FAIL_INVALID_SLOT": "Invalid save slot %d", + "OSD_LOAD_GAME_FAIL_UNAVAILABLE_SLOT": "Save slot %d is not available", + "OSD_OBJECT_NOT_FOUND": "Object not found", + "OSD_PLAY_LEVEL": "Loading %s", + "OSD_POS_GET": "Room: %d\nPosition: %.3f, %.3f, %.3f\nRotation: %.3f,%.3f,%.3f", + "OSD_POS_SET_ITEM": "Teleported to object: %s", + "OSD_POS_SET_ITEM_FAIL": "Failed to teleport to object: %s", + "OSD_POS_SET_POS": "Teleported to position: %.3f %.3f %.3f", + "OSD_POS_SET_POS_FAIL": "Failed to teleport to position: %.3f %.3f %.3f", + "OSD_POS_SET_ROOM": "Teleported to room: %d", + "OSD_POS_SET_ROOM_FAIL": "Failed to teleport to room: %d", + "OSD_SAVE_GAME": "Saved game to save slot %d", + "OSD_SAVE_GAME_FAIL": "Cannot save the game in the current state", + "OSD_SAVE_GAME_FAIL_INVALID_SLOT": "Invalid save slot %d", + "OSD_SOUND_AVAILABLE_SAMPLES": "Available sounds: %s", + "OSD_SOUND_PLAYING_SAMPLE": "Playing sound %d", + "OSD_UNKNOWN_COMMAND": "Unknown command: %s", + }, +} diff --git a/data/tr2/version.rc b/data/tr2/version.rc new file mode 100644 index 000000000..e0011a53f --- /dev/null +++ b/data/tr2/version.rc @@ -0,0 +1,22 @@ +1 VERSIONINFO +FILEVERSION 0,0,0,0 +PRODUCTVERSION 0,0,0,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904E4" + BEGIN + VALUE "CompanyName", "rr-" + VALUE "FileDescription", "Tomb Raider II: Community Edition" + VALUE "FileVersion", "{version}" + VALUE "InternalName", "TR2X" + VALUE "OriginalFilename", "TR2X.exe" + VALUE "ProductName", "Tomb Raider II: Community Edition" + VALUE "ProductVersion", "{version}" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1252 + END +END diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md new file mode 100644 index 000000000..31627119b --- /dev/null +++ b/docs/tr2/CHANGELOG.md @@ -0,0 +1,82 @@ +## [Unreleased](https://github.com/LostArtefacts/TR2X/compare/stable...develop) - ××××-××-×× +- added `/sfx` command +- added `/nextlevel` alias to `/endlevel` console command +- added `/quit` alias to `/exit` console command +- changed `/set` console command to do fuzzy matching (LostArtefacts/libtrx#38) +- fixed crash in the `/set` console command (regression from 0.3) +- fixed using console in cutscenes immediately exiting the game (regression from 0.3) +- fixed Lara remaining tilted when teleporting off a vehicle while on a slope (#275, regression from 0.3) +- fixed `/endlevel` displaying a success message in the title screen + +## [0.3](https://github.com/LostArtefacts/TR2X/compare/0.2-460-g4721b93...0.3) - 2024-09-20 +- added new console commands: + - `/endlevel` + - `/demo` + - `/title` + - `/play [level]` + - `/load [slot]` + - `/save [slot]` + - `/exit` + - `/fly` + - `/give` + - `/kill` + - `/flip` + - `/set` +- added an ability to remap the console key (#163) +- added `/tp` console command's ability to teleport to specific items +- added `/fly` console command's ability to open nearest doors +- added an option to fix M16 accuracy while running (#45) +- added a .NET-based configuration tool (#197) +- changed the default flare key from `/` to `.` to avoid conflicts with the console (#163) +- fixed numeric keys interfering with the demos (#172) +- fixed explosions sometimes being drawn too dark (#187) +- fixed killing the T-Rex with a grenade launcher crashing the game (#168) +- fixed secret rewards not displaying shotgun ammo (#159) +- fixed controls dialog remapping being too sensitive (#5) +- fixed `/tp` console command during special animations in HSH and Offshore Rig (#178, regression from 0.2) +- fixed `/hp` console command taking arbitrary integers +- fixed console commands being able to interfere with demos, cutscenes and the title screen (#182, #179, regression from 0.2) +- fixed console registering key inputs too eagerly (regression from 0.2) +- fixed console not being drawn in cutscenes (#180, regression from 0.2) +- fixed sounds not playing under certain circumstances (#113, regression from 0.2) +- fixed the excessive pitch and playback speed correction for music files with sampling rate other than 44100 Hz (LostArtefacts/TR1X#1417, regression from 0.2) +- fixed a crash potential with certain music files (regression from 0.2) +- fixed enemy movement patterns in demo 1 and demo 3 (#98, regression from 0.1) +- fixed underwater creatures dying (#98, regression from 0.1) +- fixed a crash when spawning enemy drops (#125, regression from 0.1) +- fixed how sprites are shaded (#134, regression from 0.1.1) +- fixed enemies unable to climb (#138, regression from 0.1) +- fixed items not being reset between level loads (#142, regression from 0.1) +- fixed pulling the dagger from the dragon not activating triggers (#148, regression from 0.1) +- fixed the music at the beginning of Offshore Rig not playing (#150, regression from 0.1) +- fixed wade animation when moving from deep to shallow water (#231, regression from 0.1) +- fixed the distorted skybox in room 5 of Barkhang Monastery (#196) +- improved initial level load time by lazy-loading audio samples (#114) +- improved crash debug information (#137) +- improved the console caret sprite (#91) + +## [0.2](https://github.com/LostArtefacts/TR2X/compare/0.1.1...0.2) - 2024-05-07 +- added dev console with the following commands: + - `/pos` + - `/tp [room_num]` + - `/tp [x] [y] [z]` + - `/hp` + - `/hp [num]` + - `/heal` +- changed the music backend from WinMM to libtrx (SDL + libav) +- changed the sound backend from DirectX to libtrx (SDL + libav) +- fixed seams around underwater portals (#76, regression from 0.1) +- fixed Lara's climb down camera angle (#78, regression from 0.1) +- fixed healthbar and airbar flashing the wrong way when at low values (#82, regression from 0.1) + +## [0.1.1](https://github.com/LostArtefacts/TR2X/compare/0.1...0.1.1) - 2024-04-27 +- fixed Lara's shadow with z-buffer option on (#64, regression from 0.1) +- fixed rare camera issues (#65, regression from 0.1) +- fixed flat rectangle colors (#70, regression from 0.1) +- fixed medpacks staying open after use in Lara's inventory (#69, regression from 0.1) +- fixed pickup sprites UI drawn forever in Lara's Home (#68, regression from 0.1) + +## [0.1](https://github.com/rr-/TR2X/compare/...0.1) - 2024-04-26 +- added version string to the inventory +- fixed CDAudio not playing on certain versions (uses PaulD patch) +- fixed TGA screenshots crashing the game diff --git a/docs/tr2/COMMANDS.md b/docs/tr2/COMMANDS.md new file mode 100644 index 000000000..545a5dcb5 --- /dev/null +++ b/docs/tr2/COMMANDS.md @@ -0,0 +1,65 @@ +# Commands +TR2X introduces a developer console, by default accessible with the / key. +Currently supported commands: + +- `/pos` + Retrieves precise information about Lara's coordinates. Knowledge is power! + +- `/tp {room_number}` + `/tp {x} {y} {z}` + Instant travel! Teleports Lara into a random spot within the specified room or the specified X,Y,Z coordinates. + +- `/hp` + `/hp {health}` + Sets Lara's health points to the specified value. Tougher trials await! + +- `/heal` + Tough day, Lara? Heals our girl back to full health. + +- `/fly` + Turns on the fly cheat. Why even walk? + +- `/give {item_name}` + `/give {num} {item_name}` + `/give all` + `/give guns` + `/give keys` + Gives Lara an item. Try `/give guns` to arm her to the teeth, and `/give keys` to get her all important puzzle items. Ain't nobody got time for searching! + +- `/kill` + `/kill all` + `/kill {enemy_type}` + Tired of rats, spiders, tigers, Italian mafia and literally every living thing trying to spoil your adventure? This command instantly disposes of the nearest enemy, or kills them all at once. + +- `/flip` + `/flip off` + `/flip on` + Switches the global flipmap on or off, turning the reality around you on its head. + +- `/endlevel` + Ends the current level. Ideal for speedruns. + +- `/level {num}` + `/level {name}` + `/play {num}` + `/play {name}` + Plays the specified level. + +- `/load {slot_num}` + Loads the specified savegame slot. + +- `/save {slot_num}` + Saves the game to the specified savegame slot. + +- `/demo` + Starts the demo. + +- `/title` + Exits the game to main screen. + +- `/exit` + Closes the game. + +- `/sfx` +- `/sfx {sound}` + Plays a given sound sample. diff --git a/docs/tr2/README.md b/docs/tr2/README.md new file mode 100644 index 000000000..545af2d10 --- /dev/null +++ b/docs/tr2/README.md @@ -0,0 +1,81 @@ +

+TR2X logo +TR2X logo +

+ +TR2X is an open-source decompilation project for Tomb Raider 2, created as a +sequel to the successful [TR1X](https://github.com/LostArtefacts/TR1X/) project +for Tomb Raider 1. Our project is in the early stages, and our main focus is on +decompiling as much of the game as possible. + +## The situation + +TR2X draws inspiration from existing decompilation efforts, including the +achievements of [TR2Main](https://github.com/Arsunt/TR2Main) developed by +[Arsunt](https://github.com/Arsunt/). However, we emphasize that TR2X is an +independent project and does not directly copy code from TR2Main. + +## Our Mission + +Our mission is to fully decompile Tomb Raider 2 and to eventually enhance the +overall gameplay experience. + +### Key Goals + +- **Comprehensive Decompilation:** + Our primary objective is to achieve a thorough decompilation of Tomb Raider + 2, enabling a deeper understanding of its inner workings. + +- **Enhancement of UK Box Version:** + Our work is based on the UK Box version of Tomb Raider 2. Eventually we hope + to reach feature parity with the Steam/multipatch version, and possibly + TR2Main as well. + +- **Cross-platform Compatibility:** + We are committed to making Tomb Raider 2 run natively on Linux, with the + possibility of supporting Mac systems in the future. + +- **Transparent Development Process:** + We value transparency and aim to maintain a transparent development process, + ensuring regular updates and engagement with the community. + +- **Changelog and Progress Updates:** + We strive to provide detailed and reliable changelogs, documenting + significant updates and changes made to TR2X. Additionally, we will keep + the community informed about the progress of the project. + +TR2X is currently in the early stages of development, focusing on the +decompilation process. We recognize that there is much work to be done. + +## Decompilation progress + +![](docs/progress.svg) + +## Improvements over original game + +#### Gameplay + +- added an option to fix M16 accuracy while running +- fixed killing the T-Rex with a grenade launcher crashing the game +- fixed secret rewards not displaying shotgun ammo + +#### Visuals + +- fixed TGA screenshots crashing the game + +#### Audio + +- fixed music not playing with certain game versions + +#### Mods +- added developer console (accessible with `/`, see [COMMANDS.md] for details) + +## Contributions + +Please refer to our [CONTRIBUTING.md](CONTRIBUTING.md) file for more +information on how to contribute to the project. + +## License + +TR2X follows the GPL license. Please refer to the [COPYING.md](COPYING.md) file +for further details. diff --git a/docs/tr2/progress.svg b/docs/tr2/progress.svg new file mode 100644 index 000000000..52887bba6 --- /dev/null +++ b/docs/tr2/progress.svg @@ -0,0 +1,2528 @@ + + + + + + + + +Legend: + +Function fully decompiled + + + +Function not yet decompiled, but with a known signature + + + +Function not yet decompiled, with an unknown signature + + + +Function not used by the game + + + + +Tomb2.exe progress according to the physical function order: +61.99% (755) · 35.55% (433) · 0% (0) · 2.46% (30) + + + + + + +void __cdecl Matrix_GenerateW2V(PHD_3DPOS *viewpos); +void __cdecl Matrix_LookAt(int32_t xsrc, int32_t ysrc, int32_t zsrc, int32_t xtar, int32_t ytar, int32_t ztar, int16_t roll); +void __cdecl Math_GetVectorAngles(int32_t x, int32_t y, int32_t z, int16_t *dest); +void __cdecl Matrix_RotX(int16_t rx); +void __cdecl Matrix_RotY(int16_t ry); +void __cdecl Matrix_RotZ(int16_t rz); +void __cdecl Matrix_RotYXZ(int16_t ry, int16_t rx, int16_t rz); +void __cdecl Matrix_RotYXZpack(uint32_t rpack); +bool __cdecl Matrix_TranslateRel(int32_t x, int32_t y, int32_t z); +void __cdecl Matrix_TranslateAbs(int32_t x, int32_t y, int32_t z); +void __cdecl Output_InsertPolygons(const int16_t *obj_ptr, int32_t clip); +void __cdecl Output_InsertRoom(const int16_t *obj_ptr, int32_t is_outside); +const int16_t *__cdecl Output_CalcSkyboxLight(const int16_t *obj_ptr); +void __cdecl Output_InsertSkybox(const int16_t *obj_ptr); +void __cdecl Output_InsertInventoryBackground(const int16_t *obj_ptr); +const int16_t *__cdecl Output_CalcObjectVertices(const int16_t *obj_ptr); +const int16_t *__cdecl Output_CalcVerticeLight(const int16_t *obj_ptr); +const int16_t *__cdecl Output_CalcRoomVertices(const int16_t *obj_ptr, int32_t far_clip); +void __cdecl Output_RotateLight(int16_t pitch, int16_t yaw); +void __cdecl Output_InitPolyList(void); +void __cdecl Output_SortPolyList(void); +void __cdecl Output_QuickSort(int32_t left, int32_t right); +void __cdecl Output_PrintPolyList(uint8_t *surface_ptr); +void __cdecl Output_AlterFOV(int16_t fov); +void __cdecl Output_SetNearZ(int32_t near_z); +void __cdecl Output_SetFarZ(int32_t far_z); +void __cdecl Output_Init(int16_t x, int16_t y, int32_t width, int32_t height, int32_t near_z, int32_t far_z, int16_t view_angle, int32_t screen_width, int32_t screen_height); +void __cdecl Output_DrawPolyLine(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyFlat(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyTrans(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyGouraud(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyGTMap(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyWGTMap(const int16_t *obj_ptr); +int32_t __cdecl Output_XGenX(const int16_t *obj_ptr); +int32_t __cdecl Output_XGenXG(const int16_t *obj_ptr); +int32_t __cdecl Output_XGenXGUV(const int16_t *obj_ptr); +int32_t __cdecl Output_XGenXGUVPerspFP(const int16_t *obj_ptr); +void __cdecl Output_GTMapPersp32FP(int32_t y1, int32_t y2, uint8_t *tex_page); +void __cdecl Output_WGTMapPersp32FP(int32_t y1, int32_t y2, uint8_t *tex_page); +void __cdecl Output_DrawPolyGTMapPersp(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyWGTMapPersp(const int16_t *obj_ptr); +int32_t __cdecl Output_VisibleZClip(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2); +int32_t __cdecl Output_ZedClipper(int32_t vtx_count, POINT_INFO *pts, VERTEX_INFO *vtx); +int32_t __cdecl Output_XYGUVClipper(int32_t vtx_count, VERTEX_INFO *vtx); +const int16_t *__cdecl Output_InsertObjectGT4(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectGT3(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +int32_t __cdecl Output_XYGClipper(int32_t vtx_count, VERTEX_INFO *vtx); +const int16_t *__cdecl Output_InsertObjectG4(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectG3(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +int32_t __cdecl Output_XYClipper(int32_t vtx_count, VERTEX_INFO *vtx); +void __cdecl Output_InsertTrans8(const PHD_VBUF *vbuf, int16_t shade); +void __cdecl Output_InsertTransQuad(int32_t x, int32_t y, int32_t width, int32_t height, int32_t z); +void __cdecl Output_InsertFlatRect(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +void __cdecl Output_InsertLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +void __cdecl Output_InsertGT3_ZBuffered(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, const PHD_TEXTURE *texture, const PHD_UV *uv0, const PHD_UV *uv1, const PHD_UV *uv2); +void __cdecl Output_DrawClippedPoly_Textured(int32_t vtx_count); +void __cdecl Output_InsertGT4_ZBuffered(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, const PHD_VBUF *vtx3, const PHD_TEXTURE *texture); +const int16_t *__cdecl Output_InsertObjectGT4_ZBuffered(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectGT3_ZBuffered(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectG4_ZBuffered(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __cdecl Output_DrawPoly_Gouraud(int32_t vtx_count, int32_t red, int32_t green, int32_t blue); +const int16_t *__cdecl Output_InsertObjectG3_ZBuffered(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __cdecl Output_InsertFlatRect_ZBuffered(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +void __cdecl Output_InsertLine_ZBuffered(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +void __cdecl Output_InsertGT3_Sorted(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, const PHD_TEXTURE *texture, const PHD_UV *uv0, const PHD_UV *uv1, const PHD_UV *uv2, SORT_TYPE sort_type); +void __cdecl Output_InsertClippedPoly_Textured(int32_t vtx_count, float z, int16_t poly_type, int16_t tex_page); +void __cdecl Output_InsertGT4_Sorted(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, const PHD_VBUF *vtx3, const PHD_TEXTURE *texture, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectGT4_Sorted(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectGT3_Sorted(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectG4_Sorted(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __cdecl Output_InsertPoly_Gouraud(int32_t vtx_count, float z, int32_t red, int32_t green, int32_t blue, int16_t poly_type); +const int16_t *__cdecl Output_InsertObjectG3_Sorted(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __cdecl Output_InsertSprite_Sorted(int32_t z, int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t sprite_idx, int16_t shade); +void __cdecl Output_InsertFlatRect_Sorted(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +void __cdecl Output_InsertLine_Sorted(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +void __cdecl Output_InsertTrans8_Sorted(const PHD_VBUF *vbuf, int16_t shade); +void __cdecl Output_InsertTransQuad_Sorted(int32_t x, int32_t y, int32_t width, int32_t height, int32_t z); +void __cdecl Output_InsertSprite(int32_t z, int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t sprite_idx, int16_t shade); +void __cdecl Output_DrawSprite(uint32_t flags, int32_t x, int32_t y, int32_t z, int16_t sprite_idx, int16_t shade, int16_t scale); +void __cdecl Output_DrawPickup(int32_t sx, int32_t sy, int32_t scale, int16_t sprite_idx, int16_t shade); +const int16_t *__cdecl Output_InsertRoomSprite(const int16_t *obj_ptr, int32_t vtx_count); +void __cdecl Output_DrawScreenSprite2D(int32_t sx, int32_t sy, int32_t sz, int32_t scale_h, int32_t scale_v, int16_t sprite_idx, int16_t shade, uint16_t flags); +void __cdecl Output_DrawScreenSprite(int32_t sx, int32_t sy, int32_t sz, int32_t scale_h, int32_t scale_v, int16_t sprite_idx, int16_t shade, uint16_t flags); +void __cdecl Output_DrawScaledSpriteC(const int16_t *obj_ptr); +void __cdecl Bird_Initialise(int16_t item_num); +void __cdecl Bird_Control(int16_t item_num); +void __cdecl Boat_Initialise(int16_t item_num); +int32_t __cdecl Boat_CheckGeton(int16_t item_num, COLL_INFO *coll); +void __cdecl Boat_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +int32_t __cdecl Boat_TestWaterHeight(ITEM *item, int32_t z_off, int32_t x_off, XYZ_32 *pos); +void __cdecl Boat_DoShift(int32_t boat_num); +void __cdecl Boat_DoWakeEffect(ITEM *boat); +int32_t __cdecl Boat_DoDynamics(int32_t height, int32_t fall_speed, int32_t *y); +int32_t __cdecl Boat_Dynamics(int16_t boat_num); +int32_t __cdecl Boat_UserControl(ITEM *boat); +void __cdecl Boat_Animation(ITEM *boat, int32_t collide); +void __cdecl Boat_Control(int16_t item_num); +void __cdecl Gondola_Control(int16_t item_num); +void __cdecl Creature_Initialise(int16_t item_num); +int32_t __cdecl Creature_Activate(int16_t item_num); +void __cdecl Creature_AIInfo(ITEM *item, AI_INFO *info); +int32_t __cdecl Box_SearchLOT(LOT_INFO *lot, int32_t expansion); +int32_t __cdecl Box_UpdateLOT(LOT_INFO *lot, int32_t expansion); +void __cdecl Box_TargetBox(LOT_INFO *lot, int16_t box_num); +int32_t __cdecl Box_StalkBox(const ITEM *item, const ITEM *enemy, int16_t box_num); +int32_t __cdecl Box_EscapeBox(const ITEM *item, const ITEM *enemy, int16_t box_num); +int32_t __cdecl Box_ValidBox(const ITEM *item, int16_t zone_num, int16_t box_num); +void __cdecl Creature_Mood(ITEM *item, AI_INFO *info, int32_t violent); +TARGET_TYPE __cdecl Box_CalculateTarget(XYZ_32 *target, ITEM *item, LOT_INFO *lot); +int32_t __cdecl Creature_CheckBaddieOverlap(int16_t item_num); +int32_t __cdecl Box_BadFloor(int32_t x, int32_t y, int32_t z, int32_t box_height, int32_t next_height, int16_t room_num, LOT_INFO *lot); +void __cdecl Creature_Die(int16_t item_num, int32_t explode); +int32_t __cdecl Creature_Animate(int16_t item_num, int16_t angle, int16_t tilt); +int16_t __cdecl Creature_Turn(ITEM *item, int16_t maximum_turn); +void __cdecl Creature_Tilt(ITEM *item, int16_t angle); +void __cdecl Creature_Head(ITEM *item, int16_t required); +void __cdecl Creature_Neck(ITEM *item, int16_t required); +void __cdecl Creature_Float(int16_t item_num); +void __cdecl Creature_Underwater(ITEM *item, int32_t depth); +int16_t __cdecl Creature_Effect(ITEM *item, BITE *bite, int16_t (*__cdecl spawn)(int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, int16_t room_num)); +int32_t __cdecl Creature_Vault(int16_t item_num, int16_t angle, int32_t vault, int32_t shift); +void __cdecl Creature_Kill(ITEM *item, int32_t kill_anim, int32_t kill_state, int32_t lara_kill_state); +void __cdecl Creature_GetBaddieTarget(int16_t item_num, int32_t goody); +void __cdecl Camera_Initialise(void); +void __cdecl Camera_Move(const GAME_VECTOR *target, int32_t speed); +void __cdecl Camera_Clip(int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom); +void __cdecl Camera_Shift(int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom); +const SECTOR *__cdecl Camera_GoodPosition(int32_t x, int32_t y, int32_t z, int16_t room_num); +void __cdecl Camera_SmartShift(GAME_VECTOR *target, void (*__cdecl shift)(int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom)); +void __cdecl Camera_Chase(const ITEM *item); +int32_t __cdecl Camera_ShiftClamp(GAME_VECTOR *pos, int32_t clamp); +void __cdecl Camera_Combat(const ITEM *item); +void __cdecl Camera_Look(const ITEM *item); +void __cdecl Camera_Fixed(void); +void __cdecl Camera_Update(void); +void __cdecl Game_SetCutsceneTrack(int32_t track); +int32_t __cdecl Game_Cutscene_Start(int32_t level_num); +void __cdecl Misc_InitCinematicRooms(void); +int32_t __cdecl Game_Cutscene_Control(int32_t nframes); +void __cdecl Camera_UpdateCutscene(void); +int32_t __cdecl Room_FindByPos(int32_t x, int32_t y, int32_t z); +void __cdecl CutscenePlayer_Control(int16_t item_num); +void __cdecl Lara_Control_Cutscene(int16_t item_num); +void __cdecl CutscenePlayer1_Initialise(int16_t item_num); +void __cdecl CutscenePlayerGen_Initialise(int16_t item_num); +void __cdecl Camera_LoadCutsceneFrame(void); +void __cdecl Collide_GetCollisionInfo(COLL_INFO *coll, int32_t xpos, int32_t ypos, int32_t zpos, int16_t room_num, int32_t obj_height); +int32_t __cdecl Room_FindGridShift(int32_t src, int32_t dst); +int32_t __cdecl Collide_CollideStaticObjects(COLL_INFO *coll, int32_t x, int32_t y, int32_t z, int16_t room_num, int32_t height); +void __cdecl Room_GetNearbyRooms(int32_t x, int32_t y, int32_t z, int32_t r, int32_t h, int16_t room_num); +void __cdecl Room_GetNewRoom(int32_t x, int32_t y, int32_t z, int16_t room_num); +void __cdecl Item_ShiftCol(ITEM *item, COLL_INFO *coll); +void __cdecl Item_UpdateRoom(ITEM *item, int32_t height); +int16_t __cdecl Room_GetTiltType(const SECTOR *sector, int32_t x, int32_t y, int32_t z); +void __cdecl Lara_BaddieCollision(ITEM *lara_item, COLL_INFO *coll); +void __cdecl Lara_TakeHit(ITEM *lara_item, COLL_INFO *coll); +void __cdecl Creature_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl Object_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl Object_Collision_Trap(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl Lara_Push(ITEM *item, ITEM *lara_item, COLL_INFO *coll, int32_t spaz_on, int32_t big_push); +int32_t __cdecl Item_TestBoundsCollide(const ITEM *src_item, const ITEM *dst_item, int32_t radius); +int32_t __cdecl Item_TestPosition(int16_t *bounds, ITEM *src_item, ITEM *dst_item); +void __cdecl Item_AlignPosition(XYZ_32 *vec, ITEM *src_item, ITEM *dst_item); +int32_t __cdecl Lara_MovePosition(XYZ_32 *vec, ITEM *item, ITEM *lara_item); +int32_t __cdecl Misc_Move3DPosTo3DPos(PHD_3DPOS *src_pos, const PHD_3DPOS *dest_pos, int32_t velocity, PHD_ANGLE ang_add); +int32_t __cdecl Game_Control(int32_t nframes, int32_t demo_mode); +void __cdecl Item_Animate(ITEM *item); +int32_t __cdecl Item_GetAnimChange(ITEM *item, const ANIM *anim); +void __cdecl Item_Translate(ITEM *item, int32_t x, int32_t y, int32_t z); +SECTOR *__cdecl Room_GetSector(int32_t x, int32_t y, int32_t z, int16_t *room_num); +int32_t __cdecl Room_GetWaterHeight(int32_t x, int32_t y, int32_t z, int16_t room_num); +int32_t __cdecl Room_GetHeight(const SECTOR *sector, int32_t x, int32_t y, int32_t z); +void __cdecl Camera_RefreshFromTrigger(int16_t type, const int16_t *data); +void __cdecl Room_TestTriggers(int16_t *data, int32_t heavy); +int32_t __cdecl Item_IsTriggerActive(ITEM *item); +int32_t __cdecl Room_GetCeiling(const SECTOR *sector, int32_t x, int32_t y, int32_t z); +int16_t __cdecl Room_GetDoor(const SECTOR *sector); +int32_t __cdecl LOS_Check(const GAME_VECTOR *start, GAME_VECTOR *target); +int32_t __cdecl LOS_CheckZ(const GAME_VECTOR *start, GAME_VECTOR *target); +int32_t __cdecl LOS_CheckX(const GAME_VECTOR *start, GAME_VECTOR *target); +int32_t __cdecl LOS_ClipTarget(const GAME_VECTOR *start, GAME_VECTOR *target, const SECTOR *sector); +int32_t __cdecl LOS_CheckSmashable(const GAME_VECTOR *start, GAME_VECTOR *target); +void __cdecl Room_FlipMap(void); +void __cdecl Room_RemoveFlipItems(ROOM *r); +void __cdecl Room_AddFlipItems(ROOM *r); +void __cdecl Room_TriggerMusicTrack(int16_t value, int16_t flags, int16_t type); +void __cdecl Room_TriggerMusicTrackImpl(int16_t value, int16_t flags, int16_t type); +int32_t __cdecl Demo_Control(int32_t level_num); +int32_t __cdecl Demo_Start(int32_t level_num); +void __cdecl Demo_LoadLaraPos(void); +void __cdecl Demo_GetInput(void); +int16_t __cdecl Diver_Harpoon(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE y_rot, int16_t room_num); +int32_t __cdecl Diver_GetWaterSurface(int32_t x, int32_t y, int32_t z, int16_t room_num); +void __cdecl Diver_Control(int16_t item_num); +void __cdecl Dog_Control(int16_t item_num); +void __cdecl Tiger_Control(int16_t item_num); +void __cdecl ControlTwinkle(int16_t fx_num); +void __cdecl CreateBartoliLight(int16_t item_num); +int16_t __cdecl DragonFire(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +void __cdecl DragonCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl DragonBones(int16_t item_num); +void __cdecl DragonControl(int16_t back_num); +void __cdecl InitialiseBartoli(int16_t item_num); +void __cdecl BartoliControl(int16_t item_num); +void __cdecl DinoControl(int16_t item_num); +int32_t __cdecl Game_DrawCinematic(void); +int32_t __cdecl Game_Draw(void); +void __cdecl Room_DrawAllRooms(int16_t current_room); +void __cdecl Room_GetBounds(void); +void __cdecl Room_SetBounds(const int16_t *objptr, int32_t room_num, ROOM *parent); +void __cdecl Room_Clip(ROOM *r); +void __cdecl Room_DrawSingleRoomGeometry(int16_t room_num); +void __cdecl Room_DrawSingleRoomObjects(int16_t room_num); +void __cdecl Effect_Draw(int16_t fx_num); +void __cdecl Object_DrawSpriteItem(ITEM *item); +void __cdecl Object_DrawAnimatingItem(ITEM *item); +void __cdecl Lara_Draw(const ITEM *item); +void __cdecl Lara_Draw_I(const ITEM *item, const FRAME_INFO *frame1, const FRAME_INFO *frame2, int32_t frac, int32_t rate); +void __cdecl Matrix_InitInterpolate(int32_t frac, int32_t rate); +void __cdecl Matrix_Pop_I(void); +void __cdecl Matrix_Push_I(void); +void __cdecl Matrix_RotY_I(int16_t ang); +void __cdecl Matrix_RotX_I(int16_t ang); +void __cdecl Matrix_RotZ_I(int16_t ang); +void __cdecl Matrix_TranslateRel_I(int32_t x, int32_t y, int32_t z); +void __cdecl Matrix_TranslateRel_ID(int32_t x, int32_t y, int32_t z, int32_t x2, int32_t y2, int32_t z2); +void __cdecl Matrix_RotYXZ_I(int16_t y, int16_t x, int16_t z); +void __cdecl Matrix_RotYXZsuperpack_I(const int16_t **pprot1, const int16_t **pprot2, int32_t skip); +void __cdecl Matrix_RotYXZsuperpack(const int16_t **pprot, int32_t skip); +void __cdecl Output_InsertPolygons_I(int16_t *ptr, int32_t clip); +void __cdecl Matrix_Interpolate(void); +void __cdecl Matrix_InterpolateArm(void); +void __cdecl Gun_DrawFlash(LARA_GUN_TYPE weapon_type, int32_t clip); +void __cdecl Output_CalculateObjectLighting(const ITEM *item, const FRAME_INFO *frame); +int32_t __cdecl Item_GetFrames(const ITEM *item, FRAME_INFO *frmptr[], int32_t *rate); +BOUNDS_16 *__cdecl Item_GetBoundsAccurate(const ITEM *item); +FRAME_INFO *__cdecl Item_GetBestFrame(const ITEM *item); +void __cdecl AddDynamicLight(int32_t x, int32_t y, int32_t z, int32_t intensity, int32_t falloff); +void __cdecl BigEelControl(int16_t item_num); +void __cdecl EelControl(int16_t item_num); +int32_t __cdecl Lara_IsNearItem(PHD_3DPOS *pos, int32_t distance); +void __cdecl Sound_UpdateEffects(void); +int16_t __cdecl DoBloodSplat(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE direction, int16_t room_num); +void __cdecl DoLotsOfBlood(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE direction, int16_t room_num, int32_t num); +void __cdecl ControlBlood1(int16_t fx_num); +void __cdecl ControlExplosion1(int16_t fx_num); +void __cdecl Richochet(GAME_VECTOR *pos); +void __cdecl ControlRichochet1(int16_t fx_num); +void __cdecl CreateBubble(PHD_3DPOS *pos, int16_t room_num); +void __cdecl LaraBubbles(ITEM *item); +void __cdecl ControlBubble1(int16_t fx_num); +void __cdecl Splash(ITEM *item); +void __cdecl ControlSplash1(int16_t fx_num); +void __cdecl ControlWaterSprite(int16_t fx_num); +void __cdecl ControlSnowSprite(int16_t fx_num); +void __cdecl ControlHotLiquid(int16_t fx_num); +void __cdecl WaterFall(int16_t fx_num); +void __cdecl finish_level_effect(ITEM *item); +void __cdecl turn180_effect(ITEM *item); +void __cdecl floor_shake_effect(ITEM *item); +void __cdecl lara_normal_effect(ITEM *item); +void __cdecl BoilerFX(ITEM *item); +void __cdecl FloodFX(ITEM *item); +void __cdecl RubbleFX(ITEM *item); +void __cdecl ChandelierFX(ITEM *item); +void __cdecl ExplosionFX(ITEM *item); +void __cdecl PistonFX(ITEM *item); +void __cdecl CurtainFX(ITEM *item); +void __cdecl StatueFX(ITEM *item); +void __cdecl SetChangeFX(ITEM *item); +void __cdecl ControlDingDong(int16_t item_num); +void __cdecl ControlLaraAlarm(int16_t item_num); +void __cdecl ControlAlarmSound(int16_t item_num); +void __cdecl ControlBirdTweeter(int16_t item_num); +void __cdecl DoChimeSound(ITEM *item); +void __cdecl ControlClockChimes(int16_t item_num); +void __cdecl SphereOfDoomCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl SphereOfDoom(int16_t item_num); +void __cdecl DrawSphereOfDoom(ITEM *item); +void __cdecl lara_hands_free(ITEM *item); +void __cdecl flip_map_effect(ITEM *item); +void __cdecl draw_right_gun(ITEM *item); +void __cdecl draw_left_gun(ITEM *item); +void __cdecl swap_meshes_with_meshswap1(ITEM *item); +void __cdecl swap_meshes_with_meshswap2(ITEM *item); +void __cdecl swap_meshes_with_meshswap3(ITEM *item); +void __cdecl invisibility_on(ITEM *item); +void __cdecl invisibility_off(ITEM *item); +void __cdecl dynamic_light_on(ITEM *item); +void __cdecl dynamic_light_off(ITEM *item); +void __cdecl reset_hair(ITEM *item); +void __cdecl AssaultStart(ITEM *item); +void __cdecl AssaultStop(ITEM *item); +void __cdecl AssaultReset(ITEM *item); +void __cdecl AssaultFinished(ITEM *item); +int16_t __cdecl Knife(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +void __cdecl Cult2Control(int16_t item_num); +void __cdecl MonkControl(int16_t item_num); +void __cdecl Worker3Control(int16_t item_num); +void __cdecl DrawXianLord(ITEM *item); +void __cdecl XianDamage(ITEM *item, CREATURE *xian, int32_t damage); +void __cdecl InitialiseXianLord(int16_t item_num); +void __cdecl XianLordControl(int16_t item_num); +void __cdecl WarriorSparkleTrail(ITEM *item); +void __cdecl WarriorControl(int16_t item_num); +int32_t __cdecl GF_LoadScriptFile(const char *fname); +int32_t __cdecl GF_DoFrontendSequence(void); +int32_t __cdecl GF_DoLevelSequence(int32_t level, GAMEFLOW_LEVEL_TYPE type); +int32_t __cdecl GF_InterpretSequence(int16_t *ptr, GAMEFLOW_LEVEL_TYPE type, int32_t seq_type); +void __cdecl GF_ModifyInventory(int32_t level, int32_t type); +void __cdecl InitialiseHair(void); +void __cdecl HairControl(int32_t in_cutscene); +void __cdecl DrawHair(void); +BOOL __cdecl Overlay_FlashCounter(void); +void __cdecl Overlay_DrawAssaultTimer(void); +void __cdecl Overlay_DrawGameInfo(bool pickup_state); +void __cdecl Overlay_DrawHealthBar(bool flash_state); +void __cdecl Overlay_DrawAirBar(bool flash_state); +void __cdecl Overlay_MakeAmmoString(char *string); +void __cdecl Overlay_DrawAmmoInfo(void); +void __cdecl Overlay_InitialisePickUpDisplay(void); +void __cdecl Overlay_DrawPickups(bool pickup_state); +void __cdecl Overlay_AddDisplayPickup(GAME_OBJECT_ID object_id); +void __cdecl Overlay_DisplayModeInfo(char* string); +void __cdecl Overlay_DrawModeInfo(void); +int32_t __cdecl Inv_Display(int32_t inventory_mode); +void __cdecl Inv_Construct(void); +void __cdecl Inv_SelectMeshes(INVENTORY_ITEM *inv_item); +int32_t __cdecl Inv_AnimateInventoryItem(INVENTORY_ITEM *inv_item); +void __cdecl Inv_DrawInventoryItem(INVENTORY_ITEM *inv_item); +int32_t __cdecl Input_GetDebounced(int32_t input); +void __cdecl Inv_DoInventoryPicture(void); +void __cdecl Inv_DoInventoryBackground(void); +void __cdecl Inv_InitColors(void); +void __cdecl Inv_RingIsOpen(RING_INFO *ring); +void __cdecl Inv_RingIsNotOpen(RING_INFO *ring); +void __cdecl Inv_RingNotActive(INVENTORY_ITEM *inv_item); +void __cdecl Inv_RingActive(void); +int32_t __cdecl Inv_AddItem(GAME_OBJECT_ID object_id); +void __cdecl Inv_InsertItem(INVENTORY_ITEM *inv_item); +int32_t __cdecl Inv_RequestItem(GAME_OBJECT_ID object_id); +void __cdecl Inv_RemoveAllItems(void); +int32_t __cdecl Inv_RemoveItem(GAME_OBJECT_ID object_id); +int32_t __cdecl Inv_GetItemOption(GAME_OBJECT_ID object_id); +void __cdecl Inv_RemoveInventoryText(void); +void __cdecl Inv_Ring_Init(RING_INFO *ring, int16_t type, INVENTORY_ITEM **list, int16_t qty, int16_t current, IMOTION_INFO *imo); +void __cdecl Inv_Ring_GetView(RING_INFO *ring, PHD_3DPOS *viewer); +void __cdecl Inv_Ring_Light(RING_INFO *ring); +void __cdecl Inv_Ring_CalcAdders(RING_INFO *ring, int16_t rotation_duration); +void __cdecl Inv_Ring_DoMotions(RING_INFO *ring); +void __cdecl Inv_Ring_RotateLeft(RING_INFO *ring); +void __cdecl Inv_Ring_RotateRight(RING_INFO *ring); +void __cdecl Inv_Ring_MotionInit(RING_INFO *ring, int16_t frames, int16_t status, int16_t status_target); +void __cdecl Inv_Ring_MotionSetup(RING_INFO *ring, int16_t status, int16_t status_target, int16_t frames); +void __cdecl Inv_Ring_MotionRadius(RING_INFO *ring, int16_t target); +void __cdecl Inv_Ring_MotionRotation(RING_INFO *ring, int16_t rotation, int16_t target); +void __cdecl Inv_Ring_MotionCameraPos(RING_INFO *ring, int16_t target); +void __cdecl Inv_Ring_MotionCameraPitch(RING_INFO *ring, int16_t target); +void __cdecl Inv_Ring_MotionItemSelect(RING_INFO *ring, INVENTORY_ITEM *inv_item); +void __cdecl Inv_Ring_MotionItemDeselect(RING_INFO *ring, INVENTORY_ITEM *inv_item); +void __cdecl Requester_Init(REQUEST_INFO *req); +void __cdecl Requester_Shutdown(REQUEST_INFO *req); +void __cdecl Requester_Item_CenterAlign(REQUEST_INFO *req, TEXTSTRING *txt); +void __cdecl Requester_Item_LeftAlign(REQUEST_INFO *req, TEXTSTRING *txt); +void __cdecl Requester_Item_RightAlign(REQUEST_INFO *req, TEXTSTRING *txt); +int32_t __cdecl Requester_Display(REQUEST_INFO *req, int32_t des, int32_t backgrounds); +void __cdecl Requester_SetHeading(REQUEST_INFO *req, char *text1, uint32_t flags1, char *text2, uint32_t flags2); +void __cdecl Requester_RemoveAllItems(REQUEST_INFO *req); +void __cdecl Requester_ChangeItem(REQUEST_INFO *req, int32_t item, const char *text1, uint32_t flags1, const char *text2, uint32_t flags2); +void __cdecl Requester_AddItem(REQUEST_INFO *req, const char *text1, uint32_t flags1, const char *text2, uint32_t flags2); +void __cdecl Requester_SetSize(REQUEST_INFO *req, int32_t maxlines, int32_t ypos); +int32_t __cdecl AddAssaultTime(uint32_t time); +void __cdecl ShowGymStatsText(char *time_str, int32_t type); +void __cdecl ShowStatsText(char *time_str, int32_t type); +void __cdecl ShowEndStatsText(void); +void __cdecl Item_InitialiseArray(int32_t num_items); +void __cdecl Item_Kill(int16_t item_num); +int16_t __cdecl Item_Create(void); +void __cdecl Item_Initialise(int16_t item_num); +void __cdecl Item_RemoveActive(int16_t item_num); +void __cdecl Item_RemoveDrawn(int16_t item_num); +void __cdecl Item_AddActive(int16_t item_num); +void __cdecl Item_NewRoom(int16_t item_num, int16_t room_num); +int32_t __cdecl Item_GlobalReplace(GAME_OBJECT_ID src_object_id, GAME_OBJECT_ID dst_object_id); +void __cdecl Effect_InitialiseArray(void); +int16_t __cdecl Effect_Create(int16_t room_num); +void __cdecl Effect_Kill(int16_t fx_num); +void __cdecl Effect_NewRoom(int16_t fx_num, int16_t room_num); +void __cdecl Item_ClearKilled(void); +void __cdecl Lara_HandleAboveWater(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_LookUpDown(void); +void __cdecl Lara_LookLeftRight(void); +void __cdecl Lara_ResetLook(void); +void __cdecl Lara_State_Walk(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Run(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Stop(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ForwardJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_FastBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_TurnRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_TurnLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Death(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_FastFall(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Hang(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Reach(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Splat(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Compress(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Back(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Null(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_FastTurn(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_StepRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_StepLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Slide(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_BackJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_RightJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_LeftJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_UpJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Fallback(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_HangLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_HangRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SlideBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_PushBlock(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_PPReady(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Pickup(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_PickupFlare(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SwitchOn(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_UseKey(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Special(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SwanDive(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_FastDive(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_WaterOut(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Wade(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_DeathSlide(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_Breath(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_YetiKill(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_SharkKill(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_Airlock(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_GongBong(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_DinoKill(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_PullDagger(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_StartAnim(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_StartHouse(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_FinalAnim(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_Fallen(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_CollideStop(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Walk(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Run(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Stop(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_ForwardJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_FastBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_TurnRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_TurnLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Death(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_FastFall(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Hang(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Reach(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Splat(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Land(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Compress( ITEM *item, COLL_INFO *coll ); +void __cdecl Lara_Col_Back(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_StepRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_StepLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Slide(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_BackJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_RightJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_LeftJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_UpJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Fallback(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_HangLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_HangRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SlideBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Null(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Roll(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Roll2(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SwanDive(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_FastDive(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Wade(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Default(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Jumper(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_GetCollisionInfo(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_SlideSlope(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_HitCeiling(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_DeflectEdge(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_DeflectEdgeJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_SlideEdgeJump(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_TestWall(ITEM *item, int32_t front, int32_t right, int32_t down); +int32_t __cdecl Lara_TestHangOnClimbWall(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_TestClimbStance(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_HangTest(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_TestEdgeCatch(ITEM *item, COLL_INFO *coll, int32_t *edge); +int32_t __cdecl Lara_TestHangJumpUp(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_TestHangJump(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_TestHangSwingIn(ITEM *item, PHD_ANGLE angle); +int32_t __cdecl Lara_TestVault(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_TestSlide(ITEM *item, COLL_INFO *coll); +int16_t __cdecl Lara_FloorFront(ITEM *item, PHD_ANGLE ang, int32_t dist); +int32_t __cdecl Lara_LandedBad(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_GetJointAbsPosition(XYZ_32 *vec, int32_t joint); +void __cdecl Lara_GetJointAbsPosition_I(ITEM *item, XYZ_32 *vec, int16_t *frame1, int16_t *frame2, int32_t frac, int32_t rate); +void __cdecl Gun_Rifle_DrawMeshes(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Rifle_UndrawMeshes(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Rifle_Ready(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Rifle_Control(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Rifle_FireShotgun(void); +void __cdecl Gun_Rifle_FireM16(bool running); +void __cdecl Gun_Rifle_FireHarpoon(void); +void __cdecl HarpoonBolt_Control(int16_t item_num); +void __cdecl Gun_Rifle_FireGrenade(void); +void __cdecl Rocket_Control(int16_t item_num); +void __cdecl Gun_Rifle_Draw(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Rifle_Undraw(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Rifle_Animate(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_SetArmInfo(LARA_ARM *arm, int32_t frame); +void __cdecl Gun_Pistols_Draw(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_Undraw(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_Ready(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_DrawMeshes(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_UndrawMeshLeft(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_UndrawMeshRight(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_Control(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_Animate(LARA_GUN_TYPE weapon_type); +void __cdecl Lara_State_ClimbLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbStance(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Climbing(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbEnd(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbDown(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_ClimbLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_ClimbRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_ClimbStance(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Climbing(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_ClimbDown(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_CheckForLetGo(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_TestClimb(int32_t x, int32_t y, int32_t z, int32_t xfront, int32_t zfront, int32_t item_height, int16_t item_room, int32_t *shift); +int32_t __cdecl Lara_TestClimbPos(ITEM *item, int32_t front, int32_t right, int32_t origin, int32_t height, int32_t *shift); +void __cdecl Lara_DoClimbLeftRight(ITEM *item, COLL_INFO *coll, int32_t result, int32_t shift); +int32_t __cdecl Lara_TestClimbUpPos(ITEM *item, int32_t front, int32_t right, int32_t *shift, int32_t *ledge); +void __cdecl Gun_Control(void); +int32_t __cdecl Gun_CheckForHoldingState(int32_t state); +void __cdecl Gun_InitialiseNewWeapon(void); +void __cdecl Gun_TargetInfo(const WEAPON_INFO *winfo); +void __cdecl Gun_GetNewTarget(WEAPON_INFO *winfo); +void __cdecl Gun_FindTargetPoint(const ITEM *item, GAME_VECTOR *target); +void __cdecl Gun_AimWeapon(WEAPON_INFO *winfo, LARA_ARM *arm); +int32_t __cdecl Gun_FireWeapon(LARA_GUN_TYPE weapon_type, ITEM *target, const ITEM *src, const PHD_ANGLE *angles); +void __cdecl Gun_HitTarget(ITEM *item, GAME_VECTOR *hitpos, int32_t damage); +void __cdecl Gun_SmashItem(int16_t item_num, LARA_GUN_TYPE weapon_type); +GAME_OBJECT_ID Gun_GetWeaponAnim(const LARA_GUN_TYPE gun_type); +int32_t __cdecl Flare_DoLight(XYZ_32 *pos, int32_t flare_age); +void __cdecl Flare_DoInHand(int32_t flare_age); +void __cdecl Flare_DrawInAir(ITEM *item); +void __cdecl Flare_Create(int32_t thrown); +void __cdecl Flare_SetArm(int32_t frame); +void __cdecl Flare_Draw(void); +void __cdecl Flare_Undraw(void); +void __cdecl Flare_DrawMeshes(void); +void __cdecl Flare_UndrawMeshes(void); +void __cdecl Flare_Ready(void); +void __cdecl Flare_Control(int16_t item_num); +void __cdecl Lara_Control(int16_t item_num); +void __cdecl Lara_Animate(ITEM *item); +void __cdecl Lara_UseItem(GAME_OBJECT_ID object_id); +void __cdecl Lara_CheatGetStuff(void); +void __cdecl Lara_ControlExtra(int16_t item_num); +void __cdecl Lara_InitialiseLoad(int16_t item_num); +void __cdecl Lara_Initialise(int32_t type); +void __cdecl Lara_InitialiseInventory(int32_t level_num); +void __cdecl Lara_InitialiseMeshes(int32_t level_num); +void __cdecl Lara_HandleSurface(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SurfSwim(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SurfBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SurfLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SurfRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SurfTread(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfSwim(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfTread(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_SurfaceCollision(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_TestWaterStepOut(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_TestWaterClimbOut(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_HandleUnderwater(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_SwimTurn(ITEM *item); +void __cdecl Lara_State_Swim(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Glide(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Tread(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Dive(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_UWDeath(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_UWTwist(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Swim(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_UWDeath(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_GetWaterDepth(int32_t x, int32_t y, int32_t z, int16_t room_num); +void __cdecl Lara_TestWaterDepth(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_SwimCollision(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_WaterCurrent(COLL_INFO *coll); +void __cdecl LOT_InitialiseArray(void); +void __cdecl LOT_DisableBaddieAI(int16_t item_num); +bool __cdecl LOT_EnableBaddieAI(int16_t item_num, bool always); +void __cdecl LOT_InitialiseSlot(int16_t item_num, int32_t slot); +void __cdecl LOT_CreateZone(ITEM *item); +void __cdecl LOT_ClearLOT(LOT_INFO *lot); +void __cdecl ControlMissile(int16_t fx_num); +void __cdecl ShootAtLara(FX *fx); +int32_t __cdecl Effect_ExplodingDeath(int16_t item_num, int32_t mesh_bits, int16_t damage); +void __cdecl BodyPart_Control(int16_t fx_num); +void __cdecl InitialiseMovingBlock(int16_t item_num); +void __cdecl MovableBlock(int16_t item_num); +void __cdecl MovableBlockCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +int32_t __cdecl TestBlockMovable(ITEM *item, int32_t block_height); +int32_t __cdecl TestBlockPush(ITEM *item, int32_t block_height, uint16_t quadrant); +int32_t __cdecl TestBlockPull(ITEM *item, int32_t block_height, uint16_t quadrant); +void __cdecl Room_AlterFloorHeight(ITEM *item, int32_t height); +void __cdecl DrawMovableBlock(ITEM *item); +void __cdecl DrawUnclippedItem(ITEM *item); +void __cdecl EarthQuake(int16_t item_num); +void __cdecl ControlCutShotgun(int16_t item_num); +void __cdecl InitialiseFinalLevel(void); +void __cdecl FinalLevelCounter_Control(int16_t item_num); +void __cdecl MiniCopterControl(int16_t item_num); +void __cdecl InitialiseDyingMonk(int16_t item_num); +void __cdecl DyingMonk(int16_t item_num); +void __cdecl ControlGongBonger(int16_t item_num); +void __cdecl DeathSlideCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl ControlDeathSlide(int16_t item_num); +void __cdecl BigBowlControl(int16_t item_num); +void __cdecl BellControl(int16_t item_num); +void __cdecl InitialiseWindow(int16_t item_num); +void __cdecl SmashWindow(int16_t item_num); +void __cdecl WindowControl(int16_t item_num); +void __cdecl SmashIceControl(int16_t item_num); +void __cdecl ShutThatDoor(DOORPOS_DATA *d); +void __cdecl OpenThatDoor(DOORPOS_DATA *d); +void __cdecl InitialiseDoor(int16_t item_num); +void __cdecl DoorControl(int16_t item_num); +int32_t __cdecl OnDrawBridge(ITEM *item, int32_t x, int32_t y); +void __cdecl DrawBridgeFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl DrawBridgeCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl DrawBridgeCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl InitialiseLift(int16_t item_num); +void __cdecl LiftControl(int16_t item_num); +void __cdecl LiftFloorCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *floor, int32_t *ceiling); +void __cdecl LiftFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl LiftCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl BridgeFlatFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl BridgeFlatCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +int32_t __cdecl GetOffset(ITEM *item, int32_t x, int32_t z); +void __cdecl BridgeTilt1Floor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl BridgeTilt1Ceiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl BridgeTilt2Floor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl BridgeTilt2Ceiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl CopterControl(int16_t item_num); +void __cdecl GeneralControl(int16_t item_num); +void __cdecl DetonatorControl(int16_t item_num); +bool __cdecl Creature_CanTargetEnemy(const ITEM *item, const AI_INFO *info); +void __cdecl ControlGlow(int16_t fx_num); +void __cdecl ControlGunShot(int16_t fx_num); +int16_t __cdecl GunShot(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +int16_t __cdecl GunHit(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +int16_t __cdecl GunMiss(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +int32_t __cdecl ShotLara(ITEM *item, AI_INFO *info, BITE *gun, int16_t extra_rotation, int32_t damage); +void __cdecl InitialiseCult1(int16_t item_num); +void __cdecl Cult1Control(int16_t item_num); +void __cdecl InitialiseCult3(int16_t item_num); +void __cdecl Cult3Control(int16_t item_num); +void __cdecl Worker1Control(int16_t item_num); +void __cdecl Worker2Control(int16_t item_num); +void __cdecl BanditControl(int16_t item_num); +void __cdecl Bandit2Control(int16_t item_num); +void __cdecl WinstonControl(int16_t item_num); +void __cdecl PickUpCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl SwitchCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl SwitchCollision2(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl DetonatorCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl KeyHoleCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl PuzzleHoleCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl SwitchControl(int16_t item_num); +int32_t __cdecl SwitchTrigger(int16_t item_num, int16_t timer); +int32_t __cdecl KeyTrigger(int16_t item_num); +int32_t __cdecl PickupTrigger(int16_t item_num); +void __cdecl SecretControl(int16_t item_num); +void __cdecl MouseControl(int16_t item_num); +void __cdecl InitialiseStartInfo(void); +void __cdecl ModifyStartInfo(int32_t level_num); +void __cdecl CreateStartInfo(int32_t level_num); +void __cdecl CreateSaveGameInfo(void); +void __cdecl ExtractSaveGameInfo(void); +void __cdecl ResetSG(void); +void __cdecl WriteSG(void *pointer, int32_t size); +void __cdecl ReadSG(void *pointer, int32_t size); +int32_t __cdecl Level_Initialise(int32_t level_num, int32_t level_type); +void __cdecl InitialiseGameFlags(void); +void __cdecl InitialiseLevelFlags(void); +void __cdecl BaddyObjects(void); +void __cdecl TrapObjects(void); +void __cdecl ObjectObjects(void); +void __cdecl InitialiseObjects(void); +void __cdecl GetCarriedItems(void); +void __cdecl JellyControl(int16_t item_num); +void __cdecl BaracuddaControl(int16_t item_num); +void __cdecl SharkControl(int16_t item_num); +void __cdecl InitialiseSkidoo(int16_t item_num); +int32_t __cdecl SkidooCheckGeton(int16_t item_num, COLL_INFO *coll); +void __cdecl SkidooCollision(int16_t item_num, ITEM *litem, COLL_INFO *coll); +void __cdecl SkidooBaddieCollision(ITEM *skidoo); +int32_t __cdecl TestHeight(ITEM *item, int32_t z_off, int32_t x_off, XYZ_32 *pos); +int32_t __cdecl DoShift(ITEM *skidoo, XYZ_32 *pos, XYZ_32 *old); +int32_t __cdecl DoDynamics(int32_t height, int32_t fall_speed, int32_t *y); +int32_t __cdecl GetCollisionAnim(ITEM *skidoo, XYZ_32 *moved); +void __cdecl DoSnowEffect(ITEM *skidoo); +int32_t __cdecl SkidooDynamics(ITEM *skidoo); +int32_t __cdecl SkidooUserControl(ITEM *skidoo, int32_t height, int32_t *pitch); +int32_t __cdecl SkidooCheckGetOffOK(int32_t direction); +void __cdecl SkidooAnimation(ITEM *skidoo, int32_t collide, int32_t dead); +void __cdecl SkidooExplode(ITEM *skidoo); +int32_t __cdecl SkidooCheckGetOff(void); +void __cdecl SkidooGuns(void); +int32_t __cdecl SkidooControl(void); +void __cdecl DrawSkidoo(ITEM *item); +void __cdecl InitialiseSkidman(int16_t item_num); +void __cdecl SkidManControl(int16_t rider_num); +void __cdecl SkidmanPush(ITEM *item, ITEM *lara_item, int32_t radius); +void __cdecl SkidmanCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +int32_t __cdecl Music_GetRealTrack(int32_t track); +void __cdecl Sound_Effect(int32_t sample_id, const XYZ_32 *pos, uint32_t flags); +void __cdecl Sound_StopEffect(int32_t sample_id); +void __cdecl Sound_EndScene(void); +void __cdecl Sound_Shutdown(void); +void __cdecl Sound_Init(void); +int32_t __cdecl Collide_TestCollision(ITEM *item, const ITEM *lara_item); +int32_t __cdecl Collide_GetSpheres(const ITEM *item, SPHERE *spheres, bool world_space); +void __cdecl Collide_GetJointAbsPosition(const ITEM *item, const XYZ_32 *vec, int32_t joint); +void __cdecl BaddieBiteEffect(ITEM *item, BITE *bite); +void __cdecl SpiderLeap(int16_t item_num, int16_t angle); +void __cdecl SpiderControl(int16_t item_num); +void __cdecl BigSpiderControl(int16_t item_num); +void __cdecl Text_Init(void); +TEXTSTRING *__cdecl Text_Create(int32_t x, int32_t y, int32_t z, const char *text); +void __cdecl Text_ChangeText(TEXTSTRING *string, const char *text); +void __cdecl Text_SetScale(TEXTSTRING *string, int32_t scale_h, int32_t scale_v); +void __cdecl Text_Flash(TEXTSTRING *string, int16_t enable, int16_t rate); +void __cdecl Text_AddBackground(TEXTSTRING *string, int16_t x_size, int16_t y_size, int16_t x_off, int16_t y_off, int16_t z_off, int16_t color, uint16_t *gour_ptr, uint16_t flags); +void __cdecl Text_RemoveBackground(TEXTSTRING *string); +void __cdecl Text_AddOutline(TEXTSTRING *string, int16_t enable, int16_t color, uint16_t *gour_ptr, uint16_t flags); +void __cdecl Text_RemoveOutline(TEXTSTRING *string); +void __cdecl Text_CentreH(TEXTSTRING *string, int16_t enable); +void __cdecl Text_CentreV(TEXTSTRING *string, int16_t enable); +void __cdecl Text_AlignRight(TEXTSTRING *string, int16_t enable); +void __cdecl Text_AlignBottom(TEXTSTRING *string, int16_t enable); +int32_t __cdecl Text_GetWidth(TEXTSTRING *string); +int32_t __cdecl Text_Remove(TEXTSTRING *string); +int16_t __cdecl Text_GetTextLength(const char *text); +void __cdecl Text_Draw(void); +void __cdecl Text_DrawBorder(int32_t x, int32_t y, int32_t z, int32_t width, int32_t height); +void __cdecl Text_DrawText(const TEXTSTRING *string); +uint32_t __cdecl Text_GetScaleH(uint32_t value); +uint32_t __cdecl Text_GetScaleV(uint32_t value); +void __cdecl MineControl(int16_t mine_num); +void __cdecl ControlSpikeWall(int16_t item_num); +void __cdecl ControlCeilingSpikes(int16_t item_num); +void __cdecl HookControl(int16_t item_num); +void __cdecl PropellerControl(int16_t item_num); +void __cdecl SpinningBlade(int16_t item_num); +void __cdecl IcicleControl(int16_t item_num); +void __cdecl InitialiseBlade(int16_t item_num); +void __cdecl BladeControl(int16_t item_num); +void __cdecl InitialiseKillerStatue(int16_t item_num); +void __cdecl KillerStatueControl(int16_t item_num); +void __cdecl SpringBoardControl(int16_t item_num); +void __cdecl InitialiseRollingBall(int16_t item_num); +void __cdecl RollingBallControl(int16_t item_num); +void __cdecl RollingBallCollision(int16_t item_num, ITEM *litem, COLL_INFO *coll); +void __cdecl SpikeCollision(int16_t item_num, ITEM *litem, COLL_INFO *coll); +void __cdecl TrapDoorControl(int16_t item_num); +void __cdecl TrapDoorFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl TrapDoorCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +int32_t __cdecl OnTrapDoor(ITEM *item, int32_t x, int32_t z); +void __cdecl Pendulum(int16_t item_num); +void __cdecl FallingBlock(int16_t item_num); +void __cdecl FallingBlockFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl FallingBlockCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl TeethTrap(int16_t item_num); +void __cdecl FallingCeiling(int16_t item_num); +void __cdecl DartEmitterControl(int16_t item_num); +void __cdecl DartsControl(int16_t item_num); +void __cdecl DartEffectControl(int16_t fx_num); +void __cdecl FlameEmitter_Control(int16_t item_num); +void __cdecl Flame_Control(int16_t fx_num); +void __cdecl Lara_CatchFire(void); +void __cdecl Lara_TouchLava(ITEM *item); +void __cdecl EmberEmitter_Control(int16_t item_num); +void __cdecl Ember_Control(int16_t fx_num); +void __cdecl GiantYetiControl(int16_t item_num); +void __cdecl YetiControl(int16_t item_num); +void __cdecl BGND_Make640x480(uint8_t *bitmap, RGB_888 *palette); +int32_t __cdecl BGND_AddTexture(int32_t tile_idx, BYTE *bitmap, int32_t pal_index, RGB_888 *bmp_pal); +void __cdecl BGND_GetPageHandles(void); +void __cdecl BGND_DrawInGameBlack(void); +void __cdecl DrawQuad(float sx, float sy, float width, float height, D3DCOLOR color); +void __cdecl BGND_DrawInGameBackground(void); +void __cdecl DrawTextureTile(int32_t sx, int32_t sy, int32_t width, int32_t height, HWR_TEXTURE_HANDLE tex_source, int32_t tu, int32_t tv, int32_t t_width, int32_t t_height, D3DCOLOR color0, D3DCOLOR color1, D3DCOLOR color2, D3DCOLOR color3); +D3DCOLOR __cdecl BGND_CenterLighting(int32_t x, int32_t y, int32_t width, int32_t height); +void __cdecl BGND_Free(void); +bool __cdecl BGND_Init(void); +void __cdecl Enumerate3DDevices(DISPLAY_ADAPTER *adapter); +bool __cdecl D3DCreate(void); +HRESULT __stdcall Enum3DDevicesCallback(GUID *lpGuid, LPTSTR lpDeviceDescription, LPTSTR lpDeviceName, LPD3DDEVICEDESC lpD3DHWDeviceDesc, LPD3DDEVICEDESC lpD3DHELDeviceDesc, LPVOID lpContext); +bool __cdecl D3DIsSupported(LPD3DDEVICEDESC desc); +bool __cdecl D3DSetViewport(void); +void __cdecl D3DDeviceCreate(LPDDS lpBackBuffer); +void __cdecl Direct3DRelease(void); +bool __cdecl Direct3DInit(void); +sub_444990 +sub_4449A0 +sub_4449D0 +sub_444AA0 +sub_444AB0 +sub_444B20 +sub_444BB0 +sub_444BC0 +bool __cdecl DDrawCreate(LPGUID lpGUID); +void __cdecl DDrawRelease(void); +void __cdecl GameWindowCalculateSizeFromClient(int32_t *width, int32_t *height); +void __cdecl GameWindowCalculateSizeFromClientByZero(int32_t *width, int32_t *height); +void __cdecl WinVidSetMinWindowSize(int32_t width, int32_t height); +void __cdecl WinVidClearMinWindowSize(void); +void __cdecl WinVidSetMaxWindowSize(int32_t width, int32_t height); +void __cdecl WinVidClearMaxWindowSize(void); +int32_t __cdecl CalculateWindowWidth(int32_t width, int32_t height); +int32_t __cdecl CalculateWindowHeight(int32_t width, int32_t height); +bool __cdecl WinVidGetMinMaxInfo(LPMINMAXINFO info); +HWND __cdecl WinVidFindGameWindow(void); +bool __cdecl WinVidSpinMessageLoop(bool needWait); +void __cdecl WinVidShowGameWindow(int32_t nCmdShow); +void __cdecl WinVidHideGameWindow(void); +void __cdecl WinVidSetGameWindowSize(int32_t width, int32_t height); +bool __cdecl ShowDDrawGameWindow(bool active); +bool __cdecl HideDDrawGameWindow(void); +HRESULT __cdecl DDrawSurfaceCreate(LPDDSDESC dsp, LPDDS *surface); +HRESULT __cdecl DDrawSurfaceRestoreLost(LPDDS surface1, LPDDS surface2, bool blank); +bool __cdecl WinVidClearBuffer(LPDDS surface, LPRECT rect, DWORD fill_color); +HRESULT __cdecl WinVidBufferLock(LPDDS surface, LPDDSDESC desc, DWORD flags); +HRESULT __cdecl WinVidBufferUnlock(LPDDS surface, LPDDSDESC desc); +bool __cdecl WinVidCopyBitmapToBuffer(LPDDS surface, const BYTE *bitmap); +DWORD __cdecl GetRenderBitDepth(DWORD dwRGBBitCount); +void __thiscall WinVidGetColorBitMasks(COLOR_BIT_MASKS *bm, LPDDPIXELFORMAT pixel_format); +void __cdecl BitMaskGetNumberOfBits(uint32_t bit_mask, uint32_t *bit_depth, uint32_t *bit_offset); +DWORD __cdecl CalculateCompatibleColor(COLOR_BIT_MASKS *mask, int32_t red, int32_t green, int32_t blue, int32_t alpha); +bool __cdecl WinVidGetDisplayMode(DISPLAY_MODE *disp_mode); +bool __cdecl WinVidGoFullScreen(DISPLAY_MODE *disp_mode); +bool __cdecl WinVidGoWindowed(int32_t width, int32_t height, DISPLAY_MODE *dispMode); +void __cdecl WinVidSetDisplayAdapter(DISPLAY_ADAPTER *disp_adapter); +bool __thiscall CompareVideoModes(const DISPLAY_MODE *mode1, const DISPLAY_MODE *mode2); +bool __cdecl WinVidGetDisplayModes(void); +HRESULT __stdcall EnumDisplayModesCallback(LPDDSDESC lpDDSurfaceDesc, LPVOID lpContext); +bool __cdecl WinVidInit(void); +bool __cdecl WinVidGetDisplayAdapters(void); +void __thiscall S_FlaggedString_Delete(STRING_FLAGGED *string); +bool __cdecl EnumerateDisplayAdapters(DISPLAY_ADAPTER_LIST *displayAdapterList); +BOOL __stdcall EnumDisplayAdaptersCallback(GUID *lpGUID, LPTSTR lpDriverDescription, LPTSTR lpDriverName, LPVOID lpContext); +void __thiscall S_FlaggedString_InitAdapter(DISPLAY_ADAPTER *adapter); +bool __cdecl WinVidRegisterGameWindowClass(void); +LRESULT __stdcall WinVidGameWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); +void __cdecl WinVidResizeGameWindow(HWND hWnd, int32_t edge, LPRECT rect); +bool __cdecl WinVidCheckGameWindowPalette(HWND hWnd); +bool __cdecl WinVidCreateGameWindow(void); +void __cdecl WinVidFreeWindow(void); +void __cdecl WinVidExitMessage(void); +DISPLAY_ADAPTER_NODE *__cdecl WinVidGetDisplayAdapter(GUID *lpGuid); +void __cdecl WinVidStart(void); +void __cdecl WinVidFinish(void); +void __thiscall DisplayModeListInit(DISPLAY_MODE_LIST *pList); +void __thiscall DisplayModeListDelete(DISPLAY_MODE_LIST *pList); +DISPLAY_MODE *__thiscall InsertDisplayMode(DISPLAY_MODE_LIST *modeList, DISPLAY_MODE_NODE *before); +DISPLAY_MODE *__thiscall InsertDisplayModeInListHead(DISPLAY_MODE_LIST *modeList); +DISPLAY_MODE *__thiscall InsertDisplayModeInListTail(DISPLAY_MODE_LIST *modeList); +sub_4470B0 +sub_4470C0 +sub_4470F0 +sub_447160 +sub_447170 +sub_4471C0 +bool __cdecl DInputCreate(void); +void __cdecl DInputRelease(void); +void __cdecl WinInReadKeyboard(LPVOID lpInputData); +DWORD __cdecl WinInReadJoystick(int32_t *x, int32_t *y); +sub_4473A0 +bool __cdecl WinInputInit(void); +bool __cdecl DInputEnumDevices(JOYSTICK_LIST *joystickList); +BOOL __stdcall DInputEnumDevicesCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef); +void __thiscall S_FlaggedString_Create(STRING_FLAGGED *string, int32_t size); +JOYSTICK_NODE *__cdecl GetJoystick(GUID *lpGuid); +void __cdecl DInputKeyboardCreate(void); +void __cdecl DInputKeyboardRelease(void); +bool __cdecl DInputJoystickCreate(void); +void __cdecl WinInStart(void); +void __cdecl WinInFinish(void); +void __cdecl WinInRunControlPanel(HWND hWnd); +void __cdecl IncreaseScreenSize(void); +void __cdecl DecreaseScreenSize(void); +void __cdecl setup_screen_size(void); +void __cdecl TempVideoAdjust(int32_t hires, double sizer); +void __cdecl TempVideoRemove(void); +void __cdecl S_FadeInInventory(BOOL isFade); +void __cdecl S_FadeOutInventory(BOOL isFade); +sub_447A80 +sub_447A90 +sub_447AC0 +sub_447B30 +sub_447B40 +sub_447B90 +const SOUND_ADAPTER_NODE *__cdecl S_Audio_Sample_GetAdapter(GUID *guid); +void __cdecl S_Audio_Sample_CloseAllTracks(void); +bool __cdecl S_Audio_Sample_Load(int32_t sample_id, LPWAVEFORMATEX format, const void *data, int32_t data_size); +bool __cdecl S_Audio_Sample_IsTrackPlaying(int32_t track_id); +int32_t __cdecl S_Audio_Sample_Play(int32_t sample_id, int32_t volume, int32_t pitch, int32_t pan, int32_t flags); +int32_t __cdecl S_Audio_Sample_GetFreeTrackIndex(void); +void __cdecl S_Audio_Sample_AdjustTrackVolumeAndPan(int32_t track_id, int32_t volume, int32_t pan); +void __cdecl S_Audio_Sample_AdjustTrackPitch(int32_t track_id, int32_t pitch); +void __cdecl S_Audio_Sample_CloseTrack(int32_t track_id); +sub_447FA0 +bool __cdecl S_Audio_Sample_Init(void); +bool __cdecl S_Audio_Sample_DSoundEnumerate(SOUND_ADAPTER_LIST *adapter_list); +BOOL __stdcall S_Audio_Sample_DSoundEnumCallback(LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context); +void __cdecl S_Audio_Sample_Init2(HWND hwnd); +bool __cdecl S_Audio_Sample_DSoundCreate(GUID *guid); +bool __cdecl S_Audio_Sample_DSoundBufferTest(void); +void __cdecl S_Audio_Sample_Shutdown(void); +bool __cdecl S_Audio_Sample_IsEnabled(void); +sub_448410 +sub_448420 +void __cdecl CreateScreenBuffers(void); +void __cdecl CreatePrimarySurface(void); +void __cdecl CreateBackBuffer(void); +void __cdecl CreateClipper(void); +void __cdecl CreateWindowPalette(void); +void __cdecl CreateZBuffer(void); +DWORD __cdecl GetZBufferDepth(void); +void __cdecl CreateRenderBuffer(void); +void __cdecl CreatePictureBuffer(void); +void __cdecl ClearBuffers(DWORD flags, DWORD fill_color); +void __cdecl RestoreLostBuffers(void); +void __cdecl UpdateFrame(bool need_run_message_loop, LPRECT rect); +void __cdecl WaitPrimaryBufferFlip(void); +bool __cdecl RenderInit(void); +void __cdecl RenderStart(bool is_reset); +void __cdecl RenderFinish(bool need_to_clear_textures); +bool __cdecl ApplySettings(APP_SETTINGS *new_settings); +void __cdecl FmvBackToGame(void); +void __cdecl GameApplySettings(APP_SETTINGS *new_settings); +void __cdecl UpdateGameResolution(void); +LPCTSTR __cdecl DecodeErrorMessage(DWORD error_code); +BOOL __cdecl ReadFileSync(HANDLE handle, LPVOID lpBuffer, DWORD nBytesToRead, LPDWORD lpnBytesRead, LPOVERLAPPED lpOverlapped); +BOOL __cdecl Level_LoadTexturePages(HANDLE handle); +BOOL __cdecl Level_LoadRooms(HANDLE handle); +void __cdecl AdjustTextureUVs(bool reset_uv_add); +BOOL __cdecl Level_LoadObjects(HANDLE handle); +BOOL __cdecl Level_LoadSprites(HANDLE handle); +BOOL __cdecl Level_LoadItems(HANDLE handle); +BOOL __cdecl Level_LoadDepthQ(HANDLE handle); +BOOL __cdecl Level_LoadPalettes(HANDLE handle); +BOOL __cdecl Level_LoadCameras(HANDLE handle); +BOOL __cdecl Level_LoadSoundEffects(HANDLE handle); +BOOL __cdecl Level_LoadBoxes(HANDLE handle); +BOOL __cdecl Level_LoadAnimatedTextures(HANDLE handle); +BOOL __cdecl Level_LoadCinematic(HANDLE handle); +BOOL __cdecl Level_LoadDemo(HANDLE handle); +void __cdecl Level_LoadDemoExternal(LPCTSTR level_name); +BOOL __cdecl Level_LoadSamples(HANDLE handle); +void __cdecl ChangeFileNameExtension(char *file_name, const char *file_ext); +LPCTSTR __cdecl GetFullPath(LPCTSTR file_name); +BOOL __cdecl SelectDrive(void); +bool __cdecl Level_Load(const char *file_name, int32_t level_num); +BOOL __cdecl S_LoadLevelFile(LPCTSTR file_name, int32_t level_num, GAMEFLOW_LEVEL_TYPE level_type); +void __cdecl S_UnloadLevelFile(void); +void __cdecl S_AdjustTexelCoordinates(void); +BOOL __cdecl S_ReloadLevelGraphics(BOOL reload_palettes, BOOL reload_tex_pages); +BOOL __cdecl GF_ReadStringTable(DWORD count, char **string_table, char **string_buf, LPDWORD buf_size, HANDLE handle); +BOOL __cdecl GF_LoadFromFile(const char *file_name); +bool __cdecl PlayFMV(const char *file_name); +void __cdecl WinPlayFMV(const char *file_name, bool is_playback); +void __cdecl WinStopFMV(bool is_playback); +bool __cdecl IntroFMV(const char *file_name1, const char *file_name2); +uint16_t __cdecl S_COLOR(int32_t red, int32_t green, int32_t blue); +void __cdecl S_DrawScreenLine(int32_t x, int32_t y, int32_t z, int32_t x_len, int32_t y_len, BYTE color_idx, D3DCOLOR *gour, uint16_t flags); +void __cdecl S_DrawScreenBox(int32_t sx, int32_t sy, int32_t z, int32_t width, int32_t height, BYTE color_idx, const GOURAUD_OUTLINE *gour, uint16_t flags); +void __cdecl S_DrawScreenFBox(int32_t sx, int32_t sy, int32_t z, int32_t width, int32_t height, BYTE color_idx, const GOURAUD_FILL *gour, uint16_t flags); +void __cdecl S_FinishInventory(void); +void __cdecl S_FadeToBlack(void); +void __cdecl S_Wait(int32_t timeout, BOOL input_check); +bool __cdecl S_PlayFMV(const char *file_name); +bool __cdecl S_IntroFMV(const char *file_name1, const char *file_name2); +int16_t __cdecl Game_Start(int32_t level_num, GAMEFLOW_LEVEL_TYPE level_type); +int32_t __cdecl Game_Loop(bool demo_mode); +int32_t __cdecl LevelCompleteSequence(void); +int32_t __cdecl LevelStats(int32_t level_num); +int32_t __cdecl GameStats(int32_t level_num); +int32_t __cdecl Random_GetControl(void); +void __cdecl Random_SeedControl(int32_t seed); +int32_t __cdecl Random_GetDraw(void); +void __cdecl Random_SeedDraw(int32_t seed); +void __cdecl GetValidLevelsList(REQUEST_INFO *req); +void __cdecl GetSavedGamesList(REQUEST_INFO *req); +void __cdecl DisplayCredits(void); +BOOL __cdecl S_FrontEndCheck(void); +int32_t __cdecl S_SaveGame(const void *save_data, uint32_t save_size, int32_t slot_num); +int32_t __cdecl S_LoadGame(void *save_data, uint32_t save_size, int32_t slot_num); +void __cdecl HWR_InitState(void); +void __cdecl HWR_ResetTexSource(void); +void __cdecl HWR_ResetColorKey(void); +void __cdecl HWR_ResetZBuffer(void); +void __cdecl HWR_TexSource(HWR_TEXTURE_HANDLE tex_source); +void __cdecl HWR_EnableColorKey(bool state); +void __cdecl HWR_EnableZBuffer(bool z_write_enable, bool z_enable); +void __cdecl HWR_BeginScene(void); +void __cdecl HWR_DrawPolyList(void); +void __cdecl HWR_LoadTexturePages(int32_t pages_count, void *pages_buf, RGB_888 *palette); +void __cdecl HWR_FreeTexturePages(void); +void __cdecl HWR_GetPageHandles(void); +bool __cdecl HWR_VertexBufferFull(void); +bool __cdecl HWR_Init(void); +BOOL __cdecl S_InitialiseSystem(void); +void __cdecl GameBuf_Shutdown(void); +void __cdecl init_game_malloc(void); +void *__cdecl game_malloc(size_t alloc_size, GAME_BUFFER buf_index); +void __cdecl game_free(size_t free_size); +void __cdecl CalculateWibbleTable(void); +void __cdecl Random_Seed(void); +BOOL __cdecl S_Input_Key(KEYMAP keymap); +bool __cdecl Input_Update(void); +int32_t __cdecl RenderErrorBox(int32_t error_code); +int32_t __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int32_t nShowCmd); +sub_44E6F0 +int32_t __cdecl GameInit(bool skip_cd_init); +void __cdecl Shell_Cleanup(void); +int32_t __cdecl WinGameStart(void); +void __cdecl Shell_Shutdown(void); +sub_44E860 +sub_44E880 +void __cdecl Shell_ExitSystem(const char *message); +void __cdecl ScreenshotPCX(void); +DWORD __cdecl CompPCX(BYTE *bitmap, DWORD width, DWORD height, RGB_888 *palette, BYTE **pcx_data); +DWORD __cdecl EncodeLinePCX(BYTE *src, DWORD width, BYTE *dst); +DWORD __cdecl EncodePutPCX(BYTE value, BYTE num, BYTE *buffer); +void __cdecl Screenshot(LPDDS screen); +void __cdecl Option_DoInventory(INVENTORY_ITEM *item); +void __cdecl Option_Passport(INVENTORY_ITEM *item); +void __cdecl Option_Detail(INVENTORY_ITEM *item); +void __cdecl Option_Sound(INVENTORY_ITEM *item); +void __cdecl Option_Compass(INVENTORY_ITEM *item); +void __cdecl Option_Controls_FlashConflicts(void); +void __cdecl Option_Controls_DefaultConflict(void); +void __cdecl Option_Controls(INVENTORY_ITEM *item); +void __cdecl Option_Controls_ShowControls(void); +void __cdecl Option_Controls_UpdateText(void); +void __cdecl S_RemoveCtrlText(void); +int32_t __cdecl GetRenderHeight(void); +int32_t __cdecl GetRenderWidth(void); +void __cdecl S_InitialisePolyList(BOOL clear_back_buffer); +DWORD __cdecl S_DumpScreen(void); +void __cdecl S_ClearScreen(void); +void __cdecl S_InitialiseScreen(GAMEFLOW_LEVEL_TYPE level_type); +void __cdecl S_OutputPolyList(void); +int32_t __cdecl S_GetObjectBounds(const BOUNDS_16 *bounds); +void __cdecl S_InsertBackPolygon(int32_t x0, int32_t y0, int32_t x1, int32_t y1); +void __cdecl S_PrintShadow(int16_t radius, const BOUNDS_16 *bounds, const ITEM *item); +void __cdecl S_CalculateLight(int32_t x, int32_t y, int32_t z, int16_t room_num); +void __cdecl S_CalculateStaticLight(int16_t adder); +void __cdecl S_CalculateStaticMeshLight(int32_t x, int32_t y, int32_t z, int32_t shade_1, int32_t shade_2, ROOM *room); +void __cdecl S_LightRoom(ROOM *room); +void __cdecl S_DrawHealthBar(int32_t percent); +void __cdecl S_DrawAirBar(int32_t percent); +void __cdecl AnimateTextures(int32_t ticks); +void __cdecl S_SetupBelowWater(BOOL underwater); +void __cdecl S_SetupAboveWater(BOOL underwater); +void __cdecl S_AnimateTextures(int32_t ticks); +void __cdecl S_DisplayPicture(const char *file_name, BOOL is_title); +void __cdecl S_SyncPictureBufferPalette(void); +void __cdecl S_DontDisplayPicture(void); +void __cdecl ScreenDump(void); +void __cdecl ScreenPartialDump(void); +void __cdecl FadeToPal(int32_t fade_value, RGB_888 *palette); +void __cdecl ScreenClear(bool is_phd_win_size); +void __cdecl S_CopyScreenToBuffer(void); +void __cdecl S_CopyBufferToScreen(void); +BOOL __cdecl DecompPCX(const uint8_t *pcx, size_t pcx_size, LPBYTE pic, RGB_888 *pal); +sub_4523A0 +sub_4523B0 +bool __cdecl OpenGameRegistryKey(LPCTSTR key); +LONG __cdecl CloseGameRegistryKey(void); +bool __cdecl SE_WriteAppSettings(APP_SETTINGS *settings); +int32_t __cdecl SE_ReadAppSettings(APP_SETTINGS *settings); +bool __cdecl SE_GraphicsTestStart(void); +void __cdecl SE_GraphicsTestFinish(void); +int32_t __cdecl SE_GraphicsTestExecute(void); +int32_t __cdecl SE_GraphicsTest(void); +bool __cdecl SE_SoundTestStart(void); +void __cdecl SE_SoundTestFinish(void); +int32_t __cdecl SE_SoundTestExecute(void); +int32_t __cdecl SE_SoundTest(void); +int32_t __stdcall SE_PropSheetCallback(HWND hwndDlg, UINT uMsg, LPARAM lParam); +LRESULT __stdcall SE_NewPropSheetWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +bool __cdecl SE_ShowSetupDialog(HWND hParent, bool isDefault); +INT_PTR __stdcall SE_GraphicsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +void __cdecl SE_GraphicsDlgFullScreenModesUpdate(HWND hwndDlg); +void __cdecl SE_GraphicsAdapterSet(HWND hwndDlg, DISPLAY_ADAPTER_NODE *adapter); +void __cdecl SE_GraphicsDlgUpdate(HWND hwndDlg); +void __cdecl SE_GraphicsDlgInit(HWND hwndDlg); +INT_PTR __stdcall SE_SoundDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +void __cdecl SE_SoundAdapterSet(HWND hwndDlg, SOUND_ADAPTER_NODE *adapter); +void __cdecl SE_SoundDlgUpdate(HWND hwndDlg); +void __cdecl SE_SoundDlgInit(HWND hwndDlg); +INT_PTR __stdcall SE_ControlsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +void __cdecl SE_ControlsJoystickSet(HWND hwndDlg, JOYSTICK_NODE *joystick); +void __cdecl SE_ControlsDlgUpdate(HWND hwndDlg); +void __cdecl SE_ControlsDlgInit(HWND hwndDlg); +INT_PTR __stdcall SE_OptionsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +void __cdecl SE_OptionsDlgUpdate(HWND hwndDlg); +void __cdecl SE_OptionsStrCat(LPTSTR *dstString, bool isEnabled, bool *isNext, LPCTSTR srcString); +INT_PTR __stdcall SE_AdvancedDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +void __cdecl SE_AdvancedDlgUpdate(HWND hwndDlg); +void __cdecl SE_AdvancedDlgInit(HWND hwndDlg); +HWND __cdecl SE_FindSetupDialog(void); +BOOL __cdecl Shell_Main(void); +int16_t __cdecl TitleSequence(void); +void __cdecl CheckCheatMode(void); +void __cdecl S_SaveSettings(void); +void __cdecl S_LoadSettings(void); +int32_t __cdecl S_Audio_Sample_OutPlay(int32_t sample_id, uint16_t volume, int32_t pitch, int32_t pan); +int32_t __cdecl S_Audio_Sample_CalculateSampleVolume(int32_t volume); +int32_t __cdecl S_Audio_Sample_CalculateSamplePan(int16_t pan); +int32_t __cdecl S_Audio_Sample_OutPlayLooped(int32_t track_id, uint16_t volume, int32_t pitch, int32_t pan); +void __cdecl S_Audio_Sample_OutSetPanAndVolume(int32_t track_id, int32_t pan, uint16_t volume); +void __cdecl S_Audio_Sample_OutSetPitch(int32_t track_id, int32_t pitch); +void __cdecl Sound_SetMasterVolume(int32_t volume); +void __cdecl S_Audio_Sample_OutCloseTrack(int32_t track_id); +void __cdecl S_Audio_Sample_OutCloseAllTracks(void); +BOOL __cdecl S_Audio_Sample_OutIsTrackPlaying(int32_t track_id); +bool __cdecl Music_Init(void); +void __cdecl Music_Shutdown(void); +void __cdecl Music_Play(int16_t track_id, bool is_looped); +void __cdecl Music_Stop(void); +bool __cdecl Music_PlaySynced(int32_t track_id); +int32_t __cdecl Music_GetFrames(void); +void __cdecl Music_SetVolume(int32_t volume); +void __cdecl CopyBitmapPalette(RGB_888 *src_pal, BYTE *src_bitmap, int32_t bitmap_size, RGB_888 *dest_pal); +BYTE __cdecl FindNearestPaletteEntry(RGB_888 *palette, int32_t red, int32_t green, int32_t blue, bool ignore_sys_palette); +void __cdecl SyncSurfacePalettes(void *src_data, int32_t width, int32_t height, int32_t src_pitch, RGB_888 *src_palette, void *dst_data, int32_t dst_pitch, RGB_888 *dst_palette, bool preserve_sys_palette); +int32_t __cdecl CreateTexturePalette(const RGB_888 *pal); +int32_t __cdecl GetFreePaletteIndex(void); +void __cdecl FreePalette(int32_t palette_idx); +void __cdecl SafeFreePalette(int32_t palette_idx); +int32_t __cdecl CreateTexturePage(int32_t width, int32_t height, bool alpha); +int32_t __cdecl GetFreeTexturePageIndex(void); +bool __cdecl CreateTexturePageSurface(TEXPAGE_DESC *desc); +bool __cdecl TexturePageInit(TEXPAGE_DESC *page); +LPDIRECT3DTEXTURE2 __cdecl Create3DTexture(LPDDS surface); +void __cdecl SafeFreeTexturePage(int32_t page_idx); +void __cdecl FreeTexturePage(int32_t page_idx); +void __cdecl TexturePageReleaseVidMemSurface(TEXPAGE_DESC *page); +void __cdecl FreeTexturePages(void); +bool __cdecl LoadTexturePage(int32_t page_idx, bool reset); +bool __cdecl ReloadTextures(bool reset); +HWR_TEXTURE_HANDLE __cdecl GetTexturePageHandle(int32_t page_idx); +int32_t __cdecl AddTexturePage8(int32_t width, int32_t height, const uint8_t *page_buf, int32_t pal_idx); +int32_t __cdecl AddTexturePage16(int32_t width, int32_t height, const uint8_t *page_buf); +HRESULT __stdcall EnumTextureFormatsCallback(LPDDSDESC lpDdsd, LPVOID lpContext); +HRESULT __cdecl EnumerateTextureFormats(void); +void __cdecl CleanupTextures(void); +bool __cdecl InitTextures(void); +void __cdecl UpdateTicks(void); +bool __cdecl TIME_Init(void); +DWORD __cdecl Sync(void); +LPVOID __cdecl UT_LoadResource(LPCTSTR lpName, LPCTSTR lpType); +void __cdecl UT_InitAccurateTimer(void); +double __cdecl UT_Microseconds(void); +BOOL __cdecl UT_CenterWindow(HWND hWnd); +LPTSTR __cdecl UT_FindArg(LPCTSTR str); +int32_t __cdecl UT_MessageBox(LPCTSTR lpText, HWND hWnd); +int32_t __cdecl UT_ErrorBox(UINT uID, HWND hWnd); +LPCTSTR __cdecl GuidBinaryToString(GUID *guid); +bool __cdecl GuidStringToBinary(LPCTSTR lpString, GUID *guid); +BOOL __cdecl OpenRegistryKey(LPCTSTR lpSubKey); +bool __cdecl IsNewRegistryKeyCreated(void); +LONG __cdecl CloseRegistryKey(void); +LONG __cdecl SetRegistryDwordValue(LPCTSTR lpValueName, DWORD value); +LONG __cdecl SetRegistryBoolValue(LPCTSTR lpValueName, bool value); +LONG __cdecl SetRegistryFloatValue(LPCTSTR lpValueName, double value); +LONG __cdecl SetRegistryBinaryValue(LPCTSTR lpValueName, LPBYTE value, DWORD valueSize); +LONG __cdecl SetRegistryStringValue(LPCTSTR lpValueName, LPCTSTR value, int32_t length); +LONG __cdecl DeleteRegistryValue(LPCTSTR lpValueName); +bool __cdecl GetRegistryDwordValue(LPCTSTR lpValueName, DWORD *pValue, DWORD defaultValue); +bool __cdecl GetRegistryBoolValue(LPCTSTR lpValueName, bool *pValue, bool defaultValue); +bool __cdecl GetRegistryFloatValue(LPCTSTR lpValueName, double *value, double defaultValue); +bool __cdecl GetRegistryBinaryValue(LPCTSTR lpValueName, LPBYTE value, DWORD valueSize, LPBYTE defaultValue); +bool __cdecl GetRegistryStringValue(LPCTSTR lpValueName, LPTSTR value, DWORD maxSize, LPCTSTR defaultValue); +bool __cdecl GetRegistryGuidValue(LPCTSTR lpValueName, GUID *value, GUID *defaultValue); +void __thiscall SE_ReleaseBitmapResource(BITMAP_RESOURCE *bmpRsrc); +void __thiscall SE_LoadBitmapResource(BITMAP_RESOURCE *bmpRsrc, LPCTSTR lpName); +void __thiscall SE_DrawBitmap(BITMAP_RESOURCE *bmpRsrc, HDC hdc, int32_t x, int32_t y); +void __thiscall SE_UpdateBitmapPalette(BITMAP_RESOURCE *bmpRsrc, HWND hWnd, HWND hSender); +void __thiscall SE_ChangeBitmapPalette(BITMAP_RESOURCE *bmpRsrc, HWND hWnd); +bool __cdecl SE_RegisterSetupWindowClass(void); +LRESULT __stdcall SE_SetupWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +void __cdecl SE_PassMessageToImage(HWND hWnd, UINT uMsg, WPARAM wParam); +void __cdecl UT_MemBlt(BYTE *dstBuf, DWORD dstX, DWORD dstY, DWORD width, DWORD height, DWORD dstPitch, BYTE *srcBuf, DWORD srcX, DWORD srcY, DWORD srcPitch); +void __cdecl Matrix_Push(void); +void __cdecl Matrix_PushUnit(void); +void __fastcall Output_FlatA(int32_t y0, int32_t y1, uint8_t color_idx); // actually, __watcall, which is esoteric and rarely supported +void __fastcall Output_TransA(int32_t y0, int32_t y1, uint8_t depth_q); // actually, __watcall, which is esoteric and rarely supported +void __fastcall Output_GourA(int32_t y0, int32_t y1, uint8_t color_idx); // actually, __watcall, which is esoteric and rarely supported +void __fastcall Output_GTMapA(int32_t y0, int32_t y1, uint8_t *tex_page); // actually, __watcall, which is esoteric and rarely supported +void __fastcall Output_WGTMapA(int32_t y0, int32_t y1, uint8_t *tex_page); // actually, __watcall, which is esoteric and rarely supported +int32_t __fastcall Math_Atan(int32_t x, int32_t y); +int32_t __fastcall Math_Cos(int16_t angle); +int32_t __fastcall Math_Sin(int16_t angle); +int32_t __fastcall Math_SinImpl(int16_t angle); +uint32_t __fastcall Math_Sqrt(uint32_t n); + + + +Tomb2.exe progress according to the function sizes: +65.44% · 34.23% · 0% · 0.33% + + + + + + +void __cdecl Output_WGTMapPersp32FP(int32_t y1, int32_t y2, uint8_t *tex_page); +int32_t __cdecl Inv_Display(int32_t inventory_mode); +void __cdecl BaddyObjects(void); +void __cdecl Output_GTMapPersp32FP(int32_t y1, int32_t y2, uint8_t *tex_page); +void __cdecl Lara_Draw(const ITEM *item); +void __cdecl GF_ModifyInventory(int32_t level, int32_t type); +void __cdecl ObjectObjects(void); +void __cdecl Lara_Draw_I(const ITEM *item, const FRAME_INFO *frame1, const FRAME_INFO *frame2, int32_t frac, int32_t rate); +bool __cdecl Input_Update(void); +const int16_t *__cdecl Output_InsertObjectGT4(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __cdecl HairControl(int32_t in_cutscene); +int32_t __cdecl Creature_Animate(int16_t item_num, int16_t angle, int16_t tilt); +const int16_t *__cdecl Output_InsertObjectGT3(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +int32_t __cdecl Requester_Display(REQUEST_INFO *req, int32_t des, int32_t backgrounds); +void __cdecl ExtractSaveGameInfo(void); +void __cdecl Camera_SmartShift(GAME_VECTOR *target, void (*__cdecl shift)(int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom)); +void __cdecl SE_GraphicsDlgUpdate(HWND hwndDlg); +void __cdecl Output_InsertGT3_Sorted(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, const PHD_TEXTURE *texture, const PHD_UV *uv0, const PHD_UV *uv1, const PHD_UV *uv2, SORT_TYPE sort_type); +void __cdecl Option_Controls(INVENTORY_ITEM *item); +BOOL __cdecl GF_LoadFromFile(const char *file_name); +int32_t __cdecl Inv_AddItem(GAME_OBJECT_ID object_id); +void __cdecl Room_TestTriggers(int16_t *data, int32_t heavy); +void __cdecl Collide_GetCollisionInfo(COLL_INFO *coll, int32_t xpos, int32_t ypos, int32_t zpos, int16_t room_num, int32_t obj_height); +void __cdecl Lara_Control(int16_t item_num); +void __cdecl Option_Passport(INVENTORY_ITEM *item); +void __cdecl Output_InsertGT3_ZBuffered(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, const PHD_TEXTURE *texture, const PHD_UV *uv0, const PHD_UV *uv1, const PHD_UV *uv2); +void __cdecl Boat_Control(int16_t item_num); +void __cdecl TrapObjects(void); +void __cdecl YetiControl(int16_t item_num); +void __cdecl Worker3Control(int16_t item_num); +void __cdecl XianLordControl(int16_t item_num); +BOOL __cdecl Level_LoadObjects(HANDLE handle); +void __cdecl Cult3Control(int16_t item_num); +void __cdecl CreateSaveGameInfo(void); +void __cdecl DragonControl(int16_t back_num); +int32_t __cdecl Output_XYGUVClipper(int32_t vtx_count, VERTEX_INFO *vtx); +void __cdecl Gun_Control(void); +int32_t __cdecl Boat_Dynamics(int16_t boat_num); +void __cdecl Output_InsertGT4_Sorted(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, const PHD_VBUF *vtx3, const PHD_TEXTURE *texture, SORT_TYPE sort_type); +void __cdecl Option_Controls_ShowControls(void); +void __cdecl Camera_Update(void); +void __cdecl Output_InsertTrans8(const PHD_VBUF *vbuf, int16_t shade); +void __cdecl MonkControl(int16_t item_num); +LRESULT __stdcall WinVidGameWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); +int32_t __cdecl SkidooDynamics(ITEM *skidoo); +void __cdecl Option_Sound(INVENTORY_ITEM *item); +void __cdecl Sound_Effect(int32_t sample_id, const XYZ_32 *pos, uint32_t flags); +void __cdecl PickUpCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +int32_t __cdecl GF_InterpretSequence(int16_t *ptr, GAMEFLOW_LEVEL_TYPE type, int32_t seq_type); +TARGET_TYPE __cdecl Box_CalculateTarget(XYZ_32 *target, ITEM *item, LOT_INFO *lot); +void __cdecl Output_InsertGT4_ZBuffered(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, const PHD_VBUF *vtx3, const PHD_TEXTURE *texture); +int32_t __cdecl SkidooControl(void); +void __cdecl Creature_Mood(ITEM *item, AI_INFO *info, int32_t violent); +const int16_t *__cdecl Output_InsertObjectG4_Sorted(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __cdecl Worker2Control(int16_t item_num); +void __cdecl ShowEndStatsText(void); +int32_t __cdecl Output_XYGClipper(int32_t vtx_count, VERTEX_INFO *vtx); +void __cdecl Inv_DrawInventoryItem(INVENTORY_ITEM *inv_item); +void __cdecl Cult2Control(int16_t item_num); +void __cdecl Bandit2Control(int16_t item_num); +void __cdecl Cult1Control(int16_t item_num); +void __cdecl Rocket_Control(int16_t item_num); +void __cdecl DrawXianLord(ITEM *item); +void __cdecl SkidManControl(int16_t rider_num); +void __cdecl InitialiseDoor(int16_t item_num); +int32_t __cdecl Collide_CollideStaticObjects(COLL_INFO *coll, int32_t x, int32_t y, int32_t z, int16_t room_num, int32_t height); +void __cdecl Room_Clip(ROOM *r); +void __cdecl Text_DrawText(const TEXTSTRING *string); +const int16_t *__cdecl Output_InsertObjectG4(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __cdecl Worker1Control(int16_t item_num); +void __cdecl WarriorControl(int16_t item_num); +HRESULT __stdcall EnumDisplayModesCallback(LPDDSDESC lpDDSurfaceDesc, LPVOID lpContext); +void __cdecl RenderStart(bool is_reset); +BOOL __cdecl Level_LoadRooms(HANDLE handle); +const int16_t *__cdecl Output_InsertObjectG4_ZBuffered(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __cdecl ShowStatsText(char *time_str, int32_t type); +void __cdecl Lara_GetJointAbsPosition(XYZ_32 *vec, int32_t joint); +void __cdecl Diver_Control(int16_t item_num); +void __cdecl Dog_Control(int16_t item_num); +int32_t __cdecl Effect_ExplodingDeath(int16_t item_num, int32_t mesh_bits, int16_t damage); +void __cdecl Room_SetBounds(const int16_t *objptr, int32_t room_num, ROOM *parent); +void __cdecl Gun_Rifle_Animate(LARA_GUN_TYPE weapon_type); +void __cdecl Object_DrawAnimatingItem(ITEM *item); +void __cdecl WinVidStart(void); +void __cdecl Camera_Move(const GAME_VECTOR *target, int32_t speed); +void __cdecl Lara_InitialiseInventory(int32_t level_num); +void __cdecl Inv_RingNotActive(INVENTORY_ITEM *inv_item); +int32_t __cdecl Gun_FireWeapon(LARA_GUN_TYPE weapon_type, ITEM *target, const ITEM *src, const PHD_ANGLE *angles); +int32_t __cdecl Game_Control(int32_t nframes, int32_t demo_mode); +INT_PTR __stdcall SE_GraphicsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +int32_t __cdecl SE_ReadAppSettings(APP_SETTINGS *settings); +void __cdecl Output_InsertSprite_Sorted(int32_t z, int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t sprite_idx, int16_t shade); +void __cdecl RollingBallControl(int16_t item_num); +void __cdecl HarpoonBolt_Control(int16_t item_num); +const int16_t *__cdecl Output_InsertObjectG3_Sorted(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __fastcall Output_WGTMapA(int32_t y0, int32_t y1, uint8_t *tex_page); // actually, __watcall, which is esoteric and rarely supported +void __cdecl Lara_HangTest(ITEM *item, COLL_INFO *coll); +void __cdecl Item_Animate(ITEM *item); +void __cdecl Gun_Pistols_Animate(LARA_GUN_TYPE weapon_type); +void __cdecl CheckCheatMode(void); +const int16_t *__cdecl Output_InsertObjectG3(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __cdecl Lara_GetJointAbsPosition_I(ITEM *item, XYZ_32 *vec, int16_t *frame1, int16_t *frame2, int32_t frac, int32_t rate); +void __cdecl BanditControl(int16_t item_num); +void __cdecl Lara_Push(ITEM *item, ITEM *lara_item, COLL_INFO *coll, int32_t spaz_on, int32_t big_push); +int32_t __cdecl LOS_CheckSmashable(const GAME_VECTOR *start, GAME_VECTOR *target); +void __fastcall Output_GTMapA(int32_t y0, int32_t y1, uint8_t *tex_page); // actually, __watcall, which is esoteric and rarely supported +void __cdecl S_CalculateLight(int32_t x, int32_t y, int32_t z, int16_t room_num); +void __cdecl DragonCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +int32_t __cdecl LOS_CheckX(const GAME_VECTOR *start, GAME_VECTOR *target); +int32_t __cdecl LOS_CheckZ(const GAME_VECTOR *start, GAME_VECTOR *target); +int32_t __cdecl Lara_TestVault(ITEM *item, COLL_INFO *coll); +void __cdecl WinPlayFMV(const char *file_name, bool is_playback); +bool __cdecl SE_ShowSetupDialog(HWND hParent, bool isDefault); +void __cdecl Option_Detail(INVENTORY_ITEM *item); +int32_t __cdecl Collide_GetSpheres(const ITEM *item, SPHERE *spheres, bool world_space); +void __cdecl DrawSkidoo(ITEM *item); +const int16_t *__cdecl Output_InsertObjectG3_ZBuffered(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +int32_t __cdecl Output_XYClipper(int32_t vtx_count, VERTEX_INFO *vtx); +void __cdecl ControlMissile(int16_t fx_num); +BOOL __cdecl Shell_Main(void); +void __cdecl Lara_Animate(ITEM *item); +void __cdecl GiantYetiControl(int16_t item_num); +void __cdecl Output_DrawSprite(uint32_t flags, int32_t x, int32_t y, int32_t z, int16_t sprite_idx, int16_t shade, int16_t scale); +void __cdecl Lara_Initialise(int32_t type); +void __cdecl SkidooAnimation(ITEM *skidoo, int32_t collide, int32_t dead); +void __cdecl Room_DrawAllRooms(int16_t current_room); +void __cdecl PuzzleHoleCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl ControlDeathSlide(int16_t item_num); +void __cdecl DinoControl(int16_t item_num); +int32_t __cdecl Output_XGenXGUVPerspFP(const int16_t *obj_ptr); +void __cdecl Tiger_Control(int16_t item_num); +const int16_t *__cdecl Output_CalcRoomVertices(const int16_t *obj_ptr, int32_t far_clip); +void __cdecl SharkControl(int16_t item_num); +int32_t __cdecl DoShift(ITEM *skidoo, XYZ_32 *pos, XYZ_32 *old); +int32_t __cdecl S_GetObjectBounds(const BOUNDS_16 *bounds); +void __cdecl Flare_Control(int16_t item_num); +void __cdecl Output_Init(int16_t x, int16_t y, int32_t width, int32_t height, int32_t near_z, int32_t far_z, int16_t view_angle, int32_t screen_width, int32_t screen_height); +int32_t __cdecl Room_GetHeight(const SECTOR *sector, int32_t x, int32_t y, int32_t z); +BOOL __cdecl Level_LoadSamples(HANDLE handle); +int32_t __cdecl Lara_TestClimb(int32_t x, int32_t y, int32_t z, int32_t xfront, int32_t zfront, int32_t item_height, int16_t item_room, int32_t *shift); +bool __cdecl SE_WriteAppSettings(APP_SETTINGS *settings); +void __cdecl S_CopyBufferToScreen(void); +void __cdecl DrawTextureTile(int32_t sx, int32_t sy, int32_t width, int32_t height, HWR_TEXTURE_HANDLE tex_source, int32_t tu, int32_t tv, int32_t t_width, int32_t t_height, D3DCOLOR color0, D3DCOLOR color1, D3DCOLOR color2, D3DCOLOR color3); +void __cdecl RollingBallCollision(int16_t item_num, ITEM *litem, COLL_INFO *coll); +bool __cdecl Level_Load(const char *file_name, int32_t level_num); +void __cdecl Camera_LoadCutsceneFrame(void); +void __cdecl Creature_AIInfo(ITEM *item, AI_INFO *info); +int32_t __cdecl Room_GetCeiling(const SECTOR *sector, int32_t x, int32_t y, int32_t z); +void __cdecl DetonatorCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl GameApplySettings(APP_SETTINGS *new_settings); +LRESULT __stdcall SE_SetupWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +void __cdecl MovableBlockCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +int32_t __cdecl Lara_TestClimbUpPos(ITEM *item, int32_t front, int32_t right, int32_t *shift, int32_t *ledge); +void __cdecl SE_OptionsDlgUpdate(HWND hwndDlg); +int32_t __cdecl SkidooCheckGetOff(void); +void __cdecl DisplayCredits(void); +void __cdecl Gun_Pistols_Undraw(LARA_GUN_TYPE weapon_type); +int32_t __cdecl TestBlockPull(ITEM *item, int32_t block_height, uint16_t quadrant); +void __cdecl Output_DrawScaledSpriteC(const int16_t *obj_ptr); +void __cdecl Lara_HandleUnderwater(ITEM *item, COLL_INFO *coll); +void __cdecl KeyHoleCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl Flare_Undraw(void); +BOOL __cdecl Level_LoadBoxes(HANDLE handle); +void __cdecl BGND_DrawInGameBackground(void); +void __cdecl Gun_GetNewTarget(WEAPON_INFO *winfo); +int32_t __cdecl Lara_TestWaterClimbOut(ITEM *item, COLL_INFO *coll); +void __cdecl BaracuddaControl(int16_t item_num); +int32_t __cdecl Output_XGenXGUV(const int16_t *obj_ptr); +void __cdecl Room_DrawSingleRoomObjects(int16_t room_num); +void __cdecl FinalLevelCounter_Control(int16_t item_num); +void __cdecl Lara_Col_ClimbStance(ITEM *item, COLL_INFO *coll); +void __cdecl SwitchCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl SpiderControl(int16_t item_num); +void __cdecl S_LightRoom(ROOM *room); +bool __cdecl ApplySettings(APP_SETTINGS *new_settings); +void __cdecl CreateStartInfo(int32_t level_num); +void __cdecl Bird_Control(int16_t item_num); +void __cdecl BodyPart_Control(int16_t fx_num); +void __cdecl SkidooBaddieCollision(ITEM *skidoo); +void __cdecl S_DrawAirBar(int32_t percent); +void __cdecl Screenshot(LPDDS screen); +void __cdecl MineControl(int16_t mine_num); +int32_t __cdecl Box_SearchLOT(LOT_INFO *lot, int32_t expansion); +void __cdecl S_PrintShadow(int16_t radius, const BOUNDS_16 *bounds, const ITEM *item); +void __cdecl Lara_WaterCurrent(COLL_INFO *coll); +int32_t __cdecl GF_LoadScriptFile(const char *fname); +void __cdecl Matrix_RotYXZpack(uint32_t rpack); +void __cdecl Camera_Look(const ITEM *item); +void __cdecl EelControl(int16_t item_num); +void __cdecl Matrix_RotYXZ(int16_t ry, int16_t rx, int16_t rz); +void __cdecl MouseControl(int16_t item_num); +void __cdecl SE_GraphicsDlgFullScreenModesUpdate(HWND hwndDlg); +void __cdecl Creature_GetBaddieTarget(int16_t item_num, int32_t goody); +void __cdecl Flare_Create(int32_t thrown); +void __cdecl ShowGymStatsText(char *time_str, int32_t type); +BOOL __cdecl Level_LoadItems(HANDLE handle); +int32_t __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int32_t nShowCmd); +const int16_t *__cdecl Output_CalcObjectVertices(const int16_t *obj_ptr); +void __cdecl Matrix_GenerateW2V(PHD_3DPOS *viewpos); +void __cdecl S_DrawHealthBar(int32_t percent); +void __cdecl Output_InsertFlatRect_ZBuffered(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +void __cdecl FadeToPal(int32_t fade_value, RGB_888 *palette); +void __cdecl Room_GetBounds(void); +int32_t __cdecl ShotLara(ITEM *item, AI_INFO *info, BITE *gun, int16_t extra_rotation, int32_t damage); +int32_t __cdecl LevelStats(int32_t level_num); +void __cdecl Boat_DoShift(int32_t boat_num); +void __cdecl WinVidResizeGameWindow(HWND hWnd, int32_t edge, LPRECT rect); +BOOL __stdcall EnumDisplayAdaptersCallback(GUID *lpGUID, LPTSTR lpDriverDescription, LPTSTR lpDriverName, LPVOID lpContext); +void __cdecl BGND_Make640x480(uint8_t *bitmap, RGB_888 *palette); +void __cdecl D3DDeviceCreate(LPDDS lpBackBuffer); +int32_t __cdecl SkidooUserControl(ITEM *skidoo, int32_t height, int32_t *pitch); +void __cdecl Item_Initialise(int16_t item_num); +int32_t __cdecl Demo_Start(int32_t level_num); +bool __cdecl LOT_EnableBaddieAI(int16_t item_num, bool always); +void __cdecl Output_InsertClippedPoly_Textured(int32_t vtx_count, float z, int16_t poly_type, int16_t tex_page); +void __cdecl Matrix_Interpolate(void); +void __cdecl BigSpiderControl(int16_t item_num); +void __cdecl ClearBuffers(DWORD flags, DWORD fill_color); +void __cdecl Output_DrawPolyLine(const int16_t *obj_ptr); +int32_t __cdecl Camera_ShiftClamp(GAME_VECTOR *pos, int32_t clamp); +int32_t __cdecl Lara_TestHangJump(ITEM *item, COLL_INFO *coll); +void __cdecl BigEelControl(int16_t item_num); +void __cdecl Lara_Col_ClimbDown(ITEM *item, COLL_INFO *coll); +void __cdecl Collide_GetJointAbsPosition(const ITEM *item, const XYZ_32 *vec, int32_t joint); +SECTOR *__cdecl Room_GetSector(int32_t x, int32_t y, int32_t z, int16_t *room_num); +int32_t __cdecl AddTexturePage16(int32_t width, int32_t height, const uint8_t *page_buf); +void __cdecl Output_InsertTrans8_Sorted(const PHD_VBUF *vbuf, int16_t shade); +void __cdecl Lara_BaddieCollision(ITEM *lara_item, COLL_INFO *coll); +void __cdecl Lara_HandleAboveWater(ITEM *item, COLL_INFO *coll); +void __cdecl Gun_TargetInfo(const WEAPON_INFO *winfo); +void __cdecl BartoliControl(int16_t item_num); +int32_t __cdecl Lara_GetWaterDepth(int32_t x, int32_t y, int32_t z, int16_t room_num); +void __cdecl Lara_Col_Walk(ITEM *item, COLL_INFO *coll); +void __cdecl PropellerControl(int16_t item_num); +void __cdecl Camera_Combat(const ITEM *item); +void __cdecl Gun_Pistols_Control(LARA_GUN_TYPE weapon_type); +void __cdecl Text_DrawBorder(int32_t x, int32_t y, int32_t z, int32_t width, int32_t height); +BOOL __cdecl Level_LoadTexturePages(HANDLE handle); +BOOL __cdecl Level_LoadDepthQ(HANDLE handle); +int32_t __cdecl Boat_UserControl(ITEM *boat); +int32_t __cdecl Lara_MovePosition(XYZ_32 *vec, ITEM *item, ITEM *lara_item); +void __cdecl Gun_Rifle_FireHarpoon(void); +void __cdecl Output_InsertFlatRect_Sorted(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +void __cdecl ControlTwinkle(int16_t fx_num); +void __cdecl S_Audio_Sample_Init2(HWND hwnd); +void __cdecl SE_GraphicsDlgInit(HWND hwndDlg); +void __cdecl SpinningBlade(int16_t item_num); +void __cdecl LiftFloorCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *floor, int32_t *ceiling); +void __cdecl Lara_Col_Run(ITEM *item, COLL_INFO *coll); +void __cdecl Output_InsertPoly_Gouraud(int32_t vtx_count, float z, int32_t red, int32_t green, int32_t blue, int16_t poly_type); +void __cdecl Boat_DoWakeEffect(ITEM *boat); +bool __cdecl TexturePageInit(TEXPAGE_DESC *page); +void __cdecl WinstonControl(int16_t item_num); +int32_t __cdecl Boat_CheckGeton(int16_t item_num, COLL_INFO *coll); +void __cdecl Creature_Kill(ITEM *item, int32_t kill_anim, int32_t kill_state, int32_t lara_kill_state); +int32_t __cdecl Game_Cutscene_Control(int32_t nframes); +int32_t __cdecl Misc_Move3DPosTo3DPos(PHD_3DPOS *src_pos, const PHD_3DPOS *dest_pos, int32_t velocity, PHD_ANGLE ang_add); +const int16_t *__cdecl Output_CalcVerticeLight(const int16_t *obj_ptr); +int32_t __cdecl Lara_TestHangJumpUp(ITEM *item, COLL_INFO *coll); +void __cdecl HWR_DrawPolyList(void); +void __cdecl Boat_Animation(ITEM *boat, int32_t collide); +void __cdecl Flare_Draw(void); +int32_t __cdecl Room_GetWaterHeight(int32_t x, int32_t y, int32_t z, int16_t room_num); +void __cdecl Inv_RingIsOpen(RING_INFO *ring); +void __cdecl Gun_Rifle_Draw(LARA_GUN_TYPE weapon_type); +BOOL __cdecl S_FrontEndCheck(void); +void __cdecl Flame_Control(int16_t fx_num); +void __cdecl Lara_Col_Wade(ITEM *item, COLL_INFO *coll); +void __fastcall Output_GourA(int32_t y0, int32_t y1, uint8_t color_idx); // actually, __watcall, which is esoteric and rarely supported +int32_t __cdecl Level_Initialise(int32_t level_num, int32_t level_type); +void __cdecl Boat_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl Lara_SwimCollision(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_HandleSurface(ITEM *item, COLL_INFO *coll); +void __cdecl Inv_Construct(void); +void __cdecl SpikeCollision(int16_t item_num, ITEM *litem, COLL_INFO *coll); +void __cdecl DartsControl(int16_t item_num); +void __cdecl Camera_Shift(int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom); +const int16_t *__cdecl Output_InsertRoomSprite(const int16_t *obj_ptr, int32_t vtx_count); +void __cdecl Lara_Col_Climbing(ITEM *item, COLL_INFO *coll); +void __cdecl Gun_DrawFlash(LARA_GUN_TYPE weapon_type, int32_t clip); +INT_PTR __stdcall SE_SoundDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +void __cdecl Lara_State_Stop(ITEM *item, COLL_INFO *coll); +void __cdecl MovableBlock(int16_t item_num); +void __cdecl Effect_Draw(int16_t fx_num); +int32_t __cdecl Output_XGenXG(const int16_t *obj_ptr); +void __cdecl Overlay_DrawAssaultTimer(void); +int16_t __cdecl Game_Start(int32_t level_num, GAMEFLOW_LEVEL_TYPE level_type); +void __cdecl Lara_State_Run(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Output_ZedClipper(int32_t vtx_count, POINT_INFO *pts, VERTEX_INFO *vtx); +void __cdecl Output_DrawClippedPoly_Textured(int32_t vtx_count); +void __cdecl DoSnowEffect(ITEM *skidoo); +void __cdecl Lara_UseItem(GAME_OBJECT_ID object_id); +void __cdecl Inv_Ring_DoMotions(RING_INFO *ring); +void __cdecl DartEmitterControl(int16_t item_num); +void __cdecl Output_InsertTransQuad_Sorted(int32_t x, int32_t y, int32_t width, int32_t height, int32_t z); +void __cdecl WaterFall(int16_t fx_num); +void __cdecl RestoreLostBuffers(void); +void __cdecl Item_AlignPosition(XYZ_32 *vec, ITEM *src_item, ITEM *dst_item); +void __cdecl CreateScreenBuffers(void); +void __fastcall Output_TransA(int32_t y0, int32_t y1, uint8_t depth_q); // actually, __watcall, which is esoteric and rarely supported +void __cdecl Camera_UpdateCutscene(void); +void __cdecl ControlSpikeWall(int16_t item_num); +int32_t __cdecl Item_TestPosition(int16_t *bounds, ITEM *src_item, ITEM *dst_item); +int32_t __cdecl TestBlockPush(ITEM *item, int32_t block_height, uint16_t quadrant); +void __cdecl CopyBitmapPalette(RGB_888 *src_pal, BYTE *src_bitmap, int32_t bitmap_size, RGB_888 *dest_pal); +void __cdecl Lara_DeflectEdgeJump(ITEM *item, COLL_INFO *coll); +BOOL __cdecl Level_LoadSprites(HANDLE handle); +void __cdecl Output_InsertSkybox(const int16_t *obj_ptr); +void __cdecl Output_InsertLine_ZBuffered(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +void __cdecl Overlay_DrawAmmoInfo(void); +void __cdecl Inv_DoInventoryBackground(void); +int32_t __cdecl Creature_Vault(int16_t item_num, int16_t angle, int32_t vault, int32_t shift); +int32_t __cdecl Lara_TestSlide(ITEM *item, COLL_INFO *coll); +void __cdecl DrawSphereOfDoom(ITEM *item); +void __cdecl Output_InsertLine_Sorted(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +void __cdecl Inv_InsertItem(INVENTORY_ITEM *inv_item); +void __cdecl SphereOfDoomCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +int32_t __cdecl Collide_TestCollision(ITEM *item, const ITEM *lara_item); +void __cdecl HWR_InitState(void); +void __cdecl Lara_Col_Hang(ITEM *item, COLL_INFO *coll); +void __cdecl S_CalculateStaticMeshLight(int32_t x, int32_t y, int32_t z, int32_t shade_1, int32_t shade_2, ROOM *room); +BOOL __cdecl S_Input_Key(KEYMAP keymap); +void __cdecl Item_Kill(int16_t item_num); +void __cdecl Gun_InitialiseNewWeapon(void); +void __cdecl Lara_Col_UpJump(ITEM *item, COLL_INFO *coll); +void __cdecl SkidooGuns(void); +void __cdecl SE_SoundDlgUpdate(HWND hwndDlg); +HRESULT __stdcall EnumTextureFormatsCallback(LPDDSDESC lpDdsd, LPVOID lpContext); +void __cdecl SkidmanPush(ITEM *item, ITEM *lara_item, int32_t radius); +void __cdecl JellyControl(int16_t item_num); +void __cdecl S_DrawScreenBox(int32_t sx, int32_t sy, int32_t z, int32_t width, int32_t height, BYTE color_idx, const GOURAUD_OUTLINE *gour, uint16_t flags); +void __cdecl ControlCeilingSpikes(int16_t item_num); +void __cdecl InitialiseBartoli(int16_t item_num); +int32_t __cdecl S_SaveGame(const void *save_data, uint32_t save_size, int32_t slot_num); +int32_t __cdecl GameStats(int32_t level_num); +int32_t __cdecl Game_Cutscene_Start(int32_t level_num); +int32_t __cdecl Inv_RemoveItem(GAME_OBJECT_ID object_id); +int16_t __cdecl TitleSequence(void); +void __cdecl Inv_Ring_Init(RING_INFO *ring, int16_t type, INVENTORY_ITEM **list, int16_t qty, int16_t current, IMOTION_INFO *imo); +void __cdecl ScreenshotPCX(void); +bool __cdecl S_Audio_Sample_Load(int32_t sample_id, LPWAVEFORMATEX format, const void *data, int32_t data_size); +void __cdecl Lara_State_Compress(ITEM *item, COLL_INFO *coll); +void __cdecl CopterControl(int16_t item_num); +void __cdecl Ember_Control(int16_t fx_num); +bool __cdecl WinVidGoWindowed(int32_t width, int32_t height, DISPLAY_MODE *dispMode); +void __cdecl Inv_InitColors(void); +void __cdecl Pendulum(int16_t item_num); +void __cdecl KillerStatueControl(int16_t item_num); +int32_t __cdecl Text_GetWidth(TEXTSTRING *string); +int32_t __cdecl Diver_GetWaterSurface(int32_t x, int32_t y, int32_t z, int16_t room_num); +void __cdecl LOT_InitialiseSlot(int16_t item_num, int32_t slot); +int32_t __cdecl SkidooCheckGetOffOK(int32_t direction); +INT_PTR __stdcall SE_ControlsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +TEXTSTRING *__cdecl Text_Create(int32_t x, int32_t y, int32_t z, const char *text); +void __cdecl FallingBlock(int16_t item_num); +void __cdecl FmvBackToGame(void); +void __cdecl S_DisplayPicture(const char *file_name, BOOL is_title); +void __cdecl Gun_Rifle_Undraw(LARA_GUN_TYPE weapon_type); +bool __cdecl WinVidGetMinMaxInfo(LPMINMAXINFO info); +void __cdecl IcicleControl(int16_t item_num); +void __cdecl Overlay_DrawPickups(bool pickup_state); +void __cdecl Matrix_InterpolateArm(void); +void __cdecl SwitchCollision2(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl Lara_Col_Back(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_InitialiseMeshes(int32_t level_num); +BOOL __cdecl DecompPCX(const uint8_t *pcx, size_t pcx_size, LPBYTE pic, RGB_888 *pal); +int32_t __cdecl Boat_TestWaterHeight(ITEM *item, int32_t z_off, int32_t x_off, XYZ_32 *pos); +int32_t __cdecl Creature_CheckBaddieOverlap(int16_t item_num); +void __cdecl Flare_DrawInAir(ITEM *item); +void __cdecl SkidooCollision(int16_t item_num, ITEM *litem, COLL_INFO *coll); +void __cdecl Output_DrawPoly_Gouraud(int32_t vtx_count, int32_t red, int32_t green, int32_t blue); +int32_t __cdecl Lara_TestHangOnClimbWall(ITEM *item, COLL_INFO *coll); +void __cdecl Gun_Rifle_Control(LARA_GUN_TYPE weapon_type); +int32_t __cdecl AddTexturePage8(int32_t width, int32_t height, const uint8_t *page_buf, int32_t pal_idx); +void __cdecl Output_InsertPolygons(const int16_t *obj_ptr, int32_t clip); +void __cdecl ControlBubble1(int16_t fx_num); +void __cdecl Lara_SurfaceCollision(ITEM *item, COLL_INFO *coll); +DWORD __cdecl WinInReadJoystick(int32_t *x, int32_t *y); +int32_t __cdecl Box_StalkBox(const ITEM *item, const ITEM *enemy, int16_t box_num); +void __cdecl Gun_Rifle_FireShotgun(void); +void __cdecl SphereOfDoom(int16_t item_num); +void __cdecl Gun_Rifle_FireGrenade(void); +void __cdecl Lara_DoClimbLeftRight(ITEM *item, COLL_INFO *coll, int32_t result, int32_t shift); +void __cdecl Camera_Chase(const ITEM *item); +void __cdecl Lara_State_SurfTread(ITEM *item, COLL_INFO *coll); +void __cdecl Output_InsertRoom(const int16_t *obj_ptr, int32_t is_outside); +void __cdecl Output_CalculateObjectLighting(const ITEM *item, const FRAME_INFO *frame); +BOOL __stdcall DInputEnumDevicesCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef); +void __cdecl CalculateWibbleTable(void); +void __cdecl Camera_RefreshFromTrigger(int16_t type, const int16_t *data); +int32_t __cdecl Lara_TestWaterStepOut(ITEM *item, COLL_INFO *coll); +int32_t __cdecl S_Audio_Sample_Play(int32_t sample_id, int32_t volume, int32_t pitch, int32_t pan, int32_t flags); +void __cdecl Lara_TouchLava(ITEM *item); +void __cdecl RenderFinish(bool need_to_clear_textures); +bool __cdecl DInputJoystickCreate(void); +void __cdecl S_InitialisePolyList(BOOL clear_back_buffer); +void __cdecl Effect_Kill(int16_t fx_num); +void __cdecl BigBowlControl(int16_t item_num); +void __cdecl Lara_SlideSlope(ITEM *item, COLL_INFO *coll); +bool __cdecl WinVidSpinMessageLoop(bool needWait); +BOOL __stdcall S_Audio_Sample_DSoundEnumCallback(LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context); +int32_t __cdecl Lara_TestWall(ITEM *item, int32_t front, int32_t right, int32_t down); +int32_t __cdecl SkidooCheckGeton(int16_t item_num, COLL_INFO *coll); +void __cdecl FallingCeiling(int16_t item_num); +BOOL __cdecl SelectDrive(void); +void __cdecl ControlHotLiquid(int16_t fx_num); +void __cdecl CutscenePlayer_Control(int16_t item_num); +void __cdecl SmashIceControl(int16_t item_num); +void __cdecl DrawQuad(float sx, float sy, float width, float height, D3DCOLOR color); +INT_PTR __stdcall SE_AdvancedDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +void __cdecl EarthQuake(int16_t item_num); +void __cdecl ModifyStartInfo(int32_t level_num); +void __cdecl SpringBoardControl(int16_t item_num); +void __cdecl S_LoadSettings(void); +int32_t __cdecl LOS_ClipTarget(const GAME_VECTOR *start, GAME_VECTOR *target, const SECTOR *sector); +void __cdecl Room_TriggerMusicTrackImpl(int16_t value, int16_t flags, int16_t type); +void __cdecl CreateBartoliLight(int16_t item_num); +void __cdecl DragonBones(int16_t item_num); +void __cdecl MiniCopterControl(int16_t item_num); +void __cdecl Camera_Clip(int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom); +void __cdecl Lara_Col_ForwardJump(ITEM *item, COLL_INFO *coll); +bool __cdecl SE_GraphicsTestStart(void); +int16_t __cdecl Creature_Turn(ITEM *item, int16_t maximum_turn); +void __cdecl WinVidSetDisplayAdapter(DISPLAY_ADAPTER *disp_adapter); +void __cdecl LiftControl(int16_t item_num); +void __cdecl Output_InsertTransQuad(int32_t x, int32_t y, int32_t width, int32_t height, int32_t z); +void __cdecl Lara_State_ForwardJump(ITEM *item, COLL_INFO *coll); +void __cdecl Flare_DoInHand(int32_t flare_age); +void __cdecl CreateWindowPalette(void); +int32_t __cdecl Output_XGenX(const int16_t *obj_ptr); +void __cdecl GeneralControl(int16_t item_num); +DWORD __cdecl EncodeLinePCX(BYTE *src, DWORD width, BYTE *dst); +void __cdecl UpdateFrame(bool need_run_message_loop, LPRECT rect); +void __cdecl Lara_TestWaterDepth(ITEM *item, COLL_INFO *coll); +void __cdecl GetCarriedItems(void); +void __cdecl Demo_LoadLaraPos(void); +void __cdecl Output_InsertFlatRect(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +int32_t __cdecl Item_TestBoundsCollide(const ITEM *src_item, const ITEM *dst_item, int32_t radius); +void __cdecl Lara_State_Wade(ITEM *item, COLL_INFO *coll); +void __cdecl DInputKeyboardCreate(void); +void __cdecl Room_GetNearbyRooms(int32_t x, int32_t y, int32_t z, int32_t r, int32_t h, int16_t room_num); +void __cdecl DoorControl(int16_t item_num); +BYTE __cdecl FindNearestPaletteEntry(RGB_888 *palette, int32_t red, int32_t green, int32_t blue, bool ignore_sys_palette); +void __cdecl Output_RotateLight(int16_t pitch, int16_t yaw); +bool __cdecl SE_SoundTestStart(void); +sub_4449D0 +bool __cdecl WinVidCreateGameWindow(void); +BOOL __cdecl GF_ReadStringTable(DWORD count, char **string_table, char **string_buf, LPDWORD buf_size, HANDLE handle); +void __cdecl Output_QuickSort(int32_t left, int32_t right); +void __cdecl EmberEmitter_Control(int16_t item_num); +void __cdecl SmashWindow(int16_t item_num); +bool __cdecl S_Audio_Sample_DSoundBufferTest(void); +BOOL __cdecl S_ReloadLevelGraphics(BOOL reload_palettes, BOOL reload_tex_pages); +void __thiscall SE_LoadBitmapResource(BITMAP_RESOURCE *bmpRsrc, LPCTSTR lpName); +void __cdecl Splash(ITEM *item); +int32_t __cdecl Inv_GetItemOption(GAME_OBJECT_ID object_id); +void __cdecl Gun_AimWeapon(WEAPON_INFO *winfo, LARA_ARM *arm); +void __cdecl Requester_ChangeItem(REQUEST_INFO *req, int32_t item, const char *text1, uint32_t flags1, const char *text2, uint32_t flags2); +void __cdecl Option_Compass(INVENTORY_ITEM *item); +void __cdecl AnimateTextures(int32_t ticks); +void __cdecl DeathSlideCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl Lara_Col_StepRight(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_TestClimbStance(ITEM *item, COLL_INFO *coll); +void __cdecl SE_SoundDlgInit(HWND hwndDlg); +void __cdecl ControlGongBonger(int16_t item_num); +int32_t __cdecl SwitchTrigger(int16_t item_num, int16_t timer); +void __cdecl TeethTrap(int16_t item_num); +HRESULT __stdcall Enum3DDevicesCallback(GUID *lpGuid, LPTSTR lpDeviceDescription, LPTSTR lpDeviceName, LPD3DDEVICEDESC lpD3DHWDeviceDesc, LPD3DDEVICEDESC lpD3DHELDeviceDesc, LPVOID lpContext); +void __cdecl SE_ControlsDlgInit(HWND hwndDlg); +void __cdecl Lara_State_Extra_PullDagger(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_TestClimbPos(ITEM *item, int32_t front, int32_t right, int32_t origin, int32_t height, int32_t *shift); +bool __cdecl WinVidCheckGameWindowPalette(HWND hWnd); +void __cdecl CreateZBuffer(void); +int32_t __cdecl Lara_LandedBad(ITEM *item, COLL_INFO *coll); +void __cdecl Room_AlterFloorHeight(ITEM *item, int32_t height); +void __cdecl Lara_CheatGetStuff(void); +int16_t __cdecl GunHit(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +int32_t __cdecl BGND_AddTexture(int32_t tile_idx, BYTE *bitmap, int32_t pal_index, RGB_888 *bmp_pal); +bool __cdecl D3DSetViewport(void); +void __cdecl Creature_Die(int16_t item_num, int32_t explode); +void __cdecl LOT_CreateZone(ITEM *item); +void __cdecl Output_InsertLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +void __cdecl Room_DrawSingleRoomGeometry(int16_t room_num); +void __cdecl Gondola_Control(int16_t item_num); +void __cdecl Room_FlipMap(void); +void __cdecl Lara_Col_FastBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Roll2(ITEM *item, COLL_INFO *coll); +void __cdecl AssaultFinished(ITEM *item); +int32_t __cdecl TestHeight(ITEM *item, int32_t z_off, int32_t x_off, XYZ_32 *pos); +int32_t __cdecl Lara_TestHangSwingIn(ITEM *item, PHD_ANGLE angle); +int32_t __cdecl OnDrawBridge(ITEM *item, int32_t x, int32_t y); +void __cdecl S_AnimateTextures(int32_t ticks); +void __cdecl Camera_Initialise(void); +bool __cdecl WinVidGetDisplayAdapters(void); +void __cdecl ControlWaterSprite(int16_t fx_num); +DWORD __cdecl CompPCX(BYTE *bitmap, DWORD width, DWORD height, RGB_888 *palette, BYTE **pcx_data); +void __cdecl SyncSurfacePalettes(void *src_data, int32_t width, int32_t height, int32_t src_pitch, RGB_888 *src_palette, void *dst_data, int32_t dst_pitch, RGB_888 *dst_palette, bool preserve_sys_palette); +int32_t __cdecl Lara_TestEdgeCatch(ITEM *item, COLL_INFO *coll, int32_t *edge); +void __cdecl Requester_AddItem(REQUEST_INFO *req, const char *text1, uint32_t flags1, const char *text2, uint32_t flags2); +void __cdecl SpiderLeap(int16_t item_num, int16_t angle); +bool __cdecl Matrix_TranslateRel(int32_t x, int32_t y, int32_t z); +int32_t __cdecl Item_GetAnimChange(ITEM *item, const ANIM *anim); +int16_t __cdecl DragonFire(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +void __cdecl Overlay_DrawHealthBar(bool flash_state); +void __cdecl Lara_SlideEdgeJump(ITEM *item, COLL_INFO *coll); +void __cdecl S_CopyScreenToBuffer(void); +void __cdecl Matrix_RotX(int16_t rx); +const int16_t *__cdecl Output_InsertObjectGT3_ZBuffered(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __cdecl Requester_SetHeading(REQUEST_INFO *req, char *text1, uint32_t flags1, char *text2, uint32_t flags2); +int32_t __cdecl Lara_CheckForLetGo(ITEM *item, COLL_INFO *coll); +void __cdecl Gun_FindTargetPoint(const ITEM *item, GAME_VECTOR *target); +bool __cdecl GuidStringToBinary(LPCTSTR lpString, GUID *guid); +void __cdecl Matrix_RotY(int16_t ry); +void __cdecl Matrix_RotZ(int16_t rz); +void __cdecl Creature_Float(int16_t item_num); +void __cdecl XianDamage(ITEM *item, CREATURE *xian, int32_t damage); +void __cdecl Output_InsertSprite(int32_t z, int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t sprite_idx, int16_t shade); +int32_t __cdecl Box_ValidBox(const ITEM *item, int16_t zone_num, int16_t box_num); +void __cdecl ShootAtLara(FX *fx); +int16_t __cdecl GunMiss(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +bool __cdecl ShowDDrawGameWindow(bool active); +void __cdecl Lara_State_Extra_StartHouse(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Box_EscapeBox(const ITEM *item, const ITEM *enemy, int16_t box_num); +void __cdecl DoLotsOfBlood(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE direction, int16_t room_num, int32_t num); +void __cdecl Requester_Shutdown(REQUEST_INFO *req); +void __cdecl Lara_State_Extra_FinalAnim(ITEM *item, COLL_INFO *coll); +int32_t __cdecl OnTrapDoor(ITEM *item, int32_t x, int32_t z); +bool __cdecl LoadTexturePage(int32_t page_idx, bool reset); +void __cdecl Output_AlterFOV(int16_t fov); +void __cdecl Matrix_RotYXZsuperpack(const int16_t **pprot, int32_t skip); +void __cdecl CreateRenderBuffer(void); +int32_t __cdecl LOS_Check(const GAME_VECTOR *start, GAME_VECTOR *target); +const int16_t *__cdecl Output_InsertObjectGT3_Sorted(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __cdecl setup_screen_size(void); +void __cdecl Output_DrawScreenSprite(int32_t sx, int32_t sy, int32_t sz, int32_t scale_h, int32_t scale_v, int16_t sprite_idx, int16_t shade, uint16_t flags); +int32_t __cdecl Flare_DoLight(XYZ_32 *pos, int32_t flare_age); +void __cdecl InitialiseFinalLevel(void); +void __cdecl CreateClipper(void); +void __cdecl Item_NewRoom(int16_t item_num, int16_t room_num); +bool __cdecl S_Audio_Sample_Init(void); +void __cdecl Lara_CollideStop(ITEM *item, COLL_INFO *coll); +void __cdecl swap_meshes_with_meshswap3(ITEM *item); +void __cdecl Level_LoadDemoExternal(LPCTSTR level_name); +int32_t __cdecl Game_Loop(bool demo_mode); +void __cdecl Camera_Fixed(void); +int16_t __cdecl Room_GetTiltType(const SECTOR *sector, int32_t x, int32_t y, int32_t z); +void __cdecl Lara_Col_FastFall(ITEM *item, COLL_INFO *coll); +void __cdecl WarriorSparkleTrail(ITEM *item); +void __cdecl CreateBackBuffer(void); +bool __cdecl CreateTexturePageSurface(TEXPAGE_DESC *desc); +void __cdecl Overlay_DrawAirBar(bool flash_state); +void __cdecl AdjustTextureUVs(bool reset_uv_add); +void __cdecl Output_DrawScreenSprite2D(int32_t sx, int32_t sy, int32_t sz, int32_t scale_h, int32_t scale_v, int16_t sprite_idx, int16_t shade, uint16_t flags); +void __cdecl Lara_Control_Cutscene(int16_t item_num); +void __cdecl Room_RemoveFlipItems(ROOM *r); +void __cdecl floor_shake_effect(ITEM *item); +void __cdecl Lara_State_DeathSlide(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Compress( ITEM *item, COLL_INFO *coll ); +void __cdecl Gun_HitTarget(ITEM *item, GAME_VECTOR *hitpos, int32_t damage); +void __cdecl WindowControl(int16_t item_num); +int32_t __cdecl S_LoadGame(void *save_data, uint32_t save_size, int32_t slot_num); +void __cdecl Option_Controls_UpdateText(void); +void __cdecl Output_SetNearZ(int32_t near_z); +void __cdecl Box_TargetBox(LOT_INFO *lot, int16_t box_num); +void __cdecl Lara_Col_TurnRight(ITEM *item, COLL_INFO *coll); +bool __cdecl GetRegistryStringValue(LPCTSTR lpValueName, LPTSTR value, DWORD maxSize, LPCTSTR defaultValue); +void __cdecl CreatePrimarySurface(void); +void __cdecl Misc_InitCinematicRooms(void); +void __cdecl Effect_NewRoom(int16_t fx_num, int16_t room_num); +void __cdecl SE_AdvancedDlgUpdate(HWND hwndDlg); +int32_t __cdecl Item_GetFrames(const ITEM *item, FRAME_INFO *frmptr[], int32_t *rate); +void __cdecl Music_SetVolume(int32_t volume); +const int16_t *__cdecl Output_InsertObjectGT4_ZBuffered(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +void __cdecl Lara_State_Extra_SharkKill(ITEM *item, COLL_INFO *coll); +void __cdecl BladeControl(int16_t item_num); +bool __cdecl GetRegistryGuidValue(LPCTSTR lpValueName, GUID *value, GUID *defaultValue); +void __cdecl Lara_Col_Reach(ITEM *item, COLL_INFO *coll); +int32_t __cdecl GetCollisionAnim(ITEM *skidoo, XYZ_32 *moved); +void __cdecl FlameEmitter_Control(int16_t item_num); +bool __cdecl WinVidCopyBitmapToBuffer(LPDDS surface, const BYTE *bitmap); +void __cdecl CutscenePlayer1_Initialise(int16_t item_num); +void __cdecl FloodFX(ITEM *item); +void __cdecl HWR_LoadTexturePages(int32_t pages_count, void *pages_buf, RGB_888 *palette); +void __cdecl DetonatorControl(int16_t item_num); +const int16_t *__cdecl Output_InsertObjectGT4_Sorted(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +int32_t __cdecl Lara_IsNearItem(PHD_3DPOS *pos, int32_t distance); +void __cdecl ControlSnowSprite(int16_t fx_num); +void __cdecl Text_AddBackground(TEXTSTRING *string, int16_t x_size, int16_t y_size, int16_t x_off, int16_t y_off, int16_t z_off, int16_t color, uint16_t *gour_ptr, uint16_t flags); +sub_444B20 +bool __cdecl WinVidGetDisplayMode(DISPLAY_MODE *disp_mode); +int32_t __cdecl Box_BadFloor(int32_t x, int32_t y, int32_t z, int32_t box_height, int32_t next_height, int16_t room_num, LOT_INFO *lot); +void __cdecl Lara_State_Walk(ITEM *item, COLL_INFO *coll); +D3DCOLOR __cdecl BGND_CenterLighting(int32_t x, int32_t y, int32_t width, int32_t height); +void __cdecl Item_RemoveActive(int16_t item_num); +void __cdecl Lara_State_TurnRight(ITEM *item, COLL_INFO *coll); +BOOL __cdecl Level_LoadDemo(HANDLE handle); +INT_PTR __stdcall SE_OptionsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +void __cdecl Bird_Initialise(int16_t item_num); +void __cdecl Inv_SelectMeshes(INVENTORY_ITEM *inv_item); +void __cdecl Lara_ResetLook(void); +void __cdecl Lara_State_TurnLeft(ITEM *item, COLL_INFO *coll); +void __cdecl S_OutputPolyList(void); +bool __cdecl WinVidGoFullScreen(DISPLAY_MODE *disp_mode); +bool __cdecl IntroFMV(const char *file_name1, const char *file_name2); +void __cdecl DyingMonk(int16_t item_num); +bool __cdecl HideDDrawGameWindow(void); +int32_t __cdecl CreateTexturePalette(const RGB_888 *pal); +void __cdecl Lara_SwimTurn(ITEM *item); +void __cdecl Sound_EndScene(void); +void __cdecl HookControl(int16_t item_num); +void __cdecl Output_DrawPickup(int32_t sx, int32_t sy, int32_t scale, int16_t sprite_idx, int16_t shade); +void __cdecl Lara_State_Tread(ITEM *item, COLL_INFO *coll); +bool __cdecl Creature_CanTargetEnemy(const ITEM *item, const AI_INFO *info); +void __cdecl Lara_State_Back(ITEM *item, COLL_INFO *coll); +bool __cdecl Music_PlaySynced(int32_t track_id); +void __cdecl Object_DrawSpriteItem(ITEM *item); +void __cdecl Lara_Col_Fallback(ITEM *item, COLL_INFO *coll); +void __cdecl ControlBlood1(int16_t fx_num); +void __cdecl Requester_Init(REQUEST_INFO *req); +void __cdecl HWR_EnableZBuffer(bool z_write_enable, bool z_enable); +int32_t __cdecl Inv_AnimateInventoryItem(INVENTORY_ITEM *inv_item); +void __cdecl Inv_RingIsNotOpen(RING_INFO *ring); +int32_t __cdecl AddAssaultTime(uint32_t time); +void __cdecl Lara_Col_Stop(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Roll(ITEM *item, COLL_INFO *coll); +void __cdecl SkidmanCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +int32_t __cdecl Room_FindByPos(int32_t x, int32_t y, int32_t z); +void __cdecl ControlExplosion1(int16_t fx_num); +int32_t __cdecl Lara_DeflectEdge(ITEM *item, COLL_INFO *coll); +void __cdecl InitialiseSkidman(int16_t item_num); +bool __cdecl WinInputInit(void); +void __cdecl Random_Seed(void); +void __cdecl Option_DoInventory(INVENTORY_ITEM *item); +void __cdecl BellControl(int16_t item_num); +void __cdecl Option_Controls_FlashConflicts(void); +void __cdecl S_SyncPictureBufferPalette(void); +void __cdecl S_SaveSettings(void); +BOUNDS_16 *__cdecl Item_GetBoundsAccurate(const ITEM *item); +int32_t __cdecl Item_GlobalReplace(GAME_OBJECT_ID src_object_id, GAME_OBJECT_ID dst_object_id); +void __cdecl Lara_State_StepRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_StepLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Gun_Pistols_Draw(LARA_GUN_TYPE weapon_type); +void __cdecl InitialiseDyingMonk(int16_t item_num); +void __cdecl SkidooExplode(ITEM *skidoo); +int32_t __cdecl WinGameStart(void); +void __cdecl Gun_Rifle_FireM16(bool running); +void __cdecl Matrix_TranslateAbs(int32_t x, int32_t y, int32_t z); +int16_t __cdecl Diver_Harpoon(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE y_rot, int16_t room_num); +void __cdecl Overlay_DisplayModeInfo(char* string); +void __cdecl Lara_TakeHit(ITEM *lara_item, COLL_INFO *coll); +void __cdecl Lara_Col_FastDive(ITEM *item, COLL_INFO *coll); +BOOL __cdecl Level_LoadCinematic(HANDLE handle); +void __cdecl Math_GetVectorAngles(int32_t x, int32_t y, int32_t z, int16_t *dest); +void __cdecl Creature_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl LaraBubbles(ITEM *item); +void __cdecl Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +int32_t __cdecl Inv_RequestItem(GAME_OBJECT_ID object_id); +bool __cdecl Music_Init(void); +int16_t __cdecl Knife(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +void __cdecl DrawHair(void); +void __cdecl Lara_State_Glide(ITEM *item, COLL_INFO *coll); +bool __cdecl GetRegistryBoolValue(LPCTSTR lpValueName, bool *pValue, bool defaultValue); +int16_t __cdecl Lara_FloorFront(ITEM *item, PHD_ANGLE ang, int32_t dist); +void __cdecl Lara_State_ClimbStance(ITEM *item, COLL_INFO *coll); +void __cdecl InitialiseWindow(int16_t item_num); +void __cdecl InitialiseHair(void); +void __cdecl Lara_Col_Jumper(ITEM *item, COLL_INFO *coll); +void __cdecl GameWindowCalculateSizeFromClient(int32_t *width, int32_t *height); +void __cdecl Matrix_LookAt(int32_t xsrc, int32_t ysrc, int32_t zsrc, int32_t xtar, int32_t ytar, int32_t ztar, int16_t roll); +void __cdecl Richochet(GAME_VECTOR *pos); +void __cdecl Lara_LookLeftRight(void); +void __cdecl ControlSplash1(int16_t fx_num); +void __thiscall WinVidGetColorBitMasks(COLOR_BIT_MASKS *bm, LPDDPIXELFORMAT pixel_format); +BOOL __cdecl Level_LoadPalettes(HANDLE handle); +bool __cdecl GetRegistryBinaryValue(LPCTSTR lpValueName, LPBYTE value, DWORD valueSize, LPBYTE defaultValue); +void __cdecl Gun_Rifle_Ready(LARA_GUN_TYPE weapon_type); +void __cdecl Lara_State_SurfSwim(ITEM *item, COLL_INFO *coll); +void __cdecl CreatePictureBuffer(void); +int32_t __cdecl Box_UpdateLOT(LOT_INFO *lot, int32_t expansion); +void __cdecl Item_RemoveDrawn(int16_t item_num); +void __cdecl Music_Play(int16_t track_id, bool is_looped); +BOOL __cdecl UT_CenterWindow(HWND hWnd); +void __cdecl UT_MemBlt(BYTE *dstBuf, DWORD dstX, DWORD dstY, DWORD width, DWORD height, DWORD dstPitch, BYTE *srcBuf, DWORD srcX, DWORD srcY, DWORD srcPitch); +int32_t __cdecl Output_VisibleZClip(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2); +void __cdecl Overlay_AddDisplayPickup(GAME_OBJECT_ID object_id); +int16_t __cdecl Effect_Create(int16_t room_num); +void *__cdecl game_malloc(size_t alloc_size, GAME_BUFFER buf_index); +void __cdecl Output_SetFarZ(int32_t far_z); +void __cdecl Lara_State_Swim(ITEM *item, COLL_INFO *coll); +void __cdecl DrawUnclippedItem(ITEM *item); +bool __cdecl PlayFMV(const char *file_name); +void __cdecl Direct3DRelease(void); +void __cdecl GameWindowCalculateSizeFromClientByZero(int32_t *width, int32_t *height); +bool __cdecl WinVidRegisterGameWindowClass(void); +int32_t __cdecl CreateTexturePage(int32_t width, int32_t height, bool alpha); +void __cdecl Lara_Col_SwanDive(ITEM *item, COLL_INFO *coll); +void __cdecl Sound_UpdateEffects(void); +void __cdecl ControlClockChimes(int16_t item_num); +void __cdecl Lara_Col_Death(ITEM *item, COLL_INFO *coll); +void __cdecl InitialiseObjects(void); +sub_4470F0 +sub_447AC0 +void __cdecl SE_ControlsDlgUpdate(HWND hwndDlg); +void __cdecl Object_Collision_Trap(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl ControlAlarmSound(int16_t item_num); +int32_t __cdecl Lara_HitCeiling(ITEM *item, COLL_INFO *coll); +void __cdecl UpdateGameResolution(void); +int32_t __cdecl GF_DoLevelSequence(int32_t level, GAMEFLOW_LEVEL_TYPE type); +void __cdecl Lara_LookUpDown(void); +int16_t __cdecl GunShot(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +void __cdecl CreateBubble(PHD_3DPOS *pos, int16_t room_num); +int32_t __cdecl GameInit(bool skip_cd_init); +void __thiscall SE_DrawBitmap(BITMAP_RESOURCE *bmpRsrc, HDC hdc, int32_t x, int32_t y); +void __cdecl swap_meshes_with_meshswap1(ITEM *item); +void __cdecl swap_meshes_with_meshswap2(ITEM *item); +void __cdecl Inv_Ring_MotionInit(RING_INFO *ring, int16_t frames, int16_t status, int16_t status_target); +void __cdecl Inv_Ring_MotionItemDeselect(RING_INFO *ring, INVENTORY_ITEM *inv_item); +void __cdecl InitialiseStartInfo(void); +void __cdecl IncreaseScreenSize(void); +void __cdecl DecreaseScreenSize(void); +void __cdecl InitialiseGameFlags(void); +DWORD __cdecl CalculateCompatibleColor(COLOR_BIT_MASKS *mask, int32_t red, int32_t green, int32_t blue, int32_t alpha); +int32_t __cdecl Music_GetFrames(void); +bool __cdecl SE_RegisterSetupWindowClass(void); +void __fastcall Output_FlatA(int32_t y0, int32_t y1, uint8_t color_idx); // actually, __watcall, which is esoteric and rarely supported +void __cdecl Inv_Ring_GetView(RING_INFO *ring, PHD_3DPOS *viewer); +void __cdecl Lara_State_SurfLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SurfRight(ITEM *item, COLL_INFO *coll); +BOOL __cdecl Level_LoadCameras(HANDLE handle); +BOOL __cdecl Level_LoadSoundEffects(HANDLE handle); +void __cdecl UT_InitAccurateTimer(void); +void __cdecl Item_Translate(ITEM *item, int32_t x, int32_t y, int32_t z); +void __cdecl BGND_DrawInGameBlack(void); +sub_444AB0 +void __cdecl Lara_State_SurfBack(ITEM *item, COLL_INFO *coll); +void __cdecl Sound_StopEffect(int32_t sample_id); +bool __cdecl GetRegistryDwordValue(LPCTSTR lpValueName, DWORD *pValue, DWORD defaultValue); +void __cdecl Item_UpdateRoom(ITEM *item, int32_t height); +void __cdecl ControlBirdTweeter(int16_t item_num); +void __cdecl Inv_Ring_MotionItemSelect(RING_INFO *ring, INVENTORY_ITEM *inv_item); +void __cdecl Lara_Col_ClimbLeft(ITEM *item, COLL_INFO *coll); +void __cdecl BaddieBiteEffect(ITEM *item, BITE *bite); +LRESULT __stdcall SE_NewPropSheetWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +int16_t __cdecl Creature_Effect(ITEM *item, BITE *bite, int16_t (*__cdecl spawn)(int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, int16_t room_num)); +void __cdecl Room_AddFlipItems(ROOM *r); +void __cdecl Gun_Pistols_Ready(LARA_GUN_TYPE weapon_type); +BOOL __cdecl S_InitialiseSystem(void); +bool __cdecl GetRegistryFloatValue(LPCTSTR lpValueName, double *value, double defaultValue); +void __cdecl Lara_Col_UWDeath(ITEM *item, COLL_INFO *coll); +void __cdecl Item_AddActive(int16_t item_num); +void __cdecl WinInReadKeyboard(LPVOID lpInputData); +int32_t __cdecl Demo_Control(int32_t level_num); +int16_t __cdecl DoBloodSplat(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE direction, int16_t room_num); +void __cdecl DoChimeSound(ITEM *item); +void __cdecl Lara_Col_Splat(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_ClimbRight(ITEM *item, COLL_INFO *coll); +void __cdecl HWR_ResetZBuffer(void); +void __cdecl InitialiseXianLord(int16_t item_num); +void __cdecl Item_ClearKilled(void); +void __cdecl Lara_State_Hang(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_PickupFlare(ITEM *item, COLL_INFO *coll); +DWORD __cdecl Sync(void); +void __cdecl Lara_State_FastBack(ITEM *item, COLL_INFO *coll); +void __cdecl S_Wait(int32_t timeout, BOOL input_check); +int32_t __cdecl SE_GraphicsTest(void); +int32_t __cdecl SE_SoundTest(void); +void __thiscall SE_ChangeBitmapPalette(BITMAP_RESOURCE *bmpRsrc, HWND hWnd); +void __cdecl Requester_Item_RightAlign(REQUEST_INFO *req, TEXTSTRING *txt); +void __cdecl Room_GetNewRoom(int32_t x, int32_t y, int32_t z, int16_t room_num); +void __cdecl Object_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +int32_t __cdecl Item_IsTriggerActive(ITEM *item); +BOOL __cdecl Level_LoadAnimatedTextures(HANDLE handle); +void __cdecl Shell_Cleanup(void); +void __cdecl Requester_Item_LeftAlign(REQUEST_INFO *req, TEXTSTRING *txt); +void __cdecl Lara_State_FastDive(ITEM *item, COLL_INFO *coll); +int32_t __cdecl DoDynamics(int32_t height, int32_t fall_speed, int32_t *y); +bool __cdecl DDrawCreate(LPGUID lpGUID); +void __cdecl Lara_State_UWDeath(ITEM *item, COLL_INFO *coll); +void __cdecl LOT_InitialiseArray(void); +bool __cdecl WinVidGetDisplayModes(void); +void __cdecl Item_InitialiseArray(int32_t num_items); +void __cdecl Lara_State_Extra_DinoKill(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Lara_Fallen(ITEM *item, COLL_INFO *coll); +void __cdecl Gun_SmashItem(int16_t item_num, LARA_GUN_TYPE weapon_type); +void __cdecl S_SetupBelowWater(BOOL underwater); +void __cdecl Music_Shutdown(void); +bool __cdecl TIME_Init(void); +LPCTSTR __cdecl GuidBinaryToString(GUID *guid); +void __cdecl Creature_Underwater(ITEM *item, int32_t depth); +const SECTOR *__cdecl Camera_GoodPosition(int32_t x, int32_t y, int32_t z, int16_t room_num); +void __cdecl InitialiseCult3(int16_t item_num); +void __cdecl Gun_Pistols_SetArmInfo(LARA_ARM *arm, int32_t frame); +void __cdecl LOT_DisableBaddieAI(int16_t item_num); +void __cdecl TrapDoorControl(int16_t item_num); +void __cdecl Creature_Neck(ITEM *item, int16_t required); +int16_t __cdecl Room_GetDoor(const SECTOR *sector); +void __cdecl Gun_Pistols_DrawMeshes(LARA_GUN_TYPE weapon_type); +int32_t __cdecl TestBlockMovable(ITEM *item, int32_t block_height); +void __cdecl ControlGunShot(int16_t fx_num); +JOYSTICK_NODE *__cdecl GetJoystick(GUID *lpGuid); +bool __cdecl OpenGameRegistryKey(LPCTSTR key); +double __cdecl UT_Microseconds(void); +void __cdecl Lara_State_Extra_StartAnim(ITEM *item, COLL_INFO *coll); +void __cdecl BGND_Free(void); +bool __cdecl WinVidClearBuffer(LPDDS surface, LPRECT rect, DWORD fill_color); +void __cdecl WinVidExitMessage(void); +void __cdecl Lara_State_Extra_Breath(ITEM *item, COLL_INFO *coll); +void __cdecl WriteSG(void *pointer, int32_t size); +void __cdecl GetSavedGamesList(REQUEST_INFO *req); +int32_t __cdecl Boat_DoDynamics(int32_t height, int32_t fall_speed, int32_t *y); +void __cdecl Lara_State_FastTurn(ITEM *item, COLL_INFO *coll); +void __cdecl Flare_SetArm(int32_t frame); +void __cdecl DartEffectControl(int16_t fx_num); +void __cdecl SE_OptionsStrCat(LPTSTR *dstString, bool isEnabled, bool *isNext, LPCTSTR srcString); +void __cdecl Lara_State_BackJump(ITEM *item, COLL_INFO *coll); +DISPLAY_MODE *__thiscall InsertDisplayModeInListTail(DISPLAY_MODE_LIST *modeList); +void __cdecl HWR_EnableColorKey(bool state); +void __cdecl HWR_FreeTexturePages(void); +LONG __cdecl SetRegistryStringValue(LPCTSTR lpValueName, LPCTSTR value, int32_t length); +void __cdecl Creature_Head(ITEM *item, int16_t required); +void __cdecl LOT_ClearLOT(LOT_INFO *lot); +void __cdecl Lara_CatchFire(void); +BOOL __cdecl ReadFileSync(HANDLE handle, LPVOID lpBuffer, DWORD nBytesToRead, LPDWORD lpnBytesRead, LPOVERLAPPED lpOverlapped); +void __cdecl AddDynamicLight(int32_t x, int32_t y, int32_t z, int32_t intensity, int32_t falloff); +int32_t __cdecl CalculateWindowWidth(int32_t width, int32_t height); +DISPLAY_ADAPTER_NODE *__cdecl WinVidGetDisplayAdapter(GUID *lpGuid); +DISPLAY_MODE *__thiscall InsertDisplayModeInListHead(DISPLAY_MODE_LIST *modeList); +const SOUND_ADAPTER_NODE *__cdecl S_Audio_Sample_GetAdapter(GUID *guid); +void __cdecl WinStopFMV(bool is_playback); +int32_t __fastcall Math_Atan(int32_t x, int32_t y); +int32_t __cdecl Creature_Activate(int16_t item_num); +void __cdecl Matrix_TranslateRel_ID(int32_t x, int32_t y, int32_t z, int32_t x2, int32_t y2, int32_t z2); +void __cdecl Lara_State_Extra_YetiKill(ITEM *item, COLL_INFO *coll); +void __cdecl InitialiseKillerStatue(int16_t item_num); +HRESULT __cdecl DDrawSurfaceRestoreLost(LPDDS surface1, LPDDS surface2, bool blank); +DWORD __cdecl GetRenderBitDepth(DWORD dwRGBBitCount); +void __cdecl S_InsertBackPolygon(int32_t x0, int32_t y0, int32_t x1, int32_t y1); +int32_t __cdecl S_Audio_Sample_OutPlay(int32_t sample_id, uint16_t volume, int32_t pitch, int32_t pan); +int32_t __cdecl S_Audio_Sample_OutPlayLooped(int32_t track_id, uint16_t volume, int32_t pitch, int32_t pan); +void __cdecl Overlay_DrawGameInfo(bool pickup_state); +bool __thiscall CompareVideoModes(const DISPLAY_MODE *mode1, const DISPLAY_MODE *mode2); +bool __cdecl S_Audio_Sample_IsTrackPlaying(int32_t track_id); +void __cdecl FallingBlockCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +HRESULT __cdecl DDrawSurfaceCreate(LPDDSDESC dsp, LPDDS *surface); +void __cdecl BitMaskGetNumberOfBits(uint32_t bit_mask, uint32_t *bit_depth, uint32_t *bit_offset); +void __cdecl GetValidLevelsList(REQUEST_INFO *req); +void __cdecl draw_right_gun(ITEM *item); +void __cdecl draw_left_gun(ITEM *item); +void __cdecl InitialiseCult1(int16_t item_num); +void __cdecl WinVidShowGameWindow(int32_t nCmdShow); +void __cdecl S_FadeToBlack(void); +void __cdecl ShutThatDoor(DOORPOS_DATA *d); +int32_t __cdecl UT_ErrorBox(UINT uID, HWND hWnd); +void __cdecl Matrix_TranslateRel_I(int32_t x, int32_t y, int32_t z); +void __cdecl Matrix_RotYXZ_I(int16_t y, int16_t x, int16_t z); +void __cdecl WinVidSetMinWindowSize(int32_t width, int32_t height); +void __cdecl WinVidSetMaxWindowSize(int32_t width, int32_t height); +void __cdecl lara_normal_effect(ITEM *item); +void __cdecl Inv_Ring_Light(RING_INFO *ring); +void __cdecl Lara_State_FastFall(ITEM *item, COLL_INFO *coll); +void __cdecl InitialiseSkidoo(int16_t item_num); +bool __cdecl WinVidInit(void); +void __cdecl Option_Controls_DefaultConflict(void); +void __cdecl UpdateTicks(void); +void __cdecl ControlDingDong(int16_t item_num); +void __cdecl TrapDoorCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl Flare_Ready(void); +void __cdecl FallingBlockFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl Enumerate3DDevices(DISPLAY_ADAPTER *adapter); +DWORD __cdecl EncodePutPCX(BYTE value, BYTE num, BYTE *buffer); +int32_t __stdcall SE_PropSheetCallback(HWND hwndDlg, UINT uMsg, LPARAM lParam); +HWR_TEXTURE_HANDLE __cdecl GetTexturePageHandle(int32_t page_idx); +void __cdecl Matrix_RotYXZsuperpack_I(const int16_t **pprot1, const int16_t **pprot2, int32_t skip); +int32_t __cdecl KeyTrigger(int16_t item_num); +HRESULT __cdecl WinVidBufferLock(LPDDS surface, LPDDSDESC desc, DWORD flags); +int32_t __cdecl SE_SoundTestExecute(void); +void __cdecl Output_DrawPolyGTMap(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyWGTMap(const int16_t *obj_ptr); +void __cdecl Boat_Initialise(int16_t item_num); +void __cdecl ControlCutShotgun(int16_t item_num); +void __cdecl InitialiseBlade(int16_t item_num); +void __cdecl InitialiseRollingBall(int16_t item_num); +int32_t __cdecl RenderErrorBox(int32_t error_code); +void __cdecl Shell_ExitSystem(const char *message); +void __cdecl S_Audio_Sample_OutCloseAllTracks(void); +int32_t __cdecl Gun_CheckForHoldingState(int32_t state); +GAME_OBJECT_ID Gun_GetWeaponAnim(const LARA_GUN_TYPE gun_type); +void __cdecl DrawBridgeCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +int32_t __cdecl GetOffset(ITEM *item, int32_t x, int32_t z); +void __cdecl ControlGlow(int16_t fx_num); +void __cdecl WaitPrimaryBufferFlip(void); +void __cdecl S_RemoveCtrlText(void); +void __cdecl TexturePageReleaseVidMemSurface(TEXPAGE_DESC *page); +void __cdecl Gun_Pistols_UndrawMeshLeft(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_UndrawMeshRight(LARA_GUN_TYPE weapon_type); +void __cdecl TrapDoorFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl WinVidHideGameWindow(void); +void __cdecl Output_InitPolyList(void); +void __cdecl Requester_SetSize(REQUEST_INFO *req, int32_t maxlines, int32_t ypos); +int16_t __cdecl Item_Create(void); +void __cdecl Lara_State_SwitchOn(ITEM *item, COLL_INFO *coll); +void __cdecl SwitchControl(int16_t item_num); +uint32_t __cdecl Text_GetScaleV(uint32_t value); +sub_447170 +void __cdecl TempVideoRemove(void); +sub_447B40 +int32_t __cdecl S_Audio_Sample_GetFreeTrackIndex(void); +void __cdecl Shell_Shutdown(void); +void __cdecl S_Audio_Sample_OutSetPanAndVolume(int32_t track_id, int32_t pan, uint16_t volume); +void __cdecl Music_Stop(void); +void __cdecl Output_DrawPolyGTMapPersp(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyWGTMapPersp(const int16_t *obj_ptr); +void __cdecl Item_ShiftCol(ITEM *item, COLL_INFO *coll); +int32_t __cdecl Game_DrawCinematic(void); +int32_t __cdecl Game_Draw(void); +void __cdecl ControlLaraAlarm(int16_t item_num); +void __cdecl Text_ChangeText(TEXTSTRING *string, const char *text); +uint32_t __cdecl Text_GetScaleH(uint32_t value); +bool __cdecl D3DIsSupported(LPD3DDEVICEDESC desc); +void __cdecl S_InitialiseScreen(GAMEFLOW_LEVEL_TYPE level_type); +LONG __cdecl SetRegistryBinaryValue(LPCTSTR lpValueName, LPBYTE value, DWORD valueSize); +void __thiscall SE_ReleaseBitmapResource(BITMAP_RESOURCE *bmpRsrc); +void __cdecl Output_PrintPolyList(uint8_t *surface_ptr); +void __cdecl Lara_Col_Default(ITEM *item, COLL_INFO *coll); +void __cdecl DrawBridgeFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl ChangeFileNameExtension(char *file_name, const char *file_ext); +DWORD __cdecl S_DumpScreen(void); +LPVOID __cdecl UT_LoadResource(LPCTSTR lpName, LPCTSTR lpType); +LONG __cdecl SetRegistryFloatValue(LPCTSTR lpValueName, double value); +void __cdecl Output_DrawPolyFlat(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyTrans(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyGouraud(const int16_t *obj_ptr); +void __cdecl Creature_Tilt(ITEM *item, int16_t angle); +FRAME_INFO *__cdecl Item_GetBestFrame(const ITEM *item); +void __cdecl Lara_State_HangLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_HangRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbRight(ITEM *item, COLL_INFO *coll); +void __cdecl InitialiseLift(int16_t item_num); +void __cdecl LiftFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl LiftCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl BridgeTilt1Ceiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl ReadSG(void *pointer, int32_t size); +void __cdecl WinVidSetGameWindowSize(int32_t width, int32_t height); +void __thiscall S_FlaggedString_InitAdapter(DISPLAY_ADAPTER *adapter); +void __cdecl S_FadeInInventory(BOOL isFade); +void __cdecl S_DrawScreenLine(int32_t x, int32_t y, int32_t z, int32_t x_len, int32_t y_len, BYTE color_idx, D3DCOLOR *gour, uint16_t flags); +void __cdecl HWR_GetPageHandles(void); +bool __cdecl ReloadTextures(bool reset); +void __cdecl Matrix_InitInterpolate(int32_t frac, int32_t rate); +void __cdecl BridgeTilt2Ceiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl TempVideoAdjust(int32_t hires, double sizer); +void __cdecl game_free(size_t free_size); +void __cdecl Output_SortPolyList(void); +void __cdecl CutscenePlayerGen_Initialise(int16_t item_num); +void __cdecl Lara_State_RightJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_LeftJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_HangLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_HangRight(ITEM *item, COLL_INFO *coll); +void __cdecl Gun_Rifle_DrawMeshes(LARA_GUN_TYPE weapon_type); +int32_t __cdecl PickupTrigger(int16_t item_num); +void __cdecl DDrawRelease(void); +void __cdecl Matrix_PushUnit(void); +const int16_t *__cdecl Output_CalcSkyboxLight(const int16_t *obj_ptr); +void __cdecl Inv_RingActive(void); +void __cdecl Lara_GetCollisionInfo(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfSwim(ITEM *item, COLL_INFO *coll); +void __cdecl OpenThatDoor(DOORPOS_DATA *d); +void __cdecl BGND_GetPageHandles(void); +void __thiscall DisplayModeListDelete(DISPLAY_MODE_LIST *pList); +void __cdecl FreeTexturePage(int32_t page_idx); +void __cdecl Matrix_RotY_I(int16_t ang); +void __cdecl Matrix_RotX_I(int16_t ang); +void __cdecl Matrix_RotZ_I(int16_t ang); +int32_t __cdecl Music_GetRealTrack(int32_t track); +void __cdecl S_Audio_Sample_AdjustTrackPitch(int32_t track_id, int32_t pitch); +void __cdecl S_CalculateStaticLight(int16_t adder); +void __cdecl ControlRichochet1(int16_t fx_num); +void __cdecl Effect_InitialiseArray(void); +void __cdecl Lara_State_PushBlock(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Pickup(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_UseKey(ITEM *item, COLL_INFO *coll); +void __cdecl BridgeTilt1Floor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +bool __cdecl BGND_Init(void); +void __cdecl CleanupTextures(void); +BOOL __cdecl OpenRegistryKey(LPCTSTR lpSubKey); +void __cdecl Creature_Initialise(int16_t item_num); +int32_t __cdecl Room_FindGridShift(int32_t src, int32_t dst); +void __cdecl Inv_Ring_RotateLeft(RING_INFO *ring); +void __cdecl Inv_Ring_RotateRight(RING_INFO *ring); +void __cdecl Lara_State_SwanDive(ITEM *item, COLL_INFO *coll); +void __cdecl BridgeTilt2Floor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +sub_4471C0 +sub_447B90 +void __cdecl S_Audio_Sample_CloseTrack(int32_t track_id); +void __cdecl S_Audio_Sample_CloseAllTracks(void); +void __cdecl S_DrawScreenFBox(int32_t sx, int32_t sy, int32_t z, int32_t width, int32_t height, BYTE color_idx, const GOURAUD_FILL *gour, uint16_t flags); +void __cdecl Demo_GetInput(void); +BOOL __cdecl Overlay_FlashCounter(void); +void __cdecl WinInStart(void); +void __cdecl ChandelierFX(ITEM *item); +void __cdecl Overlay_DrawModeInfo(void); +void __cdecl Inv_Ring_CalcAdders(RING_INFO *ring, int16_t rotation_duration); +void __cdecl Inv_Ring_MotionSetup(RING_INFO *ring, int16_t status, int16_t status_target, int16_t frames); +void __cdecl Lara_State_Fallback(ITEM *item, COLL_INFO *coll); +void __cdecl InitialiseMovingBlock(int16_t item_num); +void __cdecl DrawBridgeCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl Text_Init(void); +void __cdecl S_Audio_Sample_AdjustTrackVolumeAndPan(int32_t track_id, int32_t volume, int32_t pan); +LPTSTR __cdecl UT_FindArg(LPCTSTR str); +uint32_t __fastcall Math_Sqrt(uint32_t n); +void __cdecl Lara_State_Slide(ITEM *item, COLL_INFO *coll); +void __cdecl Gun_Rifle_UndrawMeshes(LARA_GUN_TYPE weapon_type); +void __cdecl Text_Flash(TEXTSTRING *string, int16_t enable, int16_t rate); +DWORD __cdecl GetZBufferDepth(void); +void __cdecl HWR_ResetColorKey(void); +void __cdecl Output_InsertPolygons_I(int16_t *ptr, int32_t clip); +void __cdecl Sound_Init(void); +void __cdecl S_Audio_Sample_Shutdown(void); +void __cdecl S_UnloadLevelFile(void); +int32_t __cdecl S_Audio_Sample_CalculateSampleVolume(int32_t volume); +LONG __cdecl SetRegistryBoolValue(LPCTSTR lpValueName, bool value); +void __cdecl Text_AddOutline(TEXTSTRING *string, int16_t enable, int16_t color, uint16_t *gour_ptr, uint16_t flags); +void __cdecl DInputKeyboardRelease(void); +void __cdecl HWR_ResetTexSource(void); +void __cdecl Overlay_MakeAmmoString(char *string); +int32_t __cdecl CalculateWindowHeight(int32_t width, int32_t height); +void __cdecl Matrix_Push_I(void); +void __cdecl Lara_State_PPReady(ITEM *item, COLL_INFO *coll); +void __cdecl InitialiseLevelFlags(void); +void __cdecl Text_Draw(void); +void __cdecl S_FadeOutInventory(BOOL isFade); +void __cdecl Inv_Ring_MotionRadius(RING_INFO *ring, int16_t target); +LPCTSTR __cdecl GetFullPath(LPCTSTR file_name); +void __cdecl ScreenClear(bool is_phd_win_size); +int32_t __cdecl S_Audio_Sample_CalculateSamplePan(int16_t pan); +void __cdecl FreeTexturePages(void); +void __cdecl SE_PassMessageToImage(HWND hWnd, UINT uMsg, WPARAM wParam); +void __cdecl Inv_Ring_MotionCameraPos(RING_INFO *ring, int16_t target); +int32_t __cdecl Text_Remove(TEXTSTRING *string); +HRESULT __cdecl WinVidBufferUnlock(LPDDS surface, LPDDSDESC desc); +LPDIRECT3DTEXTURE2 __cdecl Create3DTexture(LPDDS surface); +HRESULT __cdecl EnumerateTextureFormats(void); +void __cdecl Room_TriggerMusicTrack(int16_t value, int16_t flags, int16_t type); +void __cdecl AssaultStart(ITEM *item); +void __cdecl Inv_RemoveInventoryText(void); +void __cdecl Sound_Shutdown(void); +int16_t __cdecl Text_GetTextLength(const char *text); +bool __cdecl DInputEnumDevices(JOYSTICK_LIST *joystickList); +void __cdecl HWR_TexSource(HWR_TEXTURE_HANDLE tex_source); +void __cdecl RubbleFX(ITEM *item); +void __cdecl ExplosionFX(ITEM *item); +void __cdecl Lara_Col_BackJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_RightJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_LeftJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SlideBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfRight(ITEM *item, COLL_INFO *coll); +void __cdecl SecretControl(int16_t item_num); +uint16_t __cdecl S_COLOR(int32_t red, int32_t green, int32_t blue); +void __cdecl FreePalette(int32_t palette_idx); +void __cdecl Matrix_Pop_I(void); +void __cdecl Inv_Ring_MotionRotation(RING_INFO *ring, int16_t rotation, int16_t target); +void __cdecl DrawMovableBlock(ITEM *item); +void __cdecl WinVidFreeWindow(void); +bool __cdecl DInputCreate(void); +bool __cdecl HWR_Init(void); +void __cdecl Lara_InitialiseLoad(int16_t item_num); +void __cdecl init_game_malloc(void); +void __cdecl S_SetupAboveWater(BOOL underwater); +void __cdecl Inv_Ring_MotionCameraPitch(RING_INFO *ring, int16_t target); +void __cdecl SafeFreeTexturePage(int32_t page_idx); +void __cdecl AssaultStop(ITEM *item); +int32_t __cdecl GF_DoFrontendSequence(void); +bool __cdecl D3DCreate(void); +void __thiscall S_FlaggedString_Create(STRING_FLAGGED *string, int32_t size); +BOOL __cdecl S_Audio_Sample_OutIsTrackPlaying(int32_t track_id); +bool __cdecl InitTextures(void); +void __cdecl Lara_Col_Slide(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbEnd(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfTread(ITEM *item, COLL_INFO *coll); +void __cdecl Text_CentreH(TEXTSTRING *string, int16_t enable); +void __cdecl Text_CentreV(TEXTSTRING *string, int16_t enable); +void __cdecl Text_AlignRight(TEXTSTRING *string, int16_t enable); +void __cdecl Text_AlignBottom(TEXTSTRING *string, int16_t enable); +int32_t __cdecl Random_GetControl(void); +int32_t __cdecl Random_GetDraw(void); +LONG __cdecl SetRegistryDwordValue(LPCTSTR lpValueName, DWORD value); +void __cdecl Matrix_Push(void); +void __cdecl Lara_State_Special(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_GongBong(ITEM *item, COLL_INFO *coll); +void __cdecl BoilerFX(ITEM *item); +void __cdecl PistonFX(ITEM *item); +void __cdecl CurtainFX(ITEM *item); +void __cdecl StatueFX(ITEM *item); +void __cdecl SetChangeFX(ITEM *item); +void __cdecl Lara_State_Reach(ITEM *item, COLL_INFO *coll); +bool __cdecl S_Audio_Sample_DSoundCreate(GUID *guid); +void __cdecl S_DontDisplayPicture(void); +void __cdecl S_Audio_Sample_OutSetPitch(int32_t track_id, int32_t pitch); +int32_t __cdecl GetFreePaletteIndex(void); +int32_t __cdecl GetFreeTexturePageIndex(void); +void __thiscall SE_UpdateBitmapPalette(BITMAP_RESOURCE *bmpRsrc, HWND hWnd, HWND hSender); +void __cdecl Inv_RemoveAllItems(void); +void __cdecl Requester_Item_CenterAlign(REQUEST_INFO *req, TEXTSTRING *txt); +void __cdecl Lara_ControlExtra(int16_t item_num); +void __cdecl BridgeFlatCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +int32_t __fastcall Math_Sin(int16_t angle); +bool __cdecl EnumerateDisplayAdapters(DISPLAY_ADAPTER_LIST *displayAdapterList); +void __cdecl DInputRelease(void); +bool __cdecl S_Audio_Sample_DSoundEnumerate(SOUND_ADAPTER_LIST *adapter_list); +int32_t __fastcall Math_SinImpl(int16_t angle); +int32_t __cdecl Input_GetDebounced(int32_t input); +bool __cdecl HWR_VertexBufferFull(void); +void __cdecl Lara_State_SlideBack(ITEM *item, COLL_INFO *coll); +void __cdecl Flare_DrawMeshes(void); +void __cdecl Flare_UndrawMeshes(void); +sub_444990 +sub_444AA0 +sub_4470B0 +sub_447160 +sub_447A80 +sub_447B30 +BOOL __cdecl S_LoadLevelFile(LPCTSTR file_name, int32_t level_num, GAMEFLOW_LEVEL_TYPE level_type); +int32_t __cdecl UT_MessageBox(LPCTSTR lpText, HWND hWnd); +void __cdecl AssaultReset(ITEM *item); +void __cdecl Text_SetScale(TEXTSTRING *string, int32_t scale_h, int32_t scale_v); +void __cdecl WinInRunControlPanel(HWND hWnd); +sub_44E860 +void __cdecl SE_GraphicsAdapterSet(HWND hwndDlg, DISPLAY_ADAPTER_NODE *adapter); +void __cdecl S_Audio_Sample_OutCloseTrack(int32_t track_id); +void __cdecl turn180_effect(ITEM *item); +void __cdecl invisibility_off(ITEM *item); +void __cdecl BridgeFlatFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +void __cdecl HWR_BeginScene(void); +void __cdecl Overlay_InitialisePickUpDisplay(void); +void __cdecl Lara_State_WaterOut(ITEM *item, COLL_INFO *coll); +void __cdecl ResetSG(void); +void __cdecl Lara_State_Death(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Climbing(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbDown(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Dive(ITEM *item, COLL_INFO *coll); +void __cdecl S_AdjustTexelCoordinates(void); +void __cdecl SE_GraphicsTestFinish(void); +void __cdecl Requester_RemoveAllItems(REQUEST_INFO *req); +void __cdecl Lara_State_UpJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_Airlock(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_TurnLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Land(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_StepLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Null(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Swim(ITEM *item, COLL_INFO *coll); +void __thiscall S_FlaggedString_Delete(STRING_FLAGGED *string); +void __cdecl WinVidFinish(void); +bool __cdecl S_IntroFMV(const char *file_name1, const char *file_name2); +LONG __cdecl DeleteRegistryValue(LPCTSTR lpValueName); +sub_4449A0 +DISPLAY_MODE *__thiscall InsertDisplayMode(DISPLAY_MODE_LIST *modeList, DISPLAY_MODE_NODE *before); +sub_4470C0 +sub_447A90 +void __cdecl SafeFreePalette(int32_t palette_idx); +HWND __cdecl WinVidFindGameWindow(void); +void __cdecl GameBuf_Shutdown(void); +HWND __cdecl SE_FindSetupDialog(void); +void __cdecl Text_RemoveBackground(TEXTSTRING *string); +void __cdecl Text_RemoveOutline(TEXTSTRING *string); +sub_44E880 +void __cdecl ScreenPartialDump(void); +void __cdecl WinInFinish(void); +void __cdecl S_FinishInventory(void); +bool __cdecl IsNewRegistryKeyCreated(void); +bool __cdecl S_PlayFMV(const char *file_name); +void __cdecl SE_AdvancedDlgInit(HWND hwndDlg); +void __thiscall DisplayModeListInit(DISPLAY_MODE_LIST *pList); +void __cdecl ScreenDump(void); +LONG __cdecl CloseRegistryKey(void); +LPCTSTR __cdecl DecodeErrorMessage(DWORD error_code); +void __cdecl finish_level_effect(ITEM *item); +void __cdecl dynamic_light_off(ITEM *item); +void __cdecl Lara_State_Null(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_UWTwist(ITEM *item, COLL_INFO *coll); +void __cdecl S_ClearScreen(void); +void __cdecl Game_SetCutsceneTrack(int32_t track); +void __cdecl lara_hands_free(ITEM *item); +void __cdecl Lara_State_Splat(ITEM *item, COLL_INFO *coll); +void __cdecl Random_SeedControl(int32_t seed); +void __cdecl Random_SeedDraw(int32_t seed); +void __cdecl SE_SoundAdapterSet(HWND hwndDlg, SOUND_ADAPTER_NODE *adapter); +void __cdecl SE_ControlsJoystickSet(HWND hwndDlg, JOYSTICK_NODE *joystick); +void __cdecl Sound_SetMasterVolume(int32_t volume); +void __cdecl invisibility_on(ITEM *item); +void __cdecl dynamic_light_on(ITEM *item); +void __cdecl WinVidClearMinWindowSize(void); +void __cdecl WinVidClearMaxWindowSize(void); +bool __cdecl Direct3DInit(void); +bool __cdecl S_Audio_Sample_IsEnabled(void); +int32_t __cdecl LevelCompleteSequence(void); +int32_t __cdecl GetRenderHeight(void); +int32_t __cdecl GetRenderWidth(void); +int32_t __fastcall Math_Cos(int16_t angle); +void __cdecl flip_map_effect(ITEM *item); +void __cdecl reset_hair(ITEM *item); +void __cdecl Inv_DoInventoryPicture(void); +sub_444BB0 +sub_4473A0 +sub_447FA0 +sub_448410 +sub_4523A0 +LONG __cdecl CloseGameRegistryKey(void); +void __cdecl SE_SoundTestFinish(void); +bool __cdecl RenderInit(void); +int32_t __cdecl SE_GraphicsTestExecute(void); +void __cdecl Output_InsertInventoryBackground(const int16_t *obj_ptr); +sub_444BC0 +sub_448420 +sub_44E6F0 +sub_4523B0 + + + diff --git a/docs/tr2/progress.txt b/docs/tr2/progress.txt new file mode 100644 index 000000000..2b2a032e0 --- /dev/null +++ b/docs/tr2/progress.txt @@ -0,0 +1,4695 @@ +# TYPES +typedef IDirect3DDevice2 *LPDIRECT3DDEVICE2; +typedef IDirect3DTexture2 *LPDIRECT3DTEXTURE2; +typedef IDirect3DViewport2 *LPDIRECT3DVIEWPORT2; +typedef IDirect3DMaterial2 *LPDIRECT3DMATERIAL2; +typedef DDSURFACEDESC DDSDESC, *LPDDSDESC; +typedef LPDIRECTDRAWSURFACE3 LPDDS; +typedef LPDIRECTDRAW3 LPDD; +typedef D3DTEXTUREHANDLE HWR_TEXTURE_HANDLE; + +typedef int16_t PHD_ANGLE; + +typedef struct __unaligned { // decompiled + int32_t x; + int32_t y; + int32_t z; +} XYZ_32; + +typedef struct __unaligned { // decompiled + int16_t x; + int16_t y; + int16_t z; +} XYZ_16; + +typedef struct __unaligned { + int32_t _00; + int32_t _01; + int32_t _02; + int32_t _03; + int32_t _10; + int32_t _11; + int32_t _12; + int32_t _13; + int32_t _20; + int32_t _21; + int32_t _22; + int32_t _23; +} MATRIX; + +typedef enum { + VGA_NO_VGA = 0, + VGA_256_COLOR = 1, + VGA_MODEX = 2, + VGA_STANDARD = 3, +} VGA_MODE; + +typedef struct __unaligned { + LPBITMAPINFO bmp_info; + void *bmp_data; + HPALETTE hPalette; + DWORD flags; +} BITMAP_RESOURCE; + +typedef struct __unaligned { + int32_t width; + int32_t height; + int32_t bpp; + VGA_MODE vga; +} DISPLAY_MODE; + +typedef struct __unaligned DISPLAY_MODE_NODE { + struct DISPLAY_MODE_NODE *next; + struct DISPLAY_MODE_NODE *previous; + DISPLAY_MODE body; +} DISPLAY_MODE_NODE; + +typedef struct __unaligned { + DISPLAY_MODE_NODE *head; + DISPLAY_MODE_NODE *tail; + DWORD count; +} DISPLAY_MODE_LIST; + +typedef struct __unaligned { + char *content; + bool is_valid; +} STRING_FLAGGED; + +typedef struct __unaligned { + LPGUID adapter_guid_ptr; + GUID adapter_guid; + STRING_FLAGGED driver_desc; + STRING_FLAGGED driver_name; + DDCAPS_DX5 driver_caps; + DDCAPS_DX5 hel_caps; + GUID device_guid; + D3DDEVICEDESC_V2 hw_device_desc; + DISPLAY_MODE_LIST hw_disp_mode_list; + DISPLAY_MODE_LIST sw_disp_mode_list; + DISPLAY_MODE vga_mode1; + DISPLAY_MODE vga_mode2; + uint32_t screen_width; + bool hw_render_supported; + bool sw_windowed_supported; + bool hw_windowed_supported; + bool is_vga_mode1_presented; + bool is_vga_mode2_presented; + bool perspective_correct_supported; + bool dither_supported; + bool zbuffer_supported; + bool linear_filter_supported; + bool shade_restricted; +} DISPLAY_ADAPTER; + +typedef struct __unaligned DISPLAY_ADAPTER_NODE { + struct DISPLAY_ADAPTER_NODE *next; + struct DISPLAY_ADAPTER_NODE *previous; + DISPLAY_ADAPTER body; +} DISPLAY_ADAPTER_NODE; + +typedef struct __unaligned { + DISPLAY_ADAPTER_NODE *head; + DISPLAY_ADAPTER_NODE *tail; + DWORD count; +} DISPLAY_ADAPTER_LIST; + +typedef struct __unaligned { + GUID *adapter_guid_ptr; + GUID adapter_guid; + STRING_FLAGGED description; + STRING_FLAGGED module; +} SOUND_ADAPTER; + +typedef struct __unaligned SOUND_ADAPTER_NODE { + struct SOUND_ADAPTER_NODE *next; + struct SOUND_ADAPTER_NODE *previous; + SOUND_ADAPTER body; +} SOUND_ADAPTER_NODE; + +typedef struct __unaligned { + SOUND_ADAPTER_NODE *head; + SOUND_ADAPTER_NODE *tail; + DWORD count; +} SOUND_ADAPTER_LIST; + +typedef struct __unaligned { + GUID *lpJoystickGuid; + GUID joystickGuid; + STRING_FLAGGED productName; + STRING_FLAGGED instanceName; +} JOYSTICK; + +typedef struct __unaligned JOYSTICK_NODE { + struct JOYSTICK_NODE *next; + struct JOYSTICK_NODE *previous; + JOYSTICK body; +} JOYSTICK_NODE; + +typedef struct __unaligned JOYSTICK_LIST { + struct JOYSTICK_LIST *head; + struct JOYSTICK_LIST *tail; + DWORD count; +} JOYSTICK_LIST; + +typedef enum { + RM_UNKNOWN = 0, + RM_SOFTWARE = 1, + RM_HARDWARE = 2, +} RENDER_MODE; + +typedef enum { + AM_4_3 = 0, + AM_16_9 = 1, + AM_ANY = 2, +} ASPECT_MODE; + +typedef enum { + TAM_DISABLED = 0, + TAM_BILINEAR_ONLY = 1, + TAM_ALWAYS = 2, +} TEX_ADJUST_MODE; + +typedef struct __unaligned { + DISPLAY_ADAPTER_NODE *preferred_display_adapter; + SOUND_ADAPTER_NODE *preferred_sound_adapter; + JOYSTICK_NODE *preferred_joystick; + const DISPLAY_MODE_NODE *video_mode; + RENDER_MODE render_mode; + int32_t window_width; + int32_t window_height; + ASPECT_MODE aspect_mode; + bool perspective_correct; + bool dither; + bool zbuffer; + bool bilinear_filtering; + bool triple_buffering; + bool fullscreen; + bool sound_enabled; + bool lara_mic; + bool joystick_enabled; + bool disable_16bit_textures; + bool dont_sort_primitives; + bool flip_broken; + bool disable_fmv; + TEX_ADJUST_MODE texel_adjust_mode; + int32_t nearest_adjustment; + int32_t linear_adjustment; +} APP_SETTINGS; + +typedef struct __unaligned { + LPDDS sysMemSurface; + LPDDS vidMemSurface; + LPDIRECTDRAWPALETTE palette; + LPDIRECT3DTEXTURE2 texture3d; + HWR_TEXTURE_HANDLE texHandle; + int32_t width; + int32_t height; + int32_t status; +} TEXPAGE_DESC; + +typedef struct __unaligned { + uint8_t red; + uint8_t green; + uint8_t blue; +} RGB_888; + +typedef struct __unaligned { + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t alpha; +} RGBA_8888; + +typedef struct { + struct { + uint32_t r; + uint32_t g; + uint32_t b; + uint32_t a; + } mask, depth, offset; +} COLOR_BIT_MASKS; + +typedef struct __unaligned { + D3DCOLOR clr[4][4]; +} GOURAUD_FILL; + +typedef struct __unaligned { + D3DCOLOR clr[9]; +} GOURAUD_OUTLINE; + +typedef struct __unaligned { + uint8_t index[256]; +} DEPTHQ_ENTRY; + +typedef struct __unaligned { + uint8_t index[32]; +} GOURAUD_ENTRY; + +typedef struct __unaligned { + XYZ_32 pos; + XYZ_16 rot; +} PHD_3DPOS; + +typedef struct __unaligned { + int32_t x; + int32_t y; + int32_t z; + int32_t r; +} SPHERE; + +typedef struct __unaligned { + union { + uint32_t all; + struct { + uint32_t active: 1; + uint32_t flash: 1; + uint32_t rotate_h: 1; + uint32_t rotate_v: 1; + uint32_t centre_h: 1; + uint32_t centre_v: 1; + uint32_t hide: 1; + uint32_t right: 1; + uint32_t bottom: 1; + uint32_t background: 1; + uint32_t outline: 1; + uint32_t multiline: 1; + }; + } flags; + uint16_t text_flags; + uint16_t bgnd_flags; + uint16_t outl_flags; + XYZ_16 pos; + int16_t letter_spacing; + int16_t word_spacing; + struct { + int16_t rate; + int16_t count; + } flash; + int16_t bgnd_color; + const uint16_t *bgnd_gour; + int16_t outl_color; + const uint16_t *outl_gour; + struct { + int16_t x; + int16_t y; + } bgnd_size; + XYZ_16 bgnd_off; + struct { + int32_t h; + int32_t v; + } scale; + char *text; +} TEXTSTRING; + +typedef struct __unaligned { + float xv; + float yv; + float zv; + float rhw; + float xs; + float ys; + int16_t clip; + int16_t g; + int16_t u; + int16_t v; +} PHD_VBUF; + +typedef struct __unaligned { + uint16_t u; + uint16_t v; +} PHD_UV; + +typedef struct __unaligned { + uint16_t draw_type; + uint16_t tex_page; + PHD_UV uv[4]; +} PHD_TEXTURE; + +typedef struct __unaligned { + uint16_t tex_page; + uint16_t offset; + uint16_t width; + uint16_t height; + int16_t x0; + int16_t y0; + int16_t x1; + int16_t y1; +} PHD_SPRITE; + +typedef enum { + SHAPE_SPRITE = 1, + SHAPE_LINE = 2, + SHAPE_BOX = 3, + SHAPE_FBOX = 4, +} SHAPE; + +typedef enum { + SPRF_RGB = 0x00FFFFFF, + SPRF_ABS = 0x01000000, + SPRF_SEMITRANS = 0x02000000, + SPRF_SCALE = 0x04000000, + SPRF_SHADE = 0x08000000, + SPRF_TINT = 0x10000000, + SPRF_BLEND_ADD = 0x20000000, + SPRF_BLEND_SUB = 0x40000000, + SPRF_BLEND_QRT = SPRF_BLEND_ADD | SPRF_BLEND_SUB, + SPRF_BLEND = SPRF_BLEND_QRT, + SPRF_ITEM = 0x80000000, +} SPRITE_FLAG; + +typedef struct __unaligned { + float xv; + float yv; + float zv; + float rhw; + float xs; + float ys; + float u; + float v; + float g; +} POINT_INFO; + +typedef struct __unaligned { + float x; + float y; + float rhw; + float u; + float v; + float g; +} VERTEX_INFO; + +typedef enum { // decompiled + INPUT_ROLE_FORWARD = 0, + INPUT_ROLE_BACK = 1, + INPUT_ROLE_LEFT = 2, + INPUT_ROLE_RIGHT = 3, + INPUT_ROLE_STEP_LEFT = 4, + INPUT_ROLE_STEP_RIGHT = 5, + INPUT_ROLE_SLOW = 6, + INPUT_ROLE_JUMP = 7, + INPUT_ROLE_ACTION = 8, + INPUT_ROLE_DRAW_WEAPON = 9, + INPUT_ROLE_FLARE = 10, + INPUT_ROLE_LOOK = 11, + INPUT_ROLE_ROLL = 12, + INPUT_ROLE_OPTION = 13, +} INPUT_ROLE; + +typedef struct __unaligned { + uint16_t no_selector : 1; + uint16_t ready : 1; // not present in the OG + uint16_t pad : 14; + uint16_t items_count; + uint16_t selected; + uint16_t visible_count; + uint16_t line_offset; + uint16_t line_old_offset; + uint16_t pix_width; + uint16_t line_height; + int16_t x_pos; + int16_t y_pos; + int16_t z_pos; + uint16_t item_string_len; + char *pitem_strings1; + char *pitem_strings2; + uint32_t *pitem_flags1; + uint32_t *pitem_flags2; + uint32_t heading_flags1; + uint32_t heading_flags2; + uint32_t background_flags; + uint32_t moreup_flags; + uint32_t moredown_flags; + uint32_t item_flags1[24]; // MAX_REQUESTER_ITEMS + uint32_t item_flags2[24]; // MAX_REQUESTER_ITEMS + TEXTSTRING *heading_text1; + TEXTSTRING *heading_text2; + TEXTSTRING *background_text; + TEXTSTRING *moreup_text; + TEXTSTRING *moredown_text; + TEXTSTRING *item_texts1[24]; // MAX_REQUESTER_ITEMS + TEXTSTRING *item_texts2[24]; // MAX_REQUESTER_ITEMS + char heading_string1[32]; + char heading_string2[32]; + uint32_t render_width; + uint32_t render_height; +} REQUEST_INFO; + +typedef enum { + POLY_GTMAP = 0, + POLY_WGTMAP = 1, + POLY_GTMAP_PERSP = 2, + POLY_WGTMAP_PERSP = 3, + POLY_LINE = 4, + POLY_FLAT = 5, + POLY_GOURAUD = 6, + POLY_TRANS = 7, + POLY_SPRITE = 8, + POLY_HWR_GTMAP = 9, + POLY_HWR_WGTMAP = 10, + POLY_HWR_GOURAUD = 11, + POLY_HWR_LINE = 12, + POLY_HWR_TRANS = 13, +} POLY_TYPE; + +typedef struct __unaligned { + uint32_t best_time[10]; + uint32_t best_finish[10]; + uint32_t finish_count; +} ASSAULT_STATS; + +typedef struct __unaligned { + int32_t _0; + int32_t _1; +} SORT_ITEM; + +typedef enum { + ST_AVG_Z = 0, + ST_MAX_Z = 1, + ST_FAR_Z = 2, +} SORT_TYPE; + +typedef enum { + DRAW_OPAQUE = 0, + DRAW_COLOR_KEY = 1, +} DRAW_TYPE; + +typedef struct __unaligned { // decompiled + int32_t floor; + int32_t ceiling; + int32_t type; +} COLL_SIDE; + +typedef struct __unaligned { // decompiled + COLL_SIDE side_mid; + COLL_SIDE side_front; + COLL_SIDE side_left; + COLL_SIDE side_right; + int32_t radius; + int32_t bad_pos; + int32_t bad_neg; + int32_t bad_ceiling; + XYZ_32 shift; + XYZ_32 old; + int16_t old_anim_state; + int16_t old_anim_num; + int16_t old_frame_num; + int16_t facing; + int16_t quadrant; + int16_t coll_type; + int16_t *trigger; + int8_t x_tilt; + int8_t z_tilt; + int8_t hit_by_baddie; + int8_t hit_static; + uint16_t slopes_are_walls: 1; // 0x01 1 + uint16_t slopes_are_pits: 1; // 0x02 2 + uint16_t lava_is_pit: 1; // 0x04 4 + uint16_t enable_baddie_push: 1; // 0x08 8 + uint16_t enable_spaz: 1; // 0x10 16 + uint16_t hit_ceiling: 1; // 0x20 32 + uint16_t pad: 10; +} COLL_INFO; + +typedef struct __unaligned { // decompiled + int16_t min_x; + int16_t max_x; + int16_t min_y; + int16_t max_y; + int16_t min_z; + int16_t max_z; +} BOUNDS_16; + +typedef struct __unaligned { + int16_t mesh_idx; + uint16_t flags; + BOUNDS_16 draw_bounds; + BOUNDS_16 collision_bounds; +} STATIC_INFO; + +typedef struct __unaligned { // decompiled + int32_t floor; + uint32_t touch_bits; + uint32_t mesh_bits; + int16_t object_id; + int16_t current_anim_state; + int16_t goal_anim_state; + int16_t required_anim_state; + int16_t anim_num; + int16_t frame_num; + int16_t room_num; + int16_t next_item; + int16_t next_active; + int16_t speed; + int16_t fall_speed; + int16_t hit_points; + int16_t box_num; + int16_t timer; + uint16_t flags; + int16_t shade_1; + int16_t shade_2; + int16_t carried_item; + void *data; + union { + struct { + XYZ_32 pos; + XYZ_16 rot; + }; + PHD_3DPOS pos_full; // TODO: stick to pos and rot + }; + uint16_t active: 1; // 0x0001 + uint16_t status: 2; // 0x0002…0x0004 + uint16_t gravity: 1; // 0x0008 + uint16_t hit_status: 1; // 0x0010 + uint16_t collidable: 1; // 0x0020 + uint16_t looked_at: 1; // 0x0040 + uint16_t dynamic_light: 1; // 0x0080 + uint16_t killed: 1; // 0x0100 + uint16_t pad: 7; // 0x0200…0x8000 +} ITEM; + +typedef struct __unaligned { + uint32_t timer; + uint32_t shots; + uint32_t hits; + uint32_t distance; + uint16_t kills; + uint8_t secrets; + uint8_t medipacks; +} STATISTICS_INFO; + +typedef struct __unaligned { + uint16_t pistol_ammo; + uint16_t magnum_ammo; + uint16_t uzi_ammo; + uint16_t shotgun_ammo; + uint16_t m16_ammo; + uint16_t grenade_ammo; + uint16_t harpoon_ammo; + uint8_t small_medipacks; + uint8_t large_medipacks; + uint8_t reserved1; + uint8_t flares; + uint8_t gun_status; + uint8_t gun_type; + uint16_t available: 1; // 0x01 1 + uint16_t has_pistols: 1; // 0x02 2 + uint16_t has_magnums: 1; // 0x04 4 + uint16_t has_uzis: 1; // 0x08 8 + uint16_t has_shotgun: 1; // 0x10 16 + uint16_t has_m16: 1; // 0x20 32 + uint16_t has_grenade: 1; // 0x40 64 + uint16_t has_harpoon: 1; // 0x80 128 + uint16_t pad : 8; + uint16_t reserved2; + STATISTICS_INFO statistics; +} START_INFO; + +typedef struct __unaligned { + START_INFO start[24]; + STATISTICS_INFO statistics; + int16_t current_level; + bool bonus_flag; + uint8_t num_pickup[2]; + uint8_t num_puzzle[4]; + uint8_t num_key[4]; + uint16_t reserved; + uint8_t buffer[6272]; +} SAVEGAME_INFO; + +typedef struct __unaligned { // decompiled + uint16_t idx; + int16_t box; + uint8_t pit_room; + int8_t floor; + uint8_t sky_room; + int8_t ceiling; +} SECTOR; + +typedef struct __unaligned { + int16_t lock_angles[4]; + int16_t left_angles[4]; + int16_t right_angles[4]; + int16_t aim_speed; + int16_t shot_accuracy; + int32_t gun_height; + int32_t damage; + int32_t target_dist; + int16_t recoil_frame; + int16_t flash_time; + int16_t sample_num; +} WEAPON_INFO; + +typedef struct __unaligned { // decompiled + XYZ_32 pos; + XYZ_16 rot; + int16_t room_num; + int16_t object_id; + int16_t next_free; + int16_t next_active; + int16_t speed; + int16_t fall_speed; + int16_t frame_num; + int16_t counter; + int16_t shade; +} FX; + +typedef struct __unaligned { + int16_t zone_num; + int16_t enemy_zone_num; + int32_t distance; + int32_t ahead; + int32_t bite; + int16_t angle; + int16_t enemy_facing; +} AI_INFO; + +typedef struct __unaligned { // decompiled + int16_t exit_box; + uint16_t search_num; + int16_t next_expansion; + int16_t box_num; +} BOX_NODE; + +typedef struct __unaligned { // decompiled + BOX_NODE *node; + int16_t head; + int16_t tail; + uint16_t search_num; + uint16_t block_mask; + int16_t step; + int16_t drop; + int16_t fly; + int16_t zone_count; + int16_t target_box; + int16_t required_box; + XYZ_32 target; +} LOT_INFO; + +typedef enum { // decompiled + GFL_NO_LEVEL = -1, + GFL_TITLE = 0, + GFL_NORMAL = 1, + GFL_SAVED = 2, + GFL_DEMO = 3, + GFL_CUTSCENE = 4, + GFL_STORY = 5, + GFL_QUIET = 6, + GFL_MID_STORY = 7, +} GAMEFLOW_LEVEL_TYPE; + +typedef struct __unaligned { + int16_t timer; + int16_t sprite; +} PICKUP_INFO; + +typedef struct __unaligned { + int16_t shape; + XYZ_16 pos; + int32_t param1; + int32_t param2; + void *grdptr; + int16_t sprite_num; +} INVENTORY_SPRITE; + +typedef struct __unaligned { + char *string; + int16_t object_id; + int16_t frames_total; + int16_t current_frame; + int16_t goal_frame; + int16_t open_frame; + int16_t anim_direction; + int16_t anim_speed; + int16_t anim_count; + int16_t x_rot_pt_sel; + int16_t x_rot_pt; + int16_t x_rot_sel; + int16_t x_rot_nosel; + int16_t x_rot; + int16_t y_rot_sel; + int16_t y_rot; + int32_t y_trans_sel; + int32_t y_trans; + int32_t z_trans_sel; + int32_t z_trans; + uint32_t meshes_sel; + uint32_t meshes_drawn; + int16_t inv_pos; + INVENTORY_SPRITE **sprite_list; + int32_t reserved[4]; +} INVENTORY_ITEM; + +typedef enum { + RNG_OPENING = 0, + RNG_OPEN = 1, + RNG_CLOSING = 2, + RNG_MAIN2OPTION = 3, + RNG_MAIN2KEYS = 4, + RNG_KEYS2MAIN = 5, + RNG_OPTION2MAIN = 6, + RNG_SELECTING = 7, + RNG_SELECTED = 8, + RNG_DESELECTING = 9, + RNG_DESELECT = 10, + RNG_CLOSING_ITEM = 11, + RNG_EXITING_INVENTORY = 12, + RNG_DONE = 13, +} RING_STATUS; + +typedef struct __unaligned { + int16_t count; + int16_t status; + int16_t status_target; + int16_t radius_target; + int16_t radius_rate; + int16_t camera_y_target; + int16_t camera_y_rate; + int16_t camera_pitch_target; + int16_t camera_pitch_rate; + int16_t rotate_target; + int16_t rotate_rate; + int16_t item_pt_x_rot_target; + int16_t item_pt_x_rot_rate; + int16_t item_x_rot_target; + int16_t item_x_rot_rate; + int32_t item_y_trans_target; + int32_t item_y_trans_rate; + int32_t item_z_trans_target; + int32_t item_z_trans_rate; + int32_t misc; +} IMOTION_INFO; + +typedef enum { + PM_SPINE = 1, + PM_FRONT = 2, + PM_IN_FRONT = 4, + PM_PAGE_2 = 8, + PM_BACK = 16, + PM_IN_BACK = 32, + PM_PAGE_1 = 64, + PM_COMMON = PM_SPINE | PM_BACK | PM_FRONT, +} PASS_MESH; + +typedef struct __unaligned { + INVENTORY_ITEM **list; + int16_t type; + int16_t radius; + int16_t camera_pitch; + int16_t rotating; + int16_t rot_count; + int16_t current_object; + int16_t target_object; + int16_t number_of_objects; + int16_t angle_adder; + int16_t rot_adder; + int16_t rot_adder_l; + int16_t rot_adder_r; + PHD_3DPOS ring_pos; + PHD_3DPOS camera; + XYZ_32 light; + IMOTION_INFO *imo; +} RING_INFO; + +typedef enum { + GFE_PICTURE = 0, + GFE_LIST_START = 1, + GFE_LIST_END = 2, + GFE_PLAY_FMV = 3, + GFE_START_LEVEL = 4, + GFE_CUTSCENE = 5, + GFE_LEVEL_COMPLETE = 6, + GFE_DEMO_PLAY = 7, + GFE_JUMP_TO_SEQ = 8, + GFE_END_SEQ = 9, + GFE_SET_TRACK = 10, + GFE_SUNSET = 11, + GFE_LOADING_PIC = 12, + GFE_DEADLY_WATER = 13, + GFE_REMOVE_WEAPONS = 14, + GFE_GAME_COMPLETE = 15, + GFE_CUT_ANGLE = 16, + GFE_NO_FLOOR = 17, + GFE_ADD_TO_INV = 18, + GFE_START_ANIM = 19, + GFE_NUM_SECRETS = 20, + GFE_KILL_TO_COMPLETE = 21, + GFE_REMOVE_AMMO = 22, +} GF_EVENTS; + +typedef enum { // decompiled + MOOD_BORED = 0, + MOOD_ATTACK = 1, + MOOD_ESCAPE = 2, + MOOD_STALK = 3, +} MOOD_TYPE; + +typedef enum { + TARGET_NONE = 0, + TARGET_PRIMARY = 1, + TARGET_SECONDARY = 2, +} TARGET_TYPE; + +typedef struct __unaligned { + XYZ_32 pos; + int32_t mesh_num; +} BITE; + +typedef struct __unaligned { // decompiled + int16_t *frame_ptr; + int16_t interpolation; + int16_t current_anim_state; + int32_t velocity; + int32_t acceleration; + int16_t frame_base; + int16_t frame_end; + int16_t jump_anim_num; + int16_t jump_frame_num; + int16_t num_changes; + int16_t change_idx; + int16_t num_commands; + int16_t command_idx; +} ANIM; + +typedef struct { // decompiled + int16_t goal_anim_state; + int16_t num_ranges; + int16_t range_idx; +} ANIM_CHANGE; + +typedef struct { // decompiled + int16_t start_frame; + int16_t end_frame; + int16_t link_anim_num; + int16_t link_frame_num; +} ANIM_RANGE; + +typedef struct __unaligned { // decompiled + int16_t room; + XYZ_16 normal; + XYZ_16 vertex[4]; +} PORTAL; + +typedef struct __unaligned { // decompiled + int16_t count; + PORTAL portal[]; +} PORTALS; + +typedef struct __unaligned { // decompiled + int32_t x; + int32_t y; + int32_t z; + int16_t intensity_1; + int16_t intensity_2; + int32_t falloff_1; + int32_t falloff_2; +} LIGHT; + +typedef struct __unaligned { // decompiled + XYZ_16 pos; + struct __unaligned { + int16_t y; + } rot; + int16_t shade_1; + int16_t shade_2; + int16_t static_num; +} MESH; + +typedef enum { + RF_UNDERWATER = 0x01, + RF_OUTSIDE = 0x08, + RF_DYNAMIC_LIT = 0x10, + RF_INSIDE = 0x40, +} ROOM_FLAG; + +typedef struct __unaligned { + SECTOR *sector; + SECTOR data; + int16_t block; +} DOORPOS_DATA; + +typedef struct __unaligned { // decompiled + int16_t *data; + PORTALS *portals; + SECTOR *sectors; + LIGHT *lights; + MESH *meshes; + XYZ_32 pos; + int32_t min_floor; + int32_t max_ceiling; + struct __unaligned { + int16_t z; + int16_t x; + } size; + int16_t ambient_1; + int16_t ambient_2; + int16_t light_mode; + int16_t num_lights; + int16_t num_meshes; + int16_t bound_left; + int16_t bound_right; + int16_t bound_top; + int16_t bound_bottom; + uint16_t bound_active; + int16_t test_left; + int16_t test_right; + int16_t test_top; + int16_t test_bottom; + int16_t item_num; + int16_t fx_num; + int16_t flipped_room; + uint16_t flags; +} ROOM; + +typedef struct __unaligned { // decompiled + int16_t head_rotation; + int16_t neck_rotation; + int16_t maximum_turn; + uint16_t flags; + int16_t item_num; + MOOD_TYPE mood; + LOT_INFO lot; + XYZ_32 target; + ITEM *enemy; +} CREATURE; + +typedef enum { + CAM_CHASE = 0, + CAM_FIXED = 1, + CAM_LOOK = 2, + CAM_COMBAT = 3, + CAM_CINEMATIC = 4, + CAM_HEAVY = 5, +} CAMERA_TYPE; + +typedef struct __unaligned { + union { + XYZ_32 pos; + struct { + int32_t x; + int32_t y; + int32_t z; + }; + }; + int16_t room_num; + int16_t box_num; +} GAME_VECTOR; + +typedef struct __unaligned { + int32_t x; + int32_t y; + int32_t z; + int16_t data; + int16_t flags; +} OBJECT_VECTOR; + +typedef struct __unaligned { + uint8_t left; + uint8_t right; + uint8_t top; + uint8_t bottom; + int16_t height; + int16_t overlap_index; +} BOX_INFO; + +typedef enum { + LV_GYM = 0, + LV_FIRST = 1, +} LEVEL_TYPE; + +typedef enum { + RT_MAIN = 0, + RT_OPTION = 1, + RT_KEYS = 2, +} RING_TYPE; + +typedef enum { + INV_COLOR_BLACK = 0, + INV_COLOR_GRAY = 1, + INV_COLOR_WHITE = 2, + INV_COLOR_RED = 3, + INV_COLOR_ORANGE = 4, + INV_COLOR_YELLOW = 5, + INV_COLOR_DARK_GREEN = 12, + INV_COLOR_GREEN = 13, + INV_COLOR_CYAN = 14, + INV_COLOR_BLUE = 15, + INV_COLOR_MAGENTA = 16, + INV_COLOR_NUMBER_OF = 17, +} INV_COLOR; + +typedef enum { + INV_GAME_MODE = 0, + INV_TITLE_MODE = 1, + INV_KEYS_MODE = 2, + INV_SAVE_MODE = 3, + INV_LOAD_MODE = 4, + INV_DEATH_MODE = 5, +} INVENTORY_MODE; + +typedef enum { + GAMEMODE_NOT_IN_GAME, + GAMEMODE_IN_GAME, + GAMEMODE_IN_DEMO, + GAMEMODE_IN_CUTSCENE +} GAMEMODE; + +typedef enum { + GFD_START_GAME = 0x0000, + GFD_START_SAVED_GAME = 0x0100, + GFD_START_CINE = 0x0200, + GFD_START_FMV = 0x0300, + GFD_START_DEMO = 0x0400, + GFD_EXIT_TO_TITLE = 0x0500, + GFD_LEVEL_COMPLETE = 0x0600, + GFD_EXIT_GAME = 0x0700, + GFD_EXIT_TO_OPTION = 0x0800, + GFD_TITLE_DESELECT = 0x0900, + GFD_OVERRIDE = 0x0A00, +} GAME_FLOW_DIR; + +typedef struct __unaligned { + int32_t first_option; + int32_t title_replace; + int32_t on_death_demo_mode; + int32_t on_death_in_game; + int32_t no_input_time; + int32_t on_demo_interrupt; + int32_t on_demo_end; + uint16_t reserved1[18]; + uint16_t num_levels; + uint16_t num_pictures; + uint16_t num_titles; + uint16_t num_fmvs; + uint16_t num_cutscenes; + uint16_t num_demos; + uint16_t title_track; + int16_t single_level; + uint16_t reserved2[16]; + + uint16_t demo_version: 1; // 0x0001 + uint16_t title_disabled: 1; // 0x0002 + uint16_t cheat_mode_check_disabled: 1; // 0x0004 + uint16_t no_input_timeout: 1; // 0x0008 + uint16_t load_save_disabled: 1; // 0x0010 + uint16_t screen_sizing_disabled: 1; // 0x0020 + uint16_t lockout_option_ring: 1; // 0x0040 + uint16_t dozy_cheat_enabled: 1; // 0x0080 + uint16_t cyphered_strings: 1; // 0x0100 + uint16_t gym_enabled: 1; // 0x0200 + uint16_t play_any_level: 1; // 0x0400 + uint16_t cheat_enable: 1; // 0x0800 + + uint16_t reserved3[3]; + uint8_t cypher_code; + uint8_t language; + uint8_t secret_track; + uint8_t level_complete_track; + uint16_t reserved4[2]; +} GAME_FLOW; + +typedef struct __unaligned { // decompiled + int16_t mesh_count; + int16_t mesh_idx; + int32_t bone_idx; + int16_t *frame_base; // TODO: make me FRAME_INFO + + void (*initialise)(int16_t item_num); + void (*control)(int16_t item_num); + void (*floor)( + const ITEM *item, int32_t x, int32_t y, int32_t z, + int32_t *height); + void (*ceiling)( + const ITEM *item, int32_t x, int32_t y, int32_t z, + int32_t *height); + void (*draw_routine)(const ITEM *item); + void (*collision)(int16_t + item_num, ITEM *lara_item, COLL_INFO *coll); + + int16_t anim_idx; + int16_t hit_points; + int16_t pivot_length; + int16_t radius; + int16_t shadow_size; + + union { + uint16_t flags; + struct { + uint16_t loaded: 1; // 0x01 1 + uint16_t intelligent: 1; // 0x02 2 + uint16_t save_position: 1; // 0x04 4 + uint16_t save_hitpoints: 1; // 0x08 8 + uint16_t save_flags: 1; // 0x10 16 + uint16_t save_anim: 1; // 0x20 32 + uint16_t semi_transparent: 1; // 0x40 64 + uint16_t water_creature: 1; // 0x80 128 + uint16_t pad : 8; + }; + }; +} OBJECT; + +typedef struct __unaligned { + GAME_VECTOR pos; + GAME_VECTOR target; + CAMERA_TYPE type; + int32_t shift; + uint32_t flags; + int32_t fixed_camera; + int32_t num_frames; + int32_t bounce; + int32_t underwater; + int32_t target_distance; + int32_t target_square; + int16_t target_angle; + int16_t actual_angle; + int16_t target_elevation; + int16_t box; + int16_t num; + int16_t last; + int16_t timer; + int16_t speed; + ITEM *item; + ITEM *last_item; + OBJECT_VECTOR *fixed; + int32_t is_lara_mic; + XYZ_32 mic_pos; +} CAMERA_INFO; + +typedef struct __unaligned { // decompiled + int16_t *frame_base; + int16_t frame_num; + int16_t anim_num; + int16_t lock; + struct __unaligned { + int16_t y; + int16_t x; + int16_t z; + } rot; // TODO: XYZ_16 + int16_t flash_gun; +} LARA_ARM; + +typedef struct __unaligned { // decompiled + int32_t ammo; +} AMMO_INFO; + +typedef enum { // decompiled + LWS_ABOVE_WATER = 0, + LWS_UNDERWATER = 1, + LWS_SURFACE = 2, + LWS_CHEAT = 3, + LWS_WADE = 4, +} LARA_WATER_STATE; + +typedef struct __unaligned { // decompiled + int16_t item_num; + int16_t gun_status; + int16_t gun_type; + int16_t request_gun_type; + int16_t last_gun_type; + int16_t calc_fallspeed; + int16_t water_status; + int16_t climb_status; + int16_t pose_count; + int16_t hit_frame; + int16_t hit_direction; + int16_t air; + int16_t dive_count; + int16_t death_timer; + int16_t current_active; + int16_t spaz_effect_count; + int16_t flare_age; + int16_t skidoo; + int16_t weapon_item; + int16_t back_gun; + int16_t flare_frame; + uint16_t flare_control_left: 1; // 0x01 1 + uint16_t flare_control_right: 1; // 0x02 2 + uint16_t extra_anim: 1; // 0x04 4 + uint16_t look: 1; // 0x08 8 + uint16_t burn: 1; // 0x10 16 + uint16_t pad: 11; + int32_t water_surface_dist; + XYZ_32 last_pos; + FX *spaz_effect; + uint32_t mesh_effects; + int16_t *mesh_ptrs[15]; + ITEM *target; + int16_t target_angles[2]; + int16_t turn_rate; + int16_t move_angle; + int16_t head_y_rot; + int16_t head_x_rot; + int16_t head_z_rot; + int16_t torso_y_rot; + int16_t torso_x_rot; + int16_t torso_z_rot; + LARA_ARM left_arm; + LARA_ARM right_arm; + AMMO_INFO pistol_ammo; + AMMO_INFO magnum_ammo; + AMMO_INFO uzi_ammo; + AMMO_INFO shotgun_ammo; + AMMO_INFO harpoon_ammo; + AMMO_INFO grenade_ammo; + AMMO_INFO m16_ammo; + CREATURE *creature; +} LARA_INFO; + +typedef enum { + SFX_LARA_FEET = 0, + SFX_LARA_CLIMB2 = 1, + SFX_LARA_NO = 2, + SFX_LARA_SLIPPING = 3, + SFX_LARA_LAND = 4, + SFX_LARA_CLIMB1 = 5, + SFX_LARA_DRAW = 6, + SFX_LARA_HOLSTER = 7, + SFX_LARA_FIRE = 8, + SFX_LARA_RELOAD = 9, + SFX_LARA_RICOCHET = 10, + SFX_LARA_FLARE_IGNITE = 11, + SFX_LARA_FLARE_BURN = 12, + SFX_LARA_HARPOON_FIRE = 15, + SFX_LARA_HARPOON_LOAD = 16, + SFX_LARA_WET_FEET = 17, + SFX_LARA_WADE = 18, + SFX_LARA_TREAD = 20, + SFX_LARA_FIRE_MAGNUMS = 21, + SFX_LARA_HARPOON_LOAD_WATER = 22, + SFX_LARA_HARPOON_FIRE_WATER = 23, + SFX_MASSIVE_CRASH = 24, + SFX_PUSH_SWITCH = 25, + SFX_LARA_CLIMB3 = 26, + SFX_LARA_BODYSL = 27, + SFX_LARA_SHIMMY = 28, + SFX_LARA_JUMP = 29, + SFX_LARA_FALL = 30, + SFX_LARA_INJURY = 31, + SFX_LARA_ROLL = 32, + SFX_LARA_SPLASH = 33, + SFX_LARA_GETOUT = 34, + SFX_LARA_SWIM = 35, + SFX_LARA_BREATH = 36, + SFX_LARA_BUBBLES = 37, + SFX_LARA_SWITCH = 38, + SFX_LARA_KEY = 39, + SFX_LARA_OBJECT = 40, + SFX_LARA_GENERAL_DEATH = 41, + SFX_LARA_KNEES_DEATH = 42, + SFX_LARA_UZI_FIRE = 43, + SFX_LARA_UZI_STOP = 44, + SFX_LARA_SHOTGUN = 45, + SFX_LARA_BLOCK_PUSH1 = 46, + SFX_LARA_BLOCK_PUSH2 = 47, + SFX_CLICK = 48, + SFX_LARA_HIT = 49, + SFX_LARA_BULLETHIT = 50, + SFX_LARA_BLKPULL = 51, + SFX_LARA_FLOATING = 52, + SFX_LARA_FALLDETH = 53, + SFX_LARA_GRABHAND = 54, + SFX_LARA_GRABBODY = 55, + SFX_LARA_GRABFEET = 56, + SFX_LARA_SWITCHUP = 57, + SFX_GLASS_BREAK = 58, + SFX_WATER_LOOP = 59, + SFX_UNDERWATER = 60, + SFX_UNDERWATER_SWITCH = 61, + SFX_LARA_PICKUP = 62, + SFX_BLOCK_SOUND = 63, + SFX_DOOR = 64, + SFX_SWING = 65, + SFX_ROCK_FALL_CRUMBLE = 66, + SFX_ROCK_FALL_LAND = 67, + SFX_ROCK_FALL_SOLID = 68, + SFX_ENEMY_FEET = 69, + SFX_ENEMY_GRUNT = 70, + SFX_ENEMY_HIT1 = 71, + SFX_ENEMY_HIT2 = 72, + SFX_ENEMY_DEATH1 = 73, + SFX_ENEMY_JUMP = 74, + SFX_ENEMY_CLIMBUP = 75, + SFX_ENEMY_CLIMBDOWN = 76, + SFX_WEAPON_CLATTER = 77, + SFX_M16_FIRE = 78, + SFX_WATERFALL_LOOP = 79, + SFX_SWORD_STATUE_DROP = 80, + SFX_SWORD_STATUE_LIFT = 81, + SFX_PORTCULLIS_UP = 82, + SFX_PORTCULLIS_DOWN = 83, + SFX_DOG_FEET1 = 84, + SFX_BODY_SLAM = 85, + SFX_DOG_BARK1 = 86, + SFX_DOG_FEET2 = 87, + SFX_DOG_BARK2 = 88, + SFX_DOG_DEATH = 89, + SFX_DOG_PANT = 90, + SFX_LEOPARD_FEET = 91, + SFX_LEOPARD_ROAR = 92, + SFX_LEOPARD_BITE = 93, + SFX_LEOPARD_STRIKE = 94, + SFX_LEOPARD_DEATH = 95, + SFX_LEOPARD_GROWL = 96, + SFX_RAT_ATTACK = 97, + SFX_RAT_DEATH = 98, + SFX_TIGER_ROAR = 99, + SFX_TIGER_BITE = 100, + SFX_TIGER_STRIKE = 101, + SFX_TIGER_DEATH = 102, + SFX_TIGER_GROWL = 103, + SFX_M16_STOP = 104, + SFX_EXPLOSION1 = 105, + SFX_GROWL = 106, + SFX_SPIDER_JUMP = 107, + SFX_MENU_ROTATE = 108, + SFX_MENU_LARA_HOME = 109, + SFX_MENU_SPININ = 111, + SFX_MENU_SPINOUT = 112, + SFX_MENU_STOPWATCH = 113, + SFX_MENU_GUNS = 114, + SFX_MENU_PASSPORT = 115, + SFX_MENU_MEDI = 116, + SFX_ENEMY_HEELS = 117, + SFX_ENEMY_FIRE_SILENCER = 118, + SFX_ENEMY_AH_DYING = 119, + SFX_ENEMY_OOH_DYING = 120, + SFX_ENEMY_THUMP = 121, + SFX_SPIDER_MOVING = 122, + SFX_LARA_MINI_LOAD = 123, + SFX_LARA_MINI_LOCK = 124, + SFX_LARA_MINI_FIRE = 125, + SFX_SPIDER_BITE = 126, + SFX_SLAM_DOOR_SLIDE = 127, + SFX_SLAM_DOOR_CLOSE = 128, + SFX_EAGLE_SQUAWK = 129, + SFX_EAGLE_WING_FLAP = 130, + SFX_EAGLE_DEATH = 131, + SFX_CROW_CAW = 132, + SFX_CROW_WING_FLAP = 133, + SFX_CROW_DEATH = 134, + SFX_CROW_ATTACK = 135, + SFX_ENEMY_GUN_COCKING = 136, + SFX_ENEMY_FIRE1 = 137, + SFX_ENEMY_FIRE_TWIRL = 138, + SFX_ENEMY_HOLSTER = 139, + SFX_ENEMY_BREATH1 = 140, + SFX_ENEMY_CHUCKLE = 141, + SFX_MONK_POY = 142, + SFX_MONK_DEATH = 143, + SFX_LARA_SPIKE_DEATH = 145, + SFX_LARA_DEATH3 = 146, + SFX_ROLLING_BALL = 147, + SFX_SANDBAG_SNAP = 148, + SFX_SANDBAG_HIT = 149, + SFX_LOOP_FOR_SMALL_FIRES = 150, + SFX_SKIDOO_START = 152, + SFX_SKIDOO_IDLE = 153, + SFX_SKIDOO_ACCELERATE = 154, + SFX_SKIDOO_MOVING = 155, + SFX_SKIDOO_STOP = 156, + SFX_ENEMY_FIRE2 = 157, + SFX_ENEMY_DEATH2 = 158, + SFX_ENEMY_BREATH2 = 159, + SFX_STICK_TAP = 160, + SFX_TRAPDOOR_OPEN = 161, + SFX_TRAPDOOR_CLOSE = 162, + SFX_YETI_GROWL = 163, + SFX_YETI_CHEST_BEAT = 164, + SFX_YETI_THUMP = 165, + SFX_YETI_GRUNT1 = 166, + SFX_YETI_SCREAM = 167, + SFX_YETI_DEATH = 168, + SFX_YETI_GROWL1 = 169, + SFX_YETI_GROWL2 = 170, + SFX_YETI_GRUNT2 = 171, + SFX_YETI_GROWL3 = 172, + SFX_YETI_FEET = 173, + SFX_ENEMY_HEAVY_BREATH = 174, + SFX_ENEMY_FLAMETHROWER_FIRE = 175, + SFX_ENEMY_FLAMETHROWER_SCRAPE = 176, + SFX_ENEMY_FLAMETHROWER_CLICK = 177, + SFX_ENEMY_FLAMETHROWER_DEATH = 178, + SFX_ENEMY_FLAMETHROWER_FALL = 179, + SFX_ENEMY_BELT_JINGLE = 180, + SFX_ENEMY_WRENCH = 181, + SFX_FOOTSTEP = 182, + SFX_FOOTSTEP_HIT = 183, + SFX_ENEMY_COCKING_SHOTGUN = 184, + SFX_SCUBA_DIVER_FLIPPER = 186, + SFX_SCUBA_DIVER_BREATH = 188, + SFX_PULLEY_CRANE = 190, + SFX_CURTAIN = 191, + SFX_SCUBA_DIVER_DEATH = 192, + SFX_SCUBA_DIVER_DIVING = 193, + SFX_BOAT_START = 194, + SFX_BOAT_IDLE = 195, + SFX_BOAT_ACCELERATE = 196, + SFX_BOAT_MOVING = 197, + SFX_BOAT_STOP = 198, + SFX_BOAT_SLOW_DOWN = 199, + SFX_BOAT_HIT = 200, + SFX_CLATTER_1 = 201, + SFX_CLATTER_2 = 202, + SFX_CLATTER_3 = 203, + SFX_DOOR_SLIDE = 204, + SFX_LARA_FLESH_WOUND = 205, + SFX_SAW_REVVING = 206, + SFX_SAW_STOP = 207, + SFX_DOOR_CHIME = 208, + SFX_CHAIN_CREAK_SNAP = 209, + SFX_SWINGING = 210, + SFX_BREAKING1 = 211, + SFX_PULLEY_MOVE = 212, + SFX_AIRPLANE_IDLE = 213, + SFX_UNDERWATER_FAN_ON = 215, + SFX_SMALL_FAN_ON = 217, + SFX_SWINGING_BOX_BAG = 218, + SFX_JUMP_PAD_UP = 219, + SFX_JUMP_PAD_DOWN = 220, + SFX_BREAKING2 = 221, + SFX_SNOWBALL_ROLL = 222, + SFX_SNOWBALL_STOP = 223, + SFX_ROLLING = 224, + SFX_ROLLING_STOP1 = 225, + SFX_ROLLING_STOP2 = 226, + SFX_ROLLING2 = 227, + SFX_ROLLING2_HIT = 228, + SFX_SIDE_BLADE_SWING = 229, + SFX_SIDE_BLADE_BACK = 230, + SFX_ROLLING_BLADE = 231, + SFX_ICILE_DETACH = 232, + SFX_ICICLE_HIT = 233, + SFX_ROTATING_HANDLE_LOOSE = 234, + SFX_ROTATING_HANDLE_TURN = 235, + SFX_ROTATING_HANDLE_OPEN = 236, + SFX_ROTATING_HANDLE_CREAK = 237, + SFX_MONK_FEET = 238, + SFX_MONK_SWORD_SWING1 = 239, + SFX_MONK_SWORD_SWING2 = 240, + SFX_MONK_SHOUT1 = 241, + SFX_MONK_SHOUT2 = 242, + SFX_MONK_SHOUT3 = 243, + SFX_MONK_SHOUT4 = 244, + SFX_MONK_CRUNCH = 245, + SFX_MONK_BREATH = 246, + SFX_SPLASH_SURFACE = 247, + SFX_WATERFALL1 = 248, + SFX_ENEMY_FEET_SNOW = 249, + SFX_ENEMY_FIRE3 = 250, + SFX_ENEMY_FIRE_SEMIAUTO = 251, + SFX_ENEMY_DEATH3 = 252, + SFX_ENEMY_DEATH4 = 253, + SFX_CIRCLE_BLADE = 254, + SFX_KNIFETHROWER_FEET = 255, + SFX_MONK_OYE = 256, + SFX_MONK_AWEH = 257, + SFX_CIRCLE_BLADE_HIT = 258, + SFX_KNIFETHROWER_WARRIOR_FEET = 259, + SFX_WARRIOR_BLADE_SWING1 = 260, + SFX_WARRIOR_BLADE_SWING2 = 261, + SFX_WARRIOR_GROWL = 262, + SFX_KNIFETHROWER_HICCUP = 263, + SFX_WARROPR_BURP = 264, + SFX_WARRIOR_GROWL1 = 265, + SFX_WARRIOR_WAKE = 267, + SFX_WARRIOR_GROWL2 = 268, + SFX_SMALL_SWITCH = 269, + SFX_CHAIN_PULLEY = 278, + SFX_DEATH_SLIDE_GRAB = 279, + SFX_DEATH_SLIDE_GO = 280, + SFX_DEATH_SLIDE_STOP = 281, + SFX_BODY_SLUMP = 282, + SFX_BOWL_TIPPING = 283, + SFX_BOWL_POUR = 284, + SFX_WATERFALL2 = 285, + SFX_ELEVATOR_OPEN = 286, + SFX_ELEVATOR_CLOSE = 287, + SFX_MINISUB_CLATTER1 = 288, + SFX_MINISUB_CLATTER2 = 289, + SFX_MINISUB_CLATTER3 = 290, + SFX_BIRD_MONSTER_SCREAM = 291, + SFX_BIRD_MONSTER_GASP = 292, + SFX_BIRD_MONSTER_BREATH = 293, + SFX_BIRD_MONSTER_FEET = 294, + SFX_BIRD_MONSTER_DEATH = 295, + SFX_BIRD_MONSTER_SCRAPE = 296, + SFX_HELICOPTER_LOOP = 297, + SFX_DRAGON_FEET = 298, + SFX_DRAGON_GROWL1 = 299, + SFX_DRAGON_GROWL2 = 300, + SFX_DRAGON_FALL = 301, + SFX_DRAGON_BREATH = 302, + SFX_DRAGON_GROWL3 = 303, + SFX_DRAGON_GRUNT = 304, + SFX_DRAGON_FIRE = 305, + SFX_DRAGON_LEG_LIFT = 306, + SFX_DRAGON_LEG_HIT = 307, + SFX_WARRIOR_BLADE_SWING3 = 308, + SFX_WARRIOR_BLADE_SWING_FAST = 309, + SFX_WARRIOR_BREATH_ACTIVE = 311, + SFX_WARRIOR_HOVER = 312, + SFX_WARRIOR_LANDING = 313, + SFX_WARRIOR_SWORD_CLANK = 314, + SFX_WARRIOR_SWORD_SLICE = 315, + SFX_BIRDS_CHIRP = 316, + SFX_CRUNCH1 = 317, + SFX_CRUNCH2 = 318, + SFX_DOOR_CREAK = 319, + SFX_BREAKING3 = 320, + SFX_BIG_SPIDER_SNARL = 321, + SFX_BIG_SPIDER_FEET = 322, + SFX_BIG_SPIDER_DEATH = 323, + SFX_T_REX_ROAR = 324, + SFX_T_REX_FEET = 325, + SFX_T_REX_GROWL1 = 326, + SFX_T_REX_DEATH = 327, + SFX_DRIPS_REVERB = 329, + SFX_STAGE_BACKDROP = 330, + SFX_STONE_DOOR_SLIDE = 331, + SFX_PLATFORM_ALARM = 332, + SFX_TICK_TOCK = 333, + SFX_DOORBELL = 334, + SFX_BURGLAR_ALARM = 335, + SFX_BOAT_ENGINE = 336, + SFX_BOAT_INTO_WATER = 337, + SFX_UNKNOWN1 = 338, + SFX_UNKNOWN2 = 339, + SFX_UNKNOWN3 = 340, + SFX_MARCO_BARTOLLI_TRANSFORM = 341, + SFX_WINSTON_SHUFFLE = 342, + SFX_WINSTON_FEET = 343, + SFX_WINSTON_GRUNT1 = 344, + SFX_WINSTON_GRUNT2 = 345, + SFX_WINSTON_GRUNT3 = 346, + SFX_WINSTON_CUPS = 347, + SFX_BRITTLE_GROUND_BREAK = 348, + SFX_SPIDER_EXPLODE = 349, + SFX_SHARK_BITE = 350, + SFX_LAVA_BUBBLES = 351, + SFX_EXPLOSION2 = 352, + SFX_BURGLARS = 353, + SFX_ZIPPER = 354, + SFX_NUMBER_OF = 370, +} SOUND_EFFECT_ID; + +typedef enum { + SPM_NORMAL = 0, + SPM_UNDERWATER = 1, + SPM_ALWAYS = 2, + SPM_PITCH = 4, +} SOUND_PLAY_MODE; + +typedef enum { + CF_NORMAL = 0, + CF_FOLLOW_CENTRE = 1, + CF_NO_CHUNKY = 2, + CF_CHASE_OBJECT = 3, +} CAMERA_FLAGS; + +typedef enum { + FBBOX_MIN_X = 0, + FBBOX_MAX_X = 1, + FBBOX_MIN_Y = 2, + FBBOX_MAX_Y = 3, + FBBOX_MIN_Z = 4, + FBBOX_MAX_Z = 5, + FBBOX_X = 6, + FBBOX_Y = 7, + FBBOX_Z = 8, + FBBOX_ROT = 9, +} FRAME_BBOX_INFO; + +typedef enum { + BF_MATRIX_POP = 1, + BF_MATRIX_PUSH = 2, + BF_ROT_X = 4, + BF_ROT_Y = 8, + BF_ROT_Z = 16, +} BONE_FLAGS; + +typedef struct __unaligned { + int16_t tx; + int16_t ty; + int16_t tz; + int16_t cx; + int16_t cy; + int16_t cz; + int16_t fov; + int16_t roll; +} CINE_FRAME; + +typedef enum { // decompiled + IF_ONE_SHOT = 0x0100, + IF_CODE_BITS = 0x3E00, + IF_REVERSE = 0x4000, + IF_INVISIBLE = 0x0100, + IF_KILLED = 0x8000, +} ITEM_FLAG; + +typedef enum { // decompiled + IS_INACTIVE = 0, + IS_ACTIVE = 1, + IS_DEACTIVATED = 2, + IS_INVISIBLE = 3, +} ITEM_STATUS; + +typedef struct __unaligned { + uint16_t key[14]; // INPUT_ROLE_NUMBER_OF +} CONTROL_LAYOUT; + +typedef enum { // decompiled + IN_FORWARD = 0x00000001, + IN_BACK = 0x00000002, + IN_LEFT = 0x00000004, + IN_RIGHT = 0x00000008, + IN_JUMP = 0x00000010, + IN_DRAW = 0x00000020, + IN_ACTION = 0x00000040, + IN_SLOW = 0x00000080, + IN_OPTION = 0x00000100, + IN_LOOK = 0x00000200, + IN_STEP_LEFT = 0x00000400, + IN_STEP_RIGHT = 0x00000800, + IN_ROLL = 0x00001000, + IN_PAUSE = 0x00002000, + IN_RESERVED1 = 0x00004000, + IN_RESERVED2 = 0x00008000, + IN_DOZY_CHEAT = 0x00010000, + IN_STUFF_CHEAT = 0x00020000, + IN_DEBUG_INFO = 0x00040000, + IN_FLARE = 0x00080000, + IN_SELECT = 0x00100000, + IN_DESELECT = 0x00200000, + IN_SAVE = 0x00400000, + IN_LOAD = 0x00800000, +} INPUT_STATE; + +typedef enum { // decompiled + LA_RUN = 0, + LA_WALK_FORWARD = 1, + LA_WALK_STOP_RIGHT = 2, + LA_WALK_STOP_LEFT = 3, + LA_WALK_TO_RUN_RIGHT = 4, + LA_WALK_TO_RUN_LEFT = 5, + LA_RUN_START = 6, + LA_RUN_TO_WALK_RIGHT = 7, + LA_RUN_TO_STAND_LEFT = 8, + LA_RUN_TO_WALK_LEFT = 9, + LA_RUN_TO_STAND_RIGHT = 10, + LA_STAND_STILL = 11, + LA_TURN_RIGHT_SLOW = 12, + LA_TURN_LEFT_SLOW = 13, + LA_JUMP_FORWARD_LAND_START = 14, + LA_JUMP_FORWARD_LAND_END_UNUSED = 15, + LA_RUN_JUMP_RIGHT_START = 16, + LA_RUN_JUMP_RIGHT_CONTINUE = 17, + LA_RUN_JUMP_LEFT_START = 18, + LA_RUN_JUMP_LEFT_CONTINUE = 19, + LA_WALK_FORWARD_START = 20, + LA_WALK_FORWARD_START_CONTINUE = 21, + LA_JUMP_FORWARD_TO_FREEFALL = 22, + LA_FREEFALL = 23, + LA_FREEFALL_LAND = 24, + LA_FREEFALL_LAND_DEATH = 25, + LA_STAND_TO_JUMP_UP = 26, + LA_STAND_TO_JUMP_UP_CONTINUE = 27, + LA_JUMP_UP = 28, + LA_JUMP_UP_TO_HANG = 29, + LA_JUMP_UP_TO_FREEFALL = 30, + LA_JUMP_UP_LAND = 31, + LA_SMASH_JUMP = 32, + LA_SMASH_JUMP_CONTINUE = 33, + LA_FALL_START = 34, + LA_FALL = 35, + LA_FALL_TO_FREEFALL = 36, + LA_HANG_TO_FREEFALL = 37, + LA_WALK_BACK_END_RIGHT = 38, + LA_WALK_BACK_END_LEFT = 39, + LA_WALK_BACK = 40, + LA_WALK_BACK_START = 41, + LA_CLIMB_3CLICK = 42, + LA_CLIMB_3CLICK_END_TO_RUN = 43, + LA_TURN_RIGHT = 44, + LA_JUMP_FORWARD_TO_FREEFALL_2 = 45, + LA_REACH_TO_FREEFALL = 46, + LA_ROLL_ALTERNATE = 47, + LA_ROLL_END_ALTERNATE = 48, + LA_JUMP_FORWARD_END_TO_FREEFALL = 49, + LA_CLIMB_2CLICK = 50, + LA_CLIMB_2CLICK_END = 51, + LA_CLIMB_2CLICK_END_TO_RUN = 52, + LA_WALL_SMASH_LEFT = 53, + LA_WALL_SMASH_RIGHT = 54, + LA_RUN_UP_STEP_RIGHT = 55, + LA_RUN_UP_STEP_LEFT = 56, + LA_WALK_UP_STEP_RIGHT = 57, + LA_WALK_UP_STEP_LEFT = 58, + LA_WALK_DOWN_LEFT = 59, + LA_WALK_DOWN_RIGHT = 60, + LA_WALK_DOWN_BACK_LEFT = 61, + LA_WALK_DOWN_BACK_RIGHT = 62, + LA_WALLSWITCH_DOWN = 63, + LA_WALLSWITCH_UP = 64, + LA_SIDESTEP_LEFT = 65, + LA_SIDESTEP_LEFT_END = 66, + LA_SIDESTEP_RIGHT = 67, + LA_SIDESTEP_RIGHT_END = 68, + LA_ROTATE_LEFT = 69, + LA_SLIDE_FORWARD = 70, + LA_SLIDE_FORWARD_END = 71, + LA_SLIDE_FORWARD_STOP = 72, + LA_STAND_TO_JUMP = 73, + LA_JUMP_BACK_START = 74, + LA_JUMP_BACK = 75, + LA_JUMP_FORWARD_START = 76, + LA_JUMP_FORWARD = 77, + LA_JUMP_LEFT_START = 78, + LA_JUMP_LEFT = 79, + LA_JUMP_RIGHT_START = 80, + LA_JUMP_RIGHT = 81, + LA_LAND = 82, + LA_JUMP_BACK_TO_FREEFALL = 83, + LA_JUMP_LEFT_TO_FREEFALL = 84, + LA_JUMP_RIGHT_TO_FREEFALL = 85, + LA_UNDERWATER_SWIM_FORWARD = 86, + LA_UNDERWATER_SWIM_FORWARD_DRIFT = 87, + LA_SMALL_JUMP_BACK_START = 88, + LA_SMALL_JUMP_BACK = 89, + LA_SMALL_JUMP_BACK_END = 90, + LA_JUMP_UP_START = 91, + LA_LAND_TO_RUN = 92, + LA_FALL_BACK = 93, + LA_JUMP_FORWARD_TO_REACH = 94, + LA_REACH = 95, + LA_REACH_TO_HANG = 96, + LA_CLIMB_ON = 97, + LA_REACH_TO_FREEFALL_2 = 98, + LA_FALL_CROUCHING_LANDING = 99, + LA_JUMP_FORWARD_TO_REACH_LATE = 100, + LA_JUMP_FORWARD_START_TO_REACH_ALTERNATE = 101, + LA_CLIMB_ON_END = 102, + LA_STAND_IDLE = 103, + LA_SLIDE_BACKWARD_START = 104, + LA_SLIDE_BACKWARD = 105, + LA_SLIDE_BACKWARD_END = 106, + LA_UNDERWATER_SWIM_TO_IDLE = 107, + LA_UNDERWATER_IDLE = 108, + LA_UNDERWARER_IDLE_TO_SWIM = 109, + LA_ONWATER_IDLE = 110, + LA_ONWATER_TO_STAND_HIGH = 111, + LA_FREEFALL_TO_UNDERWATER = 112, + LA_ONWATER_DIVE_ALTERNATE = 113, + LA_UNDERWATER_TO_ONWATER = 114, + LA_ONWATER_SWIM_FORWARD_DIVE = 115, + LA_ONWATER_SWIM_FORWARD = 116, + LA_ONWATER_SWIM_FORWARD_TO_IDLE = 117, + LA_ONWATER_IDLE_TO_SWIM_FORWARD = 118, + LA_ONWATER_DIVE = 119, + LA_PUSHABLE_GRAB = 120, + LA_PUSHABLE_RELEASE = 121, + LA_PUSHABLE_PULL = 122, + LA_PUSHABLE_PUSH = 123, + LA_UNDERWATER_DEATH = 124, + LA_HIT_FRONT = 125, + LA_HIT_BACK = 126, + LA_HIT_LEFT = 127, + LA_HIT_RIGHT = 128, + LA_UNDERWATER_SWITCH = 129, + LA_UNDERWATER_PICKUP = 130, + LA_USE_KEY = 131, + LA_ONWATER_DEATH = 132, + LA_RUN_DEATH = 133, + LA_USE_PUZZLE = 134, + LA_PICKUP = 135, + LA_SHIMMY_LEFT = 136, + LA_SHIMMY_RIGHT = 137, + LA_STAND_DEATH = 138, + LA_BOULDER_DEATH = 139, + LA_ONWATER_IDLE_TO_SWIM_BACK = 140, + LA_ONWATER_SWIM_BACK = 141, + LA_ONWATER_SWIM_BACK_TO_IDLE = 142, + LA_ONWATER_SWIM_LEFT = 143, + LA_ONWATER_SWIM_RIGHT = 144, + LA_DEATH_JUMP = 145, + LA_ROLL_START = 146, + LA_ROLL_CONTINUE = 147, + LA_ROLL_END = 148, + LA_SPIKE_DEATH = 149, + LA_REACH_TO_THIN_LEDGE = 150, + LA_SWANDIVE_ROLL = 151, + LA_SWANDIVE_TO_UNDERWATER = 152, + LA_FREEFALL_SWANDIVE = 153, + LA_FREEFALL_SWANDIVE_TO_UNDERWATER = 154, + LA_SWANDIVE_DEATH = 155, + LA_SWANDIVE_LEFT = 156, + LA_SWANDIVE_RIGHT = 157, + LA_SWANDIVE_START = 158, + LA_CLIMB_ON_HANDSTAND = 159, + LA_STAND_TO_LADDER = 160, + LA_LADDER_UP = 161, + LA_LADDER_UP_STOP_RIGHT = 162, + LA_LADDER_UP_STOP_LEFT = 163, + LA_LADDER_IDLE = 164, + LA_LADDER_UP_START = 165, + LA_LADDER_DOWN_STOP_LEFT = 166, + LA_LADDER_DOWN_STOP_RIGHT = 167, + LA_LADDER_DOWN = 168, + LA_LADDER_DOWN_START = 169, + LA_LADDER_RIGHT = 170, + LA_LADDER_LEFT = 171, + LA_LADDER_HANG = 172, + LA_LADDER_HANG_TO_IDLE = 173, + LA_LADDER_CLIMB_ON = 174, + LA_UNKNOWN = 175, + LA_ONWATER_TO_WADE_SHALLOW = 176, + LA_WADE = 177, + LA_RUN_TO_WADE_LEFT = 178, + LA_RUN_TO_WADE_RIGHT = 179, + LA_WADE_TO_RUN_LEFT = 180, + LA_WADE_TO_RUN_RIGHT = 181, + LA_LADDER_BACKFLIP_START = 182, + LA_LADDER_BACKFLIP_CONTINUE = 183, + LA_WADE_TO_STAND_RIGHT = 184, + LA_WADE_TO_STAND_LEFT = 185, + LA_STAND_TO_WADE = 186, + LA_LADDER_UP_HANGING = 187, + LA_LADDER_DOWN_HANGING = 188, + LA_FLARE_THROW = 189, + LA_ONWATER_TO_WADE = 190, + LA_ONWATER_TO_STAND_MEDIUM = 191, + LA_UNDERWATER_TO_STAND = 192, + LA_ONWATER_TO_WADE_LOW = 193, + LA_LADDER_TO_HANG_DOWN = 194, + LA_SWITCH_SMALL_DOWN = 195, + LA_SWITCH_SMALL_UP = 196, + LA_BUTTON_PUSH = 197, + LA_UNDERWATER_SWIM_TO_STILL_HUDDLE = 198, + LA_UNDERWATER_SWIM_TO_STILL_SPRAWL = 199, + LA_UNDERWATER_SWIM_TO_STILL_MEDIUM = 200, + LA_LADDER_TO_HANG_RIGHT = 201, + LA_LADDER_TO_HANG_LEFT = 202, + LA_UNDERWATER_ROLL_START = 203, + LA_FLARE_PICKUP = 204, + LA_UNDERWATER_ROLL_END = 205, + LA_UNDERWATER_FLARE_PICKUP = 206, + LA_RUN_JUMP_ROLL_START = 207, + LA_SOMERSAULT = 208, + LA_RUN_JUMP_ROLL_END = 209, + LA_JUMP_FORWARD_ROLL_START = 210, + LA_JUMP_FORWARD_ROLL_END = 211, + LA_JUMP_BACK_ROLL_START = 212, + LA_JUMP_BACK_ROLL_END = 213, + LA_KICK = 214, + LA_ZIPLINE_GRAB = 215, + LA_ZIPLINE_RIDE = 216, + LA_ZIPLINE_FALL = 217, +} LARA_ANIMATION; + +typedef enum { // decompiled + LA_EXTRA_BREATH = 0, + LA_EXTRA_PLUNGER = 1, + LA_EXTRA_YETI_KILL = 2, + LA_EXTRA_SHARK_KILL = 3, + LA_EXTRA_AIRLOCK = 4, + LA_EXTRA_GONG_BONG = 5, + LA_EXTRA_DINO_KILL = 6, + LA_EXTRA_PULL_DAGGER = 7, + LA_EXTRA_START_ANIM = 8, + LA_EXTRA_START_HOUSE = 9, + LA_EXTRA_FINAL_ANIM = 10, +} LARA_EXTRA_ANIMATION; + +typedef enum { // decompiled + LS_WALK = 0, + LS_RUN = 1, + LS_STOP = 2, + LS_FORWARD_JUMP = 3, + LS_POSE = 4, + LS_FAST_BACK = 5, + LS_TURN_RIGHT = 6, + LS_TURN_LEFT = 7, + LS_DEATH = 8, + LS_FAST_FALL = 9, + LS_HANG = 10, + LS_REACH = 11, + LS_SPLAT = 12, + LS_TREAD = 13, + LS_LAND = 14, + LS_COMPRESS = 15, + LS_BACK = 16, + LS_SWIM = 17, + LS_GLIDE = 18, + LS_NULL = 19, + LS_FAST_TURN = 20, + LS_STEP_RIGHT = 21, + LS_STEP_LEFT = 22, + LS_HIT = 23, + LS_SLIDE = 24, + LS_BACK_JUMP = 25, + LS_RIGHT_JUMP = 26, + LS_LEFT_JUMP = 27, + LS_UP_JUMP = 28, + LS_FALL_BACK = 29, + LS_HANG_LEFT = 30, + LS_HANG_RIGHT = 31, + LS_SLIDE_BACK = 32, + LS_SURF_TREAD = 33, + LS_SURF_SWIM = 34, + LS_DIVE = 35, + LS_PUSH_BLOCK = 36, + LS_PULL_BLOCK = 37, + LS_PP_READY = 38, + LS_PICKUP = 39, + LS_SWITCH_ON = 40, + LS_SWITCH_OFF = 41, + LS_USE_KEY = 42, + LS_USE_PUZZLE = 43, + LS_UW_DEATH = 44, + LS_ROLL = 45, + LS_SPECIAL = 46, + LS_SURF_BACK = 47, + LS_SURF_LEFT = 48, + LS_SURF_RIGHT = 49, + LS_USE_MIDAS = 50, + LS_DIE_MIDAS = 51, + LS_SWAN_DIVE = 52, + LS_FAST_DIVE = 53, + LS_GYMNAST = 54, + LS_WATER_OUT = 55, + LS_CLIMB_STANCE = 56, + LS_CLIMBING = 57, + LS_CLIMB_LEFT = 58, + LS_CLIMB_END = 59, + LS_CLIMB_RIGHT = 60, + LS_CLIMB_DOWN = 61, + LS_LARA_TEST1 = 62, + LS_LARA_TEST2 = 63, + LS_LARA_TEST3 = 64, + LS_WADE = 65, + LS_WATER_ROLL = 66, + LS_FLARE_PICKUP = 67, + LS_TWIST = 68, + LS_KICK = 69, + LS_DEATH_SLIDE = 70, + LS_DUCK = 71, + LS_DUCK_ROLL = 72, + LS_DASH = 73, + LS_DASH_DIVE = 74, + LS_MONKEY_SWING = 75, + LS_MONKEYF = 76, + LS_LAST = 77, +} LARA_STATE; + +typedef enum { // decompiled + LGS_ARMLESS = 0, + LGS_HANDS_BUSY = 1, + LGS_DRAW = 2, + LGS_UNDRAW = 3, + LGS_READY = 4, + LGS_SPECIAL = 5, +} LARA_GUN_STATE; + +typedef enum { // decompiled + LGT_UNARMED = 0, + LGT_PISTOLS = 1, + LGT_MAGNUMS = 2, + LGT_UZIS = 3, + LGT_SHOTGUN = 4, + LGT_M16 = 5, + LGT_GRENADE = 6, + LGT_HARPOON = 7, + LGT_FLARE = 8 , + LGT_SKIDOO = 9, + NUM_WEAPONS = 10, +} LARA_GUN_TYPE; + +typedef enum { // decompiled + LM_HIPS = 0, + LM_THIGH_L = 1, + LM_CALF_L = 2, + LM_FOOT_L = 3, + LM_THIGH_R = 4, + LM_CALF_R = 5, + LM_FOOT_R = 6, + LM_TORSO = 7, + LM_UARM_R = 8, + LM_LARM_R = 9, + LM_HAND_R = 10, + LM_UARM_L = 11, + LM_LARM_L = 12, + LM_HAND_L = 13, + LM_HEAD = 14, + LM_NUMBER_OF = 15, +} LARA_MESH; + +typedef enum { // decompiled + NO_OBJECT = -1, + O_LARA = 0, + O_LARA_PISTOLS = 1, + O_LARA_HAIR = 2, + O_LARA_SHOTGUN = 3, + O_LARA_MAGNUMS = 4, + O_LARA_UZIS = 5, + O_LARA_M16 = 6, + O_LARA_GRENADE = 7, + O_LARA_HARPOON = 8, + O_LARA_FLARE = 9, + O_LARA_SKIDOO = 10, + O_LARA_BOAT = 11, + O_LARA_EXTRA = 12, + O_SKIDOO_FAST = 13, + O_BOAT = 14, + O_DOG = 15, + O_CULT_1 = 16, + O_CULT_1A = 17, + O_CULT_1B = 18, + O_CULT_2 = 19, + O_CULT_3 = 20, + O_MOUSE = 21, + O_DRAGON_FRONT = 22, + O_DRAGON_BACK = 23, + O_GONDOLA = 24, + O_SHARK = 25, + O_EEL = 26, + O_BIG_EEL = 27, + O_BARRACUDA = 28, + O_DIVER = 29, + O_WORKER_1 = 30, + O_WORKER_2 = 31, + O_WORKER_3 = 32, + O_WORKER_4 = 33, + O_WORKER_5 = 34, + O_JELLY = 35, + O_SPIDER = 36, + O_BIG_SPIDER = 37, + O_CROW = 38, + O_TIGER = 39, + O_BARTOLI = 40, + O_XIAN_SPEARMAN = 41, + O_XIAN_SPEARMAN_STATUE = 42, + O_XIAN_KNIGHT = 43, + O_XIAN_KNIGHT_STATUE = 44, + O_YETI = 45, + O_GIANT_YETI = 46, + O_EAGLE = 47, + O_BANDIT_1 = 48, + O_BANDIT_2 = 49, + O_BANDIT_2B = 50, + O_SKIDOO_ARMED = 51, + O_SKIDMAN = 52, + O_MONK_1 = 53, + O_MONK_2 = 54, + O_FALLING_BLOCK_1 = 55, + O_FALLING_BLOCK_2 = 56, + O_FALLING_BLOCK_3 = 57, + O_PENDULUM_1 = 58, + O_SPIKES = 59, + O_ROLLING_BALL_1 = 60, + O_DARTS = 61, + O_DART_EMITTER = 62, + O_DRAWBRIDGE = 63, + O_TEETH_TRAP = 64, + O_LIFT = 65, + O_GENERAL = 66, + O_MOVABLE_BLOCK_1 = 67, + O_MOVABLE_BLOCK_2 = 68, + O_MOVABLE_BLOCK_3 = 69, + O_MOVABLE_BLOCK_4 = 70, + O_BIG_BOWL = 71, + O_WINDOW_1 = 72, + O_WINDOW_2 = 73, + O_WINDOW_3 = 74, + O_WINDOW_4 = 75, + O_PROPELLER_1 = 76, + O_POWER_SAW = 77, + O_HOOK = 78, + O_FALLING_CEILING = 79, + O_SPINNING_BLADE = 80, + O_BLADE = 81, + O_KILLER_STATUE = 82, + O_ROLLING_BALL_2 = 83, + O_ICICLE = 84, + O_SPIKE_WALL = 85, + O_SPRING_BOARD = 86, + O_CEILING_SPIKES = 87, + O_BELL = 88, + O_WATER_SPRITE = 89, + O_SNOW_SPRITE = 90, + O_SKIDOO_LARA = 91, + O_SWITCH_TYPE_1 = 92, + O_SWITCH_TYPE_2 = 93, + O_PROPELLER_2 = 94, + O_PROPELLER_3 = 95, + O_PENDULUM_2 = 96, + O_MESH_SWAP_1 = 97, + O_MESH_SWAP_2 = 98, + O_LARA_SWAP = 99, + O_TEXT_BOX = 100, + O_ROLLING_BALL_3 = 101, + O_DEATH_SLIDE = 102, + O_SWITCH_TYPE_3 = 103, + O_SWITCH_TYPE_4 = 104, + O_SWITCH_TYPE_5 = 105, + O_DOOR_TYPE_1 = 106, + O_DOOR_TYPE_2 = 107, + O_DOOR_TYPE_3 = 108, + O_DOOR_TYPE_4 = 109, + O_DOOR_TYPE_5 = 110, + O_DOOR_TYPE_6 = 111, + O_DOOR_TYPE_7 = 112, + O_DOOR_TYPE_8 = 113, + O_TRAPDOOR_TYPE_1 = 114, + O_TRAPDOOR_TYPE_2 = 115, + O_TRAPDOOR_TYPE_3 = 116, + O_BRIDGE_FLAT = 117, + O_BRIDGE_TILT_1 = 118, + O_BRIDGE_TILT_2 = 119, + O_PASSPORT_OPTION = 120, + O_COMPASS_OPTION = 121, + O_PHOTO_OPTION = 122, + O_PLAYER_1 = 123, + O_PLAYER_2 = 124, + O_PLAYER_3 = 125, + O_PLAYER_4 = 126, + O_PLAYER_5 = 127, + O_PLAYER_6 = 128, + O_PLAYER_7 = 129, + O_PLAYER_8 = 130, + O_PLAYER_9 = 131, + O_PLAYER_10 = 132, + O_PASSPORT_CLOSED = 133, + O_COMPASS_ITEM = 134, + O_PISTOL_ITEM = 135, + O_SHOTGUN_ITEM = 136, + O_MAGNUM_ITEM = 137, + O_UZI_ITEM = 138, + O_HARPOON_ITEM = 139, + O_M16_ITEM = 140, + O_GRENADE_ITEM = 141, + O_PISTOL_AMMO_ITEM = 142, + O_SHOTGUN_AMMO_ITEM = 143, + O_MAGNUM_AMMO_ITEM = 144, + O_UZI_AMMO_ITEM = 145, + O_HARPOON_AMMO_ITEM = 146, + O_M16_AMMO_ITEM = 147, + O_GRENADE_AMMO_ITEM = 148, + O_SMALL_MEDIPACK_ITEM = 149, + O_LARGE_MEDIPACK_ITEM = 150, + O_FLARES_ITEM = 151, + O_FLARE_ITEM = 152, + O_DETAIL_OPTION = 153, + O_SOUND_OPTION = 154, + O_CONTROL_OPTION = 155, + O_GAMMA_OPTION = 156, + O_PISTOL_OPTION = 157, + O_SHOTGUN_OPTION = 158, + O_MAGNUM_OPTION = 159, + O_UZI_OPTION = 160, + O_HARPOON_OPTION = 161, + O_M16_OPTION = 162, + O_GRENADE_OPTION = 163, + O_PISTOL_AMMO_OPTION = 164, + O_SHOTGUN_AMMO_OPTION = 165, + O_MAGNUM_AMMO_OPTION = 166, + O_UZI_AMMO_OPTION = 167, + O_HARPOON_AMMO_OPTION = 168, + O_M16_AMMO_OPTION = 169, + O_GRENADE_AMMO_OPTION = 170, + O_SMALL_MEDIPACK_OPTION = 171, + O_LARGE_MEDIPACK_OPTION = 172, + O_FLARES_OPTION = 173, + O_PUZZLE_ITEM_1 = 174, + O_PUZZLE_ITEM_2 = 175, + O_PUZZLE_ITEM_3 = 176, + O_PUZZLE_ITEM_4 = 177, + O_PUZZLE_OPTION_1 = 178, + O_PUZZLE_OPTION_2 = 179, + O_PUZZLE_OPTION_3 = 180, + O_PUZZLE_OPTION_4 = 181, + O_PUZZLE_HOLE_1 = 182, + O_PUZZLE_HOLE_2 = 183, + O_PUZZLE_HOLE_3 = 184, + O_PUZZLE_HOLE_4 = 185, + O_PUZZLE_DONE_1 = 186, + O_PUZZLE_DONE_2 = 187, + O_PUZZLE_DONE_3 = 188, + O_PUZZLE_DONE_4 = 189, + O_SECRET_1 = 190, + O_SECRET_2 = 191, + O_SECRET_3 = 192, + O_KEY_ITEM_1 = 193, + O_KEY_ITEM_2 = 194, + O_KEY_ITEM_3 = 195, + O_KEY_ITEM_4 = 196, + O_KEY_OPTION_1 = 197, + O_KEY_OPTION_2 = 198, + O_KEY_OPTION_3 = 199, + O_KEY_OPTION_4 = 200, + O_KEY_HOLE_1 = 201, + O_KEY_HOLE_2 = 202, + O_KEY_HOLE_3 = 203, + O_KEY_HOLE_4 = 204, + O_PICKUP_ITEM_1 = 205, + O_PICKUP_ITEM_2 = 206, + O_PICKUP_OPTION_1 = 207, + O_PICKUP_OPTION_2 = 208, + O_SPHERE_OF_DOOM_1 = 209, + O_SPHERE_OF_DOOM_2 = 210, + O_SPHERE_OF_DOOM_3 = 211, + O_ALARM_SOUND = 212, + O_BIRD_TWEETER_1 = 213, + O_DINO = 214, + O_BIRD_TWEETER_2 = 215, + O_CLOCK_CHIMES = 216, + O_DRAGON_BONES_1 = 217, + O_DRAGON_BONES_2 = 218, + O_DRAGON_BONES_3 = 219, + O_HOT_LIQUID = 220, + O_BOAT_BITS = 221, + O_MINE = 222, + O_INV_BACKGROUND = 223, + O_FX_RESERVED = 224, + O_GONG_BONGER = 225, + O_DETONATOR_1 = 226, + O_DETONATOR_2 = 227, + O_COPTER = 228, + O_EXPLOSION = 229, + O_SPLASH = 230, + O_BUBBLES = 231, + O_BUBBLE_EMITTER = 232, + O_BLOOD = 233, + O_DART_EFFECT = 234, + O_FLARE_FIRE = 235, + O_GLOW = 236, + O_GLOW_RESERVED = 237, + O_RICOCHET = 238, + O_TWINKLE = 239, + O_GUN_FLASH = 240, + O_M16_FLASH = 241, + O_BODY_PART = 242, + O_CAMERA_TARGET = 243, + O_WATERFALL = 244, + O_MISSILE_HARPOON = 245, + O_MISSILE_FLAME = 246, + O_MISSILE_KNIFE = 247, + O_GRENADE = 248, + O_HARPOON_BOLT = 249, + O_EMBER = 250, + O_EMBER_EMITTER = 251, + O_FLAME = 252, + O_FLAME_EMITTER = 253, + O_SKYBOX = 254, + O_ALPHABET = 255, + O_DYING_MONK = 256, + O_DING_DONG = 257, + O_LARA_ALARM = 258, + O_MINI_COPTER = 259, + O_WINSTON = 260, + O_ASSAULT_DIGITS = 261, + O_FINAL_LEVEL_COUNTER = 262, + O_CUT_SHOTGUN = 263, + O_EARTHQUAKE = 264, + O_NUMBER_OF = 265, +} GAME_OBJECT_ID; + +typedef enum { + MX_INACTIVE = -1, + MX_UNUSED_0 = 0, // 2.mp3 + MX_UNUSED_1 = 1, // 2.mp3 + MX_CUTSCENE_THE_GREAT_WALL = 2, // 2.mp3 + MX_UNUSED_2 = 3, // 2.mp3 + MX_CUTSCENE_OPERA_HOUSE = 4, // 3.mp3 + MX_CUTSCENE_BROTHER_CHAN = 5, // 4.mp3 + MX_GYM_HINT_1 = 6, // 5.mp3 + MX_GYM_HINT_2 = 7, // 6.mp3 + MX_GYM_HINT_3 = 8, // 7.mp3 + MX_GYM_HINT_4 = 9, // 8.mp3 + MX_GYM_HINT_5 = 10, // 9.mp3 + MX_GYM_HINT_6 = 11, // 10.mp3 + MX_GYM_HINT_7 = 12, // 11.mp3 + MX_GYM_HINT_8 = 13, // 12.mp3 + MX_GYM_HINT_9 = 14, // 13.mp3 + MX_GYM_HINT_10 = 15, // 14.mp3 + MX_GYM_HINT_11 = 16, // 15.mp3 + MX_GYM_HINT_12 = 17, // 16.mp3 + MX_GYM_HINT_13 = 18, // 17.mp3 + MX_GYM_HINT_14 = 19, // 18.mp3 + MX_UNUSED_3 = 20, // 18.mp3 + MX_UNUSED_4 = 21, // 18.mp3 + MX_GYM_HINT_15 = 22, // 19.mp3 + MX_GYM_HINT_16 = 23, // 20.mp3 + MX_GYM_HINT_17 = 24, // 21.mp3 + MX_GYM_HINT_18 = 25, // 22.mp3 + MX_UNUSED_5 = 26, // 23.mp3 + MX_CUTSCENE_BATH = 27, // 23.mp3 + MX_DAGGER_PULL = 28, // 24.mp3 + MX_GYM_HINT_20 = 29, // 25.mp3 + MX_CUTSCENE_XIAN = 30, // 26.mp3 + MX_CAVES_AMBIENCE = 31, // 27.mp3 + MX_SEWERS_AMBIENCE = 32, // 28.mp3 + MX_WINDY_AMBIENCE = 33, // 29.mp3 + MX_HEARTBEAT_AMBIENCE = 34, // 30.mp3 + MX_SURPRISE_1 = 35, // 31.mp3 + MX_SURPRISE_2 = 36, // 32.mp3 + MX_SURPRISE_3 = 37, // 33.mp3 + MX_OOH_AAH_1 = 38, // 34.mp3 + MX_OOH_AAH_2 = 39, // 35.mp3 + MX_VENICE_VIOLINS = 40, // 36.mp3 + MX_END_OF_LEVEL = 41, // 37.mp3 + MX_SPOOKY_1 = 42, // 38.mp3 + MX_SPOOKY_2 = 43, // 39.mp3 + MX_SPOOKY_3 = 44, // 40.mp3 + MX_HARP_THEME = 45, // 41.mp3 + MX_MYSTERY_1 = 46, // 42.mp3 + MX_SECRET = 47, // 43.mp3 + MX_AMBUSH_1 = 48, // 44.mp3 + MX_AMBUSH_2 = 49, // 45.mp3 + MX_AMBUSH_3 = 50, // 46.mp3 + MX_AMBUSH_4 = 51, // 47.mp3 + MX_SKIDOO_THEME = 52, // 48.mp3 + MX_BATTLE_THEME = 53, // 49.mp3 + MX_MYSTERY_2 = 54, // 50.mp3 + MX_MYSTERY_3 = 55, // 51.mp3 + MX_MYSTERY_4 = 56, // 52.mp3 + MX_MYSTERY_5 = 57, // 53.mp3 + MX_RIG_AMBIENCE = 58, // 54.mp3 + MX_TOMB_AMBIENCE = 59, // 55.mp3 + MX_OOH_AAH_3 = 60, // 56.mp3 + MX_REVEAL_1 = 61, // 57.mp3 + MX_CUTSCENE_RIG = 62, // 58.mp3 + MX_REVEAL_2 = 63, // 59.mp3 + MX_TITLE_THEME = 64, // 60.mp3 + MX_UNUSED_6 = 65, // 61.mp3 +} MUSIC_TRACK_ID; + +typedef enum { + COLL_NONE = 0x00, + COLL_FRONT = 0x01, + COLL_LEFT = 0x02, + COLL_RIGHT = 0x04, + COLL_TOP = 0x08, + COLL_TOP_FRONT = 0x10, + COLL_CLAMP = 0x20, +} COLL_TYPE; + +typedef enum { + FT_FLOOR = 0, + FT_DOOR = 1, + FT_TILT = 2, + FT_ROOF = 3, + FT_TRIGGER = 4, + FT_LAVA = 5, + FT_CLIMB = 6, +} FLOOR_TYPE; + +typedef enum { + HT_WALL = 0, + HT_SMALL_SLOPE = 1, + HT_BIG_SLOPE = 2, +} HEIGHT_TYPE; + +typedef enum { // decompiled + DIR_UNKNOWN = -1, + DIR_NORTH = 0, + DIR_EAST = 1, + DIR_SOUTH = 2, + DIR_WEST = 3, +} DIRECTION; + +typedef struct __unaligned { + uint16_t x; + uint16_t y; +} XGEN_X; + +typedef struct __unaligned { + int32_t x1; + int32_t x2; +} XBUF_X; + +typedef struct __unaligned { + int16_t x; + int16_t y; + int16_t g; +} XGEN_XG; + +typedef struct __unaligned { + int32_t x1; + int32_t g1; + int32_t x2; + int32_t g2; +} XBUF_XG; + +typedef struct __unaligned { + uint16_t x; + uint16_t y; + uint16_t g; + uint16_t u; + uint16_t v; +} XGEN_XGUV; + +typedef struct __unaligned { + int32_t x1; + int32_t g1; + int32_t u1; + int32_t v1; + int32_t x2; + int32_t g2; + int32_t u2; + int32_t v2; +} XBUF_XGUV; + +typedef struct __unaligned { + uint16_t x; + uint16_t y; + uint16_t g; + float rhw; + float u; + float v; +} XGEN_XGUVP; + +typedef struct __unaligned { + int32_t x1; + int32_t g1; + float u1; + float v1; + float rhw1; + int32_t x2; + int32_t g2; + float u2; + float v2; + float rhw2; +} XBUF_XGUVP; + +typedef struct __unaligned { + uint8_t manufacturer; + uint8_t version; + uint8_t rle; + uint8_t bpp; + uint16_t x_min; + uint16_t y_min; + uint16_t x_max; + uint16_t y_max; + uint16_t h_dpi; + uint16_t v_dpi; + RGB_888 palette[16]; + uint8_t reserved; + uint8_t planes; + uint16_t bytes_per_line; + uint16_t pal_pnterpret; + uint16_t h_res; + uint16_t v_res; + uint8_t reserved_data[54]; +} PCX_HEADER; + +typedef struct __unaligned { + uint8_t id_length; + uint8_t color_map_type; + uint8_t data_type_code; + uint16_t color_map_origin; + uint16_t color_map_length; + uint8_t color_map_depth; + uint16_t x_origin; + uint16_t y_origin; + uint16_t width; + uint16_t height; + uint8_t bpp; + uint8_t image_descriptor; +} TGA_HEADER; + +typedef struct __unaligned { + int16_t number; + int16_t volume; + int16_t randomness; + int16_t flags; +} SAMPLE_INFO; + +/* +typedef struct __unaligned { + int32_t volume; + int32_t pan; + int32_t sample_num; + int32_t pitch; +} SOUND_SLOT; +*/ + +typedef enum { + SF_FLIP = 0x40, + SF_UNFLIP = 0x80, +} SOUND_FLAG; + +typedef enum { + GBUF_TEMP_ALLOC = 0, + GBUF_TEXTURE_PAGES = 1, + GBUF_MESH_POINTERS = 2, + GBUF_MESHES = 3, + GBUF_ANIMS = 4, + GBUF_STRUCTS = 5, + GBUF_ANIM_RANGES = 6, + GBUF_ANIM_COMMANDS = 7, + GBUF_ANIM_BONES = 8, + GBUF_ANIM_FRAMES = 9, + GBUF_ROOM_TEXTURES = 10, + GBUF_ROOMS = 11, + GBUF_ROOM_MESH = 12, + GBUF_ROOM_PORTALS = 13, + GBUF_ROOM_FLOOR = 14, + GBUF_ROOM_LIGHTS = 15, + GBUF_ROOM_STATIC_MESHES = 16, + GBUF_FLOOR_DATA = 17, + GBUF_ITEMS = 18, + GBUF_CAMERAS = 19, + GBUF_SOUND_FX = 20, + GBUF_BOXES = 21, + GBUF_OVERLAPS = 22, + GBUF_GROUND_ZONE = 23, + GBUF_FLY_ZONE = 24, + GBUF_ANIMATING_TEXTURE_RANGES = 25, + GBUF_CINEMATIC_FRAMES = 26, + GBUF_LOAD_DEMO_BUFFER = 27, + GBUF_SAVE_DEMO_BUFFER = 28, + GBUF_CINEMATIC_EFFECTS = 29, + GBUF_MUMMY_HEAD_TURN = 30, + GBUF_EXTRA_DOOR_STUFF = 31, + GBUF_EFFECTS_ARRAY = 32, + GBUF_CREATURE_DATA = 33, + GBUF_CREATURE_LOT = 34, + GBUF_SAMPLE_INFOS = 35, + GBUF_SAMPLES = 36, + GBUF_SAMPLE_OFFSETS = 37, + GBUF_ROLLING_BALL_STUFF = 38, + GBUF_SKIDOO_STUFF = 39, + GBUF_LOAD_PICTURE_BUFFER = 40, + GBUF_FMV_BUFFERS = 41, + GBUF_POLYGON_BUFFERS = 42, + GBUF_ORDER_TABLES = 43, + GBUF_CLUTS = 44, + GBUF_TEXTURE_INFOS = 45, + GBUF_SPRITE_INFOS = 46, + GBUF_NUM_MALLOC_TYPES = 47, +} GAME_BUFFER; + +typedef enum { + CLRB_PRIMARY_BUFFER = 0x0001, + CLRB_BACK_BUFFER = 0x0002, + CLRB_THIRD_BUFFER = 0x0004, + CLRB_Z_BUFFER = 0x0008, + CLRB_RENDER_BUFFER = 0x0010, + CLRB_PICTURE_BUFFER = 0x0020, + CLRB_WINDOWED_PRIMARY_BUFFER = 0x0040, + CLRB_RESERVED = 0x0080, + CLRB_PHDWINSIZE = 0x0100, +} CLEAR_BUFFER_FLAGS; + +typedef enum { + AC_NULL = 0, + AC_MOVE_ORIGIN = 1, + AC_JUMP_VELOCITY = 2, + AC_ATTACK_READY = 3, + AC_DEACTIVATE = 4, + AC_SOUND_FX = 5, + AC_EFFECT = 6, +} ANIM_COMMAND; + +typedef enum { + ACE_ALL = 0, + ACE_LAND = 1, + ACE_WATER = 2, +} ANIM_COMMAND_ENVIRONMENT; + +typedef struct __unaligned { + DDPIXELFORMAT pixel_fmt; + COLOR_BIT_MASKS color_bit_masks; + DWORD bpp; +} TEXTURE_FORMAT; + +typedef struct __unaligned { + int32_t boat_turn; + int32_t left_fallspeed; + int32_t right_fallspeed; + int16_t tilt_angle; + int16_t extra_rotation; + int32_t water; + int32_t pitch; +} BOAT_INFO; + +typedef struct __unaligned { + struct { + XYZ_16 min; + XYZ_16 max; + } shift, rot; +} OBJECT_BOUNDS; + +typedef struct __unaligned { + int32_t xv; + int32_t yv; + int32_t zv; +} PORTAL_VBUF; + +typedef struct __unaligned { + BOUNDS_16 bounds; + XYZ_16 offset; + int16_t mesh_rots[]; +} FRAME_INFO; + +typedef enum { + TO_OBJECT = 0, + TO_CAMERA = 1, + TO_SINK = 2, + TO_FLIP_MAP = 3, + TO_FLIP_ON = 4, + TO_FLIP_OFF = 5, + TO_TARGET = 6, + TO_FINISH = 7, + TO_CD = 8, + TO_FLIP_EFFECT = 9, + TO_SECRET = 10, + TO_BODY_BAG = 11, +} TRIGGER_OBJECT; + +typedef enum { + TT_TRIGGER = 0, + TT_PAD = 1, + TT_SWITCH = 2, + TT_KEY = 3, + TT_PICKUP = 4, + TT_HEAVY = 5, + TT_ANTIPAD = 6, + TT_COMBAT = 7, + TT_DUMMY = 8, + TT_ANTITRIGGER = 9, +} TRIGGER_TYPE; + +typedef enum { + GF_S_PC_DETAIL_LEVELS = 0, + GF_S_PC_DEMO_MODE = 1, + GF_S_PC_SOUND = 2, + GF_S_PC_CONTROLS = 3, + GF_S_PC_GAMMA = 4, + GF_S_PC_SET_VOLUMES = 5, + GF_S_PC_USER_KEYS = 6, + GF_S_PC_SAVE_FILE_WARNING = 7, + GF_S_PC_TRY_AGAIN_QUESTION = 8, + GF_S_PC_YES = 9, + GF_S_PC_NO = 10, + GF_S_PC_SAVE_COMPLETE = 11, + GF_S_PC_NO_SAVE_GAMES = 12, + GF_S_PC_NONE_VALID = 13, + GF_S_PC_SAVE_GAME_QUESTION = 14, + GF_S_PC_EMPTY_SLOT = 15, + GF_S_PC_OFF = 16, + GF_S_PC_ON = 17, + GF_S_PC_SETUP_SOUND_CARD = 18, + GF_S_PC_DEFAULT_KEYS = 19, + GF_S_PC_DOZY = 20, + GF_S_PC_NUMBER_OF = 41, +} GF_PC_STRING; + +typedef enum { + GF_S_GAME_HEADING_INVENTORY = 0, + GF_S_GAME_HEADING_OPTION = 1, + GF_S_GAME_HEADING_ITEMS = 2, + GF_S_GAME_HEADING_GAME_OVER = 3, + GF_S_GAME_PASSPORT_LOAD_GAME = 4, + GF_S_GAME_PASSPORT_SAVE_GAME = 5, + GF_S_GAME_PASSPORT_NEW_GAME = 6, + GF_S_GAME_PASSPORT_RESTART_LEVEL = 7, + GF_S_GAME_PASSPORT_EXIT_TO_TITLE = 8, + GF_S_GAME_PASSPORT_EXIT_DEMO = 9, + GF_S_GAME_PASSPORT_EXIT_GAME = 10, + GF_S_GAME_PASSPORT_SELECT_LEVEL = 11, + GF_S_GAME_PASSPORT_SAVE_POSITION = 12, + GF_S_GAME_DETAIL_SELECT_DETAIL = 13, + GF_S_GAME_DETAIL_HIGH = 14, + GF_S_GAME_DETAIL_MEDIUM = 15, + GF_S_GAME_DETAIL_LOW = 16, + GF_S_GAME_KEYMAP_WALK = 17, + GF_S_GAME_KEYMAP_ROLL = 18, + GF_S_GAME_KEYMAP_RUN = 19, + GF_S_GAME_KEYMAP_LEFT = 20, + GF_S_GAME_KEYMAP_RIGHT = 21, + GF_S_GAME_KEYMAP_BACK = 22, + GF_S_GAME_KEYMAP_STEP_LEFT = 23, + GF_S_GAME_KEYMAP_RESERVED_1 = 24, + GF_S_GAME_KEYMAP_STEP_RIGHT = 25, + GF_S_GAME_KEYMAP_RESERVED_2 = 26, + GF_S_GAME_KEYMAP_LOOK = 27, + GF_S_GAME_KEYMAP_JUMP = 28, + GF_S_GAME_KEYMAP_ACTION = 29, + GF_S_GAME_KEYMAP_DRAW_WEAPON = 30, + GF_S_GAME_KEYMAP_RESERVED_3 = 31, + GF_S_GAME_KEYMAP_INVENTORY = 32, + GF_S_GAME_KEYMAP_FLARE = 33, + GF_S_GAME_KEYMAP_STEP = 34, + GF_S_GAME_INV_ITEM_STATISTICS = 35, + GF_S_GAME_INV_ITEM_PISTOLS = 36, + GF_S_GAME_INV_ITEM_SHOTGUN = 37, + GF_S_GAME_INV_ITEM_MAGNUMS = 38, + GF_S_GAME_INV_ITEM_UZIS = 39, + GF_S_GAME_INV_ITEM_HARPOON = 40, + GF_S_GAME_INV_ITEM_M16 = 41, + GF_S_GAME_INV_ITEM_GRENADE = 42, + GF_S_GAME_INV_ITEM_FLARE = 43, + GF_S_GAME_INV_ITEM_PISTOL_AMMO = 44, + GF_S_GAME_INV_ITEM_SHOTGUN_AMMO = 45, + GF_S_GAME_INV_ITEM_MAGNUM_AMMO = 46, + GF_S_GAME_INV_ITEM_UZI_AMMO = 47, + GF_S_GAME_INV_ITEM_HARPOON_AMMO = 48, + GF_S_GAME_INV_ITEM_M16_AMMO = 49, + GF_S_GAME_INV_ITEM_GRENADE_AMMO = 50, + GF_S_GAME_INV_ITEM_SMALL_MEDIPACK = 51, + GF_S_GAME_INV_ITEM_LARGE_MEDIPACK = 52, + GF_S_GAME_INV_ITEM_PICKUP = 53, + GF_S_GAME_INV_ITEM_PUZZLE = 54, + GF_S_GAME_INV_ITEM_KEY = 55, + GF_S_GAME_INV_ITEM_GAME = 56, + GF_S_GAME_INV_ITEM_LARA_HOME = 57, + GF_S_GAME_MISC_LOADING = 58, + GF_S_GAME_MISC_TIME_TAKEN = 59, + GF_S_GAME_MISC_SECRETS_FOUND = 60, + GF_S_GAME_MISC_LOCATION = 61, + GF_S_GAME_MISC_KILLS = 62, + GF_S_GAME_MISC_AMMO_USED = 63, + GF_S_GAME_MISC_HITS = 64, + GF_S_GAME_MISC_SAVES_PERFORMED = 65, + GF_S_GAME_MISC_DISTANCE_TRAVELLED = 66, + GF_S_GAME_MISC_HEALTH_PACKS_USED = 67, + GF_S_GAME_MISC_RELEASE_VERSION = 68, + GF_S_GAME_MISC_NONE = 69, + GF_S_GAME_MISC_FINISH = 70, + GF_S_GAME_MISC_BEST_TIMES = 71, + GF_S_GAME_MISC_NO_TIMES_SET = 72, + GF_S_GAME_MISC_NA = 73, + GF_S_GAME_MISC_CURRENT_POSITION = 74, + GF_S_GAME_MISC_FINAL_STATISTICS = 75, + GF_S_GAME_MISC_OF = 76, + GF_S_GAME_MISC_STORY_SO_FAR = 77, + GF_S_GAME_NUMBER_OF = 89, +} GF_GAME_STRING; + +typedef enum { + GF_ADD_INV_PISTOLS = 0, + GF_ADD_INV_SHOTGUN = 1, + GF_ADD_INV_MAGNUMS = 2, + GF_ADD_INV_UZIS = 3, + GF_ADD_INV_HARPOON = 4, + GF_ADD_INV_M16 = 5, + GF_ADD_INV_GRENADE = 6, + GF_ADD_INV_PISTOL_AMMO = 7, + GF_ADD_INV_SHOTGUN_AMMO = 8, + GF_ADD_INV_MAGNUM_AMMO = 9, + GF_ADD_INV_UZI_AMMO = 10, + GF_ADD_INV_HARPOON_AMMO = 11, + GF_ADD_INV_M16_AMMO = 12, + GF_ADD_INV_GRENADE_AMMO = 13, + GF_ADD_INV_FLARES = 14, + GF_ADD_INV_SMALL_MEDI = 15, + GF_ADD_INV_LARGE_MEDI = 16, + GF_ADD_INV_PICKUP_1 = 17, + GF_ADD_INV_PICKUP_2 = 18, + GF_ADD_INV_PUZZLE_1 = 19, + GF_ADD_INV_PUZZLE_2 = 20, + GF_ADD_INV_PUZZLE_3 = 21, + GF_ADD_INV_PUZZLE_4 = 22, + GF_ADD_INV_KEY_1 = 23, + GF_ADD_INV_KEY_2 = 24, + GF_ADD_INV_KEY_3 = 25, + GF_ADD_INV_KEY_4 = 26, + GF_ADD_INV_NUMBER_OF = 27, +} GF_ADD_INV; + +typedef enum { + IT_NAME = 0, + IT_QTY = 1, + IT_NUMBER_OF = 2, +} INV_TEXT; + +typedef enum { + REQ_CENTER = 0x00, + REQ_USE = 0x01, + REQ_ALIGN_LEFT = 0x02, + REQ_ALIGN_RIGHT = 0x04, + REQ_HEADING = 0x08, + REQ_BEST_TIME = 0x10, + REQ_NORMAL_TIME = 0x20, + REQ_NO_TIME = 0x40, +} REQUESTER_FLAGS; + + +# FUNCTIONS +# Flags: +# - to do +# * to do, called by TR2X +# x unused +# + fully decompiled +# Offset Size Flags Declaration + +# 3dsystem/3d_gen.c +0x00401000 0x01D0 + void __cdecl Matrix_GenerateW2V(PHD_3DPOS *viewpos); +0x004011D0 0x0072 + void __cdecl Matrix_LookAt(int32_t xsrc, int32_t ysrc, int32_t zsrc, int32_t xtar, int32_t ytar, int32_t ztar, int16_t roll); +0x00401250 0x0078 + void __cdecl Math_GetVectorAngles(int32_t x, int32_t y, int32_t z, int16_t *dest); +0x004012D0 0x00AA + void __cdecl Matrix_RotX(int16_t rx); +0x00401380 0x00A8 + void __cdecl Matrix_RotY(int16_t ry); +0x00401430 0x00A8 + void __cdecl Matrix_RotZ(int16_t rz); +0x004014E0 0x01DC + void __cdecl Matrix_RotYXZ(int16_t ry, int16_t rx, int16_t rz); +0x004016C0 0x01E7 + void __cdecl Matrix_RotYXZpack(uint32_t rpack); +0x004018B0 0x00AB + bool __cdecl Matrix_TranslateRel(int32_t x, int32_t y, int32_t z); +0x00401960 0x007A + void __cdecl Matrix_TranslateAbs(int32_t x, int32_t y, int32_t z); +0x004019E0 0x00F3 + void __cdecl Output_InsertPolygons(const int16_t *obj_ptr, int32_t clip); +0x00401AE0 0x00EA + void __cdecl Output_InsertRoom(const int16_t *obj_ptr, int32_t is_outside); +0x00401BD0 0x0032 + const int16_t *__cdecl Output_CalcSkyboxLight(const int16_t *obj_ptr); +0x00401C10 0x0134 + void __cdecl Output_InsertSkybox(const int16_t *obj_ptr); +0x00401D50 0x0001 x void __cdecl Output_InsertInventoryBackground(const int16_t *obj_ptr); +0x00401D60 0x01D5 + const int16_t *__cdecl Output_CalcObjectVertices(const int16_t *obj_ptr); +0x00401F40 0x016D + const int16_t *__cdecl Output_CalcVerticeLight(const int16_t *obj_ptr); +0x004020B0 0x027D + const int16_t *__cdecl Output_CalcRoomVertices(const int16_t *obj_ptr, int32_t far_clip); +0x00402330 0x00C7 + void __cdecl Output_RotateLight(int16_t pitch, int16_t yaw); +0x00402400 0x0039 + void __cdecl Output_InitPolyList(void); +0x00402430 0x0033 + void __cdecl Output_SortPolyList(void); +0x00402470 0x00C5 + void __cdecl Output_QuickSort(int32_t left, int32_t right); +0x00402540 0x0036 + void __cdecl Output_PrintPolyList(uint8_t *surface_ptr); +0x00402580 0x00A1 + void __cdecl Output_AlterFOV(int16_t fov); +0x00402690 0x0095 + void __cdecl Output_SetNearZ(int32_t near_z); +0x004026E0 0x006B + void __cdecl Output_SetFarZ(int32_t far_z); +0x00402700 0x0266 + void __cdecl Output_Init(int16_t x, int16_t y, int32_t width, int32_t height, int32_t near_z, int32_t far_z, int16_t view_angle, int32_t screen_width, int32_t screen_height); + +# 3dsystem/3d_out.c +0x00402970 0x019F + void __cdecl Output_DrawPolyLine(const int16_t *obj_ptr); +0x00402B10 0x0035 + void __cdecl Output_DrawPolyFlat(const int16_t *obj_ptr); +0x00402B50 0x0035 + void __cdecl Output_DrawPolyTrans(const int16_t *obj_ptr); +0x00402B90 0x0035 + void __cdecl Output_DrawPolyGouraud(const int16_t *obj_ptr); +0x00402BD0 0x003C + void __cdecl Output_DrawPolyGTMap(const int16_t *obj_ptr); +0x00402C10 0x003C + void __cdecl Output_DrawPolyWGTMap(const int16_t *obj_ptr); +0x00402C50 0x00D2 + int32_t __cdecl Output_XGenX(const int16_t *obj_ptr); +0x00402D30 0x0146 + int32_t __cdecl Output_XGenXG(const int16_t *obj_ptr); +0x00402E80 0x0219 + int32_t __cdecl Output_XGenXGUV(const int16_t *obj_ptr); +0x004030A0 0x0284 + int32_t __cdecl Output_XGenXGUVPerspFP(const int16_t *obj_ptr); +0x00403330 0x0FC6 + void __cdecl Output_GTMapPersp32FP(int32_t y1, int32_t y2, uint8_t *tex_page); +0x00404300 0x14C4 + void __cdecl Output_WGTMapPersp32FP(int32_t y1, int32_t y2, uint8_t *tex_page); +0x004057D0 0x0037 + void __cdecl Output_DrawPolyGTMapPersp(const int16_t *obj_ptr); +0x00405810 0x0037 + void __cdecl Output_DrawPolyWGTMapPersp(const int16_t *obj_ptr); + +# 3dsystem/3dinsert.c +0x00405850 0x006C + int32_t __cdecl Output_VisibleZClip(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2); +0x004058C0 0x0140 + int32_t __cdecl Output_ZedClipper(int32_t vtx_count, POINT_INFO *pts, VERTEX_INFO *vtx); +0x00405A00 0x0511 + int32_t __cdecl Output_XYGUVClipper(int32_t vtx_count, VERTEX_INFO *vtx); +0x00405F20 0x0A5C + const int16_t *__cdecl Output_InsertObjectGT4(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +0x00406980 0x0872 + const int16_t *__cdecl Output_InsertObjectGT3(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +0x00407200 0x0422 + int32_t __cdecl Output_XYGClipper(int32_t vtx_count, VERTEX_INFO *vtx); +0x00407630 0x03D1 + const int16_t *__cdecl Output_InsertObjectG4(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +0x00407A10 0x031B + const int16_t *__cdecl Output_InsertObjectG3(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +0x00407D30 0x02D0 + int32_t __cdecl Output_XYClipper(int32_t vtx_count, VERTEX_INFO *vtx); +0x00408000 0x04A4 + void __cdecl Output_InsertTrans8(const PHD_VBUF *vbuf, int16_t shade); +0x004084B0 0x00D3 + void __cdecl Output_InsertTransQuad(int32_t x, int32_t y, int32_t width, int32_t height, int32_t z); +0x00408590 0x00CB + void __cdecl Output_InsertFlatRect(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +0x00408660 0x00B5 + void __cdecl Output_InsertLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +0x00408720 0x0642 + void __cdecl Output_InsertGT3_ZBuffered(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, const PHD_TEXTURE *texture, const PHD_UV *uv0, const PHD_UV *uv1, const PHD_UV *uv2); +0x00408D70 0x0140 + void __cdecl Output_DrawClippedPoly_Textured(int32_t vtx_count); +0x00408EB0 0x0444 + void __cdecl Output_InsertGT4_ZBuffered(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, const PHD_VBUF *vtx3, const PHD_TEXTURE *texture); +0x00409300 0x0091 + const int16_t *__cdecl Output_InsertObjectGT4_ZBuffered(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +0x004093A0 0x00AA + const int16_t *__cdecl Output_InsertObjectGT3_ZBuffered(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +0x00409450 0x039C + const int16_t *__cdecl Output_InsertObjectG4_ZBuffered(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +0x004097F0 0x00F7 + void __cdecl Output_DrawPoly_Gouraud(int32_t vtx_count, int32_t red, int32_t green, int32_t blue); +0x004098F0 0x02D3 + const int16_t *__cdecl Output_InsertObjectG3_ZBuffered(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +0x00409BD0 0x01C9 + void __cdecl Output_InsertFlatRect_ZBuffered(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +0x00409DA0 0x0133 + void __cdecl Output_InsertLine_ZBuffered(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +0x00409EE0 0x0706 + void __cdecl Output_InsertGT3_Sorted(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, const PHD_TEXTURE *texture, const PHD_UV *uv0, const PHD_UV *uv1, const PHD_UV *uv2, SORT_TYPE sort_type); +0x0040A5F0 0x01AC + void __cdecl Output_InsertClippedPoly_Textured(int32_t vtx_count, float z, int16_t poly_type, int16_t tex_page); +0x0040A7A0 0x04D7 + void __cdecl Output_InsertGT4_Sorted(const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, const PHD_VBUF *vtx3, const PHD_TEXTURE *texture, SORT_TYPE sort_type); +0x0040AC80 0x008C + const int16_t *__cdecl Output_InsertObjectGT4_Sorted(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +0x0040AD10 0x009F + const int16_t *__cdecl Output_InsertObjectGT3_Sorted(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +0x0040ADB0 0x043B + const int16_t *__cdecl Output_InsertObjectG4_Sorted(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +0x0040B1F0 0x0175 + void __cdecl Output_InsertPoly_Gouraud(int32_t vtx_count, float z, int32_t red, int32_t green, int32_t blue, int16_t poly_type); +0x0040B370 0x0343 + const int16_t *__cdecl Output_InsertObjectG3_Sorted(const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +0x0040B6C0 0x0347 + void __cdecl Output_InsertSprite_Sorted(int32_t z, int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t sprite_idx, int16_t shade); +0x0040BA10 0x017F + void __cdecl Output_InsertFlatRect_Sorted(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +0x0040BB90 0x012B + void __cdecl Output_InsertLine_Sorted(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +0x0040BCC0 0x0195 + void __cdecl Output_InsertTrans8_Sorted(const PHD_VBUF *vbuf, int16_t shade); +0x0040BE60 0x013D + void __cdecl Output_InsertTransQuad_Sorted(int32_t x, int32_t y, int32_t width, int32_t height, int32_t z); +0x0040BFA0 0x00A7 + void __cdecl Output_InsertSprite(int32_t z, int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t sprite_idx, int16_t shade); + +# 3dsystem/scalespr.c +0x0040C050 0x02C7 + void __cdecl Output_DrawSprite(uint32_t flags, int32_t x, int32_t y, int32_t z, int16_t sprite_idx, int16_t shade, int16_t scale); +0x0040C320 0x0085 + void __cdecl Output_DrawPickup(int32_t sx, int32_t sy, int32_t scale, int16_t sprite_idx, int16_t shade); +0x0040C3B0 0x0152 + const int16_t *__cdecl Output_InsertRoomSprite(const int16_t *obj_ptr, int32_t vtx_count); +0x0040C510 0x0096 + void __cdecl Output_DrawScreenSprite2D(int32_t sx, int32_t sy, int32_t sz, int32_t scale_h, int32_t scale_v, int16_t sprite_idx, int16_t shade, uint16_t flags); +0x0040C5B0 0x009D + void __cdecl Output_DrawScreenSprite(int32_t sx, int32_t sy, int32_t sz, int32_t scale_h, int32_t scale_v, int16_t sprite_idx, int16_t shade, uint16_t flags); +0x0040C650 0x0223 + void __cdecl Output_DrawScaledSpriteC(const int16_t *obj_ptr); + +# game/bird.c +0x0040C880 0x0089 + void __cdecl Bird_Initialise(int16_t item_num); +0x0040C910 0x0200 + void __cdecl Bird_Control(int16_t item_num); + +# game/boat.c +0x0040CB30 0x003C + void __cdecl Boat_Initialise(int16_t item_num); +0x0040CB70 0x0170 + int32_t __cdecl Boat_CheckGeton(int16_t item_num, COLL_INFO *coll); +0x0040CCE0 0x015E + void __cdecl Boat_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x0040CE40 0x00F8 + int32_t __cdecl Boat_TestWaterHeight(ITEM *item, int32_t z_off, int32_t x_off, XYZ_32 *pos); +0x0040CF40 0x01C1 + void __cdecl Boat_DoShift(int32_t boat_num); +0x0040D110 0x0174 + void __cdecl Boat_DoWakeEffect(ITEM *boat); +0x0040D290 0x004B + int32_t __cdecl Boat_DoDynamics(int32_t height, int32_t fall_speed, int32_t *y); +0x0040D2E0 0x04DD + int32_t __cdecl Boat_Dynamics(int16_t boat_num); +0x0040D7C0 0x0187 + int32_t __cdecl Boat_UserControl(ITEM *boat); +0x0040D950 0x0169 + void __cdecl Boat_Animation(ITEM *boat, int32_t collide); +0x0040DAC0 0x062A + void __cdecl Boat_Control(int16_t item_num); +0x0040E0F0 0x00B3 + void __cdecl Gondola_Control(int16_t item_num); + +# game/box.c +0x0040E1B0 0x002F + void __cdecl Creature_Initialise(int16_t item_num); +0x0040E1E0 0x0047 + int32_t __cdecl Creature_Activate(int16_t item_num); +0x0040E230 0x0242 + void __cdecl Creature_AIInfo(ITEM *item, AI_INFO *info); +0x0040E490 0x01F3 + int32_t __cdecl Box_SearchLOT(LOT_INFO *lot, int32_t expansion); +0x0040E690 0x006F + int32_t __cdecl Box_UpdateLOT(LOT_INFO *lot, int32_t expansion); +0x0040E700 0x0095 + void __cdecl Box_TargetBox(LOT_INFO *lot, int16_t box_num); +0x0040E7A0 0x00F2 + int32_t __cdecl Box_StalkBox(const ITEM *item, const ITEM *enemy, int16_t box_num); +0x0040E8A0 0x00A4 + int32_t __cdecl Box_EscapeBox(const ITEM *item, const ITEM *enemy, int16_t box_num); +0x0040E950 0x00A7 + int32_t __cdecl Box_ValidBox(const ITEM *item, int16_t zone_num, int16_t box_num); +0x0040EA00 0x043F + void __cdecl Creature_Mood(ITEM *item, AI_INFO *info, int32_t violent); +0x0040EE70 0x0459 + TARGET_TYPE __cdecl Box_CalculateTarget(XYZ_32 *target, ITEM *item, LOT_INFO *lot); +0x0040F2D0 0x00F8 + int32_t __cdecl Creature_CheckBaddieOverlap(int16_t item_num); +0x0040F3D0 0x008B + int32_t __cdecl Box_BadFloor(int32_t x, int32_t y, int32_t z, int32_t box_height, int32_t next_height, int16_t room_num, LOT_INFO *lot); +0x0040F460 0x00B8 + void __cdecl Creature_Die(int16_t item_num, int32_t explode); +0x0040F520 0x08CC + int32_t __cdecl Creature_Animate(int16_t item_num, int16_t angle, int16_t tilt); +0x0040FDF0 0x00D5 + int16_t __cdecl Creature_Turn(ITEM *item, int16_t maximum_turn); +0x0040FED0 0x0035 + void __cdecl Creature_Tilt(ITEM *item, int16_t angle); +0x0040FF10 0x0049 + void __cdecl Creature_Head(ITEM *item, int16_t required); +0x0040FF60 0x004E + void __cdecl Creature_Neck(ITEM *item, int16_t required); +0x0040FFB0 0x00A8 + void __cdecl Creature_Float(int16_t item_num); +0x00410060 0x0050 + void __cdecl Creature_Underwater(ITEM *item, int32_t depth); +0x004100B0 0x005C + int16_t __cdecl Creature_Effect(ITEM *item, BITE *bite, int16_t (*__cdecl spawn)(int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, int16_t room_num)); +0x00410110 0x0131 + int32_t __cdecl Creature_Vault(int16_t item_num, int16_t angle, int32_t vault, int32_t shift); +0x00410250 0x016F + void __cdecl Creature_Kill(ITEM *item, int32_t kill_anim, int32_t kill_state, int32_t lara_kill_state); +0x004103C0 0x01DB + void __cdecl Creature_GetBaddieTarget(int16_t item_num, int32_t goody); + +# game/camera.c +0x004105A0 0x00B0 + void __cdecl Camera_Initialise(void); +0x00410650 0x0372 + void __cdecl Camera_Move(const GAME_VECTOR *target, int32_t speed); +0x004109D0 0x00D7 + void __cdecl Camera_Clip(int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom); +0x00410AB0 0x0154 + void __cdecl Camera_Shift(int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom); +0x00410C10 0x0050 + const SECTOR *__cdecl Camera_GoodPosition(int32_t x, int32_t y, int32_t z, int16_t room_num); +0x00410C60 0x0781 + void __cdecl Camera_SmartShift(GAME_VECTOR *target, void (*__cdecl shift)(int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom)); +0x004113F0 0x00ED + void __cdecl Camera_Chase(const ITEM *item); +0x004114E0 0x019E + int32_t __cdecl Camera_ShiftClamp(GAME_VECTOR *pos, int32_t clamp); +0x00411680 0x018E + void __cdecl Camera_Combat(const ITEM *item); +0x00411810 0x01E2 + void __cdecl Camera_Look(const ITEM *item); +0x00411A00 0x0099 + void __cdecl Camera_Fixed(void); +0x00411AA0 0x04A9 + void __cdecl Camera_Update(void); + +# game/cinema.c +0x00411F50 0x000A + void __cdecl Game_SetCutsceneTrack(int32_t track); +0x00411F60 0x0112 + int32_t __cdecl Game_Cutscene_Start(int32_t level_num); +0x00412080 0x0093 +R void __cdecl Misc_InitCinematicRooms(void); +0x00412120 0x016F + int32_t __cdecl Game_Cutscene_Control(int32_t nframes); +0x00412290 0x0138 + void __cdecl Camera_UpdateCutscene(void); +0x004123D0 0x007F +R int32_t __cdecl Room_FindByPos(int32_t x, int32_t y, int32_t z); +0x00412450 0x00DC + void __cdecl CutscenePlayer_Control(int16_t item_num); +0x00412530 0x0096 + void __cdecl Lara_Control_Cutscene(int16_t item_num); +0x004125D0 0x008F +R void __cdecl CutscenePlayer1_Initialise(int16_t item_num); +0x00412660 0x0033 +R void __cdecl CutscenePlayerGen_Initialise(int16_t item_num); +0x004126A0 0x0245 + void __cdecl Camera_LoadCutsceneFrame(void); + +# game/collide.c +0x004128F0 0x067C + void __cdecl Collide_GetCollisionInfo(COLL_INFO *coll, int32_t xpos, int32_t ypos, int32_t zpos, int16_t room_num, int32_t obj_height); +0x00412FB0 0x002F + int32_t __cdecl Room_FindGridShift(int32_t src, int32_t dst); +0x00412FE0 0x03D2 + int32_t __cdecl Collide_CollideStaticObjects(COLL_INFO *coll, int32_t x, int32_t y, int32_t z, int16_t room_num, int32_t height); +0x004133D0 0x00C8 + void __cdecl Room_GetNearbyRooms(int32_t x, int32_t y, int32_t z, int32_t r, int32_t h, int16_t room_num); +0x004134A0 0x0055 + void __cdecl Room_GetNewRoom(int32_t x, int32_t y, int32_t z, int16_t room_num); +0x00413500 0x0037 + void __cdecl Item_ShiftCol(ITEM *item, COLL_INFO *coll); +0x00413540 0x005D + void __cdecl Item_UpdateRoom(ITEM *item, int32_t height); +0x004135A0 0x0099 + int16_t __cdecl Room_GetTiltType(const SECTOR *sector, int32_t x, int32_t y, int32_t z); +0x00413640 0x0195 + void __cdecl Lara_BaddieCollision(ITEM *lara_item, COLL_INFO *coll); +0x004137E0 0x0079 + void __cdecl Lara_TakeHit(ITEM *lara_item, COLL_INFO *coll); +0x00413860 0x0078 + void __cdecl Creature_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x004138E0 0x0055 + void __cdecl Object_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x00413940 0x0077 + void __cdecl Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x004139C0 0x0067 + void __cdecl Object_Collision_Trap(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x00413A30 0x0306 + void __cdecl Lara_Push(ITEM *item, ITEM *lara_item, COLL_INFO *coll, int32_t spaz_on, int32_t big_push); +0x00413D40 0x00CB + int32_t __cdecl Item_TestBoundsCollide(const ITEM *src_item, const ITEM *dst_item, int32_t radius); +0x00413E10 0x0137 + int32_t __cdecl Item_TestPosition(int16_t *bounds, ITEM *src_item, ITEM *dst_item); +0x00413F50 0x013B + void __cdecl Item_AlignPosition(XYZ_32 *vec, ITEM *src_item, ITEM *dst_item); +0x00414090 0x0187 + int32_t __cdecl Lara_MovePosition(XYZ_32 *vec, ITEM *item, ITEM *lara_item); +0x00414220 0x016E +R int32_t __cdecl Misc_Move3DPosTo3DPos(PHD_3DPOS *src_pos, const PHD_3DPOS *dest_pos, int32_t velocity, PHD_ANGLE ang_add); + +# game/control.c +0x00414390 0x0356 + int32_t __cdecl Game_Control(int32_t nframes, int32_t demo_mode); +0x004146F0 0x0338 + void __cdecl Item_Animate(ITEM *item); +0x00414A60 0x00AB + int32_t __cdecl Item_GetAnimChange(ITEM *item, const ANIM *anim); +0x00414B10 0x005F + void __cdecl Item_Translate(ITEM *item, int32_t x, int32_t y, int32_t z); +0x00414B70 0x0198 + SECTOR *__cdecl Room_GetSector(int32_t x, int32_t y, int32_t z, int16_t *room_num); +0x00414D10 0x0168 + int32_t __cdecl Room_GetWaterHeight(int32_t x, int32_t y, int32_t z, int16_t room_num); +0x00414E80 0x0265 + int32_t __cdecl Room_GetHeight(const SECTOR *sector, int32_t x, int32_t y, int32_t z); +0x00415100 0x00E7 + void __cdecl Camera_RefreshFromTrigger(int16_t type, const int16_t *data); +0x004151F0 0x0690 + void __cdecl Room_TestTriggers(int16_t *data, int32_t heavy); +0x004158D0 0x0055 + int32_t __cdecl Item_IsTriggerActive(ITEM *item); +0x00415930 0x023D + int32_t __cdecl Room_GetCeiling(const SECTOR *sector, int32_t x, int32_t y, int32_t z); +0x00415B90 0x004E + int16_t __cdecl Room_GetDoor(const SECTOR *sector); +0x00415BE0 0x00A0 + int32_t __cdecl LOS_Check(const GAME_VECTOR *start, GAME_VECTOR *target); +0x00415C80 0x02EB + int32_t __cdecl LOS_CheckZ(const GAME_VECTOR *start, GAME_VECTOR *target); +0x00415F70 0x02EC + int32_t __cdecl LOS_CheckX(const GAME_VECTOR *start, GAME_VECTOR *target); +0x00416260 0x00DA + int32_t __cdecl LOS_ClipTarget(const GAME_VECTOR *start, GAME_VECTOR *target, const SECTOR *sector); +0x00416340 0x02FE + int32_t __cdecl LOS_CheckSmashable(const GAME_VECTOR *start, GAME_VECTOR *target); +0x00416640 0x00B3 + void __cdecl Room_FlipMap(void); +0x00416700 0x0096 + void __cdecl Room_RemoveFlipItems(ROOM *r); +0x004167A0 0x005C + void __cdecl Room_AddFlipItems(ROOM *r); +0x00416800 0x0024 + void __cdecl Room_TriggerMusicTrack(int16_t value, int16_t flags, int16_t type); +0x00416830 0x00DA + void __cdecl Room_TriggerMusicTrackImpl(int16_t value, int16_t flags, int16_t type); + +# game/demo.c +0x00416910 0x0059 + int32_t __cdecl Demo_Control(int32_t level_num); +0x00416970 0x01B0 + int32_t __cdecl Demo_Start(int32_t level_num); +0x00416B20 0x00CD + void __cdecl Demo_LoadLaraPos(void); +0x00416BF0 0x002D + void __cdecl Demo_GetInput(void); + +# game/diver.c +0x00416C20 0x007A - int16_t __cdecl Diver_Harpoon(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE y_rot, int16_t room_num); +0x00416CA0 0x0106 - int32_t __cdecl Diver_GetWaterSurface(int32_t x, int32_t y, int32_t z, int16_t room_num); +0x00416DB0 0x0389 + void __cdecl Diver_Control(int16_t item_num); + +# game/dog.c +0x00417160 0x0387 - void __cdecl Dog_Control(int16_t item_num); +0x00417510 0x027E - void __cdecl Tiger_Control(int16_t item_num); + +# game/dragon.c +0x004177B0 0x017F -R void __cdecl ControlTwinkle(int16_t fx_num); +0x00417930 0x00D9 -R void __cdecl CreateBartoliLight(int16_t item_num); +0x00417A10 0x00AB -R int16_t __cdecl DragonFire(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +0x00417AC0 0x02ED -R void __cdecl DragonCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x00417DB0 0x00D9 -R void __cdecl DragonBones(int16_t item_num); +0x00417E90 0x0519 -R void __cdecl DragonControl(int16_t back_num); +0x004183E0 0x0114 -R void __cdecl InitialiseBartoli(int16_t item_num); +0x00418500 0x0193 -R void __cdecl BartoliControl(int16_t item_num); +0x004186A0 0x0287 -R void __cdecl DinoControl(int16_t item_num); + +# game/draw.c +0x00418950 0x0037 + int32_t __cdecl Game_DrawCinematic(void); +0x00418990 0x0037 + int32_t __cdecl Game_Draw(void); +0x004189D0 0x02B0 + void __cdecl Room_DrawAllRooms(int16_t current_room); +0x00418C80 0x01C6 + void __cdecl Room_GetBounds(void); +0x00418E50 0x037F + void __cdecl Room_SetBounds(const int16_t *objptr, int32_t room_num, ROOM *parent); +0x004191D0 0x03D2 + void __cdecl Room_Clip(ROOM *r); +0x004195B0 0x00B4 + void __cdecl Room_DrawSingleRoomGeometry(int16_t room_num); +0x00419670 0x0218 + void __cdecl Room_DrawSingleRoomObjects(int16_t room_num); +0x00419890 0x0147 + void __cdecl Effect_Draw(int16_t fx_num); +0x004199E0 0x0083 - void __cdecl Object_DrawSpriteItem(ITEM *item); +0x00419A70 0x0378 - void __cdecl Object_DrawAnimatingItem(ITEM *item); +0x00419DF0 0x0D02 + void __cdecl Lara_Draw(const ITEM *item); +0x0041AB20 0x0BC6 + void __cdecl Lara_Draw_I(const ITEM *item, const FRAME_INFO *frame1, const FRAME_INFO *frame2, int32_t frac, int32_t rate); +0x0041B710 0x0034 + void __cdecl Matrix_InitInterpolate(int32_t frac, int32_t rate); +0x0041B750 0x0022 + void __cdecl Matrix_Pop_I(void); +0x0041B780 0x0027 + void __cdecl Matrix_Push_I(void); +0x0041B7B0 0x0031 + void __cdecl Matrix_RotY_I(int16_t ang); +0x0041B7F0 0x0031 + void __cdecl Matrix_RotX_I(int16_t ang); +0x0041B830 0x0031 + void __cdecl Matrix_RotZ_I(int16_t ang); +0x0041B870 0x0041 + void __cdecl Matrix_TranslateRel_I(int32_t x, int32_t y, int32_t z); +0x0041B8C0 0x0047 + void __cdecl Matrix_TranslateRel_ID(int32_t x, int32_t y, int32_t z, int32_t x2, int32_t y2, int32_t z2); +0x0041B910 0x0041 + void __cdecl Matrix_RotYXZ_I(int16_t y, int16_t x, int16_t z); +0x0041B960 0x003D + void __cdecl Matrix_RotYXZsuperpack_I(const int16_t **pprot1, const int16_t **pprot2, int32_t skip); +0x0041B9A0 0x00A1 + void __cdecl Matrix_RotYXZsuperpack(const int16_t **pprot, int32_t skip); +0x0041BA50 0x002A + void __cdecl Output_InsertPolygons_I(int16_t *ptr, int32_t clip); +0x0041BA80 0x01A5 + void __cdecl Matrix_Interpolate(void); +0x0041BC30 0x00FC + void __cdecl Matrix_InterpolateArm(void); +0x0041BD30 0x014B - void __cdecl Gun_DrawFlash(LARA_GUN_TYPE weapon_type, int32_t clip); +0x0041BEA0 0x00E8 - void __cdecl Output_CalculateObjectLighting(const ITEM *item, const FRAME_INFO *frame); +0x0041BF90 0x0092 + int32_t __cdecl Item_GetFrames(const ITEM *item, FRAME_INFO *frmptr[], int32_t *rate); +0x0041C030 0x007C + BOUNDS_16 *__cdecl Item_GetBoundsAccurate(const ITEM *item); +0x0041C0B0 0x0035 + FRAME_INFO *__cdecl Item_GetBestFrame(const ITEM *item); +0x0041C0F0 0x0048 -R void __cdecl AddDynamicLight(int32_t x, int32_t y, int32_t z, int32_t intensity, int32_t falloff); + +# game/eel.c +0x0041C140 0x019D -R void __cdecl BigEelControl(int16_t item_num); +0x0041C2E0 0x01E1 -R void __cdecl EelControl(int16_t item_num); + +# game/effects.c +0x0041C4D0 0x008C + int32_t __cdecl Lara_IsNearItem(PHD_3DPOS *pos, int32_t distance); +0x0041C560 0x0068 + void __cdecl Sound_UpdateEffects(void); +0x0041C5D0 0x0059 -R int16_t __cdecl DoBloodSplat(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE direction, int16_t room_num); +0x0041C630 0x00A4 -R void __cdecl DoLotsOfBlood(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE direction, int16_t room_num, int32_t num); +0x0041C6E0 0x0082 -R void __cdecl ControlBlood1(int16_t fx_num); +0x0041C770 0x007F -R void __cdecl ControlExplosion1(int16_t fx_num); +0x0041C7F0 0x0072 -R void __cdecl Richochet(GAME_VECTOR *pos); +0x0041C870 0x0030 -R void __cdecl ControlRichochet1(int16_t fx_num); +0x0041C8A0 0x0064 -R void __cdecl CreateBubble(PHD_3DPOS *pos, int16_t room_num); +0x0041C910 0x0078 -R void __cdecl LaraBubbles(ITEM *item); +0x0041C990 0x00F3 -R void __cdecl ControlBubble1(int16_t fx_num); +0x0041CA90 0x00C2 -R void __cdecl Splash(ITEM *item); +0x0041CB60 0x0071 -R void __cdecl ControlSplash1(int16_t fx_num); +0x0041CBE0 0x00AE -R void __cdecl ControlWaterSprite(int16_t fx_num); +0x0041CC90 0x008C -R void __cdecl ControlSnowSprite(int16_t fx_num); +0x0041CD20 0x00DE -R void __cdecl ControlHotLiquid(int16_t fx_num); +0x0041CE00 0x013D -R void __cdecl WaterFall(int16_t fx_num); +0x0041CF40 0x000B -R void __cdecl finish_level_effect(ITEM *item); +0x0041CF50 0x0016 -R void __cdecl turn180_effect(ITEM *item); +0x0041CF70 0x0096 -R void __cdecl floor_shake_effect(ITEM *item); +0x0041D010 0x0040 -R void __cdecl lara_normal_effect(ITEM *item); +0x0041D050 0x001C -R void __cdecl BoilerFX(ITEM *item); +0x0041D070 0x008F -R void __cdecl FloodFX(ITEM *item); +0x0041D100 0x0023 -R void __cdecl RubbleFX(ITEM *item); +0x0041D130 0x002C -R void __cdecl ChandelierFX(ITEM *item); +0x0041D160 0x0023 -R void __cdecl ExplosionFX(ITEM *item); +0x0041D190 0x001C -R void __cdecl PistonFX(ITEM *item); +0x0041D1B0 0x001C -R void __cdecl CurtainFX(ITEM *item); +0x0041D1D0 0x001C -R void __cdecl StatueFX(ITEM *item); +0x0041D1F0 0x001C -R void __cdecl SetChangeFX(ITEM *item); +0x0041D210 0x003F -R void __cdecl ControlDingDong(int16_t item_num); +0x0041D250 0x0037 -R void __cdecl ControlLaraAlarm(int16_t item_num); +0x0041D290 0x0067 -R void __cdecl ControlAlarmSound(int16_t item_num); +0x0041D300 0x005D -R void __cdecl ControlBirdTweeter(int16_t item_num); +0x0041D360 0x0059 -R void __cdecl DoChimeSound(ITEM *item); +0x0041D3C0 0x0068 -R void __cdecl ControlClockChimes(int16_t item_num); +0x0041D430 0x0128 -R void __cdecl SphereOfDoomCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x0041D560 0x00F0 -R void __cdecl SphereOfDoom(int16_t item_num); +0x0041D650 0x012D -R void __cdecl DrawSphereOfDoom(ITEM *item); +0x0041D780 0x000A -R void __cdecl lara_hands_free(ITEM *item); +0x0041D790 0x0005 -R void __cdecl flip_map_effect(ITEM *item); +0x0041D7A0 0x0043 -R void __cdecl draw_right_gun(ITEM *item); +0x0041D7F0 0x0043 -R void __cdecl draw_left_gun(ITEM *item); +0x0041D840 0x0063 -R void __cdecl swap_meshes_with_meshswap1(ITEM *item); +0x0041D8B0 0x0063 -R void __cdecl swap_meshes_with_meshswap2(ITEM *item); +0x0041D920 0x009A -R void __cdecl swap_meshes_with_meshswap3(ITEM *item); +0x0041D9C0 0x0009 -R void __cdecl invisibility_on(ITEM *item); +0x0041D9D0 0x0016 -R void __cdecl invisibility_off(ITEM *item); +0x0041D9F0 0x0009 -R void __cdecl dynamic_light_on(ITEM *item); +0x0041DA00 0x000B -R void __cdecl dynamic_light_off(ITEM *item); +0x0041DA10 0x0005 -R void __cdecl reset_hair(ITEM *item); +0x0041DA20 0x0024 -R void __cdecl AssaultStart(ITEM *item); +0x0041DA50 0x001F -R void __cdecl AssaultStop(ITEM *item); +0x0041DA70 0x0017 -R void __cdecl AssaultReset(ITEM *item); +0x0041DA90 0x00B2 -R void __cdecl AssaultFinished(ITEM *item); + +# game/enemies.c +0x0041DB50 0x0076 -R int16_t __cdecl Knife(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +0x0041DBD0 0x040B -R void __cdecl Cult2Control(int16_t item_num); +0x0041E000 0x04A1 -R void __cdecl MonkControl(int16_t item_num); +0x0041E4D0 0x05BD -R void __cdecl Worker3Control(int16_t item_num); +0x0041EAE0 0x03F7 -R void __cdecl DrawXianLord(ITEM *item); +0x0041EEE0 0x00A8 -R void __cdecl XianDamage(ITEM *item, CREATURE *xian, int32_t damage); +0x0041EF90 0x0058 -R void __cdecl InitialiseXianLord(int16_t item_num); +0x0041EFF0 0x0590 -R void __cdecl XianLordControl(int16_t item_num); +0x0041F5D0 0x0098 -R void __cdecl WarriorSparkleTrail(ITEM *item); +0x0041F670 0x03BA -R void __cdecl WarriorControl(int16_t item_num); + +# game/gameflow.c +0x0041FA60 0x01E9 + int32_t __cdecl GF_LoadScriptFile(const char *fname); +0x0041FC50 0x001F + int32_t __cdecl GF_DoFrontendSequence(void); +0x0041FC70 0x0066 + int32_t __cdecl GF_DoLevelSequence(int32_t level, GAMEFLOW_LEVEL_TYPE type); +0x0041FCE0 0x047C + int32_t __cdecl GF_InterpretSequence(int16_t *ptr, GAMEFLOW_LEVEL_TYPE type, int32_t seq_type); +0x004201C0 0x0CD3 + void __cdecl GF_ModifyInventory(int32_t level, int32_t type); + +# game/hair.c +0x00420EA0 0x0074 -R void __cdecl InitialiseHair(void); +0x00420F20 0x09E5 -R void __cdecl HairControl(int32_t in_cutscene); +0x00421920 0x0076 -R void __cdecl DrawHair(void); + +# game/health.c +0x004219A0 0x002D + BOOL __cdecl Overlay_FlashCounter(void); +0x004219D0 0x0145 + void __cdecl Overlay_DrawAssaultTimer(void); +0x00421B20 0x0045 + void __cdecl Overlay_DrawGameInfo(bool pickup_state); +0x00421B70 0x00AB + void __cdecl Overlay_DrawHealthBar(bool flash_state); +0x00421C20 0x0097 + void __cdecl Overlay_DrawAirBar(bool flash_state); +0x00421CC0 0x0028 + void __cdecl Overlay_MakeAmmoString(char *string); +0x00421CF0 0x0132 + void __cdecl Overlay_DrawAmmoInfo(void); +0x00421E40 0x0015 + void __cdecl Overlay_InitialisePickUpDisplay(void); +0x00421E60 0x00FD + void __cdecl Overlay_DrawPickups(bool pickup_state); +0x00421F60 0x006C + void __cdecl Overlay_AddDisplayPickup(GAME_OBJECT_ID object_id); +0x00421FD0 0x007A + void __cdecl Overlay_DisplayModeInfo(char* string); +0x00422050 0x002C + void __cdecl Overlay_DrawModeInfo(void); + +# game/inventory.c +0x00422080 0x119E + int32_t __cdecl Inv_Display(int32_t inventory_mode); +0x00423310 0x0156 + void __cdecl Inv_Construct(void); +0x00423470 0x0089 + void __cdecl Inv_SelectMeshes(INVENTORY_ITEM *inv_item); +0x00423500 0x0081 + int32_t __cdecl Inv_AnimateInventoryItem(INVENTORY_ITEM *inv_item); +0x00423590 0x041D + void __cdecl Inv_DrawInventoryItem(INVENTORY_ITEM *inv_item); +0x004239C0 0x0019 + int32_t __cdecl Input_GetDebounced(int32_t input); +0x004239E0 0x0005 + void __cdecl Inv_DoInventoryPicture(void); +0x004239F0 0x0132 + void __cdecl Inv_DoInventoryBackground(void); + +# game/invfunc.c +0x00423B30 0x010A + void __cdecl Inv_InitColors(void); +0x00423C40 0x0167 + void __cdecl Inv_RingIsOpen(RING_INFO *ring); +0x00423DB0 0x0081 + void __cdecl Inv_RingIsNotOpen(RING_INFO *ring); +0x00423E40 0x0369 + void __cdecl Inv_RingNotActive(INVENTORY_ITEM *inv_item); +0x004242B0 0x0032 + void __cdecl Inv_RingActive(void); +0x004242F0 0x06BE + int32_t __cdecl Inv_AddItem(GAME_OBJECT_ID object_id); +0x00424B00 0x0129 + void __cdecl Inv_InsertItem(INVENTORY_ITEM *inv_item); +0x00424C30 0x0077 + int32_t __cdecl Inv_RequestItem(GAME_OBJECT_ID object_id); +0x00424CB0 0x001B + void __cdecl Inv_RemoveAllItems(void); +0x00424CD0 0x0110 + int32_t __cdecl Inv_RemoveItem(GAME_OBJECT_ID object_id); +0x00424DE0 0x00C1 + int32_t __cdecl Inv_GetItemOption(GAME_OBJECT_ID object_id); +0x00424FD0 0x0024 + void __cdecl Inv_RemoveInventoryText(void); +0x00425000 0x010F + void __cdecl Inv_Ring_Init(RING_INFO *ring, int16_t type, INVENTORY_ITEM **list, int16_t qty, int16_t current, IMOTION_INFO *imo); +0x00425110 0x0060 + void __cdecl Inv_Ring_GetView(RING_INFO *ring, PHD_3DPOS *viewer); +0x00425170 0x0040 + void __cdecl Inv_Ring_Light(RING_INFO *ring); +0x004251B0 0x002C + void __cdecl Inv_Ring_CalcAdders(RING_INFO *ring, int16_t rotation_duration); +0x004251E0 0x013E + void __cdecl Inv_Ring_DoMotions(RING_INFO *ring); +0x00425320 0x002F + void __cdecl Inv_Ring_RotateLeft(RING_INFO *ring); +0x00425350 0x002F + void __cdecl Inv_Ring_RotateRight(RING_INFO *ring); +0x00425380 0x0063 + void __cdecl Inv_Ring_MotionInit(RING_INFO *ring, int16_t frames, int16_t status, int16_t status_target); +0x004253F0 0x002C + void __cdecl Inv_Ring_MotionSetup(RING_INFO *ring, int16_t status, int16_t status_target, int16_t frames); +0x00425420 0x0026 + void __cdecl Inv_Ring_MotionRadius(RING_INFO *ring, int16_t target); +0x00425450 0x0022 + void __cdecl Inv_Ring_MotionRotation(RING_INFO *ring, int16_t rotation, int16_t target); +0x00425480 0x0025 + void __cdecl Inv_Ring_MotionCameraPos(RING_INFO *ring, int16_t target); +0x004254B0 0x0020 + void __cdecl Inv_Ring_MotionCameraPitch(RING_INFO *ring, int16_t target); +0x004254D0 0x005D + void __cdecl Inv_Ring_MotionItemSelect(RING_INFO *ring, INVENTORY_ITEM *inv_item); +0x00425530 0x0063 + void __cdecl Inv_Ring_MotionItemDeselect(RING_INFO *ring, INVENTORY_ITEM *inv_item); + +# game/invtext.c +0x004255A0 0x0082 + void __cdecl Requester_Init(REQUEST_INFO *req); +0x00425630 0x00A3 + void __cdecl Requester_Shutdown(REQUEST_INFO *req); +0x004256E0 0x001B + void __cdecl Requester_Item_CenterAlign(REQUEST_INFO *req, TEXTSTRING *txt); +0x00425700 0x0054 + void __cdecl Requester_Item_LeftAlign(REQUEST_INFO *req, TEXTSTRING *txt); +0x00425760 0x0056 + void __cdecl Requester_Item_RightAlign(REQUEST_INFO *req, TEXTSTRING *txt); +0x004257C0 0x0866 + int32_t __cdecl Requester_Display(REQUEST_INFO *req, int32_t des, int32_t backgrounds); +0x00426030 0x00AA + void __cdecl Requester_SetHeading(REQUEST_INFO *req, char *text1, uint32_t flags1, char *text2, uint32_t flags2); +0x004260E0 0x0013 + void __cdecl Requester_RemoveAllItems(REQUEST_INFO *req); +0x00426100 0x00C0 + void __cdecl Requester_ChangeItem(REQUEST_INFO *req, int32_t item, const char *text1, uint32_t flags1, const char *text2, uint32_t flags2); +0x004261C0 0x00AC + void __cdecl Requester_AddItem(REQUEST_INFO *req, const char *text1, uint32_t flags1, const char *text2, uint32_t flags2); +0x00426270 0x0039 + void __cdecl Requester_SetSize(REQUEST_INFO *req, int32_t maxlines, int32_t ypos); +0x004262B0 0x0081 +R int32_t __cdecl AddAssaultTime(uint32_t time); +0x00426340 0x01D6 +R void __cdecl ShowGymStatsText(char *time_str, int32_t type); +0x00426520 0x0397 +R void __cdecl ShowStatsText(char *time_str, int32_t type); +0x004268C0 0x0425 +R void __cdecl ShowEndStatsText(void); + +# game/items.c +0x00426CF0 0x0052 + void __cdecl Item_InitialiseArray(int32_t num_items); +0x00426D50 0x011E + void __cdecl Item_Kill(int16_t item_num); +0x00426E70 0x0039 + int16_t __cdecl Item_Create(void); +0x00426EB0 0x01B3 + void __cdecl Item_Initialise(int16_t item_num); +0x00427070 0x008A + void __cdecl Item_RemoveActive(int16_t item_num); +0x00427100 0x006F + void __cdecl Item_RemoveDrawn(int16_t item_num); +0x00427170 0x005A + void __cdecl Item_AddActive(int16_t item_num); +0x004271D0 0x009C + void __cdecl Item_NewRoom(int16_t item_num, int16_t room_num); +0x00427270 0x007C + int32_t __cdecl Item_GlobalReplace(GAME_OBJECT_ID src_object_id, GAME_OBJECT_ID dst_object_id); +0x004272F0 0x0030 + void __cdecl Effect_InitialiseArray(void); +0x00427320 0x006C + int16_t __cdecl Effect_Create(int16_t room_num); +0x00427390 0x00E3 + void __cdecl Effect_Kill(int16_t fx_num); +0x00427480 0x0093 + void __cdecl Effect_NewRoom(int16_t fx_num, int16_t room_num); +0x00427520 0x0058 + void __cdecl Item_ClearKilled(void); + +# game/lara.c +0x00427580 0x0195 + void __cdecl Lara_HandleAboveWater(ITEM *item, COLL_INFO *coll); +0x00427720 0x0066 + void __cdecl Lara_LookUpDown(void); +0x00427790 0x0072 + void __cdecl Lara_LookLeftRight(void); +0x00427810 0x0089 + void __cdecl Lara_ResetLook(void); +0x004278A0 0x008B + void __cdecl Lara_State_Walk(ITEM *item, COLL_INFO *coll); +0x00427930 0x0143 + void __cdecl Lara_State_Run(ITEM *item, COLL_INFO *coll); +0x00427A80 0x0148 + void __cdecl Lara_State_Stop(ITEM *item, COLL_INFO *coll); +0x00427BD0 0x00D3 + void __cdecl Lara_State_ForwardJump(ITEM *item, COLL_INFO *coll); +0x00427CB0 0x0057 + void __cdecl Lara_State_FastBack(ITEM *item, COLL_INFO *coll); +0x00427D10 0x008A + void __cdecl Lara_State_TurnRight(ITEM *item, COLL_INFO *coll); +0x00427DA0 0x0089 + void __cdecl Lara_State_TurnLeft(ITEM *item, COLL_INFO *coll); +0x00427E30 0x0014 + void __cdecl Lara_State_Death(ITEM *item, COLL_INFO *coll); +0x00427E50 0x0040 + void __cdecl Lara_State_FastFall(ITEM *item, COLL_INFO *coll); +0x00427E90 0x0058 + void __cdecl Lara_State_Hang(ITEM *item, COLL_INFO *coll); +0x00427EF0 0x001C + void __cdecl Lara_State_Reach(ITEM *item, COLL_INFO *coll); +0x00427F10 0x000A + void __cdecl Lara_State_Splat(ITEM *item, COLL_INFO *coll); +0x00427F20 0x010C + void __cdecl Lara_State_Compress(ITEM *item, COLL_INFO *coll); +0x00428030 0x0084 + void __cdecl Lara_State_Back(ITEM *item, COLL_INFO *coll); +0x004280C0 0x000B + void __cdecl Lara_State_Null(ITEM *item, COLL_INFO *coll); +0x004280D0 0x004B + void __cdecl Lara_State_FastTurn(ITEM *item, COLL_INFO *coll); +0x00428120 0x007C + void __cdecl Lara_State_StepRight(ITEM *item, COLL_INFO *coll); +0x004281A0 0x007C + void __cdecl Lara_State_StepLeft(ITEM *item, COLL_INFO *coll); +0x00428220 0x002B + void __cdecl Lara_State_Slide(ITEM *item, COLL_INFO *coll); +0x00428250 0x004A + void __cdecl Lara_State_BackJump(ITEM *item, COLL_INFO *coll); +0x004282A0 0x0033 + void __cdecl Lara_State_RightJump(ITEM *item, COLL_INFO *coll); +0x004282E0 0x0033 + void __cdecl Lara_State_LeftJump(ITEM *item, COLL_INFO *coll); +0x00428320 0x0013 + void __cdecl Lara_State_UpJump(ITEM *item, COLL_INFO *coll); +0x00428340 0x002C + void __cdecl Lara_State_Fallback(ITEM *item, COLL_INFO *coll); +0x00428370 0x0035 + void __cdecl Lara_State_HangLeft(ITEM *item, COLL_INFO *coll); +0x004283B0 0x0035 + void __cdecl Lara_State_HangRight(ITEM *item, COLL_INFO *coll); +0x004283F0 0x0018 + void __cdecl Lara_State_SlideBack(ITEM *item, COLL_INFO *coll); +0x00428410 0x0030 + void __cdecl Lara_State_PushBlock(ITEM *item, COLL_INFO *coll); +0x00428440 0x0027 + void __cdecl Lara_State_PPReady(ITEM *item, COLL_INFO *coll); +0x00428470 0x0030 + void __cdecl Lara_State_Pickup(ITEM *item, COLL_INFO *coll); +0x004284A0 0x0058 + void __cdecl Lara_State_PickupFlare(ITEM *item, COLL_INFO *coll); +0x00428500 0x0039 + void __cdecl Lara_State_SwitchOn(ITEM *item, COLL_INFO *coll); +0x00428540 0x0030 + void __cdecl Lara_State_UseKey(ITEM *item, COLL_INFO *coll); +0x00428570 0x001D + void __cdecl Lara_State_Special(ITEM *item, COLL_INFO *coll); +0x00428590 0x002F + void __cdecl Lara_State_SwanDive(ITEM *item, COLL_INFO *coll); +0x004285C0 0x0054 + void __cdecl Lara_State_FastDive(ITEM *item, COLL_INFO *coll); +0x00428620 0x0015 + void __cdecl Lara_State_WaterOut(ITEM *item, COLL_INFO *coll); +0x00428640 0x00CA + void __cdecl Lara_State_Wade(ITEM *item, COLL_INFO *coll); +0x00428710 0x0096 + void __cdecl Lara_State_DeathSlide(ITEM *item, COLL_INFO *coll); +0x004287B0 0x004C + void __cdecl Lara_State_Extra_Breath(ITEM *item, COLL_INFO *coll); +0x00428800 0x0047 + void __cdecl Lara_State_Extra_YetiKill(ITEM *item, COLL_INFO *coll); +0x00428850 0x0091 + void __cdecl Lara_State_Extra_SharkKill(ITEM *item, COLL_INFO *coll); +0x004288F0 0x0013 + void __cdecl Lara_State_Extra_Airlock(ITEM *item, COLL_INFO *coll); +0x00428910 0x001D + void __cdecl Lara_State_Extra_GongBong(ITEM *item, COLL_INFO *coll); +0x00428930 0x0051 + void __cdecl Lara_State_Extra_DinoKill(ITEM *item, COLL_INFO *coll); +0x00428990 0x00BC + void __cdecl Lara_State_Extra_PullDagger(ITEM *item, COLL_INFO *coll); +0x00428A50 0x004D + void __cdecl Lara_State_Extra_StartAnim(ITEM *item, COLL_INFO *coll); +0x00428AA0 0x00A5 + void __cdecl Lara_State_Extra_StartHouse(ITEM *item, COLL_INFO *coll); +0x00428B50 0x00A3 + void __cdecl Lara_State_Extra_FinalAnim(ITEM *item, COLL_INFO *coll); +0x00428C00 0x0051 + int32_t __cdecl Lara_Fallen(ITEM *item, COLL_INFO *coll); +0x00428C60 0x009B + void __cdecl Lara_CollideStop(ITEM *item, COLL_INFO *coll); +0x00428D20 0x0191 + void __cdecl Lara_Col_Walk(ITEM *item, COLL_INFO *coll); +0x00428EC0 0x0176 + void __cdecl Lara_Col_Run(ITEM *item, COLL_INFO *coll); +0x00429040 0x0081 + void __cdecl Lara_Col_Stop(ITEM *item, COLL_INFO *coll); +0x004290D0 0x00D7 + void __cdecl Lara_Col_ForwardJump(ITEM *item, COLL_INFO *coll); +0x004291B0 0x00B3 + void __cdecl Lara_Col_FastBack(ITEM *item, COLL_INFO *coll); +0x00429270 0x0095 + void __cdecl Lara_Col_TurnRight(ITEM *item, COLL_INFO *coll); +0x00429310 0x0013 + void __cdecl Lara_Col_TurnLeft(ITEM *item, COLL_INFO *coll); +0x00429330 0x0068 + void __cdecl Lara_Col_Death(ITEM *item, COLL_INFO *coll); +0x004293A0 0x0099 + void __cdecl Lara_Col_FastFall(ITEM *item, COLL_INFO *coll); +0x00429440 0x0127 + void __cdecl Lara_Col_Hang(ITEM *item, COLL_INFO *coll); +0x00429570 0x0090 + void __cdecl Lara_Col_Reach(ITEM *item, COLL_INFO *coll); +0x00429600 0x0059 + void __cdecl Lara_Col_Splat(ITEM *item, COLL_INFO *coll); +0x00429660 0x0013 + void __cdecl Lara_Col_Land(ITEM *item, COLL_INFO *coll); +0x00429680 0x0096 + void __cdecl Lara_Col_Compress( ITEM *item, COLL_INFO *coll ); +0x00429720 0x00FB + void __cdecl Lara_Col_Back(ITEM *item, COLL_INFO *coll); +0x00429820 0x00BE + void __cdecl Lara_Col_StepRight(ITEM *item, COLL_INFO *coll); +0x004298E0 0x0013 + void __cdecl Lara_Col_StepLeft(ITEM *item, COLL_INFO *coll); +0x00429900 0x001E + void __cdecl Lara_Col_Slide(ITEM *item, COLL_INFO *coll); +0x00429920 0x0023 + void __cdecl Lara_Col_BackJump(ITEM *item, COLL_INFO *coll); +0x00429950 0x0023 + void __cdecl Lara_Col_RightJump(ITEM *item, COLL_INFO *coll); +0x00429980 0x0023 + void __cdecl Lara_Col_LeftJump(ITEM *item, COLL_INFO *coll); +0x004299B0 0x011B + void __cdecl Lara_Col_UpJump(ITEM *item, COLL_INFO *coll); +0x00429AD0 0x0083 + void __cdecl Lara_Col_Fallback(ITEM *item, COLL_INFO *coll); +0x00429B60 0x0033 + void __cdecl Lara_Col_HangLeft(ITEM *item, COLL_INFO *coll); +0x00429BA0 0x0033 + void __cdecl Lara_Col_HangRight(ITEM *item, COLL_INFO *coll); +0x00429BE0 0x0023 + void __cdecl Lara_Col_SlideBack(ITEM *item, COLL_INFO *coll); +0x00429C10 0x0013 + void __cdecl Lara_Col_Null(ITEM *item, COLL_INFO *coll); +0x00429C30 0x0081 + void __cdecl Lara_Col_Roll(ITEM *item, COLL_INFO *coll); +0x00429CC0 0x00B3 + void __cdecl Lara_Col_Roll2(ITEM *item, COLL_INFO *coll); +0x00429D80 0x0069 + void __cdecl Lara_Col_SwanDive(ITEM *item, COLL_INFO *coll); +0x00429DF0 0x0079 + void __cdecl Lara_Col_FastDive(ITEM *item, COLL_INFO *coll); +0x00429E70 0x0162 + void __cdecl Lara_Col_Wade(ITEM *item, COLL_INFO *coll); +0x00429FE0 0x0036 + void __cdecl Lara_Col_Default(ITEM *item, COLL_INFO *coll); +0x0042A020 0x0074 + void __cdecl Lara_Col_Jumper(ITEM *item, COLL_INFO *coll); +0x0042A0A0 0x0032 + void __cdecl Lara_GetCollisionInfo(ITEM *item, COLL_INFO *coll); +0x0042A0E0 0x00E2 + void __cdecl Lara_SlideSlope(ITEM *item, COLL_INFO *coll); +0x0042A1D0 0x0067 + int32_t __cdecl Lara_HitCeiling(ITEM *item, COLL_INFO *coll); +0x0042A240 0x007F + int32_t __cdecl Lara_DeflectEdge(ITEM *item, COLL_INFO *coll); +0x0042A2C0 0x0136 + void __cdecl Lara_DeflectEdgeJump(ITEM *item, COLL_INFO *coll); +0x0042A440 0x00AB + void __cdecl Lara_SlideEdgeJump(ITEM *item, COLL_INFO *coll); +0x0042A530 0x00E1 + int32_t __cdecl Lara_TestWall(ITEM *item, int32_t front, int32_t right, int32_t down); +0x0042A640 0x00F5 + int32_t __cdecl Lara_TestHangOnClimbWall(ITEM *item, COLL_INFO *coll); +0x0042A750 0x00BE + int32_t __cdecl Lara_TestClimbStance(ITEM *item, COLL_INFO *coll); +0x0042A810 0x033E + void __cdecl Lara_HangTest(ITEM *item, COLL_INFO *coll); +0x0042AB70 0x00AD + int32_t __cdecl Lara_TestEdgeCatch(ITEM *item, COLL_INFO *coll, int32_t *edge); +0x0042AC20 0x016D + int32_t __cdecl Lara_TestHangJumpUp(ITEM *item, COLL_INFO *coll); +0x0042AD90 0x019E + int32_t __cdecl Lara_TestHangJump(ITEM *item, COLL_INFO *coll); +0x0042AF30 0x00B1 + int32_t __cdecl Lara_TestHangSwingIn(ITEM *item, PHD_ANGLE angle); +0x0042AFF0 0x02E7 + int32_t __cdecl Lara_TestVault(ITEM *item, COLL_INFO *coll); +0x0042B2E0 0x0130 + int32_t __cdecl Lara_TestSlide(ITEM *item, COLL_INFO *coll); +0x0042B410 0x0075 + int16_t __cdecl Lara_FloorFront(ITEM *item, PHD_ANGLE ang, int32_t dist); +0x0042B490 0x00BB + int32_t __cdecl Lara_LandedBad(ITEM *item, COLL_INFO *coll); +0x0042B550 0x038F + void __cdecl Lara_GetJointAbsPosition(XYZ_32 *vec, int32_t joint); +0x0042B8E0 0x031A + void __cdecl Lara_GetJointAbsPosition_I(ITEM *item, XYZ_32 *vec, int16_t *frame1, int16_t *frame2, int32_t frac, int32_t rate); + +# game/lara1gun.c +0x0042BC00 0x0033 + void __cdecl Gun_Rifle_DrawMeshes(LARA_GUN_TYPE weapon_type); +0x0042BC40 0x002B + void __cdecl Gun_Rifle_UndrawMeshes(LARA_GUN_TYPE weapon_type); +0x0042BC70 0x0070 + void __cdecl Gun_Rifle_Ready(LARA_GUN_TYPE weapon_type); +0x0042BCE0 0x00F5 + void __cdecl Gun_Rifle_Control(LARA_GUN_TYPE weapon_type); +0x0042BDE0 0x00F2 + void __cdecl Gun_Rifle_FireShotgun(void); +0x0042BEE0 0x007B + void __cdecl Gun_Rifle_FireM16(bool running); +0x0042BF60 0x0187 + void __cdecl Gun_Rifle_FireHarpoon(void); +0x0042C0F0 0x0344 - void __cdecl HarpoonBolt_Control(int16_t item_num); +0x0042C440 0x00F0 + void __cdecl Gun_Rifle_FireGrenade(void); +0x0042C530 0x03FD - void __cdecl Rocket_Control(int16_t item_num); +0x0042C930 0x0166 + void __cdecl Gun_Rifle_Draw(LARA_GUN_TYPE weapon_type); +0x0042CAA0 0x0104 + void __cdecl Gun_Rifle_Undraw(LARA_GUN_TYPE weapon_type); +0x0042CBB0 0x037E + void __cdecl Gun_Rifle_Animate(LARA_GUN_TYPE weapon_type); + +# game/lara2gun.c +0x0042CF60 0x004F + void __cdecl Gun_Pistols_SetArmInfo(LARA_ARM *arm, int32_t frame); +0x0042CFB0 0x007C + void __cdecl Gun_Pistols_Draw(LARA_GUN_TYPE weapon_type); +0x0042D030 0x0225 + void __cdecl Gun_Pistols_Undraw(LARA_GUN_TYPE weapon_type); +0x0042D260 0x005C + void __cdecl Gun_Pistols_Ready(LARA_GUN_TYPE weapon_type); +0x0042D2C0 0x004E + void __cdecl Gun_Pistols_DrawMeshes(LARA_GUN_TYPE weapon_type); +0x0042D310 0x003A + void __cdecl Gun_Pistols_UndrawMeshLeft(LARA_GUN_TYPE weapon_type); +0x0042D350 0x003A + void __cdecl Gun_Pistols_UndrawMeshRight(LARA_GUN_TYPE weapon_type); +0x0042D390 0x018C + void __cdecl Gun_Pistols_Control(LARA_GUN_TYPE weapon_type); +0x0042D520 0x0330 + void __cdecl Gun_Pistols_Animate(LARA_GUN_TYPE weapon_type); + +# game/laraclimb.c +0x0042D850 0x0035 + void __cdecl Lara_State_ClimbLeft(ITEM *item, COLL_INFO *coll); +0x0042D890 0x0035 + void __cdecl Lara_State_ClimbRight(ITEM *item, COLL_INFO *coll); +0x0042D8D0 0x0075 + void __cdecl Lara_State_ClimbStance(ITEM *item, COLL_INFO *coll); +0x0042D950 0x0014 + void __cdecl Lara_State_Climbing(ITEM *item, COLL_INFO *coll); +0x0042D970 0x001E + void __cdecl Lara_State_ClimbEnd(ITEM *item, COLL_INFO *coll); +0x0042D990 0x0014 + void __cdecl Lara_State_ClimbDown(ITEM *item, COLL_INFO *coll); +0x0042D9B0 0x005D + void __cdecl Lara_Col_ClimbLeft(ITEM *item, COLL_INFO *coll); +0x0042DA10 0x0059 + void __cdecl Lara_Col_ClimbRight(ITEM *item, COLL_INFO *coll); +0x0042DA70 0x020D + void __cdecl Lara_Col_ClimbStance(ITEM *item, COLL_INFO *coll); +0x0042DC80 0x014D + void __cdecl Lara_Col_Climbing(ITEM *item, COLL_INFO *coll); +0x0042DDD0 0x019C + void __cdecl Lara_Col_ClimbDown(ITEM *item, COLL_INFO *coll); +0x0042DF70 0x00AA + int32_t __cdecl Lara_CheckForLetGo(ITEM *item, COLL_INFO *coll); +0x0042E020 0x0263 + int32_t __cdecl Lara_TestClimb(int32_t x, int32_t y, int32_t z, int32_t xfront, int32_t zfront, int32_t item_height, int16_t item_room, int32_t *shift); +0x0042E290 0x00BC + int32_t __cdecl Lara_TestClimbPos(ITEM *item, int32_t front, int32_t right, int32_t origin, int32_t height, int32_t *shift); +0x0042E360 0x00EF + void __cdecl Lara_DoClimbLeftRight(ITEM *item, COLL_INFO *coll, int32_t result, int32_t shift); +0x0042E450 0x0235 + int32_t __cdecl Lara_TestClimbUpPos(ITEM *item, int32_t front, int32_t right, int32_t *shift, int32_t *ledge); + +# game/larafire.c +0x0042E6A0 0x04E8 + void __cdecl Gun_Control(void); +0x0042EC10 0x003B + int32_t __cdecl Gun_CheckForHoldingState(int32_t state); +0x0042EC50 0x011C + void __cdecl Gun_InitialiseNewWeapon(void); +0x0042ED90 0x0194 + void __cdecl Gun_TargetInfo(const WEAPON_INFO *winfo); +0x0042EF30 0x021C + void __cdecl Gun_GetNewTarget(WEAPON_INFO *winfo); +0x0042F150 0x00AA + void __cdecl Gun_FindTargetPoint(const ITEM *item, GAME_VECTOR *target); +0x0042F200 0x00C1 + void __cdecl Gun_AimWeapon(WEAPON_INFO *winfo, LARA_ARM *arm); +0x0042F2D0 0x0360 + int32_t __cdecl Gun_FireWeapon(LARA_GUN_TYPE weapon_type, ITEM *target, const ITEM *src, const PHD_ANGLE *angles); +0x0042F640 0x0096 + void __cdecl Gun_HitTarget(ITEM *item, GAME_VECTOR *hitpos, int32_t damage); +0x0042F6E0 0x0051 + void __cdecl Gun_SmashItem(int16_t item_num, LARA_GUN_TYPE weapon_type); +0x0042F740 0x003B + GAME_OBJECT_ID Gun_GetWeaponAnim(const LARA_GUN_TYPE gun_type); + +# game/laraflare.c +0x0042F7A0 0x009D - int32_t __cdecl Flare_DoLight(XYZ_32 *pos, int32_t flare_age); +0x0042F840 0x00D3 - void __cdecl Flare_DoInHand(int32_t flare_age); +0x0042F920 0x00F8 - void __cdecl Flare_DrawInAir(ITEM *item); +0x0042FA20 0x01D7 - void __cdecl Flare_Create(int32_t thrown); +0x0042FC00 0x004B - void __cdecl Flare_SetArm(int32_t frame); +0x0042FC50 0x0169 - void __cdecl Flare_Draw(void); +0x0042FDC0 0x0221 - void __cdecl Flare_Undraw(void); +0x0042FFF0 0x0018 - void __cdecl Flare_DrawMeshes(void); +0x00430010 0x0018 - void __cdecl Flare_UndrawMeshes(void); +0x00430030 0x003E - void __cdecl Flare_Ready(void); +0x00430070 0x026E - void __cdecl Flare_Control(int16_t item_num); + +# game/laramisc.c +0x004302E0 0x0668 + void __cdecl Lara_Control(int16_t item_num); +0x00430970 0x02CD + void __cdecl Lara_Animate(ITEM *item); +0x00430C70 0x013F + void __cdecl Lara_UseItem(GAME_OBJECT_ID object_id); +0x00430E30 0x00BA + void __cdecl Lara_CheatGetStuff(void); +0x00430EF0 0x001B + void __cdecl Lara_ControlExtra(int16_t item_num); +0x00430F10 0x0021 + void __cdecl Lara_InitialiseLoad(int16_t item_num); +0x00430F40 0x02BD + void __cdecl Lara_Initialise(int32_t type); +0x00431200 0x036C + void __cdecl Lara_InitialiseInventory(int32_t level_num); +0x00431570 0x00FA + void __cdecl Lara_InitialiseMeshes(int32_t level_num); + +# game/larasurf.c +0x00431670 0x0158 + void __cdecl Lara_HandleSurface(ITEM *item, COLL_INFO *coll); +0x004317D0 0x0070 + void __cdecl Lara_State_SurfSwim(ITEM *item, COLL_INFO *coll); +0x00431840 0x005E + void __cdecl Lara_State_SurfBack(ITEM *item, COLL_INFO *coll); +0x004318A0 0x0060 + void __cdecl Lara_State_SurfLeft(ITEM *item, COLL_INFO *coll); +0x00431900 0x0060 + void __cdecl Lara_State_SurfRight(ITEM *item, COLL_INFO *coll); +0x00431960 0x00EB + void __cdecl Lara_State_SurfTread(ITEM *item, COLL_INFO *coll); +0x00431A50 0x0032 + void __cdecl Lara_Col_SurfSwim(ITEM *item, COLL_INFO *coll); +0x00431A90 0x0023 + void __cdecl Lara_Col_SurfBack(ITEM *item, COLL_INFO *coll); +0x00431AC0 0x0023 + void __cdecl Lara_Col_SurfLeft(ITEM *item, COLL_INFO *coll); +0x00431AF0 0x0023 + void __cdecl Lara_Col_SurfRight(ITEM *item, COLL_INFO *coll); +0x00431B20 0x001E + void __cdecl Lara_Col_SurfTread(ITEM *item, COLL_INFO *coll); +0x00431B40 0x00F3 + void __cdecl Lara_SurfaceCollision(ITEM *item, COLL_INFO *coll); +0x00431C40 0x00E7 + int32_t __cdecl Lara_TestWaterStepOut(ITEM *item, COLL_INFO *coll); +0x00431D30 0x021C + int32_t __cdecl Lara_TestWaterClimbOut(ITEM *item, COLL_INFO *coll); + +# game/laraswim.c +0x00431F50 0x0223 + void __cdecl Lara_HandleUnderwater(ITEM *item, COLL_INFO *coll); +0x00432180 0x0086 + void __cdecl Lara_SwimTurn(ITEM *item); +0x00432210 0x006B + void __cdecl Lara_State_Swim(ITEM *item, COLL_INFO *coll); +0x00432280 0x0076 + void __cdecl Lara_State_Glide(ITEM *item, COLL_INFO *coll); +0x00432300 0x0085 + void __cdecl Lara_State_Tread(ITEM *item, COLL_INFO *coll); +0x00432390 0x0014 + void __cdecl Lara_State_Dive(ITEM *item, COLL_INFO *coll); +0x004323B0 0x0053 + void __cdecl Lara_State_UWDeath(ITEM *item, COLL_INFO *coll); +0x00432410 0x000B + void __cdecl Lara_State_UWTwist(ITEM *item, COLL_INFO *coll); +0x00432420 0x0013 + void __cdecl Lara_Col_Swim(ITEM *item, COLL_INFO *coll); +0x00432440 0x005B + void __cdecl Lara_Col_UWDeath(ITEM *item, COLL_INFO *coll); +0x004324A0 0x0192 + int32_t __cdecl Lara_GetWaterDepth(int32_t x, int32_t y, int32_t z, int16_t room_num); +0x00432640 0x00CE + void __cdecl Lara_TestWaterDepth(ITEM *item, COLL_INFO *coll); +0x00432710 0x015C + void __cdecl Lara_SwimCollision(ITEM *item, COLL_INFO *coll); +0x00432870 0x01EC + void __cdecl Lara_WaterCurrent(COLL_INFO *coll); + +# game/lot.c +0x00432A60 0x0053 + void __cdecl LOT_InitialiseArray(void); +0x00432AC0 0x004F + void __cdecl LOT_DisableBaddieAI(int16_t item_num); +0x00432B10 0x01B0 + bool __cdecl LOT_EnableBaddieAI(int16_t item_num, bool always); +0x00432CC0 0x0106 + void __cdecl LOT_InitialiseSlot(int16_t item_num, int32_t slot); +0x00432ED0 0x00B8 + void __cdecl LOT_CreateZone(ITEM *item); +0x00432F90 0x0049 + void __cdecl LOT_ClearLOT(LOT_INFO *lot); + +# game/missile.c +0x00432FE0 0x02D0 -R void __cdecl ControlMissile(int16_t fx_num); +0x004332B0 0x00A7 -R void __cdecl ShootAtLara(FX *fx); +0x00433360 0x0386 +R int32_t __cdecl Effect_ExplodingDeath(int16_t item_num, int32_t mesh_bits, int16_t damage); +0x004336F0 0x0200 -R void __cdecl BodyPart_Control(int16_t fx_num); + +# game/moveblock.c +0x004338F0 0x002C -R void __cdecl InitialiseMovingBlock(int16_t item_num); +0x00433920 0x0148 -R void __cdecl MovableBlock(int16_t item_num); +0x00433A70 0x0239 -R void __cdecl MovableBlockCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x00433CD0 0x004E -R int32_t __cdecl TestBlockMovable(ITEM *item, int32_t block_height); +0x00433D20 0x0137 -R int32_t __cdecl TestBlockPush(ITEM *item, int32_t block_height, uint16_t quadrant); +0x00433E70 0x0225 -R int32_t __cdecl TestBlockPull(ITEM *item, int32_t block_height, uint16_t quadrant); +0x004340B0 0x00BB + void __cdecl Room_AlterFloorHeight(ITEM *item, int32_t height); +0x00434170 0x0022 -R void __cdecl DrawMovableBlock(ITEM *item); +0x004341A0 0x006B -R void __cdecl DrawUnclippedItem(ITEM *item); + +# game/objects.c +0x00434210 0x00DB -R void __cdecl EarthQuake(int16_t item_num); +0x004342F0 0x003C -R void __cdecl ControlCutShotgun(int16_t item_num); +0x00434330 0x009D -R void __cdecl InitialiseFinalLevel(void); +0x00434400 0x020F + void __cdecl FinalLevelCounter_Control(int16_t item_num); +0x00434610 0x00D9 -R void __cdecl MiniCopterControl(int16_t item_num); +0x004346F0 0x007C -R void __cdecl InitialiseDyingMonk(int16_t item_num); +0x00434770 0x0087 -R void __cdecl DyingMonk(int16_t item_num); +0x00434800 0x00BD -R void __cdecl ControlGongBonger(int16_t item_num); +0x004348C0 0x00BF -R void __cdecl DeathSlideCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x00434980 0x028F -R void __cdecl ControlDeathSlide(int16_t item_num); +0x00434C10 0x00E3 -R void __cdecl BigBowlControl(int16_t item_num); +0x00434D00 0x007E -R void __cdecl BellControl(int16_t item_num); +0x00434D80 0x0075 -R void __cdecl InitialiseWindow(int16_t item_num); +0x00434E00 0x00C4 -R void __cdecl SmashWindow(int16_t item_num); +0x00434ED0 0x0096 -R void __cdecl WindowControl(int16_t item_num); +0x00434F70 0x00DC -R void __cdecl SmashIceControl(int16_t item_num); +0x00435050 0x0042 -R void __cdecl ShutThatDoor(DOORPOS_DATA *d); +0x004350A0 0x0032 -R void __cdecl OpenThatDoor(DOORPOS_DATA *d); +0x004350E0 0x03DC -R void __cdecl InitialiseDoor(int16_t item_num); +0x004354C0 0x00C8 -R void __cdecl DoorControl(int16_t item_num); +0x00435590 0x00B1 -R int32_t __cdecl OnDrawBridge(ITEM *item, int32_t x, int32_t y); +0x00435650 0x0036 -R void __cdecl DrawBridgeFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x00435690 0x003B -R void __cdecl DrawBridgeCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x004356D0 0x002C -R void __cdecl DrawBridgeCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x00435700 0x0035 -R void __cdecl InitialiseLift(int16_t item_num); +0x00435740 0x00D4 -R void __cdecl LiftControl(int16_t item_num); +0x00435820 0x0179 -R void __cdecl LiftFloorCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *floor, int32_t *ceiling); +0x004359A0 0x0035 -R void __cdecl LiftFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x004359E0 0x0035 -R void __cdecl LiftCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x00435A20 0x0016 -R void __cdecl BridgeFlatFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x00435A40 0x001B -R void __cdecl BridgeFlatCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x00435A60 0x003B -R int32_t __cdecl GetOffset(ITEM *item, int32_t x, int32_t z); +0x00435AA0 0x0030 -R void __cdecl BridgeTilt1Floor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x00435AD0 0x0035 -R void __cdecl BridgeTilt1Ceiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x00435B10 0x002F -R void __cdecl BridgeTilt2Floor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x00435B40 0x0034 -R void __cdecl BridgeTilt2Ceiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x00435B80 0x010C -R void __cdecl CopterControl(int16_t item_num); +0x00435C90 0x00D2 -R void __cdecl GeneralControl(int16_t item_num); +0x00435D70 0x008D -R void __cdecl DetonatorControl(int16_t item_num); + +# game/people.c +0x00435E00 0x0085 + bool __cdecl Creature_CanTargetEnemy(const ITEM *item, const AI_INFO *info); +0x00435E90 0x003B -R void __cdecl ControlGlow(int16_t fx_num); +0x00435ED0 0x004E -R void __cdecl ControlGunShot(int16_t fx_num); +0x00435F20 0x0066 -R int16_t __cdecl GunShot(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +0x00435F90 0x00B9 -R int16_t __cdecl GunHit(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +0x00436050 0x00A7 -R int16_t __cdecl GunMiss(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num); +0x00436100 0x01C4 -R int32_t __cdecl ShotLara(ITEM *item, AI_INFO *info, BITE *gun, int16_t extra_rotation, int32_t damage); +0x004362D0 0x0043 -R void __cdecl InitialiseCult1(int16_t item_num); +0x00436320 0x0401 -R void __cdecl Cult1Control(int16_t item_num); +0x00436750 0x0050 -R void __cdecl InitialiseCult3(int16_t item_num); +0x004367A0 0x053C -R void __cdecl Cult3Control(int16_t item_num); +0x00436D10 0x03CA -R void __cdecl Worker1Control(int16_t item_num); +0x00437110 0x042A -R void __cdecl Worker2Control(int16_t item_num); +0x00437570 0x030B -R void __cdecl BanditControl(int16_t item_num); +0x004378B0 0x0408 -R void __cdecl Bandit2Control(int16_t item_num); +0x00437CF0 0x0172 -R void __cdecl WinstonControl(int16_t item_num); + +# game/pickup.c +0x00437E70 0x0480 -R void __cdecl PickUpCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x004382F0 0x020A -R void __cdecl SwitchCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x00438500 0x00FC -R void __cdecl SwitchCollision2(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x00438600 0x023B -R void __cdecl DetonatorCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x00438840 0x0223 -R void __cdecl KeyHoleCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x00438A80 0x0294 -R void __cdecl PuzzleHoleCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +0x00438D40 0x0039 -R void __cdecl SwitchControl(int16_t item_num); +0x00438D80 0x00BD -R int32_t __cdecl SwitchTrigger(int16_t item_num, int16_t timer); +0x00438E40 0x003D -R int32_t __cdecl KeyTrigger(int16_t item_num); +0x00438E80 0x0033 -R int32_t __cdecl PickupTrigger(int16_t item_num); +0x00438EC0 0x0023 -R void __cdecl SecretControl(int16_t item_num); + +# game/rat.c +0x00438EF0 0x01DC -R void __cdecl MouseControl(int16_t item_num); + +# game/savegame.c +0x004390E0 0x0062 *R void __cdecl InitialiseStartInfo(void); +0x00439150 0x00DB -R void __cdecl ModifyStartInfo(int32_t level_num); +0x00439230 0x0201 -R void __cdecl CreateStartInfo(int32_t level_num); +0x00439440 0x052B -R void __cdecl CreateSaveGameInfo(void); +0x00439970 0x085C -R void __cdecl ExtractSaveGameInfo(void); +0x0043A1D0 0x0015 -R void __cdecl ResetSG(void); +0x0043A1F0 0x004C -R void __cdecl WriteSG(void *pointer, int32_t size); +0x0043A240 0x0035 -R void __cdecl ReadSG(void *pointer, int32_t size); + +# game/setup.c +0x0043A280 0x015F + int32_t __cdecl Level_Initialise(int32_t level_num, int32_t level_type); +0x0043A3E0 0x0061 -R void __cdecl InitialiseGameFlags(void); +0x0043A450 0x0027 -R void __cdecl InitialiseLevelFlags(void); +0x0043A480 0x103B -R void __cdecl BaddyObjects(void); +0x0043B4C0 0x05FD -R void __cdecl TrapObjects(void); +0x0043BAC0 0x0C4C -R void __cdecl ObjectObjects(void); +0x0043C710 0x0068 -R void __cdecl InitialiseObjects(void); +0x0043C780 0x00CE -R void __cdecl GetCarriedItems(void); + +# game/shark.c +0x0043C850 0x0116 -R void __cdecl JellyControl(int16_t item_num); +0x0043C970 0x021B -R void __cdecl BaracuddaControl(int16_t item_num); +0x0043CBA0 0x027C -R void __cdecl SharkControl(int16_t item_num); + +# game/skidoo.c +0x0043CE30 0x0040 -R void __cdecl InitialiseSkidoo(int16_t item_num); +0x0043CE70 0x00E1 -R int32_t __cdecl SkidooCheckGeton(int16_t item_num, COLL_INFO *coll); +0x0043CF60 0x00F8 -R void __cdecl SkidooCollision(int16_t item_num, ITEM *litem, COLL_INFO *coll); +0x0043D060 0x01F9 -R void __cdecl SkidooBaddieCollision(ITEM *skidoo); +0x0043D260 0x00B2 -R int32_t __cdecl TestHeight(ITEM *item, int32_t z_off, int32_t x_off, XYZ_32 *pos); +0x0043D320 0x027C -R int32_t __cdecl DoShift(ITEM *skidoo, XYZ_32 *pos, XYZ_32 *old); +0x0043D5A0 0x0054 -R int32_t __cdecl DoDynamics(int32_t height, int32_t fall_speed, int32_t *y); +0x0043D600 0x0090 -R int32_t __cdecl GetCollisionAnim(ITEM *skidoo, XYZ_32 *moved); +0x0043D690 0x0140 -R void __cdecl DoSnowEffect(ITEM *skidoo); +0x0043D7D0 0x049E -R int32_t __cdecl SkidooDynamics(ITEM *skidoo); +0x0043DC70 0x01B6 -R int32_t __cdecl SkidooUserControl(ITEM *skidoo, int32_t height, int32_t *pitch); +0x0043DE30 0x0106 -R int32_t __cdecl SkidooCheckGetOffOK(int32_t direction); +0x0043DF40 0x02B9 -R void __cdecl SkidooAnimation(ITEM *skidoo, int32_t collide, int32_t dead); +0x0043E220 0x007C -R void __cdecl SkidooExplode(ITEM *skidoo); +0x0043E2A0 0x0233 -R int32_t __cdecl SkidooCheckGetOff(void); +0x0043E4E0 0x011B -R void __cdecl SkidooGuns(void); +0x0043E600 0x0440 -R int32_t __cdecl SkidooControl(void); +0x0043EA60 0x02D5 -R void __cdecl DrawSkidoo(ITEM *item); +0x0043ED40 0x007F -R void __cdecl InitialiseSkidman(int16_t item_num); +0x0043EDD0 0x03E2 -R void __cdecl SkidManControl(int16_t rider_num); +0x0043F1D0 0x0119 -R void __cdecl SkidmanPush(ITEM *item, ITEM *lara_item, int32_t radius); +0x0043F2F0 0x0081 -R void __cdecl SkidmanCollision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); + +# game/sound.c +0x0043F380 0x0031 * int32_t __cdecl Music_GetRealTrack(int32_t track); +0x0043F3C0 0x0484 + void __cdecl Sound_Effect(int32_t sample_id, const XYZ_32 *pos, uint32_t flags); +0x0043F860 0x005E + void __cdecl Sound_StopEffect(int32_t sample_id); +0x0043F8C0 0x0086 + void __cdecl Sound_EndScene(void); +0x0043F950 0x0024 + void __cdecl Sound_Shutdown(void); +0x0043F980 0x002A + void __cdecl Sound_Init(void); + +# game/sphere.c +0x0043F9B0 0x0128 - int32_t __cdecl Collide_TestCollision(ITEM *item, const ITEM *lara_item); +0x0043FAE0 0x02D8 - int32_t __cdecl Collide_GetSpheres(const ITEM *item, SPHERE *spheres, bool world_space); +0x0043FDC0 0x019A - void __cdecl Collide_GetJointAbsPosition(const ITEM *item, const XYZ_32 *vec, int32_t joint); +0x0043FF60 0x005D -R void __cdecl BaddieBiteEffect(ITEM *item, BITE *bite); + +# game/spider.c +0x0043FFC0 0x00AC -R void __cdecl SpiderLeap(int16_t item_num, int16_t angle); +0x00440070 0x0206 -R void __cdecl SpiderControl(int16_t item_num); +0x00440290 0x01A5 -R void __cdecl BigSpiderControl(int16_t item_num); + +# game/text.c +0x00440450 0x002C + void __cdecl Text_Init(void); +0x00440480 0x0105 + TEXTSTRING *__cdecl Text_Create(int32_t x, int32_t y, int32_t z, const char *text); +0x00440590 0x0037 + void __cdecl Text_ChangeText(TEXTSTRING *string, const char *text); +0x004405D0 0x0017 + void __cdecl Text_SetScale(TEXTSTRING *string, int32_t scale_h, int32_t scale_v); +0x004405F0 0x002B + void __cdecl Text_Flash(TEXTSTRING *string, int16_t enable, int16_t rate); +0x00440620 0x008C + void __cdecl Text_AddBackground(TEXTSTRING *string, int16_t x_size, int16_t y_size, int16_t x_off, int16_t y_off, int16_t z_off, int16_t color, uint16_t *gour_ptr, uint16_t flags); +0x004406B0 0x0010 + void __cdecl Text_RemoveBackground(TEXTSTRING *string); +0x004406C0 0x0029 + void __cdecl Text_AddOutline(TEXTSTRING *string, int16_t enable, int16_t color, uint16_t *gour_ptr, uint16_t flags); +0x004406F0 0x0010 + void __cdecl Text_RemoveOutline(TEXTSTRING *string); +0x00440700 0x001E + void __cdecl Text_CentreH(TEXTSTRING *string, int16_t enable); +0x00440720 0x001E + void __cdecl Text_CentreV(TEXTSTRING *string, int16_t enable); +0x00440740 0x001E + void __cdecl Text_AlignRight(TEXTSTRING *string, int16_t enable); +0x00440760 0x001E + void __cdecl Text_AlignBottom(TEXTSTRING *string, int16_t enable); +0x00440780 0x0107 + int32_t __cdecl Text_GetWidth(TEXTSTRING *string); +0x00440890 0x0025 + int32_t __cdecl Text_Remove(TEXTSTRING *string); +0x004408C0 0x0024 + int16_t __cdecl Text_GetTextLength(const char *text); +0x004408F0 0x0027 + void __cdecl Text_Draw(void); +0x00440920 0x0189 + void __cdecl Text_DrawBorder(int32_t x, int32_t y, int32_t z, int32_t width, int32_t height); +0x00440AB0 0x03D2 + void __cdecl Text_DrawText(const TEXTSTRING *string); +0x00440E90 0x0037 + uint32_t __cdecl Text_GetScaleH(uint32_t value); +0x00440ED0 0x0039 + uint32_t __cdecl Text_GetScaleV(uint32_t value); + +# game/traps.c +0x00440F10 0x01F4 -R void __cdecl MineControl(int16_t mine_num); +0x00441110 0x0138 -R void __cdecl ControlSpikeWall(int16_t item_num); +0x00441250 0x0115 -R void __cdecl ControlCeilingSpikes(int16_t item_num); +0x00441370 0x0086 -R void __cdecl HookControl(int16_t item_num); +0x00441400 0x0190 -R void __cdecl PropellerControl(int16_t item_num); +0x00441590 0x017B -R void __cdecl SpinningBlade(int16_t item_num); +0x00441710 0x00FE -R void __cdecl IcicleControl(int16_t item_num); +0x00441810 0x003C -R void __cdecl InitialiseBlade(int16_t item_num); +0x00441850 0x0091 -R void __cdecl BladeControl(int16_t item_num); +0x004418F0 0x0046 -R void __cdecl InitialiseKillerStatue(int16_t item_num); +0x00441940 0x0109 -R void __cdecl KillerStatueControl(int16_t item_num); +0x00441A50 0x00DB -R void __cdecl SpringBoardControl(int16_t item_num); +0x00441B30 0x003C -R void __cdecl InitialiseRollingBall(int16_t item_num); +0x00441B70 0x0347 -R void __cdecl RollingBallControl(int16_t item_num); +0x00441EC0 0x024A -R void __cdecl RollingBallCollision(int16_t item_num, ITEM *litem, COLL_INFO *coll); +0x00442110 0x0155 -R void __cdecl SpikeCollision(int16_t item_num, ITEM *litem, COLL_INFO *coll); +0x00442270 0x004F -R void __cdecl TrapDoorControl(int16_t item_num); +0x004422C0 0x003A -R void __cdecl TrapDoorFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x00442300 0x003F -R void __cdecl TrapDoorCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x00442340 0x00A3 -R int32_t __cdecl OnTrapDoor(ITEM *item, int32_t x, int32_t z); +0x004423F0 0x010A -R void __cdecl Pendulum(int16_t item_num); +0x00442500 0x0105 -R void __cdecl FallingBlock(int16_t item_num); +0x00442610 0x003E -R void __cdecl FallingBlockFloor(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x00442650 0x0044 -R void __cdecl FallingBlockCeiling(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height); +0x004426A0 0x00BD -R void __cdecl TeethTrap(int16_t item_num); +0x00442760 0x00E0 -R void __cdecl FallingCeiling(int16_t item_num); +0x00442840 0x013E -R void __cdecl DartEmitterControl(int16_t item_num); +0x00442980 0x0155 -R void __cdecl DartsControl(int16_t item_num); +0x00442AE0 0x004B -R void __cdecl DartEffectControl(int16_t fx_num); +0x00442B30 0x0090 + void __cdecl FlameEmitter_Control(int16_t item_num); +0x00442BC0 0x0164 + void __cdecl Flame_Control(int16_t fx_num); +0x00442D30 0x0049 + void __cdecl Lara_CatchFire(void); +0x00442D80 0x00E6 + void __cdecl Lara_TouchLava(ITEM *item); +0x00442E70 0x00C5 + void __cdecl EmberEmitter_Control(int16_t item_num); +0x00442F40 0x010B + void __cdecl Ember_Control(int16_t fx_num); + +# game/yeti.c +0x00443050 0x02CA -R void __cdecl GiantYetiControl(int16_t item_num); +0x00443350 0x05ED -R void __cdecl YetiControl(int16_t item_num); + +0x00443990 0x01B8 +R void __cdecl BGND_Make640x480(uint8_t *bitmap, RGB_888 *palette); +0x00443B50 0x00B9 +R int32_t __cdecl BGND_AddTexture(int32_t tile_idx, BYTE *bitmap, int32_t pal_index, RGB_888 *bmp_pal); +0x00443C10 0x0032 +R void __cdecl BGND_GetPageHandles(void); +0x00443C50 0x005F +R void __cdecl BGND_DrawInGameBlack(void); +0x00443CB0 0x00DC +R void __cdecl DrawQuad(float sx, float sy, float width, float height, D3DCOLOR color); +0x00443D90 0x0220 +R void __cdecl BGND_DrawInGameBackground(void); +0x00443FB0 0x0251 +R void __cdecl DrawTextureTile(int32_t sx, int32_t sy, int32_t width, int32_t height, HWR_TEXTURE_HANDLE tex_source, int32_t tu, int32_t tv, int32_t t_width, int32_t t_height, D3DCOLOR color0, D3DCOLOR color1, D3DCOLOR color2, D3DCOLOR color3); +0x00444210 0x008B +R D3DCOLOR __cdecl BGND_CenterLighting(int32_t x, int32_t y, int32_t width, int32_t height); +0x004444C0 0x004D +R void __cdecl BGND_Free(void); +0x00444510 0x0030 +R bool __cdecl BGND_Init(void); +0x00444540 0x003E +R void __cdecl Enumerate3DDevices(DISPLAY_ADAPTER *adapter); +0x00444570 0x001F +R bool __cdecl D3DCreate(void); +0x004445B0 0x00BD +R HRESULT __stdcall Enum3DDevicesCallback(GUID *lpGuid, LPTSTR lpDeviceDescription, LPTSTR lpDeviceName, LPD3DDEVICEDESC lpD3DHWDeviceDesc, LPD3DDEVICEDESC lpD3DHELDeviceDesc, LPVOID lpContext); +0x00444670 0x0037 +R bool __cdecl D3DIsSupported(LPD3DDEVICEDESC desc); +0x004446B0 0x00B9 +R bool __cdecl D3DSetViewport(void); +0x00444770 0x01B8 +R void __cdecl D3DDeviceCreate(LPDDS lpBackBuffer); +0x00444930 0x006A +R void __cdecl Direct3DRelease(void); +0x00444980 0x0006 +R bool __cdecl Direct3DInit(void); +0x00444990 0x0018 x sub_444990 +0x004449A0 0x0012 x sub_4449A0 +0x004449D0 0x00C6 x sub_4449D0 +0x00444AA0 0x0018 x sub_444AA0 +0x00444AB0 0x005F x sub_444AB0 +0x00444B20 0x008C x sub_444B20 +0x00444BB0 0x0005 x sub_444BB0 +0x00444BC0 0x0001 x sub_444BC0 +0x00444BD0 0x0054 +R bool __cdecl DDrawCreate(LPGUID lpGUID); +0x00444C30 0x0033 +R void __cdecl DDrawRelease(void); +0x00444C70 0x0073 +R void __cdecl GameWindowCalculateSizeFromClient(int32_t *width, int32_t *height); +0x00444CF0 0x006A +R void __cdecl GameWindowCalculateSizeFromClientByZero(int32_t *width, int32_t *height); +0x00444D60 0x0041 +R void __cdecl WinVidSetMinWindowSize(int32_t width, int32_t height); +0x00444DB0 0x0008 +R void __cdecl WinVidClearMinWindowSize(void); +0x00444DC0 0x0041 +R void __cdecl WinVidSetMaxWindowSize(int32_t width, int32_t height); +0x00444E10 0x0008 +R void __cdecl WinVidClearMaxWindowSize(void); +0x00444E20 0x0048 +R int32_t __cdecl CalculateWindowWidth(int32_t width, int32_t height); +0x00444E70 0x0028 +R int32_t __cdecl CalculateWindowHeight(int32_t width, int32_t height); +0x00444EA0 0x0104 +R bool __cdecl WinVidGetMinMaxInfo(LPMINMAXINFO info); +0x00444FB0 0x0011 +R HWND __cdecl WinVidFindGameWindow(void); +0x00444FD0 0x00E2 +R bool __cdecl WinVidSpinMessageLoop(bool needWait); +0x004450C0 0x0043 +R void __cdecl WinVidShowGameWindow(int32_t nCmdShow); +0x00445110 0x003A +R void __cdecl WinVidHideGameWindow(void); +0x00445150 0x0035 +R void __cdecl WinVidSetGameWindowSize(int32_t width, int32_t height); +0x00445190 0x00A7 +R bool __cdecl ShowDDrawGameWindow(bool active); +0x00445240 0x0087 +R bool __cdecl HideDDrawGameWindow(void); +0x004452D0 0x0044 +R HRESULT __cdecl DDrawSurfaceCreate(LPDDSDESC dsp, LPDDS *surface); +0x00445320 0x0046 +R HRESULT __cdecl DDrawSurfaceRestoreLost(LPDDS surface1, LPDDS surface2, bool blank); +0x00445370 0x004D +R bool __cdecl WinVidClearBuffer(LPDDS surface, LPRECT rect, DWORD fill_color); +0x004453C0 0x003D +R HRESULT __cdecl WinVidBufferLock(LPDDS surface, LPDDSDESC desc, DWORD flags); +0x00445400 0x0025 +R HRESULT __cdecl WinVidBufferUnlock(LPDDS surface, LPDDSDESC desc); +0x00445430 0x0090 +R bool __cdecl WinVidCopyBitmapToBuffer(LPDDS surface, const BYTE *bitmap); +0x004454C0 0x0046 +R DWORD __cdecl GetRenderBitDepth(DWORD dwRGBBitCount); +0x00445550 0x0071 +R void __thiscall WinVidGetColorBitMasks(COLOR_BIT_MASKS *bm, LPDDPIXELFORMAT pixel_format); +0x004455D0 0x0044 +R void __cdecl BitMaskGetNumberOfBits(uint32_t bit_mask, uint32_t *bit_depth, uint32_t *bit_offset); +0x00445620 0x0061 +R DWORD __cdecl CalculateCompatibleColor(COLOR_BIT_MASKS *mask, int32_t red, int32_t green, int32_t blue, int32_t alpha); +0x00445690 0x008C +R bool __cdecl WinVidGetDisplayMode(DISPLAY_MODE *disp_mode); +0x00445720 0x0088 +R bool __cdecl WinVidGoFullScreen(DISPLAY_MODE *disp_mode); +0x004457B0 0x010B +R bool __cdecl WinVidGoWindowed(int32_t width, int32_t height, DISPLAY_MODE *dispMode); +0x004458C0 0x00D5 +R void __cdecl WinVidSetDisplayAdapter(DISPLAY_ADAPTER *disp_adapter); +0x004459A0 0x0045 +R bool __thiscall CompareVideoModes(const DISPLAY_MODE *mode1, const DISPLAY_MODE *mode2); +0x004459F0 0x0053 +R bool __cdecl WinVidGetDisplayModes(void); +0x00445A50 0x03B1 +R HRESULT __stdcall EnumDisplayModesCallback(LPDDSDESC lpDDSurfaceDesc, LPVOID lpContext); +0x00445E10 0x0040 +R bool __cdecl WinVidInit(void); +0x00445E50 0x00AF +R bool __cdecl WinVidGetDisplayAdapters(void); +0x00445F00 0x0013 + void __thiscall S_FlaggedString_Delete(STRING_FLAGGED *string); +0x00445F20 0x001A +R bool __cdecl EnumerateDisplayAdapters(DISPLAY_ADAPTER_LIST *displayAdapterList); +0x00445F40 0x01BE +R BOOL __stdcall EnumDisplayAdaptersCallback(GUID *lpGUID, LPTSTR lpDriverDescription, LPTSTR lpDriverName, LPVOID lpContext); +0x00446100 0x0035 + void __thiscall S_FlaggedString_InitAdapter(DISPLAY_ADAPTER *adapter); +0x00446140 0x006A +R bool __cdecl WinVidRegisterGameWindowClass(void); +0x004461B0 0x049F +R LRESULT __stdcall WinVidGameWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); +0x004467C0 0x01C0 +R void __cdecl WinVidResizeGameWindow(HWND hWnd, int32_t edge, LPRECT rect); +0x004469A0 0x00BC +R bool __cdecl WinVidCheckGameWindowPalette(HWND hWnd); +0x00446A60 0x00C6 +R bool __cdecl WinVidCreateGameWindow(void); +0x00446B30 0x0022 +R void __cdecl WinVidFreeWindow(void); +0x00446B60 0x004D +R void __cdecl WinVidExitMessage(void); +0x00446BB0 0x0048 +R DISPLAY_ADAPTER_NODE *__cdecl WinVidGetDisplayAdapter(GUID *lpGuid); +0x00446C00 0x0374 +R void __cdecl WinVidStart(void); +0x00446F80 0x0013 +R void __cdecl WinVidFinish(void); +0x00446FA0 0x000D +R void __thiscall DisplayModeListInit(DISPLAY_MODE_LIST *pList); +0x00446FB0 0x0032 +R void __thiscall DisplayModeListDelete(DISPLAY_MODE_LIST *pList); +0x00446FF0 0x0012 +R DISPLAY_MODE *__thiscall InsertDisplayMode(DISPLAY_MODE_LIST *modeList, DISPLAY_MODE_NODE *before); +0x00447010 0x0048 +R DISPLAY_MODE *__thiscall InsertDisplayModeInListHead(DISPLAY_MODE_LIST *modeList); +0x00447060 0x004A +R DISPLAY_MODE *__thiscall InsertDisplayModeInListTail(DISPLAY_MODE_LIST *modeList); +0x004470B0 0x0018 x sub_4470B0 +0x004470C0 0x0012 x sub_4470C0 +0x004470F0 0x0068 x sub_4470F0 +0x00447160 0x0018 x sub_447160 +0x00447170 0x0039 x sub_447170 +0x004471C0 0x002F x sub_4471C0 +0x004471F0 0x0022 +R bool __cdecl DInputCreate(void); +0x00447220 0x001A +R void __cdecl DInputRelease(void); +0x00447240 0x005A +R void __cdecl WinInReadKeyboard(LPVOID lpInputData); +0x004472A0 0x00F3 -R DWORD __cdecl WinInReadJoystick(int32_t *x, int32_t *y); +0x004473A0 0x0005 x sub_4473A0 +0x004473B0 0x007F -R bool __cdecl WinInputInit(void); +0x00447430 0x0024 -R bool __cdecl DInputEnumDevices(JOYSTICK_LIST *joystickList); +0x00447460 0x00E8 -R BOOL __stdcall DInputEnumDevicesCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef); +0x00447550 0x001F + void __thiscall S_FlaggedString_Create(STRING_FLAGGED *string, int32_t size); +0x00447570 0x004E -R JOYSTICK_NODE *__cdecl GetJoystick(GUID *lpGuid); +0x004475C0 0x00C9 -R void __cdecl DInputKeyboardCreate(void); +0x00447690 0x0029 -R void __cdecl DInputKeyboardRelease(void); +0x004476C0 0x00E4 -R bool __cdecl DInputJoystickCreate(void); +0x004477B0 0x002D -R void __cdecl WinInStart(void); +0x004477E0 0x000F -R void __cdecl WinInFinish(void); +0x004477F0 0x0017 -R void __cdecl WinInRunControlPanel(HWND hWnd); +0x00447810 0x0062 *R void __cdecl IncreaseScreenSize(void); +0x00447880 0x0062 *R void __cdecl DecreaseScreenSize(void); +0x004478F0 0x009F *R void __cdecl setup_screen_size(void); +0x00447990 0x0034 *R void __cdecl TempVideoAdjust(int32_t hires, double sizer); +0x004479D0 0x0039 -R void __cdecl TempVideoRemove(void); +0x00447A10 0x0035 -R void __cdecl S_FadeInInventory(BOOL isFade); +0x00447A50 0x0027 -R void __cdecl S_FadeOutInventory(BOOL isFade); +0x00447A80 0x0018 x sub_447A80 +0x00447A90 0x0012 x sub_447A90 +0x00447AC0 0x0068 x sub_447AC0 +0x00447B30 0x0018 x sub_447B30 +0x00447B40 0x0039 x sub_447B40 +0x00447B90 0x002F x sub_447B90 +0x00447BC0 0x0048 + const SOUND_ADAPTER_NODE *__cdecl S_Audio_Sample_GetAdapter(GUID *guid); +0x00447C10 0x002E + void __cdecl S_Audio_Sample_CloseAllTracks(void); +0x00447C40 0x010E + bool __cdecl S_Audio_Sample_Load(int32_t sample_id, LPWAVEFORMATEX format, const void *data, int32_t data_size); +0x00447D50 0x0045 + bool __cdecl S_Audio_Sample_IsTrackPlaying(int32_t track_id); +0x00447DA0 0x00E7 + int32_t __cdecl S_Audio_Sample_Play(int32_t sample_id, int32_t volume, int32_t pitch, int32_t pan, int32_t flags); +0x00447E90 0x0039 + int32_t __cdecl S_Audio_Sample_GetFreeTrackIndex(void); +0x00447ED0 0x002C + void __cdecl S_Audio_Sample_AdjustTrackVolumeAndPan(int32_t track_id, int32_t volume, int32_t pan); +0x00447F00 0x0031 + void __cdecl S_Audio_Sample_AdjustTrackPitch(int32_t track_id, int32_t pitch); +0x00447F40 0x002F + void __cdecl S_Audio_Sample_CloseTrack(int32_t track_id); +0x00447FA0 0x0005 x sub_447FA0 +0x00447FB0 0x009C + bool __cdecl S_Audio_Sample_Init(void); +0x00448050 0x001A + bool __cdecl S_Audio_Sample_DSoundEnumerate(SOUND_ADAPTER_LIST *adapter_list); +0x00448070 0x00E2 + BOOL __stdcall S_Audio_Sample_DSoundEnumCallback(LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context); +0x00448160 0x017C + void __cdecl S_Audio_Sample_Init2(HWND hwnd); +0x004482E0 0x001C + bool __cdecl S_Audio_Sample_DSoundCreate(GUID *guid); +0x00448300 0x00C4 + bool __cdecl S_Audio_Sample_DSoundBufferTest(void); +0x004483D0 0x002A + void __cdecl S_Audio_Sample_Shutdown(void); +0x00448400 0x0006 + bool __cdecl S_Audio_Sample_IsEnabled(void); +0x00448410 0x0005 x sub_448410 +0x00448420 0x0001 x sub_448420 +0x00448430 0x013B +R void __cdecl CreateScreenBuffers(void); +0x00448570 0x0094 +R void __cdecl CreatePrimarySurface(void); +0x00448610 0x0098 +R void __cdecl CreateBackBuffer(void); +0x004486B0 0x009D +R void __cdecl CreateClipper(void); +0x00448750 0x00D3 +R void __cdecl CreateWindowPalette(void); +0x00448830 0x00BC +R void __cdecl CreateZBuffer(void); +0x004488F0 0x002B +R DWORD __cdecl GetZBufferDepth(void); +0x00448920 0x00A1 +R void __cdecl CreateRenderBuffer(void); +0x004489D0 0x0070 +R void __cdecl CreatePictureBuffer(void); +0x00448A40 0x01A4 +R void __cdecl ClearBuffers(DWORD flags, DWORD fill_color); +0x00448BF0 0x013C +R void __cdecl RestoreLostBuffers(void); +0x00448D30 0x00CF +R void __cdecl UpdateFrame(bool need_run_message_loop, LPRECT rect); +0x00448E00 0x003B +R void __cdecl WaitPrimaryBufferFlip(void); +0x00448E40 0x0003 +R bool __cdecl RenderInit(void); +0x00448E50 0x03A5 +R void __cdecl RenderStart(bool is_reset); +0x00449200 0x00E6 +R void __cdecl RenderFinish(bool need_to_clear_textures); +0x004492F0 0x0204 +R bool __cdecl ApplySettings(APP_SETTINGS *new_settings); +0x00449500 0x0105 +R void __cdecl FmvBackToGame(void); +0x00449610 0x023A +R void __cdecl GameApplySettings(APP_SETTINGS *new_settings); +0x00449850 0x0067 +R void __cdecl UpdateGameResolution(void); +0x004498C0 0x000C +R LPCTSTR __cdecl DecodeErrorMessage(DWORD error_code); +0x004498D0 0x0049 -R BOOL __cdecl ReadFileSync(HANDLE handle, LPVOID lpBuffer, DWORD nBytesToRead, LPDWORD lpnBytesRead, LPOVERLAPPED lpOverlapped); +0x00449920 0x0188 + BOOL __cdecl Level_LoadTexturePages(HANDLE handle); +0x00449AB0 0x03A0 + BOOL __cdecl Level_LoadRooms(HANDLE handle); +0x00449E50 0x0097 -R void __cdecl AdjustTextureUVs(bool reset_uv_add); +0x00449EF0 0x057E + BOOL __cdecl Level_LoadObjects(HANDLE handle); +0x0044A470 0x0135 + BOOL __cdecl Level_LoadSprites(HANDLE handle); +0x0044A5B0 0x01D6 + BOOL __cdecl Level_LoadItems(HANDLE handle); +0x0044A790 0x0188 + BOOL __cdecl Level_LoadDepthQ(HANDLE handle); +0x0044A920 0x0071 + BOOL __cdecl Level_LoadPalettes(HANDLE handle); +0x0044A9A0 0x0060 + BOOL __cdecl Level_LoadCameras(HANDLE handle); +0x0044AA00 0x0060 + BOOL __cdecl Level_LoadSoundEffects(HANDLE handle); +0x0044AA60 0x0221 + BOOL __cdecl Level_LoadBoxes(HANDLE handle); +0x0044AC90 0x0055 + BOOL __cdecl Level_LoadAnimatedTextures(HANDLE handle); +0x0044ACF0 0x0079 + BOOL __cdecl Level_LoadCinematic(HANDLE handle); +0x0044AD70 0x008A + BOOL __cdecl Level_LoadDemo(HANDLE handle); +0x0044AE00 0x009A + void __cdecl Level_LoadDemoExternal(LPCTSTR level_name); +0x0044AEA0 0x0265 + BOOL __cdecl Level_LoadSamples(HANDLE handle); +0x0044B110 0x0036 -R void __cdecl ChangeFileNameExtension(char *file_name, const char *file_ext); +0x0044B150 0x0026 -R LPCTSTR __cdecl GetFullPath(LPCTSTR file_name); +0x0044B180 0x00E0 -R BOOL __cdecl SelectDrive(void); +0x0044B260 0x024A + bool __cdecl Level_Load(const char *file_name, int32_t level_num); +0x0044B4B0 0x0018 -R BOOL __cdecl S_LoadLevelFile(LPCTSTR file_name, int32_t level_num, GAMEFLOW_LEVEL_TYPE level_type); +0x0044B4D0 0x002A -R void __cdecl S_UnloadLevelFile(void); +0x0044B500 0x0014 -R void __cdecl S_AdjustTexelCoordinates(void); +0x0044B520 0x00C4 -R BOOL __cdecl S_ReloadLevelGraphics(BOOL reload_palettes, BOOL reload_tex_pages); +0x0044B5F0 0x00C6 + BOOL __cdecl GF_ReadStringTable(DWORD count, char **string_table, char **string_buf, LPDWORD buf_size, HANDLE handle); +0x0044B6C0 0x06D1 + BOOL __cdecl GF_LoadFromFile(const char *file_name); +0x0044BDA0 0x006B -R bool __cdecl PlayFMV(const char *file_name); +0x0044BE10 0x02E0 -R void __cdecl WinPlayFMV(const char *file_name, bool is_playback); +0x0044C0F0 0x0048 -R void __cdecl WinStopFMV(bool is_playback); +0x0044C140 0x0088 -R bool __cdecl IntroFMV(const char *file_name1, const char *file_name2); +0x0044C1D0 0x0023 -R uint16_t __cdecl S_COLOR(int32_t red, int32_t green, int32_t blue); +0x0044C200 0x0035 -R void __cdecl S_DrawScreenLine(int32_t x, int32_t y, int32_t z, int32_t x_len, int32_t y_len, BYTE color_idx, D3DCOLOR *gour, uint16_t flags); +0x0044C240 0x0116 -R void __cdecl S_DrawScreenBox(int32_t sx, int32_t sy, int32_t z, int32_t width, int32_t height, BYTE color_idx, const GOURAUD_OUTLINE *gour, uint16_t flags); +0x0044C360 0x002E -R void __cdecl S_DrawScreenFBox(int32_t sx, int32_t sy, int32_t z, int32_t width, int32_t height, BYTE color_idx, const GOURAUD_FILL *gour, uint16_t flags); +0x0044C390 0x000F -R void __cdecl S_FinishInventory(void); +0x0044C3A0 0x0043 *R void __cdecl S_FadeToBlack(void); +0x0044C3F0 0x0057 *R void __cdecl S_Wait(int32_t timeout, BOOL input_check); +0x0044C450 0x000E -R bool __cdecl S_PlayFMV(const char *file_name); +0x0044C460 0x0013 -R bool __cdecl S_IntroFMV(const char *file_name1, const char *file_name2); +0x0044C480 0x0144 + int16_t __cdecl Game_Start(int32_t level_num, GAMEFLOW_LEVEL_TYPE level_type); +0x0044C5D0 0x009A + int32_t __cdecl Game_Loop(bool demo_mode); +0x0044C670 0x0006 +R int32_t __cdecl LevelCompleteSequence(void); +0x0044C680 0x01C2 +R int32_t __cdecl LevelStats(int32_t level_num); +0x0044C850 0x0113 +R int32_t __cdecl GameStats(int32_t level_num); +0x0044C970 0x001E + int32_t __cdecl Random_GetControl(void); +0x0044C990 0x000A + void __cdecl Random_SeedControl(int32_t seed); +0x0044C9A0 0x001E + int32_t __cdecl Random_GetDraw(void); +0x0044C9C0 0x000A + void __cdecl Random_SeedDraw(int32_t seed); +0x0044C9D0 0x0044 -R void __cdecl GetValidLevelsList(REQUEST_INFO *req); +0x0044CA20 0x004C -R void __cdecl GetSavedGamesList(REQUEST_INFO *req); +0x0044CA70 0x0233 -R void __cdecl DisplayCredits(void); +0x0044CCB0 0x0165 *R BOOL __cdecl S_FrontEndCheck(void); +0x0044CE20 0x0114 -R int32_t __cdecl S_SaveGame(const void *save_data, uint32_t save_size, int32_t slot_num); +0x0044CF40 0x0096 -R int32_t __cdecl S_LoadGame(void *save_data, uint32_t save_size, int32_t slot_num); +0x0044CFE0 0x0128 +R void __cdecl HWR_InitState(void); +0x0044D110 0x0029 +R void __cdecl HWR_ResetTexSource(void); +0x0044D140 0x002B +R void __cdecl HWR_ResetColorKey(void); +0x0044D170 0x0059 +R void __cdecl HWR_ResetZBuffer(void); +0x0044D1D0 0x0024 +R void __cdecl HWR_TexSource(HWR_TEXTURE_HANDLE tex_source); +0x0044D200 0x004A +R void __cdecl HWR_EnableColorKey(bool state); +0x0044D250 0x0082 +R void __cdecl HWR_EnableZBuffer(bool z_write_enable, bool z_enable); +0x0044D2E0 0x0016 +R void __cdecl HWR_BeginScene(void); +0x0044D310 0x016C +R void __cdecl HWR_DrawPolyList(void); +0x0044D490 0x008E +R void __cdecl HWR_LoadTexturePages(int32_t pages_count, void *pages_buf, RGB_888 *palette); +0x0044D520 0x004A +R void __cdecl HWR_FreeTexturePages(void); +0x0044D570 0x0035 +R void __cdecl HWR_GetPageHandles(void); +0x0044D5B0 0x0019 +R bool __cdecl HWR_VertexBufferFull(void); +0x0044D5E0 0x0022 +R bool __cdecl HWR_Init(void); +0x0044D610 0x005C *R BOOL __cdecl S_InitialiseSystem(void); +0x0044D670 0x0011 * void __cdecl GameBuf_Shutdown(void); +0x0044D690 0x0021 -R void __cdecl init_game_malloc(void); +0x0044D6C0 0x006C -R void *__cdecl game_malloc(size_t alloc_size, GAME_BUFFER buf_index); +0x0044D740 0x0034 -R void __cdecl game_free(size_t free_size); +0x0044D780 0x00E8 -R void __cdecl CalculateWibbleTable(void); +0x0044D870 0x007F + void __cdecl Random_Seed(void); +0x0044D8F0 0x0120 + BOOL __cdecl S_Input_Key(KEYMAP keymap); +0x0044DA10 0x0AC4 + bool __cdecl Input_Update(void); +0x0044E4E0 0x003C +R int32_t __cdecl RenderErrorBox(int32_t error_code); +0x0044E520 0x01D6 +R int32_t __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int32_t nShowCmd); +0x0044E6F0 0x0001 x sub_44E6F0 +0x0044E700 0x0064 +R int32_t __cdecl GameInit(bool skip_cd_init); +0x0044E770 0x0055 + void __cdecl Shell_Cleanup(void); +0x0044E7A0 0x007C +R int32_t __cdecl WinGameStart(void); +0x0044E820 0x0039 + void __cdecl Shell_Shutdown(void); +0x0044E860 0x0017 xR sub_44E860 +0x0044E880 0x0010 xR sub_44E880 +0x0044E890 0x003C + void __cdecl Shell_ExitSystem(const char *message); +0x0044E8E0 0x010F +R void __cdecl ScreenshotPCX(void); +0x0044E9F0 0x00AE +R DWORD __cdecl CompPCX(BYTE *bitmap, DWORD width, DWORD height, RGB_888 *palette, BYTE **pcx_data); +0x0044EAA0 0x00D2 +R DWORD __cdecl EncodeLinePCX(BYTE *src, DWORD width, BYTE *dst); +0x0044EB80 0x003E +R DWORD __cdecl EncodePutPCX(BYTE value, BYTE num, BYTE *buffer); +0x0044EBC0 0x01F5 +R void __cdecl Screenshot(LPDDS screen); +0x0044EDC0 0x007F + void __cdecl Option_DoInventory(INVENTORY_ITEM *item); +0x0044EED0 0x0648 + void __cdecl Option_Passport(INVENTORY_ITEM *item); +0x0044F520 0x02DA + void __cdecl Option_Detail(INVENTORY_ITEM *item); +0x0044F800 0x049D + void __cdecl Option_Sound(INVENTORY_ITEM *item); +0x0044FCA0 0x00C0 + void __cdecl Option_Compass(INVENTORY_ITEM *item); +0x0044FD60 0x007E + void __cdecl Option_Controls_FlashConflicts(void); +0x0044FDE0 0x0040 + void __cdecl Option_Controls_DefaultConflict(void); +0x0044FE20 0x06F4 + void __cdecl Option_Controls(INVENTORY_ITEM *item); +0x00450530 0x04D0 + void __cdecl Option_Controls_ShowControls(void); +0x00450A00 0x0096 + void __cdecl Option_Controls_UpdateText(void); +0x00450AA0 0x003B +R void __cdecl S_RemoveCtrlText(void); +0x00450AE0 0x0006 -R int32_t __cdecl GetRenderHeight(void); +0x00450AF0 0x0006 -R int32_t __cdecl GetRenderWidth(void); +0x00450B00 0x00E4 *R void __cdecl S_InitialisePolyList(BOOL clear_back_buffer); +0x00450BF0 0x0036 *R DWORD __cdecl S_DumpScreen(void); +0x00450C30 0x000B -R void __cdecl S_ClearScreen(void); +0x00450C40 0x0037 -R void __cdecl S_InitialiseScreen(GAMEFLOW_LEVEL_TYPE level_type); +0x00450C80 0x0089 *R void __cdecl S_OutputPolyList(void); +0x00450CC0 0x0270 -R int32_t __cdecl S_GetObjectBounds(const BOUNDS_16 *bounds); +0x00450F30 0x0046 -R void __cdecl S_InsertBackPolygon(int32_t x0, int32_t y0, int32_t x1, int32_t y1); +0x00450F80 0x01F1 -R void __cdecl S_PrintShadow(int16_t radius, const BOUNDS_16 *bounds, const ITEM *item); +0x00451180 0x02F6 -R void __cdecl S_CalculateLight(int32_t x, int32_t y, int32_t z, int16_t room_num); +0x00451480 0x0031 -R void __cdecl S_CalculateStaticLight(int16_t adder); +0x004514C0 0x0124 -R void __cdecl S_CalculateStaticMeshLight(int32_t x, int32_t y, int32_t z, int32_t shade_1, int32_t shade_2, ROOM *room); +0x004515F0 0x0206 -R void __cdecl S_LightRoom(ROOM *room); +0x00451800 0x01CC -R void __cdecl S_DrawHealthBar(int32_t percent); +0x004519D0 0x01F6 -R void __cdecl S_DrawAirBar(int32_t percent); +0x00451BD0 0x00C0 -R void __cdecl AnimateTextures(int32_t ticks); +0x00451C90 0x0051 -R void __cdecl S_SetupBelowWater(BOOL underwater); +0x00451CF0 0x0021 -R void __cdecl S_SetupAboveWater(BOOL underwater); +0x00451D20 0x00B1 -R void __cdecl S_AnimateTextures(int32_t ticks); +0x00451DE0 0x0105 *R void __cdecl S_DisplayPicture(const char *file_name, BOOL is_title); +0x00451EF0 0x007E -R void __cdecl S_SyncPictureBufferPalette(void); +0x00451F70 0x001C *R void __cdecl S_DontDisplayPicture(void); +0x00451F80 0x000D -R void __cdecl ScreenDump(void); +0x00451F90 0x0010 -R void __cdecl ScreenPartialDump(void); +0x00451FA0 0x01C9 *R void __cdecl FadeToPal(int32_t fade_value, RGB_888 *palette); +0x00452170 0x0026 -R void __cdecl ScreenClear(bool is_phd_win_size); +0x004521A0 0x00AB -R void __cdecl S_CopyScreenToBuffer(void); +0x00452250 0x0254 *R void __cdecl S_CopyBufferToScreen(void); +0x004522A0 0x00FA -R BOOL __cdecl DecompPCX(const uint8_t *pcx, size_t pcx_size, LPBYTE pic, RGB_888 *pal); +0x004523A0 0x0005 x sub_4523A0 +0x004523B0 0x0001 x sub_4523B0 +0x004523C0 0x004E -R bool __cdecl OpenGameRegistryKey(LPCTSTR key); +0x00452410 0x0005 -R LONG __cdecl CloseGameRegistryKey(void); +0x00452420 0x0262 -R bool __cdecl SE_WriteAppSettings(APP_SETTINGS *settings); +0x00452690 0x0348 -R int32_t __cdecl SE_ReadAppSettings(APP_SETTINGS *settings); +0x004529E0 0x00D7 -R bool __cdecl SE_GraphicsTestStart(void); +0x00452AB0 0x0014 -R void __cdecl SE_GraphicsTestFinish(void); +0x00452AD0 0x0003 -R int32_t __cdecl SE_GraphicsTestExecute(void); +0x00452AE0 0x0057 -R int32_t __cdecl SE_GraphicsTest(void); +0x00452B40 0x00C7 -R bool __cdecl SE_SoundTestStart(void); +0x00452C00 0x0005 -R void __cdecl SE_SoundTestFinish(void); +0x00452C10 0x003D -R int32_t __cdecl SE_SoundTestExecute(void); +0x00452C50 0x0057 -R int32_t __cdecl SE_SoundTest(void); +0x00452CB0 0x003E -R int32_t __stdcall SE_PropSheetCallback(HWND hwndDlg, UINT uMsg, LPARAM lParam); +0x00452CF0 0x005D -R LRESULT __stdcall SE_NewPropSheetWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +0x00452D50 0x02DE -R bool __cdecl SE_ShowSetupDialog(HWND hParent, bool isDefault); +0x00453030 0x0351 -R INT_PTR __stdcall SE_GraphicsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +0x004533F0 0x01DC -R void __cdecl SE_GraphicsDlgFullScreenModesUpdate(HWND hwndDlg); +0x004535E0 0x0017 -R void __cdecl SE_GraphicsAdapterSet(HWND hwndDlg, DISPLAY_ADAPTER_NODE *adapter); +0x00453600 0x0735 -R void __cdecl SE_GraphicsDlgUpdate(HWND hwndDlg); +0x00453D40 0x017C -R void __cdecl SE_GraphicsDlgInit(HWND hwndDlg); +0x00453EC0 0x0149 -R INT_PTR __stdcall SE_SoundDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +0x00454050 0x000A -R void __cdecl SE_SoundAdapterSet(HWND hwndDlg, SOUND_ADAPTER_NODE *adapter); +0x00454060 0x011B -R void __cdecl SE_SoundDlgUpdate(HWND hwndDlg); +0x00454180 0x00BE -R void __cdecl SE_SoundDlgInit(HWND hwndDlg); +0x00454240 0x0106 -R INT_PTR __stdcall SE_ControlsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +0x00454350 0x000A -R void __cdecl SE_ControlsJoystickSet(HWND hwndDlg, JOYSTICK_NODE *joystick); +0x00454360 0x0068 -R void __cdecl SE_ControlsDlgUpdate(HWND hwndDlg); +0x004543D0 0x00BD -R void __cdecl SE_ControlsDlgInit(HWND hwndDlg); +0x00454490 0x008A -R INT_PTR __stdcall SE_OptionsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +0x00454520 0x0234 -R void __cdecl SE_OptionsDlgUpdate(HWND hwndDlg); +0x00454760 0x004B -R void __cdecl SE_OptionsStrCat(LPTSTR *dstString, bool isEnabled, bool *isNext, LPCTSTR srcString); +0x004547B0 0x00DC -R INT_PTR __stdcall SE_AdvancedDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +0x004548B0 0x0093 -R void __cdecl SE_AdvancedDlgUpdate(HWND hwndDlg); +0x00454950 0x000E -R void __cdecl SE_AdvancedDlgInit(HWND hwndDlg); +0x00454960 0x0011 -R HWND __cdecl SE_FindSetupDialog(void); +0x00454980 0x02D0 + BOOL __cdecl Shell_Main(void); +0x00454C50 0x0110 +R int16_t __cdecl TitleSequence(void); +0x00454D60 0x032D -R void __cdecl CheckCheatMode(void); +0x004550C0 0x007D +R void __cdecl S_SaveSettings(void); +0x00455140 0x00DB +R void __cdecl S_LoadSettings(void); +0x00455220 0x0046 + int32_t __cdecl S_Audio_Sample_OutPlay(int32_t sample_id, uint16_t volume, int32_t pitch, int32_t pan); +0x00455270 0x002A + int32_t __cdecl S_Audio_Sample_CalculateSampleVolume(int32_t volume); +0x004552A0 0x0026 + int32_t __cdecl S_Audio_Sample_CalculateSamplePan(int16_t pan); +0x004552D0 0x0046 + int32_t __cdecl S_Audio_Sample_OutPlayLooped(int32_t track_id, uint16_t volume, int32_t pitch, int32_t pan); +0x00455320 0x0039 + void __cdecl S_Audio_Sample_OutSetPanAndVolume(int32_t track_id, int32_t pan, uint16_t volume); +0x00455360 0x001C + void __cdecl S_Audio_Sample_OutSetPitch(int32_t track_id, int32_t pitch); +0x00455380 0x000A + void __cdecl Sound_SetMasterVolume(int32_t volume); +0x00455390 0x0017 + void __cdecl S_Audio_Sample_OutCloseTrack(int32_t track_id); +0x004553B0 0x003C + void __cdecl S_Audio_Sample_OutCloseAllTracks(void); +0x004553C0 0x001F + BOOL __cdecl S_Audio_Sample_OutIsTrackPlaying(int32_t track_id); +0x004553E0 0x0077 + bool __cdecl Music_Init(void); +0x00455460 0x0051 + void __cdecl Music_Shutdown(void); +0x00455500 0x006F + void __cdecl Music_Play(int16_t track_id, bool is_looped); +0x00455570 0x0039 + void __cdecl Music_Stop(void); +0x004555B0 0x0084 + bool __cdecl Music_PlaySynced(int32_t track_id); +0x00455640 0x0061 + int32_t __cdecl Music_GetFrames(void); +0x004556B0 0x0092 + void __cdecl Music_SetVolume(int32_t volume); +0x004557A0 0x0137 -R void __cdecl CopyBitmapPalette(RGB_888 *src_pal, BYTE *src_bitmap, int32_t bitmap_size, RGB_888 *dest_pal); +0x004558E0 0x00C8 -R BYTE __cdecl FindNearestPaletteEntry(RGB_888 *palette, int32_t red, int32_t green, int32_t blue, bool ignore_sys_palette); +0x004559B0 0x00AE -R void __cdecl SyncSurfacePalettes(void *src_data, int32_t width, int32_t height, int32_t src_pitch, RGB_888 *src_palette, void *dst_data, int32_t dst_pitch, RGB_888 *dst_palette, bool preserve_sys_palette); +0x00455A60 0x0087 -R int32_t __cdecl CreateTexturePalette(const RGB_888 *pal); +0x00455AF0 0x001C -R int32_t __cdecl GetFreePaletteIndex(void); +0x00455B10 0x0023 -R void __cdecl FreePalette(int32_t palette_idx); +0x00455B40 0x0012 -R void __cdecl SafeFreePalette(int32_t palette_idx); +0x00455B90 0x006A -R int32_t __cdecl CreateTexturePage(int32_t width, int32_t height, bool alpha); +0x00455C00 0x001C -R int32_t __cdecl GetFreeTexturePageIndex(void); +0x00455C20 0x0098 -R bool __cdecl CreateTexturePageSurface(TEXPAGE_DESC *desc); +0x00455CC0 0x0174 -R bool __cdecl TexturePageInit(TEXPAGE_DESC *page); +0x00455E40 0x0025 -R LPDIRECT3DTEXTURE2 __cdecl Create3DTexture(LPDDS surface); +0x00455E70 0x0020 -R void __cdecl SafeFreeTexturePage(int32_t page_idx); +0x00455E90 0x0032 -R void __cdecl FreeTexturePage(int32_t page_idx); +0x00455ED0 0x003B -R void __cdecl TexturePageReleaseVidMemSurface(TEXPAGE_DESC *page); +0x00455F10 0x0026 -R void __cdecl FreeTexturePages(void); +0x00455F40 0x00A2 -R bool __cdecl LoadTexturePage(int32_t page_idx, bool reset); +0x00455FF0 0x0035 -R bool __cdecl ReloadTextures(bool reset); +0x00456030 0x003E -R HWR_TEXTURE_HANDLE __cdecl GetTexturePageHandle(int32_t page_idx); +0x00456070 0x00F5 -R int32_t __cdecl AddTexturePage8(int32_t width, int32_t height, const uint8_t *page_buf, int32_t pal_idx); +0x00456170 0x0196 -R int32_t __cdecl AddTexturePage16(int32_t width, int32_t height, const uint8_t *page_buf); +0x00456310 0x011A -R HRESULT __stdcall EnumTextureFormatsCallback(LPDDSDESC lpDdsd, LPVOID lpContext); +0x00456430 0x0025 -R HRESULT __cdecl EnumerateTextureFormats(void); +0x00456460 0x0030 -R void __cdecl CleanupTextures(void); +0x00456470 0x001F -R bool __cdecl InitTextures(void); +0x00456490 0x0040 -R void __cdecl UpdateTicks(void); +0x004564D0 0x0051 -R bool __cdecl TIME_Init(void); +0x00456530 0x0058 -R DWORD __cdecl Sync(void); +0x00456590 0x0036 -R LPVOID __cdecl UT_LoadResource(LPCTSTR lpName, LPCTSTR lpType); +0x004565D0 0x0060 -R void __cdecl UT_InitAccurateTimer(void); +0x00456630 0x004E -R double __cdecl UT_Microseconds(void); +0x00456680 0x006F -R BOOL __cdecl UT_CenterWindow(HWND hWnd); +0x004566F0 0x002C -R LPTSTR __cdecl UT_FindArg(LPCTSTR str); +0x00456720 0x0018 -R int32_t __cdecl UT_MessageBox(LPCTSTR lpText, HWND hWnd); +0x00456740 0x0042 -R int32_t __cdecl UT_ErrorBox(UINT uID, HWND hWnd); +0x00456790 0x0051 -R LPCTSTR __cdecl GuidBinaryToString(GUID *guid); +0x004567F0 0x00AA -R bool __cdecl GuidStringToBinary(LPCTSTR lpString, GUID *guid); +0x004568A0 0x0030 -R BOOL __cdecl OpenRegistryKey(LPCTSTR lpSubKey); +0x004568D0 0x000F -R bool __cdecl IsNewRegistryKeyCreated(void); +0x004568E0 0x000D -R LONG __cdecl CloseRegistryKey(void); +0x004568F0 0x001E -R LONG __cdecl SetRegistryDwordValue(LPCTSTR lpValueName, DWORD value); +0x00456910 0x002A -R LONG __cdecl SetRegistryBoolValue(LPCTSTR lpValueName, bool value); +0x00456940 0x0036 -R LONG __cdecl SetRegistryFloatValue(LPCTSTR lpValueName, double value); +0x00456980 0x0037 -R LONG __cdecl SetRegistryBinaryValue(LPCTSTR lpValueName, LPBYTE value, DWORD valueSize); +0x004569C0 0x004A -R LONG __cdecl SetRegistryStringValue(LPCTSTR lpValueName, LPCTSTR value, int32_t length); +0x00456A10 0x0013 -R LONG __cdecl DeleteRegistryValue(LPCTSTR lpValueName); +0x00456A30 0x005E -R bool __cdecl GetRegistryDwordValue(LPCTSTR lpValueName, DWORD *pValue, DWORD defaultValue); +0x00456A90 0x0076 -R bool __cdecl GetRegistryBoolValue(LPCTSTR lpValueName, bool *pValue, bool defaultValue); +0x00456B10 0x005C -R bool __cdecl GetRegistryFloatValue(LPCTSTR lpValueName, double *value, double defaultValue); +0x00456B70 0x0071 -R bool __cdecl GetRegistryBinaryValue(LPCTSTR lpValueName, LPBYTE value, DWORD valueSize, LPBYTE defaultValue); +0x00456BF0 0x0095 -R bool __cdecl GetRegistryStringValue(LPCTSTR lpValueName, LPTSTR value, DWORD maxSize, LPCTSTR defaultValue); +0x00456C90 0x0091 -R bool __cdecl GetRegistryGuidValue(LPCTSTR lpValueName, GUID *value, GUID *defaultValue); +0x00456D30 0x0037 -R void __thiscall SE_ReleaseBitmapResource(BITMAP_RESOURCE *bmpRsrc); +0x00456D70 0x00C4 -R void __thiscall SE_LoadBitmapResource(BITMAP_RESOURCE *bmpRsrc, LPCTSTR lpName); +0x00456E40 0x0064 -R void __thiscall SE_DrawBitmap(BITMAP_RESOURCE *bmpRsrc, HDC hdc, int32_t x, int32_t y); +0x00456EB0 0x001C -R void __thiscall SE_UpdateBitmapPalette(BITMAP_RESOURCE *bmpRsrc, HWND hWnd, HWND hSender); +0x00456ED0 0x0057 -R void __thiscall SE_ChangeBitmapPalette(BITMAP_RESOURCE *bmpRsrc, HWND hWnd); +0x00456F30 0x0061 -R bool __cdecl SE_RegisterSetupWindowClass(void); +0x00456FA0 0x023A -R LRESULT __stdcall SE_SetupWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +0x004571E0 0x0026 -R void __cdecl SE_PassMessageToImage(HWND hWnd, UINT uMsg, WPARAM wParam); +0x00457210 0x006E -R void __cdecl UT_MemBlt(BYTE *dstBuf, DWORD dstX, DWORD dstY, DWORD width, DWORD height, DWORD dstPitch, BYTE *srcBuf, DWORD srcX, DWORD srcY, DWORD srcPitch); +0x00457280 0x001E + void __cdecl Matrix_Push(void); +0x0045729E 0x0033 + void __cdecl Matrix_PushUnit(void); +0x004572D4 0x0061 + void __fastcall Output_FlatA(int32_t y0, int32_t y1, uint8_t color_idx); // actually, __watcall, which is esoteric and rarely supported +0x00457335 0x013A + void __fastcall Output_TransA(int32_t y0, int32_t y1, uint8_t depth_q); // actually, __watcall, which is esoteric and rarely supported +0x0045746F 0x0160 + void __fastcall Output_GourA(int32_t y0, int32_t y1, uint8_t color_idx); // actually, __watcall, which is esoteric and rarely supported +0x004575CF 0x02FD + void __fastcall Output_GTMapA(int32_t y0, int32_t y1, uint8_t *tex_page); // actually, __watcall, which is esoteric and rarely supported +0x004578CC 0x0341 + void __fastcall Output_WGTMapA(int32_t y0, int32_t y1, uint8_t *tex_page); // actually, __watcall, which is esoteric and rarely supported +0x00457C10 0x0048 + int32_t __fastcall Math_Atan(int32_t x, int32_t y); +0x00457C58 0x0006 + int32_t __fastcall Math_Cos(int16_t angle); +0x00457C5E 0x001B + int32_t __fastcall Math_Sin(int16_t angle); +0x00457C79 0x001A + int32_t __fastcall Math_SinImpl(int16_t angle); +0x00457C93 0x002C + uint32_t __fastcall Math_Sqrt(uint32_t n); + + + +# VARIABLES +# Flags: +# - to do +# + decompiled +# Offset Flag Declaration + +0x00464060 - uint32_t g_PerspectiveDistance = 0x3000000; +0x00464068 - void (*__cdecl g_PolyDrawRoutines[9])(const int16_t *); +0x0046408C - float g_RhwFactor = 335544320.0f; // 10*2**25 +0x004640B0 - int32_t g_CineTrackID = 1; +0x004640B8 - int32_t g_CineTickRate = 0x8000; // 0x8000 = PHD_ONE/TICKS_PER_FRAME +0x004640BC - int16_t g_CD_TrackID = -1; +0x004640C4 - int32_t g_FlipEffect = -1; +0x004641F0 - int32_t g_AssaultBestTime = -1; +0x004641F8 - void (*__cdecl g_EffectRoutines[32])(ITEM *item); +0x00464310 - int16_t g_CineTargetAngle = 0x4000; // PHD_90 +0x004644E0 - int32_t g_OverlayStatus = 1; +0x004654E0 - int16_t g_Inv_MainObjectsCount = 8; +0x00465604 + int16_t g_Inv_OptionObjectsCount = 4; +0x00465618 - BOOL g_GymInvOpenEnabled = TRUE; +0x00465A50 - int16_t g_Inv_Chosen = -1; +0x00465A54 - INVENTORY_MODE g_Inv_Mode = INV_TITLE_MODE; +0x00465A5C - int16_t g_OptionSoundVolume = 165; // NOTE: value should be 10 +0x00465A60 - int16_t g_OptionMusicVolume = 255; // NOTE: should be 10 +0x00465AD4 - int32_t g_JumpPermitted = 1; +0x00465AD8 - int16_t g_LaraOldSlideAngle = 1; +0x00465CD0 - void (*__cdecl g_LaraControlRoutines[71])(ITEM *item, COLL_INFO *coll); +0x00465DF0 - void (*__cdecl g_ExtraControlRoutines[11])(ITEM *item, COLL_INFO *coll); +0x00465E20 - void (*__cdecl g_LaraCollisionRoutines[71])(ITEM *item, COLL_INFO *coll); +0x00466290 - int8_t g_TextSpacing[80]; +0x004662E0 - int8_t g_TextASCIIMap[]; +0x00466400 - int32_t g_BGND_PaletteIndex = -1; +0x00466480 - double g_GameSizer = 1.0; +0x00466488 - double g_GameSizerCopy = 1.0; +0x00466490 - int32_t g_FadeValue = 0x100000; +0x00466494 - int32_t g_FadeLimit = 0x100000; +0x00466498 - int32_t g_FadeAdder = 0x8000; +0x004664E8 - const char *g_ErrorMessages[43]; +0x00466BB0 + int32_t g_RandControl; +0x00466BB4 + int32_t g_RandDraw; +0x00466F70 + CONTROL_LAYOUT g_Layout[2]; +0x00467DD0 + const int32_t g_AtanBaseTable[8]; +0x00467DF0 + const int16_t g_AtanAngleTable[0x800]; +0x00468DF4 + const int16_t g_SinTable[0x402]; +0x0046C300 - int32_t g_MidSort = 0; +0x0046C304 - float g_ViewportAspectRatio = 0.0f; +0x0046C308 - int32_t g_XGenY1; +0x0046C30C - int32_t g_XGenY2; +0x0046C310 - GOURAUD_ENTRY g_GouraudTable[256]; +0x0046E310 - int32_t g_PhdWinTop; +0x0046E318 - PHD_SPRITE g_PhdSprites[512]; +0x00470318 - int32_t g_LsAdder; +0x0047031C - float g_FltWinBottom; +0x00470320 - float g_FltResZBuf; +0x00470324 - float g_FltResZ; +0x00470328 - void (*__cdecl g_Output_InsertTransQuad)(int32_t x, int32_t y, int32_t width, int32_t height, int32_t z); +0x0047032C - int32_t g_PhdWinHeight; +0x00470330 - int32_t g_PhdWinCenterX; +0x00470334 - int32_t g_PhdWinCenterY; +0x00470338 - int16_t g_LsYaw; +0x0047033C - void (*__cdecl g_Output_InsertTrans8)(const PHD_VBUF *vbuf, int16_t shade); +0x00470340 - float g_FltWinTop; +0x00470348 - SORT_ITEM g_SortBuffer[4000]; +0x00478048 - float g_FltWinLeft; +0x0047804C - int16_t g_PhdWinMinY; +0x00478058 - int32_t g_PhdFarZ; +0x0047805C - float g_FltRhwOPersp; +0x00478060 - int32_t g_PhdWinBottom; +0x00478064 - int32_t g_PhdPersp; +0x00478068 - int32_t g_PhdWinLeft; +0x0047806C - void (*__cdecl g_Output_InsertFlatRect)(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx); +0x00478070 - int16_t g_Info3DBuffer[120000]; +0x004B29F0 - int32_t g_PhdWinMaxX; +0x004B29F4 - int32_t g_PhdNearZ; +0x004B29F8 - float g_FltResZORhw; +0x004B29FC - float g_FltFarZ; +0x004B2A00 - float g_FltWinCenterX; +0x004B2A04 - float g_FltWinCenterY; +0x004B2A08 - int32_t g_PhdScreenHeight; +0x004B2A0C - uint8_t *g_PrintSurfacePtr; +0x004B2A10 - int16_t g_PhdWinMinX; +0x004B2A14 - float g_FltPerspONearZ; +0x004B2A18 - float g_FltRhwONearZ; +0x004B2A1C - int32_t g_PhdWinMaxY; +0x004B2A20 - void (*__cdecl g_Output_InsertSprite)(int32_t z, int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t sprite_idx, int16_t shade); +0x004B2A24 - float g_FltNearZ; +0x004B2A28 - MATRIX *g_MatrixPtr; +0x004B2A2C - const int16_t *(*__cdecl g_Output_DrawObjectGT3)(const int16_t*, int32_t, SORT_TYPE); +0x004B2A30 - const int16_t *(*__cdecl g_Output_DrawObjectGT4)(const int16_t*, int32_t, SORT_TYPE); +0x004B2A38 - int32_t g_RandomTable[32]; +0x004B2AB8 - float g_FltPersp; +0x004B2AC0 - MATRIX g_W2VMatrix; +0x004B2AF0 - int16_t *g_Info3DPtr; +0x004B2AF4 - int32_t g_PhdWinWidth; +0x004B2AF8 - void (*__cdecl g_Output_InsertLine)(int32_t, int32_t, int32_t, int32_t, int32_t, uint8_t); +0x004B2B00 - PHD_TEXTURE g_PhdTextureInfo[0x800]; +0x004BCB00 - int32_t g_PhdViewDistance; +0x004BCB04 - int16_t g_LsPitch; +0x004BCB08 - const int16_t *(*__cdecl g_Output_DrawObjectG4)(const int16_t*,int32_t, SORT_TYPE); +0x004BCB10 - int16_t g_ShadesTable[32]; +0x004BCB50 - const int16_t *(*__cdecl g_Output_DrawObjectG3)(const int16_t*,int32_t, SORT_TYPE); +0x004BCB58 - MATRIX g_MatrixStack[]; +0x004BD2D8 - DEPTHQ_ENTRY g_DepthQTable[32]; +0x004BF3D8 - int32_t g_PhdScreenWidth; +0x004BF3DC - int32_t g_LsDivider; +0x004BF3E0 - PHD_VBUF g_PhdVBuf[1500]; +0x004CAF60 - void *g_XBuffer; // no-dereferencing +0x004D6AE0 - uint8_t *g_TexturePageBuffer8[32]; +0x004D6B60 - float g_FltWinRight; +0x004D6B68 - XYZ_32 g_LsVectorView; +0x004D6B78 - float g_WibbleTable[32]; +0x004D6BF8 - int32_t g_PhdWinRight; +0x004D6BFC - int32_t g_SurfaceCount; +0x004D6C00 - SORT_ITEM *g_Sort3DPtr; +0x004D6C0C - int32_t g_WibbleOffset; +0x004D6C10 - int32_t g_IsWibbleEffect; +0x004D6C14 - int32_t g_IsWaterEffect; +0x004D6CD8 - VERTEX_INFO g_VBuffer[20]; +0x004D6F78 - int8_t g_IsShadeEffect; +0x004D6F80 - D3DTLVERTEX g_VBufferD3D[32]; +0x004D7380 - PALETTEENTRY g_GamePalette16[256]; +0x004D7780 - int32_t g_CineFrameCurrent; +0x004D778C - int32_t g_IsChunkyCamera; +0x004D7794 - int32_t g_NoInputCounter; +0x004D7798 + BOOL g_IsResetFlag; +0x004D779C - int32_t g_FlipTimer; +0x004D77A0 - int32_t g_LOSNumRooms = 0; +0x004D77A4 - BOOL g_StopInventory; +0x004D77AC - BOOL g_IsDemoLevelType; +0x004D77B0 - BOOL g_IsDemoLoaded; +0x004D77C0 - int32_t g_BoundStart; +0x004D77C4 - int32_t g_BoundEnd; +0x004D77E0 - int32_t g_IsAssaultTimerDisplay; +0x004D77E4 - BOOL g_IsAssaultTimerActive; +0x004D77E8 - BOOL g_IsMonkAngry; +0x004D791C + int32_t g_OldGameTimer; +0x004D7920 + BOOL g_FlashState; +0x004D7924 + int32_t g_FlashCounter; +0x004D7928 + int32_t g_OldHitPoints; +0x004D792C - TEXTSTRING *g_AmmoTextInfo; +0x004D7930 - TEXTSTRING *g_DisplayModeTextInfo; +0x004D7934 - DWORD g_DisplayModeInfoTimer; +0x004D7938 - UINT16 g_Inv_MainCurrent; +0x004D793C - UINT16 g_Inv_KeyObjectsCount; +0x004D7940 - UINT16 g_Inv_KeysCurrent; +0x004D7944 - UINT16 g_Inv_OptionCurrent; +0x004D7954 - TEXTSTRING* g_Inv_RingText; +0x004D795C - TEXTSTRING* g_Inv_UpArrow1; +0x004D7960 - TEXTSTRING* g_Inv_UpArrow2; +0x004D7964 - TEXTSTRING* g_Inv_DownArrow1; +0x004D7968 - TEXTSTRING* g_Inv_DownArrow2; +0x004D796C - uint32_t g_InputDB; +0x004D7978 - uint16_t g_Inv_IsActive; +0x004D79A0 - BOOL g_Inv_DemoMode; +0x004D79B4 - BOOL g_Inv_IsOptionsDelay; +0x004D79B8 - int32_t g_Inv_OptionsDelayCounter; +0x004D79BC - uint16_t g_SoundOptionLine; +0x004D79C0 - REQUEST_INFO g_StatsRequester; +0x004D7BD8 - ASSAULT_STATS g_Assault; +0x004D7C38 - int32_t g_LevelItemCount; +0x004D7C3C - int32_t g_HealthBarTimer; +0x004D7C80 - int32_t g_SoundTrackIds[128]; +0x004D7EBC - LPDIRECT3DDEVICE2 g_D3DDev; +0x004D7EE4 - bool g_IsGameWindowCreated; +0x004D7EE8 - bool g_IsGameWindowUpdating; +0x004D7EEC - bool g_IsDDrawGameWindowShow; +0x004D7EF0 - int32_t g_MinWindowClientWidth; +0x004D7ED0 - int32_t g_MinWindowClientHeight; +0x004D8388 - int32_t g_MinWindowWidth; +0x004D838C - int32_t g_MinWindowHeight; +0x004D7EF4 - bool g_IsGameWindowShow; +0x004D7EF8 - bool g_IsMinWindowSizeSet; +0x004D7EFC - int32_t g_MaxWindowClientWidth; +0x004D7F00 - int32_t g_GameWindowWidth; +0x004D7F04 - bool g_IsMinMaxInfoSpecial; +0x004D7F08 - bool g_IsGameFullScreen; +0x004D7F0C - bool g_IsGameWindowMaximized; +0x004D7F10 - HWND g_GameWindowHandle; +0x004D7F14 - int32_t g_GameWindowHeight; +0x004D7F18 - DISPLAY_ADAPTER_NODE* g_PrimaryDisplayAdapter; +0x004D7F20 - DISPLAY_ADAPTER g_CurrentDisplayAdapter; +0x004D8338 - uint32_t g_LockedBufferCount; +0x004D833C - int32_t g_GameWindowPositionX; +0x004D8340 - int32_t g_GameWindowPositionY; +0x004D8348 - DISPLAY_ADAPTER_LIST g_DisplayAdapterList; +0x004D8354 - int32_t g_MaxWindowClientHeight; +0x004D8358 - bool g_IsMessageLoopClosed; +0x004D835C - int32_t g_MaxWindowWidth; +0x004D7EDC - int32_t g_MaxWindowHeight; +0x004D8360 - bool g_IsMaxWindowSizeSet; +0x004D8364 - uint32_t g_AppResultCode; +0x004D8368 - int32_t g_FullScreenWidth; +0x004D836C - int32_t g_FullScreenHeight; +0x004D8370 - int32_t g_FullScreenBPP; +0x004D8374 - int32_t g_FullScreenVGA; +0x004D8378 - uint8_t g_IsGameToExit; +0x004D8568 - int32_t g_ScreenSizer; +0x004D856C - int32_t g_IsVidSizeLock; +0x004D8570 - DWORD g_SampleFreqs[256]; +0x004D8970 - SOUND_ADAPTER_LIST g_SoundAdapterList; +0x004D8980 - LPDIRECTSOUNDBUFFER g_SampleBuffers[256]; +0x004D8D80 - uint8_t g_IsSoundEnabled; +0x004D8D84 - LPDIRECTSOUND g_DSound; +0x004D8D88 - int32_t g_ChannelSamples[32]; +0x004D8E08 - LPDIRECTSOUNDBUFFER g_ChannelBuffers[32]; +0x004D8E8C - SOUND_ADAPTER g_CurrentSoundAdapter; +0x004D8EAC - SOUND_ADAPTER_NODE *g_PrimarySoundAdapter; +0x004D8EB0 - LPDDS g_RenderBufferSurface; +0x004D92B8 - LPDDS g_ThirdBufferSurface; +0x004D92BC - LPDDS g_PictureBufferSurface; +0x004D92C0 - LPDDS g_ZBufferSurface; +0x004D92C8 - LPDDS g_PrimaryBufferSurface; +0x004D9338 - int32_t g_GameVid_Width; +0x004D933C - int32_t g_GameVid_Height; +0x004D9340 - int32_t g_GameVid_BPP; +0x004D934C - int32_t g_UVAdd; +0x004D9351 - int8_t g_GameVid_IsWindowedVGA; +0x004D9EAC - int32_t g_IsFMVPlaying; +0x004D9EC0 - int32_t g_CurrentLevel; +0x004D9EC4 - int32_t g_LevelComplete; +0x004D9ED8 - D3DTLVERTEX g_HWR_VertexBuffer[0x2000]; // MAX_VERTICES +0x00519EE0 - HWR_TEXTURE_HANDLE g_HWR_PageHandles[32]; +0x00519F60 - D3DTLVERTEX *g_HWR_VertexPtr; +0x0051A0CC - void *g_GameMemoryPtr; +0x0051A0D0 + BOOL g_ConflictLayout[14]; // INPUT_ROLE_NUMBER_OF +0x0051A108 - uint8_t g_DIKeys[256]; +0x0051A208 - int32_t g_Input; +0x0051A20C - int8_t g_IsVidModeLock; +0x0051A210 - int32_t g_JoyKeys; +0x0051A214 + int32_t g_JoyXPos; +0x0051A218 + int32_t g_JoyYPos; +0x0051A220 + int32_t g_MediPackCooldown; +0x0051A224 + int8_t g_IsF3Pressed; +0x0051A228 + int8_t g_IsF4Pressed; +0x0051A22C + int8_t g_IsF7Pressed; +0x0051A230 + int8_t g_IsF8Pressed; +0x0051A234 + int8_t g_IsF11Pressed; +0x0051A238 - HINSTANCE g_GameModule; +0x0051A23C - char *g_CmdLine; +0x0051A240 - int32_t g_ScreenshotCounter; +0x0051B918 - RECT g_PhdWinRect; +0x0051B928 - int32_t g_HiRes; +0x0051B930 - RGB_888 g_GamePalette8[256]; +0x0051BCC0 - APP_SETTINGS g_SavedAppSettings; +0x0051BD20 - char g_ErrorMessage[128]; +0x0051BDA8 - int32_t g_MasterVolume; +0x0051BDAC - MCIDEVICEID g_MciDeviceID; +0x0051BDB0 - int32_t g_CD_LoopTrack; +0x0051C820 + TEXTSTRING g_TextstringTable[64]; // MAX_TEXTSTRINGS +0x0051D6A0 - int16_t g_TextstringCount = 0; +0x0051D6C0 - char g_TextstringBuffers[64][64]; +0x0051E6C4 - int32_t g_SoundIsActive; +0x0051E9E0 - SAVEGAME_INFO g_SaveGame; +0x005206E0 - LARA_INFO g_Lara; +0x005207BC - ITEM *g_LaraItem; +0x005207C0 - FX *g_Effects; +0x005207C4 - int16_t g_NextEffectFree; +0x005207C6 - int16_t g_NextItemFree; +0x005207C8 - int16_t g_NextItemActive; +0x005207CA - int16_t g_NextEffectActive; +0x005207CC - int16_t g_PrevItemActive; +0x00521CA0 - PICKUP_INFO g_Pickups[12]; +0x00521DE0 - GAME_FLOW g_GameFlow; +0x00521FDC - int32_t g_SoundEffectCount; +0x00522000 - OBJECT g_Objects[265]; +0x005252B0 - int16_t **g_Meshes; +0x005252C0 - MATRIX g_IMMatrixStack[256]; +0x005258F0 - int32_t g_IMFrac; +0x005258F4 - ANIM *g_Anims; +0x00525BE8 - int32_t *g_AnimBones; +0x00526180 - int32_t g_RoomCount; +0x00526184 - int32_t g_IMRate; +0x00526188 - MATRIX *g_IMMatrixPtr; +0x0052618C - ROOM *g_Rooms; +0x00526240 - int32_t g_FlipStatus; +0x00526288 - int16_t *g_TriggerIndex; +0x005262A0 - int32_t g_LOSRooms[20]; +0x005262F0 - ITEM *g_Items; +0x005262F6 - int16_t g_NumCineFrames; +0x005262F8 - CINE_FRAME *g_CineData = NULL; +0x00526300 - PHD_3DPOS g_CinePos; +0x00526314 - int16_t g_CineFrameIdx; +0x00526320 - CAMERA_INFO g_Camera; +0x005263CC - BOX_INFO *g_Boxes; +0x004D855C - LPDIRECTINPUT g_DInput; +0x004D8560 - LPDIRECTINPUTDEVICE IDID_SysKeyboard; +0x0051BDA0 - BOOL g_IsTitleLoaded; +0x004D7980 - int32_t g_Inv_ExtraData[8]; +0x004D8394 - int32_t g_MessageLoopCounter; +0x004D8384 - bool g_IsGameWindowMinimized; +0x004D8390 - bool g_IsGameWindowActive; +0x004D837C - int32_t g_GameWindowY; +0x004D7EE0 - LPDIRECTDRAW3 g_DDraw; +0x004D8380 - int32_t g_GameWindowX; +0x00463150 - GUID g_IID_IDirectDrawSurface3; +0x004640A0 + BITE g_CrowBite; +0x00464090 + BITE g_BirdBite; +0x005263C0 - int16_t *g_FlyZone[2]; +0x005263A0 - int16_t *g_GroundZone[][2]; +0x005263C8 - uint16_t *g_Overlap; +0x005206C0 - CREATURE *g_BaddieSlots; +0x00526312 + int16_t g_CineLevelID; +0x005252B8 - int32_t g_DrawRoomsCount; +0x00525B20 - int16_t g_DrawRoomsArray[100]; +0x00525BEC - int32_t g_DynamicLightCount; +0x004D7784 - int32_t g_CineTickCount; +0x004D7788 - int32_t g_OriginalRoom; +0x00465518 - INVENTORY_ITEM *g_Inv_MainList[]; +0x00465608 + INVENTORY_ITEM *g_Inv_OptionList[]; +0x004655A8 - INVENTORY_ITEM *g_Inv_KeysList[]; +0x004644F8 - int32_t g_Inv_NFrames; +0x00525C00 - STATIC_INFO g_StaticObjects[50]; // MAX_STATIC_OBJECTS +0x00521FE0 - OBJECT_VECTOR *g_SoundEffects; +0x0051E6E0 - int16_t g_SampleLUT[]; +0x0051E9C4 - SAMPLE_INFO *g_SampleInfos; +0x004D7C78 + SOUND_SLOT g_SoundSlots[32]; +0x004D9328 - RECT g_GameVid_Rect; +0x004D9358 - LPDDS g_BackBufferSurface; +0x004D9350 - bool g_GameVid_IsVga; +0x004D9344 - int32_t g_GameVid_BufWidth; +0x004D9348 - int32_t g_GameVid_BufHeight; +0x004D8EB4 - LPDIRECTDRAWCLIPPER g_DDrawClipper; +0x004D8EB8 - PALETTEENTRY g_WinVid_Palette[256]; +0x004D92C4 - LPDIRECTDRAWPALETTE g_DDrawPalette; +0x004D7EC4 - LPDIRECT3DVIEWPORT2 g_D3DView; +0x004D9355 - bool g_NeedToReloadTextures; +0x004D9352 - bool g_GameVid_IsFullscreenVGA; +0x004D9353 - bool g_IsWindowedVGA; +0x004D9354 - bool g_Is16bitTextures; +0x004D9318 - RECT g_GameVid_BufRect; +0x00466BE4 - int16_t g_DumpX; +0x00466BE6 - int16_t g_DumpY; +0x00466BE8 - int16_t g_DumpWidth; +0x00466BEA - int16_t g_DumpHeight; +0x0051C1B8 - TEXTURE_FORMAT g_TextureFormat; +0x004D92E8 - COLOR_BIT_MASKS g_ColorBitMasks; +0x0051BC30 - bool g_WinVidNeedToResetBuffers; +0x004D7E88 - bool g_BGND_PictureIsReady; +0x004D7E90 - int32_t g_BGND_TexturePageIndexes[5]; +0x004D7EA8 - HWR_TEXTURE_HANDLE g_BGND_PageHandles[5]; +0x004D7EC0 - LPDIRECT3D2 g_D3D; +0x004D7EC8 - LPDIRECT3DMATERIAL2 g_D3DMaterial; +0x004D7ED4 - LPDIRECTDRAW g_DDrawInterface; +0x00466448 - const char g_GameClassName[]; +0x00466468 - const char g_GameWindowName[]; +0x004D7ED8 - bool g_IsGameWindowChanging; +0x00519F68 - D3DRENDERSTATETYPE g_AlphaBlendEnabler; +0x00519ED8 - D3DTEXTUREHANDLE g_CurrentTexSource; +0x00519F6C - bool g_ColorKeyState; +0x0051C20C - bool g_TexturesAlphaChannel; +0x00519F64 - bool g_ZEnableState; +0x00519F70 - bool g_ZWriteEnableState; +0x00466BDC - int32_t g_PaletteIndex; +0x00519F78 - int32_t g_HWR_TexturePageIndexes[32]; // MAX_TEXTURE_PAGES +0x004D7790 - int32_t g_HeightType; +0x004D9D94 - int16_t *g_FloorData; +0x00525B08 - int16_t *g_AnimCommands; +0x0052617C - ANIM_CHANGE *g_AnimChanges; +0x00525B04 - ANIM_RANGE *g_AnimRanges; +0x00526260 - int32_t g_FlipMaps[10]; // MAX_FLIPMAPS +0x005252B4 - int32_t g_Outside; +0x00526198 - int32_t g_OutsideRight; +0x00526178 - int32_t g_OutsideLeft; +0x005261AC - int32_t g_OutsideTop; +0x00525B00 - int32_t g_OutsideBottom; +0x00525900 - int32_t g_BoundRooms[128]; // MAX_BOUND_ROOMS +0x005258C0 - DOOR_VBUF g_DoorVBuf[4]; +0x00464180 - int32_t g_BoxLines[12][2]; +0x00526190 - BOOL g_CameraUnderwater; +0x005263D0 - int32_t g_BoxCount; +0x004D7C50 - int32_t g_SlotsUsed; +0x004D9360 - int32_t g_TexturePageCount; +0x004D9D90 - int16_t *g_MeshBase; +0x004D9E98 - int32_t g_TextureInfoCount; +0x004D93F0 - uint8_t g_LabTextureUVFlag[2048]; // MAX_TEXTURES +0x004B2B00 - PHD_TEXTURE g_TextureInfo[]; +0x005251B0 - FRAME_INFO *g_AnimFrames; +0x0051BC38 - int32_t g_IsWet; +0x0051B308 - RGB_888 g_WaterPalette[256]; +0x004BF2D8 - uint8_t g_DepthQIndex[256]; +0x004D7C74 - int32_t g_NumCameras; +0x0051B92C - int16_t *g_AnimTextureRanges; +0x005262F4 - int16_t g_CineLoaded; +0x005261B0 - uint32_t *g_DemoPtr; +0x005261B4 - int32_t g_DemoCount; +0x0051E6C0 - int32_t g_NumSampleInfos; +0x004D9BF4 - int32_t g_LevelFilePalettesOffset; +0x004D9BF8 - int32_t g_LevelFileTexPagesOffset; +0x004D9E9C - int32_t g_LevelFileDepthQOffset; +0x004D9D98 - char g_LevelFileName[256]; +0x005261C0 - uint16_t g_MusicTrackFlags[64]; +0x00465AE0 - WEAPON_INFO g_Weapons[]; +0x005206A8 - int16_t g_FinalBossActive; +0x005206BA - int16_t g_FinalLevelCount; +0x005206BC - int16_t g_FinalBossCount; +0x005206B0 - int16_t g_FinalBossItem[5]; +0x004D77B4 + int32_t g_DemoLevel; +0x004D77B8 + int32_t g_DemoLevel2; +0x00464A90 - INVENTORY_ITEM g_Inv_Item_Stopwatch; +0x00464AE0 - INVENTORY_ITEM g_Inv_Item_Pistols; +0x00464B30 - INVENTORY_ITEM g_Inv_Item_Flare; +0x00464B80 - INVENTORY_ITEM g_Inv_Item_Shotgun; +0x00464BD0 - INVENTORY_ITEM g_Inv_Item_Magnums; +0x00464C20 - INVENTORY_ITEM g_Inv_Item_Uzis; +0x00464C70 - INVENTORY_ITEM g_Inv_Item_Harpoon; +0x00464CC0 - INVENTORY_ITEM g_Inv_Item_M16; +0x00464D10 - INVENTORY_ITEM g_Inv_Item_Grenade; +0x00464D60 - INVENTORY_ITEM g_Inv_Item_PistolAmmo; +0x00464DB0 - INVENTORY_ITEM g_Inv_Item_ShotgunAmmo; +0x00464E00 - INVENTORY_ITEM g_Inv_Item_MagnumAmmo; +0x00464E50 - INVENTORY_ITEM g_Inv_Item_UziAmmo; +0x00464EA0 - INVENTORY_ITEM g_Inv_Item_HarpoonAmmo; +0x00464EF0 - INVENTORY_ITEM g_Inv_Item_M16Ammo; +0x00464F40 - INVENTORY_ITEM g_Inv_Item_GrenadeAmmo; +0x00464F90 - INVENTORY_ITEM g_Inv_Item_SmallMedi; +0x00464FE0 - INVENTORY_ITEM g_Inv_Item_LargeMedi; +0x00465030 - INVENTORY_ITEM g_Inv_Item_Pickup1; +0x00465080 - INVENTORY_ITEM g_Inv_Item_Pickup2; +0x004650D0 - INVENTORY_ITEM g_Inv_Item_Puzzle1; +0x00465120 - INVENTORY_ITEM g_Inv_Item_Puzzle2; +0x00465170 - INVENTORY_ITEM g_Inv_Item_Puzzle3; +0x004651C0 - INVENTORY_ITEM g_Inv_Item_Puzzle4; +0x00465210 - INVENTORY_ITEM g_Inv_Item_Key1; +0x00465260 - INVENTORY_ITEM g_Inv_Item_Key2; +0x004652B0 - INVENTORY_ITEM g_Inv_Item_Key3; +0x00465300 - INVENTORY_ITEM g_Inv_Item_Key4; +0x00465350 - INVENTORY_ITEM g_Inv_Item_Passport; +0x004653A0 - INVENTORY_ITEM g_Inv_Item_Graphics; +0x004653F0 - INVENTORY_ITEM g_Inv_Item_Sound; +0x00465440 - INVENTORY_ITEM g_Inv_Item_Controls; +0x00465490 - INVENTORY_ITEM g_Inv_Item_Photo; +0x00465620 - REQUEST_INFO g_LoadGameRequester; +0x00465838 - REQUEST_INFO g_SaveGameRequester; +0x004642E8 - int16_t g_GF_NumSecrets = 3; +0x004642F0 - int16_t g_GF_MusicTracks[16]; +0x004D77EC - int32_t g_GF_ScriptVersion; +0x004D77F0 - int32_t g_GF_LaraStartAnim; +0x004D77F4 - int16_t g_GF_SunsetEnabled; +0x004D77F8 - int16_t g_GF_DeadlyWater; +0x004D77FC - int16_t g_GF_NoFloor; +0x004D7800 - int16_t g_GF_RemoveWeapons; +0x004D7804 - int16_t g_GF_RemoveAmmo; +0x004D7808 - char g_GF_Kill2Complete; +0x004D780C - int8_t g_GF_StartGame; +0x004D7818 - char g_GF_Description[256]; +0x004D9C00 - int16_t g_GF_LevelOffsets[200]; +0x00521DC4 - char **g_GF_CutsceneFileNames; +0x00521E68 - char *g_GF_FMVFilenamesBuf; +0x00521E6C - char *g_GF_Key1StringsBuf; +0x00521E70 - int16_t *g_GF_FrontendSequence; +0x00521E74 - char **g_GF_Key2Strings; +0x00521E78 - char *g_GF_CutsceneFileNamesBuf; +0x00521E7C - char *g_GF_Key4StringsBuf; +0x00521E80 - int16_t *g_GF_SequenceBuf; +0x00521E84 - char *g_GF_Key2StringsBuf; +0x00521E8C - char *g_GF_PicFilenamesBuf; +0x00521E90 - char **g_GF_Key4Strings; +0x00521DC0 - char **g_GF_Puzzle1Strings; +0x00521E98 - char **g_GF_Puzzle2Strings; +0x00521EC0 - char **g_GF_Puzzle3Strings; +0x00521E60 - char **g_GF_Puzzle4Strings; +0x00521E94 - char **g_GF_Pickup1Strings; +0x00521F44 - char **g_GF_Pickup2Strings; +0x00521EA8 - char *g_GF_Puzzle1StringsBuf; +0x00521F40 - char *g_GF_Puzzle2StringsBuf; +0x00521F98 - char *g_GF_Puzzle3StringsBuf; +0x00521F90 - char *g_GF_Puzzle4StringsBuf; +0x00521E64 - char *g_GF_Pickup1StringsBuf; +0x00521E88 - char *g_GF_Pickup2StringsBuf; +0x00521E9C - char *g_GF_LevelFileNamesBuf; +0x00521EA0 - char **g_GF_PicFilenames; +0x00521EA4 - char **g_GF_Key1Strings; +0x00521EAC - char *g_GF_LevelNamesBuf; +0x00521EB0 - char **g_GF_GameStrings; +0x00521EB4 - char *g_GF_PCStringsBuf; +0x00521EB8 - char *g_GF_GameStringsBuf; +0x00521EBC - char **g_GF_Key3Strings; +0x00521EC4 - char **g_GF_LevelNames; +0x00521EE0 - int16_t *g_GF_ScriptTable[24]; // MAX_LEVELS +0x00521F48 - char **g_GF_TitleFileNames; +0x00521F4C - char *g_GF_TitleFileNamesBuf; +0x00521F50 - char **g_GF_PCStrings; +0x00521F54 - char **g_GF_LevelFileNames; +0x00521F60 - int16_t g_GF_ValidDemos[24]; // MAX_DEMO_FILES +0x00521F94 - char **g_GF_FMVFilenames; +0x00521F9C - char *g_GF_Key3StringsBuf; +0x00521FA0 - char g_GF_SecretInvItems[27]; // GF_ADD_INV_NUMBER_OF +0x00521FC0 - char g_GF_Add2InvItems[27]; // GF_ADD_INV_NUMBER_OF +0x004D9ECC - int32_t g_GameMode; // GAMEMODE +0x004D7970 - int32_t g_OldInputDB; +0x004D7948 - TEXTSTRING *g_Inv_ItemText[3]; // IT_NUMBER_OF +0x004D7950 - TEXTSTRING *g_Inv_LevelText; +0x004D7958 - TEXTSTRING *g_Inv_TagText; +0x004D9EBC - int32_t g_SavedGames; +0x0051A2CC - TEXTSTRING *g_PasswordText1; +0x0051A2D0 - int32_t g_PassportMode; +0x0051A2D8 - TEXTSTRING *g_DetailText[5]; +0x0051A2F0 - TEXTSTRING *g_SoundText[4]; +0x0051A290 + TEXTSTRING *m_ControlsTextA[14]; // INPUT_ROLE_NUMBER_OF +0x0051A258 + TEXTSTRING *g_ControlsTextB[14]; // INPUT_ROLE_NUMBER_OF +0x0051A300 + TEXTSTRING *g_ControlsText[2]; +0x004D7C30 + int32_t m_ShowStatsTextMode; +0x005207E0 - char g_ValidLevelStrings1[]; +0x00521720 - char g_ValidLevelStrings2[]; +0x004D7C34 + int32_t m_ShowEndStatsTextMode; +0x004D7C2C + int32_t m_ShowGymStatsTextMode; +0x00520D00 - uint32_t g_RequesterFlags1[24]; // MAX_REQUESTER_ITEMS +0x00520CA0 - uint32_t g_RequesterFlags2[24]; // MAX_REQUESTER_ITEMS +0x00521C40 - uint32_t g_SaveGameReqFlags1[24]; // MAX_REQUESTER_ITEMS +0x00521BE0 - uint32_t g_SaveGameReqFlags2[24]; // MAX_REQUESTER_ITEMS +0x004D9EC8 - int32_t g_SaveCounter; +0x00466B80 - int16_t g_SavedLevels[24]; // MAX_LEVELS +0x004654E8 - int16_t g_Inv_MainQtys[]; +0x00465578 - int16_t g_Inv_KeysQtys[]; +0x0046773C - int32_t g_DetailLevel; +0x0051A250 + int32_t g_LayoutPage; +0x0051A24C + int32_t g_KeySelector; +0x0051A248 + int32_t g_KeyCursor; +0x00466FA8 + const char *g_KeyNames[]; +0x00464500 - const uint16_t g_Requester_BackgroundGour1[]; +0x00464520 - const uint16_t g_Requester_BackgroundGour2[]; +0x00464538 - const uint16_t g_Requester_MainGour1[]; +0x00464558 - const uint16_t g_Requester_MainGour2[]; +0x00464590 - const uint16_t g_Requester_SelectionGour2[]; +0x004645A8 - const uint16_t g_Requester_UnselectionGour1[]; +0x005216E0 - uint16_t g_InvColors[17]; // INV_COLOR_NUMBER_OF diff --git a/src/tr2/config.c b/src/tr2/config.c new file mode 100644 index 000000000..f24bf7326 --- /dev/null +++ b/src/tr2/config.c @@ -0,0 +1,37 @@ +#include "config.h" + +#include "config_map.h" + +#include + +CONFIG g_Config = { 0 }; + +static const char *m_ConfigPath = "cfg/TR2X.json5"; + +const char *Config_GetPath(void) +{ + return m_ConfigPath; +} + +void Config_LoadFromJSON(JSON_OBJECT *root_obj) +{ + ConfigFile_LoadOptions(root_obj, g_ConfigOptionMap); +} + +void Config_DumpToJSON(JSON_OBJECT *root_obj) +{ + ConfigFile_DumpOptions(root_obj, g_ConfigOptionMap); +} + +void Config_Sanitize(void) +{ +} + +void Config_ApplyChanges(void) +{ +} + +const CONFIG_OPTION *Config_GetOptionMap(void) +{ + return g_ConfigOptionMap; +} diff --git a/src/tr2/config.h b/src/tr2/config.h new file mode 100644 index 000000000..2b3229a8b --- /dev/null +++ b/src/tr2/config.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include + +typedef struct { + bool loaded; + + struct { + bool fix_m16_accuracy; + } gameplay; +} CONFIG; + +extern CONFIG g_Config; diff --git a/src/tr2/config_map.c b/src/tr2/config_map.c new file mode 100644 index 000000000..82a3299d2 --- /dev/null +++ b/src/tr2/config_map.c @@ -0,0 +1,15 @@ +#include "config_map.h" + +#include "config.h" + +// import order guard +#include +// import order guard + +#include "global/types.h" + +const CONFIG_OPTION g_ConfigOptionMap[] = { +#include "config_map.def" + // guard + { 0 }, +}; diff --git a/src/tr2/config_map.def b/src/tr2/config_map.def new file mode 100644 index 000000000..be3e66861 --- /dev/null +++ b/src/tr2/config_map.def @@ -0,0 +1 @@ +CFG_BOOL(g_Config, gameplay.fix_m16_accuracy, true) diff --git a/src/tr2/config_map.h b/src/tr2/config_map.h new file mode 100644 index 000000000..177ea8d62 --- /dev/null +++ b/src/tr2/config_map.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const CONFIG_OPTION g_ConfigOptionMap[]; diff --git a/src/tr2/decomp/decomp.c b/src/tr2/decomp/decomp.c new file mode 100644 index 000000000..611459dae --- /dev/null +++ b/src/tr2/decomp/decomp.c @@ -0,0 +1,3058 @@ +#include "decomp/decomp.h" + +#include "config.h" +#include "game/background.h" +#include "game/camera.h" +#include "game/console/common.h" +#include "game/effects.h" +#include "game/game.h" +#include "game/gameflow.h" +#include "game/gameflow/gameflow_new.h" +#include "game/hwr.h" +#include "game/input.h" +#include "game/inventory/backpack.h" +#include "game/inventory/common.h" +#include "game/items.h" +#include "game/lara/control.h" +#include "game/lara/draw.h" +#include "game/lot.h" +#include "game/math.h" +#include "game/music.h" +#include "game/overlay.h" +#include "game/room.h" +#include "game/shell.h" +#include "game/sound.h" +#include "game/text.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" +#include "lib/ddraw.h" +#include "lib/dinput.h" +#include "specific/s_flagged_string.h" + +#include +#include + +#include +#include +#include + +#define IDI_MAINICON 100 + +static bool M_InsertDisplayModeInListSorted( + DISPLAY_MODE_LIST *mode_list, DISPLAY_MODE *src_mode); + +static void M_DisplayModeListInit(DISPLAY_MODE_LIST *mode_list); +static void M_DisplayModeListDelete(DISPLAY_MODE_LIST *mode_list); +static bool M_DisplayModeListCopy( + DISPLAY_MODE_LIST *dst, DISPLAY_MODE_LIST *src); +static DISPLAY_MODE *M_InsertDisplayMode( + DISPLAY_MODE_LIST *mode_list, DISPLAY_MODE_NODE *before); +static DISPLAY_MODE *M_InsertDisplayModeInListHead( + DISPLAY_MODE_LIST *mode_list); +static DISPLAY_MODE *M_InsertDisplayModeInListTail( + DISPLAY_MODE_LIST *mode_list); + +static void M_DisplayModeListInit(DISPLAY_MODE_LIST *mode_list) +{ + mode_list->head = NULL; + mode_list->tail = NULL; + mode_list->count = 0; +} + +static void M_DisplayModeListDelete(DISPLAY_MODE_LIST *mode_list) +{ + DISPLAY_MODE_NODE *node; + DISPLAY_MODE_NODE *nextNode; + + for (node = mode_list->head; node; node = nextNode) { + nextNode = node->next; + free(node); + } + M_DisplayModeListInit(mode_list); +} + +static bool M_DisplayModeListCopy( + DISPLAY_MODE_LIST *dst, DISPLAY_MODE_LIST *src) +{ + if (dst == NULL || src == NULL || dst == src) { + return false; + } + + M_DisplayModeListDelete(dst); + for (DISPLAY_MODE_NODE *node = src->head; node != NULL; node = node->next) { + DISPLAY_MODE *dst_mode = M_InsertDisplayModeInListTail(dst); + *dst_mode = node->body; + } + return true; +} + +static DISPLAY_MODE *M_InsertDisplayMode( + DISPLAY_MODE_LIST *mode_list, DISPLAY_MODE_NODE *before) +{ + if (!before || !before->previous) { + return M_InsertDisplayModeInListHead(mode_list); + } + + DISPLAY_MODE_NODE *node = malloc(sizeof(DISPLAY_MODE_NODE)); + if (!node) { + return NULL; + } + + before->previous->next = node; + node->previous = before->previous; + + before->previous = node; + node->next = before; + + mode_list->count++; + return &node->body; +} + +static DISPLAY_MODE *M_InsertDisplayModeInListHead(DISPLAY_MODE_LIST *mode_list) +{ + DISPLAY_MODE_NODE *node = malloc(sizeof(DISPLAY_MODE_NODE)); + if (!node) { + return NULL; + } + + node->next = mode_list->head; + node->previous = NULL; + + if (mode_list->head) { + mode_list->head->previous = node; + } + + if (!mode_list->tail) { + mode_list->tail = node; + } + + mode_list->head = node; + mode_list->count++; + return &node->body; +} + +static DISPLAY_MODE *M_InsertDisplayModeInListTail(DISPLAY_MODE_LIST *mode_list) +{ + DISPLAY_MODE_NODE *node = malloc(sizeof(DISPLAY_MODE_NODE)); + if (!node) { + return NULL; + } + + node->next = NULL; + node->previous = mode_list->tail; + + if (mode_list->tail) { + mode_list->tail->next = node; + } + + if (!mode_list->head) { + mode_list->head = node; + } + + mode_list->tail = node; + mode_list->count++; + return &node->body; +} + +static bool M_InsertDisplayModeInListSorted( + DISPLAY_MODE_LIST *mode_list, DISPLAY_MODE *src_mode) +{ + DISPLAY_MODE *dst_mode = NULL; + + if (mode_list->head == NULL + || CompareVideoModes(src_mode, &mode_list->head->body)) { + dst_mode = M_InsertDisplayModeInListHead(mode_list); + goto finish; + } + for (DISPLAY_MODE_NODE *node = mode_list->head; node != NULL; + node = node->next) { + if (CompareVideoModes(src_mode, &node->body)) { + dst_mode = M_InsertDisplayMode(mode_list, node); + goto finish; + } + } + dst_mode = M_InsertDisplayModeInListTail(mode_list); + +finish: + if (dst_mode == NULL) { + return false; + } + *dst_mode = *src_mode; + return true; +} + +int32_t __cdecl GameInit(void) +{ + Music_Shutdown(); + UT_InitAccurateTimer(); + // clang-format off + Sound_Init(); + return WinVidInit() + && Direct3DInit() + && RenderInit() + && InitTextures() + && WinInputInit() + && TIME_Init() + && HWR_Init() + && BGND_Init(); + // clang-format on +} + +int32_t __stdcall WinMain( + HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, + int32_t nShowCmd) +{ + g_GameModule = hInstance; + g_CmdLine = lpCmdLine; + HWND game_window = WinVidFindGameWindow(); + if (game_window) { + HWND setup_window = SE_FindSetupDialog(); + if (!setup_window) { + setup_window = game_window; + } + SetForegroundWindow(setup_window); + return 0; + } + + // TODO: install exception handler + + bool is_setup_requested = UT_FindArg("setup") != 0; + if (!GameInit()) { + UT_ErrorBox(IDS_DX5_REQUIRED, NULL); + goto cleanup; + } + + int32_t app_settings_status = SE_ReadAppSettings(&g_SavedAppSettings); + if (!app_settings_status) { + goto cleanup; + } + + if (app_settings_status == 2 || is_setup_requested) { + if (SE_ShowSetupDialog(0, app_settings_status == 2)) { + SE_WriteAppSettings(&g_SavedAppSettings); + if (is_setup_requested) { + goto cleanup; + } + } else { + goto cleanup; + } + } + + int32_t result = WinGameStart(); + if (result) { + Shell_Shutdown(); + RenderErrorBox(result); + if (!SE_ShowSetupDialog(0, 0)) { + goto cleanup; + } + SE_WriteAppSettings(&g_SavedAppSettings); + } else { + g_StopInventory = 0; + g_IsGameToExit = 0; + Shell_Main(); + Config_Write(); + Shell_Shutdown(); + SE_WriteAppSettings(&g_SavedAppSettings); + } + +cleanup: + Shell_Cleanup(); + return g_AppResultCode; +} + +const char *__cdecl DecodeErrorMessage(int32_t error_code) +{ + return g_ErrorMessages[error_code]; +} + +int32_t __cdecl RenderErrorBox(int32_t error_code) +{ + char buffer[128]; + const char *decoded = DecodeErrorMessage(error_code); + sprintf(buffer, "Render init failed with \"%s\"", decoded); + return UT_MessageBox(buffer, 0); +} + +void __cdecl ScreenshotPCX(void) +{ + LPDDS screen = g_SavedAppSettings.render_mode == RM_SOFTWARE + ? g_RenderBufferSurface + : g_PrimaryBufferSurface; + + DDSURFACEDESC desc = { .dwSize = sizeof(DDSURFACEDESC), 0 }; + + int32_t result; + while (true) { + result = IDirectDrawSurface_Lock(screen, 0, &desc, 0, 0); + if (result != DDERR_WASSTILLDRAWING) { + break; + } + } + + if (result == DDERR_SURFACELOST) { + IDirectDrawSurface_Restore(screen); + } + + if (FAILED(result)) { + return; + } + + uint8_t *pcx_data; + int32_t pcx_size = CompPCX( + desc.lpSurface, desc.dwWidth, desc.dwHeight, g_GamePalette8, &pcx_data); + + IDirectDrawSurface_Unlock(screen, &desc); + if (!pcx_size) { + return; + } + + g_ScreenshotCounter++; + if (g_ScreenshotCounter > 9999) { + g_ScreenshotCounter = 1; + } + + char file_name[20]; + sprintf(file_name, "tomb%04d.pcx", g_ScreenshotCounter); + + HANDLE handle = CreateFileA( + file_name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, + NULL); + DWORD bytes_written; + WriteFile(handle, pcx_data, pcx_size, &bytes_written, 0); + CloseHandle(handle); + GlobalFree(pcx_data); +} + +size_t __cdecl CompPCX( + uint8_t *bitmap, int32_t width, int32_t height, RGB_888 *palette, + uint8_t **pcx_data) +{ + *pcx_data = (uint8_t *)GlobalAlloc( + GMEM_FIXED, + sizeof(PCX_HEADER) + sizeof(RGB_888) * 256 + width * height * 2); + if (*pcx_data == NULL) { + return 0; + } + + PCX_HEADER *pcx_header = (PCX_HEADER *)*pcx_data; + pcx_header->manufacturer = 10; + pcx_header->version = 5; + pcx_header->rle = 1; + pcx_header->bpp = 8; + pcx_header->planes = 1; + pcx_header->x_min = 0; + pcx_header->y_min = 0; + pcx_header->x_max = width - 1; + pcx_header->y_max = height - 1; + pcx_header->h_dpi = width; + pcx_header->v_dpi = height; + pcx_header->bytes_per_line = width; + + uint8_t *pic_data = *pcx_data + sizeof(PCX_HEADER); + for (int32_t y = 0; y < height; y++) { + pic_data += EncodeLinePCX(bitmap, width, pic_data); + bitmap += width; + } + + *pic_data++ = 0x0C; + for (int32_t i = 0; i < 256; i++) { + *pic_data++ = palette[i].red; + *pic_data++ = palette[i].green; + *pic_data++ = palette[i].blue; + } + + return pic_data - *pcx_data + sizeof(RGB_888) * 256; +} + +size_t __cdecl EncodeLinePCX( + const uint8_t *src, const int32_t width, uint8_t *dst) +{ + const uint8_t *const dst_start = dst; + int32_t run_count = 1; + uint8_t last = *src; + + for (int32_t i = 1; i < width; i++) { + uint8_t current = *src++; + if (*src == last) { + run_count++; + if (run_count == 63) { + const size_t add = EncodePutPCX(last, 0x3Fu, dst); + if (add == 0) { + return 0; + } + dst += add; + run_count = 0; + } + } else { + if (run_count != 0) { + const size_t add = EncodePutPCX(last, run_count, dst); + if (add == 0) { + return 0; + } + dst += add; + } + last = current; + run_count = 1; + } + } + + if (run_count != 0) { + const size_t add = EncodePutPCX(last, run_count, dst); + if (add == 0) { + return 0; + } + dst += add; + } + + const size_t total = dst - dst_start; + return total; +} + +size_t __cdecl EncodePutPCX(uint8_t value, uint8_t num, uint8_t *buffer) +{ + if (num == 0 || num > 63) { + return 0; + } + + if (num == 1 && (value & 0xC0) != 0xC0) { + buffer[0] = value; + return 1; + } + + buffer[0] = num | 0xC0; + buffer[1] = value; + return 2; +} + +void __cdecl ScreenshotTGA(IDirectDrawSurface3 *screen, int32_t bpp) +{ + DDSURFACEDESC desc = { + .dwSize = sizeof(DDSURFACEDESC), + 0, + }; + + if (FAILED(WinVidBufferLock(screen, &desc, 0x21u))) { + return; + } + + const int32_t width = desc.dwWidth; + const int32_t height = desc.dwHeight; + + g_ScreenshotCounter++; + + char file_name[20]; + sprintf(file_name, "tomb%04d.tga", g_ScreenshotCounter); + + FILE *handle = fopen(file_name, "wb"); + if (!handle) { + return; + } + + const TGA_HEADER header = { + .id_length = 0, + .color_map_type = 0, + .data_type_code = 2, // Uncompressed, RGB images + .color_map_origin = 0, + .color_map_length = 0, + .color_map_depth = 0, + .x_origin = 0, + .y_origin = 0, + .width = width, + .height = height, + .bpp = 16, + .image_descriptor = 0, + }; + + fwrite(&header, sizeof(TGA_HEADER), 1, handle); + + uint8_t *tga_pic = + (uint8_t *)GlobalAlloc(GMEM_FIXED, width * height * (bpp / 8)); + uint8_t *src = desc.lpSurface + desc.lPitch * (height - 1); + + uint8_t *dst = tga_pic; + for (int32_t y = 0; y < height; y++) { + if (desc.ddpfPixelFormat.dwRBitMask == 0xF800) { + // R5G6B5 - transform + for (int32_t x = 0; x < width; x++) { + const uint16_t sample = ((uint16_t *)src)[x]; + ((uint16_t *)dst)[x] = + ((sample & 0xFFC0) >> 1) | (sample & 0x001F); + } + } else { + // X1R5G5B5 - good + memcpy(dst, src, sizeof(uint16_t) * width); + } + src -= desc.lPitch; + dst += sizeof(uint16_t) * width; + } + fwrite(tga_pic, 2 * height * width, 1, handle); + +cleanup: + if (tga_pic) { + GlobalFree(tga_pic); + } + + if (handle) { + fclose(handle); + } + WinVidBufferUnlock(screen, &desc); +} + +void __cdecl Screenshot(LPDDS screen) +{ + DDSURFACEDESC desc = { 0 }; + desc.dwSize = sizeof(DDSURFACEDESC); + + if (SUCCEEDED(IDirectDrawSurface_GetSurfaceDesc(screen, &desc))) { + if (desc.ddpfPixelFormat.dwRGBBitCount == 8) { + ScreenshotPCX(); + } else if (desc.ddpfPixelFormat.dwRGBBitCount == 16) { + ScreenshotTGA(screen, 16); + } + } +} + +bool __cdecl DInputCreate(void) +{ + return SUCCEEDED(DirectInputCreate(g_GameModule, 1280, &g_DInput, NULL)); +} + +void __cdecl DInputRelease(void) +{ + if (g_DInput) { + IDirectInput_Release(g_DInput); + g_DInput = NULL; + } +} + +void __cdecl WinInReadKeyboard(uint8_t *input_data) +{ + if (SUCCEEDED(IDirectInputDevice_GetDeviceState( + IDID_SysKeyboard, 256, input_data))) { + return; + } + + if (SUCCEEDED(IDirectInputDevice_Acquire(IDID_SysKeyboard)) + && SUCCEEDED(IDirectInputDevice_GetDeviceState( + IDID_SysKeyboard, 256, input_data))) { + return; + } + + memset(input_data, 0, 256); +} + +int32_t __cdecl WinGameStart(void) +{ + // try { + WinVidStart(); + RenderStart(1); + WinInStart(); + // } catch (int32_t error) { + // return error; + // } + return 0; +} + +void __cdecl Shell_Shutdown(void) +{ + Console_Shutdown(); + WinInFinish(); + RenderFinish(1); + WinVidFinish(); + WinVidHideGameWindow(); + if (g_ErrorMessage[0]) { + MessageBoxA(NULL, g_ErrorMessage, NULL, MB_ICONWARNING); + } + Text_Shutdown(); + UI_Shutdown(); + Config_Shutdown(); +} + +int16_t __cdecl TitleSequence(void) +{ + GF_N_LoadStrings(-1); + + TempVideoAdjust(1, 1.0); + g_NoInputCounter = 0; + + if (!g_IsTitleLoaded) { + if (!Level_Initialise(0, GFL_TITLE)) { + return GFD_EXIT_GAME; + } + g_IsTitleLoaded = true; + } + + S_DisplayPicture("data/title.pcx", true); + if (g_GameFlow.title_track) { + Music_Play(g_GameFlow.title_track, true); + } + + GAME_FLOW_DIR dir = Inv_Display(INV_TITLE_MODE); + + S_FadeToBlack(); + S_DontDisplayPicture(); + Music_Stop(); + + if (dir == GFD_OVERRIDE) { + dir = g_GF_OverrideDir; + g_GF_OverrideDir = (GAME_FLOW_DIR)-1; + return dir; + } + + if (dir == GFD_START_DEMO) { + return GFD_START_DEMO; + } + + if (g_Inv_Chosen == O_PHOTO_OPTION) { + return GFD_START_GAME | LV_GYM; + } + + if (g_Inv_Chosen == O_PASSPORT_OPTION) { + const int32_t slot_num = g_Inv_ExtraData[1]; + + if (g_Inv_ExtraData[0] == 0) { + Inv_RemoveAllItems(); + S_LoadGame(&g_SaveGame, sizeof(SAVEGAME_INFO), slot_num); + return GFD_START_SAVED_GAME | slot_num; + } + + if (g_Inv_ExtraData[0] == 1) { + InitialiseStartInfo(); + int32_t level_id = LV_FIRST; + if (g_GameFlow.play_any_level) { + level_id = LV_FIRST + slot_num; + } + return GFD_START_GAME | level_id; + } + return GFD_EXIT_GAME; + } + + return GFD_EXIT_GAME; +} + +void __cdecl WinVidSetMinWindowSize(int32_t width, int32_t height) +{ + g_MinWindowClientWidth = width; + g_MinWindowClientHeight = height; + GameWindowCalculateSizeFromClient(&width, &height); + g_MinWindowWidth = width; + g_MinWindowHeight = height; + g_IsMinWindowSizeSet = true; +} + +void __cdecl WinVidSetMaxWindowSize(int32_t width, int32_t height) +{ + g_MaxWindowClientWidth = width; + g_MaxWindowClientHeight = height; + GameWindowCalculateSizeFromClient(&width, &height); + g_MaxWindowWidth = width; + g_MaxWindowHeight = height; + g_IsMaxWindowSizeSet = true; +} + +void __cdecl WinVidClearMinWindowSize(void) +{ + g_IsMinWindowSizeSet = false; +} + +void __cdecl WinVidClearMaxWindowSize(void) +{ + g_IsMaxWindowSizeSet = false; +} + +int32_t __cdecl CalculateWindowWidth(const int32_t width, const int32_t height) +{ + if (g_SavedAppSettings.aspect_mode == AM_4_3) { + return 4 * height / 3; + } + if (g_SavedAppSettings.aspect_mode == AM_16_9) { + return 16 * height / 9; + } + return width; +} + +int32_t __cdecl CalculateWindowHeight(const int32_t width, const int32_t height) +{ + if (g_SavedAppSettings.aspect_mode == AM_4_3) { + return (3 * width) / 4; + } + if (g_SavedAppSettings.aspect_mode == AM_16_9) { + return (9 * width) / 16; + } + return height; +} + +bool __cdecl WinVidGetMinMaxInfo(LPMINMAXINFO info) +{ + if (!g_IsGameWindowCreated) { + return false; + } + + if (g_IsGameFullScreen) { + info->ptMaxTrackSize.x = g_FullScreenWidth; + info->ptMaxTrackSize.y = g_FullScreenHeight; + info->ptMinTrackSize.x = g_FullScreenWidth; + info->ptMinTrackSize.y = g_FullScreenHeight; + info->ptMaxSize.x = g_FullScreenWidth; + info->ptMaxSize.y = g_FullScreenHeight; + return true; + } + + if (g_IsMinWindowSizeSet) { + info->ptMinTrackSize.x = g_MinWindowWidth; + info->ptMinTrackSize.y = g_MinWindowHeight; + } + + if (g_IsMinMaxInfoSpecial) { + int32_t width = g_GameWindowWidth; + int32_t height = g_GameWindowHeight; + GameWindowCalculateSizeFromClient(&width, &height); + info->ptMaxSize.x = width; + info->ptMaxTrackSize.x = width; + info->ptMaxSize.y = height; + info->ptMaxTrackSize.y = height; + } else if (g_IsMaxWindowSizeSet) { + info->ptMaxTrackSize.x = g_MaxWindowWidth; + info->ptMaxTrackSize.y = g_MaxWindowHeight; + info->ptMaxSize.x = g_MaxWindowWidth; + info->ptMaxSize.y = g_MaxWindowHeight; + } + + return g_IsMinWindowSizeSet || g_IsMaxWindowSizeSet; +} + +HWND __cdecl WinVidFindGameWindow(void) +{ + return FindWindowA(CLASS_NAME, WINDOW_NAME); +} + +bool __cdecl WinVidSpinMessageLoop(bool need_wait) +{ + if (g_IsMessageLoopClosed) { + return 0; + } + + g_MessageLoopCounter++; + + do { + if (need_wait) { + WaitMessage(); + } else { + need_wait = true; + } + + MSG msg; + while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageA(&msg); + if (msg.message == WM_QUIT) { + g_AppResultCode = msg.wParam; + g_IsMessageLoopClosed = true; + g_IsGameToExit = true; + g_StopInventory = true; + g_MessageLoopCounter--; + return 0; + } else if (msg.message == WM_KEYDOWN) { + UI_HandleKeyDown(msg.wParam); + return 0; + } else if (msg.message == WM_KEYUP) { + UI_HandleKeyUp(msg.wParam); + return 0; + } else if (msg.message == WM_CHAR) { + char insert_string[2] = { msg.wParam, '\0' }; + UI_HandleTextEdit(insert_string); + return 0; + } + } + } while (!g_IsGameWindowActive || g_IsGameWindowMinimized); + + g_MessageLoopCounter--; + return true; +} + +void __cdecl WinVidShowGameWindow(const int32_t cmd_show) +{ + if (cmd_show != SW_SHOW || !g_IsGameWindowShow) { + g_IsGameWindowUpdating = true; + ShowWindow(g_GameWindowHandle, cmd_show); + UpdateWindow(g_GameWindowHandle); + g_IsGameWindowUpdating = false; + g_IsGameWindowShow = true; + } +} + +void __cdecl WinVidHideGameWindow(void) +{ + if (g_IsGameWindowShow) { + g_IsGameWindowUpdating = true; + ShowWindow(g_GameWindowHandle, SW_HIDE); + UpdateWindow(g_GameWindowHandle); + g_IsGameWindowUpdating = false; + g_IsGameWindowShow = false; + } +} + +void __cdecl WinVidSetGameWindowSize(int32_t width, int32_t height) +{ + GameWindowCalculateSizeFromClient(&width, &height); + SetWindowPos( + g_GameWindowHandle, NULL, 0, 0, width, height, + SWP_NOCOPYBITS | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE); +} + +bool __cdecl ShowDDrawGameWindow(bool active) +{ + if (!g_GameWindowHandle || !g_DDraw) { + return false; + } + if (g_IsDDrawGameWindowShow) { + return true; + } + + RECT rect; + GetWindowRect(g_GameWindowHandle, &rect); + g_GameWindowX = rect.left; + g_GameWindowY = rect.top; + + if (active) { + WinVidShowGameWindow(SW_SHOW); + } + + g_IsGameWindowUpdating = true; + uint32_t flags = DDSCL_ALLOWMODEX | DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT + | DDSCL_FULLSCREEN; + if (!active) + flags |= DDSCL_NOWINDOWCHANGES; + const HRESULT result = + IDirectDraw_SetCooperativeLevel(g_DDraw, g_GameWindowHandle, flags); + g_IsGameWindowUpdating = false; + if (FAILED(result)) { + return false; + } + + g_IsDDrawGameWindowShow = true; + return true; +} + +bool __cdecl HideDDrawGameWindow(void) +{ + if (!g_GameWindowHandle || !g_DDraw) { + return false; + } + if (!g_IsDDrawGameWindowShow) { + return true; + } + + WinVidHideGameWindow(); + g_IsGameWindowUpdating = true; + const HRESULT result = IDirectDraw_SetCooperativeLevel( + g_DDraw, g_GameWindowHandle, DDSCL_NORMAL); + if (SUCCEEDED(result)) { + g_IsDDrawGameWindowShow = false; + SetWindowPos( + g_GameWindowHandle, NULL, g_GameWindowX, g_GameWindowY, 0, 0, + SWP_NOCOPYBITS | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE); + } + + g_IsGameWindowUpdating = false; + return SUCCEEDED(result); +} + +HRESULT __cdecl DDrawSurfaceCreate(LPDDSDESC dsp, LPDDS *surface) +{ + LPDIRECTDRAWSURFACE sub_surface; + HRESULT rc = IDirectDraw_CreateSurface(g_DDraw, dsp, &sub_surface, NULL); + + if SUCCEEDED (rc) { + rc = IDirectDrawSurface_QueryInterface( + sub_surface, &g_IID_IDirectDrawSurface3, (LPVOID *)surface); + IDirectDrawSurface_Release(sub_surface); + } + + return rc; +} + +HRESULT __cdecl DDrawSurfaceRestoreLost( + LPDDS surface1, LPDDS surface2, bool blank) +{ + HRESULT rc = IDirectDrawSurface_IsLost(surface1); + if (rc != DDERR_SURFACELOST) { + return rc; + } + rc = IDirectDrawSurface_Restore(surface2 != NULL ? surface2 : surface1); + if (blank && SUCCEEDED(rc)) { + WinVidClearBuffer(surface1, 0, 0); + } + return rc; +} + +bool __cdecl WinVidClearBuffer(LPDDS surface, LPRECT rect, DWORD fill_color) +{ + DDBLTFX blt = { .dwFillColor = fill_color, .dwSize = sizeof(DDBLTFX), 0 }; + HRESULT rc = IDirectDrawSurface_Blt( + surface, rect, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &blt); + return SUCCEEDED(rc); +} + +HRESULT __cdecl WinVidBufferLock(LPDDS surface, LPDDSDESC desc, DWORD flags) +{ + memset(desc, 0, sizeof(DDSURFACEDESC)); + desc->dwSize = sizeof(DDSURFACEDESC); + HRESULT rc = IDirectDrawSurface_Lock(surface, NULL, desc, flags, NULL); + if (SUCCEEDED(rc)) { + g_LockedBufferCount++; + } + return rc; +} + +HRESULT __cdecl WinVidBufferUnlock(LPDDS surface, LPDDSDESC desc) +{ + HRESULT rc = surface->lpVtbl->Unlock(surface, desc->lpSurface); + if (SUCCEEDED(rc)) { + g_LockedBufferCount--; + } + return rc; +} + +bool __cdecl WinVidCopyBitmapToBuffer(LPDDS surface, const BYTE *bitmap) +{ + DDSURFACEDESC desc; + if (FAILED( + WinVidBufferLock(surface, &desc, DDLOCK_WRITEONLY | DDLOCK_WAIT))) { + return false; + } + + const uint8_t *src = (const uint8_t *)bitmap; + uint8_t *dst = (uint8_t *)desc.lpSurface; + for (int32_t i = 0; i < (int32_t)desc.dwHeight; i++) { + memcpy(dst, src, desc.dwWidth); + src += desc.dwWidth; + dst += desc.lPitch; + } + WinVidBufferUnlock(surface, &desc); + return true; +} + +DWORD __cdecl GetRenderBitDepth(const uint32_t rgb_bit_count) +{ + switch (rgb_bit_count) { + // clang-format off + case 1: return DDBD_1; + case 2: return DDBD_2; + case 4: return DDBD_4; + case 8: return DDBD_8; + case 0x10: return DDBD_16; + case 0x18: return DDBD_24; + case 0x20: return DDBD_32; + // clang-format on + } + return 0; +} + +void __thiscall WinVidGetColorBitMasks( + COLOR_BIT_MASKS *bm, LPDDPIXELFORMAT pixel_format) +{ + bm->mask.r = pixel_format->dwRBitMask; + bm->mask.g = pixel_format->dwGBitMask; + bm->mask.b = pixel_format->dwBBitMask; + bm->mask.a = pixel_format->dwRGBAlphaBitMask; + BitMaskGetNumberOfBits(bm->mask.r, &bm->depth.r, &bm->offset.r); + BitMaskGetNumberOfBits(bm->mask.g, &bm->depth.g, &bm->offset.g); + BitMaskGetNumberOfBits(bm->mask.b, &bm->depth.b, &bm->offset.b); + BitMaskGetNumberOfBits(bm->mask.a, &bm->depth.a, &bm->offset.a); +} + +void __cdecl BitMaskGetNumberOfBits( + uint32_t bit_mask, uint32_t *bit_depth, uint32_t *bit_offset) +{ + if (!bit_mask) { + *bit_offset = 0; + *bit_depth = 0; + return; + } + + int32_t i; + + for (i = 0; (bit_mask & 1) == 0; i++) { + bit_mask >>= 1; + } + *bit_offset = i; + + for (i = 0; bit_mask != 0; i++) { + bit_mask >>= 1; + } + *bit_depth = i; +} + +DWORD __cdecl CalculateCompatibleColor( + const COLOR_BIT_MASKS *const mask, const int32_t red, const int32_t green, + const int32_t blue, const int32_t alpha) +{ + // clang-format off + return ( + (red >> (8 - mask->depth.r) << mask->offset.r) | + (green >> (8 - mask->depth.g) << mask->offset.g) | + (blue >> (8 - mask->depth.b) << mask->offset.b) | + (alpha >> (8 - mask->depth.a) << mask->offset.a) + ); + // clang-format on +} + +bool __cdecl WinVidGetDisplayMode(DISPLAY_MODE *disp_mode) +{ + DDSDESC dsp = { .dwSize = sizeof(DDSDESC), 0 }; + + if (FAILED(IDirectDraw_GetDisplayMode(g_DDraw, &dsp))) { + return false; + } + + // clang-format off + if (!(dsp.dwFlags & DDSD_WIDTH) + || !(dsp.dwFlags & DDSD_HEIGHT) + || !(dsp.dwFlags & DDSD_PIXELFORMAT) + || !(dsp.ddpfPixelFormat.dwFlags & DDPF_RGB) + ) { + return false; + } + // clang-format on + + disp_mode->width = dsp.dwWidth; + disp_mode->height = dsp.dwHeight; + disp_mode->bpp = dsp.ddpfPixelFormat.dwRGBBitCount; + disp_mode->vga = (dsp.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) != 0 + ? VGA_256_COLOR + : VGA_NO_VGA; + return true; +} + +bool __cdecl WinVidGoFullScreen(DISPLAY_MODE *disp_mode) +{ + g_FullScreenWidth = disp_mode->width; + g_FullScreenHeight = disp_mode->height; + g_FullScreenBPP = disp_mode->bpp; + g_FullScreenVGA = disp_mode->vga; + + if (!ShowDDrawGameWindow(true)) { + return false; + } + + g_IsGameWindowUpdating = true; + const HRESULT rc = IDirectDraw4_SetDisplayMode( + g_DDraw, disp_mode->width, disp_mode->height, disp_mode->bpp, 0, + disp_mode->vga == VGA_STANDARD ? DDSDM_STANDARDVGAMODE : 0); + g_IsGameWindowUpdating = false; + + if (FAILED(rc)) { + return false; + } + + g_IsGameFullScreen = true; + return true; +} + +bool __cdecl WinVidGoWindowed( + int32_t width, int32_t height, DISPLAY_MODE *disp_mode) +{ + if (!HideDDrawGameWindow()) { + return false; + } + if (!WinVidGetDisplayMode(disp_mode)) { + return false; + } + + int32_t max_width = disp_mode->width; + int32_t max_height = + CalculateWindowHeight(disp_mode->width, disp_mode->height); + if (max_height > disp_mode->height) { + max_height = disp_mode->height; + max_width = CalculateWindowWidth(disp_mode->width, max_height); + } + WinVidSetMaxWindowSize(max_width, max_height); + + if (width > max_width || height > max_height) { + width = max_width; + height = max_height; + } + + g_IsGameFullScreen = false; + g_IsGameWindowUpdating = true; + WinVidSetGameWindowSize(width, height); + g_IsGameWindowUpdating = false; + + RECT rect; + GetClientRect(g_GameWindowHandle, &rect); + MapWindowPoints(g_GameWindowHandle, NULL, (LPPOINT)&rect, 2); + + if ((rect.left > 0 || rect.right < disp_mode->width) + && (rect.top > 0 || rect.bottom < disp_mode->height)) { + WinVidShowGameWindow(SW_SHOW); + } else { + WinVidShowGameWindow(SW_MAXIMIZE); + } + + disp_mode->width = width; + disp_mode->height = height; + return true; +} + +void __cdecl WinVidSetDisplayAdapter(DISPLAY_ADAPTER *disp_adapter) +{ + DISPLAY_MODE disp_mode; + + disp_adapter->sw_windowed_supported = false; + disp_adapter->hw_windowed_supported = false; + disp_adapter->screen_width = 0; + + if (disp_adapter->adapter_guid_ptr != NULL) { + return; + } + + if (!DDrawCreate(NULL)) { + return; + } + + bool result = WinVidGetDisplayMode(&disp_mode); + DDrawRelease(); + + if (!result) { + return; + } + + disp_mode.width &= ~0x1F; + if (disp_mode.width * 3 / 4 > disp_mode.height) { + disp_mode.width = (disp_mode.height * 4 / 3) & ~0x1F; + } + + disp_adapter->sw_windowed_supported = disp_mode.vga == VGA_256_COLOR; + disp_adapter->hw_windowed_supported = disp_adapter->hw_render_supported + && (disp_adapter->hw_device_desc.dwFlags & D3DDD_DEVICERENDERBITDEPTH) + != 0 + && (GetRenderBitDepth(disp_mode.bpp) + & disp_adapter->hw_device_desc.dwDeviceRenderBitDepth) + != 0; +} + +void __cdecl Game_SetCutsceneTrack(const int32_t track) +{ + g_CineTrackID = track; +} + +int32_t __cdecl Game_Cutscene_Start(const int32_t level_num) +{ + g_IsTitleLoaded = false; + S_FadeToBlack(); + if (!Level_Initialise(level_num, GFL_CUTSCENE)) { + return 2; + } + + Misc_InitCinematicRooms(); + CutscenePlayer1_Initialise(g_Lara.item_num); + g_Camera.target_angle = g_CineTargetAngle; + + const bool old_sound_active = g_SoundIsActive; + g_SoundIsActive = false; + + g_CineFrameIdx = 0; + S_ClearScreen(); + + if (!Music_PlaySynced(g_CineTrackID)) { + return 1; + } + + Music_SetVolume(255); + g_CineFrameCurrent = 0; + + int32_t result; + do { + Game_DrawCinematic(); + int32_t nticks = + g_CineFrameCurrent - TICKS_PER_FRAME * (g_CineFrameIdx - 4); + CLAMPL(nticks, TICKS_PER_FRAME); + result = Game_Cutscene_Control(nticks); + } while (!result); + + if (g_OptionMusicVolume) { + Music_SetVolume(25 * g_OptionMusicVolume + 5); + } else { + Music_SetVolume(0); + } + Music_Stop(); + g_SoundIsActive = old_sound_active; + Sound_StopAllSamples(); + + g_LevelComplete = true; + return result; +} + +void __cdecl Misc_InitCinematicRooms(void) +{ + for (int32_t i = 0; i < g_RoomCount; i++) { + const int16_t flipped_room = g_Rooms[i].flipped_room; + if (flipped_room != NO_ROOM_NEG) { + g_Rooms[flipped_room].bound_active = 1; + } + g_Rooms[i].flags |= RF_OUTSIDE; + } + + g_DrawRoomsCount = 0; + for (int32_t i = 0; i < g_RoomCount; i++) { + if (!g_Rooms[i].bound_active) { + g_DrawRoomsArray[g_DrawRoomsCount++] = i; + } + } +} + +int32_t __cdecl Game_Cutscene_Control(const int32_t nframes) +{ + g_CineTickCount += g_CineTickRate * nframes; + + if (g_CineTickCount >= 0) { + while (1) { + if (g_GF_OverrideDir != (GAME_FLOW_DIR)-1) { + return 4; + } + + if (Input_Update()) { + return 3; + } + if (g_InputDB & IN_ACTION) { + return 1; + } + if (g_InputDB & IN_OPTION) { + return 2; + } + + g_DynamicLightCount = 0; + + for (int32_t id = g_NextItemActive; id != NO_ITEM;) { + const ITEM *const item = &g_Items[id]; + const OBJECT *obj = &g_Objects[item->object_id]; + if (obj->control != NULL) { + obj->control(id); + } + id = item->next_active; + } + + for (int32_t id = g_NextEffectActive; id != NO_ITEM;) { + const FX *const fx = &g_Effects[id]; + const OBJECT *const obj = &g_Objects[fx->object_id]; + if (obj->control != NULL) { + obj->control(id); + } + id = fx->next_active; + } + + HairControl(1); + Camera_UpdateCutscene(); + + g_CineFrameIdx++; + if (g_CineFrameIdx >= g_NumCineFrames) { + return 1; + } + + g_CineTickCount -= 0x10000; + if (g_CineTickCount < 0) { + break; + } + } + } + + if (Music_GetTimestamp() < 0.0) { + g_CineFrameCurrent++; + } else { + // sync with music + g_CineFrameCurrent = + Music_GetTimestamp() * FRAMES_PER_SECOND * TICKS_PER_FRAME / 1000.0; + } + + return 0; +} + +void __cdecl CutscenePlayer_Control(const int16_t item_num) +{ + ITEM *const item = &g_Items[item_num]; + item->rot.y = g_Camera.target_angle; + item->pos.x = g_Camera.pos.pos.x; + item->pos.y = g_Camera.pos.pos.y; + item->pos.z = g_Camera.pos.pos.z; + + XYZ_32 pos = { 0 }; + Collide_GetJointAbsPosition(item, &pos, 0); + + const int16_t room_num = Room_FindByPos(pos.x, pos.y, pos.z); + if (room_num != NO_ROOM_NEG && item->room_num != room_num) { + Item_NewRoom(item_num, room_num); + } + + if (item->dynamic_light && item->status != IS_INVISIBLE) { + pos.x = 0; + pos.y = 0; + pos.z = 0; + Collide_GetJointAbsPosition(item, &pos, 0); + AddDynamicLight(pos.x, pos.y, pos.z, 12, 11); + } + + Item_Animate(item); +} + +void __cdecl Lara_Control_Cutscene(const int16_t item_num) +{ + ITEM *const item = &g_Items[item_num]; + item->rot.y = g_Camera.target_angle; + item->pos.x = g_Camera.pos.pos.x; + item->pos.y = g_Camera.pos.pos.y; + item->pos.z = g_Camera.pos.pos.z; + + XYZ_32 pos = { 0 }; + Collide_GetJointAbsPosition(item, &pos, 0); + + const int16_t room_num = Room_FindByPos(pos.x, pos.y, pos.z); + if (room_num != NO_ROOM_NEG && item->room_num != room_num) { + Item_NewRoom(item_num, room_num); + } + + Lara_Animate(item); +} + +void __cdecl CutscenePlayer1_Initialise(const int16_t item_num) +{ + OBJECT *const obj = &g_Objects[O_LARA]; + obj->draw_routine = Lara_Draw; + obj->control = Lara_Control_Cutscene; + + Item_AddActive(item_num); + ITEM *const item = &g_Items[item_num]; + g_Camera.pos.pos.x = item->pos.x; + g_Camera.pos.pos.y = item->pos.y; + g_Camera.pos.pos.z = item->pos.z; + g_Camera.target_angle = 0; + g_Camera.pos.room_num = item->room_num; + g_OriginalRoom = g_Camera.pos.room_num; + + item->rot.y = 0; + item->dynamic_light = 0; + item->goal_anim_state = 0; + item->current_anim_state = 0; + item->frame_num = 0; + item->anim_num = 0; + + g_Lara.hit_direction = -1; +} + +void __cdecl CutscenePlayerGen_Initialise(const int16_t item_num) +{ + Item_AddActive(item_num); + ITEM *const item = &g_Items[item_num]; + item->rot.y = 0; + item->dynamic_light = 0; +} + +int32_t __cdecl Level_Initialise( + const int32_t level_num, const GAMEFLOW_LEVEL_TYPE level_type) +{ + g_GameInfo.current_level.num = level_num; + g_GameInfo.current_level.type = level_type; + + if (level_type != GFL_TITLE && level_type != GFL_CUTSCENE) { + g_CurrentLevel = level_num; + } + g_IsDemoLevelType = level_type == GFL_DEMO; + InitialiseGameFlags(); + g_Lara.item_num = NO_ITEM; + g_IsTitleLoaded = false; + + bool result; + if (level_type == GFL_TITLE) { + result = S_LoadLevelFile(g_GF_TitleFileNames[0], level_num, level_type); + } else if (level_type == GFL_CUTSCENE) { + result = S_LoadLevelFile( + g_GF_CutsceneFileNames[level_num], level_num, level_type); + } else { + result = S_LoadLevelFile( + g_GF_LevelFileNames[level_num], level_num, level_type); + } + if (!result) { + return result; + } + + if (g_Lara.item_num != NO_ITEM) { + Lara_Initialise(level_type); + } + if (level_type == GFL_NORMAL || level_type == GFL_SAVED + || level_type == GFL_DEMO) { + GetCarriedItems(); + } + g_Effects = game_malloc(MAX_EFFECTS * sizeof(FX), GBUF_EFFECTS_ARRAY); + Effect_InitialiseArray(); + LOT_InitialiseArray(); + Inv_InitColors(); + Overlay_HideGameInfo(); + Overlay_InitialisePickUpDisplay(); + S_InitialiseScreen(level_type); + g_HealthBarTimer = 100; + Sound_StopAllSamples(); + if (level_type == GFL_SAVED) { + ExtractSaveGameInfo(); + } else if (level_type == GFL_NORMAL) { + GF_ModifyInventory(g_CurrentLevel, 0); + } + + if (g_Objects[O_FINAL_LEVEL_COUNTER].loaded) { + InitialiseFinalLevel(); + } + + if (level_type == GFL_NORMAL || level_type == GFL_SAVED + || level_type == GFL_DEMO) { + if (g_GF_MusicTracks[0]) { + Music_Play(g_GF_MusicTracks[0], 1); + } + } + g_IsAssaultTimerActive = 0; + g_IsAssaultTimerDisplay = 0; + g_Camera.underwater = 0; + return true; +} + +void __cdecl RestoreLostBuffers(void) +{ + if (g_PrimaryBufferSurface == NULL) { + Shell_ExitSystem("Oops... no front buffer"); + return; + } + + bool rebuild = false; + + if (FAILED(DDrawSurfaceRestoreLost( + g_PrimaryBufferSurface, NULL, g_SavedAppSettings.fullscreen))) { + rebuild = true; + } + + if ((g_SavedAppSettings.fullscreen + || g_SavedAppSettings.render_mode == RM_HARDWARE) + && FAILED(DDrawSurfaceRestoreLost( + g_BackBufferSurface, g_PrimaryBufferSurface, true))) { + rebuild = true; + } + + if (g_SavedAppSettings.triple_buffering + && FAILED(DDrawSurfaceRestoreLost( + g_ThirdBufferSurface, g_PrimaryBufferSurface, true))) { + rebuild = true; + } + + if (g_SavedAppSettings.render_mode == RM_SOFTWARE + && FAILED( + DDrawSurfaceRestoreLost(g_RenderBufferSurface, NULL, false))) { + rebuild = true; + } + + if (g_ZBufferSurface != NULL + && FAILED(DDrawSurfaceRestoreLost(g_ZBufferSurface, NULL, false))) { + rebuild = true; + } + + if (g_PictureBufferSurface != NULL + && FAILED( + DDrawSurfaceRestoreLost(g_PictureBufferSurface, NULL, false))) { + rebuild = true; + } + + if (rebuild && !g_IsGameToExit) { + ApplySettings(&g_SavedAppSettings); + if (g_SavedAppSettings.render_mode == RM_HARDWARE) + HWR_GetPageHandles(); + } +} + +void __cdecl CreateScreenBuffers(void) +{ + { + DDSDESC dsp = { + .dwSize = sizeof(DDSDESC), + .dwFlags = DDSD_BACKBUFFERCOUNT | DDSD_CAPS, + .dwBackBufferCount = (g_SavedAppSettings.triple_buffering) ? 2 : 1, + .ddsCaps.dwCaps = + DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX, + }; + if (g_SavedAppSettings.render_mode == RM_HARDWARE) { + dsp.ddsCaps.dwCaps |= DDSCAPS_3DDEVICE; + } + if (FAILED(DDrawSurfaceCreate(&dsp, &g_PrimaryBufferSurface))) { + Shell_ExitSystem("Failed to create primary screen buffer"); + } + WinVidClearBuffer(g_PrimaryBufferSurface, NULL, 0); + } + + { + DDSCAPS caps = { + .dwCaps = DDSCAPS_BACKBUFFER, + }; + if (FAILED(IDirectDrawSurface_GetAttachedSurface( + g_PrimaryBufferSurface, &caps, &g_BackBufferSurface))) { + Shell_ExitSystem("Failed to create back screen buffer"); + } + WinVidClearBuffer(g_BackBufferSurface, NULL, 0); + } + + if (g_SavedAppSettings.triple_buffering) { + DDSCAPS caps = { + .dwCaps = DDSCAPS_FLIP, + }; + if (FAILED(IDirectDrawSurface_GetAttachedSurface( + g_BackBufferSurface, &caps, &g_ThirdBufferSurface))) { + Shell_ExitSystem("Failed to create third screen buffer"); + } + WinVidClearBuffer(g_ThirdBufferSurface, NULL, 0); + } +} + +void __cdecl CreatePrimarySurface(void) +{ + if ((g_GameVid_IsVga && g_SavedAppSettings.render_mode == RM_HARDWARE) + || (!g_GameVid_IsVga + && g_SavedAppSettings.render_mode == RM_SOFTWARE)) { + Shell_ExitSystem("Wrong bit depth"); + } + + DDSDESC dsp = { + .dwSize = sizeof(DDSDESC), + .dwFlags = DDSD_CAPS, + .ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE, + }; + + if (FAILED(DDrawSurfaceCreate(&dsp, &g_PrimaryBufferSurface))) { + Shell_ExitSystem("Failed to create primary screen buffer"); + } +} + +void __cdecl CreateBackBuffer(void) +{ + DDSDESC dsp = { + .dwSize = sizeof(DDSDESC), + .dwFlags = DDSD_WIDTH|DDSD_HEIGHT|DDSD_CAPS, + .dwWidth = g_GameVid_BufWidth, + .dwHeight = g_GameVid_BufHeight, + .ddsCaps = { + .dwCaps = DDSCAPS_3DDEVICE|DDSCAPS_OFFSCREENPLAIN, + }, + }; + if (g_SavedAppSettings.render_mode == RM_HARDWARE) { + dsp.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY; + } + if (FAILED(DDrawSurfaceCreate(&dsp, &g_BackBufferSurface))) { + Shell_ExitSystem("Failed to create back screen buffer"); + } + WinVidClearBuffer(g_BackBufferSurface, 0, 0); +} + +void __cdecl CreateClipper(void) +{ + if (FAILED(IDirectDraw_CreateClipper(g_DDraw, 0, &g_DDrawClipper, NULL))) { + Shell_ExitSystem("Failed to create clipper"); + } + + if (FAILED(IDirectDrawClipper_SetHWnd( + g_DDrawClipper, 0, g_GameWindowHandle))) { + Shell_ExitSystem("Failed to attach clipper to the game window"); + } + + if (FAILED(IDirectDrawSurface_SetClipper( + g_PrimaryBufferSurface, g_DDrawClipper))) { + Shell_ExitSystem("Failed to attach clipper to the primary surface"); + } +} + +void __cdecl CreateWindowPalette(void) +{ + memset(g_WinVid_Palette, 0, sizeof(g_WinVid_Palette)); + + DWORD flags = DDPCAPS_8BIT; + if (g_GameVid_IsWindowedVGA) { + for (int32_t i = 0; i < 10; i++) { + g_WinVid_Palette[i].peFlags = PC_EXPLICIT; + g_WinVid_Palette[i].peRed = i; + } + for (int32_t i = 10; i < 246; i++) { + g_WinVid_Palette[i].peFlags = PC_NOCOLLAPSE | PC_RESERVED; + } + for (int32_t i = 246; i < 256; i++) { + g_WinVid_Palette[i].peFlags = PC_EXPLICIT; + g_WinVid_Palette[i].peRed = i; // TODO: i - 246? + } + } else { + for (int32_t i = 0; i < 256; i++) { + g_WinVid_Palette[i].peFlags = PC_RESERVED; + } + flags |= DDPCAPS_ALLOW256; + } + + if (FAILED(IDirectDraw_CreatePalette( + g_DDraw, flags, g_WinVid_Palette, &g_DDrawPalette, 0))) { + Shell_ExitSystem("Failed to create palette"); + } + + if (FAILED(IDirectDrawSurface_SetPalette( + g_PrimaryBufferSurface, g_DDrawPalette))) { + Shell_ExitSystem("Failed to attach palette to the primary buffer"); + } +} + +void __cdecl CreateZBuffer(void) +{ + if ((g_CurrentDisplayAdapter.hw_device_desc.dpcTriCaps.dwRasterCaps + & D3DPRASTERCAPS_ZBUFFERLESSHSR) + != 0) { + return; + } + + DDSDESC dsp = { + .dwSize = sizeof(DDSDESC), + .dwWidth = g_GameVid_BufWidth, + .dwHeight = g_GameVid_BufHeight, + .dwFlags = DDSD_ZBUFFERBITDEPTH | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS, + .dwZBufferBitDepth = GetZBufferDepth(), + .ddsCaps.dwCaps = DDSCAPS_ZBUFFER | DDSCAPS_VIDEOMEMORY, + }; + + if (FAILED(DDrawSurfaceCreate(&dsp, &g_ZBufferSurface))) { + Shell_ExitSystem("Failed to create z-buffer"); + } + + if (FAILED(IDirectDrawSurface_AddAttachedSurface( + g_BackBufferSurface, g_ZBufferSurface))) { + Shell_ExitSystem("Failed to attach z-buffer to the back buffer"); + } +} + +int32_t __cdecl GetZBufferDepth(void) +{ + const int32_t bit_depth_mask = + g_CurrentDisplayAdapter.hw_device_desc.dwDeviceZBufferBitDepth; + if (bit_depth_mask & DDBD_16) { + return 16; + } + if (bit_depth_mask & DDBD_24) { + return 24; + } + if (bit_depth_mask & DDBD_32) { + return 32; + } + return 8; +} + +void __cdecl CreateRenderBuffer(void) +{ + DDSDESC dsp = { + .dwSize = sizeof(DDSDESC), + .dwFlags = DDSD_WIDTH|DDSD_HEIGHT|DDSD_CAPS, + .dwWidth = g_GameVid_BufWidth, + .dwHeight = g_GameVid_BufHeight, + .ddsCaps = { + .dwCaps = DDSCAPS_SYSTEMMEMORY|DDSCAPS_OFFSCREENPLAIN, + }, + }; + + if (FAILED(DDrawSurfaceCreate(&dsp, &g_RenderBufferSurface))) { + Shell_ExitSystem("Failed to create render buffer"); + } + + if (!WinVidClearBuffer(g_RenderBufferSurface, NULL, 0)) { + Shell_ExitSystem("Failed to clear render buffer"); + } +} + +void __cdecl CreatePictureBuffer(void) +{ + DDSDESC dsp = { + .dwSize = sizeof(DDSDESC), + .dwFlags = DDSD_WIDTH|DDSD_HEIGHT|DDSD_CAPS, + .dwWidth = 640, + .dwHeight = 480, + .ddsCaps = { + .dwCaps = DDSCAPS_SYSTEMMEMORY|DDSCAPS_OFFSCREENPLAIN, + }, + }; + + if (FAILED(DDrawSurfaceCreate(&dsp, &g_PictureBufferSurface))) { + Shell_ExitSystem("Failed to create picture buffer"); + } +} + +void __cdecl ClearBuffers(DWORD flags, DWORD fill_color) +{ + RECT win_rect; + if (flags & CLRB_PHDWINSIZE) { + win_rect.left = g_PhdWinMinX; + win_rect.top = g_PhdWinMinY; + win_rect.right = g_PhdWinMinX + g_PhdWinWidth; + win_rect.bottom = g_PhdWinMinY + g_PhdWinHeight; + } else { + win_rect.left = 0; + win_rect.top = 0; + win_rect.right = g_GameVid_Width; + win_rect.bottom = g_GameVid_Height; + } + + if (g_SavedAppSettings.render_mode == RM_HARDWARE) { + DWORD d3d_clear_flags = 0; + if (flags & CLRB_BACK_BUFFER) { + d3d_clear_flags |= D3DCLEAR_TARGET; + } + + if (flags & CLRB_Z_BUFFER) { + d3d_clear_flags |= D3DCLEAR_ZBUFFER; + } + + if (d3d_clear_flags != 0) { + D3DRECT d3d_rect = { + .x1 = win_rect.left, + .y1 = win_rect.top, + .x2 = win_rect.right, + .y2 = win_rect.bottom, + }; + IDirect3DViewport_Clear(g_D3DView, 1, &d3d_rect, d3d_clear_flags); + } + } else { + if (flags & CLRB_BACK_BUFFER) { + WinVidClearBuffer(g_BackBufferSurface, &win_rect, fill_color); + } + } + + if (flags & CLRB_PRIMARY_BUFFER) { + WinVidClearBuffer(g_PrimaryBufferSurface, &win_rect, fill_color); + } + + if (flags & CLRB_THIRD_BUFFER) { + WinVidClearBuffer(g_ThirdBufferSurface, &win_rect, fill_color); + } + + if (flags & CLRB_RENDER_BUFFER) { + WinVidClearBuffer(g_RenderBufferSurface, &win_rect, fill_color); + } + + if (flags & CLRB_PICTURE_BUFFER) { + win_rect.left = 0; + win_rect.top = 0; + win_rect.right = 640; + win_rect.bottom = 480; + WinVidClearBuffer(g_PictureBufferSurface, &win_rect, fill_color); + } + + if (flags & CLRB_WINDOWED_PRIMARY_BUFFER) { + win_rect.left = g_GameWindowPositionX; + win_rect.top = g_GameWindowPositionY; + win_rect.right = g_GameWindowPositionX + g_GameWindowWidth; + win_rect.bottom = g_GameWindowPositionY + g_GameWindowHeight; + WinVidClearBuffer(g_PrimaryBufferSurface, &win_rect, fill_color); + } +} + +void __cdecl UpdateFrame(const bool need_run_message_loop, LPRECT rect) +{ + if (rect == NULL) { + rect = &g_GameVid_Rect; + } + + RestoreLostBuffers(); + if (g_SavedAppSettings.fullscreen) { + if (g_SavedAppSettings.render_mode == RM_SOFTWARE) { + IDirectDrawSurface_Blt( + g_BackBufferSurface, rect, g_RenderBufferSurface, rect, + DDBLT_WAIT, NULL); + } + IDirectDrawSurface_Flip(g_PrimaryBufferSurface, NULL, DDFLIP_WAIT); + } else { + RECT dst_rect; + dst_rect.left = g_GameWindowPositionX + rect->left; + dst_rect.top = g_GameWindowPositionY + rect->top; + dst_rect.bottom = g_GameWindowPositionY + rect->bottom; + dst_rect.right = g_GameWindowPositionX + rect->right; + LPDDS dst_surface = g_SavedAppSettings.render_mode == RM_SOFTWARE + ? g_RenderBufferSurface + : g_BackBufferSurface; + IDirectDrawSurface_Blt( + g_PrimaryBufferSurface, &dst_rect, dst_surface, rect, DDBLT_WAIT, + NULL); + } + + if (need_run_message_loop) { + WinVidSpinMessageLoop(false); + } +} + +void __cdecl WaitPrimaryBufferFlip(void) +{ + if (g_SavedAppSettings.flip_broken && g_SavedAppSettings.fullscreen) { + while (IDirectDrawSurface_GetFlipStatus( + g_PrimaryBufferSurface, DDGFS_ISFLIPDONE) + == DDERR_WASSTILLDRAWING) { } + } +} + +bool __cdecl RenderInit(void) +{ + return true; +} + +void __cdecl RenderStart(const bool is_reset) +{ + if (is_reset) { + g_NeedToReloadTextures = false; + } + + if (g_SavedAppSettings.fullscreen) { + assert(g_SavedAppSettings.video_mode != NULL); + + DISPLAY_MODE disp_mode = g_SavedAppSettings.video_mode->body; + + const bool result = WinVidGoFullScreen(&disp_mode); + assert(result); + + CreateScreenBuffers(); + g_GameVid_Width = disp_mode.width; + g_GameVid_Height = disp_mode.height; + g_GameVid_BPP = disp_mode.bpp; + g_GameVid_BufWidth = disp_mode.width; + g_GameVid_BufHeight = disp_mode.height; + g_GameVid_IsVga = disp_mode.vga != VGA_NO_VGA; + g_GameVid_IsWindowedVGA = false; + g_GameVid_IsFullscreenVGA = disp_mode.vga == VGA_STANDARD; + } else { + int32_t min_width = 320; + int32_t min_height = CalculateWindowHeight(320, 200); + if (min_height < 200) { + min_width = CalculateWindowWidth(320, 200); + min_height = 200; + } + + WinVidSetMinWindowSize(min_width, min_height); + + DISPLAY_MODE disp_mode; + const bool result = WinVidGoWindowed( + g_SavedAppSettings.window_width, g_SavedAppSettings.window_height, + &disp_mode); + assert(result); + + g_GameVid_Width = disp_mode.width; + g_GameVid_Height = disp_mode.height; + g_GameVid_BPP = disp_mode.bpp; + + g_GameVid_BufWidth = (disp_mode.width + 0x1F) & ~0x1F; + g_GameVid_BufHeight = (disp_mode.height + 0x1F) & ~0x1F; + g_GameVid_IsVga = disp_mode.vga != 0; + g_GameVid_IsWindowedVGA = disp_mode.vga != VGA_NO_VGA; + g_GameVid_IsFullscreenVGA = false; + + CreatePrimarySurface(); + if (g_SavedAppSettings.render_mode == RM_HARDWARE) { + CreateBackBuffer(); + } + CreateClipper(); + } + + DDPIXELFORMAT pixel_format = { 0 }; + pixel_format.dwSize = sizeof(DDPIXELFORMAT); + const HRESULT result = IDirectDrawSurface_GetPixelFormat( + g_PrimaryBufferSurface, &pixel_format); + if (FAILED(result)) { + Shell_ExitSystem("GetPixelFormat() failed"); + } + + WinVidGetColorBitMasks(&g_ColorBitMasks, &pixel_format); + if (g_GameVid_IsVga) { + CreateWindowPalette(); + } + + if (g_SavedAppSettings.render_mode == RM_HARDWARE) { + if (g_SavedAppSettings.zbuffer) { + CreateZBuffer(); + } + D3DDeviceCreate(g_BackBufferSurface); + EnumerateTextureFormats(); + } else { + CreateRenderBuffer(); + if (g_PictureBufferSurface == NULL) { + CreatePictureBuffer(); + } + } + + if (g_NeedToReloadTextures) { + bool is_16bit_textures = g_TextureFormat.bpp >= 16; + if (g_IsWindowedVGA != g_GameVid_IsWindowedVGA + || g_Is16bitTextures != is_16bit_textures) { + S_ReloadLevelGraphics( + g_IsWindowedVGA != g_GameVid_IsWindowedVGA, + g_Is16bitTextures != is_16bit_textures); + g_IsWindowedVGA = g_GameVid_IsWindowedVGA; + g_Is16bitTextures = is_16bit_textures; + } else if (g_SavedAppSettings.render_mode == RM_HARDWARE) { + ReloadTextures(true); + HWR_GetPageHandles(); + } + } else { + g_IsWindowedVGA = g_GameVid_IsWindowedVGA; + g_Is16bitTextures = g_TextureFormat.bpp >= 16; + } + + g_GameVid_BufRect.left = 0; + g_GameVid_BufRect.top = 0; + g_GameVid_BufRect.right = g_GameVid_BufWidth; + g_GameVid_BufRect.bottom = g_GameVid_BufHeight; + + g_GameVid_Rect.left = 0; + g_GameVid_Rect.top = 0; + g_GameVid_Rect.right = g_GameVid_Width; + g_GameVid_Rect.bottom = g_GameVid_Height; + + g_DumpWidth = g_GameVid_Width; + g_DumpHeight = g_GameVid_Height; + + setup_screen_size(); + g_NeedToReloadTextures = true; +} + +void __cdecl RenderFinish(bool need_to_clear_textures) +{ + if (g_SavedAppSettings.render_mode == RM_HARDWARE) { + if (need_to_clear_textures) { + HWR_FreeTexturePages(); + CleanupTextures(); + } + + Direct3DRelease(); + if (g_ZBufferSurface != NULL) { + IDirectDrawSurface_Release(g_ZBufferSurface); + g_ZBufferSurface = NULL; + } + } else { + if (need_to_clear_textures && g_PictureBufferSurface != NULL) { + IDirectDrawSurface_Release(g_PictureBufferSurface); + g_PictureBufferSurface = NULL; + } + + if (g_RenderBufferSurface != NULL) { + IDirectDrawSurface_Release(g_RenderBufferSurface); + g_RenderBufferSurface = NULL; + } + } + + if (g_DDrawPalette != NULL) { + IDirectDrawPalette_Release(g_DDrawPalette); + g_DDrawPalette = NULL; + } + + if (g_DDrawClipper != NULL) { + IDirectDrawClipper_Release(g_DDrawClipper); + g_DDrawClipper = NULL; + } + + if (g_ThirdBufferSurface != NULL) { + IDirectDrawSurface_Release(g_ThirdBufferSurface); + g_ThirdBufferSurface = NULL; + } + + if (g_BackBufferSurface != NULL) { + IDirectDrawSurface_Release(g_BackBufferSurface); + g_BackBufferSurface = NULL; + } + + if (g_PrimaryBufferSurface != NULL) { + IDirectDrawSurface_Release(g_PrimaryBufferSurface); + g_PrimaryBufferSurface = NULL; + } + + if (need_to_clear_textures) { + g_NeedToReloadTextures = false; + } +} + +bool __cdecl ApplySettings(const APP_SETTINGS *const new_settings) +{ + char mode_string[64] = { 0 }; + APP_SETTINGS old_settings = g_SavedAppSettings; + + RenderFinish(false); + + if (new_settings != &g_SavedAppSettings) + g_SavedAppSettings = *new_settings; + + RenderStart(false); + S_InitialiseScreen(GFL_NO_LEVEL); + + if (g_SavedAppSettings.render_mode != old_settings.render_mode) { + S_ReloadLevelGraphics(1, 1); + } else if ( + g_SavedAppSettings.render_mode == RM_SOFTWARE + && g_SavedAppSettings.fullscreen != old_settings.fullscreen) { + S_ReloadLevelGraphics(1, 0); + } + + if (g_SavedAppSettings.fullscreen) { + sprintf( + mode_string, "%dx%dx%d", g_GameVid_Width, g_GameVid_Height, + g_GameVid_BPP); + } else { + sprintf(mode_string, "%dx%d", g_GameVid_Width, g_GameVid_Height); + } + + Overlay_DisplayModeInfo(mode_string); + return true; +} + +void __cdecl FmvBackToGame(void) +{ + RenderStart(true); +} + +void __cdecl GameApplySettings(APP_SETTINGS *const new_settings) +{ + bool need_init_render_state = false; + bool need_adjust_texel = false; + bool need_rebuild_buffers = false; + + if (new_settings->preferred_display_adapter + != g_SavedAppSettings.preferred_display_adapter + || new_settings->preferred_sound_adapter + != g_SavedAppSettings.preferred_sound_adapter + || new_settings->preferred_joystick + != g_SavedAppSettings.preferred_joystick) { + return; + } + + if (new_settings->render_mode != g_SavedAppSettings.render_mode + || new_settings->video_mode != g_SavedAppSettings.video_mode + || new_settings->fullscreen != g_SavedAppSettings.fullscreen + || new_settings->zbuffer != g_SavedAppSettings.zbuffer + || new_settings->triple_buffering + != g_SavedAppSettings.triple_buffering) { + ApplySettings(new_settings); + S_AdjustTexelCoordinates(); + return; + } + + if (new_settings->perspective_correct + != g_SavedAppSettings.perspective_correct + || new_settings->dither != g_SavedAppSettings.dither + || new_settings->bilinear_filtering + != g_SavedAppSettings.bilinear_filtering) { + need_init_render_state = true; + } + + if (new_settings->bilinear_filtering + != g_SavedAppSettings.bilinear_filtering + || new_settings->render_mode != g_SavedAppSettings.render_mode) { + need_adjust_texel = true; + } + + if (!new_settings->fullscreen) { + if (new_settings->window_width != g_SavedAppSettings.window_width + || new_settings->window_height + != g_SavedAppSettings.window_height) { + DISPLAY_MODE disp_mode; + if (!WinVidGoWindowed( + new_settings->window_width, new_settings->window_height, + &disp_mode)) { + return; + } + new_settings->window_width = disp_mode.width; + new_settings->window_height = disp_mode.height; + if (new_settings->window_width != g_SavedAppSettings.window_width + || new_settings->window_height + != g_SavedAppSettings.window_height) { + if (g_GameVid_BufWidth - new_settings->window_width < 0 + || g_GameVid_BufWidth - new_settings->window_width > 64 + || g_GameVid_BufHeight - new_settings->window_height < 0 + || g_GameVid_BufHeight - new_settings->window_height > 64) { + need_rebuild_buffers = true; + } else { + g_SavedAppSettings.window_width = + new_settings->window_width; + g_SavedAppSettings.window_height = + new_settings->window_height; + g_GameVid_Width = new_settings->window_width; + g_GameVid_Height = new_settings->window_height; + g_GameVid_Rect.right = new_settings->window_width; + g_GameVid_Rect.bottom = new_settings->window_height; + if (g_SavedAppSettings.render_mode == RM_HARDWARE) { + D3DSetViewport(); + } + setup_screen_size(); + g_WinVidNeedToResetBuffers = false; + } + } + } + } + + if (need_init_render_state) { + g_SavedAppSettings.perspective_correct = + new_settings->perspective_correct; + g_SavedAppSettings.dither = new_settings->dither; + g_SavedAppSettings.bilinear_filtering = + new_settings->bilinear_filtering; + if (g_SavedAppSettings.render_mode == RM_HARDWARE) { + HWR_InitState(); + } + } + + if (need_rebuild_buffers) { + ClearBuffers(CLRB_WINDOWED_PRIMARY_BUFFER, 0); + ApplySettings(new_settings); + } + + if (need_adjust_texel) { + S_AdjustTexelCoordinates(); + } +} + +void __cdecl UpdateGameResolution(void) +{ + APP_SETTINGS new_settings = g_SavedAppSettings; + new_settings.window_width = g_GameWindowWidth; + new_settings.window_height = g_GameWindowHeight; + GameApplySettings(&new_settings); + char mode_string[64] = { 0 }; + sprintf(mode_string, "%dx%d", g_GameVid_Width, g_GameVid_Height); + Overlay_DisplayModeInfo(mode_string); +} + +bool __cdecl D3DCreate(void) +{ + const HRESULT rc = + IDirectDraw_QueryInterface(g_DDraw, &IID_IDirect3D2, (LPVOID *)&g_D3D); + return SUCCEEDED(rc); +} + +void __cdecl D3DRelease(void) +{ + if (g_D3D != NULL) { + IDirect3D_Release(g_D3D); + g_D3D = NULL; + } +} + +void __cdecl Enumerate3DDevices(DISPLAY_ADAPTER *const adapter) +{ + if (D3DCreate()) { + g_D3D->lpVtbl->EnumDevices( + g_D3D, (void *)Enum3DDevicesCallback, (LPVOID)adapter); + D3DRelease(); + } +} + +HRESULT __stdcall Enum3DDevicesCallback( + GUID FAR *lpGuid, LPTSTR lpDeviceDescription, LPTSTR lpDeviceName, + LPD3DDEVICEDESC_V2 lpD3DHWDeviceDesc, LPD3DDEVICEDESC_V2 lpD3DHELDeviceDesc, + LPVOID lpContext) +{ + DISPLAY_ADAPTER *adapter = (DISPLAY_ADAPTER *)lpContext; + + if (lpD3DHWDeviceDesc != NULL && D3DIsSupported(lpD3DHWDeviceDesc)) { + adapter->hw_render_supported = true; + adapter->device_guid = *lpGuid; + adapter->hw_device_desc = *lpD3DHWDeviceDesc; + + adapter->perspective_correct_supported = + (lpD3DHWDeviceDesc->dpcTriCaps.dwTextureCaps + & D3DPTEXTURECAPS_PERSPECTIVE) + ? true + : false; + adapter->dither_supported = + (lpD3DHWDeviceDesc->dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_DITHER) + ? true + : false; + adapter->zbuffer_supported = + (lpD3DHWDeviceDesc->dwDeviceZBufferBitDepth) ? true : false; + adapter->linear_filter_supported = + (lpD3DHWDeviceDesc->dpcTriCaps.dwTextureFilterCaps + & D3DPTFILTERCAPS_LINEAR) + ? true + : false; + adapter->shade_restricted = + (lpD3DHWDeviceDesc->dpcTriCaps.dwShadeCaps + & (D3DPSHADECAPS_ALPHAGOURAUDBLEND | D3DPSHADECAPS_ALPHAFLATBLEND)) + ? false + : true; + } + + return D3DENUMRET_OK; +} + +bool __cdecl D3DIsSupported(LPD3DDEVICEDESC_V2 desc) +{ + return (desc->dwFlags & D3DDD_COLORMODEL) + && (desc->dcmColorModel & D3DCOLOR_RGB) + && (desc->dwFlags & D3DDD_TRICAPS) + && (desc->dpcTriCaps.dwShadeCaps & D3DPSHADECAPS_COLORGOURAUDRGB) + && (desc->dpcTriCaps.dwTextureBlendCaps & D3DPTBLENDCAPS_MODULATE); +} + +bool __cdecl D3DSetViewport(void) +{ + D3DVIEWPORT2 viewPort = { + + .dwSize = sizeof(D3DVIEWPORT2), + .dvClipX = 0.0, + .dvClipY = 0.0, + .dvClipWidth = (float)g_GameVid_Width, + .dvClipHeight = (float)g_GameVid_Height, + + .dwX = 0, + .dwY = 0, + .dwWidth = g_GameVid_Width, + .dwHeight = g_GameVid_Height, + + .dvMinZ = 0.0, + .dvMaxZ = 1.0, + }; + + HRESULT rc = IDirect3DViewport2_SetViewport2(g_D3DView, &viewPort); + if (FAILED(rc)) { + IDirect3DViewport2_GetViewport2(g_D3DView, &viewPort); + return false; + } + + rc = IDirect3DDevice2_SetCurrentViewport(g_D3DDev, g_D3DView); + return SUCCEEDED(rc); +} + +void __cdecl D3DDeviceCreate(LPDDS lpBackBuffer) +{ + if (g_D3D == NULL && !D3DCreate()) { + Shell_ExitSystem("Failed to create D3D"); + } + + if (FAILED(g_D3D->lpVtbl->CreateDevice( + g_D3D, &IID_IDirect3DHALDevice, (LPDIRECTDRAWSURFACE)lpBackBuffer, + &g_D3DDev))) { + Shell_ExitSystem("Failed to create device"); + } + + if (FAILED(g_D3D->lpVtbl->CreateViewport(g_D3D, &g_D3DView, NULL))) { + Shell_ExitSystem("Failed to create viewport"); + } + + if (FAILED(g_D3DDev->lpVtbl->AddViewport(g_D3DDev, g_D3DView))) { + Shell_ExitSystem("Failed to add viewport"); + } + + if (FAILED(!D3DSetViewport())) { + Shell_ExitSystem("Failed to set viewport"); + } + + if (FAILED(g_D3D->lpVtbl->CreateMaterial(g_D3D, &g_D3DMaterial, NULL))) { + Shell_ExitSystem("Failed to create material"); + } + + D3DMATERIALHANDLE mat_handle; + D3DMATERIAL mat_data = { 0 }; + mat_data.dwSize = sizeof(mat_data); + + if (FAILED(g_D3DMaterial->lpVtbl->SetMaterial(g_D3DMaterial, &mat_data))) { + Shell_ExitSystem("Failed to set material"); + } + + if (FAILED(g_D3DMaterial->lpVtbl->GetHandle( + g_D3DMaterial, g_D3DDev, &mat_handle))) { + Shell_ExitSystem("Failed to get material handle"); + } + + if (FAILED(g_D3DView->lpVtbl->SetBackground(g_D3DView, mat_handle))) { + Shell_ExitSystem("Failed to set material background"); + } +} + +void __cdecl Direct3DRelease(void) +{ + if (g_D3DMaterial != NULL) { + g_D3DMaterial->lpVtbl->Release(g_D3DMaterial); + g_D3DMaterial = NULL; + } + + if (g_D3DView != NULL) { + g_D3DView->lpVtbl->Release(g_D3DView); + g_D3DView = NULL; + } + + if (g_D3DDev != NULL) { + g_D3DDev->lpVtbl->Release(g_D3DDev); + g_D3DDev = NULL; + } + + D3DRelease(); +} + +bool __cdecl Direct3DInit(void) +{ + return true; +} + +bool __cdecl DDrawCreate(LPGUID lpGUID) +{ + if (FAILED(DirectDrawCreate(lpGUID, &g_DDrawInterface, 0))) { + return false; + } + + if (FAILED(g_DDrawInterface->lpVtbl->QueryInterface( + g_DDrawInterface, &IID_IDirectDraw2, (LPVOID *)&g_DDraw))) { + return false; + } + + g_DDraw->lpVtbl->SetCooperativeLevel( + g_DDraw, g_GameWindowHandle, DDSCL_NORMAL); + return true; +} + +void __cdecl DDrawRelease(void) +{ + if (g_DDraw != NULL) { + g_DDraw->lpVtbl->Release(g_DDraw); + g_DDraw = NULL; + } + if (g_DDrawInterface != NULL) { + g_DDrawInterface->lpVtbl->Release(g_DDrawInterface); + g_DDrawInterface = NULL; + } +} + +void __cdecl GameWindowCalculateSizeFromClient( + int32_t *const width, int32_t *const height) +{ + RECT rect = { 0, 0, *width, *height }; + const DWORD style = GetWindowLong(g_GameWindowHandle, GWL_STYLE); + const DWORD style_ex = GetWindowLong(g_GameWindowHandle, GWL_EXSTYLE); + AdjustWindowRectEx(&rect, style, FALSE, style_ex); + *width = rect.right - rect.left; + *height = rect.bottom - rect.top; +} + +void __cdecl GameWindowCalculateSizeFromClientByZero( + int32_t *const width, int32_t *const height) +{ + RECT rect = { 0, 0, 0, 0 }; + const DWORD style = GetWindowLong(g_GameWindowHandle, GWL_STYLE); + const DWORD styleEx = GetWindowLong(g_GameWindowHandle, GWL_EXSTYLE); + AdjustWindowRectEx(&rect, style, FALSE, styleEx); + *width += rect.left - rect.right; + *height += rect.top - rect.bottom; +} + +bool __thiscall CompareVideoModes( + const DISPLAY_MODE *const mode1, const DISPLAY_MODE *const mode2) +{ + const int32_t square1 = mode1->width * mode1->height; + const int32_t square2 = mode2->width * mode2->height; + if (square1 < square2) { + return true; + } + if (square1 > square2) { + return false; + } + if (mode1->bpp < mode2->bpp) { + return true; + } + if (mode1->bpp > mode2->bpp) { + return false; + } + if (mode1->vga < mode2->vga) { + return true; + } + if (mode1->vga > mode2->vga) { + return false; + } + return false; +} + +bool __cdecl WinVidGetDisplayModes(void) +{ + for (DISPLAY_ADAPTER_NODE *adapter = g_DisplayAdapterList.head; adapter; + adapter = adapter->next) { + DDrawCreate(adapter->body.adapter_guid_ptr); + ShowDDrawGameWindow(false); + g_DDraw->lpVtbl->EnumDisplayModes( + g_DDraw, DDEDM_STANDARDVGAMODES, NULL, (LPVOID)&adapter->body, + EnumDisplayModesCallback); + HideDDrawGameWindow(); + DDrawRelease(); + } + return true; +} + +HRESULT __stdcall EnumDisplayModesCallback( + LPDDSDESC lpDDSurfaceDesc, LPVOID lpContext) +{ + DISPLAY_ADAPTER *adapter = (DISPLAY_ADAPTER *)lpContext; + VGA_MODE vga_mode = VGA_NO_VGA; + bool sw_renderer_supported = false; + + if (!(lpDDSurfaceDesc->dwFlags & DDSD_HEIGHT) + || !(lpDDSurfaceDesc->dwFlags & DDSD_WIDTH) + || !(lpDDSurfaceDesc->dwFlags & DDSD_PIXELFORMAT) + || !(lpDDSurfaceDesc->ddpfPixelFormat.dwFlags & DDPF_RGB)) { + return DDENUMRET_OK; + } + + if ((lpDDSurfaceDesc->ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) != 0 + && lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount == 8) { + if (lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_MODEX) { + vga_mode = VGA_MODEX; + } else if (lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_STANDARDVGAMODE) { + vga_mode = VGA_STANDARD; + } else { + vga_mode = VGA_256_COLOR; + } + + if (lpDDSurfaceDesc->dwWidth == 320 && lpDDSurfaceDesc->dwHeight == 200 + && (!adapter->is_vga_mode1_presented + || vga_mode < adapter->vga_mode1.vga)) { + adapter->vga_mode1.width = 320; + adapter->vga_mode1.height = 200; + adapter->vga_mode1.bpp = 8; + adapter->vga_mode1.vga = vga_mode; + adapter->is_vga_mode1_presented = true; + } + + if (lpDDSurfaceDesc->dwWidth == 640 && lpDDSurfaceDesc->dwHeight == 480 + && (!adapter->is_vga_mode2_presented + || vga_mode < adapter->vga_mode2.vga)) { + adapter->vga_mode2.width = 640; + adapter->vga_mode2.height = 480; + adapter->vga_mode2.bpp = 8; + adapter->vga_mode2.vga = vga_mode; + adapter->is_vga_mode2_presented = true; + } + sw_renderer_supported = true; + } + + DISPLAY_MODE video_mode = { + .width = lpDDSurfaceDesc->dwWidth, + .height = lpDDSurfaceDesc->dwHeight, + .bpp = lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount, + .vga = vga_mode, + }; + + int32_t render_bit_depth = + GetRenderBitDepth(lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount); + + if (adapter->hw_render_supported + && (render_bit_depth & adapter->hw_device_desc.dwDeviceRenderBitDepth) + != 0) { + M_InsertDisplayModeInListSorted( + &adapter->hw_disp_mode_list, &video_mode); + } + + if (sw_renderer_supported) { + M_InsertDisplayModeInListSorted( + &adapter->sw_disp_mode_list, &video_mode); + } + + return DDENUMRET_OK; +} + +bool __cdecl WinVidInit(void) +{ + g_AppResultCode = 0; + // clang-format off + return WinVidRegisterGameWindowClass() + && WinVidCreateGameWindow() + && WinVidGetDisplayAdapters() + && g_DisplayAdapterList.count + && WinVidGetDisplayModes(); + // clang-format on +} + +bool __cdecl WinVidGetDisplayAdapters(void) +{ + for (DISPLAY_ADAPTER_NODE *node = g_DisplayAdapterList.head, + *next_node = NULL; + node != NULL; node = next_node) { + next_node = node->next; + M_DisplayModeListDelete(&node->body.sw_disp_mode_list); + M_DisplayModeListDelete(&node->body.hw_disp_mode_list); + S_FlaggedString_Delete(&node->body.driver_name); + S_FlaggedString_Delete(&node->body.driver_desc); + free(node); + } + + g_DisplayAdapterList.head = NULL; + g_DisplayAdapterList.tail = NULL; + g_DisplayAdapterList.count = 0; + + g_PrimaryDisplayAdapter = NULL; + + if (!EnumerateDisplayAdapters(&g_DisplayAdapterList)) { + return false; + } + + for (DISPLAY_ADAPTER_NODE *node = g_DisplayAdapterList.head; node != NULL; + node = node->next) { + if (node->body.adapter_guid_ptr == NULL) { + g_PrimaryDisplayAdapter = node; + return true; + } + } + return false; +} + +bool __cdecl EnumerateDisplayAdapters( + DISPLAY_ADAPTER_LIST *display_adapter_list) +{ + return SUCCEEDED(DirectDrawEnumerate( + EnumDisplayAdaptersCallback, (LPVOID)display_adapter_list)); +} + +BOOL WINAPI EnumDisplayAdaptersCallback( + GUID FAR *lpGUID, LPTSTR lpDriverDescription, LPTSTR lpDriverName, + LPVOID lpContext) +{ + DISPLAY_ADAPTER_NODE *list_node = malloc(sizeof(DISPLAY_ADAPTER_NODE)); + DISPLAY_ADAPTER_LIST *adapter_list = (DISPLAY_ADAPTER_LIST *)lpContext; + + if (list_node == NULL || !DDrawCreate(lpGUID)) { + return TRUE; + } + + DDCAPS_DX5 driver_caps = { .dwSize = sizeof(DDCAPS_DX5), 0 }; + DDCAPS_DX5 hel_caps = { .dwSize = sizeof(DDCAPS_DX5), 0 }; + if (FAILED(g_DDraw->lpVtbl->GetCaps( + g_DDraw, (void *)&driver_caps, (void *)&hel_caps))) { + goto cleanup; + } + + list_node->next = NULL; + list_node->previous = adapter_list->tail; + + S_FlaggedString_InitAdapter(&list_node->body); + M_DisplayModeListInit(&list_node->body.hw_disp_mode_list); + M_DisplayModeListInit(&list_node->body.sw_disp_mode_list); + + if (!adapter_list->head) { + adapter_list->head = list_node; + } + + if (adapter_list->tail) { + adapter_list->tail->next = list_node; + } + + adapter_list->tail = list_node; + adapter_list->count++; + + if (lpGUID != NULL) { + list_node->body.adapter_guid = *lpGUID; + list_node->body.adapter_guid_ptr = &list_node->body.adapter_guid; + } else { + memset(&list_node->body.adapter_guid, 0, sizeof(GUID)); + list_node->body.adapter_guid_ptr = NULL; + } + + lstrcpy(list_node->body.driver_desc.content, lpDriverDescription); + lstrcpy(list_node->body.driver_name.content, lpDriverName); + + list_node->body.driver_caps = driver_caps; + list_node->body.hel_caps = hel_caps; + + list_node->body.screen_width = 0; + list_node->body.hw_render_supported = false; + list_node->body.sw_windowed_supported = false; + list_node->body.hw_windowed_supported = false; + list_node->body.is_vga_mode1_presented = false; + list_node->body.is_vga_mode2_presented = false; + + Enumerate3DDevices(&list_node->body); + +cleanup: + DDrawRelease(); + return TRUE; +} + +bool __cdecl WinVidRegisterGameWindowClass(void) +{ + WNDCLASSEXA wnd_class = { + .cbSize = sizeof(WNDCLASSEXA), + .style = 0, + .lpfnWndProc = WinVidGameWindowProc, + .hInstance = g_GameModule, + .hIcon = LoadIcon(g_GameModule, MAKEINTRESOURCE(IDI_MAINICON)), + .hCursor = LoadCursor(NULL, IDC_ARROW), + .lpszClassName = g_GameClassName, + 0, + }; + return RegisterClassExA(&wnd_class) != 0; +} + +LRESULT CALLBACK +WinVidGameWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + if (g_IsFMVPlaying) { + switch (Msg) { + case WM_DESTROY: + g_IsGameWindowCreated = false; + g_GameWindowHandle = NULL; + PostQuitMessage(0); + break; + + case WM_MOVE: + g_GameWindowPositionX = LOWORD(lParam); + g_GameWindowPositionY = HIWORD(lParam); + break; + + case WM_ACTIVATEAPP: + g_IsGameWindowActive = wParam != 0; + break; + + case WM_SYSCOMMAND: + if (wParam == SC_KEYMENU) { + return 0; + } + break; + } + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + switch (Msg) { + case WM_CREATE: + g_IsGameWindowCreated = true; + break; + + case WM_DESTROY: + g_IsGameWindowCreated = false; + g_GameWindowHandle = NULL; + PostQuitMessage(0); + break; + + case WM_MOVE: + g_GameWindowPositionX = LOWORD(lParam); + g_GameWindowPositionY = HIWORD(lParam); + break; + + case WM_SIZE: + switch (wParam) { + case SIZE_RESTORED: + g_IsGameWindowMinimized = false; + g_IsGameWindowMaximized = false; + break; + + case SIZE_MAXIMIZED: + g_IsGameWindowMinimized = false; + g_IsGameWindowMaximized = true; + break; + + case SIZE_MINIMIZED: + g_IsGameWindowMinimized = true; + g_IsGameWindowMaximized = false; + return DefWindowProc(hWnd, Msg, wParam, lParam); + + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + if (g_IsGameFullScreen + || (LOWORD(lParam) == g_GameWindowWidth + && HIWORD(lParam) == g_GameWindowHeight)) { + break; + } + + g_GameWindowWidth = LOWORD(lParam); + g_GameWindowHeight = HIWORD(lParam); + if (g_IsGameWindowUpdating) { + break; + } + + UpdateGameResolution(); + break; + + case WM_PAINT: { + PAINTSTRUCT paint; + HDC hdc = BeginPaint(hWnd, &paint); + LPDDS surface = (g_SavedAppSettings.render_mode == RM_SOFTWARE) + ? g_RenderBufferSurface + : g_BackBufferSurface; + if (g_IsGameFullScreen || !g_PrimaryBufferSurface || !surface) { + HBRUSH brush = (HBRUSH)GetStockObject(BLACK_BRUSH); + FillRect(hdc, &paint.rcPaint, brush); + } else { + if (g_SavedAppSettings.render_mode == RM_SOFTWARE + && !WinVidCheckGameWindowPalette(hWnd) + && g_RenderBufferSurface) { + WinVidClearBuffer(g_RenderBufferSurface, NULL, 0); + } + UpdateFrame(false, NULL); + } + EndPaint(hWnd, &paint); + return 0; + } + + case WM_ACTIVATE: + if (LOWORD(wParam) && g_DDrawPalette != NULL + && g_PrimaryBufferSurface != NULL) { + g_PrimaryBufferSurface->lpVtbl->SetPalette( + g_PrimaryBufferSurface, g_DDrawPalette); + } + break; + + case WM_ERASEBKGND: + return 1; + + case WM_ACTIVATEAPP: + if (wParam && !g_IsGameWindowActive && g_IsGameFullScreen + && g_SavedAppSettings.render_mode == RM_HARDWARE) { + g_WinVidNeedToResetBuffers = true; + } + g_IsGameWindowActive = (wParam != 0); + break; + + case WM_SETCURSOR: + if (g_IsGameFullScreen) { + SetCursor(NULL); + return 1; + } + break; + + case WM_GETMINMAXINFO: + if (WinVidGetMinMaxInfo((LPMINMAXINFO)lParam)) { + return 0; + } + break; + + case WM_NCPAINT: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONDBLCLK: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONDBLCLK: + if (g_IsGameFullScreen) { + return 0; + } + break; + + case WM_SYSCOMMAND: + if (wParam == SC_KEYMENU) { + return 0; + } + break; + + case WM_SIZING: + WinVidResizeGameWindow(hWnd, wParam, (LPRECT)lParam); + break; + + case WM_MOVING: + if (g_IsGameFullScreen || g_IsGameWindowMaximized) { + GetWindowRect(hWnd, (LPRECT)lParam); + return 1; + } + break; + + case WM_ENTERSIZEMOVE: + g_IsGameWindowChanging = true; + break; + + case WM_EXITSIZEMOVE: + g_IsGameWindowChanging = false; + break; + + case WM_PALETTECHANGED: + if (hWnd != (HWND)wParam && !g_IsGameFullScreen && g_DDrawPalette) { + InvalidateRect(hWnd, NULL, FALSE); + } + break; + } + + return DefWindowProc(hWnd, Msg, wParam, lParam); +} + +void __cdecl WinVidResizeGameWindow(HWND hWnd, int32_t edge, LPRECT rect) +{ + if (g_IsGameFullScreen) { + rect->left = 0; + rect->top = 0; + rect->right = g_FullScreenWidth; + rect->bottom = g_FullScreenHeight; + } + + const bool is_shift_pressed = GetAsyncKeyState(VK_SHIFT) < 0; + int32_t width = rect->right - rect->left; + int32_t height = rect->bottom - rect->top; + GameWindowCalculateSizeFromClientByZero(&width, &height); + + if (edge == WMSZ_TOP || edge == WMSZ_BOTTOM) { + if (is_shift_pressed) { + height &= ~0x1F; + } + width = CalculateWindowWidth(width, height); + } else { + if (is_shift_pressed) { + width &= ~0x1F; + } + height = CalculateWindowHeight(width, height); + } + + if (g_IsMinWindowSizeSet) { + CLAMPL(width, g_MinWindowClientWidth); + CLAMPL(height, g_MinWindowClientHeight); + } + + if (g_IsMaxWindowSizeSet) { + CLAMPG(width, g_MaxWindowClientWidth); + CLAMPG(height, g_MaxWindowClientHeight); + } + + GameWindowCalculateSizeFromClient(&width, &height); + + switch (edge) { + case WMSZ_TOPLEFT: + rect->left = rect->right - width; + rect->top = rect->bottom - height; + break; + + case WMSZ_RIGHT: + case WMSZ_BOTTOM: + case WMSZ_BOTTOMRIGHT: + rect->right = rect->left + width; + rect->bottom = rect->top + height; + break; + + case WMSZ_LEFT: + case WMSZ_BOTTOMLEFT: + rect->left = rect->right - width; + rect->bottom = rect->top + height; + break; + + case WMSZ_TOP: + case WMSZ_TOPRIGHT: + rect->right = rect->left + width; + rect->top = rect->bottom - height; + break; + } +} + +bool __cdecl WinVidCheckGameWindowPalette(HWND hWnd) +{ + const HDC hdc = GetDC(hWnd); + if (hdc == NULL) { + return false; + } + + PALETTEENTRY sys_palette[256]; + GetSystemPaletteEntries(hdc, 0, 256, sys_palette); + ReleaseDC(hWnd, hdc); + + RGB_888 buf_palette[256]; + for (int32_t i = 0; i < 256; i++) { + buf_palette[i].red = sys_palette[i].peRed; + buf_palette[i].green = sys_palette[i].peGreen; + buf_palette[i].blue = sys_palette[i].peBlue; + } + + return memcmp(buf_palette, g_GamePalette8, sizeof(buf_palette)) == 0; +} + +bool __cdecl WinVidCreateGameWindow(void) +{ + g_IsGameWindowActive = true; + g_IsGameWindowShow = true; + g_IsDDrawGameWindowShow = false; + g_IsMessageLoopClosed = false; + g_IsGameWindowUpdating = false; + g_IsGameWindowMinimized = false; + g_IsGameWindowMaximized = false; + g_IsGameWindowCreated = false; + g_IsGameFullScreen = false; + g_IsMinMaxInfoSpecial = false; + g_IsGameWindowChanging = false; + + WinVidClearMinWindowSize(); + WinVidClearMaxWindowSize(); + + g_GameWindowHandle = CreateWindowEx( + WS_EX_APPWINDOW, g_GameClassName, g_GameWindowName, WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, + g_GameModule, NULL); + if (g_GameWindowHandle == NULL) { + return false; + } + + RECT rect; + GetWindowRect(g_GameWindowHandle, &rect); + g_GameWindowX = rect.left; + g_GameWindowY = rect.top; + + WinVidHideGameWindow(); + + return true; +} + +void __cdecl WinVidFreeWindow(void) +{ + WinVidExitMessage(); + UnregisterClassA(g_GameClassName, g_GameModule); +} + +void __cdecl WinVidExitMessage(void) +{ + if (g_GameWindowHandle == NULL || !IsWindow(g_GameWindowHandle)) { + return; + } + + PostMessage(g_GameWindowHandle, WM_CLOSE, 0, 0); + while (WinVidSpinMessageLoop(false)) { } + g_GameWindowHandle = NULL; +} + +DISPLAY_ADAPTER_NODE *__cdecl WinVidGetDisplayAdapter(const GUID *guid_ptr) +{ + if (guid_ptr != NULL) { + for (DISPLAY_ADAPTER_NODE *adapter = g_DisplayAdapterList.head; + adapter != NULL; adapter = adapter->next) { + if (memcmp(&adapter->body.adapter_guid, guid_ptr, sizeof(GUID)) + == 0) { + return adapter; + } + } + } + return g_PrimaryDisplayAdapter; +} + +void __cdecl WinVidStart(void) +{ + if (g_SavedAppSettings.preferred_display_adapter == NULL) { + Shell_ExitSystem("Can't create DirectDraw"); + } + + DISPLAY_ADAPTER *preferred = + &g_SavedAppSettings.preferred_display_adapter->body; + g_CurrentDisplayAdapter = *preferred; + + S_FlaggedString_Copy( + &g_CurrentDisplayAdapter.driver_desc, &preferred->driver_desc); + S_FlaggedString_Copy( + &g_CurrentDisplayAdapter.driver_name, &preferred->driver_name); + + M_DisplayModeListInit(&g_CurrentDisplayAdapter.hw_disp_mode_list); + M_DisplayModeListCopy( + &g_CurrentDisplayAdapter.hw_disp_mode_list, + &preferred->hw_disp_mode_list); + + M_DisplayModeListInit(&g_CurrentDisplayAdapter.sw_disp_mode_list); + M_DisplayModeListCopy( + &g_CurrentDisplayAdapter.sw_disp_mode_list, + &preferred->sw_disp_mode_list); + + if (!DDrawCreate(g_CurrentDisplayAdapter.adapter_guid_ptr)) { + Shell_ExitSystem("Can't create DirectDraw"); + } +} + +void __cdecl WinVidFinish(void) +{ + if (g_IsDDrawGameWindowShow) { + HideDDrawGameWindow(); + } + DDrawRelease(); +} + +int32_t __cdecl Misc_Move3DPosTo3DPos( + PHD_3DPOS *const src_pos, const PHD_3DPOS *const dst_pos, + const int32_t velocity, const PHD_ANGLE ang_add) +{ + // TODO: this function's only usage is in Lara_MovePosition. inline it + const XYZ_32 dpos = { + .x = dst_pos->pos.x - src_pos->pos.x, + .y = dst_pos->pos.y - src_pos->pos.y, + .z = dst_pos->pos.z - src_pos->pos.z, + }; + const int32_t dist = XYZ_32_GetDistance0(&dpos); + if (velocity >= dist) { + src_pos->pos.x = dst_pos->pos.x; + src_pos->pos.y = dst_pos->pos.y; + src_pos->pos.z = dst_pos->pos.z; + } else { + src_pos->pos.x += velocity * dpos.x / dist; + src_pos->pos.y += velocity * dpos.y / dist; + src_pos->pos.z += velocity * dpos.z / dist; + } + +#define ADJUST_ROT(source, target, rot) \ + do { \ + if ((PHD_ANGLE)(target - source) > rot) { \ + source += rot; \ + } else if ((PHD_ANGLE)(target - source) < -rot) { \ + source -= rot; \ + } else { \ + source = target; \ + } \ + } while (0) + + ADJUST_ROT(src_pos->rot.x, dst_pos->rot.x, ang_add); + ADJUST_ROT(src_pos->rot.y, dst_pos->rot.y, ang_add); + ADJUST_ROT(src_pos->rot.z, dst_pos->rot.z, ang_add); + + // clang-format off + return ( + src_pos->pos.x == dst_pos->pos.x && + src_pos->pos.y == dst_pos->pos.y && + src_pos->pos.z == dst_pos->pos.z && + src_pos->rot.x == dst_pos->rot.x && + src_pos->rot.y == dst_pos->rot.y && + src_pos->rot.z == dst_pos->rot.z + ); + // clang-format on +} + +int32_t __cdecl LevelCompleteSequence(void) +{ + return GFD_EXIT_TO_TITLE; +} + +void __cdecl S_LoadSettings(void) +{ + OpenGameRegistryKey("Game"); + + { + DWORD tmp; + GetRegistryDwordValue("MusicVolume", &tmp, 165); + g_OptionMusicVolume = tmp; + } + + { + DWORD tmp; + GetRegistryDwordValue("SoundFXVolume", &tmp, 10); + g_OptionSoundVolume = tmp; + } + + { + DWORD tmp; + GetRegistryDwordValue("DetailLevel", &tmp, 1); + g_DetailLevel = tmp; + } + + GetRegistryFloatValue("Sizer", &g_GameSizerCopy, 1.0); + GetRegistryBinaryValue( + "Layout", (uint8_t *)&g_Layout[1], + sizeof(uint16_t) * INPUT_ROLE_NUMBER_OF, 0); + + CloseGameRegistryKey(); + + Input_CheckConflictsWithDefaults(); + + Sound_SetMasterVolume(6 * g_OptionSoundVolume + 4); + + if (g_OptionMusicVolume) { + Music_SetVolume(25 * g_OptionMusicVolume + 5); + } else { + Music_SetVolume(0); + } +} + +void __cdecl S_SaveSettings(void) +{ + OpenGameRegistryKey("Game"); + SetRegistryDwordValue("MusicVolume", g_OptionMusicVolume); + SetRegistryDwordValue("SoundFxVolume", g_OptionSoundVolume); + SetRegistryDwordValue("DetailLevel", g_DetailLevel); + SetRegistryFloatValue("Sizer", g_GameSizerCopy); + SetRegistryBinaryValue( + "Layout", (uint8_t *)&g_Layout[1], + sizeof(uint16_t) * INPUT_ROLE_NUMBER_OF); + CloseGameRegistryKey(); +} diff --git a/src/tr2/decomp/decomp.h b/src/tr2/decomp/decomp.h new file mode 100644 index 000000000..eb51c624b --- /dev/null +++ b/src/tr2/decomp/decomp.h @@ -0,0 +1,136 @@ +#pragma once + +#include "global/types.h" + +#include +#include + +int32_t __cdecl GameInit(void); +int32_t __stdcall WinMain( + HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, + int32_t nShowCmd); +const char *__cdecl DecodeErrorMessage(int32_t error_code); +int32_t __cdecl RenderErrorBox(int32_t error_code); +void __cdecl ScreenshotPCX(void); +size_t __cdecl CompPCX( + uint8_t *bitmap, int32_t width, int32_t height, RGB_888 *palette, + uint8_t **pcx_data); +size_t __cdecl EncodeLinePCX(const uint8_t *src, int32_t width, uint8_t *dst); +size_t __cdecl EncodePutPCX(uint8_t value, uint8_t num, uint8_t *buffer); +void __cdecl Screenshot(LPDDS screen); +bool __cdecl DInputCreate(void); +void __cdecl DInputRelease(void); +void __cdecl WinInReadKeyboard(uint8_t *input_data); +int32_t __cdecl WinGameStart(void); +void __cdecl Shell_Shutdown(void); +int16_t __cdecl TitleSequence(void); +void __cdecl WinVidSetMinWindowSize(int32_t width, int32_t height); +void __cdecl WinVidSetMaxWindowSize(int32_t width, int32_t height); +void __cdecl WinVidClearMinWindowSize(void); +void __cdecl WinVidClearMaxWindowSize(void); +int32_t __cdecl CalculateWindowWidth(int32_t width, int32_t height); +int32_t __cdecl CalculateWindowHeight(int32_t width, int32_t height); +bool __cdecl WinVidGetMinMaxInfo(LPMINMAXINFO info); +HWND __cdecl WinVidFindGameWindow(void); +bool __cdecl WinVidSpinMessageLoop(bool need_wait); +void __cdecl WinVidShowGameWindow(int32_t cmd_show); +void __cdecl WinVidHideGameWindow(void); +void __cdecl WinVidSetGameWindowSize(int32_t width, int32_t height); +bool __cdecl ShowDDrawGameWindow(bool active); +bool __cdecl HideDDrawGameWindow(void); +HRESULT __cdecl DDrawSurfaceCreate(LPDDSDESC dsp, LPDDS *surface); +HRESULT __cdecl DDrawSurfaceRestoreLost( + LPDDS surface1, LPDDS surface2, bool blank); +bool __cdecl WinVidClearBuffer(LPDDS surface, LPRECT rect, DWORD fill_color); +HRESULT __cdecl WinVidBufferLock(LPDDS surface, LPDDSDESC desc, DWORD flags); +HRESULT __cdecl WinVidBufferUnlock(LPDDS surface, LPDDSDESC desc); +bool __cdecl WinVidCopyBitmapToBuffer(LPDDS surface, const BYTE *bitmap); +DWORD __cdecl GetRenderBitDepth(uint32_t rgb_bit_count); +void __thiscall WinVidGetColorBitMasks( + COLOR_BIT_MASKS *bm, LPDDPIXELFORMAT pixel_format); +void __cdecl BitMaskGetNumberOfBits( + uint32_t bit_mask, uint32_t *bit_depth, uint32_t *bit_offset); +DWORD __cdecl CalculateCompatibleColor( + const COLOR_BIT_MASKS *mask, int32_t red, int32_t green, int32_t blue, + int32_t alpha); +bool __cdecl WinVidGetDisplayMode(DISPLAY_MODE *disp_mode); +bool __cdecl WinVidGoFullScreen(DISPLAY_MODE *disp_mode); +bool __cdecl WinVidGoWindowed( + int32_t width, int32_t height, DISPLAY_MODE *disp_mode); +void __cdecl WinVidSetDisplayAdapter(DISPLAY_ADAPTER *disp_adapter); +void __cdecl Game_SetCutsceneTrack(int32_t track); +int32_t __cdecl Game_Cutscene_Start(int32_t level_num); +void __cdecl Misc_InitCinematicRooms(void); +int32_t __cdecl Game_Cutscene_Control(int32_t nframes); +void __cdecl CutscenePlayer_Control(int16_t item_num); +void __cdecl Lara_Control_Cutscene(int16_t item_num); +void __cdecl CutscenePlayer1_Initialise(int16_t item_num); +void __cdecl CutscenePlayerGen_Initialise(int16_t item_num); +int32_t __cdecl Level_Initialise( + int32_t level_num, GAMEFLOW_LEVEL_TYPE level_type); +void __cdecl CreateScreenBuffers(void); +void __cdecl CreatePrimarySurface(void); +void __cdecl CreateBackBuffer(void); +void __cdecl CreateClipper(void); +void __cdecl CreateWindowPalette(void); +void __cdecl CreateZBuffer(void); +int32_t __cdecl GetZBufferDepth(void); +void __cdecl CreateRenderBuffer(void); +void __cdecl CreatePictureBuffer(void); +void __cdecl ClearBuffers(DWORD flags, DWORD fill_color); +void __cdecl UpdateFrame(bool need_run_message_loop, LPRECT rect); +void __cdecl RestoreLostBuffers(void); +void __cdecl WaitPrimaryBufferFlip(void); +bool __cdecl RenderInit(void); +void __cdecl RenderStart(bool is_reset); +void __cdecl RenderFinish(bool need_to_clear_textures); +bool __cdecl ApplySettings(const APP_SETTINGS *new_settings); +void __cdecl FmvBackToGame(void); +void __cdecl GameApplySettings(APP_SETTINGS *new_settings); +void __cdecl UpdateGameResolution(void); +bool __cdecl D3DCreate(void); +void __cdecl D3DRelease(void); +void __cdecl Enumerate3DDevices(DISPLAY_ADAPTER *adapter); +HRESULT __stdcall Enum3DDevicesCallback( + GUID FAR *lpGuid, LPTSTR lpDeviceDescription, LPTSTR lpDeviceName, + LPD3DDEVICEDESC_V2 lpD3DHWDeviceDesc, LPD3DDEVICEDESC_V2 lpD3DHELDeviceDesc, + LPVOID lpContext); +bool __cdecl D3DIsSupported(LPD3DDEVICEDESC_V2 desc); +bool __cdecl D3DSetViewport(void); +void __cdecl D3DDeviceCreate(LPDDS lpBackBuffer); +void __cdecl Direct3DRelease(void); +bool __cdecl Direct3DInit(void); +bool __cdecl DDrawCreate(LPGUID lpGUID); +void __cdecl DDrawRelease(void); +void __cdecl GameWindowCalculateSizeFromClient(int32_t *width, int32_t *height); +void __cdecl GameWindowCalculateSizeFromClientByZero( + int32_t *width, int32_t *height); +bool __thiscall CompareVideoModes( + const DISPLAY_MODE *mode1, const DISPLAY_MODE *mode2); +bool __cdecl WinVidGetDisplayModes(void); +HRESULT __stdcall EnumDisplayModesCallback( + LPDDSDESC lpDDSurfaceDesc, LPVOID lpContext); +bool __cdecl WinVidInit(void); +bool __cdecl WinVidGetDisplayAdapters(void); +bool __cdecl EnumerateDisplayAdapters( + DISPLAY_ADAPTER_LIST *display_adapter_list); +bool __cdecl WinVidRegisterGameWindowClass(void); +LRESULT CALLBACK +WinVidGameWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); +BOOL WINAPI EnumDisplayAdaptersCallback( + GUID FAR *lpGUID, LPTSTR lpDriverDescription, LPTSTR lpDriverName, + LPVOID lpContext); +void __cdecl WinVidResizeGameWindow(HWND hWnd, int32_t edge, LPRECT rect); +bool __cdecl WinVidCheckGameWindowPalette(HWND hWnd); +bool __cdecl WinVidCreateGameWindow(void); +void __cdecl WinVidFreeWindow(void); +void __cdecl WinVidExitMessage(void); +DISPLAY_ADAPTER_NODE *__cdecl WinVidGetDisplayAdapter(const GUID *guid_ptr); +void __cdecl WinVidStart(void); +void __cdecl WinVidFinish(void); +int32_t __cdecl Misc_Move3DPosTo3DPos( + PHD_3DPOS *src_pos, const PHD_3DPOS *dst_pos, int32_t velocity, + PHD_ANGLE ang_add); +int32_t __cdecl LevelCompleteSequence(void); +void __cdecl S_LoadSettings(void); +void __cdecl S_SaveSettings(void); diff --git a/src/tr2/decomp/effects.c b/src/tr2/decomp/effects.c new file mode 100644 index 000000000..013f90944 --- /dev/null +++ b/src/tr2/decomp/effects.c @@ -0,0 +1,109 @@ +#include "decomp/effects.h" + +#include "game/effects.h" +#include "game/items.h" +#include "game/matrix.h" +#include "game/random.h" +#include "global/funcs.h" +#include "global/types.h" +#include "global/vars.h" + +int32_t __cdecl Effect_ExplodingDeath( + const int16_t item_num, const int32_t mesh_bits, const int16_t damage) +{ + ITEM *const item = &g_Items[item_num]; + const OBJECT *const object = &g_Objects[item->object_id]; + + S_CalculateLight(item->pos.x, item->pos.y, item->pos.z, item->room_num); + + const FRAME_INFO *const best_frame = Item_GetBestFrame(item); + + Matrix_PushUnit(); + g_MatrixPtr->_03 = 0; + g_MatrixPtr->_13 = 0; + g_MatrixPtr->_23 = 0; + g_MatrixPtr->_23 = 0; + + Matrix_RotYXZ(item->rot.y, item->rot.x, item->rot.z); + Matrix_TranslateRel( + best_frame->offset.x, best_frame->offset.y, best_frame->offset.z); + + const int16_t *mesh_rots = best_frame->mesh_rots; + Matrix_RotYXZsuperpack(&mesh_rots, 0); + + // main mesh + int32_t bit = 1; + if ((mesh_bits & bit) && (item->mesh_bits & bit)) { + const int16_t fx_num = Effect_Create(item->room_num); + if (fx_num != NO_ITEM) { + FX *const fx = &g_Effects[fx_num]; + fx->pos.x = item->pos.x + (g_MatrixPtr->_03 >> W2V_SHIFT); + fx->pos.y = item->pos.y + (g_MatrixPtr->_13 >> W2V_SHIFT); + fx->pos.z = item->pos.z + (g_MatrixPtr->_23 >> W2V_SHIFT); + fx->rot.y = (Random_GetControl() - 0x4000) * 2; + fx->room_num = item->room_num; + fx->speed = Random_GetControl() >> 8; + fx->fall_speed = -Random_GetControl() >> 8; + fx->counter = damage; + fx->object_id = O_BODY_PART; + fx->frame_num = object->mesh_idx; + fx->shade = g_LsAdder - 0x300; + } + item->mesh_bits &= ~bit; + } + + // additional meshes + const int32_t *bone = &g_AnimBones[object->bone_idx]; + const int16_t *extra_rotation = (int16_t *)item->data; + for (int32_t i = 1; i < object->mesh_count; i++) { + uint32_t bone_flags = *bone++; + if (bone_flags & BF_MATRIX_POP) { + Matrix_Pop(); + } + if (bone_flags & BF_MATRIX_PUSH) { + Matrix_Push(); + } + + Matrix_TranslateRel(bone[0], bone[1], bone[2]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + + if (extra_rotation != NULL + && bone_flags & (BF_ROT_X | BF_ROT_Y | BF_ROT_Z)) { + if (bone_flags & BF_ROT_Y) { + Matrix_RotY(*extra_rotation++); + } + if (bone_flags & BF_ROT_X) { + Matrix_RotX(*extra_rotation++); + } + if (bone_flags & BF_ROT_Z) { + Matrix_RotZ(*extra_rotation++); + } + } + + bit <<= 1; + if ((mesh_bits & bit) && (item->mesh_bits & bit)) { + const int16_t fx_num = Effect_Create(item->room_num); + if (fx_num != NO_ITEM) { + FX *const fx = &g_Effects[fx_num]; + fx->pos.x = item->pos.x + (g_MatrixPtr->_03 >> W2V_SHIFT); + fx->pos.y = item->pos.y + (g_MatrixPtr->_13 >> W2V_SHIFT); + fx->pos.z = item->pos.z + (g_MatrixPtr->_23 >> W2V_SHIFT); + fx->rot.y = (Random_GetControl() - 0x4000) * 2; + fx->room_num = item->room_num; + fx->speed = Random_GetControl() >> 8; + fx->fall_speed = -Random_GetControl() >> 8; + fx->counter = damage; + fx->object_id = O_BODY_PART; + fx->frame_num = object->mesh_idx + i; + fx->shade = g_LsAdder - 0x300; + } + item->mesh_bits &= ~bit; + } + + bone += 3; + } + + Matrix_Pop(); + + return !(item->mesh_bits & (INT32_MAX >> (31 - object->mesh_count))); +} diff --git a/src/tr2/decomp/effects.h b/src/tr2/decomp/effects.h new file mode 100644 index 000000000..f92455d27 --- /dev/null +++ b/src/tr2/decomp/effects.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +int32_t __cdecl Effect_ExplodingDeath( + int16_t item_num, int32_t mesh_bits, int16_t damage); diff --git a/src/tr2/decomp/stats.c b/src/tr2/decomp/stats.c new file mode 100644 index 000000000..2e3c73426 --- /dev/null +++ b/src/tr2/decomp/stats.c @@ -0,0 +1,422 @@ +#include "decomp/stats.h" + +#include "game/input.h" +#include "game/music.h" +#include "game/overlay.h" +#include "game/requester.h" +#include "game/text.h" +#include "global/funcs.h" +#include "global/types.h" +#include "global/vars.h" + +#include + +#include + +// TODO: consolidate with STATISTICS_INFO +typedef struct { + uint32_t timer; + uint32_t ammo_used; + uint32_t ammo_hits; + uint32_t distance; + uint32_t kills; + uint8_t found_secrets; // this is no longer a bitmask + uint8_t total_secrets; // this is not present in STATISTICS_INFO + uint8_t medipacks; +} STATS; + +static STATS M_GetEndGameStats(void) +{ + STATS result = { 0 }; + + const int32_t total_levels = g_GameFlow.num_levels - g_GameFlow.num_demos; + for (int32_t i = LV_FIRST; i < total_levels; i++) { + result.timer += g_SaveGame.start[i].statistics.timer; + result.ammo_used += g_SaveGame.start[i].statistics.shots; + result.ammo_hits += g_SaveGame.start[i].statistics.hits; + result.kills += g_SaveGame.start[i].statistics.kills; + result.distance += g_SaveGame.start[i].statistics.distance; + result.medipacks += g_SaveGame.start[i].statistics.medipacks; + + // TODO: #170, consult GFE_NUM_SECRETS rather than hardcoding this + if (i < total_levels - 2) { + for (int32_t j = 0; j < 3; j++) { + if (g_SaveGame.start[i].statistics.secrets & (1 << j)) { + result.found_secrets++; + } + result.total_secrets++; + } + } + } + + return result; +} + +void __cdecl ShowGymStatsText(const char *const time_str, const int32_t type) +{ + char text1[32]; + char text2[32]; + + if (g_StatsRequester.ready) { + if (!Requester_Display(&g_StatsRequester, 1, 1)) { + g_InputDB = 0; + g_Input = 0; + } + return; + } + + g_StatsRequester.no_selector = 1; + Requester_SetSize(&g_StatsRequester, 7, -32); + g_StatsRequester.line_height = 18; + g_StatsRequester.items_count = 0; + g_StatsRequester.selected = 0; + g_StatsRequester.line_offset = 0; + g_StatsRequester.line_old_offset = 0; + g_StatsRequester.pix_width = 304; + g_StatsRequester.x_pos = 0; + g_StatsRequester.z_pos = 0; + g_StatsRequester.pitem_strings1 = g_ValidLevelStrings1; + g_StatsRequester.pitem_strings2 = g_ValidLevelStrings2; + g_StatsRequester.item_string_len = MAX_LEVEL_NAME_SIZE; + + Requester_Init(&g_StatsRequester); + Requester_SetHeading( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_BEST_TIMES], + REQ_CENTER, NULL, 0); + + int32_t i; + for (i = 0; i < 10; i++) { + if (!g_Assault.best_time[i]) { + break; + } + + sprintf( + text1, "%2d: %s %d", i + 1, g_GF_GameStrings[GF_S_GAME_MISC_FINISH], + g_Assault.best_finish[i]); + const int32_t sec = g_Assault.best_time[i] / FRAMES_PER_SECOND; + sprintf( + text2, "%02d:%02d.%-2d", sec / 60, sec % 60, + g_Assault.best_time[i] % FRAMES_PER_SECOND + / (FRAMES_PER_SECOND / 10)); + Requester_AddItem( + &g_StatsRequester, text1, REQ_ALIGN_LEFT, text2, REQ_ALIGN_RIGHT); + } + + if (i == 0) { + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_NO_TIMES_SET], + REQ_CENTER, NULL, REQ_CENTER); + } + + g_StatsRequester.ready = 1; +} + +void __cdecl ShowStatsText(const char *const time_str, const int32_t type) +{ + char buffer[32]; + + if (g_StatsRequester.ready) { + Requester_ChangeItem( + &g_StatsRequester, 0, g_GF_GameStrings[GF_S_GAME_MISC_TIME_TAKEN], + REQ_ALIGN_LEFT, time_str, REQ_ALIGN_RIGHT); + if (!Requester_Display(&g_StatsRequester, type, 1)) { + g_InputDB = 0; + g_Input = 0; + } + return; + } + + g_StatsRequester.no_selector = 1; + Requester_SetSize(&g_StatsRequester, 7, -32); + g_StatsRequester.line_height = 18; + g_StatsRequester.items_count = 0; + g_StatsRequester.selected = 0; + g_StatsRequester.line_offset = 0; + g_StatsRequester.line_old_offset = 0; + g_StatsRequester.pix_width = 304; + g_StatsRequester.x_pos = 0; + g_StatsRequester.z_pos = 0; + g_StatsRequester.pitem_strings1 = g_ValidLevelStrings1; + g_StatsRequester.pitem_strings2 = g_ValidLevelStrings2; + g_StatsRequester.item_string_len = MAX_LEVEL_NAME_SIZE; + + Requester_Init(&g_StatsRequester); + Requester_SetHeading( + &g_StatsRequester, g_GF_LevelNames[g_CurrentLevel], REQ_CENTER, NULL, + 0); + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_TIME_TAKEN], + REQ_ALIGN_LEFT, time_str, REQ_ALIGN_RIGHT); + + if (g_GF_NumSecrets) { + char *ptr = buffer; + int32_t num_secrets = 0; + for (int32_t i = 0; i < 3; i++) { + if (g_SaveGame.statistics.secrets & (1 << i)) { + *ptr++ = 127 + i; + num_secrets++; + } else { + *ptr++ = ' '; + *ptr++ = ' '; + *ptr++ = ' '; + } + } + *ptr++ = '\0'; + if (num_secrets == 0) { + sprintf(buffer, g_GF_GameStrings[GF_S_GAME_MISC_NONE]); + } + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_SECRETS_FOUND], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + } + + sprintf(buffer, "%d", g_SaveGame.statistics.kills); + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_KILLS], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + + sprintf(buffer, "%d", g_SaveGame.statistics.shots); + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_AMMO_USED], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + + sprintf(buffer, "%d", g_SaveGame.statistics.hits); + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_HITS], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + + if ((g_SaveGame.statistics.medipacks & 1) != 0) { + sprintf(buffer, "%d.5", g_SaveGame.statistics.medipacks >> 1); + } else { + sprintf(buffer, "%d.0", g_SaveGame.statistics.medipacks >> 1); + } + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_HEALTH_PACKS_USED], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + + const int32_t distance = g_SaveGame.statistics.distance / 445; + if (distance < 1000) { + sprintf(buffer, "%dm", distance); + } else { + sprintf(buffer, "%d.%02dkm", distance / 1000, distance % 100); + } + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_DISTANCE_TRAVELLED], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + + g_StatsRequester.ready = 1; +} + +void __cdecl ShowEndStatsText(void) +{ + char buffer[32]; + + if (g_StatsRequester.ready) { + if (!Requester_Display(&g_StatsRequester, 0, 1)) { + g_InputDB = 0; + g_Input = 0; + } + return; + } + + g_StatsRequester.no_selector = 1; + Requester_SetSize(&g_StatsRequester, 7, -32); + g_StatsRequester.line_height = 18; + g_StatsRequester.items_count = 0; + g_StatsRequester.selected = 0; + g_StatsRequester.line_offset = 0; + g_StatsRequester.line_old_offset = 0; + g_StatsRequester.pix_width = 304; + g_StatsRequester.x_pos = 0; + g_StatsRequester.z_pos = 0; + g_StatsRequester.pitem_strings1 = g_ValidLevelStrings1; + g_StatsRequester.pitem_strings2 = g_ValidLevelStrings2; + g_StatsRequester.item_string_len = MAX_LEVEL_NAME_SIZE; + + Requester_Init(&g_StatsRequester); + Requester_SetHeading( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_FINAL_STATISTICS], 0, + 0, 0); + + const STATS stats = M_GetEndGameStats(); + + const int32_t sec = stats.timer / FRAMES_PER_SECOND; + sprintf( + buffer, "%02d:%02d:%02d", (sec / 60) / 60, (sec / 60) % 60, sec % 60); + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_TIME_TAKEN], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + + sprintf( + buffer, "%d %s %d", stats.found_secrets, + g_GF_GameStrings[GF_S_GAME_MISC_OF], stats.total_secrets); + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_SECRETS_FOUND], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + + sprintf(buffer, "%d", stats.kills); + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_KILLS], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + + sprintf(buffer, "%d", stats.ammo_used); + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_AMMO_USED], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + + sprintf(buffer, "%d", stats.ammo_hits); + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_HITS], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + + if ((stats.medipacks & 1) != 0) { + sprintf(buffer, "%d.5", stats.medipacks >> 1); + } else { + sprintf(buffer, "%d.0", stats.medipacks >> 1); + } + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_HEALTH_PACKS_USED], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + + const int32_t distance = stats.distance / 445; + if (distance < 1000) { + sprintf(buffer, "%dm", distance); + } else { + sprintf(buffer, "%d.%02dkm", distance / 1000, distance % 100); + } + Requester_AddItem( + &g_StatsRequester, g_GF_GameStrings[GF_S_GAME_MISC_DISTANCE_TRAVELLED], + REQ_ALIGN_LEFT, buffer, REQ_ALIGN_RIGHT); + + g_StatsRequester.ready = 1; +} + +int32_t __cdecl LevelStats(const int32_t level_num) +{ + START_INFO *const start = &g_SaveGame.start[level_num]; + start->statistics.timer = g_SaveGame.statistics.timer; + start->statistics.shots = g_SaveGame.statistics.shots; + start->statistics.hits = g_SaveGame.statistics.hits; + start->statistics.distance = g_SaveGame.statistics.distance; + start->statistics.kills = g_SaveGame.statistics.kills; + start->statistics.secrets = g_SaveGame.statistics.secrets; + start->statistics.medipacks = g_SaveGame.statistics.medipacks; + + const int32_t sec = g_SaveGame.statistics.timer / FRAMES_PER_SECOND; + char buffer[100]; + sprintf(buffer, "%02d:%02d:%02d", sec / 3600, (sec / 60) % 60, sec % 60); + + Music_Play(g_GameFlow.level_complete_track, false); + + TempVideoAdjust(g_HiRes, 1.0); + FadeToPal(30, g_GamePalette8); + Overlay_HideGameInfo(); + S_CopyScreenToBuffer(); + + while (g_Input & IN_SELECT) { + Input_Update(); + } + + while (true) { + S_InitialisePolyList(0); + S_CopyBufferToScreen(); + + Input_Update(); + + if (g_GF_OverrideDir != (GAME_FLOW_DIR)-1) { + break; + } + + ShowStatsText(buffer, 0); + Text_Draw(); + S_OutputPolyList(); + S_DumpScreen(); + + if (g_InputDB & IN_SELECT) { + break; + } + } + + Requester_Shutdown(&g_StatsRequester); + + CreateStartInfo(level_num + 1); + g_SaveGame.current_level = level_num + 1; + start->available = 0; + S_FadeToBlack(); + TempVideoRemove(); + return 0; +} + +int32_t __cdecl GameStats(const int32_t level_num) +{ + START_INFO *const start = &g_SaveGame.start[level_num]; + start->statistics.timer = g_SaveGame.statistics.timer; + start->statistics.shots = g_SaveGame.statistics.shots; + start->statistics.hits = g_SaveGame.statistics.hits; + start->statistics.distance = g_SaveGame.statistics.distance; + start->statistics.kills = g_SaveGame.statistics.kills; + start->statistics.secrets = g_SaveGame.statistics.secrets; + start->statistics.medipacks = g_SaveGame.statistics.medipacks; + + Overlay_HideGameInfo(); + while (g_Input & IN_SELECT) { + Input_Update(); + } + + while (true) { + S_InitialisePolyList(0); + S_CopyBufferToScreen(); + + Input_Update(); + + if (g_GF_OverrideDir != (GAME_FLOW_DIR)-1) { + break; + } + + ShowEndStatsText(); + Text_Draw(); + S_OutputPolyList(); + S_DumpScreen(); + + if (g_InputDB & IN_SELECT) { + break; + } + } + + Requester_Shutdown(&g_StatsRequester); + + g_SaveGame.bonus_flag = 1; + for (int32_t level = LV_FIRST; level <= g_GameFlow.num_levels; level++) { + ModifyStartInfo(level); + } + g_SaveGame.current_level = LV_FIRST; + + S_DontDisplayPicture(); + return 0; +} + +int32_t __cdecl AddAssaultTime(uint32_t time) +{ + ASSAULT_STATS *const stats = &g_Assault; + + int32_t insert_idx = -1; + for (int32_t i = 0; i < MAX_ASSAULT_TIMES; i++) { + if (stats->best_time[i] == 0 || time < stats->best_time[i]) { + insert_idx = i; + break; + } + } + if (insert_idx == -1) { + return false; + } + + for (int32_t i = MAX_ASSAULT_TIMES - 1; i > insert_idx; i--) { + stats->best_finish[i] = stats->best_finish[i - 1]; + stats->best_time[i] = stats->best_time[i - 1]; + } + + stats->finish_count++; + stats->best_time[insert_idx] = time; + stats->best_finish[insert_idx] = stats->finish_count; + return true; +} diff --git a/src/tr2/decomp/stats.h b/src/tr2/decomp/stats.h new file mode 100644 index 000000000..a1fd64478 --- /dev/null +++ b/src/tr2/decomp/stats.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +void __cdecl ShowGymStatsText(const char *time_str, int32_t type); +void __cdecl ShowStatsText(const char *time_str, int32_t type); +void __cdecl ShowEndStatsText(void); + +int32_t __cdecl LevelStats(int32_t level_num); +int32_t __cdecl GameStats(int32_t level_num); +int32_t __cdecl AddAssaultTime(uint32_t time); diff --git a/src/tr2/game/background.c b/src/tr2/game/background.c new file mode 100644 index 000000000..5c5aab468 --- /dev/null +++ b/src/tr2/game/background.c @@ -0,0 +1,293 @@ +#include "game/background.h" + +#include "game/hwr.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#include + +#define TEXTURE_WIDTH 256 +#define TEXTURE_HEIGHT 256 + +void __cdecl BGND_Make640x480(uint8_t *bitmap, RGB_888 *palette) +{ + if (g_TextureFormat.bpp >= 16) { + g_BGND_PaletteIndex = -1; + } else { + g_BGND_PaletteIndex = CreateTexturePalette(palette); + } + + const int32_t buf_size = 640 * 480 * 2; + uint8_t *buf = game_malloc(buf_size, GBUF_TEMP_ALLOC); + UT_MemBlt(buf, 0, 0, 256, 256, 256, bitmap, 0, 0, 640); + BGND_AddTexture(0, buf, g_BGND_PaletteIndex, palette); + + UT_MemBlt(buf, 0, 0, 256, 256, 256, bitmap, 256, 0, 640); + BGND_AddTexture(1, buf, g_BGND_PaletteIndex, palette); + + UT_MemBlt(buf, 0, 0, 128, 256, 256, bitmap, 512, 0, 640); + UT_MemBlt(buf, 128, 0, 128, 224, 256, bitmap, 512, 256, 640); + BGND_AddTexture(2, buf, g_BGND_PaletteIndex, palette); + + UT_MemBlt(buf, 0, 0, 256, 224, 256, bitmap, 0, 256, 640); + BGND_AddTexture(3, buf, g_BGND_PaletteIndex, palette); + + UT_MemBlt(buf, 0, 0, 256, 224, 256, bitmap, 256, 256, 640); + BGND_AddTexture(4, buf, g_BGND_PaletteIndex, palette); + + game_free(buf_size); + + BGND_GetPageHandles(); + + g_BGND_PictureIsReady = true; +} + +int32_t __cdecl BGND_AddTexture( + const int32_t tile_idx, uint8_t *const bitmap, const int32_t pal_index, + const RGB_888 *const bmp_pal) +{ + int32_t page_index; + if (pal_index < 0) { + uint8_t *bmp_src = &bitmap[TEXTURE_WIDTH * TEXTURE_HEIGHT]; + uint16_t *bmp_dst = + &((uint16_t *)bitmap)[TEXTURE_WIDTH * TEXTURE_HEIGHT]; + for (int32_t i = 0; i < TEXTURE_WIDTH * TEXTURE_HEIGHT; i++) { + bmp_src--; + bmp_dst--; + + const RGB_888 *const color = &bmp_pal[*bmp_src]; + + *bmp_dst = (1 << 15) | (((uint16_t)color->red >> 3) << 10) + | (((uint16_t)color->green >> 3) << 5) + | (((uint16_t)color->blue >> 3)); + } + + page_index = AddTexturePage16(256, 256, bmp_src); + } else { + page_index = AddTexturePage8(256, 256, bitmap, pal_index); + } + + g_BGND_TexturePageIndexes[tile_idx] = page_index >= 0 ? page_index : -1; + return page_index; +} + +void __cdecl BGND_GetPageHandles(void) +{ + for (int32_t i = 0; i < BGND_MAX_TEXTURE_PAGES; i++) { + g_BGND_PageHandles[i] = g_BGND_TexturePageIndexes[i] >= 0 + ? GetTexturePageHandle(g_BGND_TexturePageIndexes[i]) + : 0; + } +} + +void __cdecl BGND_DrawInGameBlack(void) +{ + HWR_EnableZBuffer(false, false); + DrawQuad( + (float)g_PhdWinMinX, (float)g_PhdWinMinY, (float)g_PhdWinWidth, + (float)g_PhdWinHeight, 0); + HWR_EnableZBuffer(true, true); +} + +void __cdecl DrawQuad( + const float sx, const float sy, const float width, const float height, + const D3DCOLOR color) +{ + D3DTLVERTEX vertex[4]; + + vertex[0].sx = sx; + vertex[0].sy = sy; + + vertex[1].sx = sx + width; + vertex[1].sy = sy; + + vertex[2].sx = sx; + vertex[2].sy = sy + height; + + vertex[3].sx = sx + width; + vertex[3].sy = sy + height; + + for (int32_t i = 0; i < 4; i++) { + vertex[i].sz = 0; + vertex[i].rhw = g_FltRhwONearZ; + vertex[i].color = RGBA_SETALPHA(color, 0xFF); + vertex[i].specular = 0; + } + + HWR_TexSource(0); + HWR_EnableColorKey(false); + HWR_DrawPrimitive(D3DPT_TRIANGLESTRIP, &vertex, 4, true); +} + +void __cdecl BGND_DrawInGameBackground(void) +{ + const OBJECT *const obj = &g_Objects[O_INV_BACKGROUND]; + if (!obj->loaded) { + BGND_DrawInGameBlack(); + return; + } + + const int16_t *mesh_ptr = g_Meshes[obj->mesh_idx]; + mesh_ptr += 5; + const int32_t num_vertices = *mesh_ptr++; + mesh_ptr += num_vertices * 3; + + const int32_t num_normals = *mesh_ptr++; + if (num_normals >= 0) { + mesh_ptr += num_normals * 3; + } else { + mesh_ptr -= num_normals; + } + const int32_t num_quads = *mesh_ptr++; + if (num_quads < 1) { + BGND_DrawInGameBlack(); + return; + } + + mesh_ptr += 4; + const int32_t texture_idx = *mesh_ptr++; + + const PHD_TEXTURE *texture = &g_PhdTextureInfo[texture_idx]; + const HWR_TEXTURE_HANDLE tex_source = g_HWR_PageHandles[texture->tex_page]; + const int32_t tu = texture->uv[0].u / PHD_HALF; + const int32_t tv = texture->uv[0].v / PHD_HALF; + const int32_t t_width = texture->uv[2].u / PHD_HALF - tu + 1; + const int32_t t_height = texture->uv[2].v / PHD_HALF - tv + 1; + + HWR_EnableZBuffer(false, false); + + const int32_t y_count = 6; + const int32_t x_count = 8; + + int32_t y_current = 0; + for (int32_t y = 0; y < y_count; y++) { + const int32_t y_next = g_PhdWinHeight + y_current; + + int32_t x_current = 0; + for (int32_t x = 0; x < x_count; x++) { + const int32_t x_next = x_current + g_PhdWinWidth; + + const int32_t y0 = g_PhdWinMinY + y_current / y_count; + const int32_t x0 = g_PhdWinMinX + x_current / x_count; + const int32_t x1 = g_PhdWinMinX + x_next / x_count; + const int32_t y1 = g_PhdWinMinY + y_next / y_count; + + const D3DCOLOR color[4] = { + BGND_CenterLighting(x0, y0, g_PhdWinWidth, g_PhdWinHeight), + BGND_CenterLighting(x1, y0, g_PhdWinWidth, g_PhdWinHeight), + BGND_CenterLighting(x0, y1, g_PhdWinWidth, g_PhdWinHeight), + BGND_CenterLighting(x1, y1, g_PhdWinWidth, g_PhdWinHeight), + }; + + DrawTextureTile( + x0, y0, x1 - x0, y1 - y0, tex_source, tu, tv, t_width, t_height, + color[0], color[1], color[2], color[3]); + + x_current = x_next; + } + y_current = y_next; + } + + HWR_EnableZBuffer(true, true); +} + +void __cdecl DrawTextureTile( + const int32_t sx, const int32_t sy, const int32_t width, + const int32_t height, const HWR_TEXTURE_HANDLE tex_source, const int32_t tu, + const int32_t tv, const int32_t t_width, const int32_t t_height, + const D3DCOLOR color0, const D3DCOLOR color1, const D3DCOLOR color2, + const D3DCOLOR color3) +{ + const D3DVALUE sx0 = sx; + const D3DVALUE sy0 = sy; + const D3DVALUE sx1 = sx + width; + const D3DVALUE sy1 = sy + height; + + const double uv_adjust = g_UVAdd / (double)PHD_ONE; + const D3DVALUE tu0 = tu / 256.0 + uv_adjust; + const D3DVALUE tv0 = tv / 256.0 + uv_adjust; + const D3DVALUE tu1 = (tu + t_width) / 256.0 - uv_adjust; + const D3DVALUE tv1 = (tv + t_height) / 256.0 - uv_adjust; + + D3DTLVERTEX vertex[4] = { + { + .sx = sx0, + .sy = sy0, + .tu = tu0, + .tv = tv0, + .color = color0, + }, + { + .sx = sx1, + .sy = sy0, + .tu = tu1, + .tv = tv0, + .color = color1, + }, + { + .sx = sx0, + .sy = sy1, + .tu = tu0, + .tv = tv1, + .color = color2, + }, + { + .sx = sx1, + .sy = sy1, + .tu = tu1, + .tv = tv1, + .color = color3, + }, + }; + + for (int32_t i = 0; i < 4; i++) { + vertex[i].sz = 0.995; + vertex[i].rhw = g_RhwFactor / g_FltFarZ; + vertex[i].specular = 0; + } + + HWR_TexSource(tex_source); + HWR_EnableColorKey(0); + HWR_DrawPrimitive(D3DPT_TRIANGLESTRIP, &vertex, 4, true); +} + +D3DCOLOR __cdecl BGND_CenterLighting( + const int32_t x, const int32_t y, const int32_t width, const int32_t height) +{ + const double x_dist = (double)(x - (width / 2)) / (double)width; + const double y_dist = (double)(y - (height / 2)) / (double)height; + + int32_t light = 256 - (sqrt(x_dist * x_dist + y_dist * y_dist) * 300.0); + CLAMP(light, 0, 255); + + return RGBA_MAKE(light, light, light, 0xFF); +} + +void __cdecl BGND_Free(void) +{ + for (int32_t i = 0; i < BGND_MAX_TEXTURE_PAGES; i++) { + if (g_BGND_TexturePageIndexes[i] >= 0) { + SafeFreeTexturePage(g_BGND_TexturePageIndexes[i]); + g_BGND_TexturePageIndexes[i] = -1; + } + g_BGND_PageHandles[i] = 0; + } + + if (g_BGND_PaletteIndex >= 0) { + SafeFreePalette(g_BGND_PaletteIndex); + g_BGND_PaletteIndex = -1; + } +} + +bool __cdecl BGND_Init(void) +{ + g_BGND_PictureIsReady = false; + g_BGND_PaletteIndex = -1; + for (int32_t i = 0; i < BGND_MAX_TEXTURE_PAGES; i++) { + g_BGND_TexturePageIndexes[i] = -1; + } + return true; +} diff --git a/src/tr2/game/background.h b/src/tr2/game/background.h new file mode 100644 index 000000000..9699f8c01 --- /dev/null +++ b/src/tr2/game/background.h @@ -0,0 +1,25 @@ +#pragma once + +#include "global/types.h" + +#define BGND_MAX_TEXTURE_PAGES 5 + +bool __cdecl BGND_Init(void); +void __cdecl BGND_Free(void); + +void __cdecl BGND_Make640x480(uint8_t *bitmap, RGB_888 *palette); +int32_t __cdecl BGND_AddTexture( + int32_t tile_idx, uint8_t *bitmap, int32_t pal_index, + const RGB_888 *bmp_pal); +void __cdecl BGND_GetPageHandles(void); +void __cdecl BGND_DrawInGameBlack(void); +void __cdecl DrawQuad( + float sx, float sy, float width, float height, D3DCOLOR color); +void __cdecl BGND_DrawInGameBackground(void); +void __cdecl DrawTextureTile( + int32_t sx, int32_t sy, int32_t width, int32_t height, + HWR_TEXTURE_HANDLE tex_source, int32_t tu, int32_t tv, int32_t t_width, + int32_t t_height, D3DCOLOR color0, D3DCOLOR color1, D3DCOLOR color2, + D3DCOLOR color3); +D3DCOLOR __cdecl BGND_CenterLighting( + int32_t x, int32_t y, int32_t width, int32_t height); diff --git a/src/tr2/game/backpack.c b/src/tr2/game/backpack.c new file mode 100644 index 000000000..e2d0ac63e --- /dev/null +++ b/src/tr2/game/backpack.c @@ -0,0 +1,8 @@ +#include "game/inventory/backpack.h" + +#include + +bool Backpack_AddItem(const GAME_OBJECT_ID object_id) +{ + return Inv_AddItem(object_id); +} diff --git a/src/tr2/game/box.c b/src/tr2/game/box.c new file mode 100644 index 000000000..be94c2def --- /dev/null +++ b/src/tr2/game/box.c @@ -0,0 +1,422 @@ +#include "game/box.h" + +#include "game/random.h" +#include "game/room.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#define BOX_OVERLAP_BITS 0x3FFF +#define BOX_SEARCH_NUM 0x7FFF +#define BOX_END_BIT 0x8000 +#define BOX_NUM_BITS (~BOX_END_BIT) // = 0x7FFF +#define BOX_STALK_DIST 3 // tiles +#define BOX_ESCAPE_DIST 5 // tiles +#define BOX_MAX_EXPANSION 5 + +#define BOX_BIFF (WALL_L / 2) // = 0x200 = 512 +#define BOX_CLIP_LEFT 1 +#define BOX_CLIP_RIGHT 2 +#define BOX_CLIP_TOP 4 +#define BOX_CLIP_BOTTOM 8 +#define BOX_CLIP_ALL \ + (BOX_CLIP_LEFT | BOX_CLIP_RIGHT | BOX_CLIP_TOP | BOX_CLIP_BOTTOM) // = 15 +#define BOX_CLIP_SECONDARY 16 + +int32_t __cdecl Box_SearchLOT(LOT_INFO *const lot, const int32_t expansion) +{ + int16_t *zone; + if (lot->fly) { + zone = g_FlyZone[g_FlipStatus]; + } else { + zone = g_GroundZone[BOX_ZONE(lot->step)][g_FlipStatus]; + } + + const int16_t search_zone = zone[lot->head]; + for (int32_t i = 0; i < expansion; i++) { + if (lot->head == NO_BOX) { + lot->tail = NO_BOX; + return false; + } + + BOX_NODE *node = &lot->node[lot->head]; + const BOX_INFO *box = &g_Boxes[lot->head]; + + bool done = false; + int32_t index = box->overlap_index & BOX_OVERLAP_BITS; + while (!done) { + int16_t box_num = g_Overlap[index++]; + if ((box_num & BOX_END_BIT) != 0) { + done = true; + box_num &= BOX_NUM_BITS; + } + + if (search_zone != zone[box_num]) { + continue; + } + + const int32_t change = g_Boxes[box_num].height - box->height; + if (change > lot->step || change < lot->drop) { + continue; + } + + BOX_NODE *const expand = &lot->node[box_num]; + if ((node->search_num & BOX_SEARCH_NUM) + < (expand->search_num & BOX_SEARCH_NUM)) { + continue; + } + + if ((node->search_num & BOX_BLOCKED_SEARCH) != 0) { + if ((expand->search_num & BOX_SEARCH_NUM) + == (node->search_num & BOX_SEARCH_NUM)) { + continue; + } + expand->search_num = node->search_num; + } else { + if ((expand->search_num & BOX_SEARCH_NUM) + == (node->search_num & BOX_SEARCH_NUM) + && (expand->search_num & BOX_BLOCKED_SEARCH) == 0) { + continue; + } + + if ((g_Boxes[box_num].overlap_index & lot->block_mask) != 0) { + expand->search_num = node->search_num | BOX_BLOCKED_SEARCH; + } else { + expand->search_num = node->search_num; + expand->exit_box = lot->head; + } + } + + if (expand->next_expansion == NO_BOX && box_num != lot->tail) { + lot->node[lot->tail].next_expansion = box_num; + lot->tail = box_num; + } + } + + lot->head = node->next_expansion; + node->next_expansion = NO_BOX; + } + + return true; +} + +int32_t __cdecl Box_UpdateLOT(LOT_INFO *const lot, const int32_t expansion) +{ + if (lot->required_box == NO_BOX || lot->required_box == lot->target_box) { + goto end; + } + + lot->target_box = lot->required_box; + BOX_NODE *const expand = &lot->node[lot->target_box]; + if (expand->next_expansion == NO_BOX && lot->tail != lot->target_box) { + expand->next_expansion = lot->head; + if (lot->head == NO_BOX) { + lot->tail = lot->target_box; + } + lot->head = lot->target_box; + } + lot->search_num++; + expand->search_num = lot->search_num; + expand->exit_box = NO_BOX; + +end: + return Box_SearchLOT(lot, expansion); +} + +void __cdecl Box_TargetBox(LOT_INFO *const lot, const int16_t box_num) +{ + const BOX_INFO *const box = &g_Boxes[box_num & BOX_NUM_BITS]; + + lot->target.z = ((box->right - box->left - 1) >> (15 - WALL_SHIFT)) + * Random_GetControl() + + (box->left << WALL_SHIFT) + WALL_L / 2; + lot->target.x = ((box->bottom - box->top - 1) >> (15 - WALL_SHIFT)) + * Random_GetControl() + + (box->top << WALL_SHIFT) + WALL_L / 2; + lot->required_box = box_num & BOX_NUM_BITS; + + if (lot->fly != 0) { + lot->target.y = box->height - STEP_L * 3 / 2; + } else { + lot->target.y = box->height; + } +} + +int32_t __cdecl Box_StalkBox( + const ITEM *const item, const ITEM *const enemy, const int16_t box_num) +{ + const BOX_INFO *const box = &g_Boxes[box_num]; + + const int32_t z = + ((box->left + box->right) << (WALL_SHIFT - 1)) - enemy->pos.z; + const int32_t x = + ((box->top + box->bottom) << (WALL_SHIFT - 1)) - enemy->pos.x; + + const int32_t x_range = (box->bottom - box->top + BOX_STALK_DIST) + << WALL_SHIFT; + const int32_t z_range = (box->right - box->left + BOX_STALK_DIST) + << WALL_SHIFT; + if (x > x_range || x < -x_range || z > z_range || z < -z_range) { + return false; + } + + const int32_t enemy_quad = (enemy->rot.y >> 14) + 2; + const int32_t box_quad = (z > 0) ? ((x > 0) ? DIR_SOUTH : DIR_EAST) + : ((x > 0) ? DIR_WEST : DIR_NORTH); + if (enemy_quad == box_quad) { + return false; + } + + const int32_t baddie_quad = item->pos.z > enemy->pos.z + ? (item->pos.x > x ? DIR_SOUTH : DIR_EAST) + : (item->pos.x > x ? DIR_WEST : DIR_NORTH); + + return enemy_quad != baddie_quad || ABS(enemy_quad - box_quad) != 2; +} + +int32_t __cdecl Box_EscapeBox( + const ITEM *const item, const ITEM *const enemy, const int16_t box_num) +{ + const BOX_INFO *const box = &g_Boxes[box_num]; + const int32_t x = + ((box->bottom + box->top) << (WALL_SHIFT - 1)) - enemy->pos.x; + const int32_t z = + ((box->left + box->right) << (WALL_SHIFT - 1)) - enemy->pos.z; + + const int32_t x_range = BOX_ESCAPE_DIST << WALL_SHIFT; + const int32_t z_range = BOX_ESCAPE_DIST << WALL_SHIFT; + if (x > -x_range && x < x_range && z > -z_range && z < z_range) { + return false; + } + + return ((z > 0) == (item->pos.z > enemy->pos.z)) + || ((x > 0) == (item->pos.x > enemy->pos.x)); +} + +int32_t __cdecl Box_ValidBox( + const ITEM *const item, const int16_t zone_num, const int16_t box_num) +{ + const CREATURE *const creature = item->data; + int16_t *zone; + if (creature->lot.fly) { + zone = g_FlyZone[g_FlipStatus]; + } else { + zone = g_GroundZone[BOX_ZONE(creature->lot.step)][g_FlipStatus]; + } + + if (zone[box_num] != zone_num) { + return false; + } + + const BOX_INFO *const box = &g_Boxes[box_num]; + if ((creature->lot.block_mask & box->overlap_index) != 0) { + return false; + } + + return !( + item->pos.z > (box->left << WALL_SHIFT) + && item->pos.z < (box->right << WALL_SHIFT) + && item->pos.x > (box->top << WALL_SHIFT) + && item->pos.x < (box->bottom << WALL_SHIFT)); +} + +TARGET_TYPE __cdecl Box_CalculateTarget( + XYZ_32 *const target, const ITEM *const item, LOT_INFO *const lot) +{ + Box_UpdateLOT(lot, BOX_MAX_EXPANSION); + + *target = item->pos; + + int32_t box_num = item->box_num; + if (box_num == NO_BOX) { + return TARGET_NONE; + } + + int32_t bottom = 0; + int32_t top = 0; + int32_t right = 0; + int32_t left = 0; + + BOX_INFO *box = NULL; + int32_t box_left = 0; + int32_t box_right = 0; + int32_t box_top = 0; + int32_t box_bottom = 0; + + int32_t prime_free = BOX_CLIP_ALL; + do { + box = &g_Boxes[box_num]; + int32_t height = box->height; + if (lot->fly != 0) { + height -= WALL_L; + } + if (target->y > height - WALL_L) { + target->y = height - WALL_L; + } + + box_left = box->left << WALL_SHIFT; + box_right = (box->right << WALL_SHIFT) - 1; + box_top = box->top << WALL_SHIFT; + box_bottom = (box->bottom << WALL_SHIFT) - 1; + + if (item->pos.z >= box_left && item->pos.z <= box_right + && item->pos.x >= box_top && item->pos.x <= box_bottom) { + left = box_left; + right = box_right; + top = box_top; + bottom = box_bottom; + } else { + if (item->pos.z < box_left) { + if ((prime_free & BOX_CLIP_LEFT) != 0 && item->pos.x >= box_top + && item->pos.x <= box_bottom) { + CLAMPL(target->z, box_left + BOX_BIFF); + if ((prime_free & BOX_CLIP_SECONDARY) != 0) { + return TARGET_SECONDARY; + } + CLAMPL(top, box_top); + CLAMPG(bottom, box_bottom); + prime_free = BOX_CLIP_LEFT; + } else if (prime_free != BOX_CLIP_LEFT) { + target->z = right - BOX_BIFF; + if (prime_free != BOX_CLIP_ALL) { + return TARGET_SECONDARY; + } + prime_free |= BOX_CLIP_SECONDARY; + } + } else if (item->pos.z > box_right) { + if ((prime_free & BOX_CLIP_RIGHT) != 0 && item->pos.x >= box_top + && item->pos.x <= box_bottom) { + CLAMPG(target->z, box_right - BOX_BIFF); + if ((prime_free & BOX_CLIP_SECONDARY) != 0) { + return TARGET_SECONDARY; + } + CLAMPL(top, box_top); + CLAMPG(bottom, box_bottom); + prime_free = BOX_CLIP_RIGHT; + } else if (prime_free != BOX_CLIP_RIGHT) { + target->z = left + BOX_BIFF; + if (prime_free != BOX_CLIP_ALL) { + return TARGET_SECONDARY; + } + prime_free |= BOX_CLIP_SECONDARY; + } + } + + if (item->pos.x < box_top) { + if ((prime_free & BOX_CLIP_TOP) != 0 + && (item->pos.z >= box_left) && item->pos.z <= box_right) { + CLAMPL(target->x, box_top + BOX_BIFF); + if ((prime_free & BOX_CLIP_SECONDARY) != 0) { + return TARGET_SECONDARY; + } + CLAMPL(left, box_left); + CLAMPG(right, box_right); + prime_free = BOX_CLIP_TOP; + } else if (prime_free != BOX_CLIP_TOP) { + target->x = bottom - BOX_BIFF; + if (prime_free != BOX_CLIP_ALL) { + return TARGET_SECONDARY; + } + prime_free |= BOX_CLIP_SECONDARY; + } + } else if (item->pos.x > box_bottom) { + if ((prime_free & BOX_CLIP_BOTTOM) != 0 + && item->pos.z >= box_left && item->pos.z <= box_right) { + CLAMPG(target->x, box_bottom - BOX_BIFF); + if ((prime_free & BOX_CLIP_SECONDARY) != 0) { + return TARGET_SECONDARY; + } + CLAMPL(left, box_left); + CLAMPG(right, box_right); + prime_free = BOX_CLIP_BOTTOM; + } else if (prime_free != BOX_CLIP_BOTTOM) { + target->x = top + BOX_BIFF; + if (prime_free != BOX_CLIP_ALL) { + return TARGET_SECONDARY; + } + prime_free |= BOX_CLIP_SECONDARY; + } + } + } + + if (box_num == lot->target_box) { + if ((prime_free & (BOX_CLIP_LEFT | BOX_CLIP_RIGHT)) != 0) { + target->z = lot->target.z; + } else if ((prime_free & BOX_CLIP_SECONDARY) == 0) { + CLAMP(target->z, box_left + BOX_BIFF, box_right - BOX_BIFF); + } + + if ((prime_free & (BOX_CLIP_TOP | BOX_CLIP_BOTTOM)) != 0) { + target->x = lot->target.x; + } else if ((prime_free & BOX_CLIP_SECONDARY) == 0) { + CLAMP(target->x, box_top + BOX_BIFF, box_bottom - BOX_BIFF); + } + + target->y = lot->target.y; + return TARGET_PRIMARY; + } + + box_num = lot->node[box_num].exit_box; + if (box_num != NO_BOX + && (g_Boxes[box_num].overlap_index & lot->block_mask) != 0) { + break; + } + } while (box_num != NO_BOX); + + if ((prime_free & (BOX_CLIP_LEFT | BOX_CLIP_RIGHT)) != 0) { + target->z = + (((box_right - box_left - WALL_L) * Random_GetControl()) >> 15) + + box_left + WALL_L / 2; + } else if ((prime_free & BOX_CLIP_SECONDARY) == 0) { + CLAMP(target->z, box_left + BOX_BIFF, box_right - BOX_BIFF); + } + + if ((prime_free & (BOX_CLIP_TOP | BOX_CLIP_BOTTOM)) != 0) { + target->x = + (((box_bottom - box_top - WALL_L) * Random_GetControl()) >> 15) + + box_top + WALL_L / 2; + } else if ((prime_free & BOX_CLIP_SECONDARY) == 0) { + CLAMP(target->x, box_top + BOX_BIFF, box_bottom - BOX_BIFF); + } + + if (lot->fly != 0) { + target->y = box->height - STEP_L * 3 / 2; + } else { + target->y = box->height; + } + + return TARGET_NONE; +} + +int32_t __cdecl Box_BadFloor( + const int32_t x, const int32_t y, const int32_t z, const int32_t box_height, + const int32_t next_height, int16_t room_num, const LOT_INFO *const lot) +{ + const SECTOR *const sector = Room_GetSector(x, y, z, &room_num); + const int16_t box_num = sector->box; + if (box_num == NO_BOX) { + return true; + } + + const BOX_INFO *const box = &g_Boxes[box_num]; + if ((box->overlap_index & lot->block_mask) != 0) { + return true; + } + + if (box_height - box->height > lot->step + || box_height - box->height < lot->drop) { + return true; + } + + if (box_height - box->height < -lot->step && box->height > next_height) { + return true; + } + + if (lot->fly != 0 && y > lot->fly + box->height) { + return true; + } + + return false; +} diff --git a/src/tr2/game/box.h b/src/tr2/game/box.h new file mode 100644 index 000000000..e42a26736 --- /dev/null +++ b/src/tr2/game/box.h @@ -0,0 +1,23 @@ +#pragma once + +#include "global/types.h" + +#define BOX_BLOCKED 0x4000 +#define BOX_BLOCKED_SEARCH 0x8000 +#define BOX_BLOCKABLE 0x8000 +#define BOX_ZONE(num) (((num) / STEP_L) - 1) + +int32_t __cdecl Box_SearchLOT(LOT_INFO *lot, int32_t expansion); +int32_t __cdecl Box_UpdateLOT(LOT_INFO *lot, int32_t expansion); +void __cdecl Box_TargetBox(LOT_INFO *lot, int16_t box_num); +int32_t __cdecl Box_StalkBox( + const ITEM *item, const ITEM *enemy, int16_t box_num); +int32_t __cdecl Box_EscapeBox( + const ITEM *item, const ITEM *enemy, int16_t box_num); +int32_t __cdecl Box_ValidBox( + const ITEM *item, int16_t zone_num, int16_t box_num); +TARGET_TYPE __cdecl Box_CalculateTarget( + XYZ_32 *target, const ITEM *item, LOT_INFO *lot); +int32_t __cdecl Box_BadFloor( + int32_t x, int32_t y, int32_t z, int32_t box_height, int32_t next_height, + int16_t room_num, const LOT_INFO *lot); diff --git a/src/tr2/game/camera.c b/src/tr2/game/camera.c new file mode 100644 index 000000000..ecc68b281 --- /dev/null +++ b/src/tr2/game/camera.c @@ -0,0 +1,939 @@ +#include "game/camera.h" + +#include "decomp/decomp.h" +#include "game/items.h" +#include "game/los.h" +#include "game/math.h" +#include "game/matrix.h" +#include "game/music.h" +#include "game/output.h" +#include "game/random.h" +#include "game/room.h" +#include "game/sound.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#include + +#define CHASE_SPEED 10 +#define CHASE_ELEVATION (WALL_L * 3 / 2) // = 1536 + +#define COMBAT_SPEED 8 +#define COMBAT_DISTANCE (WALL_L * 5 / 2) // = 2560 + +#define LOOK_DISTANCE (WALL_L * 3 / 2) // = 1536 +#define LOOK_CLAMP (STEP_L + 50) // = 296 +#define LOOK_SPEED 4 + +#define MAX_ELEVATION (85 * PHD_DEGREE) // = 15470 + +void __cdecl Camera_Initialise(void) +{ + Camera_ResetPosition(); + Output_AlterFOV(GAME_FOV * PHD_DEGREE); + Camera_Update(); +} + +void __cdecl Camera_ResetPosition(void) +{ + assert(g_LaraItem); + g_Camera.shift = g_LaraItem->pos.y - WALL_L; + + g_Camera.target.x = g_LaraItem->pos.x; + g_Camera.target.y = g_Camera.shift; + g_Camera.target.z = g_LaraItem->pos.z; + g_Camera.target.room_num = g_LaraItem->room_num; + + g_Camera.pos.x = g_Camera.target.x; + g_Camera.pos.y = g_Camera.target.y; + g_Camera.pos.z = g_Camera.target.z - 100; + g_Camera.pos.room_num = g_Camera.target.room_num; + + g_Camera.target_distance = WALL_L * 3 / 2; + g_Camera.item = NULL; + + g_Camera.num_frames = 1; + if (!g_Lara.extra_anim) { + g_Camera.type = CAM_CHASE; + } + + g_Camera.speed = 1; + g_Camera.flags = CF_NORMAL; + g_Camera.bounce = 0; + g_Camera.num = NO_CAMERA; + g_Camera.fixed_camera = 0; +} + +void __cdecl Camera_Move(const GAME_VECTOR *target, int32_t speed) +{ + g_Camera.pos.x += (target->x - g_Camera.pos.x) / speed; + g_Camera.pos.z += (target->z - g_Camera.pos.z) / speed; + g_Camera.pos.y += (target->y - g_Camera.pos.y) / speed; + g_Camera.pos.room_num = target->room_num; + + g_IsChunkyCamera = 0; + + const SECTOR *sector = Room_GetSector( + g_Camera.pos.x, g_Camera.pos.y, g_Camera.pos.z, &g_Camera.pos.room_num); + int32_t height = + Room_GetHeight(sector, g_Camera.pos.x, g_Camera.pos.y, g_Camera.pos.z) + - STEP_L; + + if (g_Camera.pos.y >= height && target->y >= height) { + LOS_Check(&g_Camera.target, &g_Camera.pos); + sector = Room_GetSector( + g_Camera.pos.x, g_Camera.pos.y, g_Camera.pos.z, + &g_Camera.pos.room_num); + height = Room_GetHeight( + sector, g_Camera.pos.x, g_Camera.pos.y, g_Camera.pos.z) + - STEP_L; + } + + int32_t ceiling = + Room_GetCeiling(sector, g_Camera.pos.x, g_Camera.pos.y, g_Camera.pos.z) + + STEP_L; + if (height < ceiling) { + ceiling = (height + ceiling) >> 1; + height = ceiling; + } + + if (g_Camera.bounce > 0) { + g_Camera.pos.y += g_Camera.bounce; + g_Camera.target.y += g_Camera.bounce; + g_Camera.bounce = 0; + } else if (g_Camera.bounce < 0) { + XYZ_32 shake = { + .x = g_Camera.bounce * (Random_GetControl() - 0x4000) / 0x7FFF, + .y = g_Camera.bounce * (Random_GetControl() - 0x4000) / 0x7FFF, + .z = g_Camera.bounce * (Random_GetControl() - 0x4000) / 0x7FFF, + }; + g_Camera.pos.x += shake.x; + g_Camera.pos.y += shake.y; + g_Camera.pos.z += shake.z; + g_Camera.target.y += shake.x; + g_Camera.target.y += shake.y; + g_Camera.target.z += shake.z; + g_Camera.bounce += 5; + } + + if (g_Camera.pos.y > height) { + g_Camera.shift = height - g_Camera.pos.y; + } else if (g_Camera.pos.y < ceiling) { + g_Camera.shift = ceiling - g_Camera.pos.y; + } else { + g_Camera.shift = 0; + } + + Room_GetSector( + g_Camera.pos.x, g_Camera.pos.y + g_Camera.shift, g_Camera.pos.z, + &g_Camera.pos.room_num); + + Matrix_LookAt( + g_Camera.pos.x, g_Camera.shift + g_Camera.pos.y, g_Camera.pos.z, + g_Camera.target.x, g_Camera.target.y, g_Camera.target.z, 0); + + if (g_Camera.is_lara_mic) { + g_Camera.actual_angle = + g_Lara.torso_y_rot + g_Lara.head_y_rot + g_LaraItem->rot.y; + g_Camera.mic_pos.x = g_LaraItem->pos.x; + g_Camera.mic_pos.y = g_LaraItem->pos.y; + g_Camera.mic_pos.z = g_LaraItem->pos.z; + } else { + g_Camera.actual_angle = Math_Atan( + g_Camera.target.z - g_Camera.pos.z, + g_Camera.target.x - g_Camera.pos.x); + g_Camera.mic_pos.x = g_Camera.pos.x + + ((g_PhdPersp * Math_Sin(g_Camera.actual_angle)) >> W2V_SHIFT); + g_Camera.mic_pos.z = g_Camera.pos.z + + ((g_PhdPersp * Math_Cos(g_Camera.actual_angle)) >> W2V_SHIFT); + g_Camera.mic_pos.y = g_Camera.pos.y; + } +} + +void __cdecl Camera_Clip( + int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, + int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom) +{ + if ((right > left) != (target_x < left)) { + *y = target_y + (left - target_x) * (*y - target_y) / (*x - target_x); + *h = target_h + (left - target_x) * (*h - target_h) / (*x - target_x); + *x = left; + } + + if ((bottom > top && target_y > top && (*y) < top) + || (bottom < top && target_y < top && (*y) > top)) { + *x = target_x + (top - target_y) * (*x - target_x) / (*y - target_y); + *h = target_h + (top - target_y) * (*h - target_h) / (*y - target_y); + *y = top; + } +} + +void __cdecl Camera_Shift( + int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, + int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom) +{ + int32_t shift; + + int32_t l_square = SQUARE(target_x - left); + int32_t r_square = SQUARE(target_x - right); + int32_t t_square = SQUARE(target_y - top); + int32_t b_square = SQUARE(target_y - bottom); + int32_t tl_square = t_square + l_square; + int32_t tr_square = t_square + r_square; + int32_t bl_square = b_square + l_square; + + if (g_Camera.target_square < tl_square) { + *x = left; + shift = g_Camera.target_square - l_square; + if (shift >= 0) { + shift = Math_Sqrt(shift); + *y = target_y + (top >= bottom ? shift : -shift); + } + } else if (tl_square > MIN_SQUARE) { + *x = left; + *y = top; + } else if (g_Camera.target_square < bl_square) { + *x = left; + shift = g_Camera.target_square - l_square; + if (shift >= 0) { + shift = Math_Sqrt(shift); + *y = target_y + (top < bottom ? shift : -shift); + } + } else if (2 * g_Camera.target_square < tr_square) { + shift = 2 * g_Camera.target_square - t_square; + if (shift >= 0) { + shift = Math_Sqrt(shift); + *x = target_x + (left < right ? shift : -shift); + *y = top; + } + } else if (bl_square <= tr_square) { + *x = right; + *y = top; + } else { + *x = left; + *y = bottom; + } +} + +const SECTOR *__cdecl Camera_GoodPosition( + int32_t x, int32_t y, int32_t z, int16_t room_num) +{ + const SECTOR *sector = Room_GetSector(x, y, z, &room_num); + int32_t height = Room_GetHeight(sector, x, y, z); + int32_t ceiling = Room_GetCeiling(sector, x, y, z); + if (y > height || y < ceiling) { + return NULL; + } + + return sector; +} + +void __cdecl Camera_SmartShift( + GAME_VECTOR *target, + void(__cdecl *shift)( + int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, + int32_t target_h, int32_t left, int32_t top, int32_t right, + int32_t bottom)) +{ + LOS_Check(&g_Camera.target, target); + + const ROOM *r = &g_Rooms[g_Camera.target.room_num]; + int32_t z_sector = (g_Camera.target.z - r->pos.z) >> WALL_SHIFT; + int32_t x_sector = (g_Camera.target.x - r->pos.x) >> WALL_SHIFT; + int16_t item_box = r->sectors[z_sector + x_sector * r->size.z].box; + const BOX_INFO *box = &g_Boxes[item_box]; + + int32_t left = (int32_t)box->left << WALL_SHIFT; + int32_t top = (int32_t)box->top << WALL_SHIFT; + int32_t right = ((int32_t)box->right << WALL_SHIFT) - 1; + int32_t bottom = ((int32_t)box->bottom << WALL_SHIFT) - 1; + + r = &g_Rooms[target->room_num]; + z_sector = (target->z - r->pos.z) >> WALL_SHIFT; + x_sector = (target->x - r->pos.x) >> WALL_SHIFT; + int16_t camera_box = r->sectors[z_sector + x_sector * r->size.z].box; + + if (camera_box != NO_BOX + && (target->z < left || target->z > right || target->x < top + || target->x > bottom)) { + box = &g_Boxes[camera_box]; + left = (int32_t)box->left << WALL_SHIFT; + top = (int32_t)box->top << WALL_SHIFT; + right = ((int32_t)box->right << WALL_SHIFT) - 1; + bottom = ((int32_t)box->bottom << WALL_SHIFT) - 1; + } + + int32_t test; + int32_t edge; + + test = (target->z - WALL_L) | (WALL_L - 1); + const SECTOR *good_left = + Camera_GoodPosition(target->x, target->y, test, target->room_num); + if (good_left) { + camera_box = good_left->box; + edge = (int32_t)g_Boxes[camera_box].left << WALL_SHIFT; + if (camera_box != NO_BOX && edge < left) { + left = edge; + } + } else { + left = test; + } + + test = (target->z + WALL_L) & (~(WALL_L - 1)); + const SECTOR *good_right = + Camera_GoodPosition(target->x, target->y, test, target->room_num); + if (good_right) { + camera_box = good_right->box; + edge = ((int32_t)g_Boxes[camera_box].right << WALL_SHIFT) - 1; + if (camera_box != NO_BOX && edge > right) { + right = edge; + } + } else { + right = test; + } + + test = (target->x - WALL_L) | (WALL_L - 1); + const SECTOR *good_top = + Camera_GoodPosition(test, target->y, target->z, target->room_num); + if (good_top) { + camera_box = good_top->box; + edge = (int32_t)g_Boxes[camera_box].top << WALL_SHIFT; + if (camera_box != NO_BOX && edge < top) { + top = edge; + } + } else { + top = test; + } + + test = (target->x + WALL_L) & (~(WALL_L - 1)); + const SECTOR *good_bottom = + Camera_GoodPosition(test, target->y, target->z, target->room_num); + if (good_bottom) { + camera_box = good_bottom->box; + edge = ((int32_t)g_Boxes[camera_box].bottom << WALL_SHIFT) - 1; + if (camera_box != NO_BOX && edge > bottom) { + bottom = edge; + } + } else { + bottom = test; + } + + left += STEP_L; + right -= STEP_L; + top += STEP_L; + bottom -= STEP_L; + + GAME_VECTOR a = { + .x = target->x, + .y = target->y, + .z = target->z, + .room_num = target->room_num, + }; + + GAME_VECTOR b = { + .x = target->x, + .y = target->y, + .z = target->z, + .room_num = target->room_num, + }; + + int32_t noclip = 1; + int32_t prefer_a; + + if (ABS(target->z - g_Camera.target.z) + > ABS(target->x - g_Camera.target.x)) { + if (target->z < left && !good_left) { + noclip = 0; + prefer_a = g_Camera.pos.x < g_Camera.target.x; + shift( + &a.z, &a.x, &a.y, g_Camera.target.z, g_Camera.target.x, + g_Camera.target.y, left, top, right, bottom); + shift( + &b.z, &b.x, &b.y, g_Camera.target.z, g_Camera.target.x, + g_Camera.target.y, left, bottom, right, top); + } else if (target->z > right && !good_right) { + noclip = 0; + prefer_a = g_Camera.pos.x < g_Camera.target.x; + shift( + &a.z, &a.x, &a.y, g_Camera.target.z, g_Camera.target.x, + g_Camera.target.y, right, top, left, bottom); + shift( + &b.z, &b.x, &b.y, g_Camera.target.z, g_Camera.target.x, + g_Camera.target.y, right, bottom, left, top); + } + + if (noclip) { + if (target->x < top && !good_top) { + noclip = 0; + prefer_a = target->z < g_Camera.target.z; + shift( + &a.x, &a.z, &a.y, g_Camera.target.x, g_Camera.target.z, + g_Camera.target.y, top, left, bottom, right); + shift( + &b.x, &b.z, &b.y, g_Camera.target.x, g_Camera.target.z, + g_Camera.target.y, top, right, bottom, left); + } else if (target->x > bottom && !good_bottom) { + noclip = 0; + prefer_a = target->z < g_Camera.target.z; + shift( + &a.x, &a.z, &a.y, g_Camera.target.x, g_Camera.target.z, + g_Camera.target.y, bottom, left, top, right); + shift( + &b.x, &b.z, &b.y, g_Camera.target.x, g_Camera.target.z, + g_Camera.target.y, bottom, right, top, left); + } + } + } else { + if (target->x < top && !good_top) { + noclip = 0; + prefer_a = g_Camera.pos.z < g_Camera.target.z; + shift( + &a.x, &a.z, &a.y, g_Camera.target.x, g_Camera.target.z, + g_Camera.target.y, top, left, bottom, right); + shift( + &b.x, &b.z, &b.y, g_Camera.target.x, g_Camera.target.z, + g_Camera.target.y, top, right, bottom, left); + } else if (target->x > bottom && !good_bottom) { + noclip = 0; + prefer_a = g_Camera.pos.z < g_Camera.target.z; + shift( + &a.x, &a.z, &a.y, g_Camera.target.x, g_Camera.target.z, + g_Camera.target.y, bottom, left, top, right); + shift( + &b.x, &b.z, &b.y, g_Camera.target.x, g_Camera.target.z, + g_Camera.target.y, bottom, right, top, left); + } + + if (noclip) { + if (target->z < left && !good_left) { + noclip = 0; + prefer_a = target->x < g_Camera.target.x; + shift( + &a.z, &a.x, &a.y, g_Camera.target.z, g_Camera.target.x, + g_Camera.target.y, left, top, right, bottom); + shift( + &b.z, &b.x, &b.y, g_Camera.target.z, g_Camera.target.x, + g_Camera.target.y, left, bottom, right, top); + } else if (target->z > right && !good_right) { + noclip = 0; + prefer_a = target->x < g_Camera.target.x; + shift( + &a.z, &a.x, &a.y, g_Camera.target.z, g_Camera.target.x, + g_Camera.target.y, right, top, left, bottom); + shift( + &b.z, &b.x, &b.y, g_Camera.target.z, g_Camera.target.x, + g_Camera.target.y, right, bottom, left, top); + } + } + } + + if (noclip) { + return; + } + + if (prefer_a) { + prefer_a = LOS_Check(&g_Camera.target, &a) != 0; + } else { + prefer_a = LOS_Check(&g_Camera.target, &b) == 0; + } + + if (prefer_a) { + target->x = a.x; + target->y = a.y; + target->z = a.z; + } else { + target->x = b.x; + target->y = b.y; + target->z = b.z; + } + + Room_GetSector(target->x, target->y, target->z, &target->room_num); +} + +void __cdecl Camera_Chase(const ITEM *item) +{ + g_Camera.target_elevation += item->rot.x; + g_Camera.target_elevation = MIN(g_Camera.target_elevation, MAX_ELEVATION); + g_Camera.target_elevation = MAX(g_Camera.target_elevation, -MAX_ELEVATION); + + int32_t distance = + (g_Camera.target_distance * Math_Cos(g_Camera.target_elevation)) + >> W2V_SHIFT; + int16_t angle = g_Camera.target_angle + item->rot.y; + + g_Camera.target_square = SQUARE(distance); + + const XYZ_32 offset = { + .y = (g_Camera.target_distance * Math_Sin(g_Camera.target_elevation)) + >> W2V_SHIFT, + .x = -((distance * Math_Sin(angle)) >> W2V_SHIFT), + .z = -((distance * Math_Cos(angle)) >> W2V_SHIFT), + }; + + GAME_VECTOR target = { + .x = g_Camera.target.x + offset.x, + .y = g_Camera.target.y + offset.y, + .z = g_Camera.target.z + offset.z, + .room_num = g_Camera.pos.room_num, + }; + + Camera_SmartShift(&target, Camera_Shift); + Camera_Move(&target, g_Camera.speed); +} + +int32_t __cdecl Camera_ShiftClamp(GAME_VECTOR *pos, int32_t clamp) +{ + int32_t x = pos->x; + int32_t y = pos->y; + int32_t z = pos->z; + const SECTOR *sector = Room_GetSector(x, y, z, &pos->room_num); + const BOX_INFO *box = &g_Boxes[sector->box]; + + int32_t left = ((int32_t)box->left << WALL_SHIFT) + clamp; + int32_t right = ((int32_t)box->right << WALL_SHIFT) - clamp - 1; + if (z < left && !Camera_GoodPosition(x, y, z - clamp, pos->room_num)) { + pos->z = left; + } else if ( + z > right && !Camera_GoodPosition(x, y, z + clamp, pos->room_num)) { + pos->z = right; + } + + int32_t top = ((int32_t)box->top << WALL_SHIFT) + clamp; + int32_t bottom = ((int32_t)box->bottom << WALL_SHIFT) - clamp - 1; + if (x < top && !Camera_GoodPosition(x - clamp, y, z, pos->room_num)) { + pos->x = top; + } else if ( + x > bottom && !Camera_GoodPosition(x + clamp, y, z, pos->room_num)) { + pos->x = bottom; + } + + int32_t height = Room_GetHeight(sector, x, y, z) - clamp; + int32_t ceiling = Room_GetCeiling(sector, x, y, z) + clamp; + if (height < ceiling) { + ceiling = (height + ceiling) >> 1; + height = ceiling; + } + + if (y > height) { + return height - y; + } + if (y < ceiling) { + return ceiling - y; + } + return 0; +} + +void __cdecl Camera_Combat(const ITEM *item) +{ + g_Camera.target.z = item->pos.z; + g_Camera.target.x = item->pos.x; + + g_Camera.target_distance = COMBAT_DISTANCE; + if (g_Lara.target) { + g_Camera.target_angle = g_Lara.target_angles[0] + item->rot.y; + g_Camera.target_elevation = g_Lara.target_angles[1] + item->rot.x; + } else { + g_Camera.target_angle = + g_Lara.torso_y_rot + g_Lara.head_y_rot + item->rot.y; + g_Camera.target_elevation = + g_Lara.torso_x_rot + g_Lara.head_x_rot + item->rot.x; + } + + int32_t distance = + (COMBAT_DISTANCE * Math_Cos(g_Camera.target_elevation)) >> W2V_SHIFT; + int16_t angle = g_Camera.target_angle; + + const XYZ_32 offset = { + .y = + +((g_Camera.target_distance * Math_Sin(g_Camera.target_elevation)) + >> W2V_SHIFT), + .x = -((distance * Math_Sin(angle)) >> W2V_SHIFT), + .z = -((distance * Math_Cos(angle)) >> W2V_SHIFT), + }; + + GAME_VECTOR target = { + .x = g_Camera.target.x + offset.x, + .y = g_Camera.target.y + offset.y, + .z = g_Camera.target.z + offset.z, + .room_num = g_Camera.pos.room_num, + }; + + if (g_Lara.water_status == LWS_UNDERWATER) { + int32_t water_height = g_Lara.water_surface_dist + g_LaraItem->pos.y; + if (g_Camera.target.y > water_height && water_height > target.y) { + target.y = g_Lara.water_surface_dist + g_LaraItem->pos.y; + target.z = g_Camera.target.z + + (water_height - g_Camera.target.y) + * (target.z - g_Camera.target.z) + / (target.y - g_Camera.target.y); + target.x = g_Camera.target.x + + (water_height - g_Camera.target.y) + * (target.x - g_Camera.target.x) + / (target.y - g_Camera.target.y); + } + } + + Camera_SmartShift(&target, Camera_Shift); + Camera_Move(&target, g_Camera.speed); +} + +void __cdecl Camera_Look(const ITEM *item) +{ + XYZ_32 old = { + .x = g_Camera.target.x, + .y = g_Camera.target.y, + .z = g_Camera.target.z, + }; + + g_Camera.target.z = item->pos.z; + g_Camera.target.x = item->pos.x; + g_Camera.target_angle = + item->rot.y + g_Lara.torso_y_rot + g_Lara.head_y_rot; + g_Camera.target_distance = LOOK_DISTANCE; + g_Camera.target_elevation = + g_Lara.torso_x_rot + g_Lara.head_x_rot + item->rot.x; + + int32_t distance = + (LOOK_DISTANCE * Math_Cos(g_Camera.target_elevation)) >> W2V_SHIFT; + + g_Camera.shift = + (-STEP_L * 2 * Math_Sin(g_Camera.target_elevation)) >> W2V_SHIFT; + g_Camera.target.z += (g_Camera.shift * Math_Cos(item->rot.y)) >> W2V_SHIFT; + g_Camera.target.x += (g_Camera.shift * Math_Sin(item->rot.y)) >> W2V_SHIFT; + + if (!Camera_GoodPosition( + g_Camera.target.x, g_Camera.target.y, g_Camera.target.z, + g_Camera.target.room_num)) { + g_Camera.target.x = item->pos.x; + g_Camera.target.z = item->pos.z; + } + + g_Camera.target.y += Camera_ShiftClamp(&g_Camera.target, LOOK_CLAMP); + + const XYZ_32 offset = { + .y = + +((g_Camera.target_distance * Math_Sin(g_Camera.target_elevation)) + >> W2V_SHIFT), + .x = -((distance * Math_Sin(g_Camera.target_angle)) >> W2V_SHIFT), + .z = -((distance * Math_Cos(g_Camera.target_angle)) >> W2V_SHIFT), + }; + + GAME_VECTOR target = { + .x = g_Camera.target.x + offset.x, + .y = g_Camera.target.y + offset.y, + .z = g_Camera.target.z + offset.z, + .room_num = g_Camera.pos.room_num, + }; + + Camera_SmartShift(&target, Camera_Clip); + g_Camera.target.z = old.z + (g_Camera.target.z - old.z) / g_Camera.speed; + g_Camera.target.x = old.x + (g_Camera.target.x - old.x) / g_Camera.speed; + Camera_Move(&target, g_Camera.speed); +} + +void __cdecl Camera_Fixed(void) +{ + const OBJECT_VECTOR *fixed = &g_Camera.fixed[g_Camera.num]; + GAME_VECTOR target = { + .x = fixed->x, + .y = fixed->y, + .z = fixed->z, + .room_num = fixed->data, + }; + if (!LOS_Check(&g_Camera.target, &target)) { + Camera_ShiftClamp(&target, STEP_L); + } + + g_Camera.fixed_camera = 1; + Camera_Move(&target, g_Camera.speed); + + if (g_Camera.timer) { + g_Camera.timer--; + if (!g_Camera.timer) { + g_Camera.timer = -1; + } + } +} + +void __cdecl Camera_Update(void) +{ + if (g_Rooms[g_Camera.pos.room_num].flags & RF_UNDERWATER) { + Sound_Effect(SFX_UNDERWATER, NULL, SPM_ALWAYS); + if (!g_Camera.underwater) { + Music_SetVolume(0); + g_Camera.underwater = 1; + } + } else if (g_Camera.underwater) { + if (g_OptionMusicVolume) { + Music_SetVolume(25 * g_OptionMusicVolume + 5); + } + g_Camera.underwater = 0; + } + + if (g_Camera.type == CAM_CINEMATIC) { + Camera_LoadCutsceneFrame(); + return; + } + + if (g_Camera.flags != CF_NO_CHUNKY) { + g_IsChunkyCamera = 1; + } + + const int32_t fixed_camera = g_Camera.item != NULL + && (g_Camera.type == CAM_FIXED || g_Camera.type == CAM_HEAVY); + const ITEM *const item = fixed_camera ? g_Camera.item : g_LaraItem; + + const BOUNDS_16 *bounds = Item_GetBoundsAccurate(item); + + int32_t y = item->pos.y; + if (fixed_camera) { + y += (bounds->min_y + bounds->max_y) / 2; + } else { + y += bounds->max_y + + (((int32_t)(bounds->min_y - bounds->max_y)) * 3 >> 2); + } + + if (g_Camera.item && !fixed_camera) { + bounds = Item_GetBoundsAccurate(g_Camera.item); + + const int32_t dx = g_Camera.item->pos.x - item->pos.x; + const int32_t dz = g_Camera.item->pos.z - item->pos.z; + const int32_t shift = Math_Sqrt(SQUARE(dx) + SQUARE(dz)); + int16_t angle = Math_Atan(dz, dx) - item->rot.y; + + int16_t tilt = Math_Atan( + shift, + y - (bounds->min_y + bounds->max_y) / 2 - g_Camera.item->pos.y); + angle >>= 1; + tilt >>= 1; + + if (angle > MIN_HEAD_ROTATION_CAM && angle < MAX_HEAD_ROTATION_CAM + && tilt > MIN_HEAD_TILT_CAM && tilt < MAX_HEAD_TILT_CAM) { + int16_t change = angle - g_Lara.head_y_rot; + if (change > HEAD_TURN_CAM) { + g_Lara.head_y_rot += HEAD_TURN_CAM; + } else if (change < -HEAD_TURN_CAM) { + g_Lara.head_y_rot -= HEAD_TURN_CAM; + } else { + g_Lara.head_y_rot = angle; + } + + change = tilt - g_Lara.head_x_rot; + if (change > HEAD_TURN_CAM) { + g_Lara.head_x_rot += HEAD_TURN_CAM; + } else if (change < -HEAD_TURN_CAM) { + g_Lara.head_x_rot -= HEAD_TURN_CAM; + } else { + g_Lara.head_x_rot += change; + } + g_Lara.torso_x_rot = g_Lara.head_x_rot; + g_Lara.torso_y_rot = g_Lara.head_y_rot; + g_Camera.type = CAM_LOOK; + g_Camera.item->looked_at = 1; + } + } + + if (g_Camera.type == CAM_LOOK || g_Camera.type == CAM_COMBAT) { + y -= STEP_L; + g_Camera.target.room_num = item->room_num; + if (g_Camera.fixed_camera) { + g_Camera.target.y = y; + g_Camera.speed = 1; + } else { + g_Camera.target.y += (y - g_Camera.target.y) >> 2; + g_Camera.speed = + g_Camera.type == CAM_LOOK ? LOOK_SPEED : COMBAT_SPEED; + } + g_Camera.fixed_camera = 0; + if (g_Camera.type == CAM_LOOK) { + Camera_Look(item); + } else { + Camera_Combat(item); + } + } else { + g_Camera.target.x = item->pos.x; + g_Camera.target.z = item->pos.z; + + if (g_Camera.flags == CF_FOLLOW_CENTRE) { + const int32_t shift = (bounds->min_z + bounds->max_z) / 2; + g_Camera.target.z += (shift * Math_Cos(item->rot.y)) >> W2V_SHIFT; + g_Camera.target.x += (shift * Math_Sin(item->rot.y)) >> W2V_SHIFT; + } + + g_Camera.target.room_num = item->room_num; + if (g_Camera.fixed_camera != fixed_camera) { + g_Camera.target.y = y; + g_Camera.fixed_camera = 1; + g_Camera.speed = 1; + } else { + g_Camera.fixed_camera = 0; + g_Camera.target.y += (y - g_Camera.target.y) / 4; + } + + const SECTOR *const sector = Room_GetSector( + g_Camera.target.x, y, g_Camera.target.z, &g_Camera.target.room_num); + const int32_t height = Room_GetHeight( + sector, g_Camera.target.x, g_Camera.target.y, g_Camera.target.z); + if (g_Camera.target.y > height) { + g_IsChunkyCamera = 0; + } + + if (g_Camera.type == CAM_CHASE || g_Camera.flags == CF_CHASE_OBJECT) { + Camera_Chase(item); + } else { + Camera_Fixed(); + } + } + + g_Camera.last = g_Camera.num; + g_Camera.fixed_camera = fixed_camera; + if (g_Camera.type != CAM_HEAVY || g_Camera.timer == -1) { + g_Camera.type = CAM_CHASE; + g_Camera.speed = CHASE_SPEED; + g_Camera.num = NO_CAMERA; + g_Camera.last_item = g_Camera.item; + g_Camera.item = NULL; + g_Camera.target_elevation = 0; + g_Camera.target_angle = 0; + g_Camera.target_distance = CHASE_ELEVATION; + g_Camera.flags = CF_NORMAL; + } + g_IsChunkyCamera = 0; +} + +void __cdecl Camera_LoadCutsceneFrame(void) +{ + g_CineFrameIdx++; + if (g_CineFrameIdx >= g_NumCineFrames) { + g_CineFrameIdx = g_NumCineFrames - 1; + } + + const CINE_FRAME *frame = &g_CineData[g_CineFrameIdx]; + int32_t tx = frame->tx; + int32_t ty = frame->ty; + int32_t tz = frame->tz; + int32_t cx = frame->cx; + int32_t cy = frame->cy; + int32_t cz = frame->cz; + int32_t fov = frame->fov; + int32_t roll = frame->roll; + int32_t c = Math_Cos(g_CinePos.rot.y); + int32_t s = Math_Sin(g_CinePos.rot.y); + + g_Camera.target.x = g_CinePos.pos.x + ((tx * c + tz * s) >> W2V_SHIFT); + g_Camera.target.y = g_CinePos.pos.y + ty; + g_Camera.target.z = g_CinePos.pos.z + ((tz * c - tx * s) >> W2V_SHIFT); + g_Camera.pos.x = g_CinePos.pos.x + ((cx * c + cz * s) >> W2V_SHIFT); + g_Camera.pos.y = g_CinePos.pos.y + cy; + g_Camera.pos.z = g_CinePos.pos.z + ((cz * c - cx * s) >> W2V_SHIFT); + + Output_AlterFOV(fov); + Matrix_LookAt( + g_Camera.pos.x, g_Camera.pos.y, g_Camera.pos.z, g_Camera.target.x, + g_Camera.target.y, g_Camera.target.z, roll); + + Room_GetSector( + g_Camera.pos.x, g_Camera.pos.y, g_Camera.pos.z, &g_Camera.pos.room_num); + + if (g_Camera.is_lara_mic) { + g_Camera.actual_angle = + g_Lara.torso_y_rot + g_Lara.head_y_rot + g_LaraItem->rot.y; + g_Camera.mic_pos.x = g_LaraItem->pos.x; + g_Camera.mic_pos.y = g_LaraItem->pos.y; + g_Camera.mic_pos.z = g_LaraItem->pos.z; + } else { + g_Camera.actual_angle = Math_Atan( + g_Camera.target.z - g_Camera.pos.z, + g_Camera.target.x - g_Camera.pos.x); + g_Camera.mic_pos.x = g_Camera.pos.x + + ((g_PhdPersp * Math_Sin(g_Camera.actual_angle)) >> W2V_SHIFT); + g_Camera.mic_pos.z = g_Camera.pos.z + + ((g_PhdPersp * Math_Cos(g_Camera.actual_angle)) >> W2V_SHIFT); + g_Camera.mic_pos.y = g_Camera.pos.y; + } +} + +void __cdecl Camera_UpdateCutscene(void) +{ + const CINE_FRAME *frame = &g_CineData[g_CineFrameIdx]; + int32_t tx = frame->tx; + int32_t ty = frame->ty; + int32_t tz = frame->tz; + int32_t cx = frame->cx; + int32_t cy = frame->cy; + int32_t cz = frame->cz; + int32_t fov = frame->fov; + int32_t roll = frame->roll; + int32_t c = Math_Cos(g_Camera.target_angle); + int32_t s = Math_Sin(g_Camera.target_angle); + const XYZ_32 camtar = { + .x = g_LaraItem->pos.x + ((tx * c + tz * s) >> W2V_SHIFT), + .y = g_LaraItem->pos.y + ty, + .z = g_LaraItem->pos.z + ((tz * c - tx * s) >> W2V_SHIFT), + }; + const XYZ_32 campos = { + .x = g_LaraItem->pos.x + ((cx * c + cz * s) >> W2V_SHIFT), + .y = g_LaraItem->pos.y + cy, + .z = g_LaraItem->pos.z + ((cz * c - cx * s) >> W2V_SHIFT), + }; + int16_t room_num = Room_FindByPos(campos.x, campos.y, campos.z); + if (room_num != NO_ROOM_NEG) { + g_Camera.pos.room_num = room_num; + } + Output_AlterFOV(fov); + Matrix_LookAt( + campos.x, campos.y, campos.z, camtar.x, camtar.y, camtar.z, roll); +} + +void __cdecl Camera_RefreshFromTrigger(const int16_t type, const int16_t *fd) +{ + int16_t target_ok = 2; + + while (true) { + const int16_t fd_cmd = *fd++; + const int16_t value = TRIGGER_VALUE(fd_cmd); + + switch (TRIGGER_TYPE(fd_cmd)) { + case TO_CAMERA: { + if (value == g_Camera.last) { + g_Camera.num = value; + if (g_Camera.timer < 0 || g_Camera.type == CAM_LOOK + || g_Camera.type == CAM_COMBAT) { + g_Camera.timer = -1; + target_ok = 0; + } else { + g_Camera.type = CAM_FIXED; + target_ok = 1; + } + } else { + target_ok = 0; + } + break; + } + + case TO_TARGET: + if (g_Camera.type != CAM_LOOK && g_Camera.type != CAM_COMBAT) { + g_Camera.item = &g_Items[value]; + } + break; + } + + if (FLOORDATA_IS_END(fd_cmd)) { + break; + } + } + + if (g_Camera.item + && (!target_ok + || (target_ok == 2 && g_Camera.item->looked_at + && g_Camera.item != g_Camera.last_item))) { + g_Camera.item = NULL; + } + + if (g_Camera.num == -1 && g_Camera.timer > 0) { + g_Camera.timer = -1; + } +} diff --git a/src/tr2/game/camera.h b/src/tr2/game/camera.h new file mode 100644 index 000000000..5f8b27bec --- /dev/null +++ b/src/tr2/game/camera.h @@ -0,0 +1,30 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Camera_Initialise(void); +void __cdecl Camera_ResetPosition(void); +void __cdecl Camera_Move(const GAME_VECTOR *target, int32_t speed); +void __cdecl Camera_Clip( + int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, + int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom); +void __cdecl Camera_Shift( + int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, + int32_t target_h, int32_t left, int32_t top, int32_t right, int32_t bottom); +const SECTOR *__cdecl Camera_GoodPosition( + int32_t x, int32_t y, int32_t z, int16_t room_num); +void __cdecl Camera_SmartShift( + GAME_VECTOR *target, + void(__cdecl *shift)( + int32_t *x, int32_t *y, int32_t *h, int32_t target_x, int32_t target_y, + int32_t target_h, int32_t left, int32_t top, int32_t right, + int32_t bottom)); +void __cdecl Camera_Chase(const ITEM *item); +int32_t __cdecl Camera_ShiftClamp(GAME_VECTOR *pos, int32_t clamp); +void __cdecl Camera_Combat(const ITEM *item); +void __cdecl Camera_Look(const ITEM *item); +void __cdecl Camera_Fixed(void); +void __cdecl Camera_Update(void); +void __cdecl Camera_LoadCutsceneFrame(void); +void __cdecl Camera_UpdateCutscene(void); +void __cdecl Camera_RefreshFromTrigger(int16_t type, const int16_t *fd); diff --git a/src/tr2/game/clock.c b/src/tr2/game/clock.c new file mode 100644 index 000000000..b43fbe6fa --- /dev/null +++ b/src/tr2/game/clock.c @@ -0,0 +1,12 @@ +#include "game/clock.h" + +#include + +double Clock_GetHighPrecisionCounter(void) +{ + LARGE_INTEGER frequency; + LARGE_INTEGER counter; + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&counter); + return counter.QuadPart * 1000.0 / frequency.QuadPart; +} diff --git a/src/tr2/game/clock.h b/src/tr2/game/clock.h new file mode 100644 index 000000000..5b4d9b743 --- /dev/null +++ b/src/tr2/game/clock.h @@ -0,0 +1,3 @@ +#pragma once + +double Clock_GetHighPrecisionCounter(void); diff --git a/src/tr2/game/collide.c b/src/tr2/game/collide.c new file mode 100644 index 000000000..6d7753d77 --- /dev/null +++ b/src/tr2/game/collide.c @@ -0,0 +1,449 @@ +#include "game/collide.h" + +#include "game/math.h" +#include "game/room.h" +#include "global/funcs.h" +#include "global/vars.h" + +void __cdecl Collide_GetCollisionInfo( + COLL_INFO *const coll, const int32_t x_pos, const int32_t y_pos, + const int32_t z_pos, int16_t room_num, const int32_t obj_height) +{ + coll->coll_type = COLL_NONE; + coll->shift.x = 0; + coll->shift.y = 0; + coll->shift.z = 0; + coll->quadrant = Math_GetDirection(coll->facing); + + const SECTOR *sector; + int32_t height; + int32_t ceiling; + int32_t x; + int32_t z; + const int32_t y = y_pos - obj_height; + const int32_t y_top = y - 160; + + x = x_pos; + z = z_pos; + sector = Room_GetSector(x, y_top, z, &room_num); + height = Room_GetHeight(sector, x, y_top, z); + if (height != NO_HEIGHT) { + height -= y_pos; + } + ceiling = Room_GetCeiling(sector, x, y_top, z); + if (ceiling != NO_HEIGHT) { + ceiling -= y; + } + + coll->side_mid.floor = height; + coll->side_mid.ceiling = ceiling; + coll->side_mid.type = g_HeightType; + coll->trigger = g_TriggerIndex; + + const int16_t tilt = Room_GetTiltType(sector, x, g_LaraItem->pos.y, z); + coll->z_tilt = tilt >> 8; + coll->x_tilt = (int8_t)tilt; + + int32_t x_left; + int32_t z_left; + int32_t x_right; + int32_t z_right; + int32_t x_front; + int32_t z_front; + switch (coll->quadrant) { + case DIR_NORTH: + x_front = (coll->radius * Math_Sin(coll->facing)) >> W2V_SHIFT; + z_front = coll->radius; + x_left = -coll->radius; + z_left = coll->radius; + x_right = coll->radius; + z_right = coll->radius; + break; + + case DIR_EAST: + x_front = coll->radius; + z_front = (coll->radius * Math_Cos(coll->facing)) >> W2V_SHIFT; + x_left = coll->radius; + z_left = coll->radius; + x_right = coll->radius; + z_right = -coll->radius; + break; + + case DIR_SOUTH: + x_front = (coll->radius * Math_Sin(coll->facing)) >> W2V_SHIFT; + z_front = -coll->radius; + x_left = coll->radius; + z_left = -coll->radius; + x_right = -coll->radius; + z_right = -coll->radius; + break; + + case DIR_WEST: + x_front = -coll->radius; + z_front = (coll->radius * Math_Cos(coll->facing)) >> W2V_SHIFT; + x_left = -coll->radius; + z_left = -coll->radius; + x_right = -coll->radius; + z_right = coll->radius; + break; + + default: + x_front = 0; + z_front = 0; + x_left = 0; + z_left = 0; + x_right = 0; + z_right = 0; + break; + } + + x = x_pos + x_front; + z = z_pos + z_front; + sector = Room_GetSector(x, y_top, z, &room_num); + height = Room_GetHeight(sector, x, y_top, z); + if (height != NO_HEIGHT) { + height -= y_pos; + } + ceiling = Room_GetCeiling(sector, x, y_top, z); + if (ceiling != NO_HEIGHT) { + ceiling -= y; + } + + coll->side_front.floor = height; + coll->side_front.ceiling = ceiling; + coll->side_front.type = g_HeightType; + if (coll->slopes_are_walls && coll->side_front.type == HT_BIG_SLOPE + && coll->side_front.floor < 0) { + coll->side_front.floor = -32767; + } else if ( + coll->slopes_are_pits && coll->side_front.type == HT_BIG_SLOPE + && coll->side_front.floor > 0) { + coll->side_front.floor = 512; + } else if ( + coll->lava_is_pit && coll->side_front.floor > 0 + && g_TriggerIndex != NULL + && FLOORDATA_TYPE(g_TriggerIndex[0]) == FT_LAVA) { + coll->side_front.floor = 512; + } + + x = x_pos + x_left; + z = z_pos + z_left; + sector = Room_GetSector(x, y_top, z, &room_num); + height = Room_GetHeight(sector, x, y_top, z); + if (height != NO_HEIGHT) { + height -= y_pos; + } + ceiling = Room_GetCeiling(sector, x, y_top, z); + if (ceiling != NO_HEIGHT) { + ceiling -= y; + } + + coll->side_left.floor = height; + coll->side_left.ceiling = ceiling; + coll->side_left.type = g_HeightType; + if (coll->slopes_are_walls && coll->side_left.type == HT_BIG_SLOPE + && coll->side_left.floor < 0) { + coll->side_left.floor = -32767; + } else if ( + coll->slopes_are_pits && coll->side_left.type == HT_BIG_SLOPE + && coll->side_left.floor > 0) { + coll->side_left.floor = 512; + } else if ( + coll->lava_is_pit && coll->side_left.floor > 0 && g_TriggerIndex != NULL + && FLOORDATA_TYPE(g_TriggerIndex[0]) == FT_LAVA) { + coll->side_left.floor = 512; + } + + x = x_pos + x_right; + z = z_pos + z_right; + sector = Room_GetSector(x, y_top, z, &room_num); + height = Room_GetHeight(sector, x, y_top, z); + if (height != NO_HEIGHT) { + height -= y_pos; + } + ceiling = Room_GetCeiling(sector, x, y_top, z); + if (ceiling != NO_HEIGHT) { + ceiling -= y; + } + + coll->side_right.floor = height; + coll->side_right.ceiling = ceiling; + coll->side_right.type = g_HeightType; + if (coll->slopes_are_walls && coll->side_right.type == HT_BIG_SLOPE + && coll->side_right.floor < 0) { + coll->side_right.floor = -32767; + } else if ( + coll->slopes_are_pits && coll->side_right.type == HT_BIG_SLOPE + && coll->side_right.floor > 0) { + coll->side_right.floor = 512; + } else if ( + coll->lava_is_pit && coll->side_right.floor > 0 + && g_TriggerIndex != NULL + && FLOORDATA_TYPE(g_TriggerIndex[0]) == FT_LAVA) { + coll->side_right.floor = 512; + } + + if (Collide_CollideStaticObjects( + coll, x_pos, y_pos, z_pos, room_num, obj_height)) { + const XYZ_32 test_pos = { + .x = x_pos + coll->shift.x, + .y = y_pos, + .z = z_pos + coll->shift.z, + }; + sector = Room_GetSector(test_pos.x, test_pos.y, test_pos.z, &room_num); + if (Room_GetHeight(sector, test_pos.x, test_pos.y, test_pos.z) + < test_pos.y - WALL_L / 2 + || Room_GetCeiling(sector, test_pos.x, test_pos.y, test_pos.z) + > y) { + coll->shift.x = -coll->shift.x; + coll->shift.z = -coll->shift.z; + } + } + + if (coll->side_mid.floor == NO_HEIGHT) { + coll->shift.x = coll->old.x - x_pos; + coll->shift.y = coll->old.y - y_pos; + coll->shift.z = coll->old.z - z_pos; + coll->coll_type = COLL_FRONT; + return; + } + + if (coll->side_mid.floor - coll->side_mid.ceiling <= 0) { + coll->shift.x = coll->old.x - x_pos; + coll->shift.y = coll->old.y - y_pos; + coll->shift.z = coll->old.z - z_pos; + coll->coll_type = COLL_CLAMP; + return; + } + + if (coll->side_mid.ceiling >= 0) { + coll->shift.y = coll->side_mid.ceiling; + coll->coll_type = COLL_TOP; + } + + if (coll->side_front.floor > coll->bad_pos + || coll->side_front.floor < coll->bad_neg + || coll->side_front.ceiling > coll->bad_ceiling) { + switch (coll->quadrant) { + case DIR_NORTH: + case DIR_SOUTH: + coll->shift.x = coll->old.x - x_pos; + coll->shift.z = Room_FindGridShift(z_pos + z_front, z_pos); + break; + + case DIR_EAST: + case DIR_WEST: + coll->shift.x = Room_FindGridShift(x_pos + x_front, x_pos); + coll->shift.z = coll->old.z - z_pos; + break; + } + + coll->coll_type = COLL_FRONT; + return; + } + + if (coll->side_front.ceiling >= coll->bad_ceiling) { + coll->shift.x = coll->old.x - x_pos; + coll->shift.y = coll->old.y - y_pos; + coll->shift.z = coll->old.z - z_pos; + coll->coll_type = COLL_TOP_FRONT; + return; + } + + if (coll->side_left.floor > coll->bad_pos + || coll->side_left.floor < coll->bad_neg) { + switch (coll->quadrant) { + case DIR_NORTH: + case DIR_SOUTH: + coll->shift.x = Room_FindGridShift(x_pos + x_left, x_pos + x_front); + break; + + case DIR_EAST: + case DIR_WEST: + coll->shift.z = Room_FindGridShift(z_pos + z_left, z_pos + z_front); + break; + } + + coll->coll_type = COLL_LEFT; + return; + } + + if (coll->side_right.floor > coll->bad_pos + || coll->side_right.floor < coll->bad_neg) { + switch (coll->quadrant) { + case DIR_NORTH: + case DIR_SOUTH: + coll->shift.x = + Room_FindGridShift(x_pos + x_right, x_pos + x_front); + break; + + case DIR_EAST: + case DIR_WEST: + coll->shift.z = + Room_FindGridShift(z_pos + z_right, z_pos + z_front); + break; + } + + coll->coll_type = COLL_RIGHT; + return; + } +} + +int32_t __cdecl Collide_CollideStaticObjects( + COLL_INFO *const coll, const int32_t x, const int32_t y, const int32_t z, + const int16_t room_num, const int32_t height) +{ + coll->hit_static = 0; + + const int32_t in_x_min = x - coll->radius; + const int32_t in_x_max = x + coll->radius; + const int32_t in_y_min = y - height; + const int32_t in_y_max = y; + const int32_t in_z_min = z - coll->radius; + const int32_t in_z_max = z + coll->radius; + XYZ_32 shifter = { .x = 0, .z = 0 }; + + Room_GetNearbyRooms(x, y, z, coll->radius + 50, height + 50, room_num); + + for (int32_t i = 0; i < g_DrawRoomsCount; i++) { + const ROOM *const room = &g_Rooms[g_DrawRoomsArray[i]]; + + for (int32_t j = 0; j < room->num_meshes; j++) { + const MESH *const mesh = &room->meshes[j]; + const STATIC_INFO *const sinfo = &g_StaticObjects[mesh->static_num]; + + if (sinfo->flags & 1) { + continue; + } + + int32_t x_min; + int32_t x_max; + int32_t z_min; + int32_t z_max; + const int32_t y_min = mesh->pos.y + sinfo->collision_bounds.min_y; + const int32_t y_max = mesh->pos.y + sinfo->collision_bounds.max_y; + switch (mesh->rot.y) { + case PHD_90: + x_min = mesh->pos.x + sinfo->collision_bounds.min_z; + x_max = mesh->pos.x + sinfo->collision_bounds.max_z; + z_min = mesh->pos.z - sinfo->collision_bounds.max_x; + z_max = mesh->pos.z - sinfo->collision_bounds.min_x; + break; + + case -PHD_180: + x_min = mesh->pos.x - sinfo->collision_bounds.max_x; + x_max = mesh->pos.x - sinfo->collision_bounds.min_x; + z_min = mesh->pos.z - sinfo->collision_bounds.max_z; + z_max = mesh->pos.z - sinfo->collision_bounds.min_z; + break; + + case -PHD_90: + x_min = mesh->pos.x - sinfo->collision_bounds.max_z; + x_max = mesh->pos.x - sinfo->collision_bounds.min_z; + z_min = mesh->pos.z + sinfo->collision_bounds.min_x; + z_max = mesh->pos.z + sinfo->collision_bounds.max_x; + break; + + default: + x_min = mesh->pos.x + sinfo->collision_bounds.min_x; + x_max = mesh->pos.x + sinfo->collision_bounds.max_x; + z_min = mesh->pos.z + sinfo->collision_bounds.min_z; + z_max = mesh->pos.z + sinfo->collision_bounds.max_z; + break; + } + + if (in_x_max <= x_min || in_x_min >= x_max || in_y_max <= y_min + || in_y_min >= y_max || in_z_max <= z_min + || in_z_min >= z_max) { + continue; + } + + int32_t shl = in_x_max - x_min; + int32_t shr = x_max - in_x_min; + if (shl < shr) { + shifter.x = -shl; + } else { + shifter.x = shr; + } + + shl = in_z_max - z_min; + shr = z_max - in_z_min; + if (shl < shr) { + shifter.z = -shl; + } else { + shifter.z = shr; + } + + switch (coll->quadrant) { + case DIR_NORTH: + if (shifter.x > coll->radius || shifter.x < -coll->radius) { + coll->coll_type = COLL_FRONT; + coll->shift.x = coll->old.x - x; + coll->shift.z = shifter.z; + } else if (shifter.x > 0) { + coll->coll_type = COLL_LEFT; + coll->shift.x = shifter.x; + coll->shift.z = 0; + } else if (shifter.x < 0) { + coll->coll_type = COLL_RIGHT; + coll->shift.x = shifter.x; + coll->shift.z = 0; + } + break; + + case DIR_EAST: + if (shifter.z > coll->radius || shifter.z < -coll->radius) { + coll->coll_type = COLL_FRONT; + coll->shift.x = shifter.x; + coll->shift.z = coll->old.z - z; + } else if (shifter.z > 0) { + coll->coll_type = COLL_RIGHT; + coll->shift.x = 0; + coll->shift.z = shifter.z; + } else if (shifter.z < 0) { + coll->coll_type = COLL_LEFT; + coll->shift.x = 0; + coll->shift.z = shifter.z; + } + break; + + case DIR_SOUTH: + if (shifter.x > coll->radius || shifter.x < -coll->radius) { + coll->coll_type = COLL_FRONT; + coll->shift.x = coll->old.x - x; + coll->shift.z = shifter.z; + } else if (shifter.x > 0) { + coll->coll_type = COLL_RIGHT; + coll->shift.x = shifter.x; + coll->shift.z = 0; + } else if (shifter.x < 0) { + coll->coll_type = COLL_LEFT; + coll->shift.x = shifter.x; + coll->shift.z = 0; + } + break; + + case DIR_WEST: + if (shifter.z > coll->radius || shifter.z < -coll->radius) { + coll->coll_type = COLL_FRONT; + coll->shift.x = shifter.x; + coll->shift.z = coll->old.z - z; + } else if (shifter.z > 0) { + coll->coll_type = COLL_LEFT; + coll->shift.x = 0; + coll->shift.z = shifter.z; + } else if (shifter.z < 0) { + coll->coll_type = COLL_RIGHT; + coll->shift.x = 0; + coll->shift.z = shifter.z; + } + break; + } + + coll->hit_static = 1; + return 1; + } + } + + return 0; +} diff --git a/src/tr2/game/collide.h b/src/tr2/game/collide.h new file mode 100644 index 000000000..61bc93fcb --- /dev/null +++ b/src/tr2/game/collide.h @@ -0,0 +1,11 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Collide_GetCollisionInfo( + COLL_INFO *coll, int32_t x_pos, int32_t y_pos, int32_t z_pos, + int16_t room_num, int32_t obj_height); + +int32_t __cdecl Collide_CollideStaticObjects( + COLL_INFO *coll, int32_t x, int32_t y, int32_t z, int16_t room_num, + int32_t height); diff --git a/src/tr2/game/console/common.c b/src/tr2/game/console/common.c new file mode 100644 index 000000000..67479a58d --- /dev/null +++ b/src/tr2/game/console/common.c @@ -0,0 +1,19 @@ +#include "game/console/common.h" + +#include "game/console/setup.h" +#include "game/text.h" + +int32_t Console_GetMaxLineLength(void) +{ + return TEXT_MAX_STRING_SIZE - 1; +} + +CONSOLE_COMMAND **Console_GetCommands(void) +{ + return g_ConsoleCommands; +} + +void Console_DrawBackdrop(void) +{ + // TODO: implement me +} diff --git a/src/tr2/game/console/common.h b/src/tr2/game/console/common.h new file mode 100644 index 000000000..ff45ef5a5 --- /dev/null +++ b/src/tr2/game/console/common.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void Console_Draw(void); diff --git a/src/tr2/game/console/setup.c b/src/tr2/game/console/setup.c new file mode 100644 index 000000000..fcb77c1f1 --- /dev/null +++ b/src/tr2/game/console/setup.c @@ -0,0 +1,44 @@ +#include "game/console/setup.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +CONSOLE_COMMAND *g_ConsoleCommands[] = { + &g_Console_Cmd_Teleport, + &g_Console_Cmd_Fly, + &g_Console_Cmd_FlipMap, + &g_Console_Cmd_Kill, + &g_Console_Cmd_EndLevel, + &g_Console_Cmd_PlayLevel, + &g_Console_Cmd_LoadGame, + &g_Console_Cmd_SaveGame, + &g_Console_Cmd_PlayDemo, + &g_Console_Cmd_ExitToTitle, + &g_Console_Cmd_ExitGame, + &g_Console_Cmd_Die, + &g_Console_Cmd_Config, + &g_Console_Cmd_Pos, + &g_Console_Cmd_Heal, + &g_Console_Cmd_SetHealth, + &g_Console_Cmd_GiveItem, + &g_Console_Cmd_SFX, + NULL, +}; diff --git a/src/tr2/game/console/setup.h b/src/tr2/game/console/setup.h new file mode 100644 index 000000000..11f2f9671 --- /dev/null +++ b/src/tr2/game/console/setup.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern CONSOLE_COMMAND *g_ConsoleCommands[]; diff --git a/src/tr2/game/creature.c b/src/tr2/game/creature.c new file mode 100644 index 000000000..9898ef7d9 --- /dev/null +++ b/src/tr2/game/creature.c @@ -0,0 +1,956 @@ +#include "game/creature.h" + +#include "decomp/effects.h" +#include "game/box.h" +#include "game/items.h" +#include "game/lara/misc.h" +#include "game/los.h" +#include "game/lot.h" +#include "game/math.h" +#include "game/objects/common.h" +#include "game/objects/vars.h" +#include "game/random.h" +#include "game/room.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#define FRONT_ARC PHD_90 +#define ESCAPE_CHANCE 2048 +#define RECOVER_CHANCE 256 +#define MAX_X_ROT (20 * PHD_DEGREE) // = 3640 +#define MAX_TILT (3 * PHD_DEGREE) // = 546 +#define MAX_HEAD_CHANGE (5 * PHD_DEGREE) // = 910 +#define HEAD_ARC 0x3000 // = 12288 +#define FLOAT_SPEED 32 +#define TARGET_TOLERANCE 0x400000 + +#define CREATURE_ATTACK_RANGE SQUARE(WALL_L * 3) // = 0x900000 = 9437184 +#define CREATURE_SHOOT_RANGE SQUARE(WALL_L * 8) // = 0x4000000 = 67108864 + +void __cdecl Creature_Initialise(const int16_t item_num) +{ + ITEM *const item = &g_Items[item_num]; + item->rot.y += (Random_GetControl() - PHD_90) >> 1; + item->collidable = 1; + item->data = 0; +} + +int32_t __cdecl Creature_Activate(const int16_t item_num) +{ + ITEM *const item = &g_Items[item_num]; + if (item->status != IS_INVISIBLE) { + return true; + } + + if (!LOT_EnableBaddieAI(item_num, false)) { + return false; + } + + item->status = IS_ACTIVE; + return true; +} + +void __cdecl Creature_AIInfo(ITEM *const item, AI_INFO *const info) +{ + CREATURE *const creature = (CREATURE *)item->data; + if (creature == NULL) { + return; + } + + switch (item->object_id) { + case O_BANDIT_1: + case O_BANDIT_2: + Creature_GetBaddieTarget(creature->item_num, false); + break; + + case O_MONK_1: + case O_MONK_2: + Creature_GetBaddieTarget(creature->item_num, true); + break; + + default: + creature->enemy = g_LaraItem; + break; + } + + ITEM *enemy = creature->enemy; + if (enemy == NULL) { + enemy = g_LaraItem; + } + + int16_t *zone; + if (creature->lot.fly) { + zone = g_FlyZone[g_FlipStatus]; + } else { + zone = g_GroundZone[BOX_ZONE(creature->lot.step)][g_FlipStatus]; + } + + { + const ROOM *const r = &g_Rooms[item->room_num]; + const int32_t z_sector = (item->pos.z - r->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (item->pos.x - r->pos.x) >> WALL_SHIFT; + item->box_num = r->sectors[z_sector + x_sector * r->size.z].box; + info->zone_num = zone[item->box_num]; + } + + { + const ROOM *const r = &g_Rooms[enemy->room_num]; + const int32_t z_sector = (enemy->pos.z - r->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (enemy->pos.x - r->pos.x) >> WALL_SHIFT; + enemy->box_num = r->sectors[z_sector + x_sector * r->size.z].box; + info->enemy_zone_num = zone[enemy->box_num]; + } + + if (((g_Boxes[enemy->box_num].overlap_index & creature->lot.block_mask) + != 0) + || (creature->lot.node[item->box_num].search_num + == (creature->lot.search_num | BOX_BLOCKED_SEARCH))) { + info->enemy_zone_num |= BOX_BLOCKED; + } + + const OBJECT *const object = &g_Objects[item->object_id]; + const int32_t z = enemy->pos.z + - ((object->pivot_length * Math_Cos(item->rot.y)) >> W2V_SHIFT) + - item->pos.z; + const int32_t x = enemy->pos.x + - ((object->pivot_length * Math_Sin(item->rot.y)) >> W2V_SHIFT) + - item->pos.x; + PHD_ANGLE angle = Math_Atan(z, x); + if (creature->enemy != NULL) { + info->distance = SQUARE(x) + SQUARE(z); + } else { + info->distance = 0x7FFFFFFF; + } + + info->angle = angle - item->rot.y; + info->enemy_facing = angle - enemy->rot.y + PHD_180; + info->ahead = info->angle > -FRONT_ARC && info->angle < FRONT_ARC; + info->bite = info->ahead && enemy->hit_points > 0 + && ABS(enemy->pos.y - item->pos.y) <= STEP_L; +} + +void __cdecl Creature_Mood( + const ITEM *item, const AI_INFO *info, int32_t violent) +{ + CREATURE *const creature = item->data; + if (creature == NULL) { + return; + } + + const LOT_INFO *const lot = &creature->lot; + const ITEM *enemy = creature->enemy; + if (creature->lot.node[item->box_num].search_num + == (creature->lot.search_num | 0x8000)) { + creature->lot.required_box = NO_BOX; + } + if (creature->mood != MOOD_ATTACK && creature->lot.required_box != NO_BOX + && !Box_ValidBox(item, info->zone_num, creature->lot.target_box)) { + if (info->zone_num == info->enemy_zone_num) { + creature->mood = MOOD_BORED; + } + creature->lot.required_box = NO_BOX; + } + + const MOOD_TYPE mood = creature->mood; + if (enemy == NULL) { + creature->mood = MOOD_BORED; + enemy = g_LaraItem; + } else if (enemy->hit_points <= 0) { + creature->mood = MOOD_BORED; + } else if (violent) { + switch (mood) { + case MOOD_BORED: + case MOOD_STALK: + if (info->zone_num == info->enemy_zone_num) { + creature->mood = MOOD_ATTACK; + } else if (item->hit_status != 0) { + creature->mood = MOOD_ESCAPE; + } + break; + + case MOOD_ATTACK: + if (info->zone_num != info->enemy_zone_num) { + creature->mood = MOOD_BORED; + } + break; + + case MOOD_ESCAPE: + if (info->zone_num == info->enemy_zone_num) { + creature->mood = MOOD_ATTACK; + } + break; + } + } else { + switch (mood) { + case MOOD_BORED: + case MOOD_STALK: + if (item->hit_status != 0 + && (Random_GetControl() < ESCAPE_CHANCE + || info->zone_num != info->enemy_zone_num)) { + creature->mood = MOOD_ESCAPE; + } else if (info->zone_num == info->enemy_zone_num) { + if (info->distance < CREATURE_ATTACK_RANGE + || (creature->mood == MOOD_STALK + && creature->lot.required_box == NO_BOX)) { + creature->mood = MOOD_ATTACK; + } else { + creature->mood = MOOD_STALK; + } + } + break; + + case MOOD_ATTACK: + if (item->hit_status != 0 + && (Random_GetControl() < ESCAPE_CHANCE + || info->zone_num != info->enemy_zone_num)) { + creature->mood = MOOD_ESCAPE; + } else if (info->zone_num != info->enemy_zone_num) { + creature->mood = MOOD_BORED; + } + break; + + case MOOD_ESCAPE: + if (info->zone_num == info->enemy_zone_num + && Random_GetControl() < RECOVER_CHANCE) { + creature->mood = MOOD_STALK; + } + break; + } + } + + if (mood != creature->mood) { + if (mood == MOOD_ATTACK) { + Box_TargetBox(&creature->lot, creature->lot.target_box); + } + creature->lot.required_box = NO_BOX; + } + + switch (creature->mood) { + case MOOD_BORED: { + const int16_t box_num = + lot->node[(creature->lot.zone_count * Random_GetControl()) >> 15] + .box_num; + if (Box_ValidBox(item, info->zone_num, box_num)) { + if (Box_StalkBox(item, enemy, box_num) && creature->enemy != NULL + && enemy->hit_points > 0) { + Box_TargetBox(&creature->lot, box_num); + creature->mood = MOOD_STALK; + } else if (creature->lot.required_box == NO_BOX) { + Box_TargetBox(&creature->lot, box_num); + } + } + break; + } + + case MOOD_ATTACK: + creature->lot.target = enemy->pos; + creature->lot.required_box = enemy->box_num; + if (creature->lot.fly != 0 && g_Lara.water_status == LWS_ABOVE_WATER) { + creature->lot.target.y += Item_GetBestFrame(enemy)->bounds.min_y; + } + break; + + case MOOD_ESCAPE: { + const int16_t box_num = + lot->node[(creature->lot.zone_count * Random_GetControl()) >> 15] + .box_num; + if (Box_ValidBox(item, info->zone_num, box_num) + && creature->lot.required_box == NO_BOX) { + if (Box_EscapeBox(item, enemy, box_num)) { + Box_TargetBox(&creature->lot, box_num); + } else if ( + info->zone_num == info->enemy_zone_num + && Box_StalkBox(item, enemy, box_num)) { + Box_TargetBox(&creature->lot, box_num); + creature->mood = MOOD_STALK; + } + } + break; + } + + case MOOD_STALK: { + int16_t box_num = creature->lot.required_box; + if (box_num == NO_BOX || !Box_StalkBox(item, enemy, box_num)) { + box_num = + lot->node + [(creature->lot.zone_count * Random_GetControl()) >> 15] + .box_num; + if (Box_ValidBox(item, info->zone_num, box_num)) { + if (Box_StalkBox(item, enemy, box_num)) { + Box_TargetBox(&creature->lot, box_num); + } else if (creature->lot.required_box == NO_BOX) { + Box_TargetBox(&creature->lot, box_num); + if (info->zone_num != info->enemy_zone_num) { + creature->mood = MOOD_BORED; + } + } + } + } + break; + } + } + + if (creature->lot.target_box == NO_BOX) { + Box_TargetBox(&creature->lot, item->box_num); + } + Box_CalculateTarget(&creature->target, item, &creature->lot); +} + +int32_t __cdecl Creature_CheckBaddieOverlap(const int16_t item_num) +{ + ITEM *item = &g_Items[item_num]; + + const int32_t x = item->pos.x; + const int32_t y = item->pos.y; + const int32_t z = item->pos.z; + const int32_t radius = SQUARE(g_Objects[item->object_id].radius); + + int16_t link = g_Rooms[item->room_num].item_num; + while (link != NO_ITEM && link != item_num) { + item = &g_Items[link]; + if (item != g_LaraItem && item->status == IS_ACTIVE + && item->speed != 0) { + const int32_t distance = + (SQUARE(item->pos.z - z) + SQUARE(item->pos.y - y) + + SQUARE(item->pos.x - x)); + if (distance < radius) { + return true; + } + } + + link = item->next_item; + } + + return false; +} + +void __cdecl Creature_Die(const int16_t item_num, const bool explode) +{ + ITEM *const item = &g_Items[item_num]; + + if (item->object_id == O_DRAGON_FRONT) { + item->hit_points = 0; + return; + } + + if (item->object_id == O_SKIDMAN) { + if (explode) { + Effect_ExplodingDeath(item_num, -1, 0); + } + const int16_t vehicle_item_num = (int16_t)(intptr_t)item->data; + ITEM *const vehicle_item = &g_Items[vehicle_item_num]; + vehicle_item->hit_points = 0; + return; + } + + item->collidable = 0; + item->hit_points = DONT_TARGET; + if (explode) { + Effect_ExplodingDeath(item_num, -1, 0); + Item_Kill(item_num); + } else { + Item_RemoveActive(item_num); + } + + if (g_Objects[item->object_id].intelligent) { + LOT_DisableBaddieAI(item_num); + } + item->flags |= IF_ONE_SHOT; + + if (item->killed) { + item->next_active = g_PrevItemActive; + g_PrevItemActive = item_num; + } + + if (g_Objects[item->object_id].intelligent) { + int16_t pickup_num = item->carried_item; + while (pickup_num != NO_ITEM) { + ITEM *const pickup = &g_Items[pickup_num]; + pickup->pos = item->pos; + Item_NewRoom(pickup_num, item->room_num); + pickup_num = pickup->carried_item; + } + } +} + +int32_t __cdecl Creature_Animate( + const int16_t item_num, const int16_t angle, const int16_t tilt) +{ + ITEM *const item = &g_Items[item_num]; + const CREATURE *const creature = item->data; + const OBJECT *const object = &g_Objects[item->object_id]; + if (creature == NULL) { + return false; + } + + const LOT_INFO *const lot = &creature->lot; + const XYZ_32 old = item->pos; + + int16_t *zone; + if (lot->fly) { + zone = g_FlyZone[g_FlipStatus]; + } else { + zone = g_GroundZone[BOX_ZONE(lot->step)][g_FlipStatus]; + } + + if (!object->water_creature) { + int16_t room_num = item->room_num; + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + if (room_num != item->room_num) { + Item_NewRoom(item_num, room_num); + } + } + + Item_Animate(item); + if (item->status == IS_DEACTIVATED) { + Creature_Die(item_num, false); + return false; + } + + const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); + int32_t y = item->pos.y + bounds->min_y; + + int16_t room_num = item->room_num; + Room_GetSector(old.x, y, old.z, &room_num); + const SECTOR *sector = + Room_GetSector(item->pos.x, y, item->pos.z, &room_num); + int32_t height = g_Boxes[sector->box].height; + int16_t next_box = lot->node[sector->box].exit_box; + int32_t next_height = + next_box != NO_BOX ? g_Boxes[next_box].height : height; + + const int32_t box_height = g_Boxes[item->box_num].height; + if (sector->box == NO_BOX || zone[item->box_num] != zone[sector->box] + || box_height - height > lot->step || box_height - height < lot->drop) { + const int32_t pos_x = item->pos.x >> WALL_SHIFT; + const int32_t shift_x = old.x >> WALL_SHIFT; + const int32_t shift_z = old.z >> WALL_SHIFT; + + if (pos_x < shift_x) { + item->pos.x = old.x & (~(WALL_L - 1)); + } else if (pos_x > shift_x) { + item->pos.x = old.x | (WALL_L - 1); + } + + if (pos_x < shift_z) { + item->pos.z = old.z & (~(WALL_L - 1)); + } else if (pos_x > shift_z) { + item->pos.z = old.z | (WALL_L - 1); + } + + sector = Room_GetSector(item->pos.x, y, item->pos.z, &room_num); + height = g_Boxes[sector->box].height; + next_box = lot->node[sector->box].exit_box; + next_height = next_box != NO_BOX ? g_Boxes[next_box].height : height; + } + + const int32_t x = item->pos.x; + const int32_t z = item->pos.z; + const int32_t pos_x = x & (WALL_L - 1); + const int32_t pos_z = z & (WALL_L - 1); + int32_t shift_x = 0; + int32_t shift_z = 0; + const int32_t radius = object->radius; + + if (pos_z < radius) { + if (Box_BadFloor( + x, y, z - radius, height, next_height, room_num, lot)) { + shift_z = radius - pos_z; + } + + if (pos_x < radius) { + if (Box_BadFloor( + x - radius, y, z, height, next_height, room_num, lot)) { + shift_x = radius - pos_x; + } else if ( + !shift_z + && Box_BadFloor( + x - radius, y, z - radius, height, next_height, room_num, + lot)) { + if (item->rot.y > -PHD_135 && item->rot.y < PHD_45) { + shift_z = radius - pos_z; + } else { + shift_x = radius - pos_x; + } + } + } else if (pos_x > WALL_L - radius) { + if (Box_BadFloor( + x + radius, y, z, height, next_height, room_num, lot)) { + shift_x = WALL_L - radius - pos_x; + } else if ( + !shift_z + && Box_BadFloor( + x + radius, y, z - radius, height, next_height, room_num, + lot)) { + if (item->rot.y > -PHD_45 && item->rot.y < PHD_135) { + shift_z = radius - pos_z; + } else { + shift_x = WALL_L - radius - pos_x; + } + } + } + } else if (pos_z > WALL_L - radius) { + if (Box_BadFloor( + x, y, z + radius, height, next_height, room_num, lot)) { + shift_z = WALL_L - radius - pos_z; + } + + if (pos_x < radius) { + if (Box_BadFloor( + x - radius, y, z, height, next_height, room_num, lot)) { + shift_x = radius - pos_x; + } else if ( + !shift_z + && Box_BadFloor( + x - radius, y, z + radius, height, next_height, room_num, + lot)) { + if (item->rot.y > -PHD_45 && item->rot.y < PHD_135) { + shift_x = radius - pos_x; + } else { + shift_z = WALL_L - radius - pos_z; + } + } + } else if (pos_x > WALL_L - radius) { + if (Box_BadFloor( + x + radius, y, z, height, next_height, room_num, lot)) { + shift_x = WALL_L - radius - pos_x; + } else if ( + !shift_z + && Box_BadFloor( + x + radius, y, z + radius, height, next_height, room_num, + lot)) { + if (item->rot.y > -PHD_135 && item->rot.y < PHD_45) { + shift_x = WALL_L - radius - pos_x; + } else { + shift_z = WALL_L - radius - pos_z; + } + } + } + } else if (pos_x < radius) { + if (Box_BadFloor( + x - radius, y, z, height, next_height, room_num, lot)) { + shift_x = radius - pos_x; + } + } else if (pos_x > WALL_L - radius) { + if (Box_BadFloor( + x + radius, y, z, height, next_height, room_num, lot)) { + shift_x = WALL_L - radius - pos_x; + } + } + + item->pos.x += shift_x; + item->pos.z += shift_z; + + if (shift_x || shift_z) { + sector = Room_GetSector(item->pos.x, y, item->pos.z, &room_num); + item->rot.y += angle; + if (tilt != 0) { + Creature_Tilt(item, tilt * 2); + } + } + + if (Creature_CheckBaddieOverlap(item_num)) { + item->pos = old; + return true; + } + + if (lot->fly) { + int32_t dy = creature->target.y - item->pos.y; + CLAMP(dy, -lot->fly, lot->fly); + + height = Room_GetHeight(sector, item->pos.x, y, item->pos.z); + if (item->pos.y + dy <= height) { + const int32_t ceiling = + Room_GetCeiling(sector, item->pos.x, y, item->pos.z); + const int32_t min_y = + item->object_id == O_SHARK ? 128 : bounds->min_y; + if (item->pos.y + min_y + dy < ceiling) { + if (item->pos.y + min_y < ceiling) { + item->pos.x = old.x; + item->pos.z = old.z; + dy = lot->fly; + } else { + dy = 0; + } + } + } else if (item->pos.y <= height) { + item->pos.y = height; + dy = 0; + } else { + item->pos.x = old.x; + item->pos.z = old.z; + dy = -lot->fly; + } + + item->pos.y += dy; + sector = Room_GetSector(item->pos.x, y, item->pos.z, &room_num); + item->floor = Room_GetHeight(sector, item->pos.x, y, item->pos.z); + + int16_t angle = item->speed != 0 ? Math_Atan(item->speed, -dy) : 0; + CLAMP(angle, -MAX_X_ROT, MAX_X_ROT); + + if (angle < item->rot.x - PHD_DEGREE) { + item->rot.x -= PHD_DEGREE; + } else if (angle > item->rot.x + PHD_DEGREE) { + item->rot.x += PHD_DEGREE; + } else { + item->rot.x = angle; + } + } else { + const SECTOR *const sector = + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + item->floor = + Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); + + if (item->pos.y > item->floor) { + item->pos.y = item->floor; + } else if (item->floor - item->pos.y > STEP_L / 4) { + item->pos.y += STEP_L / 4; + } else if (item->pos.y < item->floor) { + item->pos.y = item->floor; + } + item->rot.x = 0; + } + + if (!object->water_creature) { + Room_GetSector( + item->pos.x, item->pos.y - (STEP_L * 2), item->pos.z, &room_num); + if (g_Rooms[room_num].flags & RF_UNDERWATER) { + item->hit_points = 0; + } + } + + if (item->room_num != room_num) { + Item_NewRoom(item_num, room_num); + } + return true; +} + +int16_t __cdecl Creature_Turn(ITEM *const item, int16_t max_turn) +{ + const CREATURE *const creature = item->data; + if (creature == NULL || item->speed == 0 || max_turn == 0) { + return 0; + } + + const int32_t dx = creature->target.x - item->pos.x; + const int32_t dz = creature->target.z - item->pos.z; + + int16_t angle = Math_Atan(dz, dx) - item->rot.y; + if (angle > FRONT_ARC || angle < -FRONT_ARC) { + const int32_t range = (item->speed << 14) / max_turn; + if (SQUARE(dx) + SQUARE(dz) < SQUARE(range)) { + max_turn /= 2; + } + } + + CLAMP(angle, -max_turn, max_turn); + item->rot.y += angle; + return angle; +} + +void __cdecl Creature_Tilt(ITEM *const item, int16_t angle) +{ + angle = 4 * angle - item->rot.z; + CLAMP(angle, -MAX_TILT, MAX_TILT); + item->rot.z += angle; +} + +void __cdecl Creature_Head(ITEM *item, int16_t required) +{ + CREATURE *const creature = item->data; + if (creature == NULL) { + return; + } + + int16_t change = required - creature->head_rotation; + CLAMP(change, -MAX_HEAD_CHANGE, MAX_HEAD_CHANGE); + + creature->head_rotation += change; + CLAMP(creature->head_rotation, -HEAD_ARC, HEAD_ARC); +} + +void __cdecl Creature_Neck(ITEM *const item, const int16_t required) +{ + CREATURE *const creature = item->data; + if (creature == NULL) { + return; + } + + int16_t change = required - creature->neck_rotation; + CLAMP(change, -MAX_HEAD_CHANGE, MAX_HEAD_CHANGE); + + creature->neck_rotation += change; + CLAMP(creature->neck_rotation, -HEAD_ARC, HEAD_ARC); +} + +void __cdecl Creature_Float(const int16_t item_num) +{ + ITEM *const item = &g_Items[item_num]; + + item->hit_points = DONT_TARGET; + item->rot.x = 0; + + const int32_t wh = Room_GetWaterHeight( + item->pos.x, item->pos.y, item->pos.z, item->room_num); + if (item->pos.y > wh) { + item->pos.y -= FLOAT_SPEED; + } else if (item->pos.y < wh) { + item->pos.y = wh; + } + + Item_Animate(item); + + int16_t room_num = item->room_num; + const SECTOR *const sector = + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + item->floor = Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); + if (room_num != item->room_num) { + Item_NewRoom(item_num, room_num); + } +} + +void __cdecl Creature_Underwater(ITEM *const item, const int32_t depth) +{ + const int32_t wh = Room_GetWaterHeight( + item->pos.x, item->pos.y, item->pos.z, item->room_num); + if (item->pos.y >= wh + depth) { + return; + } + + item->pos.y = wh + depth; + if (item->rot.x > 2 * PHD_DEGREE) { + item->rot.x -= 2 * PHD_DEGREE; + } else { + CLAMPG(item->rot.x, 0); + } +} + +int16_t __cdecl Creature_Effect( + const ITEM *const item, const BITE *const bite, + int16_t(__cdecl *const spawn)( + int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, + int16_t room_num)) +{ + XYZ_32 pos = bite->pos; + Collide_GetJointAbsPosition(item, &pos, bite->mesh_num); + return (*spawn)( + pos.x, pos.y, pos.z, item->speed, item->rot.y, item->room_num); +} + +int32_t __cdecl Creature_Vault( + const int16_t item_num, const int16_t angle, int32_t vault, + const int32_t shift) +{ + ITEM *const item = &g_Items[item_num]; + const int16_t room_num = item->room_num; + const XYZ_32 old = item->pos; + + Creature_Animate(item_num, angle, 0); + + if (item->floor > old.y + STEP_L * 7 / 2) { + vault = -4; + } else if (item->pos.y > old.y - STEP_L * 3 / 2) { + return 0; + } else if (item->pos.y > old.y - STEP_L * 5 / 2) { + vault = 2; + } else if (item->pos.y > old.y - STEP_L * 7 / 2) { + vault = 3; + } else { + vault = 4; + } + + const int32_t old_x_sector = old.x >> WALL_SHIFT; + const int32_t old_z_sector = old.z >> WALL_SHIFT; + const int32_t x_sector = item->pos.x >> WALL_SHIFT; + const int32_t z_sector = item->pos.z >> WALL_SHIFT; + if (old_z_sector == z_sector) { + if (old_x_sector == x_sector) { + return 0; + } + + if (old_x_sector >= x_sector) { + item->rot.y = -PHD_90; + item->pos.x = (old_x_sector << WALL_SHIFT) + shift; + } else { + item->rot.y = PHD_90; + item->pos.x = (x_sector << WALL_SHIFT) - shift; + } + } else if (old_x_sector == x_sector) { + if (old_z_sector >= z_sector) { + item->rot.y = PHD_180; + item->pos.z = (old_z_sector << WALL_SHIFT) + shift; + } else { + item->rot.y = 0; + item->pos.z = (z_sector << WALL_SHIFT) - shift; + } + } + + item->floor = old.y; + item->pos.y = old.y; + + if (room_num != item->room_num) { + Item_NewRoom(item_num, room_num); + } + return vault; +} + +void __cdecl Creature_Kill( + ITEM *const item, const int32_t kill_anim, const int32_t kill_state, + const int32_t lara_kill_state) +{ + item->anim_num = g_Objects[item->object_id].anim_idx + kill_anim; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->current_anim_state = kill_state; + + g_LaraItem->anim_num = g_Objects[O_LARA_EXTRA].anim_idx; + g_LaraItem->frame_num = g_Anims[g_LaraItem->anim_num].frame_base; + g_LaraItem->current_anim_state = LA_EXTRA_BREATH; + g_LaraItem->goal_anim_state = lara_kill_state; + g_LaraItem->pos = item->pos; + g_LaraItem->rot = item->rot; + g_LaraItem->fall_speed = 0; + g_LaraItem->gravity = 0; + g_LaraItem->speed = 0; + + int16_t room_num = item->room_num; + if (room_num != g_LaraItem->room_num) { + Item_NewRoom(g_Lara.item_num, room_num); + } + + Item_Animate(g_LaraItem); + + g_LaraItem->goal_anim_state = lara_kill_state; + g_LaraItem->current_anim_state = lara_kill_state; + g_Lara.extra_anim = 1; + g_Lara.gun_status = LGS_HANDS_BUSY; + g_Lara.gun_type = LGT_UNARMED; + g_Lara.hit_direction = -1; + g_Lara.air = -1; + + g_Camera.pos.room_num = g_LaraItem->room_num; +} + +void __cdecl Creature_GetBaddieTarget( + const int16_t item_num, const int32_t goody) +{ + ITEM *const item = &g_Items[item_num]; + CREATURE *const creature = item->data; + + ITEM *best_item = NULL; + int32_t best_distance = INT32_MAX; + for (int32_t i = 0; i < NUM_SLOTS; i++) { + const int16_t target_item_num = g_BaddieSlots[i].item_num; + if (target_item_num == NO_ITEM || target_item_num == item_num) { + continue; + } + + ITEM *target = &g_Items[target_item_num]; + const GAME_OBJECT_ID object_id = target->object_id; + if (goody && object_id != O_BANDIT_1 && object_id != O_BANDIT_2) { + continue; + } else if (!goody && object_id != O_MONK_1 && object_id != O_MONK_2) { + continue; + } + + const int32_t dx = (target->pos.x - item->pos.x) >> 6; + const int32_t dy = (target->pos.y - item->pos.y) >> 6; + const int32_t dz = (target->pos.z - item->pos.z) >> 6; + const int32_t distance = SQUARE(dx) + SQUARE(dy) + SQUARE(dz); + if (distance < best_distance) { + best_item = target; + best_distance = distance; + } + } + + if (best_item == NULL) { + if (!goody || g_IsMonkAngry) { + creature->enemy = g_LaraItem; + } else { + creature->enemy = NULL; + } + return; + } + + if (!goody || g_IsMonkAngry) { + const int32_t dx = (g_LaraItem->pos.x - item->pos.x) >> 6; + const int32_t dy = (g_LaraItem->pos.y - item->pos.y) >> 6; + const int32_t dz = (g_LaraItem->pos.z - item->pos.z) >> 6; + const int32_t distance = SQUARE(dx) + SQUARE(dy) + SQUARE(dz); + if (distance < best_distance) { + best_item = g_LaraItem; + best_distance = distance; + } + } + + const ITEM *const target = creature->enemy; + if (target == NULL || target->status != IS_ACTIVE) { + creature->enemy = best_item; + } else { + const int32_t dx = (target->pos.x - item->pos.x) >> 6; + const int32_t dy = (target->pos.y - item->pos.y) >> 6; + const int32_t dz = (target->pos.z - item->pos.z) >> 6; + const int32_t distance = SQUARE(dz) + SQUARE(dy) + SQUARE(dx); + if (distance < best_distance + TARGET_TOLERANCE) { + creature->enemy = best_item; + } + } +} + +void __cdecl Creature_Collision( + const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) +{ + ITEM *const item = &g_Items[item_num]; + if (!Item_TestBoundsCollide(item, lara_item, coll->radius)) { + return; + } + + if (!Collide_TestCollision(item, lara_item)) { + return; + } + + if (coll->enable_baddie_push && g_Lara.water_status != LWS_UNDERWATER + && g_Lara.water_status != LWS_SURFACE) { + Lara_Push(item, lara_item, coll, coll->enable_spaz, false); + } +} + +int32_t __cdecl Creature_CanTargetEnemy( + const ITEM *const item, const AI_INFO *const info) +{ + const CREATURE *const creature = item->data; + const ITEM *const enemy = creature->enemy; + if (enemy->hit_points <= 0 || !info->ahead + || info->distance >= CREATURE_SHOOT_RANGE) { + return 0; + } + + GAME_VECTOR start; + start.pos.x = item->pos.x; + start.pos.y = item->pos.y - STEP_L * 3; + start.pos.z = item->pos.z; + start.room_num = item->room_num; + + GAME_VECTOR target; + target.pos.x = enemy->pos.x; + target.pos.y = enemy->pos.y - STEP_L * 3; + target.pos.z = enemy->pos.z; + target.room_num = enemy->room_num; + + return LOS_Check(&start, &target); +} + +bool Creature_IsEnemy(const ITEM *const item) +{ + return Object_IsObjectType(item->object_id, g_EnemyObjects) + || (g_IsMonkAngry + && (item->object_id == O_MONK_1 || item->object_id == O_MONK_2)); +} + +bool Creature_IsAlly(const ITEM *const item) +{ + return Object_IsObjectType(item->object_id, g_AllyObjects); +} diff --git a/src/tr2/game/creature.h b/src/tr2/game/creature.h new file mode 100644 index 000000000..186020433 --- /dev/null +++ b/src/tr2/game/creature.h @@ -0,0 +1,35 @@ +#pragma once + +#include "global/types.h" + +#include + +void __cdecl Creature_Initialise(int16_t item_num); +int32_t __cdecl Creature_Activate(int16_t item_num); +void __cdecl Creature_AIInfo(ITEM *item, AI_INFO *info); +void __cdecl Creature_Mood( + const ITEM *item, const AI_INFO *info, int32_t violent); +int32_t __cdecl Creature_CheckBaddieOverlap(int16_t item_num); +void __cdecl Creature_Die(int16_t item_num, bool explode); +int32_t __cdecl Creature_Animate(int16_t item_num, int16_t angle, int16_t tilt); +int16_t __cdecl Creature_Turn(ITEM *item, int16_t max_turn); +void __cdecl Creature_Tilt(ITEM *item, int16_t angle); +void __cdecl Creature_Head(ITEM *item, int16_t required); +void __cdecl Creature_Neck(ITEM *item, int16_t required); +void __cdecl Creature_Float(int16_t item_num); +void __cdecl Creature_Underwater(ITEM *item, int32_t depth); +int16_t __cdecl Creature_Effect( + const ITEM *item, const BITE *bite, + int16_t(__cdecl *spawn)( + int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, + int16_t room_num)); +int32_t __cdecl Creature_Vault( + int16_t item_num, int16_t angle, int32_t vault, int32_t shift); +void __cdecl Creature_Kill( + ITEM *item, int32_t kill_anim, int32_t kill_state, int32_t lara_kill_state); +void __cdecl Creature_GetBaddieTarget(int16_t item_num, int32_t goody); +void __cdecl Creature_Collision( + int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +int32_t __cdecl Creature_CanTargetEnemy(const ITEM *item, const AI_INFO *info); +bool Creature_IsEnemy(const ITEM *item); +bool Creature_IsAlly(const ITEM *item); diff --git a/src/tr2/game/demo.c b/src/tr2/game/demo.c new file mode 100644 index 000000000..4112588c9 --- /dev/null +++ b/src/tr2/game/demo.c @@ -0,0 +1,137 @@ +#include "game/demo.h" + +#include "decomp/decomp.h" +#include "game/game.h" +#include "game/gameflow.h" +#include "game/items.h" +#include "game/lara/cheat.h" +#include "game/random.h" +#include "game/room.h" +#include "game/shell.h" +#include "game/text.h" +#include "global/funcs.h" +#include "global/vars.h" + +static int32_t m_DemoLevel = 0; +static int32_t m_DemoLevel2 = 0; +static int32_t m_OldDemoInputDB = 0; + +int32_t __cdecl Demo_Control(int32_t level_num) +{ + if (level_num < 0 && !g_GameFlow.num_demos) { + return GFD_EXIT_TO_TITLE; + } + + if (level_num < 0) { + if (m_DemoLevel >= g_GameFlow.num_demos) { + m_DemoLevel = 0; + } + level_num = g_GF_ValidDemos[m_DemoLevel]; + m_DemoLevel++; + } else { + m_DemoLevel = level_num; + } + + return GF_DoLevelSequence(level_num, GFL_DEMO); +} + +int32_t __cdecl Demo_Start(int32_t level_num) +{ + if (level_num < 0 && !g_GameFlow.num_demos) { + return GFD_EXIT_TO_TITLE; + } + + if (level_num >= 0) { + m_DemoLevel2 = level_num; + } else { + if (m_DemoLevel2 >= g_GameFlow.num_demos) { + m_DemoLevel2 = 0; + } + level_num = g_GF_ValidDemos[m_DemoLevel2]; + m_DemoLevel2++; + } + + START_INFO *const s = &g_SaveGame.start[level_num]; + START_INFO start = *s; + s->available = 1; + s->has_pistols = 1; + s->pistol_ammo = 1000; + s->gun_status = LGS_ARMLESS; + s->gun_type = LGT_PISTOLS; + Random_SeedDraw(0xD371F947); + Random_SeedControl(0xD371F947); + + g_IsTitleLoaded = false; + if (!Level_Initialise(level_num, GFL_DEMO)) { + return GFD_EXIT_GAME; + } + + g_LevelComplete = false; + if (!g_IsDemoLoaded) { + Shell_ExitSystemFmt( + "Level '%s' has no demo data!", g_GF_LevelFileNames[level_num]); + } + + Demo_LoadLaraPos(); + Lara_Cheat_GetStuff(); + Random_SeedDraw(0xD371F947); + Random_SeedControl(0xD371F947); + + TEXTSTRING *const text = Text_Create( + 0, g_DumpHeight / 2 - 16, 0, g_GF_PCStrings[GF_S_PC_DEMO_MODE]); + + Text_Flash(text, true, 20); + Text_CentreV(text, true); + Text_CentreH(text, true); + + m_OldDemoInputDB = 0; + g_Inv_DemoMode = true; + GAME_FLOW_DIR dir = Game_Loop(true); + g_Inv_DemoMode = false; + + Text_Remove(text); + + *s = start; + S_FadeToBlack(); + if (dir == GFD_OVERRIDE) { + dir = g_GF_OverrideDir; + g_GF_OverrideDir = (GAME_FLOW_DIR)-1; + } + return dir; +} + +void __cdecl Demo_LoadLaraPos(void) +{ + g_LaraItem->pos.x = g_DemoPtr[0]; + g_LaraItem->pos.y = g_DemoPtr[1]; + g_LaraItem->pos.z = g_DemoPtr[2]; + g_LaraItem->rot.x = g_DemoPtr[3]; + g_LaraItem->rot.y = g_DemoPtr[4]; + g_LaraItem->rot.z = g_DemoPtr[5]; + int16_t room_num = g_DemoPtr[6]; + if (g_LaraItem->room_num != room_num) { + Item_NewRoom(g_Lara.item_num, room_num); + } + + const SECTOR *const sector = Room_GetSector( + g_LaraItem->pos.x, g_LaraItem->pos.y, g_LaraItem->pos.z, &room_num); + g_LaraItem->floor = Room_GetHeight( + sector, g_LaraItem->pos.x, g_LaraItem->pos.y, g_LaraItem->pos.z); + g_Lara.last_gun_type = g_DemoPtr[7]; + + g_DemoCount += 8; +} + +void __cdecl Demo_GetInput(void) +{ + if (g_DemoCount >= MAX_DEMO_SIZE) { + g_Input = -1; + } else { + g_Input = g_DemoPtr[g_DemoCount]; + } + if (g_Input != -1) { + g_InputDB = g_Input & ~m_OldDemoInputDB; + m_OldDemoInputDB = g_Input; + g_DemoCount++; + } +} diff --git a/src/tr2/game/demo.h b/src/tr2/game/demo.h new file mode 100644 index 000000000..b5ecdded7 --- /dev/null +++ b/src/tr2/game/demo.h @@ -0,0 +1,8 @@ +#pragma once + +#include "global/types.h" + +int32_t __cdecl Demo_Control(int32_t level_num); +int32_t __cdecl Demo_Start(int32_t level_num); +void __cdecl Demo_LoadLaraPos(void); +void __cdecl Demo_GetInput(void); diff --git a/src/tr2/game/effects.c b/src/tr2/game/effects.c new file mode 100644 index 000000000..e45067831 --- /dev/null +++ b/src/tr2/game/effects.c @@ -0,0 +1,155 @@ +#include "game/effects.h" + +#include "game/matrix.h" +#include "game/output.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +static void M_RemoveActive(const int16_t fx_num); +static void M_RemoveDrawn(const int16_t fx_num); + +static void M_RemoveActive(const int16_t fx_num) +{ + FX *const fx = &g_Effects[fx_num]; + int16_t link_num = g_NextEffectActive; + if (link_num == fx_num) { + g_NextEffectActive = fx->next_active; + return; + } + + while (link_num != NO_ITEM) { + if (g_Effects[link_num].next_active == fx_num) { + g_Effects[link_num].next_active = fx->next_active; + return; + } + link_num = g_Effects[link_num].next_active; + } +} + +static void M_RemoveDrawn(const int16_t fx_num) +{ + FX *const fx = &g_Effects[fx_num]; + int16_t link_num = g_Rooms[fx->room_num].fx_num; + if (link_num == fx_num) { + g_Rooms[fx->room_num].fx_num = fx->next_free; + return; + } + + while (link_num != NO_ITEM) { + if (g_Effects[link_num].next_free == fx_num) { + g_Effects[link_num].next_free = fx->next_free; + return; + } + link_num = g_Effects[link_num].next_free; + } +} + +void __cdecl Effect_InitialiseArray(void) +{ + g_NextEffectFree = 0; + g_NextEffectActive = NO_ITEM; + + for (int32_t i = 0; i < MAX_EFFECTS - 1; i++) { + FX *const fx = &g_Effects[i]; + fx->next_free = i + 1; + } + g_Effects[MAX_EFFECTS - 1].next_free = NO_ITEM; +} + +int16_t __cdecl Effect_Create(const int16_t room_num) +{ + int16_t fx_num = g_NextEffectFree; + if (fx_num == NO_ITEM) { + return NO_ITEM; + } + + FX *const fx = &g_Effects[fx_num]; + g_NextEffectFree = fx->next_free; + + ROOM *const room = &g_Rooms[room_num]; + fx->room_num = room_num; + fx->next_free = room->fx_num; + room->fx_num = fx_num; + + fx->next_active = g_NextEffectActive; + g_NextEffectActive = fx_num; + + fx->shade = 0x1000; + + return fx_num; +} + +void __cdecl Effect_Kill(const int16_t fx_num) +{ + FX *const fx = &g_Effects[fx_num]; + M_RemoveActive(fx_num); + M_RemoveDrawn(fx_num); + + fx->next_free = g_NextEffectFree; + g_NextEffectFree = fx_num; +} + +void __cdecl Effect_NewRoom(const int16_t fx_num, const int16_t room_num) +{ + FX *const fx = &g_Effects[fx_num]; + ROOM *room = &g_Rooms[fx->room_num]; + + int16_t link_num = room->fx_num; + if (link_num == fx_num) { + room->fx_num = fx->next_free; + } else { + while (link_num != NO_ITEM) { + if (g_Effects[link_num].next_free == fx_num) { + g_Effects[link_num].next_free = fx->next_free; + break; + } + link_num = g_Effects[link_num].next_free; + } + } + + fx->room_num = room_num; + room = &g_Rooms[room_num]; + fx->next_free = room->fx_num; + room->fx_num = fx_num; +} + +void __cdecl Effect_Draw(const int16_t fx_num) +{ + const FX *const fx = &g_Effects[fx_num]; + const OBJECT *const object = &g_Objects[fx->object_id]; + if (!object->loaded) { + return; + } + + if (fx->object_id == O_GLOW) { + Output_DrawSprite( + (fx->rot.y << 16) | (unsigned __int16)fx->rot.x, fx->pos.x, + fx->pos.y, fx->pos.z, g_Objects[O_GLOW].mesh_idx, fx->shade, + fx->frame_num); + return; + } + + if (object->mesh_count < 0) { + Output_DrawSprite( + SPRITE_ABS | (object->semi_transparent ? SPRITE_SEMITRANS : 0) + | SPRITE_SHADE, + fx->pos.x, fx->pos.y, fx->pos.z, object->mesh_idx - fx->frame_num, + fx->shade, 0); + return; + } + + Matrix_Push(); + Matrix_TranslateAbs(fx->pos.x, fx->pos.y, fx->pos.z); + if (g_MatrixPtr->_23 > g_PhdNearZ && g_MatrixPtr->_23 < g_PhdFarZ) { + Matrix_RotYXZ(fx->rot.y, fx->rot.x, fx->rot.z); + if (object->mesh_count) { + S_CalculateStaticLight(fx->shade); + Output_InsertPolygons(g_Meshes[object->mesh_idx], -1); + } else { + S_CalculateStaticLight(fx->shade); + Output_InsertPolygons(g_Meshes[fx->frame_num], -1); + } + } + Matrix_Pop(); +} diff --git a/src/tr2/game/effects.h b/src/tr2/game/effects.h new file mode 100644 index 000000000..f2dc307f6 --- /dev/null +++ b/src/tr2/game/effects.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +void __cdecl Effect_InitialiseArray(void); +int16_t __cdecl Effect_Create(int16_t room_num); +void __cdecl Effect_Kill(int16_t fx_num); +void __cdecl Effect_NewRoom(int16_t fx_num, int16_t room_num); +void __cdecl Effect_Draw(int16_t fx_num); diff --git a/src/tr2/game/game.c b/src/tr2/game/game.c new file mode 100644 index 000000000..21d75a8e9 --- /dev/null +++ b/src/tr2/game/game.c @@ -0,0 +1,319 @@ +#include "game/game.h" + +#include "decomp/decomp.h" +#include "game/camera.h" +#include "game/demo.h" +#include "game/gameflow/gameflow_new.h" +#include "game/input.h" +#include "game/inventory/common.h" +#include "game/lara/control.h" +#include "game/music.h" +#include "game/overlay.h" +#include "game/room_draw.h" +#include "game/sound.h" +#include "game/text.h" +#include "global/funcs.h" +#include "global/vars.h" +#include "specific/s_audio_sample.h" + +#include + +static int32_t m_FrameCount = 0; + +int32_t __cdecl Game_Control(int32_t nframes, const bool demo_mode) +{ + if (g_GF_OverrideDir != (GAME_FLOW_DIR)-1) { + return GFD_OVERRIDE; + } + + CLAMPG(nframes, MAX_FRAMES); + m_FrameCount += nframes; + + if (m_FrameCount <= 0) { + return 0; + } + + while (m_FrameCount > 0) { + // TODO: move me outta here + if (g_CD_TrackID > 0) { + Music_Init(); + } + + if (!g_GameFlow.cheat_mode_check_disabled) { + CheckCheatMode(); + } + + if (g_LevelComplete) { + return GFD_START_GAME | LV_FIRST; + } + + Input_Update(); + + if (demo_mode) { + if (g_InputDB != 0) { + return g_GameFlow.on_demo_interrupt; + } + Demo_GetInput(); + if (g_Input == -1) { + g_Input = 0; + return g_GameFlow.on_demo_end; + } + } else if (g_GameFlow.no_input_timeout) { + if (g_InputDB != 0) { + g_NoInputCounter = 0; + } else { + g_NoInputCounter++; + if (g_NoInputCounter > g_GameFlow.no_input_time) { + return GFD_START_DEMO; + } + } + } + + if (g_Lara.death_timer > DEATH_WAIT + || (g_Lara.death_timer > DEATH_WAIT_INPUT && g_Input != 0) + || g_OverlayStatus == 2) { + if (demo_mode) { + return g_GameFlow.on_death_demo_mode; + } + if (g_CurrentLevel == LV_GYM) { + return GFD_EXIT_TO_TITLE; + } + if (g_GameFlow.on_death_in_game) { + return g_GameFlow.on_death_in_game; + } + if (g_OverlayStatus == 2) { + g_OverlayStatus = 1; + const GAME_FLOW_DIR dir = Inv_Display(INV_DEATH_MODE); + if (dir != 0) { + return dir; + } + } else { + g_OverlayStatus = 2; + } + } + + if (((g_InputDB & (IN_LOAD | IN_SAVE | IN_OPTION)) + || g_OverlayStatus <= 0) + && g_Lara.death_timer == 0 && !g_Lara.extra_anim) { + if (g_OverlayStatus > 0) { + if (g_GameFlow.load_save_disabled) { + g_OverlayStatus = 0; + } else if (g_Input & IN_LOAD) { + g_OverlayStatus = -1; + } else { + g_OverlayStatus = g_Input & IN_SAVE ? -2 : 0; + } + } else { + GAME_FLOW_DIR dir; + if (g_OverlayStatus == -1) { + dir = Inv_Display(INV_LOAD_MODE); + } else if (g_OverlayStatus == -2) { + dir = Inv_Display(INV_SAVE_MODE); + } else { + dir = Inv_Display(INV_GAME_MODE); + } + if (g_GF_OverrideDir != (GAME_FLOW_DIR)-1) { + return GFD_OVERRIDE; + } + g_OverlayStatus = 1; + + if (dir != 0) { + if (g_Inv_ExtraData[0] == 1) { + if (g_CurrentLevel == LV_GYM) { + return GFD_START_GAME | LV_FIRST; + } + CreateSaveGameInfo(); + S_SaveGame( + &g_SaveGame, sizeof(SAVEGAME_INFO), + g_Inv_ExtraData[1]); + S_SaveSettings(); + } else { + return dir; + } + } + } + } + + g_DynamicLightCount = 0; + + { + int16_t item_num = g_NextItemActive; + while (item_num != NO_ITEM) { + ITEM *const item = &g_Items[item_num]; + const int16_t next = item->next_active; + const OBJECT *object = &g_Objects[item->object_id]; + if (!(item->flags & IF_KILLED) && object->control != NULL) { + object->control(item_num); + } + item_num = next; + } + } + + { + int16_t fx_num = g_NextEffectActive; + while (fx_num != NO_ITEM) { + FX *const fx = &g_Effects[fx_num]; + const OBJECT *const object = &g_Objects[fx->object_id]; + const int32_t next = fx->next_active; + if (object->control != NULL) { + object->control(fx_num); + } + fx_num = next; + } + } + + Lara_Control(0); + HairControl(0); + Camera_Update(); + Sound_UpdateEffects(); + + g_HealthBarTimer--; + if (g_CurrentLevel || g_IsAssaultTimerActive) { + g_SaveGame.statistics.timer++; + } + + m_FrameCount -= 2; + } + + return 0; +} + +int32_t __cdecl Game_Draw(void) +{ + Room_DrawAllRooms(g_Camera.pos.room_num); + Overlay_DrawGameInfo(true); + S_OutputPolyList(); + g_Camera.num_frames = S_DumpScreen(); + S_AnimateTextures(g_Camera.num_frames); + return g_Camera.num_frames; +} + +int32_t __cdecl Game_DrawCinematic(void) +{ + g_CameraUnderwater = false; + Room_DrawAllRooms(g_Camera.pos.room_num); + Text_Draw(); + S_OutputPolyList(); + g_Camera.num_frames = S_DumpScreen(); + S_AnimateTextures(g_Camera.num_frames); + return g_Camera.num_frames; +} + +int16_t __cdecl Game_Start( + const int32_t level_num, const GAMEFLOW_LEVEL_TYPE level_type) +{ + if (level_type == GFL_NORMAL || level_type == GFL_SAVED + || level_type == GFL_DEMO) { + g_CurrentLevel = level_num; + } + if (level_type != GFL_SAVED) { + ModifyStartInfo(level_num); + } + g_IsTitleLoaded = false; + if (level_type != GFL_SAVED) { + InitialiseLevelFlags(); + } + if (!Level_Initialise(level_num, level_type)) { + g_CurrentLevel = 0; + return GFD_EXIT_GAME; + } + + GAME_FLOW_DIR dir = Game_Loop(false); + if (dir == GFD_OVERRIDE) { + dir = g_GF_OverrideDir; + g_GF_OverrideDir = (GAME_FLOW_DIR)-1; + return dir; + } + if (dir == GFD_EXIT_TO_TITLE || dir == GFD_START_DEMO) { + return dir; + } + + if (dir == GFD_EXIT_GAME) { + g_CurrentLevel = 0; + return dir; + } + + if (g_LevelComplete) { + if (g_GameFlow.demo_version && g_GameFlow.single_level) { + return GFD_EXIT_TO_TITLE; + } + + if (g_CurrentLevel == LV_GYM) { + S_FadeToBlack(); + return GFD_EXIT_TO_TITLE; + } + + S_FadeInInventory(1); + return GFD_LEVEL_COMPLETE | g_CurrentLevel; + } + + S_FadeToBlack(); + if (!g_Inv_Chosen) { + return GFD_EXIT_TO_TITLE; + } + + if (g_Inv_ExtraData[0] == 0) { + S_LoadGame(&g_SaveGame, sizeof(SAVEGAME_INFO), g_Inv_ExtraData[1]); + return GFD_START_SAVED_GAME | g_Inv_ExtraData[1]; + } + + if (g_Inv_ExtraData[0] != 1) { + return GFD_EXIT_TO_TITLE; + } + + if (g_GameFlow.play_any_level) { + return g_Inv_ExtraData[1] + 1; + } + + return GFD_START_GAME | LV_FIRST; +} + +int32_t __cdecl Game_Loop(const bool demo_mode) +{ + g_OverlayStatus = 1; + Camera_Initialise(); + g_NoInputCounter = 0; + g_GameMode = demo_mode ? GAMEMODE_IN_DEMO : GAMEMODE_IN_GAME; + + GAME_FLOW_DIR dir = Game_Control(1, demo_mode); + while (dir == 0) { + const int32_t nframes = Game_Draw(); + if (g_IsGameToExit) { + dir = GFD_EXIT_GAME; + } else { + dir = Game_Control(nframes, demo_mode); + } + } + + g_GameMode = GAMEMODE_NOT_IN_GAME; + + Overlay_HideGameInfo(); + S_Audio_Sample_OutCloseAllTracks(); + Music_Stop(); + + if (g_OptionMusicVolume) { + Music_SetVolume(25 * g_OptionMusicVolume + 5); + } + + return dir; +} + +GAMEFLOW_LEVEL_TYPE Game_GetCurrentLevelType(void) +{ + return g_GameInfo.current_level.type; +} + +bool Game_IsPlayable(void) +{ + if (g_GameInfo.current_level.type == GFL_TITLE + || g_GameInfo.current_level.type == GFL_DEMO + || g_GameInfo.current_level.type == GFL_CUTSCENE) { + return false; + } + + if (!g_Objects[O_LARA].loaded || g_LaraItem == NULL) { + return false; + } + + return true; +} diff --git a/src/tr2/game/game.h b/src/tr2/game/game.h new file mode 100644 index 000000000..660899baa --- /dev/null +++ b/src/tr2/game/game.h @@ -0,0 +1,10 @@ +#pragma once + +#include "global/types.h" + +int32_t __cdecl Game_Control(int32_t nframes, bool demo_mode); +int32_t __cdecl Game_Draw(void); +int32_t __cdecl Game_DrawCinematic(void); +int16_t __cdecl Game_Start(int32_t level_num, GAMEFLOW_LEVEL_TYPE level_type); +int32_t __cdecl Game_Loop(bool demo_mode); +bool Game_IsPlayable(void); diff --git a/src/tr2/game/game_string.c b/src/tr2/game/game_string.c new file mode 100644 index 000000000..56bf3122b --- /dev/null +++ b/src/tr2/game/game_string.c @@ -0,0 +1,15 @@ +#include "game_string.h" + +#include + +void GameString_Init(void) +{ +#include "game_string.def" + +#include +} + +void GameString_Shutdown(void) +{ + GameString_Clear(); +} diff --git a/src/tr2/game/game_string.def b/src/tr2/game/game_string.def new file mode 100644 index 000000000..95cd8f270 --- /dev/null +++ b/src/tr2/game/game_string.def @@ -0,0 +1,20 @@ +GS_DEFINE(OSD_FLY_MODE_ON, "Fly mode enabled") +GS_DEFINE(OSD_FLY_MODE_OFF, "Fly mode disabled") +GS_DEFINE(OSD_GIVE_ITEM_ALL_KEYS, "Surprise! Every key item Lara needs is now in her backpack.") +GS_DEFINE(OSD_GIVE_ITEM_ALL_GUNS, "Lock'n'load - Lara's armed to the teeth!") +GS_DEFINE(OSD_GIVE_ITEM_CHEAT, "Lara's backpack just got way heavier!") +GS_DEFINE(OSD_FLIPMAP_ON, "Flipmap set to ON") +GS_DEFINE(OSD_FLIPMAP_OFF, "Flipmap set to OFF") +GS_DEFINE(OSD_FLIPMAP_FAIL_ALREADY_ON, "Flipmap is already ON") +GS_DEFINE(OSD_FLIPMAP_FAIL_ALREADY_OFF, "Flipmap is already OFF") +GS_DEFINE(OSD_COMPLETE_LEVEL, "Level complete!") +GS_DEFINE(OSD_PLAY_LEVEL, "Loading %s") +GS_DEFINE(OSD_INVALID_LEVEL, "Invalid level") +GS_DEFINE(OSD_INVALID_SAVE_SLOT, "Invalid save slot %d") +GS_DEFINE(OSD_DOOR_OPEN, "Open Sesame!") +GS_DEFINE(OSD_DOOR_CLOSE, "Close Sesame!") +GS_DEFINE(OSD_DOOR_OPEN_FAIL, "No doors in Lara's proximity") +GS_DEFINE(OSD_LOAD_GAME, "Loaded game from save slot %d") +GS_DEFINE(OSD_LOAD_GAME_FAIL_UNAVAILABLE_SLOT, "Save slot %d is not available") +GS_DEFINE(OSD_SAVE_GAME, "Saved game to save slot %d") +GS_DEFINE(OSD_SAVE_GAME_FAIL, "Cannot save the game in the current state") diff --git a/src/tr2/game/game_string.h b/src/tr2/game/game_string.h new file mode 100644 index 000000000..78c02b199 --- /dev/null +++ b/src/tr2/game/game_string.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +void GameString_Init(void); +void GameString_Shutdown(void); diff --git a/src/tr2/game/gameflow.c b/src/tr2/game/gameflow.c new file mode 100644 index 000000000..e579113a6 --- /dev/null +++ b/src/tr2/game/gameflow.c @@ -0,0 +1,637 @@ +#include "game/gameflow.h" + +#include "decomp/decomp.h" +#include "decomp/stats.h" +#include "game/demo.h" +#include "game/game.h" +#include "game/gun/gun.h" +#include "game/inventory/backpack.h" +#include "game/overlay.h" +#include "game/requester.h" +#include "gameflow/gameflow_new.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include +#include +#include +#include + +#include + +#define GF_CURRENT_VERSION 3 + +static void M_ReadStringTable( + VFILE *file, int32_t count, char ***table, char **buffer); + +static GF_ADD_INV M_ModifyInventory_GetGunAdder(LARA_GUN_TYPE gun_type); +static GF_ADD_INV M_ModifyInventory_GetAmmoAdder(LARA_GUN_TYPE gun_type); +static GF_ADD_INV M_ModifyInventory_GetItemAdder(GAME_OBJECT_ID object_id); +static void M_ModifyInventory_GunOrAmmo( + START_INFO *start, int32_t type, LARA_GUN_TYPE gun_type); +static void M_ModifyInventory_Item(int32_t type, GAME_OBJECT_ID object_id); + +static void M_ReadStringTable( + VFILE *const file, const int32_t count, char ***const table, + char **const buffer) +{ + VFile_Read(file, g_GF_LevelOffsets, sizeof(int16_t) * count); + + const int16_t buf_size = VFile_ReadS16(file); + *buffer = Memory_Alloc(buf_size); + VFile_Read(file, *buffer, buf_size); + + if (g_GameFlow.cyphered_strings) { + for (int32_t i = 0; i < buf_size; i++) { + (*buffer)[i] ^= g_GameFlow.cypher_code; + } + } + + *table = Memory_Alloc(sizeof(char *) * count); + for (int32_t i = 0; i < count; i++) { + const int32_t offset = g_GF_LevelOffsets[i]; + (*table)[i] = &(*buffer)[offset]; + } +} + +static GF_ADD_INV M_ModifyInventory_GetGunAdder(const LARA_GUN_TYPE gun_type) +{ + // clang-format off + switch (gun_type) { + case LGT_PISTOLS: return GF_ADD_INV_PISTOLS; + case LGT_MAGNUMS: return GF_ADD_INV_MAGNUMS; + case LGT_UZIS: return GF_ADD_INV_UZIS; + case LGT_SHOTGUN: return GF_ADD_INV_SHOTGUN; + case LGT_HARPOON: return GF_ADD_INV_HARPOON; + case LGT_M16: return GF_ADD_INV_M16; + case LGT_GRENADE: return GF_ADD_INV_GRENADE; + default: return (GF_ADD_INV)-1; + } + // clang-format on +} + +static GF_ADD_INV M_ModifyInventory_GetAmmoAdder(const LARA_GUN_TYPE gun_type) +{ + // clang-format off + switch (gun_type) { + case LGT_PISTOLS: return GF_ADD_INV_PISTOL_AMMO; + case LGT_MAGNUMS: return GF_ADD_INV_MAGNUM_AMMO; + case LGT_UZIS: return GF_ADD_INV_UZI_AMMO; + case LGT_SHOTGUN: return GF_ADD_INV_SHOTGUN_AMMO; + case LGT_HARPOON: return GF_ADD_INV_HARPOON_AMMO; + case LGT_M16: return GF_ADD_INV_M16_AMMO; + case LGT_GRENADE: return GF_ADD_INV_GRENADE_AMMO; + default: return (GF_ADD_INV)-1; + } + // clang-format on +} + +static GF_ADD_INV M_ModifyInventory_GetItemAdder(const GAME_OBJECT_ID object_id) +{ + // clang-format off + switch (object_id) { + case O_FLARE_ITEM: return GF_ADD_INV_FLARES; + case O_SMALL_MEDIPACK_ITEM: return GF_ADD_INV_SMALL_MEDI; + case O_LARGE_MEDIPACK_ITEM: return GF_ADD_INV_LARGE_MEDI; + case O_PICKUP_ITEM_1: return GF_ADD_INV_PICKUP_1; + case O_PICKUP_ITEM_2: return GF_ADD_INV_PICKUP_2; + case O_PUZZLE_ITEM_1: return GF_ADD_INV_PUZZLE_1; + case O_PUZZLE_ITEM_2: return GF_ADD_INV_PUZZLE_2; + case O_PUZZLE_ITEM_3: return GF_ADD_INV_PUZZLE_3; + case O_PUZZLE_ITEM_4: return GF_ADD_INV_PUZZLE_4; + case O_KEY_ITEM_1: return GF_ADD_INV_KEY_1; + case O_KEY_ITEM_2: return GF_ADD_INV_KEY_2; + case O_KEY_ITEM_3: return GF_ADD_INV_KEY_3; + case O_KEY_ITEM_4: return GF_ADD_INV_KEY_4; + default: return (GF_ADD_INV)-1; + } + // clang-format on +} + +static void M_ModifyInventory_GunOrAmmo( + START_INFO *const start, const int32_t type, const LARA_GUN_TYPE gun_type) +{ + const GAME_OBJECT_ID gun_item = Gun_GetGunObject(gun_type); + const GAME_OBJECT_ID ammo_item = Gun_GetAmmoObject(gun_type); + const int32_t ammo_qty = Gun_GetAmmoQuantity(gun_type); + AMMO_INFO *const ammo_info = Gun_GetAmmoInfo(gun_type); + + const GF_ADD_INV gun_adder = M_ModifyInventory_GetGunAdder(gun_type); + const GF_ADD_INV ammo_adder = M_ModifyInventory_GetAmmoAdder(gun_type); + + if (Inv_RequestItem(gun_item)) { + if (type == 1) { + ammo_info->ammo += ammo_qty * g_GF_SecretInvItems[ammo_adder]; + for (int32_t i = 0; i < g_GF_SecretInvItems[ammo_adder]; i++) { + Overlay_AddDisplayPickup(ammo_item); + } + } else if (type == 0) { + ammo_info->ammo += ammo_qty * g_GF_Add2InvItems[ammo_adder]; + } + } else if ( + (type == 0 && g_GF_Add2InvItems[gun_adder]) + || (type == 1 && g_GF_SecretInvItems[gun_adder])) { + + // clang-format off + // TODO: consider moving this to Inv_AddItem + switch (gun_type) { + case LGT_PISTOLS: start->has_pistols = 1; break; + case LGT_MAGNUMS: start->has_magnums = 1; break; + case LGT_UZIS: start->has_uzis = 1; break; + case LGT_SHOTGUN: start->has_shotgun = 1; break; + case LGT_HARPOON: start->has_harpoon = 1; break; + case LGT_M16: start->has_m16 = 1; break; + case LGT_GRENADE: start->has_grenade = 1; break; + default: break; + } + // clang-format on + + Inv_AddItem(gun_item); + + if (type == 1) { + ammo_info->ammo += ammo_qty * g_GF_SecretInvItems[ammo_adder]; + Overlay_AddDisplayPickup(gun_item); + for (int32_t i = 0; i < g_GF_SecretInvItems[ammo_adder]; i++) { + Overlay_AddDisplayPickup(ammo_item); + } + } else if (type == 0) { + ammo_info->ammo += ammo_qty * g_GF_Add2InvItems[ammo_adder]; + } + } else if (type == 1) { + for (int32_t i = 0; i < g_GF_SecretInvItems[ammo_adder]; i++) { + Inv_AddItem(ammo_item); + Overlay_AddDisplayPickup(ammo_item); + } + } else if (type == 0) { + for (int32_t i = 0; i < g_GF_Add2InvItems[ammo_adder]; i++) { + Inv_AddItem(ammo_item); + } + } +} + +static void M_ModifyInventory_Item( + const int32_t type, const GAME_OBJECT_ID object_id) +{ + const GF_ADD_INV item_adder = M_ModifyInventory_GetItemAdder(object_id); + int32_t qty = 0; + if (type == 1) { + qty = g_GF_SecretInvItems[item_adder]; + } else if (type == 0) { + qty = g_GF_Add2InvItems[item_adder]; + } + + for (int32_t i = 0; i < qty; i++) { + Inv_AddItem(object_id); + if (type == 1) { + Overlay_AddDisplayPickup(object_id); + } + } +} + +// TODO: inline me into GF_LoadScriptFile +BOOL __cdecl GF_LoadFromFile(const char *const file_name) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + DWORD bytes_read; + + const char *full_path = GetFullPath(file_name); + VFILE *const file = VFile_CreateFromPath(full_path); + if (file == NULL) { + return false; + } + + g_GF_ScriptVersion = VFile_ReadS32(file); + if (g_GF_ScriptVersion != GF_CURRENT_VERSION) { + return false; + } + + VFile_Read(file, g_GF_Description, 256); + + if (VFile_ReadS16(file) != sizeof(GAME_FLOW)) { + return false; + } + g_GameFlow.first_option = VFile_ReadS32(file); + g_GameFlow.title_replace = VFile_ReadS32(file); + g_GameFlow.on_death_demo_mode = VFile_ReadS32(file); + g_GameFlow.on_death_in_game = VFile_ReadS32(file); + g_GameFlow.no_input_time = VFile_ReadS32(file); + g_GameFlow.on_demo_interrupt = VFile_ReadS32(file); + g_GameFlow.on_demo_end = VFile_ReadS32(file); + VFile_Skip(file, 36); + g_GameFlow.num_levels = VFile_ReadU16(file); + g_GameFlow.num_pictures = VFile_ReadU16(file); + g_GameFlow.num_titles = VFile_ReadU16(file); + g_GameFlow.num_fmvs = VFile_ReadU16(file); + g_GameFlow.num_cutscenes = VFile_ReadU16(file); + g_GameFlow.num_demos = VFile_ReadU16(file); + g_GameFlow.title_track = VFile_ReadU16(file); + g_GameFlow.single_level = VFile_ReadS16(file); + VFile_Skip(file, 32); + + const uint16_t flags = VFile_ReadU16(file); + // clang-format off + g_GameFlow.demo_version = flags & 0x0001 ? 1 : 0; + g_GameFlow.title_disabled = flags & 0x0002 ? 1 : 0; + g_GameFlow.cheat_mode_check_disabled = flags & 0x0004 ? 1 : 0; + g_GameFlow.no_input_timeout = flags & 0x0008 ? 1 : 0; + g_GameFlow.load_save_disabled = flags & 0x0010 ? 1 : 0; + g_GameFlow.screen_sizing_disabled = flags & 0x0020 ? 1 : 0; + g_GameFlow.lockout_option_ring = flags & 0x0040 ? 1 : 0; + g_GameFlow.dozy_cheat_enabled = flags & 0x0080 ? 1 : 0; + g_GameFlow.cyphered_strings = flags & 0x0100 ? 1 : 0; + g_GameFlow.gym_enabled = flags & 0x0200 ? 1 : 0; + g_GameFlow.play_any_level = flags & 0x0400 ? 1 : 0; + g_GameFlow.cheat_enable = flags & 0x0800 ? 1 : 0; + // clang-format on + VFile_Skip(file, 6); + + g_GameFlow.cypher_code = VFile_ReadU8(file); + g_GameFlow.language = VFile_ReadU8(file); + g_GameFlow.secret_track = VFile_ReadU8(file); + g_GameFlow.level_complete_track = VFile_ReadU8(file); + VFile_Skip(file, 4); + + M_ReadStringTable( + file, g_GameFlow.num_levels, &g_GF_LevelNames, &g_GF_LevelNamesBuf); + M_ReadStringTable( + file, g_GameFlow.num_pictures, &g_GF_PicFilenames, + &g_GF_PicFilenamesBuf); + M_ReadStringTable( + file, g_GameFlow.num_titles, &g_GF_TitleFileNames, + &g_GF_TitleFileNamesBuf); + M_ReadStringTable( + file, g_GameFlow.num_fmvs, &g_GF_FMVFilenames, &g_GF_FMVFilenamesBuf); + M_ReadStringTable( + file, g_GameFlow.num_levels, &g_GF_LevelFileNames, + &g_GF_LevelFileNamesBuf); + M_ReadStringTable( + file, g_GameFlow.num_cutscenes, &g_GF_CutsceneFileNames, + &g_GF_CutsceneFileNamesBuf); + + VFile_Read( + file, &g_GF_LevelOffsets, + sizeof(int16_t) * (g_GameFlow.num_levels + 1)); + { + const int16_t size = VFile_ReadS16(file); + g_GF_SequenceBuf = Memory_Alloc(size); + VFile_Read(file, g_GF_SequenceBuf, size); + } + + g_GF_FrontendSequence = g_GF_SequenceBuf; + for (int32_t i = 0; i < g_GameFlow.num_levels; i++) { + g_GF_ScriptTable[i] = g_GF_SequenceBuf + (g_GF_LevelOffsets[i + 1] / 2); + } + + VFile_Read(file, g_GF_ValidDemos, sizeof(int16_t) * g_GameFlow.num_demos); + + if (VFile_ReadS16(file) != GF_S_GAME_NUMBER_OF) { + return false; + } + + M_ReadStringTable( + file, GF_S_GAME_NUMBER_OF, &g_GF_GameStrings, &g_GF_GameStringsBuf); + M_ReadStringTable( + file, GF_S_PC_NUMBER_OF, &g_GF_PCStrings, &g_GF_PCStringsBuf); + M_ReadStringTable( + file, g_GameFlow.num_levels, &g_GF_Puzzle1Strings, + &g_GF_Puzzle1StringsBuf); + M_ReadStringTable( + file, g_GameFlow.num_levels, &g_GF_Puzzle2Strings, + &g_GF_Puzzle2StringsBuf); + M_ReadStringTable( + file, g_GameFlow.num_levels, &g_GF_Puzzle3Strings, + &g_GF_Puzzle3StringsBuf); + M_ReadStringTable( + file, g_GameFlow.num_levels, &g_GF_Puzzle4Strings, + &g_GF_Puzzle4StringsBuf); + M_ReadStringTable( + file, g_GameFlow.num_levels, &g_GF_Pickup1Strings, + &g_GF_Pickup1StringsBuf); + M_ReadStringTable( + file, g_GameFlow.num_levels, &g_GF_Pickup2Strings, + &g_GF_Pickup2StringsBuf); + M_ReadStringTable( + file, g_GameFlow.num_levels, &g_GF_Key1Strings, &g_GF_Key1StringsBuf); + M_ReadStringTable( + file, g_GameFlow.num_levels, &g_GF_Key2Strings, &g_GF_Key2StringsBuf); + M_ReadStringTable( + file, g_GameFlow.num_levels, &g_GF_Key3Strings, &g_GF_Key3StringsBuf); + M_ReadStringTable( + file, g_GameFlow.num_levels, &g_GF_Key4Strings, &g_GF_Key4StringsBuf); + + VFile_Close(file); + Benchmark_End(benchmark, NULL); + return true; +} + +int32_t __cdecl GF_LoadScriptFile(const char *const fname) +{ + g_GF_SunsetEnabled = false; + + if (!GF_LoadFromFile(fname)) { + return false; + } + + g_GameFlow.level_complete_track = MX_END_OF_LEVEL; + + Requester_SetHeading( + &g_LoadGameRequester, g_GF_GameStrings[GF_S_GAME_PASSPORT_SELECT_LEVEL], + 0, 0, 0); + Requester_SetHeading( + &g_SaveGameRequester, g_GF_GameStrings[GF_S_GAME_PASSPORT_SELECT_LEVEL], + 0, 0, 0); + + return true; +} + +int32_t __cdecl GF_DoFrontendSequence(void) +{ + GF_N_LoadStrings(-1); + const GAME_FLOW_DIR dir = + GF_InterpretSequence(g_GF_FrontendSequence, GFL_NORMAL, 1); + return dir == GFD_EXIT_GAME; +} + +int32_t __cdecl GF_DoLevelSequence( + const int32_t start_level, const GAMEFLOW_LEVEL_TYPE type) +{ + GF_N_LoadStrings(start_level); + + int32_t current_level = start_level; + while (true) { + if (current_level > g_GameFlow.num_levels - 1) { + g_IsTitleLoaded = false; + return GFD_EXIT_TO_TITLE; + } + + const int16_t *const ptr = g_GF_ScriptTable[current_level]; + const GAME_FLOW_DIR dir = GF_InterpretSequence(ptr, type, 0); + current_level++; + + if (g_GameFlow.single_level >= 0) { + return dir; + } + if ((dir & ~0xFF) != GFD_LEVEL_COMPLETE) { + return dir; + } + } +} + +int32_t __cdecl GF_InterpretSequence( + const int16_t *ptr, GAMEFLOW_LEVEL_TYPE type, const int32_t seq_type) +{ + g_GF_NoFloor = false; + g_GF_DeadlyWater = false; + g_GF_SunsetEnabled = false; + g_GF_LaraStartAnim = 0; + g_GF_Kill2Complete = false; + g_GF_RemoveAmmo = false; + g_GF_RemoveWeapons = false; + + for (int32_t i = 0; i < GF_ADD_INV_NUMBER_OF; i++) { + g_GF_SecretInvItems[i] = 0; + g_GF_Add2InvItems[i] = 0; + } + + g_GF_MusicTracks[0] = 2; + g_CineTargetAngle = PHD_90; + g_GF_NumSecrets = 3; + + int32_t ntracks = 0; + GAME_FLOW_DIR dir = GFD_EXIT_TO_TITLE; + + while (*ptr != GFE_END_SEQ) { + switch (*ptr) { + case GFE_PICTURE: + ptr += 2; + break; + + case GFE_LIST_START: + case GFE_LIST_END: + ptr++; + break; + + case GFE_PLAY_FMV: + if (type != GFL_SAVED) { + if (ptr[2] == GFE_PLAY_FMV) { + if (S_IntroFMV( + g_GF_FMVFilenames[ptr[1]], + g_GF_FMVFilenames[ptr[3]])) { + return GFD_EXIT_GAME; + } + ptr += 2; + } else if (S_PlayFMV(g_GF_FMVFilenames[ptr[1]])) { + return GFD_EXIT_GAME; + } + } + ptr += 2; + break; + + case GFE_START_LEVEL: + if (ptr[1] > g_GameFlow.num_levels) { + dir = GFD_EXIT_TO_TITLE; + } else if (type != GFL_STORY) { + if (type == GFL_MID_STORY) { + return GFD_EXIT_TO_TITLE; + } + dir = Game_Start(ptr[1], type); + g_GF_StartGame = 0; + if (type == GFL_SAVED) { + type = GFL_NORMAL; + } + if ((dir & ~0xFF) != GFD_LEVEL_COMPLETE) { + return dir; + } + } + ptr += 2; + break; + + case GFE_CUTSCENE: + if (type != GFL_SAVED) { + const int16_t level = g_CurrentLevel; + const int32_t result = Game_Cutscene_Start(ptr[1]); + g_CurrentLevel = level; + // TODO: make Game_Cutscene_Start return GAME_FLOW_DIR + if (result == 2 + && (type == GFL_STORY || type == GFL_MID_STORY)) { + return GFD_EXIT_TO_TITLE; + } + if (result == 3) { + return GFD_EXIT_GAME; + } + if (result == 4) { + dir = g_GF_OverrideDir; + g_GF_OverrideDir = (GAME_FLOW_DIR)-1; + return dir; + } + } + ptr += 2; + break; + + case GFE_LEVEL_COMPLETE: + if (type != GFL_STORY && type != GFL_MID_STORY) { + if (LevelStats(g_CurrentLevel)) { + return GFD_EXIT_TO_TITLE; + } + dir = GFD_START_GAME | (g_CurrentLevel + 1); + } + ptr++; + break; + + case GFE_DEMO_PLAY: + if (type != GFL_SAVED && type != GFL_STORY + && type != GFL_MID_STORY) { + return Demo_Start(ptr[1]); + } + ptr += 2; + break; + + case GFE_JUMP_TO_SEQ: + ptr += 2; + break; + + case GFE_SET_TRACK: + g_GF_MusicTracks[ntracks] = ptr[1]; + Game_SetCutsceneTrack(ptr[1]); + ntracks++; + ptr += 2; + break; + + case GFE_SUNSET: + if (type != GFL_STORY && type != GFL_MID_STORY) { + g_GF_SunsetEnabled = true; + } + ptr++; + break; + + case GFE_LOADING_PIC: + ptr += 2; + break; + + case GFE_DEADLY_WATER: + if (type != GFL_STORY && type != GFL_MID_STORY) { + g_GF_DeadlyWater = true; + } + ptr++; + break; + + case GFE_REMOVE_WEAPONS: + if (type != GFL_STORY && type != GFL_MID_STORY + && type != GFL_SAVED) { + g_GF_RemoveWeapons = true; + } + ptr++; + break; + + case GFE_GAME_COMPLETE: + DisplayCredits(); + if (GameStats(g_CurrentLevel)) { + return GFD_EXIT_TO_TITLE; + } + dir = GFD_EXIT_TO_TITLE; + ptr++; + break; + + case GFE_CUT_ANGLE: + if (type != GFL_SAVED) { + g_CineTargetAngle = ptr[1]; + } + ptr += 2; + break; + + case GFE_NO_FLOOR: + if (type != GFL_STORY && type != GFL_MID_STORY) { + g_GF_NoFloor = ptr[1]; + } + ptr += 2; + break; + + case GFE_ADD_TO_INV: + if (type != GFL_STORY && type != GFL_MID_STORY) { + if (ptr[1] < 1000) { + g_GF_SecretInvItems[ptr[1]]++; + } else if (type != GFL_SAVED) { + g_GF_Add2InvItems[ptr[1] - 1000]++; + } + } + ptr += 2; + break; + + case GFE_START_ANIM: + if (type != GFL_STORY && type != GFL_MID_STORY) { + g_GF_LaraStartAnim = ptr[1]; + } + ptr += 2; + break; + + case GFE_NUM_SECRETS: + if (type != GFL_STORY && type != GFL_MID_STORY) { + g_GF_NumSecrets = ptr[1]; + } + ptr += 2; + break; + + case GFE_KILL_TO_COMPLETE: + if (type != GFL_STORY && type != GFL_MID_STORY) { + g_GF_Kill2Complete = true; + } + ptr++; + break; + + case GFE_REMOVE_AMMO: + if (type != GFL_STORY && type != GFL_MID_STORY + && type != GFL_SAVED) { + g_GF_RemoveAmmo = true; + } + ptr++; + break; + + default: + return GFD_EXIT_GAME; + } + } + + if (type == GFL_STORY || type == GFL_MID_STORY) { + return 0; + } + return dir; +} + +void __cdecl GF_ModifyInventory(const int32_t level, const int32_t type) +{ + START_INFO *const start = &g_SaveGame.start[level]; + + if (!start->has_pistols && g_GF_Add2InvItems[GF_ADD_INV_PISTOLS]) { + start->has_pistols = 1; + Inv_AddItem(O_PISTOL_ITEM); + } + + M_ModifyInventory_GunOrAmmo(start, type, LGT_MAGNUMS); + M_ModifyInventory_GunOrAmmo(start, type, LGT_UZIS); + M_ModifyInventory_GunOrAmmo(start, type, LGT_SHOTGUN); + M_ModifyInventory_GunOrAmmo(start, type, LGT_HARPOON); + M_ModifyInventory_GunOrAmmo(start, type, LGT_M16); + M_ModifyInventory_GunOrAmmo(start, type, LGT_GRENADE); + + M_ModifyInventory_Item(type, O_FLARE_ITEM); + M_ModifyInventory_Item(type, O_SMALL_MEDIPACK_ITEM); + M_ModifyInventory_Item(type, O_LARGE_MEDIPACK_ITEM); + M_ModifyInventory_Item(type, O_PICKUP_ITEM_1); + M_ModifyInventory_Item(type, O_PICKUP_ITEM_2); + M_ModifyInventory_Item(type, O_PUZZLE_ITEM_1); + M_ModifyInventory_Item(type, O_PUZZLE_ITEM_2); + M_ModifyInventory_Item(type, O_PUZZLE_ITEM_3); + M_ModifyInventory_Item(type, O_PUZZLE_ITEM_4); + M_ModifyInventory_Item(type, O_KEY_ITEM_1); + M_ModifyInventory_Item(type, O_KEY_ITEM_2); + M_ModifyInventory_Item(type, O_KEY_ITEM_3); + M_ModifyInventory_Item(type, O_KEY_ITEM_4); + + for (int32_t i = 0; i < GF_ADD_INV_NUMBER_OF; i++) { + if (type == 1) { + g_GF_SecretInvItems[i] = 0; + } else if (type == 0) { + g_GF_Add2InvItems[i] = 0; + } + } +} diff --git a/src/tr2/game/gameflow.h b/src/tr2/game/gameflow.h new file mode 100644 index 000000000..73e933b51 --- /dev/null +++ b/src/tr2/game/gameflow.h @@ -0,0 +1,13 @@ +#pragma once + +#include "global/types.h" + +#include + +BOOL __cdecl GF_LoadFromFile(const char *file_name); +int32_t __cdecl GF_LoadScriptFile(const char *fname); +int32_t __cdecl GF_DoFrontendSequence(void); +int32_t __cdecl GF_DoLevelSequence(int32_t level, GAMEFLOW_LEVEL_TYPE type); +int32_t __cdecl GF_InterpretSequence( + const int16_t *ptr, GAMEFLOW_LEVEL_TYPE type, int32_t seq_type); +void __cdecl GF_ModifyInventory(int32_t level, int32_t type); diff --git a/src/tr2/game/gameflow/gameflow_new.c b/src/tr2/game/gameflow/gameflow_new.c new file mode 100644 index 000000000..65e309c4d --- /dev/null +++ b/src/tr2/game/gameflow/gameflow_new.c @@ -0,0 +1,272 @@ +#include "game/gameflow/gameflow_new.h" + +#include "game/game_string.h" +#include "global/types.h" +#include "global/vars.h" + +#include +#include +#include +#include + +#include + +GAMEFLOW_NEW g_GameflowNew; +GAME_INFO g_GameInfo; + +static void M_LoadObjectString(const char *key, const char *value); +static void M_LoadGameString(const char *key, const char *value); +static void M_LoadObjectStrings(const int32_t level_num); +static void M_LoadGameStrings(const int32_t level_num); + +static void M_LoadObjectString(const char *const key, const char *const value) +{ + const GAME_OBJECT_ID object_id = + ENUM_MAP_GET(GAME_OBJECT_ID, key, NO_OBJECT); + if (object_id != NO_OBJECT) { + Object_SetName(object_id, value); + } +} + +static void M_LoadGameString(const char *const key, const char *const value) +{ + if (!GameString_IsKnown(key)) { + LOG_ERROR("Invalid game string key: %s", key); + } else if (value == NULL) { + LOG_ERROR("Invalid game string value: %s", key); + } else { + GameString_Define(key, value); + } +} + +static void M_LoadObjectStrings(const int32_t level_num) +{ + const GAMEFLOW_NEW *const gf = &g_GameflowNew; + + const GAMEFLOW_NEW_STRING_ENTRY *entry = gf->object_strings; + while (entry != NULL && entry->key != NULL) { + M_LoadObjectString(entry->key, entry->value); + entry++; + } + + if (level_num >= 0) { + assert(level_num < gf->level_count); + const GAMEFLOW_NEW_LEVEL *const level = &gf->levels[level_num]; + entry = level->object_strings; + while (entry != NULL && entry->key != NULL) { + M_LoadObjectString(entry->key, entry->value); + entry++; + } + } +} + +static void M_LoadGameStrings(const int32_t level_num) +{ + const GAMEFLOW_NEW *const gf = &g_GameflowNew; + + const GAMEFLOW_NEW_STRING_ENTRY *entry = gf->game_strings; + while (entry != NULL && entry->key != NULL) { + M_LoadGameString(entry->key, entry->value); + entry++; + } + + if (level_num >= 0) { + assert(level_num < gf->level_count); + const GAMEFLOW_NEW_LEVEL *const level = &gf->levels[level_num]; + entry = level->game_strings; + while (entry != NULL && entry->key != NULL) { + M_LoadGameString(entry->key, entry->value); + entry++; + } + } +} + +void GF_N_LoadStrings(const int32_t level_num) +{ + Object_ResetNames(); + + M_LoadObjectStrings(level_num); + M_LoadGameStrings(level_num); + + struct { + GAME_OBJECT_ID object_id; + GF_GAME_STRING game_string; + } game_string_defs[] = { + { O_COMPASS_OPTION, GF_S_GAME_INV_ITEM_STATISTICS }, + { O_COMPASS_ITEM, GF_S_GAME_INV_ITEM_STATISTICS }, + { O_PISTOL_ITEM, GF_S_GAME_INV_ITEM_PISTOLS }, + { O_PISTOL_OPTION, GF_S_GAME_INV_ITEM_PISTOLS }, + { O_FLARE_ITEM, GF_S_GAME_INV_ITEM_FLARE }, + { O_FLARES_OPTION, GF_S_GAME_INV_ITEM_FLARE }, + { O_SHOTGUN_ITEM, GF_S_GAME_INV_ITEM_SHOTGUN }, + { O_SHOTGUN_OPTION, GF_S_GAME_INV_ITEM_SHOTGUN }, + { O_MAGNUM_ITEM, GF_S_GAME_INV_ITEM_MAGNUMS }, + { O_MAGNUM_OPTION, GF_S_GAME_INV_ITEM_MAGNUMS }, + { O_UZI_ITEM, GF_S_GAME_INV_ITEM_UZIS }, + { O_UZI_OPTION, GF_S_GAME_INV_ITEM_UZIS }, + { O_HARPOON_ITEM, GF_S_GAME_INV_ITEM_HARPOON }, + { O_HARPOON_OPTION, GF_S_GAME_INV_ITEM_HARPOON }, + { O_M16_ITEM, GF_S_GAME_INV_ITEM_M16 }, + { O_M16_OPTION, GF_S_GAME_INV_ITEM_M16 }, + { O_GRENADE_ITEM, GF_S_GAME_INV_ITEM_GRENADE }, + { O_GRENADE_OPTION, GF_S_GAME_INV_ITEM_GRENADE }, + { O_PISTOL_AMMO_ITEM, GF_S_GAME_INV_ITEM_PISTOL_AMMO }, + { O_PISTOL_AMMO_OPTION, GF_S_GAME_INV_ITEM_PISTOL_AMMO }, + { O_SHOTGUN_AMMO_ITEM, GF_S_GAME_INV_ITEM_SHOTGUN_AMMO }, + { O_SHOTGUN_AMMO_OPTION, GF_S_GAME_INV_ITEM_SHOTGUN_AMMO }, + { O_MAGNUM_AMMO_ITEM, GF_S_GAME_INV_ITEM_MAGNUM_AMMO }, + { O_MAGNUM_AMMO_OPTION, GF_S_GAME_INV_ITEM_MAGNUM_AMMO }, + { O_UZI_AMMO_ITEM, GF_S_GAME_INV_ITEM_UZI_AMMO }, + { O_UZI_AMMO_OPTION, GF_S_GAME_INV_ITEM_UZI_AMMO }, + { O_HARPOON_AMMO_ITEM, GF_S_GAME_INV_ITEM_HARPOON_AMMO }, + { O_HARPOON_AMMO_OPTION, GF_S_GAME_INV_ITEM_HARPOON_AMMO }, + { O_M16_AMMO_ITEM, GF_S_GAME_INV_ITEM_M16_AMMO }, + { O_M16_AMMO_OPTION, GF_S_GAME_INV_ITEM_M16_AMMO }, + { O_GRENADE_AMMO_ITEM, GF_S_GAME_INV_ITEM_GRENADE_AMMO }, + { O_GRENADE_AMMO_OPTION, GF_S_GAME_INV_ITEM_GRENADE_AMMO }, + { O_SMALL_MEDIPACK_ITEM, GF_S_GAME_INV_ITEM_SMALL_MEDIPACK }, + { O_SMALL_MEDIPACK_OPTION, GF_S_GAME_INV_ITEM_SMALL_MEDIPACK }, + { O_LARGE_MEDIPACK_ITEM, GF_S_GAME_INV_ITEM_LARGE_MEDIPACK }, + { O_LARGE_MEDIPACK_OPTION, GF_S_GAME_INV_ITEM_LARGE_MEDIPACK }, + { O_PICKUP_ITEM_1, GF_S_GAME_INV_ITEM_PICKUP }, + { O_PICKUP_OPTION_1, GF_S_GAME_INV_ITEM_PICKUP }, + { O_PICKUP_ITEM_2, GF_S_GAME_INV_ITEM_PICKUP }, + { O_PICKUP_OPTION_2, GF_S_GAME_INV_ITEM_PICKUP }, + { O_PUZZLE_ITEM_1, GF_S_GAME_INV_ITEM_PUZZLE }, + { O_PUZZLE_OPTION_1, GF_S_GAME_INV_ITEM_PUZZLE }, + { O_PUZZLE_ITEM_2, GF_S_GAME_INV_ITEM_PUZZLE }, + { O_PUZZLE_OPTION_2, GF_S_GAME_INV_ITEM_PUZZLE }, + { O_PUZZLE_ITEM_3, GF_S_GAME_INV_ITEM_PUZZLE }, + { O_PUZZLE_OPTION_3, GF_S_GAME_INV_ITEM_PUZZLE }, + { O_PUZZLE_ITEM_4, GF_S_GAME_INV_ITEM_PUZZLE }, + { O_PUZZLE_OPTION_4, GF_S_GAME_INV_ITEM_PUZZLE }, + { O_KEY_ITEM_1, GF_S_GAME_INV_ITEM_KEY }, + { O_KEY_OPTION_1, GF_S_GAME_INV_ITEM_KEY }, + { O_KEY_ITEM_2, GF_S_GAME_INV_ITEM_KEY }, + { O_KEY_OPTION_2, GF_S_GAME_INV_ITEM_KEY }, + { O_KEY_ITEM_3, GF_S_GAME_INV_ITEM_KEY }, + { O_KEY_OPTION_3, GF_S_GAME_INV_ITEM_KEY }, + { O_KEY_ITEM_4, GF_S_GAME_INV_ITEM_KEY }, + { O_KEY_OPTION_4, GF_S_GAME_INV_ITEM_KEY }, + { O_PASSPORT_OPTION, GF_S_GAME_INV_ITEM_GAME }, + { O_PASSPORT_CLOSED, GF_S_GAME_INV_ITEM_GAME }, + { O_PHOTO_OPTION, GF_S_GAME_INV_ITEM_LARA_HOME }, + { NO_OBJECT, -1 }, + }; + + for (int32_t i = 0; game_string_defs[i].object_id != NO_OBJECT; i++) { + const char *const new_name = + g_GF_GameStrings[game_string_defs[i].game_string]; + if (new_name != NULL) { + Object_SetName(game_string_defs[i].object_id, new_name); + } + } + + struct { + GAME_OBJECT_ID object_id; + GF_PC_STRING pc_string; + } pc_string_defs[] = { + { O_DETAIL_OPTION, GF_S_PC_DETAIL_LEVELS }, + { O_SOUND_OPTION, GF_S_PC_SOUND }, + { O_CONTROL_OPTION, GF_S_PC_CONTROLS }, + { NO_OBJECT, -1 }, + }; + + for (int32_t i = 0; pc_string_defs[i].object_id != NO_OBJECT; i++) { + const char *const new_name = + g_GF_PCStrings[pc_string_defs[i].pc_string]; + if (new_name != NULL) { + Object_SetName(pc_string_defs[i].object_id, new_name); + } + } + + struct { + GAME_OBJECT_ID object_id; + char **strings; + } level_item_defs[] = { + { O_PUZZLE_ITEM_1, g_GF_Puzzle1Strings }, + { O_PUZZLE_ITEM_2, g_GF_Puzzle2Strings }, + { O_PUZZLE_ITEM_3, g_GF_Puzzle3Strings }, + { O_PUZZLE_ITEM_4, g_GF_Puzzle4Strings }, + { O_KEY_ITEM_1, g_GF_Key1Strings }, + { O_KEY_ITEM_2, g_GF_Key2Strings }, + { O_KEY_ITEM_3, g_GF_Key3Strings }, + { O_KEY_ITEM_4, g_GF_Key4Strings }, + { O_PICKUP_ITEM_1, g_GF_Pickup1Strings }, + { O_PICKUP_ITEM_2, g_GF_Pickup2Strings }, + { O_PUZZLE_OPTION_1, g_GF_Puzzle1Strings }, + { O_PUZZLE_OPTION_2, g_GF_Puzzle2Strings }, + { O_PUZZLE_OPTION_3, g_GF_Puzzle3Strings }, + { O_PUZZLE_OPTION_4, g_GF_Puzzle4Strings }, + { O_KEY_OPTION_1, g_GF_Key1Strings }, + { O_KEY_OPTION_2, g_GF_Key2Strings }, + { O_KEY_OPTION_3, g_GF_Key3Strings }, + { O_KEY_OPTION_4, g_GF_Key4Strings }, + { O_PICKUP_OPTION_1, g_GF_Pickup1Strings }, + { O_PICKUP_OPTION_2, g_GF_Pickup2Strings }, + { NO_OBJECT, NULL }, + }; + + if (level_num >= 0 && level_num < g_GameFlow.num_levels) { + for (int32_t i = 0; level_item_defs[i].object_id != NO_OBJECT; i++) { + const char *const new_name = level_item_defs[i].strings[level_num]; + if (new_name != NULL) { + Object_SetName(level_item_defs[i].object_id, new_name); + } + } + } +} + +int32_t Gameflow_GetLevelCount(void) +{ + return g_GameflowNew.level_count; +} + +const char *Gameflow_GetLevelFileName(int32_t level_num) +{ + return g_GF_LevelFileNames[level_num]; +} + +const char *Gameflow_GetLevelTitle(int32_t level_num) +{ + return g_GF_LevelNames[level_num]; +} + +int32_t Gameflow_GetGymLevelNumber(void) +{ + return g_GameFlow.gym_enabled ? LV_GYM : -1; +} + +void Gameflow_OverrideCommand(const GAMEFLOW_COMMAND command) +{ + switch (command.action) { + case GF_START_GAME: + g_GF_OverrideDir = GFD_START_GAME | command.param; + break; + case GF_START_SAVED_GAME: + g_GF_OverrideDir = GFD_START_SAVED_GAME | command.param; + break; + case GF_START_CINE: + g_GF_OverrideDir = GFD_START_CINE; + break; + case GF_START_FMV: + g_GF_OverrideDir = GFD_START_FMV; + break; + case GF_START_DEMO: + g_GF_OverrideDir = GFD_START_DEMO; + break; + case GF_EXIT_TO_TITLE: + g_GF_OverrideDir = GFD_EXIT_TO_TITLE; + break; + case GF_LEVEL_COMPLETE: + g_GF_OverrideDir = GFD_LEVEL_COMPLETE; + break; + case GF_EXIT_GAME: + g_GF_OverrideDir = GFD_EXIT_GAME; + break; + default: + LOG_ERROR("Not implemented"); + assert(false); + break; + } +} diff --git a/src/tr2/game/gameflow/gameflow_new.h b/src/tr2/game/gameflow/gameflow_new.h new file mode 100644 index 000000000..ed36de74d --- /dev/null +++ b/src/tr2/game/gameflow/gameflow_new.h @@ -0,0 +1,32 @@ +#pragma once + +#include "global/types.h" + +typedef struct { + struct { + GAMEFLOW_LEVEL_TYPE type; + int32_t num; + } current_level; +} GAME_INFO; + +typedef struct { + const char *key; + const char *value; +} GAMEFLOW_NEW_STRING_ENTRY; + +typedef struct { + GAMEFLOW_NEW_STRING_ENTRY *object_strings; + GAMEFLOW_NEW_STRING_ENTRY *game_strings; +} GAMEFLOW_NEW_LEVEL; + +typedef struct { + int32_t level_count; + GAMEFLOW_NEW_LEVEL *levels; + GAMEFLOW_NEW_STRING_ENTRY *object_strings; + GAMEFLOW_NEW_STRING_ENTRY *game_strings; +} GAMEFLOW_NEW; + +extern GAMEFLOW_NEW g_GameflowNew; +extern GAME_INFO g_GameInfo; + +void GF_N_LoadStrings(int32_t level_num); diff --git a/src/tr2/game/gameflow/reader.c b/src/tr2/game/gameflow/reader.c new file mode 100644 index 000000000..80113d55f --- /dev/null +++ b/src/tr2/game/gameflow/reader.c @@ -0,0 +1,171 @@ +#include "game/gameflow/reader.h" + +#include "game/gameflow/gameflow_new.h" +#include "global/vars.h" + +#include +#include +#include +#include + +static void M_StringTableShutdown(GAMEFLOW_NEW_STRING_ENTRY *dest); +static bool M_LoadStringTable( + JSON_OBJECT *root_obj, const char *key, GAMEFLOW_NEW_STRING_ENTRY **dest); +static bool M_LoadScriptLevels(JSON_OBJECT *obj, GAMEFLOW_NEW *gf); + +static void M_StringTableShutdown(GAMEFLOW_NEW_STRING_ENTRY *const dest) +{ + if (dest == NULL) { + return; + } + GAMEFLOW_NEW_STRING_ENTRY *cur = dest; + while (cur->key != NULL) { + Memory_FreePointer(&cur->key); + Memory_FreePointer(&cur->value); + cur++; + } + Memory_Free(dest); +} + +static bool M_LoadStringTable( + JSON_OBJECT *const root_obj, const char *const key, + GAMEFLOW_NEW_STRING_ENTRY **dest) +{ + JSON_VALUE *const strings_value = JSON_ObjectGetValue(root_obj, key); + if (strings_value == NULL) { + // key is missing - rely on default strings + return true; + } + + JSON_OBJECT *const strings_obj = JSON_ValueAsObject(strings_value); + if (strings_obj == NULL) { + LOG_ERROR("'%s' must be a dictionary", key); + return false; + } + + *dest = Memory_Alloc( + sizeof(GAMEFLOW_NEW_STRING_ENTRY) * (strings_obj->length + 1)); + + GAMEFLOW_NEW_STRING_ENTRY *cur = *dest; + JSON_OBJECT_ELEMENT *strings_elem = strings_obj->start; + for (size_t i = 0; i < strings_obj->length; + i++, strings_elem = strings_elem->next) { + JSON_STRING *const value = JSON_ValueAsString(strings_elem->value); + if (value == NULL) { + LOG_ERROR("invalid string key %s", strings_elem->name->string); + return NULL; + } + cur->key = Memory_DupStr(strings_elem->name->string); + cur->value = Memory_DupStr(value->string); + cur++; + } + + cur->key = NULL; + cur->value = NULL; + return true; +} + +static bool M_LoadScriptLevels(JSON_OBJECT *obj, GAMEFLOW_NEW *const gf) +{ + bool result = true; + + JSON_ARRAY *const jlvl_arr = JSON_ObjectGetArray(obj, "levels"); + if (jlvl_arr == NULL) { + LOG_ERROR("'levels' must be a list"); + result = false; + goto end; + } + + int32_t level_count = jlvl_arr->length; + if (level_count != g_GameFlow.num_levels) { + LOG_ERROR( + "'levels' must have exactly %d levels, as we still rely on legacy " + "tombpc.dat", + g_GameFlow.num_levels); + result = false; + goto end; + } + + gf->level_count = level_count; + gf->levels = Memory_Alloc(sizeof(GAMEFLOW_NEW_LEVEL) * level_count); + + JSON_ARRAY_ELEMENT *jlvl_elem = jlvl_arr->start; + for (size_t i = 0; i < jlvl_arr->length; i++, jlvl_elem = jlvl_elem->next) { + GAMEFLOW_NEW_LEVEL *const level = &gf->levels[i]; + + JSON_OBJECT *const jlvl_obj = JSON_ValueAsObject(jlvl_elem->value); + if (jlvl_obj == NULL) { + LOG_ERROR("'levels' elements must be dictionaries"); + result = false; + goto end; + } + + result &= M_LoadStringTable( + jlvl_obj, "object_strings", &level->object_strings); + result &= + M_LoadStringTable(jlvl_obj, "game_strings", &level->game_strings); + } + +end: + return result; +} + +bool GF_N_Load(const char *const path) +{ + GF_N_Shutdown(); + + bool result = true; + + char *script_data = NULL; + if (!File_Load(path, &script_data, NULL)) { + LOG_ERROR("failed to open script file"); + result = false; + goto end; + } + + JSON_PARSE_RESULT parse_result; + JSON_VALUE *root = JSON_ParseEx( + script_data, strlen(script_data), JSON_PARSE_FLAGS_ALLOW_JSON5, NULL, + NULL, &parse_result); + if (root == NULL) { + LOG_ERROR( + "failed to parse script file: %s in line %d, char %d", + JSON_GetErrorDescription(parse_result.error), + parse_result.error_line_no, parse_result.error_row_no, script_data); + result = false; + goto end; + } + + GAMEFLOW_NEW *const gf = &g_GameflowNew; + JSON_OBJECT *root_obj = JSON_ValueAsObject(root); + result &= + M_LoadStringTable(root_obj, "object_strings", &gf->object_strings); + result &= M_LoadStringTable(root_obj, "game_strings", &gf->game_strings); + result &= M_LoadScriptLevels(root_obj, gf); + +end: + if (root) { + JSON_ValueFree(root); + root = NULL; + } + + if (!result) { + GF_N_Shutdown(); + } + + Memory_FreePointer(&script_data); + return result; +} + +void GF_N_Shutdown(void) +{ + GAMEFLOW_NEW *const gf = &g_GameflowNew; + + for (int32_t i = 0; i < gf->level_count; i++) { + M_StringTableShutdown(gf->levels[i].object_strings); + M_StringTableShutdown(gf->levels[i].game_strings); + } + + M_StringTableShutdown(gf->object_strings); + M_StringTableShutdown(gf->game_strings); +} diff --git a/src/tr2/game/gameflow/reader.h b/src/tr2/game/gameflow/reader.h new file mode 100644 index 000000000..e5e2cb0b4 --- /dev/null +++ b/src/tr2/game/gameflow/reader.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +bool GF_N_Load(const char *path); +void GF_N_Shutdown(void); diff --git a/src/tr2/game/gun/gun.c b/src/tr2/game/gun/gun.c new file mode 100644 index 000000000..2d23a8e40 --- /dev/null +++ b/src/tr2/game/gun/gun.c @@ -0,0 +1,425 @@ +#include "game/gun/gun.h" + +#include "game/gun/gun_misc.h" +#include "game/gun/gun_pistols.h" +#include "game/gun/gun_rifle.h" +#include "game/input.h" +#include "game/inventory/backpack.h" +#include "game/lara/control.h" +#include "game/sound.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +typedef enum { + LF_FL_HOLD_FT = 1, + LF_FL_THROW_FT = 32, + LF_FL_DRAW_FT = 39, + LF_FL_IGNITE_FT = 23, + LF_FL_2_HOLD_FT = 15, + + LF_FL_HOLD = 0, + LF_FL_THROW = (LF_FL_HOLD + LF_FL_HOLD_FT), // = 1 + LF_FL_DRAW = (LF_FL_THROW + LF_FL_THROW_FT), // = 33 + LF_FL_IGNITE = (LF_FL_DRAW + LF_FL_DRAW_FT), // = 72 + LF_FL_2_HOLD = (LF_FL_IGNITE + LF_FL_IGNITE_FT), // = 95 + LF_FL_END = (LF_FL_2_HOLD + LF_FL_2_HOLD_FT), // = 110 +} LARA_FLARE_ANIMATION_FRAME; + +void __cdecl Gun_Control(void) +{ + if (g_Lara.left_arm.flash_gun > 0) { + g_Lara.left_arm.flash_gun--; + } + + if (g_Lara.right_arm.flash_gun > 0) { + g_Lara.right_arm.flash_gun--; + } + + if (g_LaraItem->hit_points <= 0) { + g_Lara.gun_status = LGS_ARMLESS; + } else if (g_Lara.gun_status == LGS_ARMLESS) { + if (g_Input & IN_DRAW) { + g_Lara.request_gun_type = g_Lara.last_gun_type; + } else if (g_InputDB & IN_FLARE) { + if (g_Lara.gun_type == LGT_FLARE) { + g_Lara.gun_status = LGS_UNDRAW; + } else if (Inv_RequestItem(O_FLARES_ITEM)) { + g_Lara.request_gun_type = LGT_FLARE; + } + } + + if (g_Lara.request_gun_type != g_Lara.gun_type || (g_Input & IN_DRAW)) { + if (g_Lara.request_gun_type == LGT_FLARE + || (g_Lara.skidoo == NO_ITEM && g_Lara.water_status != LWS_CHEAT + && (g_Lara.request_gun_type == LGT_HARPOON + || g_Lara.water_status == LWS_ABOVE_WATER + || (g_Lara.water_status == LWS_WADE + && g_Lara.water_surface_dist + > -g_Weapons[g_Lara.gun_type].gun_height)))) { + if (g_Lara.gun_type == LGT_FLARE) { + Flare_Create(0); + Flare_UndrawMeshes(); + g_Lara.flare_control_left = 0; + } + g_Lara.gun_type = g_Lara.request_gun_type; + Gun_InitialiseNewWeapon(); + g_Lara.gun_status = LGS_DRAW; + g_Lara.right_arm.frame_num = 0; + g_Lara.left_arm.frame_num = 0; + } else { + g_Lara.last_gun_type = g_Lara.request_gun_type; + if (g_Lara.gun_type == LGT_FLARE) { + g_Lara.request_gun_type = LGT_FLARE; + } else { + g_Lara.gun_type = g_Lara.request_gun_type; + } + } + } + } else if (g_Lara.gun_status == LGS_READY) { + if ((g_InputDB & IN_FLARE) && Inv_RequestItem(O_FLARES_ITEM)) { + g_Lara.request_gun_type = LGT_FLARE; + } + + if ((g_Input & IN_DRAW) || g_Lara.request_gun_type != g_Lara.gun_type) { + g_Lara.gun_status = LGS_UNDRAW; + } else if ( + g_Lara.gun_type == LGT_HARPOON + && g_Lara.water_status == LWS_CHEAT) { + g_Lara.gun_status = LGS_UNDRAW; + } else if ( + g_Lara.gun_type != LGT_HARPOON + && g_Lara.water_status != LWS_ABOVE_WATER + && (g_Lara.water_status != LWS_WADE + || g_Lara.water_surface_dist + < -g_Weapons[g_Lara.gun_type].gun_height)) { + g_Lara.gun_status = LGS_UNDRAW; + } + } + + switch (g_Lara.gun_status) { + case LGS_ARMLESS: + if (g_Lara.gun_type == LGT_FLARE) { + if (g_Lara.skidoo != NO_ITEM + || Gun_CheckForHoldingState(g_LaraItem->current_anim_state)) { + if (!g_Lara.flare_control_left) { + g_Lara.left_arm.frame_num = LF_FL_2_HOLD; + g_Lara.flare_control_left = 1; + } else if (g_Lara.left_arm.frame_num != LF_FL_HOLD) { + g_Lara.left_arm.frame_num++; + if (g_Lara.left_arm.frame_num == LF_FL_END) { + g_Lara.left_arm.frame_num = LF_FL_HOLD; + } + } + } else { + g_Lara.flare_control_left = 0; + } + Flare_DoInHand(g_Lara.flare_age); + Flare_SetArm(g_Lara.left_arm.frame_num); + } + break; + + case LGS_HANDS_BUSY: + if (g_Lara.gun_type == LGT_FLARE) { + g_Lara.flare_control_left = g_Lara.skidoo != NO_ITEM + || Gun_CheckForHoldingState(g_LaraItem->current_anim_state); + Flare_DoInHand(g_Lara.flare_age); + Flare_SetArm(g_Lara.left_arm.frame_num); + } + break; + + case LGS_DRAW: + if (g_Lara.gun_type != LGT_FLARE && g_Lara.gun_type != LGT_UNARMED) { + g_Lara.last_gun_type = g_Lara.gun_type; + } + + switch (g_Lara.gun_type) { + case LGT_PISTOLS: + case LGT_MAGNUMS: + case LGT_UZIS: + if (g_Camera.type != CAM_CINEMATIC && g_Camera.type != CAM_LOOK) { + g_Camera.type = CAM_COMBAT; + } + Gun_Pistols_Draw(g_Lara.gun_type); + break; + + case LGT_SHOTGUN: + case LGT_M16: + case LGT_GRENADE: + case LGT_HARPOON: + if (g_Camera.type != CAM_CINEMATIC && g_Camera.type != CAM_LOOK) { + g_Camera.type = CAM_COMBAT; + } + Gun_Rifle_Draw(g_Lara.gun_type); + break; + + case LGT_FLARE: + Flare_Draw(); + break; + + default: + g_Lara.gun_status = LGS_ARMLESS; + break; + } + break; + + case LGS_UNDRAW: + g_Lara.mesh_ptrs[LM_HEAD] = + g_Meshes[g_Objects[O_LARA].mesh_idx + LM_HEAD]; + + switch (g_Lara.gun_type) { + case LGT_PISTOLS: + case LGT_MAGNUMS: + case LGT_UZIS: + Gun_Pistols_Undraw(g_Lara.gun_type); + break; + + case LGT_SHOTGUN: + case LGT_M16: + case LGT_GRENADE: + case LGT_HARPOON: + Gun_Rifle_Undraw(g_Lara.gun_type); + break; + + case LGT_FLARE: + Flare_Undraw(); + break; + default: + return; + } + break; + + case LGS_READY: + if (g_Lara.pistol_ammo.ammo && (g_Input & IN_ACTION)) { + g_Lara.mesh_ptrs[LM_HEAD] = + g_Meshes[g_Objects[O_LARA_UZIS].mesh_idx + LM_HEAD]; + } else { + g_Lara.mesh_ptrs[LM_HEAD] = + g_Meshes[g_Objects[O_LARA].mesh_idx + LM_HEAD]; + } + + if (g_Camera.type != CAM_CINEMATIC && g_Camera.type != CAM_LOOK) { + g_Camera.type = CAM_COMBAT; + } + + if (g_Input & IN_ACTION) { + AMMO_INFO *const ammo = Gun_GetAmmoInfo(g_Lara.gun_type); + assert(ammo != NULL); + + if (ammo->ammo <= 0) { + ammo->ammo = 0; + Sound_Effect(SFX_CLICK, &g_LaraItem->pos, SPM_NORMAL); + g_Lara.request_gun_type = + Inv_RequestItem(O_PISTOL_ITEM) ? LGT_PISTOLS : LGT_UNARMED; + break; + } + } + + switch (g_Lara.gun_type) { + case LGT_PISTOLS: + case LGT_MAGNUMS: + case LGT_UZIS: + Gun_Pistols_Control(g_Lara.gun_type); + break; + + case LGT_SHOTGUN: + case LGT_M16: + case LGT_GRENADE: + case LGT_HARPOON: + Gun_Rifle_Control(g_Lara.gun_type); + break; + + default: + return; + } + break; + + case LGS_SPECIAL: + Flare_Draw(); + break; + + default: + return; + } +} + +void __cdecl Gun_InitialiseNewWeapon(void) +{ + g_Lara.left_arm.flash_gun = 0; + g_Lara.left_arm.frame_num = 0; + g_Lara.left_arm.lock = 0; + g_Lara.left_arm.rot.x = 0; + g_Lara.left_arm.rot.y = 0; + g_Lara.left_arm.rot.z = 0; + g_Lara.right_arm.flash_gun = 0; + g_Lara.right_arm.frame_num = 0; + g_Lara.right_arm.lock = 0; + g_Lara.right_arm.rot.x = 0; + g_Lara.right_arm.rot.y = 0; + g_Lara.right_arm.rot.z = 0; + g_Lara.target = NULL; + + switch (g_Lara.gun_type) { + case LGT_PISTOLS: + case LGT_MAGNUMS: + case LGT_UZIS: + g_Lara.left_arm.frame_base = g_Objects[O_LARA_PISTOLS].frame_base; + g_Lara.right_arm.frame_base = g_Objects[O_LARA_PISTOLS].frame_base; + if (g_Lara.gun_status != LGS_ARMLESS) { + Gun_Pistols_DrawMeshes(g_Lara.gun_type); + } + break; + + case LGT_SHOTGUN: + case LGT_M16: + case LGT_GRENADE: + case LGT_HARPOON: + g_Lara.left_arm.frame_base = + g_Objects[Gun_GetWeaponAnim(g_Lara.gun_type)].frame_base; + g_Lara.right_arm.frame_base = + g_Objects[Gun_GetWeaponAnim(g_Lara.gun_type)].frame_base; + if (g_Lara.gun_status != LGS_ARMLESS) { + Gun_Rifle_DrawMeshes(g_Lara.gun_type); + } + break; + + case LGT_FLARE: + g_Lara.left_arm.frame_base = g_Objects[O_LARA_FLARE].frame_base; + g_Lara.right_arm.frame_base = g_Objects[O_LARA_FLARE].frame_base; + if (g_Lara.gun_status != LGS_ARMLESS) { + Flare_DrawMeshes(); + } + break; + + default: + g_Lara.left_arm.frame_base = g_Anims[g_LaraItem->anim_num].frame_ptr; + g_Lara.right_arm.frame_base = g_Anims[g_LaraItem->anim_num].frame_ptr; + break; + } +} + +int32_t __cdecl Gun_GetWeaponAnim(const LARA_GUN_TYPE gun_type) +{ + switch (gun_type) { + case LGT_UNARMED: + return O_LARA; + case LGT_PISTOLS: + return O_LARA_PISTOLS; + case LGT_MAGNUMS: + return O_LARA_MAGNUMS; + case LGT_UZIS: + return O_LARA_UZIS; + case LGT_SHOTGUN: + return O_LARA_SHOTGUN; + case LGT_M16: + return O_LARA_M16; + case LGT_GRENADE: + return O_LARA_GRENADE; + case LGT_HARPOON: + return O_LARA_HARPOON; + default: + return NO_OBJECT; + } +} + +GAME_OBJECT_ID Gun_GetGunObject(const LARA_GUN_TYPE gun_type) +{ + // clang-format off + switch (gun_type) { + case LGT_PISTOLS: return O_PISTOL_ITEM; + case LGT_MAGNUMS: return O_MAGNUM_ITEM; + case LGT_UZIS: return O_UZI_ITEM; + case LGT_SHOTGUN: return O_SHOTGUN_ITEM; + case LGT_HARPOON: return O_HARPOON_ITEM; + case LGT_M16: return O_M16_ITEM; + case LGT_GRENADE: return O_GRENADE_ITEM; + default: return NO_OBJECT; + } + // clang-format on +} + +GAME_OBJECT_ID Gun_GetAmmoObject(const LARA_GUN_TYPE gun_type) +{ + // clang-format off + switch (gun_type) { + case LGT_PISTOLS: return O_PISTOL_AMMO_ITEM; + case LGT_MAGNUMS: return O_MAGNUM_AMMO_ITEM; + case LGT_UZIS: return O_UZI_AMMO_ITEM; + case LGT_SHOTGUN: return O_SHOTGUN_AMMO_ITEM; + case LGT_HARPOON: return O_HARPOON_AMMO_ITEM; + case LGT_M16: return O_M16_AMMO_ITEM; + case LGT_GRENADE: return O_GRENADE_AMMO_ITEM; + default: return NO_OBJECT; + } + // clang-format on +} + +int32_t Gun_GetAmmoQuantity(const LARA_GUN_TYPE gun_type) +{ + // clang-format off + switch (gun_type) { + case LGT_PISTOLS: return 1; + case LGT_MAGNUMS: return MAGNUM_AMMO_QTY; + case LGT_UZIS: return UZI_AMMO_QTY; + case LGT_SHOTGUN: return SHOTGUN_AMMO_QTY; + case LGT_HARPOON: return HARPOON_AMMO_QTY; + case LGT_M16: return M16_AMMO_QTY; + case LGT_GRENADE: return GRENADE_AMMO_QTY; + default: return -1; + } + // clang-format on +} + +AMMO_INFO *Gun_GetAmmoInfo(const LARA_GUN_TYPE gun_type) +{ + // clang-format off + switch (gun_type) { + case LGT_PISTOLS: return &g_Lara.pistol_ammo; + case LGT_MAGNUMS: return &g_Lara.magnum_ammo; + case LGT_UZIS: return &g_Lara.uzi_ammo; + case LGT_SHOTGUN: return &g_Lara.shotgun_ammo; + case LGT_HARPOON: return &g_Lara.harpoon_ammo; + case LGT_M16: return &g_Lara.m16_ammo; + case LGT_GRENADE: return &g_Lara.grenade_ammo; + case LGT_SKIDOO: return &g_Lara.pistol_ammo; + default: return NULL; + } + // clang-format on +} + +void Gun_SetLaraHandLMesh(const LARA_GUN_TYPE weapon_type) +{ + const GAME_OBJECT_ID object_id = Gun_GetWeaponAnim(weapon_type); + assert(object_id != NO_OBJECT); + Lara_SwapSingleMesh(LM_HAND_L, object_id); +} + +void Gun_SetLaraHandRMesh(const LARA_GUN_TYPE weapon_type) +{ + const GAME_OBJECT_ID object_id = Gun_GetWeaponAnim(weapon_type); + assert(object_id != NO_OBJECT); + Lara_SwapSingleMesh(LM_HAND_R, object_id); +} + +void Gun_SetLaraBackMesh(const LARA_GUN_TYPE weapon_type) +{ + const GAME_OBJECT_ID object_id = Gun_GetWeaponAnim(weapon_type); + assert(object_id != NO_OBJECT); + Lara_SwapSingleMesh(LM_TORSO, object_id); + g_Lara.back_gun = object_id; +} + +void Gun_SetLaraHolsterLMesh(const LARA_GUN_TYPE weapon_type) +{ + const GAME_OBJECT_ID object_id = Gun_GetWeaponAnim(weapon_type); + assert(object_id != NO_OBJECT); + Lara_SwapSingleMesh(LM_THIGH_L, object_id); +} + +void Gun_SetLaraHolsterRMesh(const LARA_GUN_TYPE weapon_type) +{ + const GAME_OBJECT_ID object_id = Gun_GetWeaponAnim(weapon_type); + assert(object_id != NO_OBJECT); + Lara_SwapSingleMesh(LM_THIGH_R, object_id); +} diff --git a/src/tr2/game/gun/gun.h b/src/tr2/game/gun/gun.h new file mode 100644 index 000000000..ada32ff8a --- /dev/null +++ b/src/tr2/game/gun/gun.h @@ -0,0 +1,19 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Gun_Control(void); +void __cdecl Gun_InitialiseNewWeapon(void); +int32_t __cdecl Gun_GetWeaponAnim(LARA_GUN_TYPE gun_type); + +// TODO: make this a struct +GAME_OBJECT_ID Gun_GetGunObject(LARA_GUN_TYPE gun_type); +GAME_OBJECT_ID Gun_GetAmmoObject(LARA_GUN_TYPE gun_type); +int32_t Gun_GetAmmoQuantity(LARA_GUN_TYPE gun_type); +AMMO_INFO *Gun_GetAmmoInfo(LARA_GUN_TYPE gun_type); + +void Gun_SetLaraBackMesh(LARA_GUN_TYPE weapon_type); +void Gun_SetLaraHandLMesh(LARA_GUN_TYPE weapon_type); +void Gun_SetLaraHandRMesh(LARA_GUN_TYPE weapon_type); +void Gun_SetLaraHolsterLMesh(LARA_GUN_TYPE weapon_type); +void Gun_SetLaraHolsterRMesh(LARA_GUN_TYPE weapon_type); diff --git a/src/tr2/game/gun/gun_misc.c b/src/tr2/game/gun/gun_misc.c new file mode 100644 index 000000000..f80a9390e --- /dev/null +++ b/src/tr2/game/gun/gun_misc.c @@ -0,0 +1,357 @@ +#include "game/gun/gun_misc.h" + +#include "game/gun/gun.h" +#include "game/items.h" +#include "game/los.h" +#include "game/math.h" +#include "game/math_misc.h" +#include "game/matrix.h" +#include "game/random.h" +#include "game/room.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#include + +#define NEAR_ANGLE (PHD_DEGREE * 15) // = 2730 + +static LARA_STATE m_HoldStates[] = { + LS_WALK, LS_STOP, LS_POSE, LS_TURN_RIGHT, LS_TURN_LEFT, + LS_BACK, LS_FAST_TURN, LS_STEP_LEFT, LS_STEP_RIGHT, LS_WADE, + LS_PICKUP, LS_SWITCH_ON, LS_SWITCH_OFF, (LARA_STATE)-1, +}; + +int32_t __cdecl Gun_CheckForHoldingState(const LARA_STATE state) +{ + if (g_Lara.extra_anim) { + return false; + } + + const LARA_STATE *hold_state = m_HoldStates; + while (*hold_state != (LARA_STATE)-1) { + if (*hold_state == state) { + return true; + } + hold_state++; + } + return false; +} + +void __cdecl Gun_TargetInfo(const WEAPON_INFO *const winfo) +{ + if (!g_Lara.target) { + g_Lara.left_arm.lock = 0; + g_Lara.right_arm.lock = 0; + g_Lara.target_angles[0] = 0; + g_Lara.target_angles[1] = 0; + return; + } + + GAME_VECTOR start; + start.pos.x = g_LaraItem->pos.x; + start.pos.y = g_LaraItem->pos.y - 650; + start.pos.z = g_LaraItem->pos.z; + start.room_num = g_LaraItem->room_num; + + GAME_VECTOR target; + Gun_FindTargetPoint(g_Lara.target, &target); + + int16_t angles[2]; + // clang-format off + Math_GetVectorAngles( + target.pos.x - start.pos.x, + target.pos.y - start.pos.y, + target.pos.z - start.pos.z, + angles); + // clang-format on + + angles[0] -= g_LaraItem->rot.y; + angles[1] -= g_LaraItem->rot.x; + + if (!LOS_Check(&start, &target)) { + g_Lara.right_arm.lock = 0; + g_Lara.left_arm.lock = 0; + } else if ( + angles[0] >= winfo->lock_angles[0] && angles[0] <= winfo->lock_angles[1] + && angles[1] >= winfo->lock_angles[2] + && angles[1] <= winfo->lock_angles[3]) { + g_Lara.right_arm.lock = 1; + g_Lara.left_arm.lock = 1; + } else { + if (g_Lara.left_arm.lock + && (angles[0] < winfo->left_angles[0] + || angles[0] > winfo->left_angles[1] + || angles[1] < winfo->left_angles[2] + || angles[1] > winfo->left_angles[3])) { + g_Lara.left_arm.lock = 0; + } + if (g_Lara.right_arm.lock + && (angles[0] < winfo->right_angles[0] + || angles[0] > winfo->right_angles[1] + || angles[1] < winfo->right_angles[2] + || angles[1] > winfo->right_angles[3])) { + g_Lara.right_arm.lock = 0; + } + } + + g_Lara.target_angles[0] = angles[0]; + g_Lara.target_angles[1] = angles[1]; +} + +void __cdecl Gun_GetNewTarget(const WEAPON_INFO *const winfo) +{ + GAME_VECTOR start; + start.pos.x = g_LaraItem->pos.x; + start.pos.y = g_LaraItem->pos.y - 650; + start.pos.z = g_LaraItem->pos.z; + start.room_num = g_LaraItem->room_num; + + int16_t best_y_rot = 0x7FFF; + int32_t best_dist = 0x7FFFFFFF; + ITEM *best_target = NULL; + + const int16_t max_dist = winfo->target_dist; + for (int32_t i = 0; i < NUM_SLOTS; i++) { + const int16_t item_num = g_BaddieSlots[i].item_num; + if (item_num == NO_ITEM || item_num == g_Lara.item_num) { + continue; + } + + ITEM *const item = &g_Items[item_num]; + if (item->hit_points <= 0) { + continue; + } + + const int32_t dx = item->pos.x - start.pos.x; + const int32_t dy = item->pos.y - start.pos.y; + const int32_t dz = item->pos.z - start.pos.z; + if (ABS(dx) > max_dist || ABS(dy) > max_dist || ABS(dz) > max_dist) { + continue; + } + + const int32_t dist = SQUARE(dz) + SQUARE(dy) + SQUARE(dx); + if (dist >= SQUARE(max_dist)) { + continue; + } + + GAME_VECTOR target; + Gun_FindTargetPoint(item, &target); + if (!LOS_Check(&start, &target)) { + continue; + } + + int16_t angles[2]; + Math_GetVectorAngles( + target.pos.x - start.pos.x, target.pos.y - start.pos.y, + target.pos.z - start.pos.z, angles); + angles[0] -= g_Lara.torso_y_rot + g_LaraItem->rot.y; + angles[1] -= g_Lara.torso_x_rot + g_LaraItem->rot.x; + + if (angles[0] >= winfo->lock_angles[0] + && angles[0] <= winfo->lock_angles[1] + && angles[1] >= winfo->lock_angles[2] + && angles[1] <= winfo->lock_angles[3]) { + const int16_t y_rot = ABS(angles[0]); + if (y_rot < best_y_rot + NEAR_ANGLE && dist < best_dist) { + best_dist = dist; + best_y_rot = y_rot; + best_target = item; + } + } + } + + g_Lara.target = best_target; + Gun_TargetInfo(winfo); +} + +void __cdecl Gun_AimWeapon(const WEAPON_INFO *const winfo, LARA_ARM *const arm) +{ + const int16_t speed = winfo->aim_speed; + + int16_t dest_y = 0; + int16_t dest_x = 0; + if (arm->lock) { + dest_y = g_Lara.target_angles[0]; + dest_x = g_Lara.target_angles[1]; + } + + if (arm->rot.y >= dest_y - speed && arm->rot.y <= dest_y + speed) { + arm->rot.y = dest_y; + } else if (arm->rot.y < dest_y) { + arm->rot.y += speed; + } else { + arm->rot.y -= speed; + } + + if (arm->rot.x >= dest_x - speed && arm->rot.x <= dest_x + speed) { + arm->rot.x = dest_x; + } else if (arm->rot.x < dest_x) { + arm->rot.x += speed; + } else { + arm->rot.x -= speed; + } + + arm->rot.z = 0; +} + +int32_t __cdecl Gun_FireWeapon( + const LARA_GUN_TYPE weapon_type, ITEM *const target, const ITEM *const src, + const PHD_ANGLE *const angles) +{ + const WEAPON_INFO *const winfo = &g_Weapons[weapon_type]; + AMMO_INFO *const ammo = Gun_GetAmmoInfo(weapon_type); + assert(ammo != NULL); + + if (ammo == &g_Lara.pistol_ammo || g_SaveGame.bonus_flag) { + ammo->ammo = 1000; + } + + if (ammo->ammo <= 0) { + ammo->ammo = 0; + return 0; + } + + ammo->ammo--; + + const PHD_3DPOS view = { + .pos = { + .x = src->pos.x, + .y = src->pos.y - winfo->gun_height, + .z = src->pos.z, + }, + .rot = { + .x = angles[1] + winfo->shot_accuracy * (Random_GetControl() - PHD_90) / PHD_ONE, + .y = angles[0] + winfo->shot_accuracy * (Random_GetControl() - PHD_90) / PHD_ONE, + .z = 0, + }, + }; + Matrix_GenerateW2V(&view); + + SPHERE spheres[33]; + int32_t sphere_count = Collide_GetSpheres(target, spheres, false); + int32_t best_sphere = -1; + int32_t best_dist = 0x7FFFFFFF; + + for (int32_t i = 0; i < sphere_count; i++) { + const SPHERE *const sphere = &spheres[i]; + const int32_t r = sphere->r; + if (ABS(sphere->x) < r && ABS(sphere->y) < r && sphere->z > r + && SQUARE(sphere->x) + SQUARE(sphere->y) <= SQUARE(r)) { + const int32_t dist = sphere->z - r; + if (dist < best_dist) { + best_dist = dist; + best_sphere = i; + } + } + } + + g_SaveGame.statistics.shots++; + + GAME_VECTOR start; + start.pos.x = view.pos.x; + start.pos.y = view.pos.y; + start.pos.z = view.pos.z; + start.room_num = src->room_num; + + if (best_sphere < 0) { + const int32_t dist = winfo->target_dist; + GAME_VECTOR hit_pos; + hit_pos.pos.x = view.pos.x + ((dist * g_MatrixPtr->_20) >> W2V_SHIFT); + hit_pos.pos.y = view.pos.y + ((dist * g_MatrixPtr->_21) >> W2V_SHIFT); + hit_pos.pos.z = view.pos.z + ((dist * g_MatrixPtr->_22) >> W2V_SHIFT); + hit_pos.room_num = + Room_GetIndexFromPos(hit_pos.pos.x, hit_pos.pos.y, hit_pos.pos.z); + const bool object_on_los = LOS_Check(&start, &hit_pos); + const int16_t item_to_smash = LOS_CheckSmashable(&start, &hit_pos); + if (item_to_smash == NO_ITEM) { + if (!object_on_los) { + Richochet(&hit_pos); + } + return -1; + } else { + Gun_SmashItem(item_to_smash, weapon_type); + return -1; + } + } else { + g_SaveGame.statistics.hits++; + GAME_VECTOR hit_pos; + hit_pos.pos.x = + view.pos.x + ((best_dist * g_MatrixPtr->_20) >> W2V_SHIFT); + hit_pos.pos.y = + view.pos.y + ((best_dist * g_MatrixPtr->_21) >> W2V_SHIFT); + hit_pos.pos.z = + view.pos.z + ((best_dist * g_MatrixPtr->_22) >> W2V_SHIFT); + hit_pos.room_num = + Room_GetIndexFromPos(hit_pos.pos.x, hit_pos.pos.y, hit_pos.pos.z); + const int16_t item_to_smash = LOS_CheckSmashable(&start, &hit_pos); + if (item_to_smash != NO_ITEM) { + Gun_SmashItem(item_to_smash, weapon_type); + } + Gun_HitTarget(target, &hit_pos, winfo->damage); + return 1; + } +} + +void __cdecl Gun_FindTargetPoint( + const ITEM *const item, GAME_VECTOR *const target) +{ + const BOUNDS_16 *const bounds = &Item_GetBestFrame(item)->bounds; + const int32_t x = bounds->min_x + (bounds->max_x - bounds->min_x) / 2; + const int32_t y = bounds->min_y + (bounds->max_y - bounds->min_y) / 3; + const int32_t z = bounds->min_z + (bounds->max_z - bounds->min_z) / 2; + const int32_t cy = Math_Cos(item->rot.y); + const int32_t sy = Math_Sin(item->rot.y); + target->pos.x = item->pos.x + ((cy * x + sy * z) >> W2V_SHIFT); + target->pos.y = item->pos.y + y; + target->pos.z = item->pos.z + ((cy * z - sy * x) >> W2V_SHIFT); + target->room_num = item->room_num; +} + +void __cdecl Gun_HitTarget( + ITEM *const item, const GAME_VECTOR *const hit_pos, const int32_t damage) +{ + if (item->hit_points > 0 && item->hit_points <= damage) { + g_SaveGame.statistics.kills++; + } + Item_TakeDamage(item, damage, true); + + if (hit_pos != NULL) { + DoBloodSplat( + hit_pos->pos.x, hit_pos->pos.y, hit_pos->pos.z, item->speed, + item->rot.y, item->room_num); + } + + if (!g_IsMonkAngry + && (item->object_id == O_MONK_1 || item->object_id == O_MONK_2)) { + CREATURE *const creature = item->data; + creature->flags += damage; + if ((creature->flags & 0xFFF) > MONK_FRIENDLY_FIRE_THRESHOLD + || creature->mood == MOOD_BORED) { + g_IsMonkAngry = true; + } + } +} + +void __cdecl Gun_SmashItem( + const int16_t item_num, const LARA_GUN_TYPE weapon_type) +{ + ITEM *const item = &g_Items[item_num]; + + switch (item->object_id) { + case O_WINDOW_1: + SmashWindow(item_num); + break; + + case O_BELL: + if (item->status != IS_ACTIVE) { + item->status = IS_ACTIVE; + Item_AddActive(item_num); + } + break; + + default: + break; + } +} diff --git a/src/tr2/game/gun/gun_misc.h b/src/tr2/game/gun/gun_misc.h new file mode 100644 index 000000000..20d03326e --- /dev/null +++ b/src/tr2/game/gun/gun_misc.h @@ -0,0 +1,42 @@ +#pragma once + +#include "global/types.h" + +typedef enum { + LF_G_AIM_START = 0, + LF_G_AIM_BEND = 1, + LF_G_AIM_EXTEND = 3, + LF_G_AIM_END = 4, + LF_G_UNDRAW_START = 5, + LF_G_UNDRAW_BEND = 6, + LF_G_UNDRAW_END = 12, + LF_G_DRAW_START = 13, + LF_G_DRAW_END = 23, + LF_G_RECOIL_START = 24, + LF_G_RECOIL_END = 32, +} LARA_GUN_ANIMATION_FRAME; + +typedef enum { + LA_G_AIM = 0, + LA_G_DRAW = 1, + LA_G_RECOIL = 2, + LA_G_UNDRAW = 3, + LA_G_UNAIM = 4, + LA_G_RELOAD = 5, + LA_G_UAIM = 6, + LA_G_UUNAIM = 7, + LA_G_URECOIL = 8, + LA_G_SURF_UNDRAW = 9, +} LARA_GUN_ANIMATION; + +int32_t __cdecl Gun_CheckForHoldingState(const LARA_STATE state); +void __cdecl Gun_TargetInfo(const WEAPON_INFO *winfo); +void __cdecl Gun_GetNewTarget(const WEAPON_INFO *winfo); +void __cdecl Gun_AimWeapon(const WEAPON_INFO *winfo, LARA_ARM *arm); +int32_t __cdecl Gun_FireWeapon( + LARA_GUN_TYPE weapon_type, ITEM *target, const ITEM *src, + const PHD_ANGLE *angles); +void __cdecl Gun_FindTargetPoint(const ITEM *item, GAME_VECTOR *target); +void __cdecl Gun_HitTarget( + ITEM *item, const GAME_VECTOR *hit_pos, int32_t damage); +void __cdecl Gun_SmashItem(int16_t item_num, LARA_GUN_TYPE weapon_type); diff --git a/src/tr2/game/gun/gun_pistols.c b/src/tr2/game/gun/gun_pistols.c new file mode 100644 index 000000000..2b1088535 --- /dev/null +++ b/src/tr2/game/gun/gun_pistols.c @@ -0,0 +1,296 @@ +#include "game/gun/gun_pistols.h" + +#include "game/gun/gun.h" +#include "game/gun/gun_misc.h" +#include "game/input.h" +#include "game/math.h" +#include "game/sound.h" +#include "global/funcs.h" +#include "global/vars.h" + +static bool m_UziRight = false; +static bool m_UziLeft = false; + +void __cdecl Gun_Pistols_SetArmInfo(LARA_ARM *const arm, const int32_t frame) +{ + const int16_t anim_idx = g_Objects[O_LARA_PISTOLS].anim_idx; + + if (frame >= LF_G_AIM_START && frame <= LF_G_AIM_END) { + arm->anim_num = anim_idx; + } else if (frame >= LF_G_UNDRAW_START && frame <= LF_G_UNDRAW_END) { + arm->anim_num = anim_idx + 1; + } else if (frame >= LF_G_DRAW_START && frame <= LF_G_DRAW_END) { + arm->anim_num = anim_idx + 2; + } else if (frame >= LF_G_RECOIL_START && frame <= LF_G_RECOIL_END) { + arm->anim_num = anim_idx + 3; + } + + arm->frame_num = frame; + arm->frame_base = g_Anims[arm->anim_num].frame_ptr; +} + +void __cdecl Gun_Pistols_Draw(const LARA_GUN_TYPE weapon_type) +{ + int16_t frame = g_Lara.left_arm.frame_num + 1; + + if (!(frame >= LF_G_UNDRAW_START && frame <= LF_G_DRAW_END)) { + frame = LF_G_UNDRAW_START; + } else if (frame == LF_G_DRAW_START) { + Gun_Pistols_DrawMeshes(weapon_type); + Sound_Effect(SFX_LARA_DRAW, &g_LaraItem->pos, SPM_NORMAL); + } else if (frame == LF_G_DRAW_END) { + Gun_Pistols_Ready(weapon_type); + frame = LF_G_AIM_START; + } + + Gun_Pistols_SetArmInfo(&g_Lara.right_arm, frame); + Gun_Pistols_SetArmInfo(&g_Lara.left_arm, frame); +} + +void __cdecl Gun_Pistols_Undraw(const LARA_GUN_TYPE weapon_type) +{ + int16_t frame_l = g_Lara.left_arm.frame_num; + if (frame_l >= LF_G_RECOIL_START && frame_l <= LF_G_RECOIL_END) { + frame_l = LF_G_AIM_END; + } else if (frame_l >= LF_G_AIM_BEND && frame_l <= LF_G_AIM_END) { + g_Lara.left_arm.rot.x -= g_Lara.left_arm.rot.x / frame_l; + g_Lara.left_arm.rot.y -= g_Lara.left_arm.rot.y / frame_l; + frame_l--; + } else if (frame_l == LF_G_AIM_START) { + g_Lara.left_arm.rot.x = 0; + g_Lara.left_arm.rot.y = 0; + g_Lara.left_arm.rot.z = 0; + frame_l = LF_G_DRAW_END; + } else if (frame_l == LF_G_DRAW_START) { + Gun_Pistols_UndrawMeshLeft(weapon_type); + Sound_Effect(SFX_LARA_HOLSTER, &g_LaraItem->pos, SPM_NORMAL); + frame_l--; + } else if (frame_l >= LF_G_UNDRAW_BEND && frame_l <= LF_G_DRAW_END) { + frame_l--; + } + Gun_Pistols_SetArmInfo(&g_Lara.left_arm, frame_l); + + int16_t frame_r = g_Lara.right_arm.frame_num; + if (frame_r >= LF_G_RECOIL_START && frame_r <= LF_G_RECOIL_END) { + frame_r = LF_G_AIM_END; + } else if (frame_r >= LF_G_AIM_BEND && frame_r <= LF_G_AIM_END) { + g_Lara.right_arm.rot.x -= g_Lara.right_arm.rot.x / frame_r; + g_Lara.right_arm.rot.y -= g_Lara.right_arm.rot.y / frame_r; + frame_r--; + } else if (frame_r == LF_G_AIM_START) { + g_Lara.right_arm.rot.x = 0; + g_Lara.right_arm.rot.y = 0; + g_Lara.right_arm.rot.z = 0; + frame_r = LF_G_DRAW_END; + } else if (frame_r == LF_G_DRAW_START) { + Gun_Pistols_UndrawMeshRight(weapon_type); + Sound_Effect(SFX_LARA_HOLSTER, &g_LaraItem->pos, SPM_NORMAL); + frame_r--; + } else if (frame_r >= LF_G_UNDRAW_BEND && frame_r <= LF_G_DRAW_END) { + frame_r--; + } + Gun_Pistols_SetArmInfo(&g_Lara.right_arm, frame_r); + + if (frame_l == LF_G_UNDRAW_START && frame_r == LF_G_UNDRAW_START) { + g_Lara.gun_status = LGS_ARMLESS; + g_Lara.target = NULL; + g_Lara.left_arm.frame_num = LF_G_AIM_START; + g_Lara.left_arm.lock = 0; + g_Lara.right_arm.frame_num = LF_G_AIM_START; + g_Lara.right_arm.lock = 0; + } + + if (!(g_Input & IN_LOOK)) { + g_Lara.head_x_rot = + (g_Lara.left_arm.rot.x + g_Lara.right_arm.rot.x) / 4; + g_Lara.head_y_rot = + (g_Lara.left_arm.rot.y + g_Lara.right_arm.rot.y) / 4; + g_Lara.torso_x_rot = g_Lara.head_x_rot; + g_Lara.torso_y_rot = g_Lara.head_y_rot; + } +} + +void __cdecl Gun_Pistols_Ready(const LARA_GUN_TYPE weapon_type) +{ + g_Lara.gun_status = LGS_READY; + g_Lara.target = NULL; + + g_Lara.left_arm.frame_base = g_Objects[O_LARA_PISTOLS].frame_base; + g_Lara.left_arm.frame_num = LF_G_AIM_START; + g_Lara.left_arm.lock = 0; + g_Lara.left_arm.rot.x = 0; + g_Lara.left_arm.rot.y = 0; + g_Lara.left_arm.rot.z = 0; + g_Lara.right_arm.frame_base = g_Objects[O_LARA_PISTOLS].frame_base; + g_Lara.right_arm.frame_num = LF_G_AIM_START; + g_Lara.right_arm.lock = 0; + g_Lara.right_arm.rot.x = 0; + g_Lara.right_arm.rot.y = 0; + g_Lara.right_arm.rot.z = 0; +} + +void __cdecl Gun_Pistols_DrawMeshes(const LARA_GUN_TYPE weapon_type) +{ + Gun_SetLaraHandLMesh(weapon_type); + Gun_SetLaraHandRMesh(weapon_type); + Gun_SetLaraHolsterLMesh(LGT_UNARMED); + Gun_SetLaraHolsterRMesh(LGT_UNARMED); +} + +void Gun_Pistols_UndrawMeshLeft(const LARA_GUN_TYPE weapon_type) +{ + Gun_SetLaraHandLMesh(LGT_UNARMED); + Gun_SetLaraHolsterLMesh(weapon_type); +} + +void Gun_Pistols_UndrawMeshRight(const LARA_GUN_TYPE weapon_type) +{ + Gun_SetLaraHandRMesh(LGT_UNARMED); + Gun_SetLaraHolsterRMesh(weapon_type); +} + +void __cdecl Gun_Pistols_Control(const LARA_GUN_TYPE weapon_type) +{ + const WEAPON_INFO *const winfo = &g_Weapons[weapon_type]; + + if (g_Input & IN_ACTION) { + Gun_TargetInfo(winfo); + } else { + g_Lara.target = NULL; + } + + if (g_Lara.target == NULL) { + Gun_GetNewTarget(winfo); + } + + Gun_AimWeapon(winfo, &g_Lara.left_arm); + Gun_AimWeapon(winfo, &g_Lara.right_arm); + + if (g_Lara.left_arm.lock && !g_Lara.right_arm.lock) { + g_Lara.head_x_rot = g_Lara.left_arm.rot.x / 2; + g_Lara.head_y_rot = g_Lara.left_arm.rot.y / 2; + g_Lara.torso_x_rot = g_Lara.head_x_rot; + g_Lara.torso_y_rot = g_Lara.head_y_rot; + } else if (!g_Lara.left_arm.lock && g_Lara.right_arm.lock) { + g_Lara.head_x_rot = g_Lara.right_arm.rot.x / 2; + g_Lara.head_y_rot = g_Lara.right_arm.rot.y / 2; + g_Lara.torso_x_rot = g_Lara.head_x_rot; + g_Lara.torso_y_rot = g_Lara.head_y_rot; + } else if (g_Lara.right_arm.lock) { + g_Lara.head_x_rot = + (g_Lara.right_arm.rot.x + g_Lara.left_arm.rot.x) / 4; + g_Lara.head_y_rot = + (g_Lara.right_arm.rot.y + g_Lara.left_arm.rot.y) / 4; + g_Lara.torso_x_rot = g_Lara.head_x_rot; + g_Lara.torso_y_rot = g_Lara.head_y_rot; + } + + Gun_Pistols_Animate(weapon_type); + + if (g_Lara.left_arm.flash_gun || g_Lara.right_arm.flash_gun) { + const int32_t c = Math_Cos(g_LaraItem->rot.y); + const int32_t s = Math_Sin(g_LaraItem->rot.y); + const int32_t x = g_LaraItem->pos.x + (s >> (W2V_SHIFT - 10)); + const int32_t y = g_LaraItem->pos.y - WALL_L / 2; + const int32_t z = g_LaraItem->pos.z + (c >> (W2V_SHIFT - 10)); + AddDynamicLight(x, y, z, 12, 11); + } +} + +void __cdecl Gun_Pistols_Animate(const LARA_GUN_TYPE weapon_type) +{ + const WEAPON_INFO *const winfo = &g_Weapons[weapon_type]; + + bool sound_already = false; + int16_t angles[2]; + + int32_t frame_r = g_Lara.right_arm.frame_num; + if (!g_Lara.right_arm.lock + && (!(g_Input & IN_ACTION) || g_Lara.target != NULL)) { + if (frame_r >= LF_G_RECOIL_START && frame_r <= LF_G_RECOIL_END) { + frame_r = LF_G_AIM_END; + } else if (frame_r >= LF_G_AIM_BEND && frame_r <= LF_G_AIM_END) { + frame_r--; + } + if (m_UziRight) { + Sound_Effect(winfo->sample_num + 1, &g_LaraItem->pos, SPM_NORMAL); + m_UziRight = false; + } + } else if (frame_r >= LF_G_AIM_START && frame_r <= LF_G_AIM_EXTEND) { + frame_r++; + } else if (frame_r == LF_G_AIM_END) { + if (g_Input & IN_ACTION) { + angles[0] = g_Lara.right_arm.rot.y + g_LaraItem->rot.y; + angles[1] = g_Lara.right_arm.rot.x; + if (Gun_FireWeapon( + weapon_type, g_Lara.target, g_LaraItem, angles)) { + g_Lara.right_arm.flash_gun = winfo->flash_time; + Sound_Effect(winfo->sample_num, &g_LaraItem->pos, SPM_NORMAL); + sound_already = true; + if (weapon_type == LGT_UZIS) { + m_UziRight = true; + } + } + frame_r = LF_G_RECOIL_START; + } else if (m_UziRight) { + Sound_Effect(winfo->sample_num + 1, &g_LaraItem->pos, SPM_NORMAL); + m_UziRight = false; + } + } else if (frame_r >= LF_G_RECOIL_START && frame_r <= LF_G_RECOIL_END) { + frame_r++; + if (frame_r == LF_G_RECOIL_START + winfo->recoil_frame) { + frame_r = LF_G_AIM_END; + } + if (weapon_type == LGT_UZIS) { + Sound_Effect(winfo->sample_num, &g_LaraItem->pos, SPM_NORMAL); + m_UziRight = true; + } + } + Gun_Pistols_SetArmInfo(&g_Lara.right_arm, frame_r); + + int16_t frame_l = g_Lara.left_arm.frame_num; + if (!g_Lara.left_arm.lock + && (!(g_Input & IN_ACTION) || g_Lara.target != NULL)) { + if (frame_l >= LF_G_RECOIL_START && frame_l <= LF_G_RECOIL_END) { + frame_l = LF_G_AIM_END; + } else if (frame_l >= LF_G_AIM_BEND && frame_l <= LF_G_AIM_END) { + frame_l--; + } + if (m_UziLeft) { + Sound_Effect(winfo->sample_num + 1, &g_LaraItem->pos, SPM_NORMAL); + m_UziLeft = false; + } + } else if (frame_l >= LF_G_AIM_START && frame_l <= LF_G_AIM_EXTEND) { + frame_l++; + } else if (frame_l == LF_G_AIM_END) { + if (g_Input & IN_ACTION) { + angles[0] = g_Lara.left_arm.rot.y + g_LaraItem->rot.y; + angles[1] = g_Lara.left_arm.rot.x; + if (Gun_FireWeapon( + weapon_type, g_Lara.target, g_LaraItem, angles)) { + g_Lara.left_arm.flash_gun = winfo->flash_time; + if (!sound_already) { + Sound_Effect( + winfo->sample_num, &g_LaraItem->pos, SPM_NORMAL); + } + if (weapon_type == LGT_UZIS) { + m_UziLeft = true; + } + } + frame_l = LF_G_RECOIL_START; + } else if (m_UziLeft) { + Sound_Effect(winfo->sample_num + 1, &g_LaraItem->pos, SPM_NORMAL); + m_UziLeft = false; + } + } else if (frame_l >= LF_G_RECOIL_START) { + frame_l++; + if (frame_l == LF_G_RECOIL_START + winfo->recoil_frame) { + frame_l = LF_G_AIM_END; + } + if (weapon_type == LGT_UZIS) { + Sound_Effect(winfo->sample_num, &g_LaraItem->pos, SPM_NORMAL); + m_UziLeft = true; + } + } + Gun_Pistols_SetArmInfo(&g_Lara.left_arm, frame_l); +} diff --git a/src/tr2/game/gun/gun_pistols.h b/src/tr2/game/gun/gun_pistols.h new file mode 100644 index 000000000..85a935d64 --- /dev/null +++ b/src/tr2/game/gun/gun_pistols.h @@ -0,0 +1,13 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Gun_Pistols_SetArmInfo(LARA_ARM *arm, int32_t frame); +void __cdecl Gun_Pistols_Draw(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_Undraw(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_Ready(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_DrawMeshes(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_UndrawMeshLeft(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_UndrawMeshRight(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_Control(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Pistols_Animate(LARA_GUN_TYPE weapon_type); diff --git a/src/tr2/game/gun/gun_rifle.c b/src/tr2/game/gun/gun_rifle.c new file mode 100644 index 000000000..600043a01 --- /dev/null +++ b/src/tr2/game/gun/gun_rifle.c @@ -0,0 +1,441 @@ +#include "game/gun/gun_rifle.h" + +#include "config.h" +#include "game/gun/gun.h" +#include "game/gun/gun_misc.h" +#include "game/input.h" +#include "game/items.h" +#include "game/lara/misc.h" +#include "game/math.h" +#include "game/random.h" +#include "game/sound.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +static bool m_M16Firing = false; +static bool m_HarpoonFired = false; + +void __cdecl Gun_Rifle_DrawMeshes(const LARA_GUN_TYPE weapon_type) +{ + Gun_SetLaraHandRMesh(weapon_type); + g_Lara.back_gun = O_LARA; +} + +void __cdecl Gun_Rifle_UndrawMeshes(const LARA_GUN_TYPE weapon_type) +{ + Gun_SetLaraHandRMesh(LGT_UNARMED); + g_Lara.back_gun = Gun_GetWeaponAnim(weapon_type); +} + +void __cdecl Gun_Rifle_Ready(const LARA_GUN_TYPE weapon_type) +{ + g_Lara.gun_status = LGS_READY; + g_Lara.target = NULL; + + g_Lara.left_arm.frame_base = + g_Objects[Gun_GetWeaponAnim(weapon_type)].frame_base; + g_Lara.left_arm.frame_num = LF_G_AIM_START; + g_Lara.left_arm.lock = 0; + g_Lara.left_arm.rot.x = 0; + g_Lara.left_arm.rot.y = 0; + g_Lara.left_arm.rot.z = 0; + + g_Lara.right_arm.frame_base = + g_Objects[Gun_GetWeaponAnim(weapon_type)].frame_base; + g_Lara.right_arm.frame_num = LF_G_AIM_START; + g_Lara.right_arm.lock = 0; + g_Lara.right_arm.rot.x = 0; + g_Lara.right_arm.rot.y = 0; + g_Lara.right_arm.rot.z = 0; +} + +void __cdecl Gun_Rifle_Control(const LARA_GUN_TYPE weapon_type) +{ + const WEAPON_INFO *const winfo = &g_Weapons[weapon_type]; + + if (g_Input & IN_ACTION) { + Gun_TargetInfo(winfo); + } else { + g_Lara.target = NULL; + } + + if (g_Lara.target == NULL) { + Gun_GetNewTarget(winfo); + } + + Gun_AimWeapon(winfo, &g_Lara.left_arm); + + if (g_Lara.left_arm.lock) { + g_Lara.head_x_rot = 0; + g_Lara.head_y_rot = 0; + g_Lara.torso_x_rot = g_Lara.left_arm.rot.x; + g_Lara.torso_y_rot = g_Lara.left_arm.rot.y; + } + + Gun_Rifle_Animate(weapon_type); + + if (g_Lara.right_arm.flash_gun + && (weapon_type == LGT_SHOTGUN || weapon_type == LGT_M16)) { + const int32_t c = Math_Cos(g_LaraItem->rot.y); + const int32_t s = Math_Sin(g_LaraItem->rot.y); + const int32_t x = g_LaraItem->pos.x + (s >> (W2V_SHIFT - 10)); + const int32_t y = g_LaraItem->pos.y - WALL_L / 2; + const int32_t z = g_LaraItem->pos.z + (c >> (W2V_SHIFT - 10)); + AddDynamicLight(x, y, z, 12, 11); + } +} + +void __cdecl Gun_Rifle_FireShotgun(void) +{ + bool fired = false; + + int16_t angles[2]; + angles[0] = g_Lara.left_arm.rot.y + g_LaraItem->rot.y; + angles[1] = g_Lara.left_arm.rot.x; + + for (int32_t i = 0; i < SHOTGUN_AMMO_CLIP; i++) { + int16_t dangles[2]; + dangles[0] = angles[0] + + SHOTGUN_PELLET_SCATTER * (Random_GetControl() - 0x4000) / 0x10000; + dangles[1] = angles[1] + + SHOTGUN_PELLET_SCATTER * (Random_GetControl() - 0x4000) / 0x10000; + if (Gun_FireWeapon(LGT_SHOTGUN, g_Lara.target, g_LaraItem, dangles)) { + fired = true; + } + } + + if (fired) { + g_Lara.right_arm.flash_gun = g_Weapons[LGT_SHOTGUN].flash_time; + Sound_Effect( + g_Weapons[LGT_SHOTGUN].sample_num, &g_LaraItem->pos, SPM_NORMAL); + } +} + +void __cdecl Gun_Rifle_FireM16(const bool running) +{ + int16_t angles[2]; + angles[0] = g_Lara.left_arm.rot.y + g_LaraItem->rot.y; + angles[1] = g_Lara.left_arm.rot.x; + + if (g_Config.gameplay.fix_m16_accuracy) { + if (running) { + g_Weapons[LGT_M16].shot_accuracy = PHD_DEGREE * 12; + g_Weapons[LGT_M16].damage = 1; + } else { + g_Weapons[LGT_M16].shot_accuracy = PHD_DEGREE * 4; + g_Weapons[LGT_M16].damage = 3; + } + } + + if (Gun_FireWeapon(LGT_M16, g_Lara.target, g_LaraItem, angles)) { + g_Lara.right_arm.flash_gun = g_Weapons[LGT_M16].flash_time; + } +} + +void __cdecl Gun_Rifle_FireHarpoon(void) +{ + if (g_Lara.harpoon_ammo.ammo <= 0) { + return; + } + + const int16_t item_num = Item_Create(); + if (item_num == NO_ITEM) { + return; + } + + ITEM *const item = &g_Items[item_num]; + item->object_id = O_HARPOON_BOLT; + item->room_num = g_LaraItem->room_num; + + XYZ_32 offset = { + .x = -2, + .y = 373, + .z = 77, + }; + + Lara_GetJointAbsPosition(&offset, LM_HAND_R); + item->pos.x = offset.x; + item->pos.y = offset.y; + item->pos.z = offset.z; + Item_Initialise(item_num); + + if (g_Lara.target != NULL) { + GAME_VECTOR lara_vec; + Gun_FindTargetPoint(g_Lara.target, &lara_vec); + const int32_t dx = lara_vec.pos.x - item->pos.x; + const int32_t dz = lara_vec.pos.z - item->pos.z; + const int32_t dy = lara_vec.pos.y - item->pos.y; + const int32_t dxz = Math_Sqrt(SQUARE(dx) + SQUARE(dz)); + item->rot.y = Math_Atan(dz, dx); + item->rot.x = -Math_Atan(dxz, dy); + item->rot.z = 0; + } else { + item->rot.x = g_Lara.left_arm.rot.x + g_LaraItem->rot.x; + item->rot.y = g_Lara.left_arm.rot.y + g_LaraItem->rot.y; + item->rot.z = 0; + } + + item->fall_speed = + (-HARPOON_BOLT_SPEED * Math_Sin(item->rot.x)) >> W2V_SHIFT; + item->speed = (HARPOON_BOLT_SPEED * Math_Cos(item->rot.x)) >> W2V_SHIFT; + Item_AddActive(item_num); + + if (!g_SaveGame.bonus_flag) { + g_Lara.harpoon_ammo.ammo--; + } + g_SaveGame.statistics.shots++; +} + +void __cdecl Gun_Rifle_FireGrenade(void) +{ + if (g_Lara.grenade_ammo.ammo <= 0) { + return; + } + + const int16_t item_num = Item_Create(); + if (item_num == NO_ITEM) { + return; + } + + ITEM *const item = &g_Items[item_num]; + item->object_id = O_GRENADE; + item->room_num = g_LaraItem->room_num; + + XYZ_32 offset = { + .x = -2, + .y = 373, + .z = 77, + }; + Lara_GetJointAbsPosition(&offset, LM_HAND_R); + item->pos.x = offset.x; + item->pos.y = offset.y; + item->pos.z = offset.z; + Item_Initialise(item_num); + + item->rot.x = g_Lara.left_arm.rot.x + g_LaraItem->rot.x; + item->rot.y = g_Lara.left_arm.rot.y + g_LaraItem->rot.y; + item->rot.z = 0; + item->speed = GRENADE_SPEED; + item->fall_speed = 0; + Item_AddActive(item_num); + + if (!g_SaveGame.bonus_flag) { + g_Lara.grenade_ammo.ammo--; + } + g_SaveGame.statistics.shots++; +} + +void __cdecl Gun_Rifle_Draw(const LARA_GUN_TYPE weapon_type) +{ + ITEM *item; + if (g_Lara.weapon_item != NO_ITEM) { + item = &g_Items[g_Lara.weapon_item]; + } else { + g_Lara.weapon_item = Item_Create(); + item = &g_Items[g_Lara.weapon_item]; + item->object_id = Gun_GetWeaponAnim(weapon_type); + if (weapon_type == LGT_GRENADE) { + item->anim_num = g_Objects[O_LARA_GRENADE].anim_idx; + } else { + item->anim_num = g_Objects[item->object_id].anim_idx + 1; + } + item->frame_num = g_Anims[item->anim_num].frame_base; + item->goal_anim_state = LA_G_DRAW; + item->current_anim_state = LA_G_DRAW; + item->status = IS_ACTIVE; + item->room_num = NO_ROOM; + g_Lara.right_arm.frame_base = g_Objects[item->object_id].frame_base; + g_Lara.left_arm.frame_base = g_Objects[item->object_id].frame_base; + } + Item_Animate(item); + + if (item->current_anim_state == LA_G_AIM + || item->current_anim_state == LA_G_UAIM) { + Gun_Rifle_Ready(weapon_type); + } else if (item->frame_num - g_Anims[item->anim_num].frame_base == 10) { + Gun_Rifle_DrawMeshes(weapon_type); + } else if (g_Lara.water_status == LWS_UNDERWATER) { + item->goal_anim_state = LA_G_UAIM; + } + + g_Lara.left_arm.anim_num = item->anim_num; + g_Lara.left_arm.frame_base = g_Anims[item->anim_num].frame_ptr; + g_Lara.left_arm.frame_num = + item->frame_num - g_Anims[item->anim_num].frame_base; + g_Lara.right_arm.anim_num = item->anim_num; + g_Lara.right_arm.frame_base = g_Anims[item->anim_num].frame_ptr; + g_Lara.right_arm.frame_num = + item->frame_num - g_Anims[item->anim_num].frame_base; +} + +void __cdecl Gun_Rifle_Undraw(const LARA_GUN_TYPE weapon_type) +{ + ITEM *const item = &g_Items[g_Lara.weapon_item]; + if (g_Lara.water_status == LWS_SURFACE) { + item->goal_anim_state = LA_G_SURF_UNDRAW; + } else { + item->goal_anim_state = LA_G_UNDRAW; + } + Item_Animate(item); + + if (item->status == IS_DEACTIVATED) { + Item_Kill(g_Lara.weapon_item); + g_Lara.weapon_item = NO_ITEM; + g_Lara.gun_status = LGS_ARMLESS; + g_Lara.target = NULL; + g_Lara.left_arm.frame_num = 0; + g_Lara.left_arm.lock = 0; + g_Lara.right_arm.frame_num = 0; + g_Lara.right_arm.lock = 0; + } else if ( + item->current_anim_state == LA_G_UNDRAW + && item->frame_num - g_Anims[item->anim_num].frame_base == 21) { + Gun_Rifle_UndrawMeshes(weapon_type); + } + + g_Lara.left_arm.anim_num = item->anim_num; + g_Lara.left_arm.frame_base = g_Anims[item->anim_num].frame_ptr; + g_Lara.left_arm.frame_num = + item->frame_num - g_Anims[item->anim_num].frame_base; + g_Lara.right_arm.anim_num = item->anim_num; + g_Lara.right_arm.frame_base = g_Anims[item->anim_num].frame_ptr; + g_Lara.right_arm.frame_num = + item->frame_num - g_Anims[item->anim_num].frame_base; +} + +void __cdecl Gun_Rifle_Animate(const LARA_GUN_TYPE weapon_type) +{ + const bool running = weapon_type == LGT_M16 && g_LaraItem->speed != 0; + ITEM *const item = &g_Items[g_Lara.weapon_item]; + + switch (item->current_anim_state) { + case LA_G_AIM: + m_M16Firing = false; + if (m_HarpoonFired) { + item->goal_anim_state = LA_G_RELOAD; + m_HarpoonFired = false; + } else if (g_Lara.water_status == LWS_UNDERWATER || running) { + item->goal_anim_state = LA_G_UAIM; + } else if ( + ((g_Input & IN_ACTION) && g_Lara.target == NULL) + || g_Lara.left_arm.lock) { + item->goal_anim_state = LA_G_RECOIL; + } else { + item->goal_anim_state = LA_G_UNAIM; + } + break; + + case LA_G_UAIM: + m_M16Firing = false; + if (m_HarpoonFired) { + item->goal_anim_state = LA_G_RELOAD; + m_HarpoonFired = false; + } else if (g_Lara.water_status != LWS_UNDERWATER && !running) { + item->goal_anim_state = LA_G_AIM; + } else if ( + ((g_Input & IN_ACTION) && g_Lara.target == NULL) + || g_Lara.left_arm.lock) { + item->goal_anim_state = LA_G_URECOIL; + } else { + item->goal_anim_state = LA_G_UUNAIM; + } + break; + + case LA_G_RECOIL: + if (item->frame_num - g_Anims[item->anim_num].frame_base == 0) { + item->goal_anim_state = LA_G_UNAIM; + if (g_Lara.water_status != LWS_UNDERWATER && !running + && !m_HarpoonFired) { + if (g_Input & IN_ACTION) { + if (g_Lara.target == NULL || g_Lara.left_arm.lock) { + switch (weapon_type) { + case LGT_HARPOON: + Gun_Rifle_FireHarpoon(); + if ((g_Lara.harpoon_ammo.ammo % HARPOON_RECOIL) + == 0) { + m_HarpoonFired = true; + } + break; + + case LGT_GRENADE: + Gun_Rifle_FireGrenade(); + break; + + case LGT_M16: + Gun_Rifle_FireM16(false); + Sound_Effect( + SFX_M16_FIRE, &g_LaraItem->pos, SPM_NORMAL); + m_M16Firing = true; + break; + + default: + Gun_Rifle_FireShotgun(); + break; + } + + item->goal_anim_state = LA_G_RECOIL; + } + } else if (g_Lara.left_arm.lock) { + item->goal_anim_state = LA_G_AIM; + } + } + + if (item->goal_anim_state != LA_G_RECOIL && m_M16Firing) { + Sound_Effect(SFX_M16_STOP, &g_LaraItem->pos, SPM_NORMAL); + m_M16Firing = false; + } + } else if (m_M16Firing) { + Sound_Effect(SFX_M16_FIRE, &g_LaraItem->pos, SPM_NORMAL); + } else if ( + weapon_type == LGT_SHOTGUN && !(g_Input & IN_ACTION) + && !g_Lara.left_arm.lock) { + item->goal_anim_state = LA_G_UNAIM; + } + break; + + case LA_G_URECOIL: + if (item->frame_num - g_Anims[item->anim_num].frame_base == 0) { + item->goal_anim_state = LA_G_UUNAIM; + if ((g_Lara.water_status == LWS_UNDERWATER || running) + && !m_HarpoonFired) { + if (g_Input & IN_ACTION) { + if (g_Lara.target == NULL || g_Lara.left_arm.lock) { + if (weapon_type == LGT_HARPOON) { + Gun_Rifle_FireHarpoon(); + if ((g_Lara.harpoon_ammo.ammo % HARPOON_RECOIL) + == 0) { + m_HarpoonFired = true; + } + } else { + Gun_Rifle_FireM16(true); + } + item->goal_anim_state = LA_G_URECOIL; + } + } else if (g_Lara.left_arm.lock) { + item->goal_anim_state = LA_G_UAIM; + } + } + } + + if (weapon_type == LGT_M16 && item->goal_anim_state == LA_G_URECOIL) { + Sound_Effect(SFX_M16_FIRE, &g_LaraItem->pos, SPM_NORMAL); + } + break; + + default: + break; + } + + Item_Animate(item); + g_Lara.left_arm.anim_num = item->anim_num; + g_Lara.left_arm.frame_base = g_Anims[item->anim_num].frame_ptr; + g_Lara.left_arm.frame_num = + item->frame_num - g_Anims[item->anim_num].frame_base; + g_Lara.right_arm.anim_num = item->anim_num; + g_Lara.right_arm.frame_base = g_Anims[item->anim_num].frame_ptr; + g_Lara.right_arm.frame_num = + item->frame_num - g_Anims[item->anim_num].frame_base; +} diff --git a/src/tr2/game/gun/gun_rifle.h b/src/tr2/game/gun/gun_rifle.h new file mode 100644 index 000000000..39ca02e01 --- /dev/null +++ b/src/tr2/game/gun/gun_rifle.h @@ -0,0 +1,15 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Gun_Rifle_DrawMeshes(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Rifle_UndrawMeshes(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Rifle_Ready(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Rifle_Control(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Rifle_FireShotgun(void); +void __cdecl Gun_Rifle_FireM16(bool running); +void __cdecl Gun_Rifle_FireHarpoon(void); +void __cdecl Gun_Rifle_FireGrenade(void); +void __cdecl Gun_Rifle_Draw(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Rifle_Undraw(LARA_GUN_TYPE weapon_type); +void __cdecl Gun_Rifle_Animate(LARA_GUN_TYPE weapon_type); diff --git a/src/tr2/game/hwr.c b/src/tr2/game/hwr.c new file mode 100644 index 000000000..8922912b7 --- /dev/null +++ b/src/tr2/game/hwr.c @@ -0,0 +1,284 @@ +#include "game/hwr.h" + +#include "decomp/decomp.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +HRESULT HWR_DrawPrimitive( + D3DPRIMITIVETYPE primitive_type, LPVOID vertices, DWORD vtx_count, + bool is_no_clip) +{ + return g_D3DDev->lpVtbl->DrawPrimitive( + g_D3DDev, primitive_type, D3DVT_TLVERTEX, vertices, vtx_count, + is_no_clip ? D3DDP_DONOTUPDATEEXTENTS | D3DDP_DONOTCLIP : 0); +} + +void __cdecl HWR_InitState(void) +{ + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_FILLMODE, D3DFILL_SOLID); + + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_SHADEMODE, D3DSHADE_GOURAUD); + + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_CULLMODE, D3DCULL_NONE); + + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_SRCBLEND, D3DBLEND_SRCALPHA); + + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_DESTBLEND, D3DBLEND_INVSRCALPHA); + + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_TEXTUREPERSPECTIVE, + g_SavedAppSettings.perspective_correct ? TRUE : FALSE); + + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_DITHERENABLE, + g_SavedAppSettings.dither ? TRUE : FALSE); + + g_AlphaBlendEnabler = g_CurrentDisplayAdapter.shade_restricted + ? D3DRENDERSTATE_STIPPLEDALPHA + : D3DRENDERSTATE_ALPHABLENDENABLE; + + const DWORD texture_filter = g_SavedAppSettings.bilinear_filtering + ? D3DFILTER_LINEAR + : D3DFILTER_NEAREST; + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_TEXTUREMAG, texture_filter); + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_TEXTUREMIN, texture_filter); + + const DWORD blend_mode = + (g_CurrentDisplayAdapter.hw_device_desc.dpcTriCaps.dwTextureBlendCaps + & D3DPTBLENDCAPS_MODULATEALPHA) + ? D3DTBLEND_MODULATEALPHA + : D3DTBLEND_MODULATE; + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_TEXTUREMAPBLEND, blend_mode); + + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_TEXTUREADDRESS, D3DTADDRESS_CLAMP); + + HWR_ResetTexSource(); + HWR_ResetColorKey(); + HWR_ResetZBuffer(); +} + +void __cdecl HWR_ResetTexSource(void) +{ + g_CurrentTexSource = 0; + g_D3DDev->lpVtbl->SetRenderState(g_D3DDev, D3DRENDERSTATE_TEXTUREHANDLE, 0); + g_D3DDev->lpVtbl->SetRenderState(g_D3DDev, D3DRENDERSTATE_FLUSHBATCH, 0); +} + +void __cdecl HWR_ResetColorKey(void) +{ + g_ColorKeyState = FALSE; + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, + g_TexturesAlphaChannel ? D3DRENDERSTATE_ALPHABLENDENABLE + : D3DRENDERSTATE_COLORKEYENABLE, + FALSE); +} + +void __cdecl HWR_ResetZBuffer(void) +{ + g_ZEnableState = FALSE; + g_ZWriteEnableState = FALSE; + if (g_ZBufferSurface != NULL) { + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_ZFUNC, D3DCMP_ALWAYS); + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_ZENABLE, + g_SavedAppSettings.zbuffer ? TRUE : FALSE); + } else { + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_ZFUNC, D3DCMP_LESSEQUAL); + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_ZENABLE, FALSE); + } + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_ZWRITEENABLE, FALSE); +} + +void __cdecl HWR_TexSource(const HWR_TEXTURE_HANDLE tex_source) +{ + if (g_CurrentTexSource != tex_source) { + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_TEXTUREHANDLE, tex_source); + g_CurrentTexSource = tex_source; + } +} + +void __cdecl HWR_EnableColorKey(const bool state) +{ + if (g_ColorKeyState != state) { + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, + g_TexturesAlphaChannel ? D3DRENDERSTATE_ALPHABLENDENABLE + : D3DRENDERSTATE_COLORKEYENABLE, + state ? TRUE : FALSE); + g_ColorKeyState = state; + } +} + +void __cdecl HWR_EnableZBuffer(const bool z_write_enable, const bool z_enable) +{ + if (!g_SavedAppSettings.zbuffer) { + return; + } + + if (g_ZWriteEnableState != z_write_enable) { + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_ZWRITEENABLE, + z_write_enable ? TRUE : FALSE); + g_ZWriteEnableState = z_write_enable; + } + + if (g_ZEnableState != z_enable) { + if (g_ZBufferSurface != NULL) { + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_ZFUNC, + z_enable ? D3DCMP_LESSEQUAL : D3DCMP_ALWAYS); + } else { + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, D3DRENDERSTATE_ZENABLE, z_enable ? TRUE : FALSE); + } + g_ZEnableState = z_enable; + } +} + +void __cdecl HWR_BeginScene(void) +{ + HWR_GetPageHandles(); + WaitPrimaryBufferFlip(); + g_D3DDev->lpVtbl->BeginScene(g_D3DDev); +} + +void __cdecl HWR_DrawPolyList(void) +{ + HWR_EnableZBuffer(false, true); + + for (int32_t i = 0; i < g_SurfaceCount; i++) { + uint16_t *buf_ptr = (uint16_t *)g_SortBuffer[i]._0; + + uint16_t poly_type = *buf_ptr++; + uint16_t tex_page = + (poly_type == POLY_HWR_GTMAP || poly_type == POLY_HWR_WGTMAP) + ? *buf_ptr++ + : 0; + uint16_t vtx_count = *buf_ptr++; + D3DTLVERTEX *vtx_ptr = *(D3DTLVERTEX **)buf_ptr; + + switch (poly_type) { + // triangle fan (texture) + case POLY_HWR_GTMAP: + // triangle fan (texture + colorkey) + case POLY_HWR_WGTMAP: + HWR_TexSource(g_HWR_PageHandles[tex_page]); + HWR_EnableColorKey(poly_type == POLY_HWR_WGTMAP); + HWR_DrawPrimitive(D3DPT_TRIANGLEFAN, vtx_ptr, vtx_count, true); + break; + + // triangle fan (color) + case POLY_HWR_GOURAUD: + HWR_TexSource(0); + HWR_EnableColorKey(false); + HWR_DrawPrimitive(D3DPT_TRIANGLEFAN, vtx_ptr, vtx_count, true); + break; + + // line strip (color) + case POLY_HWR_LINE: + HWR_TexSource(0); + HWR_EnableColorKey(false); + HWR_DrawPrimitive(D3DPT_LINESTRIP, vtx_ptr, vtx_count, true); + break; + + // triangle fan (color + semitransparent) + case POLY_HWR_TRANS: { + DWORD alpha_state; + HWR_TexSource(0); + g_D3DDev->lpVtbl->GetRenderState( + g_D3DDev, g_AlphaBlendEnabler, &alpha_state); + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, g_AlphaBlendEnabler, TRUE); + HWR_DrawPrimitive(D3DPT_TRIANGLEFAN, vtx_ptr, vtx_count, true); + g_D3DDev->lpVtbl->SetRenderState( + g_D3DDev, g_AlphaBlendEnabler, alpha_state); + break; + } + } + } +} + +void __cdecl HWR_LoadTexturePages( + const int32_t pages_count, const void *const pages_buffer, + const RGB_888 *const palette) +{ + int32_t page_idx = -1; + const BYTE *buffer_ptr = (const BYTE *)pages_buffer; + + HWR_FreeTexturePages(); + + if (palette != NULL) { + g_PaletteIndex = CreateTexturePalette(palette); + } + + for (int32_t i = 0; i < pages_count; i++) { + if (palette != NULL) { + page_idx = AddTexturePage8(256, 256, buffer_ptr, g_PaletteIndex); + buffer_ptr += 256 * 256 * 1; + } else { + page_idx = AddTexturePage16(256, 256, buffer_ptr); + buffer_ptr += 256 * 256 * 2; + } + g_HWR_TexturePageIndexes[i] = page_idx < 0 ? -1 : page_idx; + } + + HWR_GetPageHandles(); +} + +void __cdecl HWR_FreeTexturePages(void) +{ + for (int32_t i = 0; i < MAX_TEXTURE_PAGES; i++) { + if (g_HWR_TexturePageIndexes[i] >= 0) { + SafeFreeTexturePage(g_HWR_TexturePageIndexes[i]); + g_HWR_TexturePageIndexes[i] = -1; + } + g_HWR_PageHandles[i] = 0; + } + if (g_PaletteIndex >= 0) { + SafeFreePalette(g_PaletteIndex); + } +} + +void __cdecl HWR_GetPageHandles(void) +{ + for (int32_t i = 0; i < MAX_TEXTURE_PAGES; i++) { + if (g_HWR_TexturePageIndexes[i] < 0) + g_HWR_PageHandles[i] = 0; + else + g_HWR_PageHandles[i] = + GetTexturePageHandle(g_HWR_TexturePageIndexes[i]); + } +} + +bool __cdecl HWR_VertexBufferFull(void) +{ + const int32_t index = + (g_HWR_VertexPtr - g_HWR_VertexBuffer) / sizeof(D3DTLVERTEX); + return index >= MAX_VERTICES; +} + +bool __cdecl HWR_Init(void) +{ + memset(g_HWR_VertexBuffer, 0, sizeof(g_HWR_VertexBuffer)); + memset( + g_HWR_TexturePageIndexes, (uint8_t)-1, + sizeof(g_HWR_TexturePageIndexes)); + return true; +} diff --git a/src/tr2/game/hwr.h b/src/tr2/game/hwr.h new file mode 100644 index 000000000..3b7c1df2e --- /dev/null +++ b/src/tr2/game/hwr.h @@ -0,0 +1,27 @@ +#pragma once + +#include "global/types.h" + +#include +#include +#include + +HRESULT HWR_DrawPrimitive( + D3DPRIMITIVETYPE primitive_type, LPVOID vertices, DWORD vtx_count, + bool is_no_clip); + +void __cdecl HWR_InitState(void); +void __cdecl HWR_ResetTexSource(void); +void __cdecl HWR_ResetColorKey(void); +void __cdecl HWR_ResetZBuffer(void); +void __cdecl HWR_TexSource(HWR_TEXTURE_HANDLE tex_source); +void __cdecl HWR_EnableColorKey(bool state); +void __cdecl HWR_EnableZBuffer(bool z_write_enable, bool z_enable); +void __cdecl HWR_BeginScene(void); +void __cdecl HWR_DrawPolyList(void); +void __cdecl HWR_LoadTexturePages( + int32_t pages_count, const void *pages_buffer, const RGB_888 *palette); +void __cdecl HWR_FreeTexturePages(void); +void __cdecl HWR_GetPageHandles(void); +bool __cdecl HWR_VertexBufferFull(void); +bool __cdecl HWR_Init(void); diff --git a/src/tr2/game/input.c b/src/tr2/game/input.c new file mode 100644 index 000000000..bfff8ac9e --- /dev/null +++ b/src/tr2/game/input.c @@ -0,0 +1,153 @@ +#include "game/input.h" + +#include "game/console/common.h" +#include "global/funcs.h" +#include "global/vars.h" +#include "specific/s_input.h" + +static const char *m_KeyNames[] = { + NULL, "ESC", "1", "2", "3", "4", "5", "6", + "7", "8", "9", "0", "-", "+", "BKSP", "TAB", + "Q", "W", "E", "R", "T", "Y", "U", "I", + "O", "P", "<", ">", "RET", "CTRL", "A", "S", + "D", "F", "G", "H", "J", "K", "L", ";", + "'", "`", "SHIFT", "#", "Z", "X", "C", "V", + "B", "N", "M", ",", ".", "/", "SHIFT", "PADx", + "ALT", "SPACE", "CAPS", NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, "NMLK", NULL, "PAD7", + "PAD8", "PAD9", "PAD-", "PAD4", "PAD5", "PAD6", "PAD+", "PAD1", + "PAD2", "PAD3", "PAD0", "PAD.", NULL, NULL, "\\", NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, "ENTER", "CTRL", NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, "SHIFT", NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, "PAD/", NULL, NULL, + "ALT", NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, "HOME", + "UP", "PGUP", NULL, "LEFT", NULL, "RIGHT", NULL, "END", + "DOWN", "PGDN", "INS", "DEL", NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + "JOY1", "JOY2", "JOY3", "JOY4", "JOY5", "JOY6", "JOY7", "JOY8", + "JOY9", "JOY10", "JOY11", "JOY12", "JOY13", "JOY14", "JOY15", "JOY16", +}; + +bool g_ConflictLayout[INPUT_ROLE_NUMBER_OF] = { false }; +bool m_ListenMode = false; + +bool Input_Update(void) +{ + bool result = S_Input_Update(); + + g_InputDB = Input_GetDebounced(g_Input); + + if (m_ListenMode) { + g_Input = 0; + g_InputDB = 0; + return result; + } + + if (g_InputDB & IN_CONSOLE) { + Console_Open(); + g_Input = 0; + g_InputDB = 0; + } + + return result; +} + +int32_t __cdecl Input_GetDebounced(const int32_t input) +{ + const int32_t result = input & ~g_OldInputDB; + g_OldInputDB = input; + return result; +} + +uint16_t Input_GetAssignedKey(const int32_t layout, const INPUT_ROLE role) +{ + return g_Layout[layout].key[role]; +} + +void Input_AssignKey( + const int32_t layout, const INPUT_ROLE role, const uint16_t key) +{ + g_Layout[layout].key[role] = key; +} + +const char *Input_GetLayoutName(const int32_t layout) +{ + // clang-format off + switch (layout) { + case 0: return g_GF_PCStrings[GF_S_PC_DEFAULT_KEYS]; + case 1: return g_GF_PCStrings[GF_S_PC_USER_KEYS]; + default: return ""; + } + // clang-format on +} + +const char *Input_GetRoleName(const INPUT_ROLE role) +{ + // clang-format off + switch (role) { + case INPUT_ROLE_UP: return g_GF_GameStrings[GF_S_GAME_KEYMAP_RUN]; + case INPUT_ROLE_DOWN: return g_GF_GameStrings[GF_S_GAME_KEYMAP_BACK]; + case INPUT_ROLE_LEFT: return g_GF_GameStrings[GF_S_GAME_KEYMAP_LEFT]; + case INPUT_ROLE_RIGHT: return g_GF_GameStrings[GF_S_GAME_KEYMAP_RIGHT]; + case INPUT_ROLE_STEP_LEFT: return g_GF_GameStrings[GF_S_GAME_KEYMAP_STEP_LEFT]; + case INPUT_ROLE_STEP_RIGHT: return g_GF_GameStrings[GF_S_GAME_KEYMAP_STEP_RIGHT]; + case INPUT_ROLE_SLOW: return g_GF_GameStrings[GF_S_GAME_KEYMAP_WALK]; + case INPUT_ROLE_JUMP: return g_GF_GameStrings[GF_S_GAME_KEYMAP_JUMP]; + case INPUT_ROLE_ACTION: return g_GF_GameStrings[GF_S_GAME_KEYMAP_ACTION]; + case INPUT_ROLE_DRAW_WEAPON: return g_GF_GameStrings[GF_S_GAME_KEYMAP_DRAW_WEAPON]; + case INPUT_ROLE_FLARE: return g_GF_GameStrings[GF_S_GAME_KEYMAP_FLARE]; + case INPUT_ROLE_LOOK: return g_GF_GameStrings[GF_S_GAME_KEYMAP_LOOK]; + case INPUT_ROLE_ROLL: return g_GF_GameStrings[GF_S_GAME_KEYMAP_ROLL]; + case INPUT_ROLE_OPTION: return g_GF_GameStrings[GF_S_GAME_KEYMAP_INVENTORY]; + case INPUT_ROLE_CONSOLE: return "Console"; + default: return ""; + } + // clang-format on +} + +const char *Input_GetKeyName(const uint16_t key) +{ + return m_KeyNames[key]; +} + +void __cdecl Input_CheckConflictsWithDefaults(void) +{ + for (int32_t i = 0; i < INPUT_ROLE_NUMBER_OF; i++) { + g_ConflictLayout[i] = false; + for (int32_t j = 0; j < INPUT_ROLE_NUMBER_OF; j++) { + const uint16_t key1 = Input_GetAssignedKey(0, i); + const uint16_t key2 = Input_GetAssignedKey(1, j); + if (key1 == key2) { + g_ConflictLayout[i] = true; + break; + } + } + } +} + +void Input_EnterListenMode(void) +{ + m_ListenMode = true; +} + +void Input_ExitListenMode(void) +{ + m_ListenMode = false; + S_Input_Update(); + g_OldInputDB = g_Input; + g_InputDB = Input_GetDebounced(g_Input); +} diff --git a/src/tr2/game/input.h b/src/tr2/game/input.h new file mode 100644 index 000000000..799fdd8ba --- /dev/null +++ b/src/tr2/game/input.h @@ -0,0 +1,80 @@ +#pragma once + +#include "global/types.h" + +#define INPUT_MAX_LAYOUT 2 + +typedef enum { + // clang-format off + INPUT_ROLE_UP = 0, + INPUT_ROLE_FORWARD = INPUT_ROLE_UP, + INPUT_ROLE_DOWN = 1, + INPUT_ROLE_BACK = INPUT_ROLE_DOWN, + INPUT_ROLE_LEFT = 2, + INPUT_ROLE_RIGHT = 3, + INPUT_ROLE_STEP_LEFT = 4, + INPUT_ROLE_STEP_RIGHT = 5, + INPUT_ROLE_SLOW = 6, + INPUT_ROLE_JUMP = 7, + INPUT_ROLE_ACTION = 8, + INPUT_ROLE_DRAW_WEAPON = 9, + INPUT_ROLE_FLARE = 10, + INPUT_ROLE_LOOK = 11, + INPUT_ROLE_ROLL = 12, + INPUT_ROLE_OPTION = 13, + INPUT_ROLE_CONSOLE = 14, + INPUT_ROLE_NUMBER_OF = 15, + // clang-format on +} INPUT_ROLE; + +typedef enum { + // clang-format off + IN_FORWARD = 1 << 0, + IN_BACK = 1 << 1, + IN_LEFT = 1 << 2, + IN_RIGHT = 1 << 3, + IN_JUMP = 1 << 4, + IN_DRAW = 1 << 5, + IN_ACTION = 1 << 6, + IN_SLOW = 1 << 7, + IN_OPTION = 1 << 8, + IN_LOOK = 1 << 9, + IN_STEP_LEFT = 1 << 10, + IN_STEP_RIGHT = 1 << 11, + IN_ROLL = 1 << 12, + IN_PAUSE = 1 << 13, + IN_RESERVED1 = 1 << 14, + IN_RESERVED2 = 1 << 15, + IN_DOZY_CHEAT = 1 << 16, + IN_STUFF_CHEAT = 1 << 17, + IN_DEBUG_INFO = 1 << 18, + IN_FLARE = 1 << 19, + IN_SELECT = 1 << 20, + IN_DESELECT = 1 << 21, + IN_SAVE = 1 << 22, + IN_LOAD = 1 << 23, + IN_CONSOLE = 1 << 24, + // clang-format on +} INPUT_STATE; + +typedef struct { + uint16_t key[INPUT_ROLE_NUMBER_OF]; +} INPUT_LAYOUT; + +extern INPUT_LAYOUT g_Layout[2]; +extern bool g_ConflictLayout[INPUT_ROLE_NUMBER_OF]; + +bool Input_Update(void); +int32_t __cdecl Input_GetDebounced(int32_t input); + +void Input_EnterListenMode(void); +void Input_ExitListenMode(void); +bool Input_IsAnythingPressed(void); +void Input_AssignKey(int32_t layout, INPUT_ROLE role, uint16_t key); +uint16_t Input_GetAssignedKey(int32_t layout, INPUT_ROLE role); + +const char *Input_GetLayoutName(int32_t layout); +const char *Input_GetRoleName(INPUT_ROLE role); +const char *Input_GetKeyName(uint16_t key); + +void __cdecl Input_CheckConflictsWithDefaults(void); diff --git a/src/tr2/game/inventory/backpack.c b/src/tr2/game/inventory/backpack.c new file mode 100644 index 000000000..c140a820e --- /dev/null +++ b/src/tr2/game/inventory/backpack.c @@ -0,0 +1,365 @@ +#include "game/inventory/backpack.h" + +#include "game/inventory/common.h" +#include "game/items.h" +#include "global/funcs.h" +#include "global/vars.h" + +void __cdecl Inv_InsertItem(INVENTORY_ITEM *const inv_item) +{ + int32_t n; + + if (inv_item->inv_pos < 100) { + for (n = 0; n < g_Inv_MainObjectsCount; n++) { + if (g_Inv_MainList[n]->inv_pos > inv_item->inv_pos) { + break; + } + } + + for (int32_t i = g_Inv_MainObjectsCount; i > n - 1; i--) { + g_Inv_MainList[i + 1] = g_Inv_MainList[i]; + g_Inv_MainQtys[i + 1] = g_Inv_MainQtys[i]; + } + g_Inv_MainList[n] = inv_item; + g_Inv_MainQtys[n] = 1; + g_Inv_MainObjectsCount++; + } else { + for (n = 0; n < g_Inv_KeyObjectsCount; n++) { + if (g_Inv_KeysList[n]->inv_pos > inv_item->inv_pos) { + break; + } + } + + for (int32_t i = g_Inv_KeyObjectsCount; i > n - 1; i--) { + g_Inv_KeysList[i + 1] = g_Inv_KeysList[i]; + g_Inv_KeysQtys[i + 1] = g_Inv_KeysQtys[i]; + } + g_Inv_KeysList[n] = inv_item; + g_Inv_KeysQtys[n] = 1; + g_Inv_KeyObjectsCount++; + } +} + +int32_t __cdecl Inv_AddItem(const GAME_OBJECT_ID object_id) +{ + const GAME_OBJECT_ID inv_object_id = Inv_GetItemOption(object_id); + + for (int32_t i = 0; i < g_Inv_MainObjectsCount; i++) { + const INVENTORY_ITEM *const inv_item = g_Inv_MainList[i]; + if (inv_item->object_id == inv_object_id) { + const int32_t qty = object_id == O_FLARES_ITEM ? FLARE_AMMO_QTY : 1; + g_Inv_MainQtys[i] += qty; + return true; + } + } + + for (int32_t i = 0; i < g_Inv_KeyObjectsCount; i++) { + const INVENTORY_ITEM *const inv_item = g_Inv_KeysList[i]; + if (inv_item->object_id == inv_object_id) { + g_Inv_KeysQtys[i]++; + return true; + } + } + + if (!g_Objects[inv_object_id == NO_OBJECT ? object_id : inv_object_id] + .loaded) { + return false; + } + switch (object_id) { + case O_COMPASS_OPTION: + case O_COMPASS_ITEM: + Inv_InsertItem(&g_Inv_Item_Stopwatch); + return true; + + case O_PISTOL_ITEM: + case O_PISTOL_OPTION: + Inv_InsertItem(&g_Inv_Item_Pistols); + if (g_Lara.last_gun_type) { + return true; + } + g_Lara.last_gun_type = 1; + return true; + + case O_SHOTGUN_ITEM: + case O_SHOTGUN_OPTION: + for (int32_t i = Inv_RequestItem(O_SHOTGUN_AMMO_ITEM); i > 0; i--) { + Inv_RemoveItem(O_SHOTGUN_AMMO_ITEM); + g_Lara.shotgun_ammo.ammo += SHOTGUN_AMMO_QTY; + } + g_Lara.shotgun_ammo.ammo += SHOTGUN_AMMO_QTY; + Inv_InsertItem(&g_Inv_Item_Shotgun); + if (g_Lara.last_gun_type == LGT_UNARMED) { + g_Lara.last_gun_type = LGT_SHOTGUN; + } + if (!g_Lara.back_gun) { + g_Lara.back_gun = O_LARA_SHOTGUN; + } + Item_GlobalReplace(O_SHOTGUN_ITEM, O_SHOTGUN_AMMO_ITEM); + return false; + + case O_MAGNUM_ITEM: + case O_MAGNUM_OPTION: + for (int32_t i = Inv_RequestItem(O_MAGNUM_AMMO_ITEM); i > 0; i--) { + Inv_RemoveItem(O_MAGNUM_AMMO_ITEM); + g_Lara.magnum_ammo.ammo += MAGNUM_AMMO_QTY; + } + g_Lara.magnum_ammo.ammo += MAGNUM_AMMO_QTY; + Inv_InsertItem(&g_Inv_Item_Magnums); + Item_GlobalReplace(O_MAGNUM_ITEM, O_MAGNUM_AMMO_ITEM); + return false; + + case O_UZI_ITEM: + case O_UZI_OPTION: + for (int32_t i = Inv_RequestItem(O_UZI_AMMO_ITEM); i > 0; i--) { + Inv_RemoveItem(O_UZI_AMMO_ITEM); + g_Lara.uzi_ammo.ammo += UZI_AMMO_QTY; + } + g_Lara.uzi_ammo.ammo += UZI_AMMO_QTY; + Inv_InsertItem(&g_Inv_Item_Uzis); + Item_GlobalReplace(O_UZI_ITEM, O_UZI_AMMO_ITEM); + return false; + + case O_HARPOON_ITEM: + case O_HARPOON_OPTION: + for (int32_t i = Inv_RequestItem(O_HARPOON_AMMO_ITEM); i > 0; i--) { + Inv_RemoveItem(O_HARPOON_AMMO_ITEM); + g_Lara.harpoon_ammo.ammo += HARPOON_AMMO_QTY; + } + g_Lara.harpoon_ammo.ammo += HARPOON_AMMO_QTY; + Inv_InsertItem(&g_Inv_Item_Harpoon); + Item_GlobalReplace(O_HARPOON_ITEM, O_HARPOON_AMMO_ITEM); + return false; + + case O_M16_ITEM: + case O_M16_OPTION: + for (int32_t i = Inv_RequestItem(O_M16_AMMO_ITEM); i > 0; i--) { + Inv_RemoveItem(O_M16_AMMO_ITEM); + g_Lara.m16_ammo.ammo += M16_AMMO_QTY; + } + g_Lara.m16_ammo.ammo += M16_AMMO_QTY; + Inv_InsertItem(&g_Inv_Item_M16); + Item_GlobalReplace(O_M16_ITEM, O_M16_AMMO_ITEM); + return false; + + case O_GRENADE_ITEM: + case O_GRENADE_OPTION: + for (int32_t i = Inv_RequestItem(O_GRENADE_AMMO_ITEM); i > 0; i--) { + Inv_RemoveItem(O_GRENADE_AMMO_ITEM); + g_Lara.grenade_ammo.ammo += GRENADE_AMMO_QTY; + } + g_Lara.grenade_ammo.ammo += GRENADE_AMMO_QTY; + Inv_InsertItem(&g_Inv_Item_Grenade); + Item_GlobalReplace(O_GRENADE_ITEM, O_GRENADE_AMMO_ITEM); + return false; + + case O_SHOTGUN_AMMO_ITEM: + case O_SHOTGUN_AMMO_OPTION: + if (Inv_RequestItem(O_SHOTGUN_ITEM)) { + g_Lara.shotgun_ammo.ammo += 12; + } else { + Inv_InsertItem(&g_Inv_Item_ShotgunAmmo); + } + return false; + + case O_MAGNUM_AMMO_ITEM: + case O_MAGNUM_AMMO_OPTION: + if (Inv_RequestItem(O_MAGNUM_ITEM)) { + g_Lara.magnum_ammo.ammo += 40; + } else { + Inv_InsertItem(&g_Inv_Item_MagnumAmmo); + } + return false; + + case O_UZI_AMMO_ITEM: + case O_UZI_AMMO_OPTION: + if (Inv_RequestItem(O_UZI_ITEM)) { + g_Lara.uzi_ammo.ammo += 80; + } else { + Inv_InsertItem(&g_Inv_Item_UziAmmo); + } + return false; + + case O_HARPOON_AMMO_ITEM: + case O_HARPOON_AMMO_OPTION: + if (Inv_RequestItem(O_HARPOON_ITEM)) { + g_Lara.harpoon_ammo.ammo += 3; + } else { + Inv_InsertItem(&g_Inv_Item_HarpoonAmmo); + } + return false; + + case O_M16_AMMO_ITEM: + case O_M16_AMMO_OPTION: + if (Inv_RequestItem(O_M16_ITEM)) { + g_Lara.m16_ammo.ammo += 40; + } else { + Inv_InsertItem(&g_Inv_Item_M16Ammo); + } + return false; + + case O_GRENADE_AMMO_ITEM: + case O_GRENADE_AMMO_OPTION: + if (Inv_RequestItem(O_GRENADE_ITEM)) { + g_Lara.grenade_ammo.ammo += 2; + } else { + Inv_InsertItem(&g_Inv_Item_GrenadeAmmo); + } + return false; + + case O_SMALL_MEDIPACK_ITEM: + case O_SMALL_MEDIPACK_OPTION: + Inv_InsertItem(&g_Inv_Item_SmallMedi); + return true; + + case O_LARGE_MEDIPACK_ITEM: + case O_LARGE_MEDIPACK_OPTION: + Inv_InsertItem(&g_Inv_Item_LargeMedi); + return true; + + case O_FLARES_ITEM: + case O_FLARES_OPTION: + for (int32_t i = 0; i < FLARE_AMMO_QTY; i++) { + Inv_AddItem(O_FLARE_ITEM); + } + return true; + + case O_FLARE_ITEM: + Inv_InsertItem(&g_Inv_Item_Flare); + return true; + + case O_PUZZLE_ITEM_1: + case O_PUZZLE_OPTION_1: + Inv_InsertItem(&g_Inv_Item_Puzzle1); + return true; + + case O_PUZZLE_ITEM_2: + case O_PUZZLE_OPTION_2: + Inv_InsertItem(&g_Inv_Item_Puzzle2); + return true; + + case O_PUZZLE_ITEM_3: + case O_PUZZLE_OPTION_3: + Inv_InsertItem(&g_Inv_Item_Puzzle3); + return true; + + case O_PUZZLE_ITEM_4: + case O_PUZZLE_OPTION_4: + Inv_InsertItem(&g_Inv_Item_Puzzle4); + return true; + + case O_SECRET_1: + g_SaveGame.statistics.secrets |= 1; + return true; + + case O_SECRET_2: + g_SaveGame.statistics.secrets |= 2; + return true; + + case O_SECRET_3: + g_SaveGame.statistics.secrets |= 4; + return true; + + case O_KEY_ITEM_1: + case O_KEY_OPTION_1: + Inv_InsertItem(&g_Inv_Item_Key1); + return true; + + case O_KEY_ITEM_2: + case O_KEY_OPTION_2: + Inv_InsertItem(&g_Inv_Item_Key2); + return true; + + case O_KEY_ITEM_3: + case O_KEY_OPTION_3: + Inv_InsertItem(&g_Inv_Item_Key3); + return true; + + case O_KEY_ITEM_4: + case O_KEY_OPTION_4: + Inv_InsertItem(&g_Inv_Item_Key4); + return true; + + case O_PICKUP_ITEM_1: + case O_PICKUP_OPTION_1: + Inv_InsertItem(&g_Inv_Item_Pickup1); + return true; + + case O_PICKUP_ITEM_2: + case O_PICKUP_OPTION_2: + Inv_InsertItem(&g_Inv_Item_Pickup2); + return true; + + default: + return false; + } +} + +void Inv_AddItemNTimes(GAME_OBJECT_ID object_id, int32_t qty) +{ + for (int32_t i = 0; i < qty; i++) { + Inv_AddItem(object_id); + } +} + +int32_t __cdecl Inv_RequestItem(const GAME_OBJECT_ID object_id) +{ + const GAME_OBJECT_ID inv_object_id = Inv_GetItemOption(object_id); + + for (int32_t i = 0; i < g_Inv_MainObjectsCount; i++) { + if (g_Inv_MainList[i]->object_id == inv_object_id) { + return g_Inv_MainQtys[i]; + } + } + + for (int32_t i = 0; i < g_Inv_KeyObjectsCount; i++) { + if (g_Inv_KeysList[i]->object_id == inv_object_id) { + return g_Inv_KeysQtys[i]; + } + } + + return 0; +} + +void __cdecl Inv_RemoveAllItems(void) +{ + g_Inv_MainObjectsCount = 0; + g_Inv_MainCurrent = 0; + g_Inv_KeyObjectsCount = 0; + g_Inv_KeysCurrent = 0; +} + +int32_t Inv_RemoveItem(const GAME_OBJECT_ID object_id) +{ + const GAME_OBJECT_ID inv_object_id = Inv_GetItemOption(object_id); + + for (int32_t i = 0; i < g_Inv_MainObjectsCount; i++) { + if (g_Inv_MainList[i]->object_id == inv_object_id) { + g_Inv_MainQtys[i]--; + if (g_Inv_MainQtys[i] > 0) { + return true; + } + g_Inv_MainObjectsCount--; + for (int32_t j = i; j < g_Inv_MainObjectsCount; j++) { + g_Inv_MainList[j] = g_Inv_MainList[j + 1]; + g_Inv_MainQtys[j] = g_Inv_MainQtys[j + 1]; + } + } + } + + for (int32_t i = 0; i < g_Inv_KeyObjectsCount; i++) { + if (g_Inv_KeysList[i]->object_id == inv_object_id) { + g_Inv_KeysQtys[i]--; + if (g_Inv_KeysQtys[i] > 0) { + return true; + } + g_Inv_KeyObjectsCount--; + for (int32_t j = i; j < g_Inv_KeyObjectsCount; j++) { + g_Inv_KeysList[j] = g_Inv_KeysList[j + 1]; + g_Inv_KeysQtys[j] = g_Inv_KeysQtys[j + 1]; + } + return true; + } + } + + return false; +} diff --git a/src/tr2/game/inventory/backpack.h b/src/tr2/game/inventory/backpack.h new file mode 100644 index 000000000..9783e2ebd --- /dev/null +++ b/src/tr2/game/inventory/backpack.h @@ -0,0 +1,10 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Inv_InsertItem(INVENTORY_ITEM *inv_item); +int32_t __cdecl Inv_AddItem(GAME_OBJECT_ID object_id); +void Inv_AddItemNTimes(GAME_OBJECT_ID object_id, int32_t qty); +int32_t __cdecl Inv_RequestItem(GAME_OBJECT_ID object_id); +void __cdecl Inv_RemoveAllItems(void); +int32_t __cdecl Inv_RemoveItem(GAME_OBJECT_ID object_id); diff --git a/src/tr2/game/inventory/common.c b/src/tr2/game/inventory/common.c new file mode 100644 index 000000000..ec36b99df --- /dev/null +++ b/src/tr2/game/inventory/common.c @@ -0,0 +1,1209 @@ +#include "game/inventory/common.h" + +#include "game/demo.h" +#include "game/input.h" +#include "game/inventory/backpack.h" +#include "game/inventory/ring.h" +#include "game/inventory/vars.h" +#include "game/lara/control.h" +#include "game/math_misc.h" +#include "game/matrix.h" +#include "game/music.h" +#include "game/objects/common.h" +#include "game/objects/vars.h" +#include "game/option/option.h" +#include "game/output.h" +#include "game/overlay.h" +#include "game/sound.h" +#include "game/text.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/types.h" +#include "global/vars.h" + +#include + +#include + +#define TITLE_RING_OBJECTS 3 +#define OPTION_RING_OBJECTS 3 + +static TEXTSTRING *m_VersionText = NULL; + +static void M_RemoveItemsText(void); +static void M_RemoveAllText(void); +static void M_ShowItemQuantity(const char *fmt, int32_t qty); +static void M_ShowAmmoQuantity(const char *fmt, int32_t qty); + +static void M_RemoveItemsText(void) +{ + for (int32_t i = 0; i < 2; i++) { + Text_Remove(g_Inv_ItemText[i]); + g_Inv_ItemText[i] = NULL; + } +} + +static void M_RemoveAllText(void) +{ + M_RemoveItemsText(); + + Text_Remove(g_Inv_TagText); + g_Inv_TagText = NULL; + Text_Remove(g_Inv_RingText); + g_Inv_RingText = NULL; + Text_Remove(g_Inv_UpArrow1); + g_Inv_UpArrow1 = NULL; + Text_Remove(g_Inv_UpArrow2); + g_Inv_UpArrow2 = NULL; + Text_Remove(g_Inv_DownArrow1); + g_Inv_DownArrow1 = NULL; + Text_Remove(g_Inv_DownArrow2); + g_Inv_DownArrow2 = NULL; + + Text_Remove(m_VersionText); + m_VersionText = NULL; +} + +static void M_ShowItemQuantity(const char *const fmt, const int32_t qty) +{ + if (g_Inv_ItemText[1] == NULL && !g_SaveGame.bonus_flag) { + char string[64]; + sprintf(string, fmt, qty); + Overlay_MakeAmmoString(string); + g_Inv_ItemText[1] = Text_Create(64, -56, 0, string); + Text_AlignBottom(g_Inv_ItemText[1], true); + Text_CentreH(g_Inv_ItemText[1], true); + } +} + +static void M_ShowAmmoQuantity(const char *const fmt, const int32_t qty) +{ + if (!g_SaveGame.bonus_flag) { + M_ShowItemQuantity(fmt, qty); + } +} + +void __cdecl Inv_InitColors(void) +{ + g_InvColors[INV_COLOR_BLACK] = S_COLOR(0x00, 0x00, 0x00); + g_InvColors[INV_COLOR_GRAY] = S_COLOR(0x40, 0x40, 0x40); + g_InvColors[INV_COLOR_WHITE] = S_COLOR(0xFF, 0xFF, 0xFF); + g_InvColors[INV_COLOR_RED] = S_COLOR(0xFF, 0x00, 0x00); + g_InvColors[INV_COLOR_ORANGE] = S_COLOR(0xFF, 0x80, 0x00); + g_InvColors[INV_COLOR_YELLOW] = S_COLOR(0xFF, 0xFF, 0x00); + g_InvColors[INV_COLOR_DARK_GREEN] = S_COLOR(0x00, 0x80, 0x00); + g_InvColors[INV_COLOR_GREEN] = S_COLOR(0x00, 0xFF, 0x00); + g_InvColors[INV_COLOR_CYAN] = S_COLOR(0x00, 0xFF, 0xFF); + g_InvColors[INV_COLOR_BLUE] = S_COLOR(0x00, 0x00, 0xFF); + g_InvColors[INV_COLOR_MAGENTA] = S_COLOR(0xFF, 0x00, 0xFF); +} + +void __cdecl Inv_Construct(void) +{ + S_SetupAboveWater(0); + + if (g_Inv_Mode != INV_TITLE_MODE) { + TempVideoAdjust(g_HiRes, 1.0); + } + + g_PhdWinRight = g_PhdWinMaxX; + g_PhdWinLeft = 0; + g_PhdWinTop = 0; + g_PhdWinBottom = g_PhdWinMaxY; + + g_Inv_IsActive = 1; + g_Inv_Chosen = 0; + + if (g_Inv_Mode == INV_TITLE_MODE) { + g_Inv_OptionObjectsCount = TITLE_RING_OBJECTS; + if (g_GameFlow.gym_enabled) { + g_Inv_OptionObjectsCount++; + } + m_VersionText = Text_Create(-20, -18, 0, g_TR2XVersion); + Text_AlignRight(m_VersionText, 1); + Text_AlignBottom(m_VersionText, 1); + Text_SetScale(m_VersionText, PHD_ONE * 0.5, PHD_ONE * 0.5); + } else { + g_Inv_OptionObjectsCount = OPTION_RING_OBJECTS; + Text_Remove(m_VersionText); + m_VersionText = NULL; + } + + for (int32_t i = 0; i < 8; i++) { + g_Inv_ExtraData[i] = 0; + } + + for (int32_t i = 0; i < g_Inv_MainObjectsCount; i++) { + INVENTORY_ITEM *const inv_item = g_Inv_MainList[i]; + inv_item->meshes_drawn = inv_item->meshes_sel; + inv_item->current_frame = 0; + inv_item->goal_frame = 0; + inv_item->anim_count = 0; + inv_item->x_rot_pt = 0; + inv_item->x_rot = 0; + inv_item->y_rot = 0; + inv_item->y_trans = 0; + inv_item->z_trans = 0; + if (inv_item->object_id == O_PASSPORT_OPTION) { + inv_item->object_id = O_PASSPORT_CLOSED; + } + } + + for (int32_t i = 0; i < g_Inv_OptionObjectsCount; i++) { + INVENTORY_ITEM *const inv_item = g_Inv_OptionList[i]; + inv_item->current_frame = 0; + inv_item->goal_frame = 0; + inv_item->anim_count = 0; + inv_item->x_rot_pt = 0; + inv_item->x_rot = 0; + inv_item->y_rot = 0; + inv_item->y_trans = 0; + inv_item->z_trans = 0; + if (inv_item->object_id == O_PASSPORT_OPTION) { + inv_item->object_id = O_PASSPORT_CLOSED; + } + } + + g_Inv_MainCurrent = 0; + + if (g_GymInvOpenEnabled && g_Inv_Mode == INV_TITLE_MODE + && !g_GameFlow.load_save_disabled && g_GameFlow.gym_enabled) { + g_Inv_OptionCurrent = 3; // TODO: don't hardcode me + g_SoundOptionLine = 0; + } else { + g_Inv_OptionCurrent = 0; + g_GymInvOpenEnabled = 0; + g_SoundOptionLine = 0; + } +} + +int32_t __cdecl Inv_Display(int32_t inventory_mode) +{ + RING_INFO ring = { 0 }; + IMOTION_INFO imo = { 0 }; + + bool demo_needed = false; + bool pass_open = false; + if (inventory_mode == INV_KEYS_MODE && !g_Inv_KeyObjectsCount) { + g_Inv_Chosen = NO_OBJECT; + return 0; + } + + Overlay_HideGameInfo(); + + Output_AlterFOV(80 * PHD_DEGREE); + g_Inv_Mode = inventory_mode; + g_Inv_NFrames = 2; + + Inv_Construct(); + if (inventory_mode == INV_TITLE_MODE) { + S_FadeInInventory(0); + } else { + S_FadeInInventory(1); + } + + Sound_StopAllSamples(); + if (inventory_mode != INV_TITLE_MODE) { + Music_SetVolume(0); + } + + switch (inventory_mode) { + case INV_TITLE_MODE: + case INV_SAVE_MODE: + case INV_LOAD_MODE: + case INV_DEATH_MODE: + Inv_Ring_Init( + &ring, 1, g_Inv_OptionList, g_Inv_OptionObjectsCount, + g_Inv_OptionCurrent, &imo); + break; + + case INV_KEYS_MODE: + Inv_Ring_Init( + &ring, 2, g_Inv_KeysList, g_Inv_KeyObjectsCount, g_Inv_MainCurrent, + &imo); + break; + + default: + if (g_Inv_MainObjectsCount) { + Inv_Ring_Init( + &ring, 0, g_Inv_MainList, g_Inv_MainObjectsCount, + g_Inv_MainCurrent, &imo); + } else { + Inv_Ring_Init( + &ring, 1, g_Inv_OptionList, g_Inv_OptionObjectsCount, + g_Inv_OptionCurrent, &imo); + } + break; + } + + Sound_Effect(SFX_MENU_SPININ, 0, SPM_ALWAYS); + g_Inv_NFrames = 2; + + do { + if (g_GF_OverrideDir != (GAME_FLOW_DIR)-1) { + INVENTORY_ITEM *inv_item = ring.list[ring.current_object]; + M_RemoveAllText(); + Option_ShutdownInventory(inv_item); + return GFD_OVERRIDE; + } + + if (inventory_mode == INV_TITLE_MODE && g_CD_TrackID > 0) { + Music_Init(); + } + Inv_Ring_CalcAdders(&ring, 24); + Input_Update(); + + if (g_Inv_DemoMode) { + if (g_InputDB != 0) { + return g_GameFlow.on_demo_interrupt; + } + Demo_GetInput(); + if (g_Input == -1) { + return g_GameFlow.on_demo_end; + } + } else if (g_InputDB != 0) { + g_NoInputCounter = 0; + } + + if (g_Inv_Mode != INV_TITLE_MODE || g_Input != 0 || g_InputDB != 0) { + g_NoInputCounter = 0; + } else if (g_GameFlow.num_demos || g_GameFlow.no_input_timeout) { + g_NoInputCounter++; + if (g_NoInputCounter > g_GameFlow.no_input_time) { + demo_needed = true; + } + } + + if (g_StopInventory) { + return GFD_EXIT_TO_TITLE; + } + + if ((g_Inv_Mode == INV_SAVE_MODE || g_Inv_Mode == INV_LOAD_MODE + || g_Inv_Mode == INV_DEATH_MODE) + && !pass_open) { + g_InputDB = IN_SELECT; + g_Input = 0; + } + + for (int32_t frame = 0; frame < g_Inv_NFrames; frame++) { + if (g_Inv_IsOptionsDelay) { + if (g_Inv_OptionsDelayCounter) { + g_Inv_OptionsDelayCounter--; + } else { + g_Inv_IsOptionsDelay = 0; + } + } + Inv_Ring_DoMotions(&ring); + } + ring.camera.pos.z = ring.radius + 598; + + S_InitialisePolyList(0); + if (g_Inv_Mode == INV_TITLE_MODE) { + Inv_DoInventoryPicture(); + } else { + Inv_DoInventoryBackground(); + } + S_AnimateTextures(g_Inv_NFrames); + + PHD_3DPOS view; + Inv_Ring_GetView(&ring, &view); + Matrix_GenerateW2V(&view); + Inv_Ring_Light(&ring); + + Matrix_Push(); + Matrix_TranslateAbs( + ring.ring_pos.pos.x, ring.ring_pos.pos.y, ring.ring_pos.pos.z); + Matrix_RotYXZ( + ring.ring_pos.rot.y, ring.ring_pos.rot.x, ring.ring_pos.rot.z); + + int32_t angle = 0; + for (int32_t j = 0; j < ring.number_of_objects; j++) { + INVENTORY_ITEM *inv_item = ring.list[j]; + if (j != ring.current_object) { + g_LsAdder = LOW_LIGHT; + for (int32_t k = 0; k < g_Inv_NFrames; k++) { + if (inv_item->y_rot < 0) { + inv_item->y_rot += 256; + } else if (inv_item->y_rot > 0) { + inv_item->y_rot -= 256; + } + } + } else { + for (int32_t k = 0; k < g_Inv_NFrames; k++) { + if (ring.rotating) { + g_LsAdder = LOW_LIGHT; + if (inv_item->y_rot > 0) { + inv_item->y_rot -= 512; + } else if (inv_item->y_rot < 0) { + inv_item->y_rot += 512; + } + } else if ( + imo.status == RNG_SELECTED + || imo.status == RNG_DESELECTING + || imo.status == RNG_SELECTING + || imo.status == RNG_DESELECT + || imo.status == RNG_CLOSING_ITEM) { + g_LsAdder = HIGH_LIGHT; + const int32_t delta = + inv_item->y_rot_sel - inv_item->y_rot; + if (delta != 0) { + if (delta > 0 && delta < PHD_180) { + inv_item->y_rot += 1024; + } else { + inv_item->y_rot -= 1024; + } + inv_item->y_rot &= ~(1024 - 1); + } + } else if ( + ring.number_of_objects == 1 + || (!(g_Input & IN_RIGHT) && !(g_Input & IN_LEFT))) { + g_LsAdder = HIGH_LIGHT; + inv_item->y_rot += 256; + } + } + + if ((imo.status == RNG_OPEN || imo.status == RNG_SELECTING + || imo.status == RNG_SELECTED + || imo.status == RNG_DESELECTING + || imo.status == RNG_DESELECT + || imo.status == RNG_CLOSING_ITEM) + && !ring.rotating && !(g_Input & IN_LEFT) + && !(g_Input & IN_RIGHT)) { + Inv_RingNotActive(inv_item); + } + } + + if (imo.status == RNG_OPEN || imo.status == RNG_SELECTING + || imo.status == RNG_SELECTED || imo.status == RNG_DESELECTING + || imo.status == RNG_DESELECT + || imo.status == RNG_CLOSING_ITEM) { + Inv_RingIsOpen(&ring); + } else { + Inv_RingIsNotOpen(&ring); + } + + if (imo.status == RNG_OPENING || imo.status == RNG_CLOSING + || imo.status == RNG_MAIN2OPTION + || imo.status == RNG_OPTION2MAIN + || imo.status == RNG_EXITING_INVENTORY || imo.status == RNG_DONE + || ring.rotating) { + Inv_RingActive(); + } + + Matrix_Push(); + Matrix_RotYXZ(angle, 0, 0); + Matrix_TranslateRel(ring.radius, 0, 0); + Matrix_RotYXZ(PHD_90, inv_item->x_rot_pt, 0); + Inv_DrawInventoryItem(inv_item); + Matrix_Pop(); + angle += ring.angle_adder; + } + + Matrix_Pop(); + Overlay_DrawModeInfo(); + Text_Draw(); + S_OutputPolyList(); + Sound_EndScene(); + + const int32_t frames = S_DumpScreen(); + g_Inv_NFrames = frames; + g_Camera.num_frames = frames; + if (g_CurrentLevel) { + g_SaveGame.statistics.timer += frames / 2; + } + + if (!ring.rotating) { + switch (imo.status) { + case RNG_OPEN: + if ((g_Input & IN_RIGHT) != 0 && ring.number_of_objects > 1) { + Inv_Ring_RotateLeft(&ring); + Sound_Effect(SFX_MENU_ROTATE, 0, SPM_ALWAYS); + break; + } + + if ((g_Input & IN_LEFT) != 0 && ring.number_of_objects > 1) { + Inv_Ring_RotateRight(&ring); + Sound_Effect(SFX_MENU_ROTATE, 0, SPM_ALWAYS); + break; + } + + if (demo_needed + || (((g_InputDB & IN_OPTION) || (g_InputDB & IN_DESELECT)) + && g_Inv_Mode != INV_TITLE_MODE)) { + Sound_Effect(SFX_MENU_SPINOUT, 0, SPM_ALWAYS); + g_Inv_Chosen = NO_OBJECT; + if (ring.type != RT_MAIN) { + g_Inv_OptionCurrent = ring.current_object; + } else { + g_Inv_MainCurrent = ring.current_object; + } + if (inventory_mode == INV_TITLE_MODE) { + S_FadeOutInventory(0); + } else { + S_FadeOutInventory(1); + } + Inv_Ring_MotionSetup(&ring, RNG_CLOSING, RNG_DONE, 32); + Inv_Ring_MotionRadius(&ring, 0); + Inv_Ring_MotionCameraPos(&ring, -1536); + Inv_Ring_MotionRotation( + &ring, PHD_180, ring.ring_pos.rot.y + PHD_180); + g_Input = 0; + g_InputDB = 0; + } + + if ((g_InputDB & IN_SELECT) != 0) { + if ((g_Inv_Mode == INV_SAVE_MODE + || g_Inv_Mode == INV_LOAD_MODE + || g_Inv_Mode == INV_DEATH_MODE) + && !pass_open) { + pass_open = true; + } + + g_SoundOptionLine = 0; + INVENTORY_ITEM *inv_item; + if (ring.type == RT_MAIN) { + g_Inv_MainCurrent = ring.current_object; + inv_item = g_Inv_MainList[ring.current_object]; + } else if (ring.type == RT_OPTION) { + g_Inv_OptionCurrent = ring.current_object; + inv_item = g_Inv_OptionList[ring.current_object]; + } else { + g_Inv_KeysCurrent = ring.current_object; + inv_item = g_Inv_KeysList[ring.current_object]; + } + + inv_item->goal_frame = inv_item->open_frame; + inv_item->anim_direction = 1; + Inv_Ring_MotionSetup( + &ring, RNG_SELECTING, RNG_SELECTED, 16); + Inv_Ring_MotionRotation( + &ring, 0, + -16384 - ring.angle_adder * ring.current_object); + Inv_Ring_MotionItemSelect(&ring, inv_item); + g_Input = 0; + g_InputDB = 0; + + switch (inv_item->object_id) { + case O_COMPASS_OPTION: + Sound_Effect(SFX_MENU_STOPWATCH, 0, SPM_ALWAYS); + break; + + case O_PHOTO_OPTION: + Sound_Effect(SFX_MENU_LARA_HOME, 0, SPM_ALWAYS); + break; + + case O_PISTOL_OPTION: + case O_SHOTGUN_OPTION: + case O_MAGNUM_OPTION: + case O_UZI_OPTION: + case O_HARPOON_OPTION: + case O_M16_OPTION: + case O_GRENADE_OPTION: + Sound_Effect(SFX_MENU_GUNS, 0, SPM_ALWAYS); + break; + + default: + Sound_Effect(SFX_MENU_SPININ, 0, SPM_ALWAYS); + break; + } + } + + if ((g_InputDB & IN_FORWARD) != 0 + && inventory_mode != INV_TITLE_MODE + && inventory_mode != INV_KEYS_MODE) { + if (ring.type == RT_OPTION) { + if (g_Inv_MainObjectsCount > 0) { + Inv_Ring_MotionSetup( + &ring, RNG_CLOSING, RNG_OPTION2MAIN, 24); + Inv_Ring_MotionRadius(&ring, 0); + Inv_Ring_MotionRotation( + &ring, PHD_180, ring.ring_pos.rot.y + PHD_180); + Inv_Ring_MotionCameraPitch(&ring, 0x2000); + imo.misc = 0x2000; + } + g_InputDB = 0; + } else if (ring.type == RT_MAIN) { + if (g_Inv_KeyObjectsCount > 0) { + Inv_Ring_MotionSetup( + &ring, RNG_CLOSING, RNG_MAIN2KEYS, 24); + Inv_Ring_MotionRadius(&ring, 0); + Inv_Ring_MotionRotation( + &ring, PHD_180, ring.ring_pos.rot.y + PHD_180); + Inv_Ring_MotionCameraPitch(&ring, 0x2000); + imo.misc = 0x2000; + } + g_Input = 0; + g_InputDB = 0; + } + } else if ( + (g_InputDB & IN_BACK) != 0 + && inventory_mode != INV_TITLE_MODE + && inventory_mode != INV_KEYS_MODE) { + if (ring.type == RT_KEYS) { + if (g_Inv_MainObjectsCount > 0) { + Inv_Ring_MotionSetup( + &ring, RNG_CLOSING, RNG_KEYS2MAIN, 24); + Inv_Ring_MotionRadius(&ring, 0); + Inv_Ring_MotionRotation( + &ring, PHD_180, ring.ring_pos.rot.y + PHD_180); + Inv_Ring_MotionCameraPitch(&ring, -0x2000); + imo.misc = -0x2000; + } + g_Input = 0; + g_InputDB = 0; + } else if (ring.type == RT_MAIN) { + if (g_Inv_OptionObjectsCount > 0 + && !g_GameFlow.lockout_option_ring) { + Inv_Ring_MotionSetup( + &ring, RNG_CLOSING, RNG_MAIN2OPTION, 24); + Inv_Ring_MotionRadius(&ring, 0); + Inv_Ring_MotionRotation( + &ring, PHD_180, ring.ring_pos.rot.y + PHD_180); + Inv_Ring_MotionCameraPitch(&ring, -0x2000); + imo.misc = -0x2000; + } + g_InputDB = 0; + } + } + break; + + case RNG_MAIN2OPTION: + Inv_Ring_MotionSetup(&ring, RNG_OPENING, RNG_OPEN, 24); + Inv_Ring_MotionRadius(&ring, 688); + ring.camera_pitch = -(int16_t)(imo.misc); + imo.camera_pitch_rate = imo.misc / 24; + imo.camera_pitch_target = 0; + ring.list = g_Inv_OptionList; + ring.type = RT_OPTION; + g_Inv_MainCurrent = ring.current_object; + g_Inv_MainObjectsCount = ring.number_of_objects; + ring.number_of_objects = g_Inv_OptionObjectsCount; + ring.current_object = g_Inv_OptionCurrent; + Inv_Ring_CalcAdders(&ring, 24); + Inv_Ring_MotionRotation( + &ring, PHD_180, + -16384 - ring.angle_adder * ring.current_object); + ring.ring_pos.rot.y = imo.rotate_target + PHD_180; + break; + + case RNG_MAIN2KEYS: + Inv_Ring_MotionSetup(&ring, RNG_OPENING, RNG_OPEN, 24); + Inv_Ring_MotionRadius(&ring, 688); + imo.camera_pitch_target = 0; + ring.camera_pitch = -(int16_t)(imo.misc); + imo.camera_pitch_rate = imo.misc / 24; + g_Inv_MainCurrent = ring.current_object; + g_Inv_MainObjectsCount = ring.number_of_objects; + ring.list = g_Inv_KeysList; + ring.type = RT_KEYS; + ring.number_of_objects = g_Inv_KeyObjectsCount; + ring.current_object = g_Inv_KeysCurrent; + Inv_Ring_CalcAdders(&ring, 24); + Inv_Ring_MotionRotation( + &ring, PHD_180, + -16384 - ring.angle_adder * ring.current_object); + ring.ring_pos.rot.y = imo.rotate_target + PHD_180; + break; + + case RNG_KEYS2MAIN: + Inv_Ring_MotionSetup(&ring, RNG_OPENING, RNG_OPEN, 24); + Inv_Ring_MotionRadius(&ring, 688); + ring.camera_pitch = -(int16_t)(imo.misc); + imo.camera_pitch_rate = imo.misc / 24; + imo.camera_pitch_target = 0; + ring.list = g_Inv_MainList; + ring.type = RT_MAIN; + g_Inv_KeysCurrent = ring.current_object; + ring.number_of_objects = g_Inv_MainObjectsCount; + ring.current_object = g_Inv_MainCurrent; + Inv_Ring_CalcAdders(&ring, 24); + Inv_Ring_MotionRotation( + &ring, PHD_180, + -16384 - ring.angle_adder * ring.current_object); + ring.ring_pos.rot.y = imo.rotate_target + PHD_180; + break; + + case RNG_OPTION2MAIN: + Inv_Ring_MotionSetup(&ring, RNG_OPENING, RNG_OPEN, 24); + Inv_Ring_MotionRadius(&ring, 688); + ring.camera_pitch = -(int16_t)(imo.misc); + imo.camera_pitch_rate = imo.misc / 24; + g_Inv_OptionCurrent = ring.current_object; + g_Inv_OptionObjectsCount = ring.number_of_objects; + imo.camera_pitch_target = 0; + ring.list = g_Inv_MainList; + ring.type = RT_MAIN; + ring.number_of_objects = g_Inv_MainObjectsCount; + ring.current_object = g_Inv_MainCurrent; + Inv_Ring_CalcAdders(&ring, 24); + Inv_Ring_MotionRotation( + &ring, PHD_180, + -16384 - ring.angle_adder * ring.current_object); + ring.ring_pos.rot.y = imo.rotate_target + PHD_180; + break; + + case RNG_SELECTED: { + INVENTORY_ITEM *inv_item = ring.list[ring.current_object]; + if (inv_item->object_id == O_PASSPORT_CLOSED) { + inv_item->object_id = O_PASSPORT_OPTION; + } + + bool busy = false; + for (int32_t frame = 0; frame < g_Inv_NFrames; frame++) { + busy = false; + if (inv_item->y_rot == inv_item->y_rot_sel) { + busy = Inv_AnimateInventoryItem(inv_item); + } + } + + if (!busy && !g_Inv_IsOptionsDelay) { + Option_DoInventory(inv_item); + + if (g_InputDB & IN_DESELECT) { + inv_item->sprite_list = NULL; + Inv_Ring_MotionSetup( + &ring, RNG_CLOSING_ITEM, RNG_DESELECT, 0); + g_Input = 0; + g_InputDB = 0; + if (g_Inv_Mode == INV_LOAD_MODE + || g_Inv_Mode == INV_SAVE_MODE) { + Inv_Ring_MotionSetup( + &ring, RNG_CLOSING_ITEM, RNG_EXITING_INVENTORY, + 0); + g_InputDB = 0; + g_Input = 0; + } + } + + if ((g_InputDB & IN_SELECT) != 0) { + inv_item->sprite_list = NULL; + g_Inv_Chosen = inv_item->object_id; + if (ring.type != RT_MAIN) { + g_Inv_OptionCurrent = ring.current_object; + } else { + g_Inv_MainCurrent = ring.current_object; + } + if (g_Inv_Mode == INV_TITLE_MODE + && (inv_item->object_id == O_DETAIL_OPTION + || inv_item->object_id == O_SOUND_OPTION + || inv_item->object_id == O_CONTROL_OPTION + || inv_item->object_id == O_GAMMA_OPTION)) { + Inv_Ring_MotionSetup( + &ring, RNG_CLOSING_ITEM, RNG_DESELECT, 0); + } else { + Inv_Ring_MotionSetup( + &ring, RNG_CLOSING_ITEM, RNG_EXITING_INVENTORY, + 0); + } + g_Input = 0; + g_InputDB = 0; + } + } + break; + } + + case RNG_DESELECT: + Sound_Effect(SFX_MENU_SPINOUT, 0, SPM_ALWAYS); + Inv_Ring_MotionSetup(&ring, RNG_DESELECTING, RNG_OPEN, 16); + Inv_Ring_MotionRotation( + &ring, 0, -16384 - ring.angle_adder * ring.current_object); + g_Input = 0; + g_InputDB = 0; + break; + + case RNG_CLOSING_ITEM: { + INVENTORY_ITEM *inv_item = ring.list[ring.current_object]; + for (int32_t frame = 0; frame < g_Inv_NFrames; frame++) { + if (!Inv_AnimateInventoryItem(inv_item)) { + if (inv_item->object_id == O_PASSPORT_OPTION) { + inv_item->object_id = O_PASSPORT_CLOSED; + inv_item->current_frame = 0; + } + + imo.count = 16; + imo.status = imo.status_target; + Inv_Ring_MotionItemDeselect(&ring, inv_item); + break; + } + } + break; + } + + case RNG_EXITING_INVENTORY: + if (!imo.count) { + if (inventory_mode == INV_TITLE_MODE) { + S_FadeOutInventory(0); + } else { + S_FadeOutInventory(1); + } + Inv_Ring_MotionSetup(&ring, RNG_CLOSING, RNG_DONE, 32); + Inv_Ring_MotionRadius(&ring, 0); + Inv_Ring_MotionCameraPos(&ring, -1536); + Inv_Ring_MotionRotation( + &ring, PHD_180, ring.ring_pos.rot.y + PHD_180); + } + break; + + default: + break; + } + } + } while (imo.status != RNG_DONE); + + M_RemoveAllText(); + S_FinishInventory(); + g_Inv_IsActive = 0; + + // enable buffering + g_OldInputDB = 0; + + if (demo_needed) { + return GFD_START_DEMO; + } + + if (g_Inv_Chosen == NO_OBJECT) { + if (inventory_mode != INV_TITLE_MODE && g_OptionMusicVolume != 0) { + Music_SetVolume(25 * g_OptionMusicVolume + 5); + } + return 0; + } + + switch (g_Inv_Chosen) { + case O_PASSPORT_OPTION: + if (g_Inv_ExtraData[0] == 1 && g_OptionMusicVolume != 0) { + Music_SetVolume(25 * g_OptionMusicVolume + 5); + } + return 1; + + case O_PHOTO_OPTION: + if (g_GameFlow.gym_enabled) { + g_Inv_ExtraData[1] = 0; + return 1; + } + break; + + case O_PISTOL_OPTION: + case O_SHOTGUN_OPTION: + case O_MAGNUM_OPTION: + case O_UZI_OPTION: + case O_HARPOON_OPTION: + case O_M16_OPTION: + case O_GRENADE_OPTION: + case O_SMALL_MEDIPACK_OPTION: + case O_LARGE_MEDIPACK_OPTION: + case O_FLARES_OPTION: + Lara_UseItem(g_Inv_Chosen); + break; + + default: + break; + } + + if (inventory_mode != INV_TITLE_MODE && g_OptionMusicVolume != 0) { + Music_SetVolume(25 * g_OptionMusicVolume + 5); + } + return 0; +} + +void __cdecl Inv_SelectMeshes(INVENTORY_ITEM *const inv_item) +{ + switch (inv_item->object_id) { + case O_PASSPORT_OPTION: + if (inv_item->current_frame < 4) { + inv_item->meshes_drawn = PM_COMMON | PM_IN_FRONT; + } else if (inv_item->current_frame <= 16) { + inv_item->meshes_drawn = PM_COMMON | PM_IN_FRONT | PM_PAGE_1; + } else if (inv_item->current_frame < 19) { + inv_item->meshes_drawn = + PM_COMMON | PM_IN_FRONT | PM_PAGE_1 | PM_PAGE_2; + } else if (inv_item->current_frame == 19) { + inv_item->meshes_drawn = PM_COMMON | PM_PAGE_1 | PM_PAGE_2; + } else if (inv_item->current_frame < 24) { + inv_item->meshes_drawn = + PM_COMMON | PM_IN_BACK | PM_PAGE_1 | PM_PAGE_2; + } else if (inv_item->current_frame < 29) { + inv_item->meshes_drawn = PM_COMMON | PM_IN_BACK | PM_PAGE_2; + } else if (inv_item->current_frame == 29) { + inv_item->meshes_drawn = PM_COMMON; + } + break; + + case O_COMPASS_OPTION: + if (inv_item->current_frame == 0 || inv_item->current_frame >= 18) { + inv_item->meshes_drawn = inv_item->meshes_sel; + } else { + inv_item->meshes_drawn = -1; + } + break; + + default: + inv_item->meshes_drawn = -1; + break; + } +} + +int32_t __cdecl Inv_AnimateInventoryItem(INVENTORY_ITEM *const inv_item) +{ + if (inv_item->current_frame == inv_item->goal_frame) { + Inv_SelectMeshes(inv_item); + return false; + } + + if (inv_item->anim_count > 0) { + inv_item->anim_count--; + } else { + inv_item->anim_count = inv_item->anim_speed; + inv_item->current_frame += inv_item->anim_direction; + if (inv_item->current_frame >= inv_item->frames_total) { + inv_item->current_frame = 0; + } else if (inv_item->current_frame < 0) { + inv_item->current_frame = inv_item->frames_total - 1; + } + } + + Inv_SelectMeshes(inv_item); + return true; +} + +void __cdecl Inv_DrawInventoryItem(INVENTORY_ITEM *const inv_item) +{ + int32_t minutes; + int32_t hours; + int32_t seconds; + if (inv_item->object_id == O_COMPASS_OPTION) { + const int32_t total_seconds = + g_SaveGame.statistics.timer / FRAMES_PER_SECOND; + hours = (total_seconds % 43200) * PHD_DEGREE * -360 / 43200; + minutes = (total_seconds % 3600) * PHD_DEGREE * -360 / 3600; + seconds = (total_seconds % 60) * PHD_DEGREE * -360 / 60; + } else { + seconds = 0; + minutes = 0; + hours = 0; + } + + Matrix_TranslateRel(0, inv_item->y_trans, inv_item->z_trans); + Matrix_RotYXZ(inv_item->y_rot, inv_item->x_rot, 0); + const OBJECT *const obj = &g_Objects[inv_item->object_id]; + if ((obj->flags & 1) == 0) { + return; + } + + if (obj->mesh_count < 0) { + Output_DrawSprite(0, 0, 0, 0, obj->mesh_idx, 0, 0); + return; + } + + if (inv_item->sprite_list != NULL) { + const int32_t zv = g_MatrixPtr->_23; + const int32_t zp = zv / g_PhdPersp; + const int32_t sx = g_PhdWinCenterX + g_MatrixPtr->_03 / zp; + const int32_t sy = g_PhdWinCenterY + g_MatrixPtr->_13 / zp; + + INVENTORY_SPRITE **sprite_list = inv_item->sprite_list; + INVENTORY_SPRITE *sprite; + while ((sprite = *sprite_list++)) { + if (zv < g_PhdNearZ || zv > g_PhdFarZ) { + break; + } + + while (sprite->shape) { + switch (sprite->shape) { + case SHAPE_SPRITE: + Output_DrawScreenSprite( + sx + sprite->pos.x, sy + sprite->pos.y, sprite->pos.z, + sprite->param1, sprite->param2, + g_Objects[O_ALPHABET].mesh_idx + sprite->sprite_num, + 4096, 0); + break; + + case SHAPE_LINE: + S_DrawScreenLine( + sx + sprite->pos.x, sy + sprite->pos.y, sprite->pos.z, + sprite->param1, sprite->param2, sprite->sprite_num, + sprite->grdptr, 0); + break; + + case SHAPE_BOX: + S_DrawScreenBox( + sx + sprite->pos.x, sy + sprite->pos.y, sprite->pos.z, + sprite->param1, sprite->param2, sprite->sprite_num, + sprite->grdptr, 0); + break; + + case SHAPE_FBOX: + S_DrawScreenFBox( + sx + sprite->pos.x, sy + sprite->pos.y, sprite->pos.z, + sprite->param1, sprite->param2, sprite->sprite_num, + sprite->grdptr, 0); + break; + + default: + break; + } + sprite++; + } + } + } + + FRAME_INFO *frame_ptr = (FRAME_INFO *)&obj->frame_base + [inv_item->current_frame + * (g_Anims[obj->anim_idx].interpolation >> 8)]; + + Matrix_Push(); + const int32_t clip = S_GetObjectBounds(&frame_ptr->bounds); + if (!clip) { + Matrix_Pop(); + return; + } + + const int32_t *bone = &g_AnimBones[obj->bone_idx]; + Matrix_TranslateRel( + frame_ptr->offset.x, frame_ptr->offset.y, frame_ptr->offset.z); + const int16_t *rot = frame_ptr->mesh_rots; + Matrix_RotYXZsuperpack(&rot, 0); + + for (int32_t mesh_idx = 0; mesh_idx < obj->mesh_count; mesh_idx++) { + if (mesh_idx > 0) { + const int32_t bone_flags = bone[0]; + if (bone_flags & BF_MATRIX_POP) { + Matrix_Pop(); + } + if (bone_flags & BF_MATRIX_PUSH) { + Matrix_Push(); + } + + Matrix_TranslateRel(bone[1], bone[2], bone[3]); + Matrix_RotYXZsuperpack(&rot, 0); + bone += 4; + + if (inv_item->object_id == O_COMPASS_OPTION) { + if (mesh_idx == 6) { + Matrix_RotZ(seconds); + const int32_t tmp = inv_item->reserved[0]; + inv_item->reserved[0] = seconds; + inv_item->reserved[1] = tmp; + } + if (mesh_idx == 5) { + Matrix_RotZ(minutes); + } + if (mesh_idx == 4) { + Matrix_RotZ(hours); + } + } + } + + if (inv_item->meshes_drawn & (1 << mesh_idx)) { + Output_InsertPolygons(g_Meshes[obj->mesh_idx + mesh_idx], clip); + } + } + + Matrix_Pop(); +} + +GAME_OBJECT_ID Inv_GetItemOption(const GAME_OBJECT_ID object_id) +{ + if (Object_IsObjectType(object_id, g_InvObjects)) { + return object_id; + } + + return Object_GetCognate(object_id, g_ItemToInvObjectMap); +} + +void __cdecl Inv_DoInventoryPicture(void) +{ + S_CopyBufferToScreen(); +} + +void __cdecl Inv_DoInventoryBackground(void) +{ + S_CopyBufferToScreen(); + if (!g_Objects[O_INV_BACKGROUND].loaded) { + return; + } + + int16_t angles[2]; + Math_GetVectorAngles(0, 4096, 0, angles); + PHD_3DPOS view; + view.pos.x = 0; + view.pos.y = -512; + view.pos.z = 0; + view.rot.x = angles[1]; + view.rot.y = angles[0]; + view.rot.z = 0; + Matrix_GenerateW2V(&view); + + g_LsDivider = 0x6000; + Math_GetVectorAngles(-1536, 256, 1024, angles); + Output_RotateLight(angles[1], angles[0]); + + Matrix_Push(); + Matrix_TranslateAbs(0, 12288, 0); + Matrix_RotYXZ(0, PHD_90, PHD_180); + + const int16_t *rot = + g_Anims[g_Objects[O_INV_BACKGROUND].anim_idx].frame_ptr + FBBOX_ROT; + Matrix_RotYXZsuperpack(&rot, 0); + Matrix_RotYXZ(PHD_180, 0, 0); + + Output_InsertInventoryBackground( + g_Meshes[g_Objects[O_INV_BACKGROUND].mesh_idx]); + + Matrix_Pop(); +} + +void __cdecl Inv_RingIsOpen(RING_INFO *const ring) +{ + if (g_Inv_Mode == INV_TITLE_MODE) { + return; + } + + if (g_Inv_RingText == NULL) { + switch (ring->type) { + case RT_MAIN: + g_Inv_RingText = Text_Create( + 0, 26, 0, g_GF_GameStrings[GF_S_GAME_HEADING_INVENTORY]); + break; + + case RT_OPTION: + if (g_Inv_Mode == INV_DEATH_MODE) { + g_Inv_RingText = Text_Create( + 0, 26, 0, g_GF_GameStrings[GF_S_GAME_HEADING_GAME_OVER]); + } else { + g_Inv_RingText = Text_Create( + 0, 26, 0, g_GF_GameStrings[GF_S_GAME_HEADING_OPTION]); + } + Text_CentreH(g_Inv_RingText, true); + break; + + case RT_KEYS: + g_Inv_RingText = Text_Create( + 0, 26, 0, g_GF_GameStrings[GF_S_GAME_HEADING_ITEMS]); + break; + } + + Text_CentreH(g_Inv_RingText, true); + } + + if (g_Inv_Mode == INV_KEYS_MODE || g_Inv_Mode == INV_DEATH_MODE) { + return; + } + + if (g_Inv_UpArrow1 == NULL) { + if (ring->type == RT_OPTION + || (ring->type == RT_MAIN && g_Inv_KeyObjectsCount > 0)) { + g_Inv_UpArrow1 = Text_Create(20, 28, 0, "["); + g_Inv_UpArrow2 = Text_Create(-20, 28, 0, "["); + Text_AlignRight(g_Inv_UpArrow2, true); + } + } + + if (g_Inv_DownArrow1 == NULL + && (( + (ring->type == RT_MAIN && !g_GameFlow.lockout_option_ring) + || ring->type == RT_KEYS))) { + g_Inv_DownArrow1 = Text_Create(20, -15, 0, "]"); + Text_AlignBottom(g_Inv_DownArrow1, true); + g_Inv_DownArrow2 = Text_Create(-20, -15, 0, "]"); + Text_AlignBottom(g_Inv_DownArrow2, true); + Text_AlignRight(g_Inv_DownArrow2, true); + } +} + +void __cdecl Inv_RingIsNotOpen(RING_INFO *const ring) +{ + Text_Remove(g_Inv_TagText); + g_Inv_TagText = NULL; + Text_Remove(g_Inv_RingText); + g_Inv_RingText = NULL; + Text_Remove(g_Inv_UpArrow1); + g_Inv_UpArrow1 = NULL; + Text_Remove(g_Inv_UpArrow2); + g_Inv_UpArrow2 = NULL; + Text_Remove(g_Inv_DownArrow1); + g_Inv_DownArrow1 = NULL; + Text_Remove(g_Inv_DownArrow2); + g_Inv_DownArrow2 = NULL; +} + +void __cdecl Inv_RingNotActive(const INVENTORY_ITEM *const inv_item) +{ + if (g_Inv_ItemText[0] == NULL) { + if (inv_item->object_id != O_PASSPORT_OPTION) { + g_Inv_ItemText[0] = + Text_Create(0, -16, 0, Object_GetName(inv_item->object_id)); + } + + if (g_Inv_ItemText[0]) { + Text_AlignBottom(g_Inv_ItemText[0], true); + Text_CentreH(g_Inv_ItemText[0], true); + } + } + + const int32_t qty = Inv_RequestItem(inv_item->object_id); + switch (inv_item->object_id) { + case O_SHOTGUN_OPTION: + M_ShowAmmoQuantity("%5d", g_Lara.shotgun_ammo.ammo / SHOTGUN_AMMO_CLIP); + break; + case O_MAGNUM_OPTION: + M_ShowAmmoQuantity("%5d", g_Lara.magnum_ammo.ammo); + break; + case O_UZI_OPTION: + M_ShowAmmoQuantity("%5d", g_Lara.uzi_ammo.ammo); + break; + case O_HARPOON_OPTION: + M_ShowAmmoQuantity("%5d", g_Lara.harpoon_ammo.ammo); + break; + case O_M16_OPTION: + M_ShowAmmoQuantity("%5d", g_Lara.m16_ammo.ammo); + break; + case O_GRENADE_OPTION: + M_ShowAmmoQuantity("%5d", g_Lara.grenade_ammo.ammo); + break; + case O_SHOTGUN_AMMO_OPTION: + M_ShowAmmoQuantity("%d", SHOTGUN_SHELL_COUNT * qty); + break; + + case O_MAGNUM_AMMO_OPTION: + case O_UZI_AMMO_OPTION: + case O_HARPOON_AMMO_OPTION: + case O_M16_AMMO_OPTION: + M_ShowAmmoQuantity("%d", 2 * qty); + break; + + case O_GRENADE_AMMO_OPTION: + case O_FLARES_OPTION: + M_ShowAmmoQuantity("%d", qty); + break; + + case O_SMALL_MEDIPACK_OPTION: + case O_LARGE_MEDIPACK_OPTION: + g_HealthBarTimer = 40; + Overlay_DrawHealthBar(Overlay_FlashCounter()); + M_ShowItemQuantity("%d", qty); + break; + + case O_PUZZLE_OPTION_1: + case O_PUZZLE_OPTION_2: + case O_PUZZLE_OPTION_3: + case O_PUZZLE_OPTION_4: + case O_KEY_OPTION_1: + case O_KEY_OPTION_2: + case O_KEY_OPTION_3: + case O_KEY_OPTION_4: + case O_PICKUP_OPTION_1: + case O_PICKUP_OPTION_2: + if (qty > 1) { + M_ShowItemQuantity("%d", qty); + } + break; + + default: + break; + } +} + +void __cdecl Inv_RingActive(void) +{ + M_RemoveItemsText(); +} diff --git a/src/tr2/game/inventory/common.h b/src/tr2/game/inventory/common.h new file mode 100644 index 000000000..d86c2d93b --- /dev/null +++ b/src/tr2/game/inventory/common.h @@ -0,0 +1,18 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Inv_InitColors(void); +void __cdecl Inv_Construct(void); +int32_t __cdecl Inv_Display(int32_t inventory_mode); +void __cdecl Inv_SelectMeshes(INVENTORY_ITEM *inv_item); +int32_t __cdecl Inv_AnimateInventoryItem(INVENTORY_ITEM *inv_item); +void __cdecl Inv_DrawInventoryItem(INVENTORY_ITEM *inv_item); +GAME_OBJECT_ID Inv_GetItemOption(GAME_OBJECT_ID object_id); +void __cdecl Inv_DoInventoryPicture(void); +void __cdecl Inv_DoInventoryBackground(void); +void __cdecl Inv_RingIsOpen(RING_INFO *ring); +void __cdecl Inv_RingIsNotOpen(RING_INFO *ring); +void __cdecl Inv_RingNotActive(const INVENTORY_ITEM *inv_item); +void __cdecl Inv_RingActive(void); +void __cdecl Inv_RemoveInventoryText(void); diff --git a/src/tr2/game/inventory/ring.c b/src/tr2/game/inventory/ring.c new file mode 100644 index 000000000..a5136d900 --- /dev/null +++ b/src/tr2/game/inventory/ring.c @@ -0,0 +1,292 @@ +#include "game/inventory/ring.h" + +#include "game/math_misc.h" +#include "game/output.h" +#include "global/funcs.h" +#include "global/vars.h" + +#define RING_OPEN_FRAMES 32 +#define RING_OPEN_ROTATION PHD_180 +#define RING_ROTATE_DURATION 24 +#define RING_RADIUS 688 +#define RING_CAMERA_START_HEIGHT (-1536) +#define RING_CAMERA_HEIGHT (-256) +#define RING_CAMERA_Y_OFFSET (-96) + +void __cdecl Inv_Ring_Init( + RING_INFO *const ring, const RING_TYPE type, INVENTORY_ITEM **const list, + const int16_t qty, const int16_t current, IMOTION_INFO *const imo) +{ + ring->type = type; + ring->list = list; + ring->number_of_objects = qty; + ring->current_object = current; + ring->radius = 0; + ring->angle_adder = 0x10000 / qty; + + if (g_Inv_Mode == INV_TITLE_MODE) { + ring->camera_pitch = 1024; + } else { + ring->camera_pitch = 0; + } + + ring->rotating = 0; + ring->rot_count = 0; + ring->target_object = 0; + ring->rot_adder = 0; + ring->rot_adder_l = 0; + ring->rot_adder_r = 0; + + ring->imo = imo; + + ring->camera.pos.x = 0; + ring->camera.pos.y = RING_CAMERA_START_HEIGHT; + ring->camera.pos.z = 896; + ring->camera.rot.x = 0; + ring->camera.rot.y = 0; + ring->camera.rot.z = 0; + + Inv_Ring_MotionInit(ring, RING_OPEN_FRAMES, RNG_OPENING, RNG_OPEN); + Inv_Ring_MotionRadius(ring, RING_RADIUS); + Inv_Ring_MotionCameraPos(ring, RING_CAMERA_HEIGHT); + Inv_Ring_MotionRotation( + ring, RING_OPEN_ROTATION, + -PHD_90 - ring->current_object * ring->angle_adder); + + ring->ring_pos.pos.x = 0; + ring->ring_pos.pos.y = 0; + ring->ring_pos.pos.z = 0; + ring->ring_pos.rot.x = 0; + ring->ring_pos.rot.y = imo->rotate_target + RING_OPEN_ROTATION; + ring->ring_pos.rot.z = 0; + + ring->light.x = -1536; + ring->light.y = 256; + ring->light.z = 1024; +} + +void __cdecl Inv_Ring_GetView( + const RING_INFO *const ring, PHD_3DPOS *const view) +{ + int16_t angles[2]; + + Math_GetVectorAngles( + -ring->camera.pos.x, RING_CAMERA_Y_OFFSET - ring->camera.pos.y, + ring->radius - ring->camera.pos.z, angles); + view->pos.x = ring->camera.pos.x; + view->pos.y = ring->camera.pos.y; + view->pos.z = ring->camera.pos.z; + view->rot.x = angles[1] + ring->camera_pitch; + view->rot.y = angles[0]; + view->rot.z = 0; +} + +void __cdecl Inv_Ring_Light(const RING_INFO *const ring) +{ + g_LsDivider = 0x6000; + int16_t angles[2]; + Math_GetVectorAngles(ring->light.x, ring->light.y, ring->light.z, angles); + Output_RotateLight(angles[1], angles[0]); +} + +void __cdecl Inv_Ring_CalcAdders( + RING_INFO *const ring, const int16_t rotation_duration) +{ + ring->angle_adder = PHD_360 / ring->number_of_objects; + ring->rot_adder_l = ring->angle_adder / rotation_duration; + ring->rot_adder_r = -ring->angle_adder / rotation_duration; +} + +void __cdecl Inv_Ring_DoMotions(RING_INFO *const ring) +{ + IMOTION_INFO *const imo = ring->imo; + + if (imo->count != 0) { + ring->radius += imo->radius_rate; + ring->camera.pos.y += imo->camera_y_rate; + ring->ring_pos.rot.y += imo->rotate_rate; + ring->camera_pitch += imo->camera_pitch_rate; + + INVENTORY_ITEM *const inv_item = ring->list[ring->current_object]; + inv_item->x_rot_pt += imo->item_pt_x_rot_rate; + inv_item->x_rot += imo->item_x_rot_rate; + inv_item->y_trans += imo->item_y_trans_rate; + inv_item->z_trans += imo->item_z_trans_rate; + + imo->count--; + if (imo->count == 0) { + imo->status = imo->status_target; + + if (imo->radius_rate != 0) { + imo->radius_rate = 0; + ring->radius = imo->radius_target; + } + if (imo->camera_y_rate != 0) { + imo->camera_y_rate = 0; + ring->camera.pos.y = imo->camera_y_target; + } + if (imo->rotate_rate != 0) { + imo->rotate_rate = 0; + ring->ring_pos.rot.y = imo->rotate_target; + } + if (imo->item_pt_x_rot_rate != 0) { + imo->item_pt_x_rot_rate = 0; + inv_item->x_rot_pt = imo->item_pt_x_rot_target; + } + if (imo->item_x_rot_rate != 0) { + imo->item_x_rot_rate = 0; + inv_item->x_rot = imo->item_x_rot_target; + } + if (imo->item_y_trans_rate != 0) { + imo->item_y_trans_rate = 0; + inv_item->y_trans = imo->item_y_trans_target; + } + if (imo->item_z_trans_rate != 0) { + imo->item_z_trans_rate = 0; + inv_item->z_trans = imo->item_z_trans_target; + } + if (imo->camera_pitch_rate != 0) { + imo->camera_pitch_rate = 0; + ring->camera_pitch = imo->camera_pitch_target; + } + } + } + + if (ring->rotating) { + ring->ring_pos.rot.y += ring->rot_adder; + ring->rot_count--; + + if (ring->rot_count == 0) { + ring->current_object = ring->target_object; + ring->ring_pos.rot.y = + -PHD_90 - ring->target_object * ring->angle_adder; + ring->rotating = 0; + } + } +} + +void __cdecl Inv_Ring_RotateLeft(RING_INFO *const ring) +{ + ring->rotating = 1; + if (ring->current_object == 0) { + ring->target_object = ring->number_of_objects - 1; + } else { + ring->target_object = ring->current_object - 1; + } + ring->rot_count = RING_ROTATE_DURATION; + ring->rot_adder = ring->rot_adder_l; +} + +void __cdecl Inv_Ring_RotateRight(RING_INFO *const ring) +{ + ring->rotating = 1; + if (ring->current_object + 1 >= ring->number_of_objects) { + ring->target_object = 0; + } else { + ring->target_object = ring->current_object + 1; + } + ring->rot_count = RING_ROTATE_DURATION; + ring->rot_adder = ring->rot_adder_r; +} + +void __cdecl Inv_Ring_MotionInit( + RING_INFO *const ring, const int16_t frames, const RING_STATUS status, + const RING_STATUS status_target) +{ + IMOTION_INFO *const imo = ring->imo; + imo->count = frames; + imo->status = status; + imo->status_target = status_target; + + imo->radius_target = 0; + imo->radius_rate = 0; + imo->camera_y_target = 0; + imo->camera_y_rate = 0; + imo->camera_pitch_target = 0; + imo->camera_pitch_rate = 0; + imo->rotate_target = 0; + imo->rotate_rate = 0; + imo->item_pt_x_rot_target = 0; + imo->item_pt_x_rot_rate = 0; + imo->item_x_rot_target = 0; + imo->item_x_rot_rate = 0; + imo->item_y_trans_target = 0; + imo->item_y_trans_rate = 0; + imo->item_z_trans_target = 0; + imo->item_z_trans_rate = 0; + + imo->misc = 0; +} + +void __cdecl Inv_Ring_MotionSetup( + RING_INFO *const ring, const RING_STATUS status, + const RING_STATUS status_target, const int16_t frames) +{ + IMOTION_INFO *const imo = ring->imo; + imo->count = frames; + imo->status = status; + imo->status_target = status_target; + imo->radius_rate = 0; + imo->camera_y_rate = 0; +} + +void __cdecl Inv_Ring_MotionRadius(RING_INFO *const ring, const int16_t target) +{ + IMOTION_INFO *const imo = ring->imo; + imo->radius_target = target; + imo->radius_rate = (target - ring->radius) / imo->count; +} + +void __cdecl Inv_Ring_MotionRotation( + RING_INFO *const ring, const int16_t rotation, const int16_t target) +{ + IMOTION_INFO *const imo = ring->imo; + imo->rotate_target = target; + imo->rotate_rate = rotation / imo->count; +} + +void __cdecl Inv_Ring_MotionCameraPos( + RING_INFO *const ring, const int16_t target) +{ + IMOTION_INFO *const imo = ring->imo; + imo->camera_y_target = target; + imo->camera_y_rate = (target - ring->camera.pos.y) / imo->count; +} + +void __cdecl Inv_Ring_MotionCameraPitch( + RING_INFO *const ring, const int16_t target) +{ + IMOTION_INFO *const imo = ring->imo; + imo->camera_pitch_target = target; + imo->camera_pitch_rate = target / imo->count; +} + +void __cdecl Inv_Ring_MotionItemSelect( + RING_INFO *const ring, const INVENTORY_ITEM *const inv_item) +{ + IMOTION_INFO *const imo = ring->imo; + imo->item_pt_x_rot_target = inv_item->x_rot_pt_sel; + imo->item_pt_x_rot_rate = inv_item->x_rot_pt_sel / imo->count; + imo->item_x_rot_target = inv_item->x_rot_sel; + imo->item_x_rot_rate = + (inv_item->x_rot_sel - inv_item->x_rot_nosel) / imo->count; + imo->item_y_trans_target = inv_item->y_trans_sel; + imo->item_y_trans_rate = inv_item->y_trans_sel / imo->count; + imo->item_z_trans_target = inv_item->z_trans_sel; + imo->item_z_trans_rate = inv_item->z_trans_sel / imo->count; +} + +void __cdecl Inv_Ring_MotionItemDeselect( + RING_INFO *const ring, const INVENTORY_ITEM *const inv_item) +{ + IMOTION_INFO *const imo = ring->imo; + imo->item_pt_x_rot_target = 0; + imo->item_pt_x_rot_rate = -(inv_item->x_rot_pt_sel / imo->count); + imo->item_x_rot_target = inv_item->x_rot_nosel; + imo->item_x_rot_rate = + (inv_item->x_rot_nosel - inv_item->x_rot_sel) / imo->count; + imo->item_y_trans_target = 0; + imo->item_y_trans_rate = -(inv_item->y_trans_sel / imo->count); + imo->item_z_trans_target = 0; + imo->item_z_trans_rate = -(inv_item->z_trans_sel / imo->count); +} diff --git a/src/tr2/game/inventory/ring.h b/src/tr2/game/inventory/ring.h new file mode 100644 index 000000000..c2ba630ee --- /dev/null +++ b/src/tr2/game/inventory/ring.h @@ -0,0 +1,28 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Inv_Ring_Init( + RING_INFO *ring, RING_TYPE type, INVENTORY_ITEM **list, int16_t qty, + int16_t current, IMOTION_INFO *imo); +void __cdecl Inv_Ring_GetView(const RING_INFO *ring, PHD_3DPOS *view); +void __cdecl Inv_Ring_Light(const RING_INFO *ring); +void __cdecl Inv_Ring_CalcAdders(RING_INFO *ring, int16_t rotation_duration); +void __cdecl Inv_Ring_DoMotions(RING_INFO *ring); +void __cdecl Inv_Ring_RotateLeft(RING_INFO *ring); +void __cdecl Inv_Ring_RotateRight(RING_INFO *ring); +void __cdecl Inv_Ring_MotionInit( + RING_INFO *ring, int16_t frames, RING_STATUS status, + RING_STATUS status_target); +void __cdecl Inv_Ring_MotionSetup( + RING_INFO *ring, RING_STATUS status, RING_STATUS status_target, + int16_t frames); +void __cdecl Inv_Ring_MotionRadius(RING_INFO *ring, int16_t target); +void __cdecl Inv_Ring_MotionRotation( + RING_INFO *ring, int16_t rotation, int16_t target); +void __cdecl Inv_Ring_MotionCameraPos(RING_INFO *ring, int16_t target); +void __cdecl Inv_Ring_MotionCameraPitch(RING_INFO *ring, int16_t target); +void __cdecl Inv_Ring_MotionItemSelect( + RING_INFO *ring, const INVENTORY_ITEM *inv_item); +void __cdecl Inv_Ring_MotionItemDeselect( + RING_INFO *ring, const INVENTORY_ITEM *inv_item); diff --git a/src/tr2/game/inventory/vars.c b/src/tr2/game/inventory/vars.c new file mode 100644 index 000000000..76381f530 --- /dev/null +++ b/src/tr2/game/inventory/vars.c @@ -0,0 +1,12 @@ +#include "game/inventory/vars.h" + +#include "global/vars.h" + +int32_t g_Inv_OptionObjectsCount = 4; + +INVENTORY_ITEM *g_Inv_OptionList[] = { + &g_Inv_Item_Passport, + &g_Inv_Item_Controls, + &g_Inv_Item_Sound, + &g_Inv_Item_Photo, +}; diff --git a/src/tr2/game/inventory/vars.h b/src/tr2/game/inventory/vars.h new file mode 100644 index 000000000..9dc7d6c7a --- /dev/null +++ b/src/tr2/game/inventory/vars.h @@ -0,0 +1,6 @@ +#pragma once + +#include "global/types.h" + +extern int32_t g_Inv_OptionObjectsCount; +extern INVENTORY_ITEM *g_Inv_OptionList[]; diff --git a/src/tr2/game/items.c b/src/tr2/game/items.c new file mode 100644 index 000000000..fab4ed9d6 --- /dev/null +++ b/src/tr2/game/items.c @@ -0,0 +1,735 @@ +#include "game/items.h" + +#include "game/math.h" +#include "game/matrix.h" +#include "game/room.h" +#include "game/sound.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#include + +static int16_t m_MaxUsedItemCount = 0; +static BOUNDS_16 m_InterpolatedBounds = { 0 }; + +static OBJECT_BOUNDS M_ConvertBounds(const int16_t *bounds_in); + +static OBJECT_BOUNDS M_ConvertBounds(const int16_t *const bounds_in) +{ + // TODO: remove this conversion utility once we gain control over its + // incoming arguments + return (OBJECT_BOUNDS) { + .shift = { + .min = { + .x = bounds_in[0], + .y = bounds_in[2], + .z = bounds_in[4], + }, + .max = { + .x = bounds_in[1], + .y = bounds_in[3], + .z = bounds_in[5], + }, + }, + .rot = { + .min = { + .x = bounds_in[6], + .y = bounds_in[8], + .z = bounds_in[10], + }, + .max = { + .x = bounds_in[7], + .y = bounds_in[9], + .z = bounds_in[11], + }, + }, + }; +} + +void __cdecl Item_InitialiseArray(const int32_t num_items) +{ + assert(num_items > 0); + g_NextItemFree = g_LevelItemCount; + g_PrevItemActive = NO_ITEM; + g_NextItemActive = NO_ITEM; + m_MaxUsedItemCount = g_LevelItemCount; + for (int32_t i = g_NextItemFree; i < num_items - 1; i++) { + ITEM *const item = &g_Items[i]; + item->active = 0; + item->next_item = i + 1; + } + g_Items[num_items - 1].next_item = NO_ITEM; +} + +int32_t Item_GetTotalCount(void) +{ + return m_MaxUsedItemCount; +} + +int16_t __cdecl Item_Create(void) +{ + const int16_t item_num = g_NextItemFree; + if (item_num != NO_ITEM) { + g_Items[item_num].flags = 0; + g_NextItemFree = g_Items[item_num].next_item; + } + m_MaxUsedItemCount = MAX(m_MaxUsedItemCount, item_num + 1); + return item_num; +} + +void __cdecl Item_Kill(const int16_t item_num) +{ + Item_RemoveActive(item_num); + Item_RemoveDrawn(item_num); + + ITEM *const item = &g_Items[item_num]; + if (item == g_Lara.target) { + g_Lara.target = NULL; + } + + if (item_num < g_LevelItemCount) { + item->flags |= IF_KILLED; + } else { + item->next_item = g_NextItemFree; + g_NextItemFree = item_num; + } + + while (m_MaxUsedItemCount > 0 + && g_Items[m_MaxUsedItemCount - 1].flags & IF_KILLED) { + m_MaxUsedItemCount--; + } +} + +void __cdecl Item_Initialise(const int16_t item_num) +{ + ITEM *const item = &g_Items[item_num]; + item->anim_num = g_Objects[item->object_id].anim_idx; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->goal_anim_state = g_Anims[item->anim_num].current_anim_state; + item->current_anim_state = item->goal_anim_state; + item->required_anim_state = 0; + item->rot.x = 0; + item->rot.z = 0; + item->speed = 0; + item->fall_speed = 0; + item->hit_points = g_Objects[item->object_id].hit_points; + item->timer = 0; + item->mesh_bits = 0xFFFFFFFF; + item->touch_bits = 0; + item->data = NULL; + + item->active = 0; + item->status = IS_INACTIVE; + item->gravity = 0; + item->hit_status = 0; + item->collidable = 1; + item->looked_at = 0; + item->killed = 0; + + if ((item->flags & IF_INVISIBLE) != 0) { + item->status = IS_INVISIBLE; + item->flags &= ~IF_INVISIBLE; + } else if (g_Objects[item->object_id].intelligent) { + item->status = IS_INVISIBLE; + } + + if (item->flags & IF_KILLED) { + item->killed = 1; + item->flags &= ~IF_KILLED; + } + + if ((item->flags & IF_CODE_BITS) == IF_CODE_BITS) { + item->flags &= ~IF_CODE_BITS; + item->flags |= IF_REVERSE; + Item_AddActive(item_num); + item->status = IS_ACTIVE; + } + + ROOM *const room = &g_Rooms[item->room_num]; + item->next_item = room->item_num; + room->item_num = item_num; + + const int32_t dx = (item->pos.x - room->pos.x) >> WALL_SHIFT; + const int32_t dz = (item->pos.z - room->pos.z) >> WALL_SHIFT; + const SECTOR *const sector = &room->sectors[dx * room->size.z + dz]; + item->floor = sector->floor << 8; + + if (g_SaveGame.bonus_flag && !g_IsDemoLevelType) { + item->hit_points *= 2; + } + + if (g_Objects[item->object_id].initialise != NULL) { + g_Objects[item->object_id].initialise(item_num); + } +} + +void __cdecl Item_RemoveActive(const int16_t item_num) +{ + ITEM *const item = &g_Items[item_num]; + if (!item->active) { + return; + } + + item->active = 0; + + int16_t link_num = g_NextItemActive; + if (link_num == item_num) { + g_NextItemActive = item->next_active; + return; + } + + while (link_num != NO_ITEM) { + if (g_Items[link_num].next_active == item_num) { + g_Items[link_num].next_active = item->next_active; + return; + } + link_num = g_Items[link_num].next_active; + } +} + +void __cdecl Item_RemoveDrawn(const int16_t item_num) +{ + const ITEM *const item = &g_Items[item_num]; + if (item->room_num == NO_ROOM) { + return; + } + + int16_t link_num = g_Rooms[item->room_num].item_num; + if (link_num == item_num) { + g_Rooms[item->room_num].item_num = item->next_item; + return; + } + + while (link_num != NO_ITEM) { + if (g_Items[link_num].next_item == item_num) { + g_Items[link_num].next_item = item->next_item; + return; + } + link_num = g_Items[link_num].next_item; + } +} + +void __cdecl Item_AddActive(const int16_t item_num) +{ + ITEM *const item = &g_Items[item_num]; + if (g_Objects[item->object_id].control == NULL) { + item->status = IS_INACTIVE; + return; + } + + if (item->active) { + return; + } + + item->active = 1; + item->next_active = g_NextItemActive; + g_NextItemActive = item_num; +} + +void __cdecl Item_NewRoom(const int16_t item_num, const int16_t room_num) +{ + ITEM *const item = &g_Items[item_num]; + ROOM *room = NULL; + + if (item->room_num != NO_ROOM) { + room = &g_Rooms[item->room_num]; + + int16_t link_num = room->item_num; + if (link_num == item_num) { + room->item_num = item->next_item; + } else { + while (link_num != NO_ITEM) { + if (g_Items[link_num].next_item == item_num) { + g_Items[link_num].next_item = item->next_item; + break; + } + link_num = g_Items[link_num].next_item; + } + } + } + + item->room_num = room_num; + room = &g_Rooms[room_num]; + item->next_item = room->item_num; + room->item_num = item_num; +} + +int32_t __cdecl Item_GlobalReplace( + const GAME_OBJECT_ID src_object_id, const GAME_OBJECT_ID dst_object_id) +{ + int32_t changed = 0; + + for (int32_t i = 0; i < g_RoomCount; i++) { + int16_t j = g_Rooms[i].item_num; + while (j != NO_ITEM) { + ITEM *const item = &g_Items[j]; + if (item->object_id == src_object_id) { + item->object_id = dst_object_id; + changed++; + } + j = item->next_item; + } + } + + return changed; +} + +void __cdecl Item_ClearKilled(void) +{ + // Remove corpses and other killed items. Part of OG performance + // improvements, generously used in Opera House and Barkhang Monastery + int16_t link_num = g_PrevItemActive; + while (link_num != NO_ITEM) { + ITEM *const item = &g_Items[link_num]; + Item_Kill(link_num); + link_num = item->next_active; + item->next_active = NO_ITEM; + } + g_PrevItemActive = NO_ITEM; +} + +bool Item_IsSmashable(const ITEM *item) +{ + return (item->object_id == O_WINDOW_1 || item->object_id == O_BELL); +} + +void __cdecl Item_ShiftCol(ITEM *const item, COLL_INFO *const coll) +{ + item->pos.x += coll->shift.x; + item->pos.y += coll->shift.y; + item->pos.z += coll->shift.z; + coll->shift.z = 0; + coll->shift.y = 0; + coll->shift.x = 0; +} + +void __cdecl Item_UpdateRoom(ITEM *const item, const int32_t height) +{ + int32_t x = item->pos.x; + int32_t y = height + item->pos.y; + int32_t z = item->pos.z; + + int16_t room_num = item->room_num; + const SECTOR *const sector = Room_GetSector(x, y, z, &room_num); + item->floor = Room_GetHeight(sector, x, y, z); + if (item->room_num != room_num) { + Item_NewRoom(g_Lara.item_num, room_num); + } +} + +int32_t __cdecl Item_TestBoundsCollide( + const ITEM *const src_item, const ITEM *const dst_item, + const int32_t radius) +{ + const BOUNDS_16 *const src_bounds = &Item_GetBestFrame(src_item)->bounds; + const BOUNDS_16 *const dst_bounds = &Item_GetBestFrame(dst_item)->bounds; + + if (src_item->pos.y + src_bounds->max_y + <= dst_item->pos.y + dst_bounds->min_y + || src_item->pos.y + src_bounds->min_y + >= dst_item->pos.y + dst_bounds->max_y) { + return false; + } + + const int32_t c = Math_Cos(src_item->rot.y); + const int32_t s = Math_Sin(src_item->rot.y); + const int32_t dx = dst_item->pos.x - src_item->pos.x; + const int32_t dz = dst_item->pos.z - src_item->pos.z; + const int32_t rx = (c * dx - s * dz) >> W2V_SHIFT; + const int32_t rz = (c * dz + s * dx) >> W2V_SHIFT; + + // clang-format off + return ( + rx >= src_bounds->min_x - radius && + rx <= src_bounds->max_x + radius && + rz >= src_bounds->min_z - radius && + rz <= src_bounds->max_z + radius); + // clang-format on +} + +int32_t __cdecl Item_TestPosition( + const int16_t *const bounds_in, const ITEM *const src_item, + const ITEM *const dst_item) +{ + const OBJECT_BOUNDS bounds = M_ConvertBounds(bounds_in); + + const XYZ_16 rot = { + .x = dst_item->rot.x - src_item->rot.x, + .y = dst_item->rot.y - src_item->rot.y, + .z = dst_item->rot.z - src_item->rot.z, + }; + const XYZ_32 dist = { + .x = dst_item->pos.x - src_item->pos.x, + .y = dst_item->pos.y - src_item->pos.y, + .z = dst_item->pos.z - src_item->pos.z, + }; + + // clang-format off + if (rot.x < bounds.rot.min.x || + rot.x > bounds.rot.max.x || + rot.y < bounds.rot.min.y || + rot.y > bounds.rot.max.y || + rot.z < bounds.rot.min.z || + rot.z > bounds.rot.max.z + ) { + return false; + } + // clang-format on + + Matrix_PushUnit(); + Matrix_RotYXZ(src_item->rot.y, src_item->rot.x, src_item->rot.z); + const MATRIX *const m = g_MatrixPtr; + const XYZ_32 shift = { + .x = (dist.x * m->_00 + dist.y * m->_10 + dist.z * m->_20) >> W2V_SHIFT, + .y = (dist.x * m->_01 + dist.y * m->_11 + dist.z * m->_21) >> W2V_SHIFT, + .z = (dist.x * m->_02 + dist.y * m->_12 + dist.z * m->_22) >> W2V_SHIFT, + }; + Matrix_Pop(); + + // clang-format off + return ( + shift.x >= bounds.shift.min.x && + shift.x <= bounds.shift.max.x && + shift.y >= bounds.shift.min.y && + shift.y <= bounds.shift.max.y && + shift.z >= bounds.shift.min.z && + shift.z <= bounds.shift.max.z + ); + // clang-format on +} + +void __cdecl Item_AlignPosition( + const XYZ_32 *const vec, const ITEM *const src_item, ITEM *const dst_item) +{ + dst_item->rot = src_item->rot; + Matrix_PushUnit(); + Matrix_RotYXZ(src_item->rot.y, src_item->rot.x, src_item->rot.z); + const MATRIX *const m = g_MatrixPtr; + const XYZ_32 shift = { + .x = (vec->x * m->_00 + vec->y * m->_01 + vec->z * m->_02) >> W2V_SHIFT, + .y = (vec->x * m->_10 + vec->y * m->_11 + vec->z * m->_12) >> W2V_SHIFT, + .z = (vec->x * m->_20 + vec->y * m->_21 + vec->z * m->_22) >> W2V_SHIFT, + }; + Matrix_Pop(); + + const XYZ_32 new_pos = { + .x = src_item->pos.x + shift.x, + .y = src_item->pos.y + shift.y, + .z = src_item->pos.z + shift.z, + }; + + int16_t room_num = dst_item->room_num; + const SECTOR *const sector = + Room_GetSector(new_pos.x, new_pos.y, new_pos.z, &room_num); + const int32_t height = + Room_GetHeight(sector, new_pos.x, new_pos.y, new_pos.z); + const int32_t ceiling = + Room_GetCeiling(sector, new_pos.x, new_pos.y, new_pos.z); + + if (ABS(height - dst_item->pos.y) > STEP_L + || ABS(ceiling - dst_item->pos.y) < LARA_HEIGHT) { + return; + } + + dst_item->pos.x = new_pos.x; + dst_item->pos.y = new_pos.y; + dst_item->pos.z = new_pos.z; +} + +void __cdecl Item_Animate(ITEM *const item) +{ + item->hit_status = 0; + item->touch_bits = 0; + + const ANIM *anim = &g_Anims[item->anim_num]; + + item->frame_num++; + + if (anim->num_changes > 0) { + if (Item_GetAnimChange(item, anim)) { + anim = &g_Anims[item->anim_num]; + item->current_anim_state = anim->current_anim_state; + + if (item->required_anim_state == anim->current_anim_state) { + item->required_anim_state = 0; + } + } + } + + if (item->frame_num > anim->frame_end) { + if (anim->num_commands > 0) { + const int16_t *cmd_ptr = &g_AnimCommands[anim->command_idx]; + + for (int32_t i = 0; i < anim->num_commands; i++) { + const int16_t cmd = *cmd_ptr++; + + switch (cmd) { + case AC_MOVE_ORIGIN: + Item_Translate(item, cmd_ptr[0], cmd_ptr[1], cmd_ptr[2]); + cmd_ptr += 3; + break; + + case AC_JUMP_VELOCITY: + item->fall_speed = cmd_ptr[0]; + item->speed = cmd_ptr[1]; + item->gravity = 1; + cmd_ptr += 2; + break; + + case AC_DEACTIVATE: + item->status = IS_DEACTIVATED; + break; + + case AC_SOUND_FX: + case AC_EFFECT: + cmd_ptr += 2; + break; + + default: + break; + } + } + } + + item->anim_num = anim->jump_anim_num; + item->frame_num = anim->jump_frame_num; + anim = &g_Anims[item->anim_num]; + + if (item->current_anim_state != anim->current_anim_state) { + item->current_anim_state = anim->current_anim_state; + item->goal_anim_state = anim->current_anim_state; + } + + if (item->required_anim_state == item->current_anim_state) { + item->required_anim_state = 0; + } + } + + if (anim->num_commands > 0) { + const int16_t *cmd_ptr = &g_AnimCommands[anim->command_idx]; + for (int32_t i = 0; i < anim->num_commands; i++) { + const int16_t cmd = *cmd_ptr++; + switch (cmd) { + case AC_MOVE_ORIGIN: + cmd_ptr += 3; + break; + + case AC_JUMP_VELOCITY: + cmd_ptr += 2; + break; + + case AC_SOUND_FX: { + const int32_t frame = cmd_ptr[0]; + const SOUND_EFFECT_ID sound_id = cmd_ptr[1] & 0x3FFF; + const ANIM_COMMAND_ENVIRONMENT type = + (cmd_ptr[1] & 0xC000) >> 14; + cmd_ptr += 2; + + if (item->frame_num != frame) { + break; + } + + if (g_Objects[item->object_id].water_creature) { + Sound_Effect(sound_id, &item->pos, SPM_UNDERWATER); + } else if (item->room_num == NO_ROOM) { + item->pos.x = g_LaraItem->pos.x; + item->pos.y = g_LaraItem->pos.y - LARA_HEIGHT; + item->pos.z = g_LaraItem->pos.z; + Sound_Effect( + sound_id, &item->pos, + item->object_id == O_LARA_HARPOON ? SPM_ALWAYS + : SPM_NORMAL); + } else if (g_Rooms[item->room_num].flags & RF_UNDERWATER) { + if (type == ACE_ALL || type == ACE_WATER) { + Sound_Effect(sound_id, &item->pos, SPM_NORMAL); + } + } else if (type == ACE_ALL || type == ACE_LAND) { + Sound_Effect(sound_id, &item->pos, SPM_NORMAL); + } + break; + } + + case AC_EFFECT: { + const int32_t frame = cmd_ptr[0]; + const int32_t fx_func_idx = cmd_ptr[1] & 0x3FFF; + cmd_ptr += 2; + + if (item->frame_num == frame) { + g_EffectRoutines[fx_func_idx](item); + } + break; + } + + default: + break; + } + } + } + + if (item->gravity) { + item->fall_speed += item->fall_speed < FAST_FALL_SPEED ? GRAVITY : 1; + item->pos.y += item->fall_speed; + } else { + int32_t speed = anim->velocity; + if (anim->acceleration) { + speed += anim->acceleration * (item->frame_num - anim->frame_base); + } + item->speed = speed >> 16; + } + + item->pos.x += (item->speed * Math_Sin(item->rot.y)) >> W2V_SHIFT; + item->pos.z += (item->speed * Math_Cos(item->rot.y)) >> W2V_SHIFT; +} + +int32_t __cdecl Item_GetAnimChange(ITEM *const item, const ANIM *const anim) +{ + if (item->current_anim_state == item->goal_anim_state) { + return false; + } + + for (int32_t i = 0; i < anim->num_changes; i++) { + const ANIM_CHANGE *const change = &g_AnimChanges[anim->change_idx + i]; + if (change->goal_anim_state != item->goal_anim_state) { + continue; + } + + for (int32_t j = 0; j < change->num_ranges; j++) { + const ANIM_RANGE *const range = + &g_AnimRanges[change->range_idx + j]; + + if (item->frame_num >= range->start_frame + && item->frame_num <= range->end_frame) { + item->anim_num = range->link_anim_num; + item->frame_num = range->link_frame_num; + return true; + } + } + } + + return false; +} + +void __cdecl Item_Translate( + ITEM *const item, const int32_t x, const int32_t y, const int32_t z) +{ + const int32_t c = Math_Cos(item->rot.y); + const int32_t s = Math_Sin(item->rot.y); + item->pos.x += ((c * x + s * z) >> W2V_SHIFT); + item->pos.y += y; + item->pos.z += ((c * z - s * x) >> W2V_SHIFT); +} + +int32_t __cdecl Item_IsTriggerActive(ITEM *const item) +{ + const bool ok = !(item->flags & IF_REVERSE); + + if ((item->flags & IF_CODE_BITS) != IF_CODE_BITS) { + return !ok; + } + + if (!item->timer) { + return ok; + } + + if (item->timer == -1) { + return !ok; + } + + item->timer--; + if (item->timer == 0) { + item->timer = -1; + } + + return ok; +} + +int32_t __cdecl Item_GetFrames( + const ITEM *item, FRAME_INFO *frmptr[], int32_t *rate) +{ + const ANIM *const anim = &g_Anims[item->anim_num]; + const int32_t cur_frame_num = item->frame_num - anim->frame_base; + const int32_t size = anim->interpolation >> 8; + const int32_t key_frame_span = anim->interpolation & 0xFF; + const int32_t key_frame_shift = cur_frame_num % key_frame_span; + const int32_t first_key_frame_num = cur_frame_num / key_frame_span * size; + const int32_t second_key_frame_num = first_key_frame_num + size; + + const int32_t numerator = key_frame_shift; + int32_t denominator = key_frame_span; + if (numerator != 0) { + // TODO: ?? + const int32_t second_key_frame_num2 = + (cur_frame_num / key_frame_span + 1) * key_frame_span; + if (second_key_frame_num2 > anim->frame_end) { + denominator += anim->frame_end - second_key_frame_num2; + } + } + + frmptr[0] = (FRAME_INFO *)(anim->frame_ptr + first_key_frame_num); + frmptr[1] = (FRAME_INFO *)(anim->frame_ptr + second_key_frame_num); + *rate = denominator; + return numerator; +} + +BOUNDS_16 *__cdecl Item_GetBoundsAccurate(const ITEM *const item) +{ + int32_t rate; + FRAME_INFO *frmptr[2]; + const int32_t frac = Item_GetFrames(item, frmptr, &rate); + if (!frac) { + return &frmptr[0]->bounds; + } + +#define CALC(target, b1, b2, prop) \ + target->prop = (b1)->prop + ((((b2)->prop - (b1)->prop) * frac) / rate); + + BOUNDS_16 *const result = &m_InterpolatedBounds; + CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, min_x); + CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, max_x); + CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, min_y); + CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, max_y); + CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, min_z); + CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, max_z); + return result; +} + +FRAME_INFO *__cdecl Item_GetBestFrame(const ITEM *const item) +{ + FRAME_INFO *frmptr[2]; + int32_t rate; + const int32_t frac = Item_GetFrames(item, frmptr, &rate); + return frmptr[(frac > rate / 2) ? 1 : 0]; +} + +bool __cdecl Item_IsNearItem( + const ITEM *const item, const XYZ_32 *const pos, const int32_t distance) +{ + const XYZ_32 d = { + .x = pos->x - item->pos.x, + .y = pos->y - item->pos.y, + .z = pos->z - item->pos.z, + }; + if (ABS(d.x) > distance || ABS(d.z) > distance || ABS(d.y) > WALL_L * 3) { + return false; + } + + if (SQUARE(d.x) + SQUARE(d.z) > SQUARE(distance)) { + return false; + } + + const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); + return d.y >= bounds->min_y && d.y <= bounds->max_y + 100; +} + +int32_t Item_GetDistance(const ITEM *const item, const XYZ_32 *const target) +{ + return XYZ_32_GetDistance(&item->pos, target); +} + +ITEM *Item_Get(const int16_t item_num) +{ + return &g_Items[item_num]; +} diff --git a/src/tr2/game/items.h b/src/tr2/game/items.h new file mode 100644 index 000000000..a170f38eb --- /dev/null +++ b/src/tr2/game/items.h @@ -0,0 +1,37 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Item_InitialiseArray(int32_t num_items); +int32_t Item_GetTotalCount(void); +int16_t __cdecl Item_Create(void); +void __cdecl Item_Kill(int16_t item_num); +void __cdecl Item_Initialise(int16_t item_num); +void __cdecl Item_RemoveActive(int16_t item_num); +void __cdecl Item_RemoveDrawn(int16_t item_num); +void __cdecl Item_AddActive(int16_t item_num); +void __cdecl Item_NewRoom(int16_t item_num, int16_t room_num); +int32_t __cdecl Item_GlobalReplace( + GAME_OBJECT_ID src_object_id, GAME_OBJECT_ID dst_object_id); +void __cdecl Item_ClearKilled(void); +void __cdecl Item_ShiftCol(ITEM *item, COLL_INFO *coll); +void __cdecl Item_UpdateRoom(ITEM *item, int32_t height); +int32_t __cdecl Item_TestBoundsCollide( + const ITEM *src_item, const ITEM *dst_item, int32_t radius); +int32_t __cdecl Item_TestPosition( + const int16_t *bounds, const ITEM *src_item, const ITEM *dst_item); +void __cdecl Item_AlignPosition( + const XYZ_32 *vec, const ITEM *src_item, ITEM *dst_item); +void __cdecl Item_Animate(ITEM *item); +int32_t __cdecl Item_GetAnimChange(ITEM *item, const ANIM *anim); +void __cdecl Item_Translate(ITEM *item, int32_t x, int32_t y, int32_t z); +int32_t __cdecl Item_IsTriggerActive(ITEM *item); +int32_t __cdecl Item_GetFrames( + const ITEM *item, FRAME_INFO *frmptr[], int32_t *rate); +BOUNDS_16 *__cdecl Item_GetBoundsAccurate(const ITEM *item); +FRAME_INFO *__cdecl Item_GetBestFrame(const ITEM *item); +bool __cdecl Item_IsNearItem( + const ITEM *item, const XYZ_32 *pos, int32_t distance); + +bool Item_IsSmashable(const ITEM *item); +int32_t Item_GetDistance(const ITEM *item, const XYZ_32 *target); diff --git a/src/tr2/game/lara/cheat.c b/src/tr2/game/lara/cheat.c new file mode 100644 index 000000000..b041bae71 --- /dev/null +++ b/src/tr2/game/lara/cheat.c @@ -0,0 +1,437 @@ +#include "game/lara/cheat.h" + +#include "decomp/effects.h" +#include "game/camera.h" +#include "game/console/common.h" +#include "game/creature.h" +#include "game/game_string.h" +#include "game/gun/gun.h" +#include "game/inventory/backpack.h" +#include "game/items.h" +#include "game/lara/control.h" +#include "game/math.h" +#include "game/objects/common.h" +#include "game/objects/vars.h" +#include "game/output.h" +#include "game/room.h" +#include "game/sound.h" +#include "global/funcs.h" +#include "global/utils.h" +#include "global/vars.h" + +#include +#include + +static void M_GiveAllGunsImpl(void); +static void M_GiveAllMedpacksImpl(void); +static void M_GiveAllKeysImpl(void); +static void M_ReinitialiseGunMeshes(void); +static void M_ResetGunStatus(void); + +static void M_ReinitialiseGunMeshes(void) +{ + // TODO: consider refactoring flare check once more is known about overall + // flare control. + const bool has_flare = g_Lara.mesh_ptrs[LM_HAND_L] + == g_Meshes[g_Objects[O_LARA_FLARE].mesh_idx + LM_HAND_L]; + + Lara_InitialiseMeshes(g_CurrentLevel); + Gun_InitialiseNewWeapon(); + if (has_flare) { + Flare_DrawMeshes(); + } +} + +static void M_GiveAllGunsImpl(void) +{ + Inv_AddItem(O_PISTOL_ITEM); + Inv_AddItem(O_MAGNUM_ITEM); + Inv_AddItem(O_UZI_ITEM); + Inv_AddItem(O_SHOTGUN_ITEM); + Inv_AddItem(O_HARPOON_ITEM); + Inv_AddItem(O_M16_ITEM); + Inv_AddItem(O_GRENADE_ITEM); + g_Lara.magnum_ammo.ammo = 1000; + g_Lara.uzi_ammo.ammo = 2000; + g_Lara.shotgun_ammo.ammo = 300; + g_Lara.harpoon_ammo.ammo = 300; + g_Lara.m16_ammo.ammo = 300; + g_Lara.grenade_ammo.ammo = 300; +} + +static void M_GiveAllMedpacksImpl(void) +{ + Inv_AddItemNTimes(O_FLARES_ITEM, 10); + Inv_AddItemNTimes(O_SMALL_MEDIPACK_ITEM, 10); + Inv_AddItemNTimes(O_LARGE_MEDIPACK_ITEM, 10); +} + +static void M_GiveAllKeysImpl(void) +{ + Inv_AddItem(O_PUZZLE_ITEM_1); + Inv_AddItem(O_PUZZLE_ITEM_2); + Inv_AddItem(O_PUZZLE_ITEM_3); + Inv_AddItem(O_PUZZLE_ITEM_4); + Inv_AddItem(O_KEY_ITEM_1); + Inv_AddItem(O_KEY_ITEM_2); + Inv_AddItem(O_KEY_ITEM_3); + Inv_AddItem(O_KEY_ITEM_4); + Inv_AddItem(O_PICKUP_ITEM_1); + Inv_AddItem(O_PICKUP_ITEM_2); +} + +static void M_ResetGunStatus(void) +{ + const bool has_flare = g_Lara.mesh_ptrs[LM_HAND_L] + == g_Meshes[g_Objects[O_LARA_FLARE].mesh_idx + LM_HAND_L]; + if (has_flare) { + g_Lara.gun_type = LGT_FLARE; + return; + } + + g_Lara.gun_status = LGS_ARMLESS; + g_Lara.gun_type = LGT_UNARMED; + g_Lara.request_gun_type = LGT_UNARMED; + g_Lara.weapon_item = NO_ITEM; + g_Lara.gun_status = LGS_ARMLESS; + g_Lara.left_arm.frame_num = 0; + g_Lara.left_arm.lock = 0; + g_Lara.right_arm.frame_num = 0; + g_Lara.right_arm.lock = 0; + g_Lara.left_arm.anim_num = g_LaraItem->anim_num; + g_Lara.right_arm.anim_num = g_LaraItem->anim_num; + g_Lara.left_arm.frame_base = g_Anims[g_LaraItem->anim_num].frame_ptr; + g_Lara.right_arm.frame_base = g_Anims[g_LaraItem->anim_num].frame_ptr; +} + +void __cdecl Lara_Cheat_EndLevel(void) +{ + g_LevelComplete = true; + Console_Log(GS(OSD_COMPLETE_LEVEL)); +} + +bool Lara_Cheat_EnterFlyMode(void) +{ + if (g_LaraItem == NULL) { + return false; + } + + if (g_Lara.extra_anim) { + M_ResetGunStatus(); + } + + Lara_GetOffVehicle(); + + if (g_Lara.water_status != LWS_UNDERWATER || g_LaraItem->hit_points <= 0) { + g_LaraItem->pos.y -= STEP_L; + g_LaraItem->current_anim_state = LS_SWIM; + g_LaraItem->goal_anim_state = LS_SWIM; + g_LaraItem->anim_num = + g_Objects[O_LARA].anim_idx + LA_UNDERWATER_SWIM_FORWARD_DRIFT; + g_LaraItem->frame_num = g_Anims[g_LaraItem->anim_num].frame_base; + g_LaraItem->gravity = 0; + g_LaraItem->rot.x = 30 * PHD_DEGREE; + g_LaraItem->fall_speed = 30; + g_Lara.head_x_rot = 0; + g_Lara.head_y_rot = 0; + g_Lara.torso_x_rot = 0; + g_Lara.torso_y_rot = 0; + } + + g_Lara.water_status = LWS_CHEAT; + g_Lara.spaz_effect_count = 0; + g_Lara.spaz_effect = NULL; + g_Lara.hit_frame = 0; + g_Lara.hit_direction = -1; + g_Lara.air = LARA_MAX_AIR; + g_Lara.death_timer = 0; + g_Lara.mesh_effects = 0; + g_Lara.burn = 0; + g_Lara.extra_anim = 0; + + M_ReinitialiseGunMeshes(); + g_Camera.type = CAM_CHASE; + Output_AlterFOV(GAME_FOV * PHD_DEGREE); + + Console_Log(GS(OSD_FLY_MODE_ON)); + return true; +} + +bool Lara_Cheat_ExitFlyMode(void) +{ + if (g_LaraItem == NULL) { + return false; + } + + const ROOM *const room = &g_Rooms[g_LaraItem->room_num]; + const bool room_submerged = (room->flags & RF_UNDERWATER) != 0; + const int16_t water_height = Room_GetWaterHeight( + g_LaraItem->pos.x, g_LaraItem->pos.y, g_LaraItem->pos.z, + g_LaraItem->room_num); + + if (room_submerged || (water_height != NO_HEIGHT && water_height > 0)) { + g_Lara.water_status = LWS_UNDERWATER; + } else { + g_Lara.water_status = LWS_ABOVE_WATER; + g_LaraItem->anim_num = g_Objects[O_LARA].anim_idx + LA_STAND_STILL; + g_LaraItem->frame_num = g_Anims[g_LaraItem->anim_num].frame_base; + g_LaraItem->rot.x = 0; + g_LaraItem->rot.z = 0; + g_Lara.head_x_rot = 0; + g_Lara.head_y_rot = 0; + g_Lara.torso_x_rot = 0; + g_Lara.torso_y_rot = 0; + } + + if (g_Lara.weapon_item != NO_ITEM) { + g_Lara.gun_status = LGS_UNDRAW; + } else { + g_Lara.gun_status = LGS_ARMLESS; + M_ReinitialiseGunMeshes(); + } + + Console_Log(GS(OSD_FLY_MODE_OFF)); + return true; +} + +bool Lara_Cheat_OpenNearestDoor(void) +{ + if (g_LaraItem == NULL) { + return false; + } + + int32_t opened = 0; + int32_t closed = 0; + + const int32_t shift = 8; // constant shift to avoid overflow errors + const int32_t max_dist = SQUARE((WALL_L * 2) >> shift); + for (int item_num = 0; item_num < g_LevelItemCount; item_num++) { + ITEM *const item = &g_Items[item_num]; + if (!Object_IsObjectType(item->object_id, g_DoorObjects) + && !Object_IsObjectType(item->object_id, g_TrapdoorObjects)) { + continue; + } + + const int32_t dx = (item->pos.x - g_LaraItem->pos.x) >> shift; + const int32_t dy = (item->pos.y - g_LaraItem->pos.y) >> shift; + const int32_t dz = (item->pos.z - g_LaraItem->pos.z) >> shift; + const int32_t dist = SQUARE(dx) + SQUARE(dy) + SQUARE(dz); + if (dist > max_dist) { + continue; + } + + if (!item->active) { + Item_AddActive(item_num); + item->flags |= IF_CODE_BITS; + opened++; + } else if (item->flags & IF_CODE_BITS) { + item->flags &= ~IF_CODE_BITS; + closed++; + } else { + item->flags |= IF_CODE_BITS; + opened++; + } + item->timer = 0; + item->touch_bits = 0; + } + + if (opened > 0 || closed > 0) { + Console_Log(opened > 0 ? GS(OSD_DOOR_OPEN) : GS(OSD_DOOR_CLOSE)); + return true; + } + Console_Log(GS(OSD_DOOR_OPEN_FAIL)); + return false; +} + +void __cdecl Lara_Cheat_GetStuff(void) +{ + M_GiveAllGunsImpl(); + M_GiveAllMedpacksImpl(); +} + +bool Lara_Cheat_GiveAllKeys(void) +{ + if (g_LaraItem == NULL) { + return false; + } + + M_GiveAllKeysImpl(); + + Sound_Effect(SFX_LARA_KEY, NULL, SPM_ALWAYS); + Console_Log(GS(OSD_GIVE_ITEM_ALL_KEYS)); + return true; +} + +bool Lara_Cheat_GiveAllGuns(void) +{ + if (g_LaraItem == NULL) { + return false; + } + + M_GiveAllGunsImpl(); + + Sound_Effect(SFX_LARA_RELOAD, NULL, SPM_ALWAYS); + Console_Log(GS(OSD_GIVE_ITEM_ALL_GUNS)); + return true; +} + +bool Lara_Cheat_GiveAllItems(void) +{ + if (g_LaraItem == NULL) { + return false; + } + + M_GiveAllGunsImpl(); + M_GiveAllKeysImpl(); + M_GiveAllMedpacksImpl(); + + Sound_Effect(SFX_LARA_HOLSTER, &g_LaraItem->pos, SPM_NORMAL); + Console_Log(GS(OSD_GIVE_ITEM_CHEAT)); + return true; +} + +bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z) +{ + int16_t room_num = Room_GetIndexFromPos(x, y, z); + if (room_num == NO_ROOM) { + return false; + } + + const SECTOR *sector = Room_GetSector(x, y, z, &room_num); + int16_t height = Room_GetHeight(sector, x, y, z); + + if (height == NO_HEIGHT) { + // Sample a sphere of points around target x, y, z + // and teleport to the first available location. + VECTOR *const points = Vector_Create(sizeof(XYZ_32)); + + const int32_t radius = 10; + const int32_t unit = STEP_L; + for (int32_t dx = -radius; dx <= radius; dx++) { + for (int32_t dz = -radius; dz <= radius; dz++) { + if (SQUARE(dx) + SQUARE(dz) > SQUARE(radius)) { + continue; + } + + const XYZ_32 point = { + .x = ROUND_TO_SECTOR(x + dx * unit) + WALL_L / 2, + .y = y, + .z = ROUND_TO_SECTOR(z + dz * unit) + WALL_L / 2, + }; + room_num = Room_GetIndexFromPos(point.x, point.y, point.z); + if (room_num == NO_ROOM) { + continue; + } + sector = Room_GetSector(point.x, point.y, point.z, &room_num); + height = Room_GetHeight(sector, point.x, point.y, point.z); + if (height == NO_HEIGHT) { + continue; + } + Vector_Add(points, (void *)&point); + } + } + + int32_t best_distance = INT32_MAX; + for (int32_t i = 0; i < points->count; i++) { + const XYZ_32 *const point = (const XYZ_32 *)Vector_Get(points, i); + const int32_t distance = + XYZ_32_GetDistance(point, &g_LaraItem->pos); + if (distance < best_distance) { + best_distance = distance; + x = point->x; + y = point->y; + z = point->z; + } + } + + Vector_Free(points); + if (best_distance == INT32_MAX) { + return false; + } + } + + room_num = Room_GetIndexFromPos(x, y, z); + if (room_num == NO_ROOM) { + return false; + } + sector = Room_GetSector(x, y, z, &room_num); + height = Room_GetHeight(sector, x, y, z); + if (height == NO_HEIGHT) { + return false; + } + + g_LaraItem->pos.x = x; + g_LaraItem->pos.y = y; + g_LaraItem->pos.z = z; + g_LaraItem->floor = height; + + if (g_LaraItem->room_num != room_num) { + const int16_t item_num = g_LaraItem - g_Items; + Item_NewRoom(item_num, room_num); + } + + if (g_Lara.gun_status == LGS_HANDS_BUSY) { + g_Lara.gun_status = LGS_ARMLESS; + } + + Lara_GetOffVehicle(); + + if (g_Lara.extra_anim) { + const ROOM *const room = &g_Rooms[g_LaraItem->room_num]; + const bool room_submerged = (room->flags & RF_UNDERWATER) != 0; + const int16_t water_height = Room_GetWaterHeight( + g_LaraItem->pos.x, g_LaraItem->pos.y, g_LaraItem->pos.z, + g_LaraItem->room_num); + + if (room_submerged || (water_height != NO_HEIGHT && water_height > 0)) { + g_Lara.water_status = LWS_UNDERWATER; + g_LaraItem->current_anim_state = LS_SWIM; + g_LaraItem->goal_anim_state = LS_SWIM; + g_LaraItem->anim_num = + g_Objects[O_LARA].anim_idx + LA_UNDERWATER_SWIM_FORWARD_DRIFT; + g_LaraItem->frame_num = g_Anims[g_LaraItem->anim_num].frame_base; + } else { + g_Lara.water_status = LWS_ABOVE_WATER; + g_LaraItem->current_anim_state = LS_STOP; + g_LaraItem->goal_anim_state = LS_STOP; + g_LaraItem->anim_num = g_Objects[O_LARA].anim_idx + LA_STAND_STILL; + g_LaraItem->frame_num = g_Anims[g_LaraItem->anim_num].frame_base; + g_LaraItem->rot.x = 0; + g_LaraItem->rot.z = 0; + g_Lara.head_x_rot = 0; + g_Lara.head_y_rot = 0; + g_Lara.torso_x_rot = 0; + g_Lara.torso_y_rot = 0; + } + + g_Lara.extra_anim = 0; + M_ResetGunStatus(); + M_ReinitialiseGunMeshes(); + } + + g_Lara.spaz_effect_count = 0; + g_Lara.spaz_effect = NULL; + g_Lara.hit_frame = 0; + g_Lara.hit_direction = -1; + g_Lara.air = LARA_MAX_AIR; + g_Lara.death_timer = 0; + g_Lara.mesh_effects = 0; + + g_Camera.type = CAM_CHASE; + Output_AlterFOV(GAME_FOV * PHD_DEGREE); + + Camera_ResetPosition(); + return true; +} + +bool Lara_Cheat_KillEnemy(const int16_t item_num) +{ + ITEM *const item = &g_Items[item_num]; + if ((item->hit_points == DONT_TARGET && item->object_id != O_WINSTON)) { + return false; + } + + Sound_Effect(SFX_EXPLOSION1, &item->pos, SPM_NORMAL); + Creature_Die(item_num, true); + return true; +} diff --git a/src/tr2/game/lara/cheat.h b/src/tr2/game/lara/cheat.h new file mode 100644 index 000000000..4ec8fd765 --- /dev/null +++ b/src/tr2/game/lara/cheat.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +void __cdecl Lara_Cheat_EndLevel(void); +bool Lara_Cheat_EnterFlyMode(void); +bool Lara_Cheat_ExitFlyMode(void); +void __cdecl Lara_Cheat_GetStuff(void); +bool Lara_Cheat_GiveAllKeys(void); +bool Lara_Cheat_GiveAllGuns(void); +bool Lara_Cheat_GiveAllItems(void); +bool Lara_Cheat_OpenNearestDoor(void); +bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z); +bool Lara_Cheat_KillEnemy(int16_t item_num); diff --git a/src/tr2/game/lara/col.c b/src/tr2/game/lara/col.c new file mode 100644 index 000000000..354424ab5 --- /dev/null +++ b/src/tr2/game/lara/col.c @@ -0,0 +1,1295 @@ +#include "game/lara/col.h" + +#include "game/collide.h" +#include "game/input.h" +#include "game/items.h" +#include "game/lara/control.h" +#include "game/lara/misc.h" +#include "game/math.h" +#include "game/room.h" +#include "game/sound.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +void __cdecl Lara_CollideStop(ITEM *const item, const COLL_INFO *const coll) +{ + switch (coll->old_anim_state) { + case LS_STOP: + case LS_TURN_RIGHT: + case LS_TURN_LEFT: + case LS_FAST_TURN: + item->current_anim_state = coll->old_anim_state; + item->anim_num = coll->old_anim_num; + item->frame_num = coll->old_frame_num; + if (g_Input & IN_LEFT) { + item->goal_anim_state = LS_TURN_LEFT; + } else if (g_Input & IN_RIGHT) { + item->goal_anim_state = LS_TURN_RIGHT; + } else { + item->goal_anim_state = LS_STOP; + } + Lara_Animate(item); + break; + + default: + item->anim_num = LA_STAND_STILL; + item->frame_num = g_Anims[item->anim_num].frame_base; + break; + } +} + +bool __cdecl Lara_Fallen(ITEM *const item, const COLL_INFO *const coll) +{ + if (coll->side_mid.floor <= STEPUP_HEIGHT + || g_Lara.water_status == LWS_WADE) { + return false; + } + item->current_anim_state = LS_FORWARD_JUMP; + item->goal_anim_state = LS_FORWARD_JUMP; + item->anim_num = LA_FALL_START; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->gravity = 1; + item->fall_speed = 0; + return true; +} + +bool __cdecl Lara_TestWaterClimbOut( + ITEM *const item, const COLL_INFO *const coll) +{ + if (coll->coll_type != COLL_FRONT || !(g_Input & IN_ACTION) + || coll->side_front.type == HT_BIG_SLOPE) { + return false; + } + + const int32_t coll_hdif = + ABS(coll->side_left.floor - coll->side_right.floor); + const int32_t min_coll_hdif = 60; + if (coll_hdif >= min_coll_hdif) { + return false; + } + + if (g_Lara.gun_status != LGS_ARMLESS + && (g_Lara.gun_status != LGS_READY || g_Lara.gun_type != LGT_FLARE)) { + return false; + } + + if (coll->side_front.ceiling > 0) { + return false; + } + if (coll->side_mid.ceiling > -STEPUP_HEIGHT) { + return false; + } + + const int32_t lara_hdif = coll->side_front.floor + LARA_HEIGHT_SURF; + if (lara_hdif <= -STEP_L * 2 + || lara_hdif > LARA_HEIGHT_SURF - STEPUP_HEIGHT) { + return false; + } + + const DIRECTION dir = Math_GetDirectionCone(item->rot.y, 35 * PHD_DEGREE); + if (dir == DIR_UNKNOWN) { + return false; + } + + item->pos.y += lara_hdif - 5; + Item_UpdateRoom(item, -LARA_HEIGHT / 2); + + switch (dir) { + case DIR_NORTH: + item->pos.z = (item->pos.z & -WALL_L) + WALL_L + LARA_RADIUS; + break; + case DIR_WEST: + item->pos.x = (item->pos.x & -WALL_L) + WALL_L + LARA_RADIUS; + break; + case DIR_SOUTH: + item->pos.z = (item->pos.z & -WALL_L) - LARA_RADIUS; + break; + case DIR_EAST: + item->pos.x = (item->pos.x & -WALL_L) - LARA_RADIUS; + break; + case DIR_UNKNOWN: + return false; + } + + if (lara_hdif < -STEP_L / 2) { + item->anim_num = LA_ONWATER_TO_STAND_HIGH; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else if (lara_hdif < STEP_L / 2) { + item->anim_num = LA_ONWATER_TO_STAND_MEDIUM; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else { + item->anim_num = LA_ONWATER_TO_WADE_LOW; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + + item->current_anim_state = LS_WATER_OUT; + item->goal_anim_state = LS_STOP; + item->rot.y = Math_DirectionToAngle(dir); + item->rot.x = 0; + item->rot.z = 0; + item->gravity = 0; + item->speed = 0; + item->fall_speed = 0; + g_Lara.gun_status = LGS_HANDS_BUSY; + g_Lara.water_status = LWS_ABOVE_WATER; + return true; +} + +bool __cdecl Lara_TestWaterStepOut( + ITEM *const item, const COLL_INFO *const coll) +{ + if (coll->coll_type == COLL_FRONT || coll->side_mid.type == HT_BIG_SLOPE + || coll->side_mid.floor >= 0) { + return false; + } + + if (coll->side_mid.floor < -STEP_L / 2) { + item->current_anim_state = LS_WATER_OUT; + item->goal_anim_state = LS_STOP; + item->anim_num = LA_ONWATER_TO_WADE; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else if (item->goal_anim_state == LS_SURF_LEFT) { + item->goal_anim_state = LS_STEP_LEFT; + } else if (item->goal_anim_state == LS_SURF_RIGHT) { + item->goal_anim_state = LS_STEP_RIGHT; + } else { + item->current_anim_state = LS_WADE; + item->goal_anim_state = LS_WADE; + item->anim_num = LA_WADE; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + + item->pos.y += coll->side_front.floor + LARA_HEIGHT_SURF - 5; + Item_UpdateRoom(item, -LARA_HEIGHT / 2); + item->gravity = 0; + item->rot.x = 0; + item->rot.z = 0; + item->speed = 0; + item->fall_speed = 0; + g_Lara.water_status = LWS_WADE; + return true; +} + +void __cdecl Lara_SurfaceCollision(ITEM *const item, COLL_INFO *const coll) +{ + coll->facing = g_Lara.move_angle; + Collide_GetCollisionInfo( + coll, item->pos.x, item->pos.y + LARA_HEIGHT_SURF, item->pos.z, + item->room_num, LARA_HEIGHT_SURF + 100); + + Item_ShiftCol(item, coll); + + if (coll->coll_type == COLL_LEFT) { + item->rot.y += 5 * PHD_DEGREE; + } else if (coll->coll_type == COLL_RIGHT) { + item->rot.y -= 5 * PHD_DEGREE; + } else if ( + coll->coll_type != COLL_NONE + || (coll->side_mid.floor < 0 && coll->side_mid.type == HT_BIG_SLOPE)) { + item->fall_speed = 0; + item->pos.x = coll->old.x; + item->pos.y = coll->old.y; + item->pos.z = coll->old.z; + } + + const int32_t water_height = Room_GetWaterHeight( + item->pos.x, item->pos.y, item->pos.z, item->room_num); + if (water_height - item->pos.y <= -100) { + item->current_anim_state = LS_DIVE; + item->goal_anim_state = LS_SWIM; + item->anim_num = LA_ONWATER_DIVE; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->rot.x = -45 * PHD_DEGREE; + item->fall_speed = 80; + g_Lara.water_status = LWS_UNDERWATER; + return; + } + + Lara_TestWaterStepOut(item, coll); +} + +void __cdecl Lara_Col_Walk(ITEM *item, COLL_INFO *coll) +{ + item->gravity = 0; + item->fall_speed = 0; + g_Lara.move_angle = item->rot.y; + coll->slopes_are_pits = 1; + coll->slopes_are_walls = 1; + coll->lava_is_pit = 1; + coll->bad_pos = STEPUP_HEIGHT; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + Lara_GetCollisionInfo(item, coll); + + if (Lara_HitCeiling(item, coll) || Lara_TestVault(item, coll)) { + return; + } + + if (Lara_DeflectEdge(item, coll)) { + if (item->frame_num >= 29 && item->frame_num <= 47) { + item->anim_num = LA_WALK_STOP_LEFT; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else if ( + (item->frame_num >= 22 && item->frame_num <= 28) + || (item->frame_num >= 48 && item->frame_num <= 57)) { + item->anim_num = LA_WALK_STOP_RIGHT; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else { + Lara_CollideStop(item, coll); + } + } + + if (Lara_Fallen(item, coll)) { + return; + } + + if (coll->side_mid.floor > STEP_L / 2) { + if (item->frame_num >= 28 && item->frame_num <= 45) { + item->anim_num = LA_WALK_DOWN_LEFT; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else { + item->anim_num = LA_WALK_DOWN_RIGHT; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + } + + if (coll->side_mid.floor >= -STEPUP_HEIGHT + && coll->side_mid.floor < -STEP_L / 2) { + if (item->frame_num >= 27 && item->frame_num <= 44) { + item->anim_num = LA_WALK_UP_STEP_LEFT; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else { + item->anim_num = LA_WALK_UP_STEP_RIGHT; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + } + + if (Lara_TestSlide(item, coll)) { + return; + } + + item->pos.y += coll->side_mid.floor; +} + +void __cdecl Lara_Col_Run(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y; + coll->slopes_are_walls = 1; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + Lara_GetCollisionInfo(item, coll); + + if (Lara_HitCeiling(item, coll) || Lara_TestVault(item, coll)) { + return; + } + + if (Lara_DeflectEdge(item, coll)) { + item->rot.z = 0; + if (item->anim_num != LA_RUN_START + && Lara_TestWall(item, STEP_L, 0, -STEP_L * 5 / 2)) { + item->current_anim_state = LS_SPLAT; + if (item->frame_num >= 0 && item->frame_num <= 9) { + item->anim_num = LA_WALL_SMASH_LEFT; + item->frame_num = g_Anims[item->anim_num].frame_base; + return; + } + if (item->frame_num >= 10 && item->frame_num <= 21) { + item->anim_num = LA_WALL_SMASH_RIGHT; + item->frame_num = g_Anims[item->anim_num].frame_base; + return; + } + } + Lara_CollideStop(item, coll); + } + + if (Lara_Fallen(item, coll)) { + return; + } + + if (coll->side_mid.floor >= -STEPUP_HEIGHT + && coll->side_mid.floor < -STEP_L / 2) { + if (item->frame_num >= 3 && item->frame_num <= 14) { + item->anim_num = LA_RUN_UP_STEP_LEFT; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else { + item->anim_num = LA_RUN_UP_STEP_RIGHT; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + } + + if (Lara_TestSlide(item, coll)) { + return; + } + + item->pos.y += MIN(coll->side_mid.floor, 50); +} + +void __cdecl Lara_Col_Stop(ITEM *item, COLL_INFO *coll) +{ + item->gravity = 0; + item->fall_speed = 0; + g_Lara.move_angle = item->rot.y; + coll->slopes_are_pits = 1; + coll->slopes_are_walls = 1; + coll->bad_pos = STEPUP_HEIGHT; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + Lara_GetCollisionInfo(item, coll); + + if (Lara_HitCeiling(item, coll) || Lara_Fallen(item, coll) + || Lara_TestSlide(item, coll)) { + return; + } + + Item_ShiftCol(item, coll); + item->pos.y += coll->side_mid.floor; +} + +void __cdecl Lara_Col_ForwardJump(ITEM *item, COLL_INFO *coll) +{ + if (item->speed < 0) { + g_Lara.move_angle = item->rot.y + PHD_180; + } else { + g_Lara.move_angle = item->rot.y; + } + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = BAD_JUMP_CEILING; + + Lara_GetCollisionInfo(item, coll); + Lara_DeflectEdgeJump(item, coll); + if (item->speed < 0) { + g_Lara.move_angle = item->rot.y; + } + + if (coll->side_mid.floor > 0 || item->fall_speed <= 0) { + return; + } + + if (Lara_LandedBad(item, coll)) { + item->goal_anim_state = LS_DEATH; + } else if ( + g_Lara.water_status != LWS_WADE && (g_Input & IN_FORWARD) + && !(g_Input & IN_SLOW)) { + item->goal_anim_state = LS_RUN; + } else { + item->goal_anim_state = LS_STOP; + } + + item->gravity = 0; + item->fall_speed = 0; + item->pos.y += coll->side_mid.floor; + item->speed = 0; + Lara_Animate(item); +} + +void __cdecl Lara_Col_FastBack(ITEM *item, COLL_INFO *coll) +{ + item->gravity = 0; + item->fall_speed = 0; + g_Lara.move_angle = item->rot.y + PHD_180; + coll->slopes_are_pits = 1; + coll->slopes_are_walls = 1; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + + Lara_GetCollisionInfo(item, coll); + if (Lara_HitCeiling(item, coll)) { + return; + } + + if (coll->side_mid.floor <= 200) { + if (Lara_DeflectEdge(item, coll)) { + Lara_CollideStop(item, coll); + } + item->pos.y += coll->side_mid.floor; + } else { + item->anim_num = LA_FALL_BACK; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->current_anim_state = LS_FALL_BACK; + item->goal_anim_state = LS_FALL_BACK; + item->gravity = 1; + item->fall_speed = 0; + } +} + +void __cdecl Lara_Col_TurnRight(ITEM *item, COLL_INFO *coll) +{ + item->gravity = 0; + item->fall_speed = 0; + g_Lara.move_angle = item->rot.y; + coll->slopes_are_pits = 1; + coll->slopes_are_walls = 1; + coll->bad_pos = STEPUP_HEIGHT; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + + Lara_GetCollisionInfo(item, coll); + + if (coll->side_mid.floor <= 100) { + if (!Lara_TestSlide(item, coll)) { + item->pos.y += coll->side_mid.floor; + } + } else { + item->anim_num = LA_FALL_START; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->current_anim_state = LS_FORWARD_JUMP; + item->goal_anim_state = LS_FORWARD_JUMP; + item->gravity = 1; + item->fall_speed = 0; + } +} + +void __cdecl Lara_Col_TurnLeft(ITEM *item, COLL_INFO *coll) +{ + Lara_Col_TurnRight(item, coll); +} + +void __cdecl Lara_Col_Death(ITEM *item, COLL_INFO *coll) +{ + Sound_StopEffect(SFX_LARA_FALL); + g_Lara.move_angle = item->rot.y; + coll->bad_pos = STEPUP_HEIGHT; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + coll->radius = 400; + + Lara_GetCollisionInfo(item, coll); + Item_ShiftCol(item, coll); + + item->pos.y += coll->side_mid.floor; + item->hit_points = -1; + g_Lara.air = -1; +} + +void __cdecl Lara_Col_FastFall(ITEM *item, COLL_INFO *coll) +{ + item->gravity = 1; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = BAD_JUMP_CEILING; + + Lara_GetCollisionInfo(item, coll); + Lara_SlideEdgeJump(item, coll); + if (coll->side_mid.floor > 0) { + return; + } + + if (Lara_LandedBad(item, coll)) { + item->goal_anim_state = LS_DEATH; + } else { + item->goal_anim_state = LS_STOP; + item->current_anim_state = LS_STOP; + item->anim_num = LA_FREEFALL_LAND; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + + Sound_StopEffect(SFX_LARA_FALL); + item->gravity = 0; + item->fall_speed = 0; + item->pos.y += coll->side_mid.floor; +} + +void __cdecl Lara_Col_Hang(ITEM *item, COLL_INFO *coll) +{ + Lara_HangTest(item, coll); + if (item->goal_anim_state != LS_HANG) { + return; + } + + if ((g_Input & IN_FORWARD)) { + if (coll->side_front.floor <= -850 || coll->side_front.floor >= -650 + || coll->side_front.floor - coll->side_front.ceiling < 0 + || coll->side_left.floor - coll->side_left.ceiling < 0 + || coll->side_right.floor - coll->side_right.ceiling < 0 + || coll->hit_static) { + if (g_Lara.climb_status && item->anim_num == LA_REACH_TO_HANG + && item->frame_num == g_Anims[item->anim_num].frame_base + 21 + && coll->side_mid.ceiling <= -256) { + item->goal_anim_state = LS_HANG; + item->current_anim_state = LS_HANG; + item->anim_num = LA_LADDER_UP_HANGING; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + } else if (g_Input & IN_SLOW) { + item->goal_anim_state = LS_GYMNAST; + } else { + item->goal_anim_state = LS_NULL; + } + } else if ( + (g_Input & IN_BACK) && g_Lara.climb_status + && item->anim_num == LA_REACH_TO_HANG + && item->frame_num == g_Anims[item->anim_num].frame_base + 21) { + item->goal_anim_state = LS_HANG; + item->current_anim_state = LS_HANG; + item->anim_num = LA_LADDER_DOWN_HANGING; + item->frame_num = g_Anims[item->anim_num].frame_base; + } +} + +void __cdecl Lara_Col_Reach(ITEM *item, COLL_INFO *coll) +{ + item->gravity = 1; + g_Lara.move_angle = item->rot.y; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = 0; + coll->bad_ceiling = BAD_JUMP_CEILING; + + Lara_GetCollisionInfo(item, coll); + if (Lara_TestHangJump(item, coll)) { + return; + } + + Lara_SlideEdgeJump(item, coll); + if (item->fall_speed <= 0 || coll->side_mid.floor > 0) { + return; + } + + if (Lara_LandedBad(item, coll)) { + item->goal_anim_state = LS_DEATH; + } else { + item->goal_anim_state = LS_STOP; + } + item->gravity = 0; + item->fall_speed = 0; + item->pos.y += coll->side_mid.floor; +} + +void __cdecl Lara_Col_Splat(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y; + coll->slopes_are_pits = 1; + coll->slopes_are_walls = 1; + coll->bad_pos = STEPUP_HEIGHT; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + + Lara_GetCollisionInfo(item, coll); + Item_ShiftCol(item, coll); + + if (coll->side_mid.floor > -STEP_L && coll->side_mid.floor < STEP_L) { + item->pos.y += coll->side_mid.floor; + } +} + +void __cdecl Lara_Col_Land(ITEM *item, COLL_INFO *coll) +{ + Lara_Col_Stop(item, coll); +} + +void __cdecl Lara_Col_Compress(ITEM *item, COLL_INFO *coll) +{ + item->gravity = 0; + item->fall_speed = 0; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = NO_BAD_NEG; + coll->bad_ceiling = 0; + + Lara_GetCollisionInfo(item, coll); + + if (coll->side_mid.ceiling > -100) { + item->anim_num = LA_STAND_STILL; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->goal_anim_state = LS_STOP; + item->current_anim_state = LS_STOP; + item->gravity = 0; + item->speed = 0; + item->fall_speed = 0; + item->pos.x = coll->old.x; + item->pos.y = coll->old.y; + item->pos.z = coll->old.z; + } + + if (coll->side_mid.floor > -STEP_L && coll->side_mid.floor < STEP_L) { + item->pos.y += coll->side_mid.floor; + } +} + +void __cdecl Lara_Col_Back(ITEM *item, COLL_INFO *coll) +{ + item->gravity = 0; + item->fall_speed = 0; + g_Lara.move_angle = item->rot.y + PHD_180; + if (g_Lara.water_status == LWS_WADE) { + coll->bad_pos = NO_BAD_POS; + } else { + coll->bad_pos = STEPUP_HEIGHT; + } + coll->slopes_are_pits = 1; + coll->slopes_are_walls = 1; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + + Lara_GetCollisionInfo(item, coll); + if (Lara_HitCeiling(item, coll)) { + return; + } + + if (Lara_DeflectEdge(item, coll)) { + Lara_CollideStop(item, coll); + } + + if (Lara_Fallen(item, coll)) { + return; + } + + if (coll->side_mid.floor > STEP_L / 2 + && coll->side_mid.floor < STEPUP_HEIGHT) { + if (item->frame_num >= 964 && item->frame_num <= 993) { + item->anim_num = LA_WALK_DOWN_BACK_RIGHT; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else { + item->anim_num = LA_WALK_DOWN_BACK_LEFT; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + } + + if (!Lara_TestSlide(item, coll)) { + item->pos.y += coll->side_mid.floor; + } +} + +void __cdecl Lara_Col_StepRight(ITEM *item, COLL_INFO *coll) +{ + if (item->current_anim_state == LS_STEP_RIGHT) { + g_Lara.move_angle = item->rot.y + PHD_90; + } else { + g_Lara.move_angle = item->rot.y - PHD_90; + } + + item->gravity = 0; + item->fall_speed = 0; + if (g_Lara.water_status == LWS_WADE) { + coll->bad_pos = NO_BAD_POS; + } else { + coll->bad_pos = STEP_L / 2; + } + coll->slopes_are_pits = 1; + coll->slopes_are_walls = 1; + coll->bad_neg = -STEP_L / 2; + coll->bad_ceiling = 0; + + Lara_GetCollisionInfo(item, coll); + if (Lara_HitCeiling(item, coll)) { + return; + } + + if (Lara_DeflectEdge(item, coll)) { + Lara_CollideStop(item, coll); + } + + if (!Lara_Fallen(item, coll) && !Lara_TestSlide(item, coll)) { + item->pos.y += coll->side_mid.floor; + } +} + +void __cdecl Lara_Col_StepLeft(ITEM *item, COLL_INFO *coll) +{ + Lara_Col_StepRight(item, coll); +} + +void __cdecl Lara_Col_Slide(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y; + Lara_SlideSlope(item, coll); +} + +void __cdecl Lara_Col_BackJump(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y + PHD_180; + Lara_Col_Jumper(item, coll); +} + +void __cdecl Lara_Col_RightJump(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y + PHD_90; + Lara_Col_Jumper(item, coll); +} + +void __cdecl Lara_Col_LeftJump(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y - PHD_90; + Lara_Col_Jumper(item, coll); +} + +void __cdecl Lara_Col_UpJump(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = BAD_JUMP_CEILING; + if (item->speed < 0) { + coll->facing = g_Lara.move_angle + PHD_180; + } else { + coll->facing = g_Lara.move_angle; + } + + Collide_GetCollisionInfo( + coll, item->pos.x, item->pos.y, item->pos.z, item->room_num, 870); + if (Lara_TestHangJumpUp(item, coll)) { + return; + } + + Lara_SlideEdgeJump(item, coll); + if (coll->coll_type != COLL_NONE) { + item->speed = item->speed > 0 ? 2 : -2; + } else if (item->fall_speed < -70) { + if (g_Input & IN_FORWARD && item->speed < 5) { + item->speed++; + } else if (g_Input & IN_BACK && item->speed > -5) { + item->speed -= 2; + } + } + + if (item->fall_speed <= 0 || coll->side_mid.floor > 0) { + return; + } + + if (Lara_LandedBad(item, coll)) { + item->goal_anim_state = LS_DEATH; + } else { + item->goal_anim_state = LS_STOP; + } + item->gravity = 0; + item->fall_speed = 0; + item->pos.y += coll->side_mid.floor; +} + +void __cdecl Lara_Col_Fallback(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y + PHD_180; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = BAD_JUMP_CEILING; + + Lara_GetCollisionInfo(item, coll); + Lara_DeflectEdgeJump(item, coll); + + if (coll->side_mid.floor > 0 || item->fall_speed <= 0) { + return; + } + + if (Lara_LandedBad(item, coll)) { + item->goal_anim_state = LS_DEATH; + } else { + item->goal_anim_state = LS_STOP; + } + + item->gravity = 0; + item->fall_speed = 0; + item->pos.y += coll->side_mid.floor; +} + +void __cdecl Lara_Col_HangLeft(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y - PHD_90; + Lara_HangTest(item, coll); + g_Lara.move_angle = item->rot.y - PHD_90; +} + +void __cdecl Lara_Col_HangRight(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y + PHD_90; + Lara_HangTest(item, coll); + g_Lara.move_angle = item->rot.y + PHD_90; +} + +void __cdecl Lara_Col_SlideBack(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y + PHD_180; + Lara_SlideSlope(item, coll); +} + +void __cdecl Lara_Col_Null(ITEM *item, COLL_INFO *coll) +{ + Lara_Col_Default(item, coll); +} + +void __cdecl Lara_Col_Roll(ITEM *item, COLL_INFO *coll) +{ + item->gravity = 0; + item->fall_speed = 0; + g_Lara.move_angle = item->rot.y; + coll->slopes_are_walls = 1; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + + Lara_GetCollisionInfo(item, coll); + if (Lara_HitCeiling(item, coll) || Lara_TestSlide(item, coll) + || Lara_Fallen(item, coll)) { + return; + } + + Item_ShiftCol(item, coll); + item->pos.y += coll->side_mid.floor; +} + +void __cdecl Lara_Col_Roll2(ITEM *item, COLL_INFO *coll) +{ + item->gravity = 0; + item->fall_speed = 0; + g_Lara.move_angle = item->rot.y + PHD_180; + coll->slopes_are_walls = 1; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + + Lara_GetCollisionInfo(item, coll); + if (Lara_HitCeiling(item, coll) || Lara_TestSlide(item, coll)) { + return; + } + + if (coll->side_mid.floor > 200) { + item->anim_num = LA_FALL_BACK; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->current_anim_state = LS_FALL_BACK; + item->goal_anim_state = LS_FALL_BACK; + item->gravity = 1; + item->fall_speed = 0; + } else { + Item_ShiftCol(item, coll); + item->pos.y += coll->side_mid.floor; + } +} + +void __cdecl Lara_Col_SwanDive(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = BAD_JUMP_CEILING; + + Lara_GetCollisionInfo(item, coll); + Lara_DeflectEdgeJump(item, coll); + if (coll->side_mid.floor > 0 || item->fall_speed <= 0) { + return; + } + + item->goal_anim_state = LS_STOP; + item->gravity = 0; + item->fall_speed = 0; + item->pos.y += coll->side_mid.floor; +} + +void __cdecl Lara_Col_FastDive(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = BAD_JUMP_CEILING; + + Lara_GetCollisionInfo(item, coll); + Lara_DeflectEdgeJump(item, coll); + + if (coll->side_mid.floor > 0 || item->fall_speed <= 0) { + return; + } + + if (item->fall_speed > 133) { + item->goal_anim_state = LS_DEATH; + } else { + item->goal_anim_state = LS_STOP; + } + item->gravity = 0; + item->fall_speed = 0; + item->pos.y += coll->side_mid.floor; +} + +void __cdecl Lara_Col_Wade(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y; + coll->slopes_are_walls = 1; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + + Lara_GetCollisionInfo(item, coll); + if (Lara_HitCeiling(item, coll) || Lara_TestVault(item, coll)) { + return; + } + + if (Lara_DeflectEdge(item, coll)) { + item->rot.z = 0; + if (coll->side_front.type != COLL_NONE + && coll->side_front.floor < -STEP_L * 5 / 2) { + item->current_anim_state = LS_SPLAT; + if (item->frame_num >= 0 && item->frame_num <= 9) { + item->anim_num = LA_WALL_SMASH_LEFT; + item->frame_num = g_Anims[item->anim_num].frame_base; + return; + } + if (item->frame_num >= 10 && item->frame_num <= 21) { + item->anim_num = LA_WALL_SMASH_RIGHT; + item->frame_num = g_Anims[item->anim_num].frame_base; + return; + } + } + Lara_CollideStop(item, coll); + } + + if (Lara_Fallen(item, coll)) { + return; + } + + if (coll->side_mid.floor >= -STEPUP_HEIGHT + && coll->side_mid.floor < -STEP_L / 2) { + if (item->frame_num >= 3 && item->frame_num <= 14) { + item->anim_num = LA_RUN_UP_STEP_LEFT; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else { + item->anim_num = LA_RUN_UP_STEP_RIGHT; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + } + + if (Lara_TestSlide(item, coll)) { + return; + } + + item->pos.y += MIN(coll->side_mid.floor, 50); +} + +void __cdecl Lara_Col_Default(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y; + coll->slopes_are_walls = 1; + coll->slopes_are_pits = 1; + coll->bad_pos = STEPUP_HEIGHT; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + Lara_GetCollisionInfo(item, coll); +} + +void __cdecl Lara_Col_Jumper(ITEM *item, COLL_INFO *coll) +{ + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = BAD_JUMP_CEILING; + + Lara_GetCollisionInfo(item, coll); + Lara_DeflectEdgeJump(item, coll); + if (item->fall_speed <= 0 || coll->side_mid.floor > 0) { + return; + } + + if (Lara_LandedBad(item, coll)) { + item->goal_anim_state = LS_DEATH; + } else { + item->goal_anim_state = LS_STOP; + } + item->gravity = 0; + item->fall_speed = 0; + item->pos.y += coll->side_mid.floor; +} + +void __cdecl Lara_Col_ClimbLeft(ITEM *item, COLL_INFO *coll) +{ + if (Lara_CheckForLetGo(item, coll)) { + return; + } + g_Lara.move_angle = item->rot.y - PHD_90; + + int32_t shift; + int32_t result = Lara_TestClimbPos( + item, coll->radius, -(coll->radius + LARA_CLIMB_WIDTH_LEFT), + -LARA_CLIMB_HEIGHT, LARA_CLIMB_HEIGHT, &shift); + + Lara_DoClimbLeftRight(item, coll, result, shift); +} + +void __cdecl Lara_Col_ClimbRight(ITEM *item, COLL_INFO *coll) +{ + if (Lara_CheckForLetGo(item, coll)) { + return; + } + g_Lara.move_angle = item->rot.y + PHD_90; + + int32_t shift; + int32_t result = Lara_TestClimbPos( + item, coll->radius, coll->radius + LARA_CLIMB_WIDTH_RIGHT, + -LARA_CLIMB_HEIGHT, LARA_CLIMB_HEIGHT, &shift); + Lara_DoClimbLeftRight(item, coll, result, shift); +} + +void __cdecl Lara_Col_ClimbStance(ITEM *item, COLL_INFO *coll) +{ + if (Lara_CheckForLetGo(item, coll) || item->anim_num != LA_LADDER_IDLE) { + return; + } + + if (g_Input & IN_FORWARD) { + if (item->goal_anim_state == LS_NULL) { + return; + } + + item->goal_anim_state = LS_CLIMB_STANCE; + + int32_t shift_r = 0; + int32_t ledge_r = 0; + int32_t result_r = Lara_TestClimbUpPos( + item, coll->radius, coll->radius + LARA_CLIMB_WIDTH_RIGHT, &shift_r, + &ledge_r); + + int32_t shift_l = 0; + int32_t ledge_l = 0; + int32_t result_l = Lara_TestClimbUpPos( + item, coll->radius, -(coll->radius + LARA_CLIMB_WIDTH_LEFT), + &shift_l, &ledge_l); + + if (!result_r || !result_l) { + return; + } + + if (result_r < 0 || result_l < 0) { + if (ABS(ledge_l - ledge_r) > 120) { + return; + } + item->goal_anim_state = LS_NULL; + item->pos.y += (ledge_l + ledge_r) / 2 - STEP_L; + return; + } + + int32_t shift = shift_l; + if (shift_r) { + if (shift_l) { + if ((shift_r < 0) != (shift_l < 0)) { + return; + } + if (shift_r > 0 && shift_r > shift_l) { + shift = shift_r; + } else if (shift_r < 0 && shift_r < shift_l) { + shift = shift_r; + } + } else { + shift = shift_r; + } + } + + item->goal_anim_state = LS_CLIMBING; + item->pos.y += shift; + } else if (g_Input & IN_BACK) { + if (item->goal_anim_state == LS_HANG) { + return; + } + + item->goal_anim_state = LS_CLIMB_STANCE; + item->pos.y += STEP_L; + + int32_t shift_r = 0; + int32_t result_r = Lara_TestClimbPos( + item, coll->radius, coll->radius + LARA_CLIMB_WIDTH_RIGHT, + -LARA_CLIMB_HEIGHT, LARA_CLIMB_HEIGHT, &shift_r); + + int32_t shift_l = 0; + int32_t result_l = Lara_TestClimbPos( + item, coll->radius, -(coll->radius + LARA_CLIMB_WIDTH_LEFT), + -LARA_CLIMB_HEIGHT, LARA_CLIMB_HEIGHT, &shift_l); + + item->pos.y -= STEP_L; + if (!result_r || !result_l) { + return; + } + + int32_t shift = shift_l; + if (shift_r && shift_l) { + if ((shift_r < 0) != (shift_l < 0)) { + return; + } + if (shift_r < 0 && shift_r < shift_l) { + shift = shift_r; + } else if (shift_r > 0 && shift_r > shift_l) { + shift = shift_r; + } + } + + if (result_r == 1 && result_l == 1) { + item->goal_anim_state = LS_CLIMB_DOWN; + item->pos.y += shift; + } else { + item->goal_anim_state = LS_HANG; + } + } +} + +void __cdecl Lara_Col_Climbing(ITEM *item, COLL_INFO *coll) +{ + if (Lara_CheckForLetGo(item, coll) || item->anim_num != LA_LADDER_UP) { + return; + } + + int32_t yshift; + int32_t frame_rel = item->frame_num - g_Anims[item->anim_num].frame_base; + if (frame_rel == 0) { + yshift = 0; + } else if (frame_rel == 28 || frame_rel == 29) { + yshift = -STEP_L; + } else if (frame_rel == 57) { + yshift = -STEP_L * 2; + } else { + return; + } + + item->pos.y += yshift - STEP_L; + + int32_t shift_r = 0; + int32_t ledge_r = 0; + int32_t result_r = Lara_TestClimbUpPos( + item, coll->radius, coll->radius + LARA_CLIMB_WIDTH_RIGHT, &shift_r, + &ledge_r); + + int32_t shift_l = 0; + int32_t ledge_l = 0; + int32_t result_l = Lara_TestClimbUpPos( + item, coll->radius, -(coll->radius + LARA_CLIMB_WIDTH_LEFT), &shift_l, + &ledge_l); + + item->pos.y += STEP_L; + + if (!result_r || !result_l || !(g_Input & IN_FORWARD)) { + item->goal_anim_state = LS_CLIMB_STANCE; + if (yshift) { + Lara_Animate(item); + } + return; + } + + if (result_r < 0 || result_l < 0) { + item->goal_anim_state = LS_CLIMB_STANCE; + Lara_Animate(item); + if (ABS(ledge_l - ledge_r) <= 120) { + item->goal_anim_state = LS_NULL; + item->pos.y += (ledge_r + ledge_l) / 2 - STEP_L; + } + return; + } + + item->goal_anim_state = LS_CLIMBING; + item->pos.y -= yshift; +} + +void __cdecl Lara_Col_ClimbDown(ITEM *item, COLL_INFO *coll) +{ + if (Lara_CheckForLetGo(item, coll) || item->anim_num != LA_LADDER_DOWN) { + return; + } + + int32_t yshift; + int32_t frame_rel = item->frame_num - g_Anims[item->anim_num].frame_base; + if (frame_rel == 0) { + yshift = 0; + } else if (frame_rel >= 28 && frame_rel <= 29) { + yshift = STEP_L; + } else if (frame_rel == 57) { + yshift = STEP_L * 2; + } else { + return; + } + + item->pos.y += yshift + STEP_L; + + int32_t shift_r = 0; + int32_t result_r = Lara_TestClimbPos( + item, coll->radius, coll->radius + LARA_CLIMB_WIDTH_RIGHT, + -LARA_CLIMB_HEIGHT, LARA_CLIMB_HEIGHT, &shift_r); + + int32_t shift_l = 0; + int32_t result_l = Lara_TestClimbPos( + item, coll->radius, -(coll->radius + LARA_CLIMB_WIDTH_LEFT), + -LARA_CLIMB_HEIGHT, LARA_CLIMB_HEIGHT, &shift_l); + + item->pos.y -= STEP_L; + + if (!result_r || !result_l || !(g_Input & IN_BACK)) { + item->goal_anim_state = LS_CLIMB_STANCE; + if (yshift) { + Lara_Animate(item); + } + return; + } + +#if 0 + int32_t shift = shift_l; +#endif + if (shift_r && shift_l) { + if ((shift_r < 0) != (shift_l < 0)) { + item->goal_anim_state = LS_CLIMB_STANCE; + Lara_Animate(item); + return; + } +#if 0 + if (shift_r < 0 && shift_r < shift_l) { + shift = shift_r; + } else if (shift_r > 0 && shift_r > shift_l) { + shift = shift_r; + } +#endif + } + + if (result_r == -1 || result_l == -1) { + item->anim_num = LA_LADDER_IDLE; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->current_anim_state = LS_CLIMB_STANCE; + item->goal_anim_state = LS_HANG; + Lara_Animate(item); + return; + } + + item->goal_anim_state = LS_CLIMB_DOWN; + item->pos.y -= yshift; +} + +void __cdecl Lara_Col_SurfSwim(ITEM *item, COLL_INFO *coll) +{ + coll->bad_neg = -STEPUP_HEIGHT; + g_Lara.move_angle = item->rot.y; + Lara_SurfaceCollision(item, coll); + Lara_TestWaterClimbOut(item, coll); +} + +void __cdecl Lara_Col_SurfBack(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y + PHD_180; + Lara_SurfaceCollision(item, coll); +} + +void __cdecl Lara_Col_SurfLeft(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y - PHD_90; + Lara_SurfaceCollision(item, coll); +} + +void __cdecl Lara_Col_SurfRight(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y + PHD_90; + Lara_SurfaceCollision(item, coll); +} + +void __cdecl Lara_Col_SurfTread(ITEM *item, COLL_INFO *coll) +{ + g_Lara.move_angle = item->rot.y; + Lara_SurfaceCollision(item, coll); +} + +void __cdecl Lara_Col_Swim(ITEM *item, COLL_INFO *coll) +{ + Lara_SwimCollision(item, coll); +} + +void __cdecl Lara_Col_UWDeath(ITEM *item, COLL_INFO *coll) +{ + item->hit_points = -1; + g_Lara.air = -1; + g_Lara.gun_status = LGS_HANDS_BUSY; + int32_t wh = Room_GetWaterHeight( + item->pos.x, item->pos.y, item->pos.z, item->room_num); + if (wh != NO_HEIGHT && wh < item->pos.y - 100) { + item->pos.y -= 5; + } + Lara_SwimCollision(item, coll); +} diff --git a/src/tr2/game/lara/col.h b/src/tr2/game/lara/col.h new file mode 100644 index 000000000..d4d5b4565 --- /dev/null +++ b/src/tr2/game/lara/col.h @@ -0,0 +1,57 @@ +#pragma once + +#include "global/types.h" + +// TODO: make static +void __cdecl Lara_CollideStop(ITEM *item, const COLL_INFO *coll); +bool __cdecl Lara_Fallen(ITEM *item, const COLL_INFO *coll); +bool __cdecl Lara_TestWaterClimbOut(ITEM *item, const COLL_INFO *coll); +bool __cdecl Lara_TestWaterStepOut(ITEM *item, const COLL_INFO *coll); +void __cdecl Lara_SurfaceCollision(ITEM *item, COLL_INFO *coll); + +void __cdecl Lara_Col_Walk(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Run(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Stop(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_ForwardJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_FastBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_TurnRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_TurnLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Death(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_FastFall(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Hang(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Reach(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Splat(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Land(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Compress(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Back(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_StepRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_StepLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Slide(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_BackJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_RightJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_LeftJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_UpJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Fallback(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_HangLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_HangRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SlideBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Null(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Roll(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Roll2(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SwanDive(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_FastDive(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Wade(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Default(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Jumper(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_ClimbLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_ClimbRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_ClimbStance(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Climbing(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_ClimbDown(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfSwim(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_SurfTread(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_Swim(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_Col_UWDeath(ITEM *item, COLL_INFO *coll); diff --git a/src/tr2/game/lara/common.c b/src/tr2/game/lara/common.c new file mode 100644 index 000000000..d1799d0de --- /dev/null +++ b/src/tr2/game/lara/common.c @@ -0,0 +1,13 @@ +#include "global/vars.h" + +#include + +LARA_INFO *Lara_GetLaraInfo(void) +{ + return &g_Lara; +} + +ITEM *Lara_GetItem(void) +{ + return g_LaraItem; +} diff --git a/src/tr2/game/lara/control.c b/src/tr2/game/lara/control.c new file mode 100644 index 000000000..5ac6aa929 --- /dev/null +++ b/src/tr2/game/lara/control.c @@ -0,0 +1,1017 @@ +#include "game/lara/control.h" + +#include "game/creature.h" +#include "game/gun/gun.h" +#include "game/input.h" +#include "game/inventory/backpack.h" +#include "game/items.h" +#include "game/lara/cheat.h" +#include "game/lara/look.h" +#include "game/lara/misc.h" +#include "game/math.h" +#include "game/music.h" +#include "game/room.h" +#include "game/sound.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +static int32_t m_OpenDoorsCheatCooldown = 0; + +void __cdecl Lara_HandleAboveWater(ITEM *const item, COLL_INFO *const coll) +{ + coll->old.x = item->pos.x; + coll->old.y = item->pos.y; + coll->old.z = item->pos.z; + coll->old_anim_state = item->current_anim_state; + coll->old_anim_num = item->anim_num; + coll->old_frame_num = item->frame_num; + coll->radius = LARA_RADIUS; + coll->trigger = NULL; + + coll->slopes_are_walls = 0; + coll->slopes_are_pits = 0; + coll->lava_is_pit = 0; + coll->enable_baddie_push = 1; + coll->enable_spaz = 1; + + if ((g_Input & IN_LOOK) && !g_Lara.extra_anim && g_Lara.look) { + Lara_LookLeftRight(); + } else { + Lara_ResetLook(); + } + g_Lara.look = 1; + + if (g_Lara.skidoo != NO_ITEM) { + if (g_Items[g_Lara.skidoo].object_id == O_SKIDOO_FAST) { + if (SkidooControl()) { + return; + } + } else { + Gun_Control(); + return; + } + } + + if (g_Lara.extra_anim) { + g_ExtraControlRoutines[item->current_anim_state](item, coll); + } else { + g_LaraControlRoutines[item->current_anim_state](item, coll); + } + + if (item->rot.z < -LARA_LEAN_UNDO) { + item->rot.z += LARA_LEAN_UNDO; + } else if (item->rot.z > LARA_LEAN_UNDO) { + item->rot.z -= LARA_LEAN_UNDO; + } else { + item->rot.z = 0; + } + + if (g_Lara.turn_rate < -LARA_TURN_UNDO) { + g_Lara.turn_rate += LARA_TURN_UNDO; + } else if (g_Lara.turn_rate > LARA_TURN_UNDO) { + g_Lara.turn_rate -= LARA_TURN_UNDO; + } else { + g_Lara.turn_rate = 0; + } + item->rot.y += g_Lara.turn_rate; + + Lara_Animate(item); + + if (!g_Lara.extra_anim && g_Lara.water_status != LWS_CHEAT) { + Lara_BaddieCollision(item, coll); + if (g_Lara.skidoo == NO_ITEM) { + g_LaraCollisionRoutines[item->current_anim_state](item, coll); + } + } + + Item_UpdateRoom(item, -LARA_HEIGHT / 2); + Gun_Control(); + Room_TestTriggers(coll->trigger, 0); +} + +void __cdecl Lara_HandleSurface(ITEM *const item, COLL_INFO *const coll) +{ + g_Camera.target_elevation = -22 * PHD_DEGREE; + + coll->old.x = item->pos.x; + coll->old.y = item->pos.y; + coll->old.z = item->pos.z; + coll->radius = LARA_RADIUS; + coll->trigger = NULL; + + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEP_L / 2; + coll->bad_ceiling = 100; + + coll->slopes_are_walls = 0; + coll->slopes_are_pits = 0; + coll->lava_is_pit = 0; + coll->enable_baddie_push = 0; + coll->enable_spaz = 0; + + if ((g_Input & IN_LOOK) && g_Lara.look) { + Lara_LookLeftRight(); + } else { + Lara_ResetLook(); + } + g_Lara.look = 1; + + g_LaraControlRoutines[item->current_anim_state](item, coll); + + if (item->rot.z > LARA_LEAN_UNDO_SURF) { + item->rot.z -= LARA_LEAN_UNDO_SURF; + } else if (item->rot.z < -LARA_LEAN_UNDO_SURF) { + item->rot.z += LARA_LEAN_UNDO_SURF; + } else { + item->rot.z = 0; + } + + if (g_Lara.current_active && g_Lara.water_status != LWS_CHEAT) { + Lara_WaterCurrent(coll); + } + + Lara_Animate(item); + item->pos.x += + (item->fall_speed * Math_Sin(g_Lara.move_angle)) >> (W2V_SHIFT + 2); + item->pos.z += + (item->fall_speed * Math_Cos(g_Lara.move_angle)) >> (W2V_SHIFT + 2); + + Lara_BaddieCollision(item, coll); + + if (g_Lara.skidoo == NO_ITEM) { + g_LaraCollisionRoutines[item->current_anim_state](item, coll); + } + + Item_UpdateRoom(item, 100); + Gun_Control(); + Room_TestTriggers(coll->trigger, 0); +} + +void __cdecl Lara_HandleUnderwater(ITEM *const item, COLL_INFO *const coll) +{ + coll->old.x = item->pos.x; + coll->old.y = item->pos.y; + coll->old.z = item->pos.z; + coll->radius = LARA_RADIUS_UW; + coll->trigger = NULL; + + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -LARA_HEIGHT_UW; + coll->bad_ceiling = LARA_HEIGHT_UW; + + coll->slopes_are_walls = 0; + coll->slopes_are_pits = 0; + coll->lava_is_pit = 0; + coll->enable_baddie_push = 1; + coll->enable_spaz = 0; + + if ((g_Input & IN_LOOK) && g_Lara.look) { + Lara_LookLeftRight(); + } else { + Lara_ResetLook(); + } + g_Lara.look = 1; + + if (g_Lara.extra_anim) { + g_ExtraControlRoutines[item->current_anim_state](item, coll); + } else { + g_LaraControlRoutines[item->current_anim_state](item, coll); + } + + if (item->rot.z > LARA_LEAN_UNDO_UW) { + item->rot.z -= LARA_LEAN_UNDO_UW; + } else if (item->rot.z < -LARA_LEAN_UNDO_UW) { + item->rot.z += LARA_LEAN_UNDO_UW; + } else { + item->rot.z = 0; + } + + CLAMP(item->rot.x, -85 * PHD_DEGREE, 85 * PHD_DEGREE); + CLAMP(item->rot.z, -LARA_LEAN_MAX_UW, LARA_LEAN_MAX_UW); + + if (g_Lara.turn_rate < -LARA_TURN_UNDO) { + g_Lara.turn_rate += LARA_TURN_UNDO; + } else if (g_Lara.turn_rate > LARA_TURN_UNDO) { + g_Lara.turn_rate -= LARA_TURN_UNDO; + } else { + g_Lara.turn_rate = 0; + } + + item->rot.y += g_Lara.turn_rate; + if (g_Lara.current_active && g_Lara.water_status != LWS_CHEAT) { + Lara_WaterCurrent(coll); + } + + Lara_Animate(item); + item->pos.y -= + (item->fall_speed * Math_Sin(item->rot.x)) >> (W2V_SHIFT + 2); + item->pos.x += + (Math_Cos(item->rot.x) + * ((item->fall_speed * Math_Sin(item->rot.y)) >> (W2V_SHIFT + 2))) + >> W2V_SHIFT; + item->pos.z += + (Math_Cos(item->rot.x) + * ((item->fall_speed * Math_Cos(item->rot.y)) >> (W2V_SHIFT + 2))) + >> W2V_SHIFT; + + if (g_Lara.water_status != LWS_CHEAT && !g_Lara.extra_anim) { + Lara_BaddieCollision(item, coll); + } + + if (g_Lara.water_status == LWS_CHEAT) { + if (m_OpenDoorsCheatCooldown) { + m_OpenDoorsCheatCooldown--; + } else if (g_InputDB & IN_DRAW) { + m_OpenDoorsCheatCooldown = FRAMES_PER_SECOND; + Lara_Cheat_OpenNearestDoor(); + } + } + + if (!g_Lara.extra_anim) { + g_LaraCollisionRoutines[item->current_anim_state](item, coll); + } + + Item_UpdateRoom(item, 0); + Gun_Control(); + Room_TestTriggers(coll->trigger, 0); +} + +void __cdecl Lara_Control(const int16_t item_num) +{ + ITEM *const item = g_LaraItem; + + const bool room_submerged = g_Rooms[item->room_num].flags & RF_UNDERWATER; + const int32_t water_depth = Lara_GetWaterDepth( + item->pos.x, item->pos.y, item->pos.z, item->room_num); + const int32_t water_height = Room_GetWaterHeight( + item->pos.x, item->pos.y, item->pos.z, item->room_num); + const int32_t water_height_diff = + water_height == NO_HEIGHT ? NO_HEIGHT : item->pos.y - water_height; + + g_Lara.water_surface_dist = -water_height_diff; + + if (g_Lara.skidoo == NO_ITEM && !g_Lara.extra_anim) { + switch (g_Lara.water_status) { + case LWS_ABOVE_WATER: + if (water_height_diff == NO_HEIGHT + || water_height_diff < LARA_WADE_DEPTH) { + break; + } + + if (water_depth <= LARA_SWIM_DEPTH - STEP_L) { + if (water_height_diff > LARA_WADE_DEPTH) { + g_Lara.water_status = LWS_WADE; + if (!item->gravity) { + item->goal_anim_state = LS_STOP; + } + } + } else if (room_submerged) { + g_Lara.air = LARA_MAX_AIR; + g_Lara.water_status = LWS_UNDERWATER; + item->gravity = 0; + item->pos.y += 100; + Item_UpdateRoom(item, 0); + Sound_StopEffect(SFX_LARA_FALL); + if (item->current_anim_state == LS_SWAN_DIVE) { + item->rot.x = -45 * PHD_DEGREE; + item->goal_anim_state = LS_DIVE; + Lara_Animate(item); + item->fall_speed *= 2; + } else if (item->current_anim_state == LS_FAST_DIVE) { + item->rot.x = -85 * PHD_DEGREE; + item->goal_anim_state = LS_DIVE; + Lara_Animate(item); + item->fall_speed *= 2; + } else { + item->rot.x = -45 * PHD_DEGREE; + item->anim_num = LA_FREEFALL_TO_UNDERWATER; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->current_anim_state = LS_DIVE; + item->goal_anim_state = LS_SWIM; + item->fall_speed = item->fall_speed * 3 / 2; + } + g_Lara.torso_y_rot = 0; + g_Lara.torso_x_rot = 0; + g_Lara.head_y_rot = 0; + g_Lara.head_x_rot = 0; + Splash(item); + } + break; + + case LWS_UNDERWATER: + if (room_submerged) { + break; + } + + if (water_depth == NO_HEIGHT || ABS(water_height_diff) >= STEP_L) { + g_Lara.water_status = LWS_ABOVE_WATER; + item->anim_num = LA_FALL_START; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->goal_anim_state = LS_FORWARD_JUMP; + item->current_anim_state = LS_FORWARD_JUMP; + item->gravity = 1; + item->speed = item->fall_speed / 4; + item->fall_speed = 0; + item->rot.x = 0; + item->rot.z = 0; + g_Lara.torso_y_rot = 0; + g_Lara.torso_x_rot = 0; + g_Lara.head_y_rot = 0; + g_Lara.head_x_rot = 0; + } else { + g_Lara.water_status = LWS_SURFACE; + item->anim_num = LA_UNDERWATER_TO_ONWATER; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->goal_anim_state = LS_SURF_TREAD; + item->current_anim_state = LS_SURF_TREAD; + item->fall_speed = 0; + item->pos.y += 1 - water_height_diff; + item->rot.z = 0; + item->rot.x = 0; + g_Lara.dive_count = 11; + g_Lara.torso_y_rot = 0; + g_Lara.torso_x_rot = 0; + g_Lara.head_y_rot = 0; + g_Lara.head_x_rot = 0; + Item_UpdateRoom(item, -381); + Sound_Effect(SFX_LARA_BREATH, &item->pos, SPM_ALWAYS); + } + break; + + case LWS_SURFACE: + if (room_submerged) { + break; + } + + if (water_height_diff <= LARA_WADE_DEPTH) { + g_Lara.water_status = LWS_ABOVE_WATER; + item->anim_num = LA_FALL_START; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->goal_anim_state = LS_FORWARD_JUMP; + item->current_anim_state = LS_FORWARD_JUMP; + item->gravity = 1; + item->speed = item->fall_speed / 4; + } else { + g_Lara.water_status = LWS_WADE; + item->anim_num = LA_STAND_IDLE; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->current_anim_state = LS_STOP; + item->goal_anim_state = LS_WADE; + Item_Animate(item); + item->fall_speed = 0; + } + item->rot.x = 0; + item->rot.z = 0; + g_Lara.torso_y_rot = 0; + g_Lara.torso_x_rot = 0; + g_Lara.head_y_rot = 0; + g_Lara.head_x_rot = 0; + break; + + case LWS_WADE: + g_Camera.target_elevation = -22 * PHD_DEGREE; + + if (water_height_diff < LARA_WADE_DEPTH) { + g_Lara.water_status = LWS_ABOVE_WATER; + if (item->current_anim_state == LS_WADE) { + item->goal_anim_state = LS_RUN; + } + } else if (water_height_diff > 730) { + g_Lara.water_status = LWS_SURFACE; + item->pos.y += 1 - water_height_diff; + + switch (item->current_anim_state) { + case LS_BACK: + item->goal_anim_state = LS_SURF_BACK; + item->anim_num = LA_ONWATER_IDLE_TO_SWIM_BACK; + break; + + case LS_STEP_RIGHT: + item->goal_anim_state = LS_SURF_RIGHT; + item->anim_num = LA_ONWATER_SWIM_RIGHT; + break; + + case LS_STEP_LEFT: + item->goal_anim_state = LS_SURF_LEFT; + item->anim_num = LA_ONWATER_SWIM_LEFT; + break; + + default: + item->goal_anim_state = LS_SURF_SWIM; + item->anim_num = LA_ONWATER_SWIM_FORWARD; + break; + } + item->current_anim_state = item->goal_anim_state; + item->frame_num = g_Anims[item->anim_num].frame_base; + + item->rot.z = 0; + item->rot.x = 0; + item->gravity = 0; + item->fall_speed = 0; + g_Lara.dive_count = 0; + g_Lara.torso_y_rot = 0; + g_Lara.torso_x_rot = 0; + g_Lara.head_y_rot = 0; + g_Lara.head_x_rot = 0; + Item_UpdateRoom(item, -LARA_HEIGHT / 2); + } + break; + + default: + break; + } + } + + if (item->hit_points <= 0) { + item->hit_points = -1; + if (!g_Lara.death_timer) { + Music_Stop(); + } + g_Lara.death_timer++; + if (item->flags & IF_ONE_SHOT) { + g_Lara.death_timer++; + return; + } + } else if (g_GF_NoFloor && item->pos.y >= g_GF_NoFloor) { + item->hit_points = -1; + g_Lara.death_timer = 9 * FRAMES_PER_SECOND; + } + + COLL_INFO coll; + switch (g_Lara.water_status) { + case LWS_ABOVE_WATER: + case LWS_WADE: + g_Lara.air = LARA_MAX_AIR; + Lara_HandleAboveWater(item, &coll); + break; + + case LWS_UNDERWATER: + if (item->hit_points >= 0) { + g_Lara.air--; + if (g_Lara.air < 0) { + g_Lara.air = -1; + item->hit_points -= 5; + } + } + Lara_HandleUnderwater(item, &coll); + break; + + case LWS_SURFACE: + if (item->hit_points >= 0) { + g_Lara.air += 10; + CLAMPG(g_Lara.air, LARA_MAX_AIR); + } + Lara_HandleSurface(item, &coll); + break; + + case LWS_CHEAT: + // TODO: make Lara immune to lava and flames + item->hit_points = LARA_MAX_HITPOINTS; + g_Lara.death_timer = 0; + Lara_HandleUnderwater(item, &coll); + if (g_Input & IN_SLOW && !(g_Input & IN_LOOK)) { + Lara_Cheat_ExitFlyMode(); + } + break; + + default: + break; + } + + g_SaveGame.statistics.distance += Math_Sqrt( + SQUARE(item->pos.z - g_Lara.last_pos.z) + + SQUARE(item->pos.y - g_Lara.last_pos.y) + + SQUARE(item->pos.x - g_Lara.last_pos.x)); + + g_Lara.last_pos = item->pos; +} + +void __cdecl Lara_ControlExtra(const int16_t item_num) +{ + Item_Animate(&g_Items[item_num]); +} + +void __cdecl Lara_Animate(ITEM *const item) +{ + item->frame_num++; + + const ANIM *anim = &g_Anims[item->anim_num]; + if (anim->num_changes > 0 && Item_GetAnimChange(item, anim)) { + anim = &g_Anims[item->anim_num]; + item->current_anim_state = anim->current_anim_state; + } + + if (item->frame_num > anim->frame_end) { + if (anim->num_commands > 0) { + const int16_t *cmd_ptr = &g_AnimCommands[anim->command_idx]; + for (int32_t i = 0; i < anim->num_commands; i++) { + const int16_t cmd = *cmd_ptr++; + + switch (cmd) { + case AC_MOVE_ORIGIN: + Item_Translate(item, cmd_ptr[0], cmd_ptr[1], cmd_ptr[2]); + cmd_ptr += 3; + break; + + case AC_JUMP_VELOCITY: + item->fall_speed = *cmd_ptr++; + item->speed = *cmd_ptr++; + item->gravity = 1; + + if (g_Lara.calc_fallspeed) { + item->fall_speed = g_Lara.calc_fallspeed; + g_Lara.calc_fallspeed = 0; + } + break; + + case AC_ATTACK_READY: + if (g_Lara.gun_status != LGS_SPECIAL) { + g_Lara.gun_status = LGS_ARMLESS; + } + break; + + case AC_SOUND_FX: + case AC_EFFECT: + cmd_ptr += 2; + break; + + default: + break; + } + } + } + + item->anim_num = anim->jump_anim_num; + item->frame_num = anim->jump_frame_num; + anim = &g_Anims[item->anim_num]; + item->current_anim_state = anim->current_anim_state; + } + + if (anim->num_commands > 0) { + const int16_t *cmd_ptr = &g_AnimCommands[anim->command_idx]; + for (int32_t i = 0; i < anim->num_commands; i++) { + const int16_t cmd = *cmd_ptr++; + + switch (cmd) { + case AC_MOVE_ORIGIN: + cmd_ptr += 3; + break; + + case AC_JUMP_VELOCITY: + cmd_ptr += 2; + break; + + case AC_SOUND_FX: { + const int32_t frame = cmd_ptr[0]; + const SOUND_EFFECT_ID sound_id = cmd_ptr[1] & 0x3FFF; + const ANIM_COMMAND_ENVIRONMENT type = + (cmd_ptr[1] & 0xC000) >> 14; + cmd_ptr += 2; + + if (item->frame_num != frame) { + break; + } + + if (type == ACE_ALL + || (type == ACE_LAND + && (g_Lara.water_surface_dist >= 0 + || g_Lara.water_surface_dist == NO_HEIGHT)) + || (type == ACE_WATER && g_Lara.water_surface_dist < 0 + && g_Lara.water_surface_dist != NO_HEIGHT)) { + Sound_Effect(sound_id, &item->pos, SPM_ALWAYS); + } + break; + } + + case AC_EFFECT: + const int32_t frame = cmd_ptr[0]; + const int32_t fx_func_idx = cmd_ptr[1] & 0x3FFF; + const ANIM_COMMAND_ENVIRONMENT type = + (cmd_ptr[1] & 0xC000) >> 14; + cmd_ptr += 2; + + if (item->frame_num != frame) { + break; + } + + if (type == ACE_ALL + || (type == ACE_LAND + && (g_Lara.water_surface_dist >= 0 + || g_Lara.water_surface_dist == NO_HEIGHT)) + || (type == ACE_WATER && g_Lara.water_surface_dist < 0)) { + g_EffectRoutines[fx_func_idx](item); + } + break; + + default: + break; + } + } + } + + if (item->gravity) { + int32_t speed = anim->velocity + + anim->acceleration * (item->frame_num - anim->frame_base - 1); + item->speed -= (int16_t)(speed >> 16); + speed += anim->acceleration; + item->speed += (int16_t)(speed >> 16); + + item->fall_speed += item->fall_speed < FAST_FALL_SPEED ? GRAVITY : 1; + item->pos.y += item->fall_speed; + } else { + int32_t speed = anim->velocity; + if (anim->acceleration) { + speed += anim->acceleration * (item->frame_num - anim->frame_base); + } + item->speed = (int16_t)(speed >> 16); + } + + item->pos.x += (item->speed * Math_Sin(g_Lara.move_angle)) >> W2V_SHIFT; + item->pos.z += (item->speed * Math_Cos(g_Lara.move_angle)) >> W2V_SHIFT; +} + +void __cdecl Lara_UseItem(const GAME_OBJECT_ID object_id) +{ + ITEM *const item = g_LaraItem; + + switch (object_id) { + case O_PISTOL_ITEM: + case O_PISTOL_OPTION: + g_Lara.request_gun_type = LGT_PISTOLS; + break; + + case O_SHOTGUN_ITEM: + case O_SHOTGUN_OPTION: + g_Lara.request_gun_type = LGT_SHOTGUN; + break; + + case O_MAGNUM_ITEM: + case O_MAGNUM_OPTION: + g_Lara.request_gun_type = LGT_MAGNUMS; + break; + + case O_UZI_ITEM: + case O_UZI_OPTION: + g_Lara.request_gun_type = LGT_UZIS; + break; + + case O_HARPOON_ITEM: + case O_HARPOON_OPTION: + g_Lara.request_gun_type = LGT_HARPOON; + break; + + case O_M16_ITEM: + case O_M16_OPTION: + g_Lara.request_gun_type = LGT_M16; + break; + + case O_GRENADE_ITEM: + case O_GRENADE_OPTION: + g_Lara.request_gun_type = LGT_GRENADE; + break; + + case O_SMALL_MEDIPACK_ITEM: + case O_SMALL_MEDIPACK_OPTION: + if (item->hit_points > 0 && item->hit_points < LARA_MAX_HITPOINTS) { + item->hit_points += LARA_MAX_HITPOINTS / 2; + CLAMPG(item->hit_points, LARA_MAX_HITPOINTS); + Inv_RemoveItem(O_SMALL_MEDIPACK_ITEM); + Sound_Effect(SFX_MENU_MEDI, NULL, SPM_ALWAYS); + g_SaveGame.statistics.medipacks++; + } + break; + + case O_LARGE_MEDIPACK_ITEM: + case O_LARGE_MEDIPACK_OPTION: + if (item->hit_points > 0 && item->hit_points < LARA_MAX_HITPOINTS) { + item->hit_points = LARA_MAX_HITPOINTS; + Inv_RemoveItem(O_LARGE_MEDIPACK_ITEM); + Sound_Effect(SFX_MENU_MEDI, NULL, SPM_ALWAYS); + g_SaveGame.statistics.medipacks += 2; + } + break; + + case O_FLARES_ITEM: + case O_FLARES_OPTION: + g_Lara.request_gun_type = LGT_FLARE; + break; + + default: + break; + } +} + +void __cdecl Lara_InitialiseLoad(const int16_t item_num) +{ + g_Lara.item_num = item_num; + g_LaraItem = &g_Items[item_num]; +} + +void __cdecl Lara_Initialise(const GAMEFLOW_LEVEL_TYPE type) +{ + ITEM *const item = g_LaraItem; + + item->data = &g_Lara; + item->collidable = 0; + item->hit_points = LARA_MAX_HITPOINTS; + + g_Lara.hit_direction = -1; + g_Lara.skidoo = NO_ITEM; + g_Lara.weapon_item = NO_ITEM; + g_Lara.calc_fallspeed = 0; + g_Lara.climb_status = 0; + g_Lara.pose_count = 0; + g_Lara.hit_frame = 0; + g_Lara.air = LARA_MAX_AIR; + g_Lara.dive_count = 0; + g_Lara.death_timer = 0; + g_Lara.current_active = 0; + g_Lara.spaz_effect_count = 0; + g_Lara.flare_age = 0; + g_Lara.back_gun = 0; + g_Lara.flare_frame = 0; + g_Lara.flare_control_left = 0; + g_Lara.flare_control_right = 0; + g_Lara.extra_anim = 0; + g_Lara.look = 1; + g_Lara.burn = 0; + g_Lara.water_surface_dist = 100; + g_Lara.last_pos = item->pos; + g_Lara.spaz_effect = NULL; + g_Lara.mesh_effects = 0; + g_Lara.target = NULL; + g_Lara.turn_rate = 0; + g_Lara.move_angle = 0; + g_Lara.head_x_rot = 0; + g_Lara.head_y_rot = 0; + g_Lara.head_z_rot = 0; + g_Lara.torso_x_rot = 0; + g_Lara.torso_y_rot = 0; + g_Lara.torso_z_rot = 0; + g_Lara.left_arm.flash_gun = 0; + g_Lara.right_arm.flash_gun = 0; + g_Lara.left_arm.lock = 0; + g_Lara.right_arm.lock = 0; + g_Lara.creature = NULL; + + if (type == GFL_NORMAL && g_GF_LaraStartAnim) { + g_Lara.water_status = LWS_ABOVE_WATER; + g_Lara.gun_status = LGS_HANDS_BUSY; + item->anim_num = g_Objects[O_LARA_EXTRA].anim_idx; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->current_anim_state = LA_EXTRA_BREATH; + item->goal_anim_state = g_GF_LaraStartAnim; + Lara_Animate(item); + g_Lara.extra_anim = 1; + g_Camera.type = CAM_CINEMATIC; + g_CineFrameIdx = 0; + g_CinePos.pos = item->pos; + g_CinePos.rot = item->rot; + } else if ((g_Rooms[item->room_num].flags & RF_UNDERWATER)) { + g_Lara.water_status = LWS_UNDERWATER; + item->fall_speed = 0; + item->goal_anim_state = LS_TREAD; + item->current_anim_state = LS_TREAD; + item->anim_num = LA_UNDERWATER_IDLE; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else { + g_Lara.water_status = LWS_ABOVE_WATER; + item->goal_anim_state = LS_STOP; + item->current_anim_state = LS_STOP; + item->anim_num = LA_STAND_STILL; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + + if (type == GFL_CUTSCENE) { + for (int32_t i = 0; i < LM_NUMBER_OF; i++) { + g_Lara.mesh_ptrs[i] = g_Meshes[g_Objects[O_LARA].mesh_idx + i]; + } + + g_Lara.mesh_ptrs[LM_THIGH_L] = + g_Meshes[g_Objects[O_LARA_PISTOLS].mesh_idx + LM_THIGH_L]; + g_Lara.mesh_ptrs[LM_THIGH_R] = + g_Meshes[g_Objects[O_LARA_PISTOLS].mesh_idx + LM_THIGH_R]; + g_Lara.gun_status = LGS_ARMLESS; + } else { + Lara_InitialiseInventory(g_CurrentLevel); + } +} + +void __cdecl Lara_InitialiseInventory(const int32_t level_num) +{ + Inv_RemoveAllItems(); + + START_INFO *const start = &g_SaveGame.start[level_num]; + if (g_GF_RemoveWeapons) { + start->has_pistols = 0; + start->has_magnums = 0; + start->has_uzis = 0; + start->has_shotgun = 0; + start->has_m16 = 0; + start->has_grenade = 0; + start->has_harpoon = 0; + start->gun_type = LGT_UNARMED; + start->gun_status = LGS_ARMLESS; + g_GF_RemoveWeapons = 0; + } + + if (g_GF_RemoveAmmo) { + start->m16_ammo = 0; + start->grenade_ammo = 0; + start->harpoon_ammo = 0; + start->shotgun_ammo = 0; + start->uzi_ammo = 0; + start->magnum_ammo = 0; + start->pistol_ammo = 0; + start->flares = 0; + start->large_medipacks = 0; + start->small_medipacks = 0; + g_GF_RemoveAmmo = 0; + } + + Inv_AddItem(O_COMPASS_ITEM); + + g_Lara.pistol_ammo.ammo = 1000; + if (start->has_pistols) { + Inv_AddItem(O_PISTOL_ITEM); + } + + if (start->has_magnums) { + Inv_AddItem(O_MAGNUM_ITEM); + g_Lara.magnum_ammo.ammo = start->magnum_ammo; + Item_GlobalReplace(O_MAGNUM_ITEM, O_MAGNUM_AMMO_ITEM); + } else { + Inv_AddItemNTimes(O_MAGNUM_AMMO_ITEM, start->magnum_ammo / 40); + g_Lara.magnum_ammo.ammo = 0; + } + + if (start->has_uzis) { + Inv_AddItem(O_UZI_ITEM); + g_Lara.uzi_ammo.ammo = start->uzi_ammo; + Item_GlobalReplace(O_UZI_ITEM, O_UZI_AMMO_ITEM); + } else { + Inv_AddItemNTimes(O_UZI_AMMO_ITEM, start->uzi_ammo / 80); + g_Lara.uzi_ammo.ammo = 0; + } + + if (start->has_shotgun) { + Inv_AddItem(O_SHOTGUN_ITEM); + g_Lara.shotgun_ammo.ammo = start->shotgun_ammo; + Item_GlobalReplace(O_SHOTGUN_ITEM, O_SHOTGUN_AMMO_ITEM); + } else { + Inv_AddItemNTimes(O_SHOTGUN_AMMO_ITEM, start->shotgun_ammo / 12); + g_Lara.shotgun_ammo.ammo = 0; + } + + if (start->has_m16) { + Inv_AddItem(O_M16_ITEM); + g_Lara.m16_ammo.ammo = start->m16_ammo; + Item_GlobalReplace(O_M16_ITEM, O_M16_AMMO_ITEM); + } else { + Inv_AddItemNTimes(O_M16_AMMO_ITEM, start->m16_ammo / 40); + g_Lara.m16_ammo.ammo = 0; + } + + if (start->has_grenade) { + Inv_AddItem(O_GRENADE_ITEM); + g_Lara.grenade_ammo.ammo = start->grenade_ammo; + Item_GlobalReplace(O_GRENADE_ITEM, O_GRENADE_AMMO_ITEM); + } else { + Inv_AddItemNTimes(O_GRENADE_AMMO_ITEM, start->grenade_ammo / 2); + g_Lara.grenade_ammo.ammo = 0; + } + + if (start->has_harpoon) { + Inv_AddItem(O_HARPOON_ITEM); + g_Lara.harpoon_ammo.ammo = start->harpoon_ammo; + Item_GlobalReplace(O_HARPOON_ITEM, O_HARPOON_AMMO_ITEM); + } else { + Inv_AddItemNTimes(O_HARPOON_AMMO_ITEM, start->harpoon_ammo / 3); + g_Lara.harpoon_ammo.ammo = 0; + } + + Inv_AddItemNTimes(O_FLARE_ITEM, start->flares); + Inv_AddItemNTimes(O_SMALL_MEDIPACK_ITEM, start->small_medipacks); + Inv_AddItemNTimes(O_LARGE_MEDIPACK_ITEM, start->large_medipacks); + + g_Lara.gun_status = LGS_ARMLESS; + g_Lara.last_gun_type = start->gun_type; + g_Lara.gun_type = g_Lara.last_gun_type; + g_Lara.request_gun_type = g_Lara.last_gun_type; + + Lara_InitialiseMeshes(level_num); + Gun_InitialiseNewWeapon(); +} + +void __cdecl Lara_InitialiseMeshes(const int32_t level_num) +{ + for (int32_t i = 0; i < LM_NUMBER_OF; i++) { + g_Lara.mesh_ptrs[i] = g_Meshes[g_Objects[O_LARA].mesh_idx + i]; + } + + const START_INFO *const start = &g_SaveGame.start[level_num]; + + GAME_OBJECT_ID holster_object_id = NO_OBJECT; + if (start->gun_type != LGT_UNARMED) { + if (start->gun_type == LGT_MAGNUMS) { + holster_object_id = O_LARA_MAGNUMS; + } else if (start->gun_type == LGT_UZIS) { + holster_object_id = O_LARA_UZIS; + } else { + holster_object_id = O_LARA_PISTOLS; + } + } + + if (holster_object_id != NO_OBJECT) { + g_Lara.mesh_ptrs[LM_THIGH_L] = + g_Meshes[g_Objects[holster_object_id].mesh_idx + LM_THIGH_L]; + g_Lara.mesh_ptrs[LM_THIGH_R] = + g_Meshes[g_Objects[holster_object_id].mesh_idx + LM_THIGH_R]; + } + + if (start->gun_type == LGT_FLARE) { + g_Lara.mesh_ptrs[LM_HAND_L] = + g_Meshes[g_Objects[O_LARA_FLARE].mesh_idx + LM_HAND_L]; + } + + switch (start->gun_type) { + case LGT_M16: + g_Lara.back_gun = O_LARA_M16; + return; + + case LGT_GRENADE: + g_Lara.back_gun = O_LARA_GRENADE; + return; + + case LGT_HARPOON: + g_Lara.back_gun = O_LARA_HARPOON; + return; + } + + if (start->has_shotgun) { + g_Lara.back_gun = O_LARA_SHOTGUN; + } else if (start->has_m16) { + g_Lara.back_gun = O_LARA_M16; + } else if (start->has_grenade) { + g_Lara.back_gun = O_LARA_GRENADE; + } else if (start->has_harpoon) { + g_Lara.back_gun = O_LARA_HARPOON; + } +} + +void Lara_SwapSingleMesh(const LARA_MESH mesh, const GAME_OBJECT_ID object_id) +{ + g_Lara.mesh_ptrs[mesh] = g_Meshes[g_Objects[object_id].mesh_idx + mesh]; +} + +void Lara_GetOffVehicle(void) +{ + if (g_Lara.skidoo != NO_ITEM) { + ITEM *const vehicle = &g_Items[g_Lara.skidoo]; + vehicle->anim_num = g_Objects[vehicle->object_id].anim_idx; + vehicle->frame_num = g_Anims[vehicle->anim_num].frame_base; + g_Lara.skidoo = NO_ITEM; + + g_LaraItem->current_anim_state = LS_STOP; + g_LaraItem->goal_anim_state = LS_STOP; + g_LaraItem->anim_num = LA_STAND_STILL; + g_LaraItem->frame_num = g_Anims[g_LaraItem->anim_num].frame_base; + + g_LaraItem->rot.x = 0; + g_LaraItem->rot.z = 0; + } +} + +int16_t Lara_GetNearestEnemy(void) +{ + if (g_LaraItem == NULL) { + return NO_ITEM; + } + + int32_t best_distance = -1; + int16_t best_item_num = NO_ITEM; + int16_t item_num = g_NextItemActive; + while (item_num != NO_ITEM) { + const ITEM *const item = &g_Items[item_num]; + + if (Creature_IsEnemy(item)) { + const int32_t distance = Item_GetDistance(item, &g_LaraItem->pos); + if (best_item_num == NO_ITEM || distance < best_distance) { + best_item_num = item_num; + best_distance = distance; + } + } + + item_num = item->next_active; + } + + return best_item_num; +} + +void Lara_TakeDamage(const int16_t damage, const bool hit_status) +{ + Item_TakeDamage(g_LaraItem, damage, hit_status); +} diff --git a/src/tr2/game/lara/control.h b/src/tr2/game/lara/control.h new file mode 100644 index 000000000..47c9a8fdb --- /dev/null +++ b/src/tr2/game/lara/control.h @@ -0,0 +1,30 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Lara_HandleAboveWater(ITEM *item, COLL_INFO *coll); + +void __cdecl Lara_HandleSurface(ITEM *item, COLL_INFO *coll); + +void __cdecl Lara_HandleUnderwater(ITEM *item, COLL_INFO *coll); + +void __cdecl Lara_Control(int16_t item_num); +void __cdecl Lara_ControlExtra(int16_t item_num); + +void __cdecl Lara_Animate(ITEM *item); + +void __cdecl Lara_UseItem(GAME_OBJECT_ID object_id); + +void __cdecl Lara_InitialiseLoad(int16_t item_num); + +void __cdecl Lara_Initialise(GAMEFLOW_LEVEL_TYPE type); + +void __cdecl Lara_InitialiseInventory(int32_t level_num); + +void __cdecl Lara_InitialiseMeshes(int32_t level_num); + +void Lara_GetOffVehicle(void); +void Lara_SwapSingleMesh(LARA_MESH mesh, GAME_OBJECT_ID); + +int16_t Lara_GetNearestEnemy(void); +void Lara_TakeDamage(int16_t damage, bool hit_status); diff --git a/src/tr2/game/lara/draw.c b/src/tr2/game/lara/draw.c new file mode 100644 index 000000000..9bef0d73c --- /dev/null +++ b/src/tr2/game/lara/draw.c @@ -0,0 +1,644 @@ +#include "game/lara/draw.h" + +#include "game/items.h" +#include "game/matrix.h" +#include "game/output.h" +#include "game/random.h" +#include "global/funcs.h" +#include "global/vars.h" + +void __cdecl Lara_Draw(const ITEM *const item) +{ + FRAME_INFO *frame; + MATRIX saved_matrix; + + const int32_t top = g_PhdWinTop; + const int32_t left = g_PhdWinLeft; + const int32_t right = g_PhdWinRight; + const int32_t bottom = g_PhdWinBottom; + + g_PhdWinTop = 0; + g_PhdWinLeft = 0; + g_PhdWinBottom = g_PhdWinMaxY; + g_PhdWinRight = g_PhdWinMaxX; + + FRAME_INFO *frames[2]; + if (g_Lara.hit_direction < 0) { + int32_t rate; + const int32_t frac = Item_GetFrames(item, frames, &rate); + if (frac) { + Lara_Draw_I(item, frames[0], frames[1], frac, rate); + goto finish; + } + } + + const OBJECT *const object = &g_Objects[item->object_id]; + if (g_Lara.hit_direction < 0) { + frame = frames[0]; + } else { + // clang-format off + LARA_ANIMATION anim; + switch (g_Lara.hit_direction) { + case DIR_EAST: anim = LA_HIT_LEFT; break; + case DIR_SOUTH: anim = LA_HIT_BACK; break; + case DIR_WEST: anim = LA_HIT_RIGHT; break; + default: anim = LA_HIT_FRONT; break; + } + // clang-format on + frame = (FRAME_INFO *)(g_Anims[anim].frame_ptr + + g_Lara.hit_frame + * (g_Anims[anim].interpolation >> 8)); + } + + if (g_Lara.skidoo == NO_ITEM) { + S_PrintShadow(object->shadow_size, &frame->bounds, item); + } + + saved_matrix = *g_MatrixPtr; + + Matrix_Push(); + Matrix_TranslateAbs(item->pos.x, item->pos.y, item->pos.z); + Matrix_RotYXZ(item->rot.y, item->rot.x, item->rot.z); + const int32_t clip = S_GetObjectBounds(&frame->bounds); + if (!clip) { + Matrix_Pop(); + return; + } + + Matrix_Push(); + Output_CalculateObjectLighting(item, frame); + + const int32_t *bone = &g_AnimBones[object->bone_idx]; + const int16_t *mesh_rots = frame->mesh_rots; + const int16_t *mesh_rots_c; + + Matrix_TranslateRel(frame->offset.x, frame->offset.y, frame->offset.z); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_HIPS], clip); + + Matrix_Push(); + Matrix_TranslateRel(bone[1], bone[2], bone[3]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_THIGH_L], clip); + + Matrix_TranslateRel(bone[5], bone[6], bone[7]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_CALF_L], clip); + + Matrix_TranslateRel(bone[9], bone[10], bone[11]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_FOOT_L], clip); + Matrix_Pop(); + + Matrix_Push(); + Matrix_TranslateRel(bone[13], bone[14], bone[15]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_THIGH_R], clip); + + Matrix_TranslateRel(bone[17], bone[18], bone[19]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_CALF_R], clip); + + Matrix_TranslateRel(bone[21], bone[22], bone[23]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_FOOT_R], clip); + Matrix_Pop(); + + Matrix_TranslateRel(bone[25], bone[26], bone[27]); + if (g_Lara.weapon_item != NO_ITEM && g_Lara.gun_type == LGT_M16 + && (g_Items[g_Lara.weapon_item].current_anim_state == 0 + || g_Items[g_Lara.weapon_item].current_anim_state == 2 + || g_Items[g_Lara.weapon_item].current_anim_state == 4)) { + mesh_rots = + &g_Lara.right_arm.frame_base + [g_Lara.right_arm.frame_num + * (g_Anims[g_Lara.right_arm.anim_num].interpolation >> 8) + + FBBOX_ROT]; + Matrix_RotYXZsuperpack(&mesh_rots, 7); + } else { + Matrix_RotYXZsuperpack(&mesh_rots, 0); + } + + Matrix_RotYXZ(g_Lara.torso_y_rot, g_Lara.torso_x_rot, g_Lara.torso_z_rot); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_TORSO], clip); + + Matrix_Push(); + Matrix_TranslateRel(bone[53], bone[54], bone[55]); + mesh_rots_c = mesh_rots; + Matrix_RotYXZsuperpack(&mesh_rots, 6); + mesh_rots = mesh_rots_c; + Matrix_RotYXZ(g_Lara.head_y_rot, g_Lara.head_x_rot, g_Lara.head_z_rot); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_HEAD], clip); + + *g_MatrixPtr = saved_matrix; + DrawHair(); + + Matrix_Pop(); + + if (g_Lara.back_gun) { + Matrix_Push(); + const int32_t *bone_c = + &g_AnimBones[g_Objects[g_Lara.back_gun].bone_idx]; + Matrix_TranslateRel(bone_c[53], bone_c[54], bone_c[55]); + mesh_rots_c = g_Objects[g_Lara.back_gun].frame_base + FBBOX_ROT; + Matrix_RotYXZsuperpack(&mesh_rots_c, 14); + Output_InsertPolygons( + g_Meshes[g_Objects[g_Lara.back_gun].mesh_idx + LM_HEAD], clip); + Matrix_Pop(); + } + + LARA_GUN_TYPE gun_type = LGT_UNARMED; + if (g_Lara.gun_status == LGS_READY || g_Lara.gun_status == LGS_SPECIAL + || g_Lara.gun_status == LGS_DRAW || g_Lara.gun_status == LGS_UNDRAW) { + gun_type = g_Lara.gun_type; + } + + switch (gun_type) { + case LGT_UNARMED: + case LGT_FLARE: + Matrix_Push(); + Matrix_TranslateRel(bone[29], bone[30], bone[31]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_UARM_R], clip); + + Matrix_TranslateRel(bone[33], bone[34], bone[35]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_LARM_R], clip); + + Matrix_TranslateRel(bone[37], bone[38], bone[39]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_HAND_R], clip); + Matrix_Pop(); + + Matrix_Push(); + Matrix_TranslateRel(bone[41], bone[42], bone[43]); + if (g_Lara.flare_control_left) { + mesh_rots = + &g_Lara.left_arm.frame_base + [(g_Anims[g_Lara.left_arm.anim_num].interpolation >> 8) + * (g_Lara.left_arm.frame_num + - g_Anims[g_Lara.left_arm.anim_num].frame_base) + + FBBOX_ROT]; + Matrix_RotYXZsuperpack(&mesh_rots, 11); + } else { + Matrix_RotYXZsuperpack(&mesh_rots, 0); + } + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_UARM_L], clip); + Matrix_TranslateRel(bone[45], bone[46], bone[47]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_LARM_L], clip); + Matrix_TranslateRel(bone[49], bone[50], bone[51]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_HAND_L], clip); + if (g_Lara.gun_type == LGT_FLARE && g_Lara.left_arm.flash_gun) { + Gun_DrawFlash(LGT_FLARE, clip); + } + + Matrix_Pop(); + break; + + case LGT_PISTOLS: + case LGT_MAGNUMS: + case LGT_UZIS: + Matrix_Push(); + Matrix_TranslateRel(bone[29], bone[30], bone[31]); + + g_MatrixPtr->_00 = g_MatrixPtr[-2]._00; + g_MatrixPtr->_01 = g_MatrixPtr[-2]._01; + g_MatrixPtr->_02 = g_MatrixPtr[-2]._02; + g_MatrixPtr->_10 = g_MatrixPtr[-2]._10; + g_MatrixPtr->_11 = g_MatrixPtr[-2]._11; + g_MatrixPtr->_12 = g_MatrixPtr[-2]._12; + g_MatrixPtr->_20 = g_MatrixPtr[-2]._20; + g_MatrixPtr->_21 = g_MatrixPtr[-2]._21; + g_MatrixPtr->_22 = g_MatrixPtr[-2]._22; + + Matrix_RotYXZ( + g_Lara.right_arm.rot.y, g_Lara.right_arm.rot.x, + g_Lara.right_arm.rot.z); + mesh_rots = + &g_Lara.right_arm.frame_base + [(g_Anims[g_Lara.right_arm.anim_num].interpolation >> 8) + * (g_Lara.right_arm.frame_num + - g_Anims[g_Lara.right_arm.anim_num].frame_base) + + FBBOX_ROT]; + Matrix_RotYXZsuperpack(&mesh_rots, 8); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_UARM_R], clip); + + Matrix_TranslateRel(bone[33], bone[34], bone[35]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_LARM_R], clip); + + Matrix_TranslateRel(bone[37], bone[38], bone[39]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_HAND_R], clip); + + if (g_Lara.right_arm.flash_gun) { + saved_matrix = *g_MatrixPtr; + } + Matrix_Pop(); + + Matrix_Push(); + Matrix_TranslateRel(bone[41], bone[42], bone[43]); + g_MatrixPtr->_00 = g_MatrixPtr[-2]._00; + g_MatrixPtr->_01 = g_MatrixPtr[-2]._01; + g_MatrixPtr->_02 = g_MatrixPtr[-2]._02; + g_MatrixPtr->_10 = g_MatrixPtr[-2]._10; + g_MatrixPtr->_11 = g_MatrixPtr[-2]._11; + g_MatrixPtr->_12 = g_MatrixPtr[-2]._12; + g_MatrixPtr->_20 = g_MatrixPtr[-2]._20; + g_MatrixPtr->_21 = g_MatrixPtr[-2]._21; + g_MatrixPtr->_22 = g_MatrixPtr[-2]._22; + Matrix_RotYXZ( + g_Lara.left_arm.rot.y, g_Lara.left_arm.rot.x, + g_Lara.left_arm.rot.z); + mesh_rots = &g_Lara.left_arm.frame_base + [(g_Anims[g_Lara.left_arm.anim_num].interpolation >> 8) + * (g_Lara.left_arm.frame_num + - g_Anims[g_Lara.left_arm.anim_num].frame_base) + + FBBOX_ROT]; + Matrix_RotYXZsuperpack(&mesh_rots, 11); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_UARM_L], clip); + + Matrix_TranslateRel(bone[45], bone[46], bone[47]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_LARM_L], clip); + + Matrix_TranslateRel(bone[49], bone[50], bone[51]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_HAND_L], clip); + + if (g_Lara.left_arm.flash_gun) { + Gun_DrawFlash(gun_type, clip); + } + + if (g_Lara.right_arm.flash_gun) { + *g_MatrixPtr = saved_matrix; + Gun_DrawFlash(gun_type, clip); + } + Matrix_Pop(); + break; + + case LGT_SHOTGUN: + case LGT_M16: + case LGT_GRENADE: + case LGT_HARPOON: + Matrix_Push(); + Matrix_TranslateRel(bone[29], bone[30], bone[31]); + mesh_rots = + &g_Lara.right_arm.frame_base + [g_Lara.right_arm.frame_num + * (g_Anims[g_Lara.right_arm.anim_num].interpolation >> 8) + + FBBOX_ROT]; + Matrix_RotYXZsuperpack(&mesh_rots, 8); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_UARM_R], clip); + + Matrix_TranslateRel(bone[33], bone[34], bone[35]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_LARM_R], clip); + + Matrix_TranslateRel(bone[37], bone[38], bone[39]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_HAND_R], clip); + + if (g_Lara.right_arm.flash_gun) { + saved_matrix = *g_MatrixPtr; + } + Matrix_Pop(); + + Matrix_Push(); + Matrix_TranslateRel(bone[41], bone[42], bone[43]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_UARM_L], clip); + + Matrix_TranslateRel(bone[45], bone[46], bone[47]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_LARM_L], clip); + + Matrix_TranslateRel(bone[49], bone[50], bone[51]); + Matrix_RotYXZsuperpack(&mesh_rots, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_HAND_L], clip); + + if (g_Lara.right_arm.flash_gun) { + *g_MatrixPtr = saved_matrix; + Gun_DrawFlash(gun_type, clip); + } + + Matrix_Pop(); + break; + + default: + break; + } + + Matrix_Pop(); + Matrix_Pop(); + +finish: + g_PhdWinLeft = left; + g_PhdWinRight = right; + g_PhdWinTop = top; + g_PhdWinBottom = bottom; +} + +void __cdecl Lara_Draw_I( + const ITEM *const item, const FRAME_INFO *const frame1, + const FRAME_INFO *const frame2, const int32_t frac, const int32_t rate) +{ + const OBJECT *const object = &g_Objects[item->object_id]; + const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); + + if (g_Lara.skidoo == NO_ITEM) { + S_PrintShadow(object->shadow_size, bounds, item); + } + + MATRIX saved_matrix = *g_MatrixPtr; + + Matrix_Push(); + Matrix_TranslateAbs(item->pos.x, item->pos.y, item->pos.z); + Matrix_RotYXZ(item->rot.y, item->rot.x, item->rot.z); + + const int32_t clip = S_GetObjectBounds(&frame1->bounds); + + if (!clip) { + Matrix_Pop(); + return; + } + + Matrix_Push(); + Output_CalculateObjectLighting(item, frame1); + + const int32_t *bone = &g_AnimBones[object->bone_idx]; + const int16_t *mesh_rots_1 = frame1->mesh_rots; + const int16_t *mesh_rots_2 = frame2->mesh_rots; + const int16_t *mesh_rots_1_c; + const int16_t *mesh_rots_2_c; + + Matrix_InitInterpolate(frac, rate); + Matrix_TranslateRel_ID( + frame1->offset.x, frame1->offset.y, frame1->offset.z, frame2->offset.x, + frame2->offset.y, frame2->offset.z); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_HIPS], clip); + + Matrix_Push_I(); + Matrix_TranslateRel_I(bone[1], bone[2], bone[3]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_THIGH_L], clip); + + Matrix_TranslateRel_I(bone[5], bone[6], bone[7]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_CALF_L], clip); + + Matrix_TranslateRel_I(bone[9], bone[10], bone[11]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_FOOT_L], clip); + Matrix_Pop_I(); + + Matrix_Push_I(); + Matrix_TranslateRel_I(bone[13], bone[14], bone[15]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_THIGH_R], clip); + + Matrix_TranslateRel_I(bone[17], bone[18], bone[19]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_CALF_R], clip); + + Matrix_TranslateRel_I(bone[21], bone[22], bone[23]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_FOOT_R], clip); + Matrix_Pop_I(); + + Matrix_TranslateRel_I(bone[25], bone[26], bone[27]); + if (g_Lara.weapon_item != -1 && g_Lara.gun_type == 5 + && ((g_Items[g_Lara.weapon_item].current_anim_state) == 0 + || g_Items[g_Lara.weapon_item].current_anim_state == 2 + || g_Items[g_Lara.weapon_item].current_anim_state == 4)) { + mesh_rots_2 = + &g_Lara.right_arm.frame_base + [g_Lara.right_arm.frame_num + * (g_Anims[g_Lara.right_arm.anim_num].interpolation >> 8) + + FBBOX_ROT]; + mesh_rots_1 = mesh_rots_2; + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 7); + } else { + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + } + Matrix_RotYXZ_I(g_Lara.torso_y_rot, g_Lara.torso_x_rot, g_Lara.torso_z_rot); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_TORSO], clip); + + Matrix_Push_I(); + Matrix_TranslateRel_I(bone[53], bone[54], bone[55]); + mesh_rots_1_c = mesh_rots_1; + mesh_rots_2_c = mesh_rots_2; + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 6); + mesh_rots_1 = mesh_rots_1_c; + mesh_rots_2 = mesh_rots_2_c; + Matrix_RotYXZ_I(g_Lara.head_y_rot, g_Lara.head_x_rot, g_Lara.head_z_rot); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_HEAD], clip); + + *g_MatrixPtr = saved_matrix; + DrawHair(); + Matrix_Pop_I(); + + if (g_Lara.back_gun) { + Matrix_Push_I(); + const int32_t *bone_c = + &g_AnimBones[g_Objects[g_Lara.back_gun].bone_idx]; + Matrix_TranslateRel_I(bone_c[53], bone_c[54], bone_c[55]); + mesh_rots_1_c = g_Objects[g_Lara.back_gun].frame_base + FBBOX_ROT; + mesh_rots_2_c = g_Objects[g_Lara.back_gun].frame_base + FBBOX_ROT; + Matrix_RotYXZsuperpack_I(&mesh_rots_1_c, &mesh_rots_2_c, 14); + Output_InsertPolygons_I( + g_Meshes[g_Objects[g_Lara.back_gun].mesh_idx + LM_HEAD], clip); + Matrix_Pop_I(); + } + + LARA_GUN_TYPE gun_type = LGT_UNARMED; + if (g_Lara.gun_status == LGS_READY || g_Lara.gun_status == LGS_SPECIAL + || g_Lara.gun_status == LGS_DRAW || g_Lara.gun_status == LGS_UNDRAW) { + gun_type = g_Lara.gun_type; + } + + switch (gun_type) { + case LGT_UNARMED: + case LGT_FLARE: + Matrix_Push_I(); + Matrix_TranslateRel_I(bone[29], bone[30], bone[31]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_UARM_R], clip); + + Matrix_TranslateRel_I(bone[33], bone[34], bone[35]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_LARM_R], clip); + + Matrix_TranslateRel_I(bone[37], bone[38], bone[39]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_HAND_R], clip); + Matrix_Pop_I(); + + Matrix_Push_I(); + Matrix_TranslateRel_I(bone[41], bone[42], bone[43]); + if (g_Lara.flare_control_left) { + mesh_rots_1 = + &g_Lara.left_arm.frame_base + [(g_Anims[g_Lara.left_arm.anim_num].interpolation >> 8) + * (g_Lara.left_arm.frame_num + - g_Anims[g_Lara.left_arm.anim_num].frame_base) + + FBBOX_ROT]; + + mesh_rots_2 = + &g_Lara.left_arm.frame_base + [(g_Anims[g_Lara.left_arm.anim_num].interpolation >> 8) + * (g_Lara.left_arm.frame_num + - g_Anims[g_Lara.left_arm.anim_num].frame_base) + + FBBOX_ROT]; + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 11); + } else { + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + } + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_UARM_L], clip); + + Matrix_TranslateRel_I(bone[45], bone[46], bone[47]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_LARM_L], clip); + + Matrix_TranslateRel_I(bone[49], bone[50], bone[51]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_HAND_L], clip); + + if (g_Lara.gun_type == LGT_FLARE && g_Lara.left_arm.flash_gun) { + Matrix_TranslateRel_I(11, 32, 80); + Matrix_RotX_I(-16380); + Matrix_RotY_I(2 * Random_GetDraw()); + S_CalculateStaticLight(2048); + Output_InsertPolygons_I(g_Meshes[g_Objects[235].mesh_idx], clip); + } + Matrix_Pop(); + break; + + case LGT_PISTOLS: + case LGT_MAGNUMS: + case LGT_UZIS: + Matrix_Push_I(); + Matrix_TranslateRel_I(bone[29], bone[30], bone[31]); + Matrix_InterpolateArm(); + Matrix_RotYXZ( + g_Lara.right_arm.rot.y, g_Lara.right_arm.rot.x, + g_Lara.right_arm.rot.z); + mesh_rots_1 = + &g_Lara.right_arm.frame_base + [(g_Anims[g_Lara.right_arm.anim_num].interpolation >> 8) + * (g_Lara.right_arm.frame_num + - g_Anims[g_Lara.right_arm.anim_num].frame_base) + + FBBOX_ROT]; + Matrix_RotYXZsuperpack(&mesh_rots_1, 8); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_UARM_R], clip); + + Matrix_TranslateRel(bone[33], bone[34], bone[35]); + Matrix_RotYXZsuperpack(&mesh_rots_1, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_LARM_R], clip); + + Matrix_TranslateRel(bone[37], bone[38], bone[39]); + Matrix_RotYXZsuperpack(&mesh_rots_1, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_HAND_R], clip); + + if (g_Lara.right_arm.flash_gun) { + saved_matrix = *g_MatrixPtr; + } + Matrix_Pop_I(); + + Matrix_Push_I(); + Matrix_TranslateRel_I(bone[41], bone[42], bone[43]); + Matrix_InterpolateArm(); + Matrix_RotYXZ( + g_Lara.left_arm.rot.y, g_Lara.left_arm.rot.x, + g_Lara.left_arm.rot.z); + mesh_rots_1 = + &g_Lara.left_arm.frame_base + [(g_Anims[g_Lara.left_arm.anim_num].interpolation >> 8) + * (g_Lara.left_arm.frame_num + - g_Anims[g_Lara.left_arm.anim_num].frame_base) + + FBBOX_ROT]; + Matrix_RotYXZsuperpack(&mesh_rots_1, 11); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_UARM_L], clip); + + Matrix_TranslateRel(bone[45], bone[46], bone[47]); + Matrix_RotYXZsuperpack(&mesh_rots_1, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_LARM_L], clip); + + Matrix_TranslateRel(bone[49], bone[50], bone[51]); + Matrix_RotYXZsuperpack(&mesh_rots_1, 0); + Output_InsertPolygons(g_Lara.mesh_ptrs[LM_HAND_L], clip); + + if (g_Lara.left_arm.flash_gun) { + Gun_DrawFlash((int32_t)gun_type, clip); + } + + if (g_Lara.right_arm.flash_gun) { + *g_MatrixPtr = saved_matrix; + Gun_DrawFlash(gun_type, clip); + } + Matrix_Pop(); + break; + + case LGT_SHOTGUN: + case LGT_M16: + case LGT_GRENADE: + case LGT_HARPOON: + Matrix_Push_I(); + Matrix_TranslateRel_I(bone[29], bone[30], bone[31]); + mesh_rots_1 = + &g_Lara.right_arm.frame_base + [g_Lara.right_arm.frame_num + * (g_Anims[g_Lara.right_arm.anim_num].interpolation >> 8) + + FBBOX_ROT]; + mesh_rots_2 = + &g_Lara.right_arm.frame_base + [g_Lara.right_arm.frame_num + * (g_Anims[g_Lara.right_arm.anim_num].interpolation >> 8) + + FBBOX_ROT]; + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 8); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_UARM_R], clip); + + Matrix_TranslateRel_I(bone[33], bone[34], bone[35]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_LARM_R], clip); + + Matrix_TranslateRel_I(bone[37], bone[38], bone[39]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_HAND_R], clip); + + if (g_Lara.right_arm.flash_gun) { + saved_matrix = *g_MatrixPtr; + } + Matrix_Pop_I(); + + Matrix_Push_I(); + Matrix_TranslateRel_I(bone[41], bone[42], bone[43]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_UARM_L], clip); + + Matrix_TranslateRel_I(bone[45], bone[46], bone[47]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_LARM_L], clip); + + Matrix_TranslateRel_I(bone[49], bone[50], bone[51]); + Matrix_RotYXZsuperpack_I(&mesh_rots_1, &mesh_rots_2, 0); + Output_InsertPolygons_I(g_Lara.mesh_ptrs[LM_HAND_L], clip); + + if (g_Lara.right_arm.flash_gun) { + *g_MatrixPtr = saved_matrix; + Gun_DrawFlash(gun_type, clip); + } + + Matrix_Pop(); + break; + + default: + break; + } + + Matrix_Pop(); + Matrix_Pop(); +} diff --git a/src/tr2/game/lara/draw.h b/src/tr2/game/lara/draw.h new file mode 100644 index 000000000..544e44b9a --- /dev/null +++ b/src/tr2/game/lara/draw.h @@ -0,0 +1,8 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Lara_Draw(const ITEM *item); +void __cdecl Lara_Draw_I( + const ITEM *item, const FRAME_INFO *frame1, const FRAME_INFO *frame2, + int32_t frac, int32_t rate); diff --git a/src/tr2/game/lara/look.c b/src/tr2/game/lara/look.c new file mode 100644 index 000000000..8ad48d115 --- /dev/null +++ b/src/tr2/game/lara/look.c @@ -0,0 +1,67 @@ +#include "game/lara/look.h" + +#include "game/input.h" +#include "global/const.h" +#include "global/vars.h" + +void __cdecl Lara_LookUpDown(void) +{ + g_Camera.type = CAM_LOOK; + + if (g_Input & IN_FORWARD) { + g_Input &= ~IN_FORWARD; + if (g_Lara.head_x_rot > MIN_HEAD_TILT) { + g_Lara.head_x_rot -= HEAD_TURN; + } + } else if (g_Input & IN_BACK) { + g_Input &= ~IN_BACK; + if (g_Lara.head_x_rot < MAX_HEAD_TILT) { + g_Lara.head_x_rot += HEAD_TURN; + } + } + + if (g_Lara.gun_status != LGS_HANDS_BUSY) { + g_Lara.torso_x_rot = g_Lara.head_x_rot; + } +} + +void __cdecl Lara_LookLeftRight(void) +{ + g_Camera.type = CAM_LOOK; + + if (g_Input & IN_LEFT) { + g_Input &= ~IN_LEFT; + if (g_Lara.head_y_rot > MIN_HEAD_ROTATION) + g_Lara.head_y_rot -= HEAD_TURN; + } else if (g_Input & IN_RIGHT) { + g_Input &= ~IN_RIGHT; + if (g_Lara.head_y_rot < MAX_HEAD_ROTATION) + g_Lara.head_y_rot += HEAD_TURN; + } + + if (g_Lara.gun_status != LGS_HANDS_BUSY && g_Lara.skidoo == NO_ITEM) { + g_Lara.torso_y_rot = g_Lara.head_y_rot; + } +} + +void __cdecl Lara_ResetLook(void) +{ + if (g_Camera.type == CAM_LOOK) { + return; + } + + if (g_Lara.head_x_rot <= -HEAD_TURN || g_Lara.head_x_rot >= HEAD_TURN) { + g_Lara.head_x_rot -= g_Lara.head_x_rot / 8; + } else { + g_Lara.head_x_rot = 0; + } + + if (g_Lara.head_y_rot <= -HEAD_TURN || g_Lara.head_y_rot >= HEAD_TURN) { + g_Lara.head_y_rot += g_Lara.head_y_rot / -8; + } else { + g_Lara.head_y_rot = 0; + } + + g_Lara.torso_x_rot = g_Lara.head_x_rot; + g_Lara.torso_y_rot = g_Lara.head_y_rot; +} diff --git a/src/tr2/game/lara/look.h b/src/tr2/game/lara/look.h new file mode 100644 index 000000000..a3b41d150 --- /dev/null +++ b/src/tr2/game/lara/look.h @@ -0,0 +1,5 @@ +#pragma once + +void __cdecl Lara_LookUpDown(void); +void __cdecl Lara_LookLeftRight(void); +void __cdecl Lara_ResetLook(void); diff --git a/src/tr2/game/lara/misc.c b/src/tr2/game/lara/misc.c new file mode 100644 index 000000000..767f7d34f --- /dev/null +++ b/src/tr2/game/lara/misc.c @@ -0,0 +1,1870 @@ +#include "game/lara/misc.h" + +#include "decomp/decomp.h" +#include "game/box.h" +#include "game/collide.h" +#include "game/effects.h" +#include "game/input.h" +#include "game/items.h" +#include "game/lara/control.h" +#include "game/math.h" +#include "game/matrix.h" +#include "game/random.h" +#include "game/room.h" +#include "game/sound.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include +#include +#include + +#define MAX_BADDIE_COLLISION 20 +#define MOVE_SPEED 16 +#define MOVE_ANGLE (2 * PHD_DEGREE) // = 364 +#define CLIMB_HANG 900 +#define CLIMB_SHIFT 70 + +static void __cdecl M_TakeHit( + ITEM *const lara_item, const int32_t dx, const int32_t dz); + +static void __cdecl M_TakeHit( + ITEM *const lara_item, const int32_t dx, const int32_t dz) +{ + const PHD_ANGLE hit_angle = lara_item->rot.y + PHD_180 - Math_Atan(dz, dx); + g_Lara.hit_direction = Math_GetDirection(hit_angle); + if (g_Lara.hit_frame == 0) { + Sound_Effect(SFX_LARA_INJURY, &lara_item->pos, SPM_NORMAL); + } + g_Lara.hit_frame++; + CLAMPG(g_Lara.hit_frame, 34); +} + +void __cdecl Lara_GetCollisionInfo(ITEM *item, COLL_INFO *coll) +{ + coll->facing = g_Lara.move_angle; + Collide_GetCollisionInfo( + coll, item->pos.x, item->pos.y, item->pos.z, item->room_num, + LARA_HEIGHT); +} + +void __cdecl Lara_SlideSlope(ITEM *item, COLL_INFO *coll) +{ + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEP_L * 2; + coll->bad_ceiling = 0; + Lara_GetCollisionInfo(item, coll); + + if (Lara_HitCeiling(item, coll)) { + return; + } + + Lara_DeflectEdge(item, coll); + + if (coll->side_mid.floor > 200) { + if (item->current_anim_state == LS_SLIDE) { + item->goal_anim_state = LS_FORWARD_JUMP; + item->current_anim_state = LS_FORWARD_JUMP; + item->anim_num = LS_SURF_SWIM; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else { + item->goal_anim_state = LS_FALL_BACK; + item->current_anim_state = LS_FALL_BACK; + item->anim_num = LA_FALL_BACK; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + item->gravity = 1; + item->fall_speed = 0; + return; + } + + Lara_TestSlide(item, coll); + item->pos.y += coll->side_mid.floor; + if (ABS(coll->x_tilt) <= 2 && ABS(coll->z_tilt) <= 2) { + item->goal_anim_state = LS_STOP; + } +} + +int32_t __cdecl Lara_HitCeiling(ITEM *item, COLL_INFO *coll) +{ + if (coll->coll_type != COLL_TOP && coll->coll_type != COLL_CLAMP) { + return 0; + } + + item->pos.x = coll->old.x; + item->pos.y = coll->old.y; + item->pos.z = coll->old.z; + item->goal_anim_state = LS_STOP; + item->current_anim_state = LS_STOP; + item->anim_num = LS_REACH; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->speed = 0; + item->gravity = 0; + item->fall_speed = 0; + return 1; +} + +int32_t __cdecl Lara_DeflectEdge(ITEM *item, COLL_INFO *coll) +{ + switch (coll->coll_type) { + case COLL_FRONT: + case COLL_TOP_FRONT: + Item_ShiftCol(item, coll); + item->goal_anim_state = LS_STOP; + item->current_anim_state = LS_STOP; + item->gravity = 0; + item->speed = 0; + return 1; + + case COLL_LEFT: + Item_ShiftCol(item, coll); + item->rot.y += LARA_DEFLECT_ANGLE; + return 0; + + case COLL_RIGHT: + Item_ShiftCol(item, coll); + item->rot.y -= LARA_DEFLECT_ANGLE; + return 0; + + default: + return 0; + } +} + +void __cdecl Lara_DeflectEdgeJump(ITEM *item, COLL_INFO *coll) +{ + Item_ShiftCol(item, coll); + switch (coll->coll_type) { + case COLL_FRONT: + case COLL_TOP_FRONT: + if (!g_Lara.climb_status || item->speed != 2) { + if (coll->side_mid.floor > 512) { + item->goal_anim_state = LS_FAST_FALL; + item->current_anim_state = LS_FAST_FALL; + item->anim_num = LA_SMASH_JUMP; + item->frame_num = g_Anims[item->anim_num].frame_base + 1; + } else if (coll->side_mid.floor <= 128) { + item->goal_anim_state = LS_LAND; + item->current_anim_state = LS_LAND; + item->anim_num = LA_JUMP_UP_LAND; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + item->speed /= 4; + g_Lara.move_angle += PHD_180; + CLAMPL(item->fall_speed, 1); + } + break; + + case COLL_LEFT: + item->rot.y += LARA_DEFLECT_ANGLE; + break; + + case COLL_RIGHT: + item->rot.y -= LARA_DEFLECT_ANGLE; + break; + + case COLL_TOP: + CLAMPL(item->fall_speed, 1); + break; + + case COLL_CLAMP: + item->pos.z -= (Math_Cos(coll->facing) * 100) >> W2V_SHIFT; + item->pos.x -= (Math_Sin(coll->facing) * 100) >> W2V_SHIFT; + item->speed = 0; + coll->side_mid.floor = 0; + if (item->fall_speed <= 0) { + item->fall_speed = 16; + } + break; + + default: + break; + } +} + +void __cdecl Lara_SlideEdgeJump(ITEM *item, COLL_INFO *coll) +{ + Item_ShiftCol(item, coll); + + switch (coll->coll_type) { + case COLL_LEFT: + item->rot.y += LARA_DEFLECT_ANGLE; + break; + + case COLL_RIGHT: + item->rot.y -= LARA_DEFLECT_ANGLE; + break; + + case COLL_TOP: + case COLL_TOP_FRONT: + CLAMPL(item->fall_speed, 1); + break; + + case COLL_CLAMP: + item->pos.z -= (Math_Cos(coll->facing) * 100) >> W2V_SHIFT; + item->pos.x -= (Math_Sin(coll->facing) * 100) >> W2V_SHIFT; + item->speed = 0; + coll->side_mid.floor = 0; + if (item->fall_speed <= 0) { + item->fall_speed = 16; + } + break; + + default: + break; + } +} + +int32_t __cdecl Lara_TestWall( + ITEM *item, int32_t front, int32_t right, int32_t down) +{ + int32_t x = item->pos.x; + int32_t y = item->pos.y + down; + int32_t z = item->pos.z; + + DIRECTION dir = Math_GetDirection(item->rot.y); + switch (dir) { + case DIR_NORTH: + x -= right; + break; + case DIR_EAST: + z -= right; + break; + case DIR_SOUTH: + x += right; + break; + case DIR_WEST: + z += right; + break; + default: + break; + } + + int16_t room_num = item->room_num; + Room_GetSector(x, y, z, &room_num); + + switch (dir) { + case DIR_NORTH: + z += front; + break; + case DIR_EAST: + x += front; + break; + case DIR_SOUTH: + z -= front; + break; + case DIR_WEST: + x -= front; + break; + default: + break; + } + + const SECTOR *const sector = Room_GetSector(x, y, z, &room_num); + const int32_t height = Room_GetHeight(sector, x, y, z); + const int32_t ceiling = Room_GetCeiling(sector, x, y, z); + if (height == NO_HEIGHT) { + return 1; + } + if (height - y > 0 && ceiling - y < 0) { + return 0; + } + return 2; +} + +int32_t __cdecl Lara_TestHangOnClimbWall(ITEM *item, COLL_INFO *coll) +{ + if (!g_Lara.climb_status || item->fall_speed < 0) { + return 0; + } + + DIRECTION dir = Math_GetDirection(item->rot.y); + switch (dir) { + case DIR_NORTH: + case DIR_SOUTH: + item->pos.z += coll->shift.z; + break; + + case DIR_EAST: + case DIR_WEST: + item->pos.x += coll->shift.x; + break; + + default: + break; + } + + const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); + int32_t y = bounds->min_y; + int32_t h = bounds->max_y - y; + + int32_t shift; + if (!Lara_TestClimbPos(item, coll->radius, coll->radius, y, h, &shift)) { + return 0; + } + + if (!Lara_TestClimbPos(item, coll->radius, -coll->radius, y, h, &shift)) { + return 0; + } + + int32_t result = Lara_TestClimbPos(item, coll->radius, 0, y, h, &shift); + switch (result) { + case 0: + case 1: + return result; + + default: + item->pos.y += shift; + return 1; + } +} + +int32_t __cdecl Lara_TestClimbStance(ITEM *item, COLL_INFO *coll) +{ + int32_t shift_r; + int32_t result_r = Lara_TestClimbPos( + item, coll->radius, coll->radius + LARA_CLIMB_WIDTH_RIGHT, -700, + STEP_L * 2, &shift_r); + if (result_r != 1) { + return 0; + } + + int32_t shift_l; + int32_t result_l = Lara_TestClimbPos( + item, coll->radius, -(coll->radius + LARA_CLIMB_WIDTH_LEFT), -700, + STEP_L * 2, &shift_l); + if (result_l != 1) { + return 0; + } + + int32_t shift = 0; + if (shift_r) { + if (shift_l) { + if ((shift_r < 0) != (shift_l < 0)) { + return 0; + } + if (shift_r < 0 && shift_l < shift_r) { + shift = shift_l; + } else if (shift_r > 0 && shift_l > shift_r) { + shift = shift_l; + } else { + shift = shift_r; + } + } else { + shift = shift_r; + } + } else if (shift_l) { + shift = shift_l; + } + + item->pos.y += shift; + return 1; +} + +void __cdecl Lara_HangTest(ITEM *item, COLL_INFO *coll) +{ + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = NO_BAD_NEG; + coll->bad_ceiling = 0; + Lara_GetCollisionInfo(item, coll); + bool flag = coll->side_front.floor < 200; + + item->gravity = 0; + item->fall_speed = 0; + g_Lara.move_angle = item->rot.y; + + DIRECTION dir = Math_GetDirection(item->rot.y); + switch (dir) { + case DIR_NORTH: + item->pos.z += 2; + break; + case DIR_EAST: + item->pos.x += 2; + break; + case DIR_SOUTH: + item->pos.z -= 2; + break; + case DIR_WEST: + item->pos.x -= 2; + break; + default: + break; + } + + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + Lara_GetCollisionInfo(item, coll); + + if (g_Lara.climb_status) { + if (!(g_Input & IN_ACTION) || item->hit_points <= 0) { + XYZ_32 pos = { + .x = 0, + .y = 0, + .z = 0, + }; + Collide_GetJointAbsPosition(item, &pos, 0); + if (dir == DIR_NORTH || dir == DIR_SOUTH) { + item->pos.x = pos.x; + } else { + item->pos.z = pos.z; + } + + item->goal_anim_state = LS_FORWARD_JUMP; + item->current_anim_state = LS_FORWARD_JUMP; + item->anim_num = LA_FALL_START; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->pos.y += STEP_L; + item->gravity = 1; + item->speed = 2; + item->fall_speed = 1; + g_Lara.gun_status = LGS_ARMLESS; + return; + } + + if (!Lara_TestHangOnClimbWall(item, coll)) { + item->pos.x = coll->old.x; + item->pos.y = coll->old.y; + item->pos.z = coll->old.z; + item->goal_anim_state = LS_HANG; + item->current_anim_state = LS_HANG; + item->anim_num = LA_REACH_TO_HANG; + item->frame_num = g_Anims[item->anim_num].frame_base + 21; + return; + } + + if (item->anim_num == LA_REACH_TO_HANG + && item->frame_num == g_Anims[item->anim_num].frame_base + 21 + && Lara_TestClimbStance(item, coll)) { + item->goal_anim_state = LS_CLIMB_STANCE; + } + return; + } + + if (!(g_Input & IN_ACTION) || item->hit_points <= 0 + || coll->side_front.floor > 0) { + item->goal_anim_state = LS_UP_JUMP; + item->current_anim_state = LS_UP_JUMP; + item->anim_num = LA_JUMP_UP; + item->frame_num = g_Anims[item->anim_num].frame_base + 9; + const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); + item->pos.y += bounds->max_y; + item->pos.x += coll->shift.x; + item->pos.z += coll->shift.z; + item->gravity = 1; + item->speed = 2; + item->fall_speed = 1; + g_Lara.gun_status = LGS_ARMLESS; + return; + } + + const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); + int32_t hdif = coll->side_front.floor - bounds->min_y; + + if (ABS(coll->side_left.floor - coll->side_right.floor) >= SLOPE_DIF + || coll->side_mid.ceiling >= 0 || coll->coll_type != COLL_FRONT || flag + || coll->hit_static || hdif < -SLOPE_DIF || hdif > SLOPE_DIF) { + item->pos.x = coll->old.x; + item->pos.y = coll->old.y; + item->pos.z = coll->old.z; + if (item->current_anim_state == LS_HANG_LEFT + || item->current_anim_state == LS_HANG_RIGHT) { + item->goal_anim_state = LS_HANG; + item->current_anim_state = LS_HANG; + item->anim_num = LA_REACH_TO_HANG; + item->frame_num = g_Anims[item->anim_num].frame_base + 21; + } + return; + } + + switch (dir) { + case DIR_NORTH: + case DIR_SOUTH: + item->pos.z += coll->shift.z; + break; + + case DIR_EAST: + case DIR_WEST: + item->pos.x += coll->shift.x; + break; + + default: + break; + } + + item->pos.y += hdif; +} + +int32_t __cdecl Lara_TestEdgeCatch(ITEM *item, COLL_INFO *coll, int32_t *edge) +{ + const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); + int32_t hdif1 = coll->side_front.floor - bounds->min_y; + int32_t hdif2 = hdif1 + item->fall_speed; + if ((hdif1 < 0 && hdif2 < 0) || (hdif1 > 0 && hdif2 > 0)) { + hdif1 = item->pos.y + bounds->min_y; + hdif2 = hdif1 + item->fall_speed; + if ((hdif1 >> (WALL_SHIFT - 2)) == (hdif2 >> (WALL_SHIFT - 2))) { + return 0; + } + if (item->fall_speed > 0) { + *edge = hdif2 & ~(STEP_L - 1); + } else { + *edge = hdif1 & ~(STEP_L - 1); + } + return -1; + } + + return ABS(coll->side_left.floor - coll->side_right.floor) < SLOPE_DIF; +} + +int32_t __cdecl Lara_TestHangJumpUp(ITEM *item, COLL_INFO *coll) +{ + if (coll->coll_type != COLL_FRONT || !(g_Input & IN_ACTION) + || g_Lara.gun_status != LGS_ARMLESS || coll->hit_static + || coll->side_mid.ceiling > -STEPUP_HEIGHT) { + return 0; + } + + int32_t edge; + int32_t edge_catch = Lara_TestEdgeCatch(item, coll, &edge); + if (!edge_catch + || (edge_catch < 0 && !Lara_TestHangOnClimbWall(item, coll))) { + return 0; + } + + DIRECTION dir = Math_GetDirectionCone(item->rot.y, LARA_HANG_ANGLE); + if (dir == DIR_UNKNOWN) { + return 0; + } + int16_t angle = Math_DirectionToAngle(dir); + + item->goal_anim_state = LS_HANG; + item->current_anim_state = LS_HANG; + item->anim_num = LA_REACH_TO_HANG; + item->frame_num = g_Anims[item->anim_num].frame_base + 12; + + const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); + if (edge_catch > 0) { + item->pos.y += coll->side_front.floor - bounds->min_y; + } else { + item->pos.y = edge - bounds->min_y; + } + item->pos.x += coll->shift.x; + item->pos.z += coll->shift.z; + item->rot.y = angle; + item->speed = 0; + item->gravity = 0; + item->fall_speed = 0; + g_Lara.gun_status = LGS_HANDS_BUSY; + return 1; +} + +int32_t __cdecl Lara_TestHangJump(ITEM *item, COLL_INFO *coll) +{ + if (coll->coll_type != COLL_FRONT || !(g_Input & IN_ACTION) + || g_Lara.gun_status != LGS_ARMLESS || coll->hit_static + || coll->side_mid.ceiling > -STEPUP_HEIGHT + || coll->side_mid.floor < 200) { + return 0; + } + + int32_t edge; + int32_t edge_catch = Lara_TestEdgeCatch(item, coll, &edge); + if (!edge_catch + || (edge_catch < 0 && !Lara_TestHangOnClimbWall(item, coll))) { + return 0; + } + + DIRECTION dir = Math_GetDirectionCone(item->rot.y, LARA_HANG_ANGLE); + if (dir == DIR_UNKNOWN) { + return 0; + } + int16_t angle = Math_DirectionToAngle(dir); + + if (Lara_TestHangSwingIn(item, angle)) { + item->anim_num = LA_REACH_TO_THIN_LEDGE; + item->frame_num = g_Anims[item->anim_num].frame_base; + } else { + item->anim_num = LA_REACH_TO_HANG; + item->frame_num = g_Anims[item->anim_num].frame_base; + } + item->current_anim_state = LS_HANG; + item->goal_anim_state = LS_HANG; + + const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); + if (edge_catch > 0) { + item->pos.y += coll->side_front.floor - bounds->min_y; + item->pos.x += coll->shift.x; + item->pos.z += coll->shift.z; + } else { + item->pos.y = edge - bounds->min_y; + } + + item->rot.y = angle; + item->speed = 2; + item->gravity = 1; + item->fall_speed = 1; + g_Lara.gun_status = LGS_HANDS_BUSY; + return 1; +} + +int32_t __cdecl Lara_TestHangSwingIn(ITEM *item, PHD_ANGLE angle) +{ + int32_t x = item->pos.x; + int32_t y = item->pos.y; + int32_t z = item->pos.z; + int16_t room_num = item->room_num; + switch (angle) { + case 0: + z += STEP_L; + break; + case PHD_90: + x += STEP_L; + break; + case -PHD_180: + z -= STEP_L; + break; + case -PHD_90: + x -= STEP_L; + break; + } + + const SECTOR *const sector = Room_GetSector(x, y, z, &room_num); + int32_t height = Room_GetHeight(sector, x, y, z); + int32_t ceiling = Room_GetCeiling(sector, x, y, z); + return height != NO_HEIGHT && height - y > 0 && ceiling - y < -400; +} + +int32_t __cdecl Lara_TestVault(ITEM *item, COLL_INFO *coll) +{ + if (coll->coll_type != COLL_FRONT || !(g_Input & IN_ACTION) + || g_Lara.gun_status != LGS_ARMLESS) { + return 0; + } + + DIRECTION dir = Math_GetDirectionCone(item->rot.y, LARA_VAULT_ANGLE); + if (dir == DIR_UNKNOWN) { + return 0; + } + int16_t angle = Math_DirectionToAngle(dir); + + int32_t left_floor = coll->side_left.floor; + int32_t left_ceiling = coll->side_left.ceiling; + int32_t right_floor = coll->side_right.floor; + int32_t right_ceiling = coll->side_right.ceiling; + int32_t front_floor = coll->side_front.floor; + int32_t front_ceiling = coll->side_front.ceiling; + bool slope = ABS(left_floor - right_floor) >= SLOPE_DIF; + + int32_t mid = STEP_L / 2; + if (front_floor >= -STEP_L * 2 - mid && front_floor <= -STEP_L * 2 + mid) { + if (slope || front_floor - front_ceiling < 0 + || left_floor - left_ceiling < 0 + || right_floor - right_ceiling < 0) { + return 0; + } + item->goal_anim_state = LS_STOP; + item->current_anim_state = LS_NULL; + item->anim_num = LA_CLIMB_2CLICK; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->pos.y += front_floor + STEP_L * 2; + g_Lara.gun_status = LGS_HANDS_BUSY; + } else if ( + front_floor >= -STEP_L * 3 - mid && front_floor <= -STEP_L * 3 + mid) { + if (slope || front_floor - front_ceiling < 0 + || left_floor - left_ceiling < 0 + || right_floor - right_ceiling < 0) { + return 0; + } + item->goal_anim_state = LS_STOP; + item->current_anim_state = LS_NULL; + item->anim_num = LA_CLIMB_3CLICK; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->pos.y += front_floor + STEP_L * 3; + g_Lara.gun_status = LGS_HANDS_BUSY; + } else if ( + !slope && front_floor >= -STEP_L * 7 - mid + && front_floor <= -STEP_L * 4 + mid) { + item->goal_anim_state = LS_UP_JUMP; + item->current_anim_state = LS_STOP; + item->anim_num = LA_STAND_STILL; + item->frame_num = g_Anims[item->anim_num].frame_base; + g_Lara.calc_fallspeed = + -(Math_Sqrt(-2 * GRAVITY * (front_floor + 800)) + 3); + Lara_Animate(item); + } else if ( + g_Lara.climb_status && front_floor <= -1920 + && g_Lara.water_status != LWS_WADE && left_floor <= -STEP_L * 8 + mid + && right_floor <= -STEP_L * 8 + && coll->side_mid.ceiling <= -STEP_L * 8 + mid + LARA_HEIGHT) { + item->goal_anim_state = LS_UP_JUMP; + item->current_anim_state = LS_STOP; + item->anim_num = LA_STAND_STILL; + item->frame_num = g_Anims[item->anim_num].frame_base; + g_Lara.calc_fallspeed = -116; + Lara_Animate(item); + } else if ( + g_Lara.climb_status + && (front_floor < -STEP_L * 4 || front_ceiling >= LARA_HEIGHT - STEP_L) + && coll->side_mid.ceiling <= -STEP_L * 5 + LARA_HEIGHT) { + Item_ShiftCol(item, coll); + if (Lara_TestClimbStance(item, coll)) { + item->goal_anim_state = LS_CLIMB_STANCE; + item->current_anim_state = LS_STOP; + item->anim_num = LA_STAND_STILL; + item->frame_num = g_Anims[item->anim_num].frame_base; + Lara_Animate(item); + item->rot.y = angle; + g_Lara.gun_status = LGS_HANDS_BUSY; + return 1; + } + return 0; + } else { + return 0; + } + + item->rot.y = angle; + Item_ShiftCol(item, coll); + return 1; +} + +int32_t __cdecl Lara_TestSlide(ITEM *item, COLL_INFO *coll) +{ + if (ABS(coll->x_tilt) <= 2 && ABS(coll->z_tilt) <= 2) { + return 0; + } + + int16_t angle = 0; + if (coll->x_tilt > 2) { + angle = -PHD_90; + } else if (coll->x_tilt < -2) { + angle = PHD_90; + } + + if (coll->z_tilt > 2 && coll->z_tilt > ABS(coll->x_tilt)) { + angle = PHD_180; + } else if (coll->z_tilt < -2 && -coll->z_tilt > ABS(coll->x_tilt)) { + angle = 0; + } + + const int16_t angle_dif = angle - item->rot.y; + Item_ShiftCol(item, coll); + + if (angle_dif >= -PHD_90 && angle_dif <= PHD_90) { + if (item->current_anim_state == LS_SLIDE + && g_LaraOldSlideAngle == angle) { + return 1; + } + item->goal_anim_state = LS_SLIDE; + item->current_anim_state = LS_SLIDE; + item->anim_num = LA_SLIDE_FORWARD; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->rot.y = angle; + } else { + if (item->current_anim_state == LS_SLIDE_BACK + && g_LaraOldSlideAngle == angle) { + return 1; + } + item->goal_anim_state = LS_SLIDE_BACK; + item->current_anim_state = LS_SLIDE_BACK; + item->anim_num = LA_SLIDE_BACKWARD_START; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->rot.y = angle + PHD_180; + } + + g_Lara.move_angle = angle; + g_LaraOldSlideAngle = angle; + return 1; +} + +int16_t __cdecl Lara_FloorFront(ITEM *item, int16_t ang, int32_t dist) +{ + const int32_t x = item->pos.x + ((dist * Math_Sin(ang)) >> W2V_SHIFT); + const int32_t y = item->pos.y - LARA_HEIGHT; + const int32_t z = item->pos.z + ((dist * Math_Cos(ang)) >> W2V_SHIFT); + int16_t room_num = item->room_num; + const SECTOR *const sector = Room_GetSector(x, y, z, &room_num); + int32_t height = Room_GetHeight(sector, x, y, z); + if (height != NO_HEIGHT) { + height -= item->pos.y; + } + return height; +} + +int32_t __cdecl Lara_LandedBad(ITEM *item, COLL_INFO *coll) +{ + const int32_t x = item->pos.x; + const int32_t y = item->pos.y; + const int32_t z = item->pos.z; + + int16_t room_num = item->room_num; + const SECTOR *const sector = Room_GetSector(x, y, z, &room_num); + const int32_t height = Room_GetHeight(sector, x, y - LARA_HEIGHT, z); + item->pos.y = height; + item->floor = height; + + Room_TestTriggers(g_TriggerIndex, 0); + int32_t land_speed = item->fall_speed - DAMAGE_START; + item->pos.y = y; + if (land_speed <= 0) { + return 0; + } + if (land_speed <= DAMAGE_LENGTH) { + item->hit_points += -LARA_MAX_HITPOINTS * land_speed * land_speed + / (DAMAGE_LENGTH * DAMAGE_LENGTH); + } else { + item->hit_points = -1; + } + return item->hit_points <= 0; +} + +int32_t __cdecl Lara_CheckForLetGo(ITEM *item, COLL_INFO *coll) +{ + item->gravity = 0; + item->fall_speed = 0; + + int16_t room_num = item->room_num; + int32_t x = item->pos.x; + int32_t y = item->pos.y; + int32_t z = item->pos.z; + const SECTOR *const sector = Room_GetSector(x, y, z, &room_num); + Room_GetHeight(sector, x, y, z); + coll->trigger = g_TriggerIndex; + if ((g_Input & IN_ACTION) && item->hit_points > 0) { + return 0; + } + + item->goal_anim_state = LS_FORWARD_JUMP; + item->current_anim_state = LS_FORWARD_JUMP; + item->anim_num = LA_FALL_START; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->gravity = 1; + item->speed = 2; + item->fall_speed = 1; + g_Lara.gun_status = LGS_ARMLESS; + return 1; +} + +void __cdecl Lara_GetJointAbsPosition(XYZ_32 *vec, int32_t joint) +{ + FRAME_INFO *frmptr[2] = { NULL, NULL }; + if (g_Lara.hit_direction < 0) { + int32_t rate; + int32_t frac = Item_GetFrames(g_LaraItem, frmptr, &rate); + if (frac) { + Lara_GetJointAbsPosition_I( + g_LaraItem, vec, frmptr[0], frmptr[1], frac, rate); + return; + } + } + + const FRAME_INFO *frame_ptr = NULL; + const OBJECT *obj = &g_Objects[g_LaraItem->object_id]; + if (g_Lara.hit_direction >= 0) { + LARA_ANIMATION anim_num; + switch (g_Lara.hit_direction) { + case DIR_EAST: + anim_num = LA_HIT_RIGHT; + break; + case DIR_SOUTH: + anim_num = LA_HIT_BACK; + break; + case DIR_WEST: + anim_num = LA_HIT_LEFT; + break; + default: + anim_num = LA_HIT_FRONT; + break; + } + const ANIM *anim = &g_Anims[anim_num]; + int32_t interpolation = anim->interpolation; + frame_ptr = (const FRAME_INFO *)(anim->frame_ptr + + (int32_t)(g_Lara.hit_frame + * (interpolation >> 8))); + } else { + frame_ptr = frmptr[0]; + } + + Matrix_PushUnit(); + g_MatrixPtr->_03 = 0; + g_MatrixPtr->_13 = 0; + g_MatrixPtr->_23 = 0; + Matrix_RotYXZ(g_LaraItem->rot.y, g_LaraItem->rot.x, g_LaraItem->rot.z); + + const int16_t *rot = frame_ptr->mesh_rots; + const int32_t *bone = &g_AnimBones[obj->bone_idx]; + + Matrix_TranslateRel( + frame_ptr->offset.x, frame_ptr->offset.y, frame_ptr->offset.z); + Matrix_RotYXZsuperpack(&rot, 0); + + Matrix_TranslateRel(bone[25], bone[26], bone[27]); + Matrix_RotYXZsuperpack(&rot, 6); + Matrix_RotYXZ(g_Lara.torso_y_rot, g_Lara.torso_x_rot, g_Lara.torso_z_rot); + + LARA_GUN_TYPE gun_type = LGT_UNARMED; + if (g_Lara.gun_status == LGS_READY || g_Lara.gun_status == LGS_SPECIAL + || g_Lara.gun_status == LGS_DRAW || g_Lara.gun_status == LGS_UNDRAW) { + gun_type = g_Lara.gun_type; + } + + if (g_Lara.gun_type == LGT_FLARE) { + Matrix_TranslateRel(bone[41], bone[42], bone[43]); + if (g_Lara.flare_control_left) { + const LARA_ARM *arm = &g_Lara.left_arm; + const ANIM *anim = &g_Anims[arm->anim_num]; + rot = &arm->frame_base + [(anim->interpolation >> 8) + * (arm->frame_num - anim->frame_base) + + FBBOX_ROT]; + } else { + rot = frame_ptr->mesh_rots; + } + Matrix_RotYXZsuperpack(&rot, 11); + + Matrix_TranslateRel(bone[45], bone[46], bone[47]); + Matrix_RotYXZsuperpack(&rot, 0); + + Matrix_TranslateRel(bone[49], bone[50], bone[51]); + Matrix_RotYXZsuperpack(&rot, 0); + } else if (gun_type != LGT_UNARMED) { + Matrix_TranslateRel(bone[29], bone[30], bone[31]); + + const LARA_ARM *arm = &g_Lara.right_arm; + const ANIM *anim = &g_Anims[arm->anim_num]; + rot = &arm->frame_base + [arm->frame_num * (anim->interpolation >> 8) + FBBOX_ROT]; + Matrix_RotYXZsuperpack(&rot, 8); + + Matrix_TranslateRel(bone[33], bone[34], bone[35]); + Matrix_RotYXZsuperpack(&rot, 0); + + Matrix_TranslateRel(bone[37], bone[38], bone[39]); + Matrix_RotYXZsuperpack(&rot, 0); + } + + Matrix_TranslateRel(vec->x, vec->y, vec->z); + vec->x = g_LaraItem->pos.x + (g_MatrixPtr->_03 >> W2V_SHIFT); + vec->y = g_LaraItem->pos.y + (g_MatrixPtr->_13 >> W2V_SHIFT); + vec->z = g_LaraItem->pos.z + (g_MatrixPtr->_23 >> W2V_SHIFT); + Matrix_Pop(); +} + +void __cdecl Lara_GetJointAbsPosition_I( + ITEM *item, XYZ_32 *vec, FRAME_INFO *frame1, FRAME_INFO *frame2, + int32_t frac, int32_t rate) +{ + const OBJECT *obj = &g_Objects[item->object_id]; + + Matrix_PushUnit(); + g_MatrixPtr->_03 = 0; + g_MatrixPtr->_13 = 0; + g_MatrixPtr->_23 = 0; + Matrix_RotYXZ(item->rot.y, item->rot.x, item->rot.z); + + const int32_t *const bone = &g_AnimBones[obj->bone_idx]; + const int16_t *rot1 = frame1->mesh_rots; + const int16_t *rot2 = frame2->mesh_rots; + Matrix_InitInterpolate(frac, rate); + + Matrix_TranslateRel_ID( + frame1->offset.x, frame1->offset.y, frame1->offset.z, frame2->offset.x, + frame2->offset.y, frame2->offset.z); + Matrix_RotYXZsuperpack_I(&rot1, &rot2, 0); + + Matrix_TranslateRel_I(bone[25], bone[26], bone[27]); + Matrix_RotYXZsuperpack_I(&rot1, &rot2, 6); + Matrix_RotYXZ_I(g_Lara.torso_y_rot, g_Lara.torso_x_rot, g_Lara.torso_z_rot); + + LARA_GUN_TYPE gun_type = LGT_UNARMED; + if (g_Lara.gun_status == LGS_READY || g_Lara.gun_status == LGS_SPECIAL + || g_Lara.gun_status == LGS_DRAW || g_Lara.gun_status == LGS_UNDRAW) { + gun_type = g_Lara.gun_type; + } + + if (g_Lara.gun_type == LGT_FLARE) { + Matrix_Interpolate(); + Matrix_TranslateRel(bone[29], bone[30], bone[31]); + if (g_Lara.flare_control_left) { + const LARA_ARM *arm = &g_Lara.left_arm; + const ANIM *anim = &g_Anims[arm->anim_num]; + rot1 = &arm->frame_base + [(anim->interpolation >> 8) + * (arm->frame_num - anim->frame_base) + + FBBOX_ROT]; + } else { + rot1 = frame1->mesh_rots; + } + Matrix_RotYXZsuperpack(&rot1, 11); + + Matrix_TranslateRel(bone[45], bone[46], bone[47]); + Matrix_RotYXZsuperpack(&rot1, 0); + + Matrix_TranslateRel(bone[49], bone[50], bone[51]); + Matrix_RotYXZsuperpack(&rot1, 0); + } else if (gun_type != LGT_UNARMED) { + Matrix_Interpolate(); + Matrix_TranslateRel(bone[29], bone[30], bone[31]); + + const LARA_ARM *arm = &g_Lara.right_arm; + const ANIM *anim = &g_Anims[arm->anim_num]; + rot1 = &arm->frame_base + [arm->frame_num * (anim->interpolation >> 8) + FBBOX_ROT]; + Matrix_RotYXZsuperpack(&rot1, 8); + + Matrix_TranslateRel(bone[33], bone[34], bone[35]); + Matrix_RotYXZsuperpack(&rot1, 0); + + Matrix_TranslateRel(bone[37], bone[38], bone[39]); + Matrix_RotYXZsuperpack(&rot1, 0); + } + + Matrix_TranslateRel(vec->x, vec->y, vec->z); + vec->x = item->pos.x + (g_MatrixPtr->_03 >> W2V_SHIFT); + vec->y = item->pos.y + (g_MatrixPtr->_13 >> W2V_SHIFT); + vec->z = item->pos.z + (g_MatrixPtr->_23 >> W2V_SHIFT); + + Matrix_Pop(); +} + +void __cdecl Lara_TakeHit(ITEM *const lara_item, const COLL_INFO *const coll) +{ + const int32_t dx = g_Lara.spaz_effect->pos.x - lara_item->pos.x; + const int32_t dz = g_Lara.spaz_effect->pos.z - lara_item->pos.z; + M_TakeHit(lara_item, dx, dz); + g_Lara.spaz_effect_count--; +} + +void __cdecl Lara_BaddieCollision(ITEM *lara_item, COLL_INFO *coll) +{ + lara_item->hit_status = 0; + g_Lara.hit_direction = -1; + if (lara_item->hit_points <= 0) { + return; + } + + int16_t roomies[MAX_BADDIE_COLLISION] = { 0 }; + int32_t roomies_count = 0; + + roomies[roomies_count++] = lara_item->room_num; + + PORTALS *portals = g_Rooms[roomies[0]].portals; + if (portals != NULL) { + for (int32_t i = 0; i < portals->count; i++) { + if (roomies_count >= MAX_BADDIE_COLLISION) { + break; + } + roomies[roomies_count++] = portals->portal[i].room_num; + } + } + + for (int32_t i = 0; i < roomies_count; i++) { + int16_t item_num = g_Rooms[roomies[i]].item_num; + while (item_num != NO_ITEM) { + const ITEM *const item = &g_Items[item_num]; + + // the collision routine can destroy the item - need to store the + // next item beforehand + const int16_t next_item_num = item->next_item; + + if (item->collidable && item->status != IS_INVISIBLE) { + const OBJECT *const object = &g_Objects[item->object_id]; + if (object->collision) { + // clang-format off + const XYZ_32 d = { + .x = lara_item->pos.x - item->pos.x, + .y = lara_item->pos.y - item->pos.y, + .z = lara_item->pos.z - item->pos.z, + }; + if (d.x > -TARGET_DIST && d.x < TARGET_DIST && + d.y > -TARGET_DIST && d.y < TARGET_DIST && + d.z > -TARGET_DIST && d.z < TARGET_DIST) { + object->collision(item_num, lara_item, coll); + } + // clang-format on + } + } + + item_num = next_item_num; + } + } + + if (g_Lara.spaz_effect_count) { + Lara_TakeHit(lara_item, coll); + } + + if (g_Lara.hit_direction == -1) { + g_Lara.hit_frame = 0; + } + + g_Inv_Chosen = -1; +} + +void __cdecl Lara_Push( + const ITEM *const item, ITEM *const lara_item, COLL_INFO *const coll, + const bool spaz_on, const bool big_push) +{ + int32_t dx = lara_item->pos.x - item->pos.x; + int32_t dz = lara_item->pos.z - item->pos.z; + const int32_t c = Math_Cos(item->rot.y); + const int32_t s = Math_Sin(item->rot.y); + int32_t rx = (c * dx - s * dz) >> W2V_SHIFT; + int32_t rz = (c * dz + s * dx) >> W2V_SHIFT; + + const BOUNDS_16 *const bounds = &Item_GetBestFrame(item)->bounds; + int32_t min_x = bounds->min_x; + int32_t max_x = bounds->max_x; + int32_t min_z = bounds->min_z; + int32_t max_z = bounds->max_z; + + if (big_push) { + max_x += coll->radius; + min_z -= coll->radius; + max_z += coll->radius; + min_x -= coll->radius; + } + + if (rx < min_x || rx > max_x || rz < min_z || rz > max_z) { + return; + } + + int32_t l = rx - min_x; + int32_t r = max_x - rx; + int32_t t = max_z - rz; + int32_t b = rz - min_z; + + if (l <= r && l <= t && l <= b) { + rx -= l; + } else if (r <= l && r <= t && r <= b) { + rx += r; + } else if (t <= l && t <= r && t <= b) { + rz += t; + } else { + rz = min_z; + } + + lara_item->pos.x = item->pos.x + ((rz * s + rx * c) >> W2V_SHIFT); + lara_item->pos.z = item->pos.z + ((rz * c - rx * s) >> W2V_SHIFT); + + rz = (bounds->max_z + bounds->min_z) / 2; + rx = (bounds->max_x + bounds->min_x) / 2; + dx -= (c * rx + s * rz) >> W2V_SHIFT; + dz -= (c * rz - s * rx) >> W2V_SHIFT; + + if (spaz_on && bounds->max_y - bounds->min_y > STEP_L) { + M_TakeHit(lara_item, dx, dz); + } + + int16_t old_facing = coll->facing; + coll->bad_pos = NO_BAD_POS; + coll->bad_neg = -STEPUP_HEIGHT; + coll->bad_ceiling = 0; + coll->facing = Math_Atan( + lara_item->pos.z - coll->old.z, lara_item->pos.x - coll->old.x); + Collide_GetCollisionInfo( + coll, lara_item->pos.x, lara_item->pos.y, lara_item->pos.z, + lara_item->room_num, LARA_HEIGHT); + coll->facing = old_facing; + + if (coll->coll_type != COLL_NONE) { + lara_item->pos.x = coll->old.x; + lara_item->pos.z = coll->old.z; + } else { + coll->old.x = lara_item->pos.x; + coll->old.y = lara_item->pos.y; + coll->old.z = lara_item->pos.z; + Item_UpdateRoom(lara_item, -WALL_SHIFT); + } +} + +int32_t __cdecl Lara_MovePosition(XYZ_32 *vec, ITEM *item, ITEM *lara_item) +{ + const XYZ_16 rot = { + .x = item->rot.x, + .y = item->rot.y, + .z = item->rot.z, + }; + + Matrix_PushUnit(); + Matrix_RotYXZ(rot.y, rot.x, rot.z); + const MATRIX *const m = g_MatrixPtr; + const XYZ_32 shift = { + .x = (vec->y * m->_01 + vec->z * m->_02 + vec->x * m->_00) >> W2V_SHIFT, + .y = (vec->x * m->_10 + vec->z * m->_12 + vec->y * m->_11) >> W2V_SHIFT, + .z = (vec->y * m->_21 + vec->x * m->_20 + vec->z * m->_22) >> W2V_SHIFT, + }; + Matrix_Pop(); + + const XYZ_32 new_pos = { + .x = item->pos.x + shift.x, + .y = item->pos.y + shift.y, + .z = item->pos.z + shift.z, + }; + + if (item->object_id == O_FLARE_ITEM) { + int16_t room_num = lara_item->room_num; + const SECTOR *const sector = + Room_GetSector(new_pos.x, new_pos.y, new_pos.z, &room_num); + const int32_t height = + Room_GetHeight(sector, new_pos.x, new_pos.y, new_pos.z); + if (ABS(height - lara_item->pos.y) > STEP_L * 2) { + return false; + } + if (XYZ_32_GetDistance(&new_pos, &lara_item->pos) < STEP_L) { + return true; + } + } + + // TODO: get rid of this conversion + const PHD_3DPOS new_pos_full = { + .pos = new_pos, + .rot = rot, + }; + PHD_3DPOS src_pos = { + .pos = lara_item->pos, + .rot = lara_item->rot, + }; + const int32_t result = + Misc_Move3DPosTo3DPos(&src_pos, &new_pos_full, MOVE_SPEED, MOVE_ANGLE); + lara_item->pos = src_pos.pos; + lara_item->rot = src_pos.rot; + return result; +} + +int32_t __cdecl Lara_IsNearItem(const XYZ_32 *const pos, const int32_t distance) +{ + return Item_IsNearItem(g_LaraItem, pos, distance); +} + +int32_t __cdecl Lara_TestClimb( + const int32_t x, const int32_t y, const int32_t z, const int32_t x_front, + const int32_t z_front, const int32_t item_height, const int16_t item_room, + int32_t *const shift) +{ + *shift = 0; + bool hang = true; + if (!g_Lara.climb_status) { + return 0; + } + + const SECTOR *sector; + int32_t height; + int32_t ceiling; + + int16_t room_num = item_room; + sector = Room_GetSector(x, y - 128, z, &room_num); + height = Room_GetHeight(sector, x, y, z); + if (height == NO_HEIGHT) { + return 0; + } + + height -= y + item_height + STEP_L / 2; + if (height < -CLIMB_SHIFT) { + return 0; + } + if (height < 0) { + *shift = height; + } + + ceiling = Room_GetCeiling(sector, x, y, z) - y; + if (ceiling > CLIMB_SHIFT) { + return 0; + } + if (ceiling > 0) { + if (*shift) { + return 0; + } + *shift = ceiling; + } + + if (item_height + height < CLIMB_HANG) { + hang = false; + } + + const int32_t x2 = x + x_front; + const int32_t z2 = z + z_front; + sector = Room_GetSector(x2, y, z2, &room_num); + height = Room_GetHeight(sector, x2, y, z2); + if (height != NO_HEIGHT) { + height -= y; + } + + if (height > CLIMB_SHIFT) { + ceiling = Room_GetCeiling(sector, x2, y, z2) - y; + if (ceiling >= LARA_CLIMB_HEIGHT) { + return 1; + } + + if (ceiling > LARA_CLIMB_HEIGHT - CLIMB_SHIFT) { + if (*shift > 0) { + return hang ? -1 : 0; + } + *shift = ceiling - LARA_CLIMB_HEIGHT; + return 1; + } + + if (ceiling > 0) { + return hang ? -1 : 0; + } + + if (ceiling > -CLIMB_SHIFT && hang && *shift <= 0) { + if (*shift > ceiling) { + *shift = ceiling; + } + + return -1; + } + + return 0; + } + + if (height > 0) { + if (*shift < 0) { + return 0; + } + if (height > *shift) { + *shift = height; + } + } + + room_num = item_room; + sector = Room_GetSector(x, item_height + y, z, &room_num); + sector = Room_GetSector(x2, item_height + y, z2, &room_num); + ceiling = Room_GetCeiling(sector, x2, item_height + y, z2); + if (ceiling == NO_HEIGHT) { + return 1; + } + + ceiling -= y; + if (ceiling <= height) { + return 1; + } + + if (ceiling >= LARA_CLIMB_HEIGHT) { + return 1; + } + + if (ceiling > LARA_CLIMB_HEIGHT - CLIMB_SHIFT) { + if (*shift > 0) { + return hang ? -1 : 0; + } + *shift = ceiling - LARA_CLIMB_HEIGHT; + return 1; + } + + return hang ? -1 : 0; +} + +int32_t __cdecl Lara_TestClimbPos( + const ITEM *const item, const int32_t front, const int32_t right, + const int32_t origin, const int32_t height, int32_t *const shift) +{ + const int32_t y = item->pos.y + origin; + int32_t x; + int32_t z; + int32_t x_front = 0; + int32_t z_front = 0; + + switch (Math_GetDirection(item->rot.y)) { + case DIR_NORTH: + x = item->pos.x + right; + z = item->pos.z + front; + z_front = 2; + break; + + case DIR_EAST: + x = item->pos.x + front; + z = item->pos.z - right; + x_front = 2; + break; + + case DIR_SOUTH: + x = item->pos.x - right; + z = item->pos.z - front; + z_front = -2; + break; + + case DIR_WEST: + x = item->pos.x - front; + z = item->pos.z + right; + x_front = -2; + break; + + default: + x = front; + z = front; + break; + } + + return Lara_TestClimb( + x, y, z, x_front, z_front, height, item->room_num, shift); +} + +void __cdecl Lara_DoClimbLeftRight( + ITEM *const item, const COLL_INFO *const coll, const int32_t result, + const int32_t shift) +{ + if (result == 1) { + if (g_Input & IN_LEFT) { + item->goal_anim_state = LS_CLIMB_LEFT; + } else if (g_Input & IN_RIGHT) { + item->goal_anim_state = LS_CLIMB_RIGHT; + } else { + item->goal_anim_state = LS_CLIMB_STANCE; + } + item->pos.y += shift; + return; + } + + if (result) { + item->goal_anim_state = LS_HANG; + do { + Item_Animate(item); + } while (item->current_anim_state != LS_HANG); + item->pos.x = coll->old.x; + item->pos.z = coll->old.z; + return; + } + + item->pos.x = coll->old.x; + item->pos.z = coll->old.z; + item->goal_anim_state = LS_CLIMB_STANCE; + item->current_anim_state = LS_CLIMB_STANCE; + if (coll->old_anim_state == LS_CLIMB_STANCE) { + item->frame_num = coll->old_frame_num; + item->anim_num = coll->old_anim_num; + Lara_Animate(item); + } else { + item->anim_num = LA_LADDER_IDLE; + item->frame_num = g_Anims[item->anim_num].frame_base; + } +} + +int32_t __cdecl Lara_TestClimbUpPos( + const ITEM *const item, const int32_t front, const int32_t right, + int32_t *const shift, int32_t *const ledge) +{ + const int32_t y = item->pos.y - LARA_CLIMB_HEIGHT - STEP_L; + int32_t x; + int32_t z; + int32_t x_front = 0; + int32_t z_front = 0; + + switch (Math_GetDirection(item->rot.y)) { + case DIR_NORTH: + x = item->pos.x + right; + z = item->pos.z + front; + z_front = 2; + break; + + case DIR_EAST: + x = item->pos.x + front; + z = item->pos.z - right; + x_front = 2; + break; + + case DIR_SOUTH: + x = item->pos.x - right; + z = item->pos.z - front; + z_front = -2; + break; + + case DIR_WEST: + z = item->pos.z + right; + x = item->pos.x - front; + x_front = -2; + break; + + default: + x = front; + z = front; + break; + } + + *shift = 0; + + const SECTOR *sector; + int32_t height; + int32_t ceiling; + + int16_t room_num = item->room_num; + sector = Room_GetSector(x, y, z, &room_num); + ceiling = Room_GetCeiling(sector, x, y, z) + STEP_L - y; + if (ceiling > CLIMB_SHIFT) { + return 0; + } + + if (ceiling > 0) { + *shift = ceiling; + } + + const int32_t x2 = x + x_front; + const int32_t z2 = z + z_front; + sector = Room_GetSector(x2, y, z2, &room_num); + height = Room_GetHeight(sector, x2, y, z2); + if (height == NO_HEIGHT) { + *ledge = NO_HEIGHT; + return 1; + } + + height -= y; + *ledge = height; + if (height > STEP_L / 2) { + ceiling = Room_GetCeiling(sector, x2, y, z2) - y; + if (ceiling >= LARA_CLIMB_HEIGHT) { + return 1; + } + + if (height - ceiling > LARA_HEIGHT) { + *shift = height; + return -1; + } + + return 0; + } + + if (height > 0 && height > *shift) { + *shift = height; + } + + room_num = item->room_num; + sector = Room_GetSector(x, y + LARA_CLIMB_HEIGHT, z, &room_num); + sector = Room_GetSector(x2, y + LARA_CLIMB_HEIGHT, z2, &room_num); + ceiling = Room_GetCeiling(sector, x2, y + LARA_CLIMB_HEIGHT, z2) - y; + if (ceiling <= height) { + return 1; + } + + if (ceiling >= LARA_CLIMB_HEIGHT) { + return 1; + } + return 0; +} + +int32_t __cdecl Lara_GetWaterDepth( + const int32_t x, const int32_t y, const int32_t z, int16_t room_num) +{ + const ROOM *r = &g_Rooms[room_num]; + const SECTOR *sector; + + while (true) { + int32_t z_sector = (z - r->pos.z) >> WALL_SHIFT; + int32_t x_sector = (x - r->pos.x) >> WALL_SHIFT; + + if (z_sector <= 0) { + z_sector = 0; + if (x_sector < 1) { + x_sector = 1; + } else if (x_sector > r->size.x - 2) { + x_sector = r->size.x - 2; + } + } else if (z_sector >= r->size.z - 1) { + z_sector = r->size.z - 1; + if (x_sector < 1) { + x_sector = 1; + } else if (x_sector > r->size.x - 2) { + x_sector = r->size.x - 2; + } + } else if (x_sector < 0) { + x_sector = 0; + } else if (x_sector >= r->size.x) { + x_sector = r->size.x - 1; + } + + sector = &r->sectors[z_sector + x_sector * r->size.z]; + const int16_t data = Room_GetDoor(sector); + if (data == NO_ROOM) { + break; + } + room_num = data; + r = &g_Rooms[room_num]; + } + + if (r->flags & RF_UNDERWATER) { + while (sector->sky_room != NO_ROOM) { + r = &g_Rooms[sector->sky_room]; + if (!(r->flags & RF_UNDERWATER)) { + const int32_t water_height = sector->ceiling << 8; + sector = Room_GetSector(x, y, z, &room_num); + return Room_GetHeight(sector, x, y, z) - water_height; + } + const int32_t z_sector = (z - r->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (x - r->pos.x) >> WALL_SHIFT; + sector = &r->sectors[z_sector + x_sector * r->size.z]; + } + return 0x7FFF; + } + + while (sector->pit_room != NO_ROOM) { + r = &g_Rooms[sector->pit_room]; + if (r->flags & RF_UNDERWATER) { + const int32_t water_height = sector->floor << 8; + sector = Room_GetSector(x, y, z, &room_num); + return Room_GetHeight(sector, x, y, z) - water_height; + } + const int32_t z_sector = (z - r->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (x - r->pos.x) >> WALL_SHIFT; + sector = &r->sectors[z_sector + x_sector * r->size.z]; + } + return NO_HEIGHT; +} + +void __cdecl Lara_TestWaterDepth(ITEM *const item, const COLL_INFO *const coll) +{ + int16_t room_num = item->room_num; + + const SECTOR *const sector = + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + const int32_t water_depth = + Lara_GetWaterDepth(item->pos.x, item->pos.y, item->pos.z, room_num); + + if (water_depth == NO_HEIGHT) { + item->pos = coll->old; + item->fall_speed = 0; + } else if (water_depth <= STEP_L * 2) { + item->anim_num = LA_UNDERWATER_TO_STAND; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->current_anim_state = LS_WATER_OUT; + item->goal_anim_state = LS_STOP; + item->rot.x = 0; + item->rot.z = 0; + item->gravity = 0; + item->speed = 0; + item->fall_speed = 0; + g_Lara.water_status = LWS_WADE; + item->pos.y = + Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); + } +} + +void __cdecl Lara_SwimCollision(ITEM *const item, COLL_INFO *const coll) +{ + if (item->rot.x < -PHD_90 || item->rot.x > PHD_90) { + g_Lara.move_angle = item->rot.y + PHD_180; + } else { + g_Lara.move_angle = item->rot.y; + } + + coll->facing = g_Lara.move_angle; + + int32_t height = (LARA_HEIGHT * Math_Sin(item->rot.x)) >> W2V_SHIFT; + if (height < 0) { + height = -height; + } + CLAMPL(height, 200); + + coll->bad_neg = -height; + Collide_GetCollisionInfo( + coll, item->pos.x, item->pos.y + height / 2, item->pos.z, + item->room_num, height); + Item_ShiftCol(item, coll); + + switch (coll->coll_type) { + case COLL_FRONT: + if (item->rot.x > 35 * PHD_DEGREE) { + item->rot.x += LARA_UW_WALL_DEFLECT; + } else if (item->rot.x < -35 * PHD_DEGREE) { + item->rot.x -= LARA_UW_WALL_DEFLECT; + } else { + item->fall_speed = 0; + } + break; + + case COLL_TOP: + if (item->rot.x >= -45 * PHD_DEGREE) { + item->rot.x -= LARA_UW_WALL_DEFLECT; + } + break; + + case COLL_TOP_FRONT: + item->fall_speed = 0; + break; + + case COLL_LEFT: + item->rot.y += 5 * PHD_DEGREE; + break; + + case COLL_RIGHT: + item->rot.y -= 5 * PHD_DEGREE; + break; + + case COLL_CLAMP: + item->pos = coll->old; + item->fall_speed = 0; + return; + } + + if (coll->side_mid.floor < 0) { + item->rot.x += LARA_UW_WALL_DEFLECT; + item->pos.y = coll->side_mid.floor + item->pos.y; + } + + if (g_Lara.water_status != LWS_CHEAT && !g_Lara.extra_anim) { + Lara_TestWaterDepth(item, coll); + } +} + +void __cdecl Lara_WaterCurrent(COLL_INFO *const coll) +{ + ITEM *const item = g_LaraItem; + + int16_t room_num = g_LaraItem->room_num; + const ROOM *const r = &g_Rooms[g_LaraItem->room_num]; + const int32_t z_sector = (g_LaraItem->pos.z - r->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (g_LaraItem->pos.x - r->pos.x) >> WALL_SHIFT; + g_LaraItem->box_num = r->sectors[z_sector + x_sector * r->size.z].box; + + if (g_Lara.creature == NULL) { + g_Lara.current_active = 0; + return; + } + + XYZ_32 target; + if (Box_CalculateTarget(&target, item, &g_Lara.creature->lot) + == TARGET_NONE) { + return; + } + + target.x -= item->pos.x; + if (target.x > g_Lara.current_active) { + item->pos.x += g_Lara.current_active; + } else if (target.x < -g_Lara.current_active) { + item->pos.x -= g_Lara.current_active; + } else { + item->pos.x += target.x; + } + + target.z -= item->pos.z; + if (target.z > g_Lara.current_active) { + item->pos.z += g_Lara.current_active; + } else if (target.z < -g_Lara.current_active) { + item->pos.z -= g_Lara.current_active; + } else { + item->pos.z += target.z; + } + + target.y = target.y - item->pos.y; + if (target.y > g_Lara.current_active) { + item->pos.y += g_Lara.current_active; + } else if (target.y < -g_Lara.current_active) { + item->pos.y -= g_Lara.current_active; + } else { + item->pos.y += target.y; + } + + g_Lara.current_active = 0; + coll->facing = + Math_Atan(item->pos.z - coll->old.z, item->pos.x - coll->old.x); + Collide_GetCollisionInfo( + coll, item->pos.x, item->pos.y + LARA_HEIGHT_UW / 2, item->pos.z, + room_num, LARA_HEIGHT_UW); + + switch (coll->coll_type) { + case COLL_FRONT: + if (item->rot.x > 35 * PHD_DEGREE) { + item->rot.x = item->rot.x + LARA_UW_WALL_DEFLECT; + } else if (item->rot.x < -35 * PHD_DEGREE) { + item->rot.x = item->rot.x - LARA_UW_WALL_DEFLECT; + } else { + item->fall_speed = 0; + } + break; + + case COLL_TOP: + item->rot.x -= LARA_UW_WALL_DEFLECT; + break; + + case COLL_TOP_FRONT: + item->fall_speed = 0; + break; + + case COLL_LEFT: + item->rot.y += 910; + break; + + case COLL_RIGHT: + item->rot.y -= 910; + break; + + default: + break; + } + + if (coll->side_mid.floor < 0) { + item->pos.y += coll->side_mid.floor; + item->rot.x += LARA_UW_WALL_DEFLECT; + } + Item_ShiftCol(item, coll); + + coll->old = item->pos; +} + +void __cdecl Lara_CatchFire(void) +{ + if (g_Lara.burn || g_Lara.water_status == LWS_CHEAT) { + return; + } + + const int16_t fx_num = Effect_Create(g_LaraItem->room_num); + if (fx_num == NO_ITEM) { + return; + } + + FX *const fx = &g_Effects[fx_num]; + fx->frame_num = 0; + fx->object_id = O_FLAME; + fx->counter = -1; + g_Lara.burn = 1; +} + +void __cdecl Lara_TouchLava(ITEM *const item) +{ + if (item->hit_points < 0 || g_Lara.water_status == LWS_CHEAT) { + return; + } + + int16_t room_num = item->room_num; + const SECTOR *const sector = + Room_GetSector(item->pos.x, MAX_HEIGHT, item->pos.z, &room_num); + const int32_t height = + Room_GetHeight(sector, item->pos.x, MAX_HEIGHT, item->pos.z); + if (item->floor != height) { + return; + } + + item->hit_points = -1; + item->hit_status = 1; + + for (int32_t i = 0; i < 10; i++) { + const int16_t fx_num = Effect_Create(item->room_num); + if (fx_num != NO_ITEM) { + FX *const fx = &g_Effects[fx_num]; + fx->object_id = O_FLAME; + fx->frame_num = + g_Objects[O_FLAME].mesh_count * Random_GetControl() / 0x7FFF; + fx->counter = -1 - 24 * Random_GetControl() / 0x7FFF; + } + } +} + +void __cdecl Lara_Extinguish(void) +{ + if (!g_Lara.burn) { + return; + } + + g_Lara.burn = 0; + + // put out flame objects + int16_t fx_num = g_NextEffectActive; + while (fx_num != NO_ITEM) { + FX *const fx = &g_Effects[fx_num]; + const int16_t next_fx_num = fx->next_active; + if (fx->object_id == O_FLAME && fx->counter < 0) { + fx->counter = 0; + Effect_Kill(fx_num); + } + fx_num = next_fx_num; + } +} diff --git a/src/tr2/game/lara/misc.h b/src/tr2/game/lara/misc.h new file mode 100644 index 000000000..06f02cf73 --- /dev/null +++ b/src/tr2/game/lara/misc.h @@ -0,0 +1,84 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Lara_GetCollisionInfo(ITEM *item, COLL_INFO *coll); + +void __cdecl Lara_SlideSlope(ITEM *item, COLL_INFO *coll); + +int32_t __cdecl Lara_HitCeiling(ITEM *item, COLL_INFO *coll); + +int32_t __cdecl Lara_DeflectEdge(ITEM *item, COLL_INFO *coll); + +void __cdecl Lara_DeflectEdgeJump(ITEM *item, COLL_INFO *coll); + +void __cdecl Lara_SlideEdgeJump(ITEM *item, COLL_INFO *coll); + +int32_t __cdecl Lara_TestWall( + ITEM *item, int32_t front, int32_t right, int32_t down); + +int32_t __cdecl Lara_TestHangOnClimbWall(ITEM *item, COLL_INFO *coll); + +int32_t __cdecl Lara_TestClimbStance(ITEM *item, COLL_INFO *coll); + +void __cdecl Lara_HangTest(ITEM *item, COLL_INFO *coll); + +int32_t __cdecl Lara_TestEdgeCatch(ITEM *item, COLL_INFO *coll, int32_t *edge); + +int32_t __cdecl Lara_TestHangJumpUp(ITEM *item, COLL_INFO *coll); + +int32_t __cdecl Lara_TestHangJump(ITEM *item, COLL_INFO *coll); + +int32_t __cdecl Lara_TestHangSwingIn(ITEM *item, PHD_ANGLE angle); + +int32_t __cdecl Lara_TestVault(ITEM *item, COLL_INFO *coll); + +int32_t __cdecl Lara_TestSlide(ITEM *item, COLL_INFO *coll); + +int16_t __cdecl Lara_FloorFront(ITEM *item, int16_t ang, int32_t dist); + +int32_t __cdecl Lara_LandedBad(ITEM *item, COLL_INFO *coll); + +int32_t __cdecl Lara_CheckForLetGo(ITEM *item, COLL_INFO *coll); + +void __cdecl Lara_GetJointAbsPosition(XYZ_32 *vec, int32_t joint); + +void __cdecl Lara_GetJointAbsPosition_I( + ITEM *item, XYZ_32 *vec, FRAME_INFO *frame1, FRAME_INFO *frame2, + int32_t frac, int32_t rate); + +void __cdecl Lara_BaddieCollision(ITEM *lara_item, COLL_INFO *coll); +void __cdecl Lara_TakeHit(ITEM *lara_item, const COLL_INFO *coll); +void __cdecl Lara_Push( + const ITEM *item, ITEM *lara_item, COLL_INFO *coll, bool spaz_on, + bool big_push); +int32_t __cdecl Lara_MovePosition(XYZ_32 *vec, ITEM *item, ITEM *lara_item); +int32_t __cdecl Lara_IsNearItem(const XYZ_32 *pos, int32_t distance); + +int32_t __cdecl Lara_TestClimb( + int32_t x, int32_t y, int32_t z, int32_t x_front, int32_t z_front, + int32_t item_height, int16_t item_room, int32_t *shift); + +int32_t __cdecl Lara_TestClimbPos( + const ITEM *item, int32_t front, int32_t right, int32_t origin, + int32_t height, int32_t *shift); + +void __cdecl Lara_DoClimbLeftRight( + ITEM *item, const COLL_INFO *coll, int32_t result, int32_t shift); + +int32_t __cdecl Lara_TestClimbUpPos( + const ITEM *item, int32_t front, int32_t right, int32_t *shift, + int32_t *ledge); + +int32_t __cdecl Lara_GetWaterDepth( + int32_t x, int32_t y, int32_t z, int16_t room_num); + +void __cdecl Lara_TestWaterDepth(ITEM *item, const COLL_INFO *coll); + +void __cdecl Lara_SwimCollision(ITEM *item, COLL_INFO *coll); + +void __cdecl Lara_WaterCurrent(COLL_INFO *coll); + +void __cdecl Lara_CatchFire(void); + +void __cdecl Lara_TouchLava(ITEM *item); diff --git a/src/tr2/game/lara/state.c b/src/tr2/game/lara/state.c new file mode 100644 index 000000000..0c995dc12 --- /dev/null +++ b/src/tr2/game/lara/state.c @@ -0,0 +1,1133 @@ +#include "game/lara/state.h" + +#include "game/input.h" +#include "game/inventory/backpack.h" +#include "game/lara/control.h" +#include "game/lara/look.h" +#include "game/lara/misc.h" +#include "game/music.h" +#include "game/output.h" +#include "game/room.h" +#include "game/sound.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +void __cdecl Lara_SwimTurn(ITEM *const item) +{ + if (g_Input & IN_FORWARD) { + item->rot.x -= LARA_TURN_RATE_UW; + } else if (g_Input & IN_BACK) { + item->rot.x += LARA_TURN_RATE_UW; + } + + if (g_Input & IN_LEFT) { + g_Lara.turn_rate -= LARA_TURN_RATE; + CLAMPL(g_Lara.turn_rate, -LARA_MED_TURN); + item->rot.z -= LARA_LEAN_RATE_SWIM; + } else if (g_Input & IN_RIGHT) { + g_Lara.turn_rate += LARA_TURN_RATE; + CLAMPG(g_Lara.turn_rate, LARA_MED_TURN); + item->rot.z += LARA_LEAN_RATE_SWIM; + } +} + +void __cdecl Lara_State_Walk(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_STOP; + } + + if (g_Input & IN_LEFT) { + g_Lara.turn_rate -= LARA_TURN_RATE; + CLAMPL(g_Lara.turn_rate, -LARA_SLOW_TURN); + } else if (g_Input & IN_RIGHT) { + g_Lara.turn_rate += LARA_TURN_RATE; + CLAMPG(g_Lara.turn_rate, +LARA_SLOW_TURN); + } + + if (g_Input & IN_FORWARD) { + if (g_Lara.water_status == LWS_WADE) { + item->goal_anim_state = LS_WADE; + } else if (g_Input & IN_SLOW) { + item->goal_anim_state = LS_WALK; + } else { + item->goal_anim_state = LS_RUN; + } + } else { + item->goal_anim_state = LS_STOP; + } +} + +void __cdecl Lara_State_Run(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_DEATH; + return; + } + + if (g_Input & IN_ROLL) { + item->anim_num = LA_ROLL_START; + item->current_anim_state = LS_ROLL; + item->goal_anim_state = LS_STOP; + item->frame_num = g_Anims[item->anim_num].frame_base + 2; + return; + } + + if (g_Input & IN_LEFT) { + g_Lara.turn_rate -= LARA_TURN_RATE; + CLAMPL(g_Lara.turn_rate, -LARA_FAST_TURN); + item->rot.z -= LARA_LEAN_RATE; + CLAMPL(item->rot.z, -LARA_LEAN_MAX); + } else if (g_Input & IN_RIGHT) { + g_Lara.turn_rate += LARA_TURN_RATE; + CLAMPG(g_Lara.turn_rate, +LARA_FAST_TURN); + item->rot.z += LARA_LEAN_RATE; + CLAMPG(item->rot.z, +LARA_LEAN_MAX); + } + + if (item->anim_num == LA_RUN_START) { + g_JumpPermitted = 0; + } else if (item->anim_num != LA_RUN || item->frame_num == 4) { + g_JumpPermitted = 1; + } + + if ((g_Input & IN_JUMP) && g_JumpPermitted && !item->gravity) { + item->goal_anim_state = LS_FORWARD_JUMP; + } else if (g_Input & IN_FORWARD) { + if (g_Lara.water_status == LWS_WADE) { + item->goal_anim_state = LS_WADE; + } else if (g_Input & IN_SLOW) { + item->goal_anim_state = LS_WALK; + } else { + item->goal_anim_state = LS_RUN; + } + } else { + item->goal_anim_state = LS_STOP; + } +} + +void __cdecl Lara_State_Stop(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_DEATH; + return; + } + + if ((g_Input & IN_ROLL) && g_Lara.water_status != LWS_WADE) { + item->anim_num = LA_ROLL_START; + item->current_anim_state = LS_ROLL; + item->goal_anim_state = LS_STOP; + item->frame_num = g_Anims[item->anim_num].frame_base + 2; + return; + } + + item->goal_anim_state = LS_STOP; + if (g_Input & IN_LOOK) { + Lara_LookUpDown(); + } + + if (g_Input & IN_STEP_LEFT) { + item->goal_anim_state = LS_STEP_LEFT; + } else if (g_Input & IN_STEP_RIGHT) { + item->goal_anim_state = LS_STEP_RIGHT; + } else if (g_Input & IN_LEFT) { + item->goal_anim_state = LS_TURN_LEFT; + } else if (g_Input & IN_RIGHT) { + item->goal_anim_state = LS_TURN_RIGHT; + } + + if (g_Lara.water_status == LWS_WADE) { + if (g_Input & IN_JUMP) { + item->goal_anim_state = LS_COMPRESS; + } + + if (g_Input & IN_FORWARD) { + if (g_Input & IN_SLOW) { + Lara_State_Wade(item, coll); + } else { + Lara_State_Walk(item, coll); + } + } else if (g_Input & IN_BACK) { + Lara_State_Back(item, coll); + } + } else if (g_Input & IN_JUMP) { + item->goal_anim_state = LS_COMPRESS; + } else if (g_Input & IN_FORWARD) { + if (g_Input & IN_SLOW) { + Lara_State_Walk(item, coll); + } else { + Lara_State_Run(item, coll); + } + } else if (g_Input & IN_BACK) { + if (g_Input & IN_SLOW) { + Lara_State_Back(item, coll); + } else { + item->goal_anim_state = LS_FAST_BACK; + } + } +} + +void __cdecl Lara_State_ForwardJump(ITEM *item, COLL_INFO *coll) +{ + if (item->goal_anim_state == LS_SWAN_DIVE + || item->goal_anim_state == LS_REACH) { + item->goal_anim_state = LS_FORWARD_JUMP; + } + + if (item->goal_anim_state != LS_DEATH && item->goal_anim_state != LS_STOP + && item->goal_anim_state != LS_RUN) { + if ((g_Input & IN_ACTION) && g_Lara.gun_status == LGS_ARMLESS) { + item->goal_anim_state = LS_REACH; + } + if ((g_Input & IN_ROLL) || (g_Input & IN_BACK)) { + item->goal_anim_state = LS_TWIST; + } + if ((g_Input & IN_SLOW) && g_Lara.gun_status == LGS_ARMLESS) { + item->goal_anim_state = LS_SWAN_DIVE; + } + if (item->fall_speed > LARA_FAST_FALL_SPEED) { + item->goal_anim_state = LS_FAST_FALL; + } + } + + if (g_Input & IN_LEFT) { + g_Lara.turn_rate -= LARA_TURN_RATE; + CLAMPL(g_Lara.turn_rate, -LARA_JUMP_TURN); + } else if (g_Input & IN_RIGHT) { + g_Lara.turn_rate += LARA_TURN_RATE; + CLAMPG(g_Lara.turn_rate, +LARA_JUMP_TURN); + } +} + +void __cdecl Lara_State_FastBack(ITEM *item, COLL_INFO *coll) +{ + item->goal_anim_state = LS_STOP; + if (g_Input & IN_LEFT) { + g_Lara.turn_rate -= LARA_TURN_RATE; + CLAMPL(g_Lara.turn_rate, -LARA_MED_TURN); + } else if (g_Input & IN_RIGHT) { + g_Lara.turn_rate += LARA_TURN_RATE; + CLAMPG(g_Lara.turn_rate, LARA_MED_TURN); + } +} + +void __cdecl Lara_State_TurnRight(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_STOP; + return; + } + + g_Lara.turn_rate += LARA_TURN_RATE; + + if (g_Lara.gun_status == LWS_WADE) { + item->goal_anim_state = LS_FAST_TURN; + } else if (g_Lara.turn_rate > LARA_SLOW_TURN) { + if (g_Input & IN_SLOW) { + g_Lara.turn_rate = LARA_SLOW_TURN; + } else { + item->goal_anim_state = LS_FAST_TURN; + } + } + + if (g_Input & IN_FORWARD) { + if (g_Lara.water_status == LWS_WADE) { + item->goal_anim_state = LS_WADE; + } else if (g_Input & IN_SLOW) { + item->goal_anim_state = LS_WALK; + } else { + item->goal_anim_state = LS_RUN; + } + } else if (!(g_Input & IN_RIGHT)) { + item->goal_anim_state = LS_STOP; + } +} + +void __cdecl Lara_State_TurnLeft(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_STOP; + return; + } + + g_Lara.turn_rate -= LARA_TURN_RATE; + + if (g_Lara.gun_status == IN_LEFT) { + item->goal_anim_state = LS_FAST_TURN; + } else if (g_Lara.turn_rate < -LARA_SLOW_TURN) { + if (g_Input & IN_SLOW) { + g_Lara.turn_rate = -LARA_SLOW_TURN; + } else { + item->goal_anim_state = LS_FAST_TURN; + } + } + + if (g_Input & IN_FORWARD) { + if (g_Lara.water_status == IN_LEFT) { + item->goal_anim_state = LS_WADE; + } else if (g_Input & IN_SLOW) { + item->goal_anim_state = LS_WALK; + } else { + item->goal_anim_state = LS_RUN; + } + } else if (!(g_Input & IN_LEFT)) { + item->goal_anim_state = LS_STOP; + } +} + +void __cdecl Lara_State_Death(ITEM *item, COLL_INFO *coll) +{ + g_Lara.look = 0; + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; +} + +void __cdecl Lara_State_FastFall(ITEM *item, COLL_INFO *coll) +{ + item->speed = item->speed * 95 / 100; + if (item->fall_speed == DAMAGE_START + DAMAGE_LENGTH) { + Sound_Effect(SFX_LARA_FALL, &item->pos, SPM_NORMAL); + } +} + +void __cdecl Lara_State_Hang(ITEM *item, COLL_INFO *coll) +{ + if (g_Input & IN_LOOK) { + Lara_LookUpDown(); + } + + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.target_angle = CAM_HANG_ANGLE; + g_Camera.target_elevation = CAM_HANG_ELEVATION; + if ((g_Input & IN_LEFT) || (g_Input & IN_STEP_LEFT)) { + item->goal_anim_state = LS_HANG_LEFT; + } else if ((g_Input & IN_RIGHT) || (g_Input & IN_STEP_RIGHT)) { + item->goal_anim_state = LS_HANG_RIGHT; + } +} + +void __cdecl Lara_State_Reach(ITEM *item, COLL_INFO *coll) +{ + g_Camera.target_angle = CAM_REACH_ANGLE; + if (item->fall_speed > LARA_FAST_FALL_SPEED) { + item->goal_anim_state = LS_FAST_FALL; + } +} + +void __cdecl Lara_State_Splat(ITEM *item, COLL_INFO *coll) +{ + g_Lara.look = 0; +} + +void __cdecl Lara_State_Compress(ITEM *item, COLL_INFO *coll) +{ + if (g_Lara.water_status != LWS_WADE) { + if ((g_Input & IN_FORWARD) + && Lara_FloorFront(item, item->rot.y, STEP_L) >= -STEPUP_HEIGHT) { + item->goal_anim_state = LS_FORWARD_JUMP; + g_Lara.move_angle = item->rot.y; + } else if ( + (g_Input & IN_LEFT) + && Lara_FloorFront(item, item->rot.y - PHD_90, STEP_L) + >= -STEPUP_HEIGHT) { + item->goal_anim_state = LS_LEFT_JUMP; + g_Lara.move_angle = item->rot.y - PHD_90; + } else if ( + (g_Input & IN_RIGHT) + && Lara_FloorFront(item, item->rot.y + PHD_90, STEP_L) + >= -STEPUP_HEIGHT) { + item->goal_anim_state = LS_RIGHT_JUMP; + g_Lara.move_angle = item->rot.y + PHD_90; + } else if ( + (g_Input & IN_BACK) + && Lara_FloorFront(item, item->rot.y + PHD_180, STEP_L) + >= -STEPUP_HEIGHT) { + item->goal_anim_state = LS_BACK_JUMP; + g_Lara.move_angle = item->rot.y + PHD_180; + } + } + + if (item->fall_speed > LARA_FAST_FALL_SPEED) { + item->goal_anim_state = LS_FAST_FALL; + } +} + +void __cdecl Lara_State_Back(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_STOP; + return; + } + + if ((g_Input & IN_BACK) + && ((g_Input & IN_SLOW) || g_Lara.water_status == LWS_WADE)) { + item->goal_anim_state = LS_BACK; + } else { + item->goal_anim_state = LS_STOP; + } + + if (g_Input & IN_LEFT) { + g_Lara.turn_rate -= LARA_TURN_RATE; + CLAMPL(g_Lara.turn_rate, -LARA_SLOW_TURN); + } else if (g_Input & IN_RIGHT) { + g_Lara.turn_rate += LARA_TURN_RATE; + CLAMPG(g_Lara.turn_rate, LARA_SLOW_TURN); + } +} + +void __cdecl Lara_State_Null(ITEM *item, COLL_INFO *coll) +{ + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; +} + +void __cdecl Lara_State_FastTurn(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_STOP; + return; + } + + if (g_Lara.turn_rate >= 0) { + g_Lara.turn_rate = LARA_FAST_TURN; + if (!(g_Input & IN_RIGHT)) { + item->goal_anim_state = LS_STOP; + } + } else { + g_Lara.turn_rate = -LARA_FAST_TURN; + if (!(g_Input & IN_LEFT)) { + item->goal_anim_state = LS_STOP; + } + } +} + +void __cdecl Lara_State_StepRight(ITEM *item, COLL_INFO *coll) +{ + g_Lara.look = 0; + if (item->hit_points <= 0) { + item->goal_anim_state = LS_STOP; + return; + } + + if (!(g_Input & IN_STEP_RIGHT)) { + item->goal_anim_state = LS_STOP; + } + + if (g_Input & IN_LEFT) { + g_Lara.turn_rate -= LARA_TURN_RATE; + CLAMPL(g_Lara.turn_rate, -LARA_SLOW_TURN); + } else if (g_Input & IN_RIGHT) { + g_Lara.turn_rate += LARA_TURN_RATE; + CLAMPG(g_Lara.turn_rate, LARA_SLOW_TURN); + } +} + +void __cdecl Lara_State_StepLeft(ITEM *item, COLL_INFO *coll) +{ + g_Lara.look = 0; + if (item->hit_points <= 0) { + item->goal_anim_state = LS_STOP; + return; + } + + if (!(g_Input & IN_STEP_LEFT)) { + item->goal_anim_state = LS_STOP; + } + + if (g_Input & IN_LEFT) { + g_Lara.turn_rate -= LARA_TURN_RATE; + CLAMPL(g_Lara.turn_rate, -LARA_SLOW_TURN); + } else if (g_Input & IN_RIGHT) { + g_Lara.turn_rate += LARA_TURN_RATE; + CLAMPG(g_Lara.turn_rate, LARA_SLOW_TURN); + } +} + +void __cdecl Lara_State_Slide(ITEM *item, COLL_INFO *coll) +{ + g_Camera.flags = CF_NO_CHUNKY; + g_Camera.target_elevation = CAM_SLIDE_ELEVATION; + if ((g_Input & IN_JUMP) && !(g_Input & IN_BACK)) { + item->goal_anim_state = LS_FORWARD_JUMP; + } +} + +void __cdecl Lara_State_BackJump(ITEM *item, COLL_INFO *coll) +{ + g_Camera.target_angle = CAM_BACK_JUMP_ANGLE; + if (item->fall_speed > LARA_FAST_FALL_SPEED) { + item->goal_anim_state = LS_FAST_FALL; + return; + } + + if (item->goal_anim_state == LS_RUN) { + item->goal_anim_state = LS_STOP; + } else if ( + ((g_Input & IN_FORWARD) || (g_Input & IN_ROLL)) + && item->goal_anim_state != LS_STOP) { + item->goal_anim_state = LS_TWIST; + } +} + +void __cdecl Lara_State_RightJump(ITEM *item, COLL_INFO *coll) +{ + g_Lara.look = 0; + if (item->fall_speed > LARA_FAST_FALL_SPEED) { + item->goal_anim_state = LS_FAST_FALL; + return; + } + + if ((g_Input & IN_LEFT) && item->goal_anim_state != LS_STOP) { + item->goal_anim_state = LS_TWIST; + } +} + +void __cdecl Lara_State_LeftJump(ITEM *item, COLL_INFO *coll) +{ + g_Lara.look = 0; + if (item->fall_speed > LARA_FAST_FALL_SPEED) { + item->goal_anim_state = LS_FAST_FALL; + return; + } + + if ((g_Input & IN_RIGHT) && item->goal_anim_state != LS_STOP) { + item->goal_anim_state = LS_TWIST; + } +} + +void __cdecl Lara_State_UpJump(ITEM *item, COLL_INFO *coll) +{ + if (item->fall_speed > LARA_FAST_FALL_SPEED) { + item->goal_anim_state = LS_FAST_FALL; + } +} + +void __cdecl Lara_State_Fallback(ITEM *item, COLL_INFO *coll) +{ + if (item->fall_speed > LARA_FAST_FALL_SPEED) { + item->goal_anim_state = LS_FAST_FALL; + return; + } + + if ((g_Input & IN_ACTION) && g_Lara.gun_status == LGS_ARMLESS) { + item->goal_anim_state = LS_REACH; + } +} + +void __cdecl Lara_State_HangLeft(ITEM *item, COLL_INFO *coll) +{ + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.target_angle = CAM_HANG_ANGLE; + g_Camera.target_elevation = CAM_HANG_ELEVATION; + if (!(g_Input & IN_LEFT) && !(g_Input & IN_STEP_LEFT)) { + item->goal_anim_state = LS_HANG; + } +} + +void __cdecl Lara_State_HangRight(ITEM *item, COLL_INFO *coll) +{ + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.target_angle = CAM_HANG_ANGLE; + g_Camera.target_elevation = CAM_HANG_ELEVATION; + if (!(g_Input & IN_RIGHT) && !(g_Input & IN_STEP_RIGHT)) { + item->goal_anim_state = LS_HANG; + } +} + +void __cdecl Lara_State_SlideBack(ITEM *item, COLL_INFO *coll) +{ + if ((g_Input & IN_JUMP) && !(g_Input & IN_FORWARD)) { + item->goal_anim_state = LS_BACK_JUMP; + } +} + +void __cdecl Lara_State_PushBlock(ITEM *item, COLL_INFO *coll) +{ + g_Lara.look = 0; + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.flags = CF_FOLLOW_CENTRE; + g_Camera.target_angle = CAM_PUSH_BLOCK_ANGLE; + g_Camera.target_elevation = CAM_PUSH_BLOCK_ELEVATION; +} + +void __cdecl Lara_State_PPReady(ITEM *item, COLL_INFO *coll) +{ + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.target_angle = CAM_PP_READY_ANGLE; + if (!(g_Input & IN_ACTION)) { + item->goal_anim_state = LS_STOP; + } +} + +void __cdecl Lara_State_Pickup(ITEM *item, COLL_INFO *coll) +{ + g_Lara.look = 0; + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.target_angle = CAM_PICKUP_ANGLE; + g_Camera.target_elevation = CAM_PICKUP_ELEVATION; + g_Camera.target_distance = CAM_PICKUP_DISTANCE; +} + +void __cdecl Lara_State_PickupFlare(ITEM *item, COLL_INFO *coll) +{ + Lara_State_Pickup(item, coll); + if (item->frame_num == g_Anims[item->anim_num].frame_end - 1) { + g_Lara.gun_status = LGS_ARMLESS; + } +} + +void __cdecl Lara_State_SwitchOn(ITEM *item, COLL_INFO *coll) +{ + g_Lara.look = 0; + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.target_angle = CAM_SWITCH_ON_ANGLE; + g_Camera.target_elevation = CAM_SWITCH_ON_ELEVATION; + g_Camera.target_distance = CAM_SWITCH_ON_DISTANCE; + g_Camera.speed = CAM_SWITCH_ON_SPEED; +} + +void __cdecl Lara_State_UseKey(ITEM *item, COLL_INFO *coll) +{ + g_Lara.look = 0; + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.target_angle = CAM_USE_KEY_ANGLE; + g_Camera.target_elevation = CAM_USE_KEY_ELEVATION; + g_Camera.target_distance = CAM_USE_KEY_DISTANCE; +} + +void __cdecl Lara_State_Special(ITEM *item, COLL_INFO *coll) +{ + g_Camera.flags = CF_FOLLOW_CENTRE; + g_Camera.target_angle = CAM_SPECIAL_ANGLE; + g_Camera.target_elevation = CAM_SPECIAL_ELEVATION; +} + +void __cdecl Lara_State_SwanDive(ITEM *item, COLL_INFO *coll) +{ + coll->enable_spaz = 0; + coll->enable_baddie_push = 1; + if (item->fall_speed > LARA_FAST_FALL_SPEED + && item->goal_anim_state != LS_DIVE) { + item->goal_anim_state = LS_FAST_DIVE; + } +} + +void __cdecl Lara_State_FastDive(ITEM *item, COLL_INFO *coll) +{ + if ((g_Input & IN_ROLL) && item->goal_anim_state == LS_FAST_DIVE) { + item->goal_anim_state = LS_TWIST; + } + coll->enable_spaz = 0; + coll->enable_baddie_push = 1; + item->speed = item->speed * 95 / 100; +} + +void __cdecl Lara_State_WaterOut(ITEM *item, COLL_INFO *coll) +{ + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.flags = CF_FOLLOW_CENTRE; +} + +void __cdecl Lara_State_Wade(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_STOP; + return; + } + + g_Camera.target_elevation = CAM_WADE_ELEVATION; + if (g_Input & IN_LEFT) { + g_Lara.turn_rate -= LARA_TURN_RATE; + CLAMPL(g_Lara.turn_rate, -LARA_FAST_TURN); + item->rot.z -= LARA_LEAN_RATE; + CLAMPL(item->rot.z, -LARA_LEAN_MAX); + } else if (g_Input & IN_RIGHT) { + g_Lara.turn_rate += LARA_TURN_RATE; + CLAMPG(g_Lara.turn_rate, LARA_FAST_TURN); + item->rot.z += LARA_LEAN_RATE; + CLAMPG(item->rot.z, LARA_LEAN_MAX); + } + + if (g_Input & IN_FORWARD) { + if (g_Lara.water_status != LWS_ABOVE_WATER) { + item->goal_anim_state = LS_WADE; + } else { + item->goal_anim_state = IN_FORWARD; + } + } else { + item->goal_anim_state = LS_STOP; + } +} + +void __cdecl Lara_State_DeathSlide(ITEM *item, COLL_INFO *coll) +{ + g_Camera.target_angle = CAM_DEATH_SLIDE_ANGLE; + + int16_t room_num = item->room_num; + const SECTOR *sector = + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); + + coll->trigger = g_TriggerIndex; + + if (!(g_Input & IN_ACTION)) { + item->goal_anim_state = LS_FORWARD_JUMP; + Lara_Animate(item); + g_LaraItem->gravity = 1; + g_LaraItem->speed = 100; + g_LaraItem->fall_speed = 40; + g_Lara.move_angle = item->rot.y; + } +} + +void __cdecl Lara_State_Extra_Breath(ITEM *item, COLL_INFO *coll) +{ + item->anim_num = LA_STAND_IDLE; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->goal_anim_state = LS_STOP; + item->current_anim_state = LS_STOP; + g_Lara.extra_anim = 0; + g_Lara.gun_status = LGS_ARMLESS; + g_Camera.type = CAM_CHASE; + Output_AlterFOV(GAME_FOV * PHD_DEGREE); +} + +void __cdecl Lara_State_Extra_YetiKill(ITEM *item, COLL_INFO *coll) +{ + g_Camera.target_angle = CAM_YETI_KILL_ANGLE; + g_Camera.target_distance = CAM_YETI_KILL_DISTANCE; + g_Lara.hit_direction = -1; + if (item->frame_num < g_Anims[item->anim_num].frame_end - 30) { + g_Lara.death_timer = 1; + } +} + +void __cdecl Lara_State_Extra_SharkKill(ITEM *item, COLL_INFO *coll) +{ + g_Camera.target_angle = CAM_SHARK_KILL_ANGLE; + g_Camera.target_distance = CAM_SHARK_KILL_DISTANCE; + g_Lara.hit_direction = -1; + + if (item->frame_num == g_Anims[item->anim_num].frame_end) { + int32_t water_height = Room_GetWaterHeight( + item->pos.x, item->pos.y, item->pos.z, item->room_num); + if (water_height != NO_HEIGHT && water_height < item->pos.y - 100) { + item->pos.y -= 5; + } + } + + if (item->frame_num < g_Anims[item->anim_num].frame_end - 30) { + g_Lara.death_timer = 1; + } +} + +void __cdecl Lara_State_Extra_Airlock(ITEM *item, COLL_INFO *coll) +{ + g_Camera.target_angle = CAM_AIRLOCK_ANGLE; + g_Camera.target_elevation = CAM_AIRLOCK_ELEVATION; +} + +void __cdecl Lara_State_Extra_GongBong(ITEM *item, COLL_INFO *coll) +{ + g_Camera.target_angle = CAM_GONG_BONG_ANGLE; + g_Camera.target_elevation = CAM_GONG_BONG_ELEVATION; + g_Camera.target_distance = CAM_GONG_BONG_DISTANCE; +} + +void __cdecl Lara_State_Extra_DinoKill(ITEM *item, COLL_INFO *coll) +{ + g_Camera.flags = CF_FOLLOW_CENTRE; + g_Camera.target_angle = CAM_DINO_KILL_ANGLE; + g_Camera.target_elevation = CAM_DINO_KILL_ELEVATION; + g_Lara.hit_direction = -1; + if (item->frame_num < g_Anims[item->anim_num].frame_end - 30) { + g_Lara.death_timer = 1; + } +} + +void __cdecl Lara_State_Extra_PullDagger(ITEM *item, COLL_INFO *coll) +{ + int32_t frame_num_rel = + item->frame_num - g_Anims[item->anim_num].frame_base; + if (frame_num_rel == 1) { + Music_PlaySynced(MX_DAGGER_PULL); + } else if (frame_num_rel == 180) { + g_Lara.mesh_ptrs[LM_HAND_R] = + g_Meshes[g_Objects[O_LARA].mesh_idx + LM_HAND_R]; + Inv_AddItem(O_PUZZLE_ITEM_2); + } + + if (item->frame_num == g_Anims[item->anim_num].frame_end) { + item->rot.y += PHD_90; + int16_t room_num = item->room_num; + const SECTOR *sector = + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); + Room_TestTriggers(g_TriggerIndex, 1); + } +} + +void __cdecl Lara_State_Extra_StartAnim(ITEM *item, COLL_INFO *coll) +{ + int16_t room_num = item->room_num; + const SECTOR *sector = + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); + Room_TestTriggers(g_TriggerIndex, 0); +} + +void __cdecl Lara_State_Extra_StartHouse(ITEM *item, COLL_INFO *coll) +{ + int32_t frame_num_rel = + item->frame_num - g_Anims[item->anim_num].frame_base; + if (frame_num_rel == 1) { + Music_PlaySynced(MX_REVEAL_2); + g_Lara.mesh_ptrs[LM_HAND_R] = + g_Meshes[g_Objects[O_LARA_EXTRA].mesh_idx + LM_HAND_R]; + g_Lara.mesh_ptrs[LM_HIPS] = g_Meshes[g_Objects[O_LARA_EXTRA].mesh_idx]; + } else if (frame_num_rel == 401) { + g_Lara.mesh_ptrs[LM_HAND_R] = + g_Meshes[g_Objects[O_LARA].mesh_idx + LM_HAND_R]; + g_Lara.mesh_ptrs[LM_HIPS] = g_Meshes[g_Objects[O_LARA].mesh_idx]; + Inv_AddItem(O_PUZZLE_ITEM_1); + } else if (item->frame_num == g_Anims[item->anim_num].frame_end) { + g_Camera.type = CAM_CHASE; + Output_AlterFOV(GAME_FOV * PHD_DEGREE); + } +} + +void __cdecl Lara_State_Extra_FinalAnim(ITEM *item, COLL_INFO *coll) +{ + item->hit_points = 1000; + + int32_t frame_num_rel = + item->frame_num - g_Anims[item->anim_num].frame_base; + if (frame_num_rel == 1) { + g_Lara.back_gun = 0; + g_Lara.mesh_ptrs[LM_HAND_R] = + g_Meshes[g_Objects[O_LARA].mesh_idx + LM_HAND_R]; + g_Lara.mesh_ptrs[LM_HEAD] = + g_Meshes[g_Objects[O_LARA].mesh_idx + LM_HEAD]; + g_Lara.mesh_ptrs[LM_HIPS] = + g_Meshes[g_Objects[O_LARA_EXTRA].mesh_idx + LM_HIPS]; + Music_PlaySynced(MX_CUTSCENE_BATH); + } else if (frame_num_rel == 316) { + g_Lara.mesh_ptrs[LM_HAND_R] = + g_Meshes[g_Objects[O_LARA_SHOTGUN].mesh_idx + LM_HAND_R]; + } else if (item->frame_num == g_Anims[item->anim_num].frame_end - 1) { + g_LevelComplete = true; + } +} + +void __cdecl Lara_State_ClimbLeft(ITEM *item, COLL_INFO *coll) +{ + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.target_angle = CAM_CLIMB_LEFT_ANGLE; + g_Camera.target_elevation = CAM_CLIMB_LEFT_ELEVATION; + if (!(g_Input & IN_LEFT) && !(g_Input & IN_STEP_LEFT)) { + item->goal_anim_state = LS_CLIMB_STANCE; + } +} + +void __cdecl Lara_State_ClimbRight(ITEM *item, COLL_INFO *coll) +{ + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.target_angle = CAM_CLIMB_RIGHT_ANGLE; + g_Camera.target_elevation = CAM_CLIMB_RIGHT_ELEVATION; + if (!(g_Input & IN_RIGHT) && !(g_Input & IN_STEP_RIGHT)) { + item->goal_anim_state = LS_CLIMB_STANCE; + } +} + +void __cdecl Lara_State_ClimbStance(ITEM *item, COLL_INFO *coll) +{ + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.target_elevation = CAM_CLIMB_STANCE_ELEVATION; + + if (g_Input & IN_LOOK) { + Lara_LookUpDown(); + } + + if ((g_Input & IN_LEFT) || (g_Input & IN_STEP_LEFT)) { + item->goal_anim_state = LS_CLIMB_LEFT; + } else if ((g_Input & IN_RIGHT) || (g_Input & IN_STEP_RIGHT)) { + item->goal_anim_state = LS_CLIMB_RIGHT; + } else if ((g_Input & IN_JUMP)) { + item->goal_anim_state = LS_BACK_JUMP; + g_Lara.gun_status = LGS_ARMLESS; + g_Lara.move_angle = item->rot.y + PHD_180; + } +} + +void __cdecl Lara_State_Climbing(ITEM *item, COLL_INFO *coll) +{ + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.target_elevation = CAM_CLIMBING_ELEVATION; +} + +void __cdecl Lara_State_ClimbEnd(ITEM *item, COLL_INFO *coll) +{ + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.flags = CF_FOLLOW_CENTRE; + g_Camera.target_angle = CAM_CLIMB_END_ELEVATION; +} + +void __cdecl Lara_State_ClimbDown(ITEM *item, COLL_INFO *coll) +{ + coll->enable_spaz = 0; + coll->enable_baddie_push = 0; + g_Camera.target_elevation = CAM_CLIMB_DOWN_ELEVATION; +} + +void __cdecl Lara_State_SurfSwim(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_UW_DEATH; + return; + } + + g_Lara.dive_count = 0; + if (g_Input & IN_LEFT) { + item->rot.y -= LARA_SLOW_TURN; + } else if (g_Input & IN_RIGHT) { + item->rot.y += LARA_SLOW_TURN; + } + if (!(g_Input & LS_RUN) || (g_Input & LS_BACK)) { + item->goal_anim_state = LS_SURF_TREAD; + } + item->fall_speed += 8; + CLAMPG(item->fall_speed, LARA_MAX_SURF_SPEED); +} + +void __cdecl Lara_State_SurfBack(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_UW_DEATH; + return; + } + + g_Lara.dive_count = 0; + if (g_Input & IN_LEFT) { + item->rot.y -= LARA_SURF_TURN; + } else if (g_Input & IN_RIGHT) { + item->rot.y += LARA_SURF_TURN; + } + if (!(g_Input & IN_BACK)) { + item->goal_anim_state = LS_SURF_TREAD; + } + item->fall_speed += 8; + CLAMPG(item->fall_speed, LARA_MAX_SURF_SPEED); +} + +void __cdecl Lara_State_SurfLeft(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_UW_DEATH; + return; + } + + g_Lara.dive_count = 0; + if (g_Input & IN_LEFT) { + item->rot.y -= LARA_SURF_TURN; + } else if (g_Input & IN_RIGHT) { + item->rot.y += LARA_SURF_TURN; + } + if (!(g_Input & IN_STEP_LEFT)) { + item->goal_anim_state = LS_SURF_TREAD; + } + item->fall_speed += 8; + CLAMPG(item->fall_speed, LARA_MAX_SURF_SPEED); +} + +void __cdecl Lara_State_SurfRight(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_UW_DEATH; + return; + } + + g_Lara.dive_count = 0; + if (g_Input & IN_LEFT) { + item->rot.y -= LARA_SURF_TURN; + } else if (g_Input & IN_RIGHT) { + item->rot.y += LARA_SURF_TURN; + } + if (!(g_Input & IN_STEP_RIGHT)) { + item->goal_anim_state = LS_SURF_TREAD; + } + item->fall_speed += 8; + CLAMPG(item->fall_speed, LARA_MAX_SURF_SPEED); +} + +void __cdecl Lara_State_SurfTread(ITEM *item, COLL_INFO *coll) +{ + item->fall_speed -= 4; + CLAMPL(item->fall_speed, 0); + + if (item->hit_points <= 0) { + item->goal_anim_state = LS_UW_DEATH; + return; + } + if (g_Input & IN_LOOK) { + Lara_LookUpDown(); + return; + } + + if (g_Input & IN_LEFT) { + item->rot.y -= LARA_SLOW_TURN; + } else if (g_Input & IN_RIGHT) { + item->rot.y += LARA_SLOW_TURN; + } + + if (g_Input & IN_FORWARD) { + item->goal_anim_state = LS_SURF_SWIM; + } else if (g_Input & IN_BACK) { + item->goal_anim_state = LS_SURF_BACK; + } + + if (g_Input & IN_STEP_LEFT) { + item->goal_anim_state = LS_SURF_LEFT; + } else if (g_Input & IN_STEP_RIGHT) { + item->goal_anim_state = LS_SURF_RIGHT; + } + + if (g_Input & IN_JUMP) { + g_Lara.dive_count++; + if (g_Lara.dive_count == 10) { + item->anim_num = LA_ONWATER_DIVE; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->goal_anim_state = LS_SWIM; + item->current_anim_state = LS_DIVE; + item->rot.x = -45 * PHD_DEGREE; + item->fall_speed = 80; + g_Lara.water_status = LWS_UNDERWATER; + } + } else { + g_Lara.dive_count = 0; + } +} + +void __cdecl Lara_State_Swim(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_UW_DEATH; + return; + } + + if ((g_Input & IN_ROLL)) { + item->current_anim_state = LS_WATER_ROLL; + item->anim_num = LA_UNDERWATER_ROLL_START; + item->frame_num = g_Anims[item->anim_num].frame_base; + return; + } + + Lara_SwimTurn(item); + item->fall_speed += 8; + if (g_Lara.water_status == LWS_CHEAT) { + CLAMPG(item->fall_speed, LARA_MAX_SWIM_SPEED * 2); + } else { + CLAMPG(item->fall_speed, LARA_MAX_SWIM_SPEED); + } + + if (!(g_Input & IN_JUMP)) { + item->goal_anim_state = LS_GLIDE; + } +} + +void __cdecl Lara_State_Glide(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_UW_DEATH; + return; + } + + if ((g_Input & IN_ROLL) != 0) { + item->current_anim_state = LS_WATER_ROLL; + item->anim_num = LA_UNDERWATER_ROLL_START; + item->frame_num = g_Anims[item->anim_num].frame_base; + return; + } + + Lara_SwimTurn(item); + if (g_Input & IN_JUMP) { + item->goal_anim_state = LS_SWIM; + } + item->fall_speed -= LARA_UW_FRICTION; + CLAMPL(item->fall_speed, 0); + if (item->fall_speed <= LARA_MAX_SWIM_SPEED * 2 / 3) { + item->goal_anim_state = LS_TREAD; + } +} + +void __cdecl Lara_State_Tread(ITEM *item, COLL_INFO *coll) +{ + if (item->hit_points <= 0) { + item->goal_anim_state = LS_UW_DEATH; + return; + } + + if (g_Input & IN_ROLL) { + item->current_anim_state = LS_WATER_ROLL; + item->anim_num = LA_UNDERWATER_ROLL_START; + item->frame_num = g_Anims[item->anim_num].frame_base; + return; + } + + if (g_Input & IN_LOOK) { + Lara_LookUpDown(); + } + Lara_SwimTurn(item); + if (g_Input & IN_JUMP) { + item->goal_anim_state = LS_SWIM; + } + item->fall_speed -= LARA_UW_FRICTION; + CLAMPL(item->fall_speed, 0); + if (g_Lara.gun_status == LGS_HANDS_BUSY) { + g_Lara.gun_status = LGS_ARMLESS; + } +} + +void __cdecl Lara_State_Dive(ITEM *item, COLL_INFO *coll) +{ + if (g_Input & IN_FORWARD) { + item->rot.x -= PHD_DEGREE; + } +} + +void __cdecl Lara_State_UWDeath(ITEM *item, COLL_INFO *coll) +{ + item->gravity = 0; + item->fall_speed -= 8; + CLAMPL(item->fall_speed, 0); + + int32_t angle = 2 * PHD_DEGREE; + if (item->rot.x >= -angle && item->rot.x <= angle) { + item->rot.x = 0; + } else if (item->rot.x >= 0) { + item->rot.x -= angle; + } else { + item->rot.x += angle; + } +} + +void __cdecl Lara_State_UWTwist(ITEM *item, COLL_INFO *coll) +{ + item->fall_speed = 0; +} diff --git a/src/tr2/game/lara/state.h b/src/tr2/game/lara/state.h new file mode 100644 index 000000000..c00c8938a --- /dev/null +++ b/src/tr2/game/lara/state.h @@ -0,0 +1,75 @@ +#pragma once + +// Lara state routines. + +#include "global/types.h" + +// TODO: make static +void __cdecl Lara_SwimTurn(ITEM *item); + +void __cdecl Lara_State_Walk(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Run(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Stop(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ForwardJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_FastBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_TurnRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_TurnLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Death(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_FastFall(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Hang(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Reach(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Splat(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Compress(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Back(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Null(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_FastTurn(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_StepRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_StepLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Slide(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_BackJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_RightJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_LeftJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_UpJump(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Fallback(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_HangLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_HangRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SlideBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_PushBlock(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_PPReady(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Pickup(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_PickupFlare(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SwitchOn(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_UseKey(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Special(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SwanDive(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_FastDive(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_WaterOut(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Wade(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_DeathSlide(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_Breath(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_YetiKill(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_SharkKill(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_Airlock(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_GongBong(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_DinoKill(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_PullDagger(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_StartAnim(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_StartHouse(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Extra_FinalAnim(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbStance(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Climbing(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbEnd(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_ClimbDown(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SurfSwim(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SurfBack(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SurfLeft(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SurfRight(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_SurfTread(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Swim(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Glide(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Tread(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_Dive(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_UWDeath(ITEM *item, COLL_INFO *coll); +void __cdecl Lara_State_UWTwist(ITEM *item, COLL_INFO *coll); diff --git a/src/tr2/game/level.c b/src/tr2/game/level.c new file mode 100644 index 000000000..5f577362a --- /dev/null +++ b/src/tr2/game/level.c @@ -0,0 +1,920 @@ +#include "game/level.h" + +#include "game/hwr.h" +#include "game/items.h" +#include "game/shell.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" +#include "specific/s_audio_sample.h" + +#include +#include +#include +#include + +#include + +static void __cdecl M_LoadTexturePages(VFILE *file); +static void __cdecl M_LoadRooms(VFILE *file); +static void __cdecl M_LoadMeshBase(VFILE *file); +static void __cdecl M_LoadMeshes(VFILE *file); +static int32_t __cdecl M_LoadAnims(VFILE *file, int32_t **frame_pointers); +static void __cdecl M_LoadAnimChanges(VFILE *file); +static void __cdecl M_LoadAnimRanges(VFILE *file); +static void __cdecl M_LoadAnimCommands(VFILE *file); +static void __cdecl M_LoadAnimBones(VFILE *file); +static void __cdecl M_LoadAnimFrames(VFILE *file); +static void __cdecl M_LoadObjects(VFILE *file); +static void __cdecl M_LoadStaticObjects(VFILE *file); +static void __cdecl M_LoadTextures(VFILE *file); +static void __cdecl M_LoadSprites(VFILE *file); +static void __cdecl M_LoadItems(VFILE *file); +static void __cdecl M_LoadDepthQ(VFILE *file); +static void __cdecl M_LoadPalettes(VFILE *file); +static void __cdecl M_LoadCameras(VFILE *file); +static void __cdecl M_LoadSoundEffects(VFILE *file); +static void __cdecl M_LoadBoxes(VFILE *file); +static void __cdecl M_LoadAnimatedTextures(VFILE *file); +static void __cdecl M_LoadCinematic(VFILE *file); +static void __cdecl M_LoadDemo(VFILE *file); +static void __cdecl M_LoadDemoExternal(const char *level_name); +static void __cdecl M_LoadSamples(VFILE *file); + +static void __cdecl M_LoadTexturePages(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + char *base = NULL; + + const bool is_16_bit = g_TextureFormat.bpp >= 16; + const int32_t texture_size = TEXTURE_PAGE_WIDTH * TEXTURE_PAGE_HEIGHT; + const int32_t texture_size_8_bit = texture_size * sizeof(uint8_t); + const int32_t texture_size_16_bit = texture_size * sizeof(uint16_t); + + const int32_t num_pages = VFile_ReadS32(file); + LOG_INFO("texture pages: %d", num_pages); + + if (g_SavedAppSettings.render_mode == RM_SOFTWARE) { + for (int32_t i = 0; i < num_pages; i++) { + if (g_TexturePageBuffer8[i] == NULL) { + g_TexturePageBuffer8[i] = + game_malloc(texture_size, GBUF_TEXTURE_PAGES); + } + VFile_Read(file, g_TexturePageBuffer8[i], texture_size); + } + // skip 16-bit texture pages + VFile_Skip(file, num_pages * texture_size_16_bit); + goto finish; + } + + base = Memory_Alloc( + num_pages * (is_16_bit ? texture_size_16_bit : texture_size_8_bit)); + + if (is_16_bit) { + VFile_Skip(file, num_pages * texture_size_8_bit); + char *ptr = base; + for (int32_t i = 0; i < num_pages; i++) { + VFile_Read(file, ptr, texture_size_16_bit); + ptr += texture_size_16_bit; + } + HWR_LoadTexturePages(num_pages, base, NULL); + } else { + char *ptr = base; + for (int32_t i = 0; i < num_pages; i++) { + VFile_Read(file, ptr, texture_size_8_bit); + ptr += texture_size_8_bit; + } + VFile_Skip(file, num_pages * texture_size_16_bit); + HWR_LoadTexturePages((int32_t)num_pages, base, g_GamePalette8); + } + + g_TexturePageCount = num_pages; + +finish: + Memory_FreePointer(&base); + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadRooms(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + + g_RoomCount = VFile_ReadS16(file); + LOG_INFO("rooms: %d", g_RoomCount); + if (g_RoomCount > MAX_ROOMS) { + Shell_ExitSystem("Too many rooms"); + goto finish; + } + + g_Rooms = game_malloc(sizeof(ROOM) * g_RoomCount, GBUF_ROOMS); + assert(g_Rooms != NULL); + + for (int32_t i = 0; i < g_RoomCount; i++) { + ROOM *const r = &g_Rooms[i]; + + r->pos.x = VFile_ReadS32(file); + r->pos.y = 0; + r->pos.z = VFile_ReadS32(file); + + r->min_floor = VFile_ReadS32(file); + r->max_ceiling = VFile_ReadS32(file); + + const int32_t data_size = VFile_ReadS32(file); + r->data = game_malloc(sizeof(int16_t) * data_size, GBUF_ROOM_MESH); + VFile_Read(file, r->data, sizeof(int16_t) * data_size); + + const int16_t num_doors = VFile_ReadS16(file); + if (num_doors <= 0) { + r->portals = NULL; + } else { + r->portals = game_malloc( + sizeof(PORTAL) * num_doors + sizeof(PORTALS), + GBUF_ROOM_PORTALS); + r->portals->count = num_doors; + VFile_Read(file, r->portals->portal, sizeof(PORTAL) * num_doors); + } + + r->size.z = VFile_ReadS16(file); + r->size.x = VFile_ReadS16(file); + + r->sectors = game_malloc( + sizeof(SECTOR) * r->size.z * r->size.x, GBUF_ROOM_FLOOR); + for (int32_t i = 0; i < r->size.z * r->size.x; i++) { + SECTOR *const sector = &r->sectors[i]; + sector->idx = VFile_ReadU16(file); + sector->box = VFile_ReadS16(file); + sector->pit_room = VFile_ReadU8(file); + sector->floor = VFile_ReadS8(file); + sector->sky_room = VFile_ReadU8(file); + sector->ceiling = VFile_ReadS8(file); + } + + r->ambient_1 = VFile_ReadS16(file); + r->ambient_2 = VFile_ReadS16(file); + r->light_mode = VFile_ReadS16(file); + + r->num_lights = VFile_ReadS16(file); + if (!r->num_lights) { + r->lights = NULL; + } else { + r->lights = + game_malloc(sizeof(LIGHT) * r->num_lights, GBUF_ROOM_LIGHTS); + for (int32_t i = 0; i < r->num_lights; i++) { + LIGHT *const light = &r->lights[i]; + light->pos.x = VFile_ReadS32(file); + light->pos.y = VFile_ReadS32(file); + light->pos.z = VFile_ReadS32(file); + light->intensity_1 = VFile_ReadS16(file); + light->intensity_2 = VFile_ReadS16(file); + light->falloff_1 = VFile_ReadS32(file); + light->falloff_2 = VFile_ReadS32(file); + } + } + + r->num_meshes = VFile_ReadS16(file); + if (!r->num_meshes) { + r->meshes = NULL; + } else { + r->meshes = game_malloc( + sizeof(MESH) * r->num_meshes, GBUF_ROOM_STATIC_MESHES); + for (int32_t i = 0; i < r->num_meshes; i++) { + MESH *const mesh = &r->meshes[i]; + mesh->pos.x = VFile_ReadS32(file); + mesh->pos.y = VFile_ReadS32(file); + mesh->pos.z = VFile_ReadS32(file); + mesh->rot.y = VFile_ReadS16(file); + mesh->shade_1 = VFile_ReadS16(file); + mesh->shade_2 = VFile_ReadS16(file); + mesh->static_num = VFile_ReadS16(file); + } + } + + r->flipped_room = VFile_ReadS16(file); + r->flags = VFile_ReadU16(file); + + r->bound_active = 0; + r->bound_left = g_PhdWinMaxX; + r->bound_top = g_PhdWinMaxY; + r->bound_bottom = 0; + r->bound_right = 0; + r->item_num = NO_ITEM; + r->fx_num = NO_ITEM; + } + + const int32_t floor_data_size = VFile_ReadS32(file); + g_FloorData = + game_malloc(sizeof(int16_t) * floor_data_size, GBUF_FLOOR_DATA); + VFile_Read(file, g_FloorData, sizeof(int16_t) * floor_data_size); + +finish: + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadMeshBase(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t num_meshes = VFile_ReadS32(file); + LOG_INFO("meshes: %d", num_meshes); + g_MeshBase = game_malloc(sizeof(int16_t) * num_meshes, GBUF_MESHES); + VFile_Read(file, g_MeshBase, sizeof(int16_t) * num_meshes); + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadMeshes(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t num_mesh_ptrs = VFile_ReadS32(file); + LOG_INFO("mesh pointers: %d", num_mesh_ptrs); + int32_t *const mesh_indices = + (int32_t *)Memory_Alloc(sizeof(int32_t) * num_mesh_ptrs); + VFile_Read(file, mesh_indices, sizeof(int32_t) * num_mesh_ptrs); + + g_Meshes = + game_malloc(sizeof(int16_t *) * num_mesh_ptrs, GBUF_MESH_POINTERS); + for (int32_t i = 0; i < num_mesh_ptrs; i++) { + g_Meshes[i] = &g_MeshBase[mesh_indices[i] / 2]; + } + + Memory_Free(mesh_indices); + Benchmark_End(benchmark, NULL); +} + +static int32_t __cdecl M_LoadAnims(VFILE *const file, int32_t **frame_pointers) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t num_anims = VFile_ReadS32(file); + LOG_INFO("anims: %d", num_anims); + g_Anims = game_malloc(sizeof(ANIM) * num_anims, GBUF_ANIMS); + if (frame_pointers != NULL) { + *frame_pointers = Memory_Alloc(sizeof(int32_t) * num_anims); + } + + for (int32_t i = 0; i < num_anims; i++) { + ANIM *const anim = &g_Anims[i]; + const int32_t frame_idx = VFile_ReadS32(file); + if (frame_pointers != NULL) { + (*frame_pointers)[i] = frame_idx; + } + anim->frame_ptr = NULL; // filled later by the animation frame loader + anim->interpolation = VFile_ReadS16(file); + anim->current_anim_state = VFile_ReadS16(file); + anim->velocity = VFile_ReadS32(file); + anim->acceleration = VFile_ReadS32(file); + anim->frame_base = VFile_ReadS16(file); + anim->frame_end = VFile_ReadS16(file); + anim->jump_anim_num = VFile_ReadS16(file); + anim->jump_frame_num = VFile_ReadS16(file); + anim->num_changes = VFile_ReadS16(file); + anim->change_idx = VFile_ReadS16(file); + anim->num_commands = VFile_ReadS16(file); + anim->command_idx = VFile_ReadS16(file); + } + Benchmark_End(benchmark, NULL); + return num_anims; +} + +static void __cdecl M_LoadAnimChanges(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t num_anim_changes = VFile_ReadS32(file); + LOG_INFO("anim changes: %d", num_anim_changes); + g_AnimChanges = + game_malloc(sizeof(ANIM_CHANGE) * num_anim_changes, GBUF_STRUCTS); + for (int32_t i = 0; i < num_anim_changes; i++) { + ANIM_CHANGE *const change = &g_AnimChanges[i]; + change->goal_anim_state = VFile_ReadS16(file); + change->num_ranges = VFile_ReadS16(file); + change->range_idx = VFile_ReadS16(file); + } + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadAnimRanges(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t num_anim_ranges = VFile_ReadS32(file); + LOG_INFO("anim ranges: %d", num_anim_ranges); + g_AnimRanges = + game_malloc(sizeof(ANIM_RANGE) * num_anim_ranges, GBUF_ANIM_RANGES); + for (int32_t i = 0; i < num_anim_ranges; i++) { + ANIM_RANGE *const range = &g_AnimRanges[i]; + range->start_frame = VFile_ReadS16(file); + range->end_frame = VFile_ReadS16(file); + range->link_anim_num = VFile_ReadS16(file); + range->link_frame_num = VFile_ReadS16(file); + } + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadAnimCommands(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t num_anim_commands = VFile_ReadS32(file); + LOG_INFO("anim commands: %d", num_anim_commands); + g_AnimCommands = + game_malloc(sizeof(int16_t) * num_anim_commands, GBUF_ANIM_COMMANDS); + VFile_Read(file, g_AnimCommands, sizeof(int16_t) * num_anim_commands); + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadAnimBones(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t num_anim_bones = VFile_ReadS32(file); + LOG_INFO("anim bones: %d", num_anim_bones); + g_AnimBones = + game_malloc(sizeof(int32_t) * num_anim_bones, GBUF_ANIM_BONES); + VFile_Read(file, g_AnimBones, sizeof(int32_t) * num_anim_bones); + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadAnimFrames(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t anim_frame_data_size = VFile_ReadS32(file); + LOG_INFO("anim frame data size: %d", anim_frame_data_size); + g_AnimFrames = + game_malloc(sizeof(int16_t) * anim_frame_data_size, GBUF_ANIM_FRAMES); + // TODO: make me FRAME_INFO + int16_t *ptr = (int16_t *)&g_AnimFrames[0]; + VFile_Read(file, ptr, sizeof(int16_t) * anim_frame_data_size); + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadObjects(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t num_objects = VFile_ReadS32(file); + LOG_INFO("objects: %d", num_objects); + for (int32_t i = 0; i < num_objects; i++) { + const GAME_OBJECT_ID object_id = VFile_ReadS32(file); + OBJECT *const object = &g_Objects[object_id]; + object->mesh_count = VFile_ReadS16(file); + object->mesh_idx = VFile_ReadS16(file); + object->bone_idx = VFile_ReadS32(file); + const int32_t frame_idx = VFile_ReadS32(file); + object->frame_base = ((int16_t *)g_AnimFrames) + frame_idx / 2; + object->anim_idx = VFile_ReadS16(file); + object->loaded = 1; + } + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadStaticObjects(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t num_static_objects = VFile_ReadS32(file); + LOG_INFO("static objects: %d", num_static_objects); + for (int32_t i = 0; i < num_static_objects; i++) { + const int32_t static_num = VFile_ReadS32(file); + STATIC_INFO *static_obj = &g_StaticObjects[static_num]; + static_obj->mesh_idx = VFile_ReadS16(file); + static_obj->draw_bounds.min_x = VFile_ReadS16(file); + static_obj->draw_bounds.max_x = VFile_ReadS16(file); + static_obj->draw_bounds.min_y = VFile_ReadS16(file); + static_obj->draw_bounds.max_y = VFile_ReadS16(file); + static_obj->draw_bounds.min_z = VFile_ReadS16(file); + static_obj->draw_bounds.max_z = VFile_ReadS16(file); + static_obj->collision_bounds.min_x = VFile_ReadS16(file); + static_obj->collision_bounds.max_x = VFile_ReadS16(file); + static_obj->collision_bounds.min_y = VFile_ReadS16(file); + static_obj->collision_bounds.max_y = VFile_ReadS16(file); + static_obj->collision_bounds.min_z = VFile_ReadS16(file); + static_obj->collision_bounds.max_z = VFile_ReadS16(file); + static_obj->flags = VFile_ReadU16(file); + } + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadTextures(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t num_textures = VFile_ReadS32(file); + LOG_INFO("textures: %d", num_textures); + if (num_textures > MAX_TEXTURES) { + Shell_ExitSystem("Too many textures"); + return; + } + + g_TextureInfoCount = num_textures; + for (int32_t i = 0; i < num_textures; i++) { + PHD_TEXTURE *texture = &g_TextureInfo[i]; + texture->draw_type = VFile_ReadU16(file); + texture->tex_page = VFile_ReadU16(file); + for (int32_t j = 0; j < 4; j++) { + texture->uv[j].u = VFile_ReadU16(file); + texture->uv[j].v = VFile_ReadU16(file); + } + } + + for (int32_t i = 0; i < num_textures; i++) { + uint16_t *const uv = &g_TextureInfo[i].uv[0].u; + uint8_t byte = 0; + for (int32_t j = 0; j < 8; j++) { + if ((uv[j] & 0x80) != 0) { + uv[j] |= 0xFF; + byte |= 1 << j; + } else { + uv[j] &= 0xFF00; + } + } + g_LabTextureUVFlag[i] = byte; + } + + AdjustTextureUVs(true); + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadSprites(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t num_sprites = VFile_ReadS32(file); + LOG_DEBUG("sprites: %d", num_sprites); + for (int32_t i = 0; i < num_sprites; i++) { + PHD_SPRITE *const sprite = &g_PhdSprites[i]; + sprite->tex_page = VFile_ReadU16(file); + sprite->offset = VFile_ReadU16(file); + sprite->width = VFile_ReadU16(file); + sprite->height = VFile_ReadU16(file); + sprite->x0 = VFile_ReadS16(file); + sprite->y0 = VFile_ReadS16(file); + sprite->x1 = VFile_ReadS16(file); + sprite->y1 = VFile_ReadS16(file); + } + + const int32_t num_statics = VFile_ReadS32(file); + LOG_DEBUG("statics: %d", num_statics); + for (int32_t i = 0; i < num_statics; i++) { + int32_t object_id = VFile_ReadS32(file); + if (object_id >= O_NUMBER_OF) { + object_id -= O_NUMBER_OF; + STATIC_INFO *const static_object = &g_StaticObjects[object_id]; + VFile_Skip(file, sizeof(int16_t)); + static_object->mesh_idx = VFile_ReadS16(file); + } else { + OBJECT *const object = &g_Objects[object_id]; + object->mesh_count = VFile_ReadS16(file); + object->mesh_idx = VFile_ReadS16(file); + object->loaded = 1; + } + } + + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadItems(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + + const int32_t num_items = VFile_ReadS32(file); + LOG_DEBUG("items: %d", num_items); + if (!num_items) { + goto finish; + } + + if (num_items > MAX_ITEMS) { + Shell_ExitSystem("Too many items"); + goto finish; + } + + g_Items = game_malloc(sizeof(ITEM) * MAX_ITEMS, GBUF_ITEMS); + g_LevelItemCount = num_items; + + Item_InitialiseArray(MAX_ITEMS); + + for (int32_t i = 0; i < num_items; i++) { + ITEM *const item = &g_Items[i]; + item->object_id = VFile_ReadS16(file); + item->room_num = VFile_ReadS16(file); + item->pos.x = VFile_ReadS32(file); + item->pos.y = VFile_ReadS32(file); + item->pos.z = VFile_ReadS32(file); + item->rot.y = VFile_ReadS16(file); + item->shade_1 = VFile_ReadS16(file); + item->shade_2 = VFile_ReadS16(file); + item->flags = VFile_ReadS16(file); + if (item->object_id < 0 || item->object_id >= O_NUMBER_OF) { + Shell_ExitSystemFmt( + "Bad object number (%d) on item %d", item->object_id, i); + goto finish; + } + Item_Initialise(i); + } + +finish: + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadDepthQ(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + for (int32_t i = 0; i < 32; i++) { + VFile_Read(file, g_DepthQTable[i].index, sizeof(uint8_t) * 256); + g_DepthQTable[i].index[0] = 0; + } + + if (g_GameVid_IsWindowedVGA) { + RGB_888 palette[256]; + CopyBitmapPalette( + g_GamePalette8, g_DepthQTable[0].index, 32 * sizeof(DEPTHQ_ENTRY), + palette); + SyncSurfacePalettes( + g_DepthQTable, 256, 32, 256, g_GamePalette8, g_DepthQTable, 256, + palette, true); + memcpy(g_GamePalette8, palette, sizeof(g_GamePalette8)); + + for (int32_t i = 0; i < 256; i++) { + g_DepthQIndex[i] = S_COLOR( + g_GamePalette8[i].red, g_GamePalette8[i].green, + g_GamePalette8[i].blue); + } + } else { + for (int32_t i = 0; i < 256; i++) { + g_DepthQIndex[i] = g_DepthQTable[24].index[i]; + } + } + + for (int32_t i = 0; i < 32; i++) { + for (int32_t j = 0; j < 256; j++) { + g_GouraudTable[j].index[i] = g_DepthQTable[i].index[j]; + } + } + + g_IsWet = 0; + for (int32_t i = 0; i < 256; i++) { + g_WaterPalette[i].red = g_GamePalette8[i].red * 2 / 3; + g_WaterPalette[i].green = g_GamePalette8[i].green * 2 / 3; + g_WaterPalette[i].blue = g_GamePalette8[i].blue; + } + + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadPalettes(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + VFile_Read(file, g_GamePalette8, sizeof(RGB_888) * 256); + + g_GamePalette8[0].red = 0; + g_GamePalette8[0].green = 0; + g_GamePalette8[0].blue = 0; + + for (int32_t i = 1; i < 256; i++) { + RGB_888 *col = &g_GamePalette8[i]; + col->red = (col->red << 2) | (col->red >> 4); + col->green = (col->green << 2) | (col->green >> 4); + col->blue = (col->blue << 2) | (col->blue >> 4); + } + + VFile_Read(file, g_GamePalette16, sizeof(PALETTEENTRY) * 256); + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadCameras(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + g_NumCameras = VFile_ReadS32(file); + LOG_DEBUG("fixed cameras: %d", g_NumCameras); + if (!g_NumCameras) { + goto finish; + } + + g_Camera.fixed = + game_malloc(sizeof(OBJECT_VECTOR) * g_NumCameras, GBUF_CAMERAS); + for (int32_t i = 0; i < g_NumCameras; i++) { + OBJECT_VECTOR *const camera = &g_Camera.fixed[i]; + camera->x = VFile_ReadS32(file); + camera->y = VFile_ReadS32(file); + camera->z = VFile_ReadS32(file); + camera->data = VFile_ReadS16(file); + camera->flags = VFile_ReadS16(file); + } + +finish: + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadSoundEffects(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + + g_SoundEffectCount = VFile_ReadS32(file); + LOG_DEBUG("sound effects: %d", g_SoundEffectCount); + if (!g_SoundEffectCount) { + goto finish; + } + + g_SoundEffects = + game_malloc(sizeof(OBJECT_VECTOR) * g_SoundEffectCount, GBUF_SOUND_FX); + for (int32_t i = 0; i < g_SoundEffectCount; i++) { + OBJECT_VECTOR *const effect = &g_SoundEffects[i]; + effect->x = VFile_ReadS32(file); + effect->y = VFile_ReadS32(file); + effect->z = VFile_ReadS32(file); + effect->data = VFile_ReadS16(file); + effect->flags = VFile_ReadS16(file); + } + +finish: + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadBoxes(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + g_BoxCount = VFile_ReadS32(file); + g_Boxes = game_malloc(sizeof(BOX_INFO) * g_BoxCount, GBUF_BOXES); + for (int32_t i = 0; i < g_BoxCount; i++) { + BOX_INFO *const box = &g_Boxes[i]; + box->left = VFile_ReadU8(file); + box->right = VFile_ReadU8(file); + box->top = VFile_ReadU8(file); + box->bottom = VFile_ReadU8(file); + box->height = VFile_ReadS16(file); + box->overlap_index = VFile_ReadS16(file); + } + + const int32_t num_overlaps = VFile_ReadS32(file); + g_Overlap = game_malloc(sizeof(uint16_t) * num_overlaps, GBUF_OVERLAPS); + VFile_Read(file, g_Overlap, sizeof(uint16_t) * num_overlaps); + + for (int32_t i = 0; i < 2; i++) { + for (int32_t j = 0; j < 4; j++) { + const bool skip = j == 2 + || (j == 1 && !g_Objects[O_SPIDER].loaded + && !g_Objects[O_SKIDOO_ARMED].loaded) + || (j == 3 && !g_Objects[O_YETI].loaded + && !g_Objects[O_WORKER_3].loaded); + + if (skip) { + VFile_Skip(file, sizeof(int16_t) * g_BoxCount); + continue; + } + + g_GroundZone[j][i] = + game_malloc(sizeof(int16_t) * g_BoxCount, GBUF_GROUND_ZONE); + VFile_Read(file, g_GroundZone[j][i], sizeof(int16_t) * g_BoxCount); + } + + g_FlyZone[i] = game_malloc(sizeof(int16_t) * g_BoxCount, GBUF_FLY_ZONE); + VFile_Read(file, g_FlyZone[i], sizeof(int16_t) * g_BoxCount); + } + + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadAnimatedTextures(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + const int32_t num_ranges = VFile_ReadS32(file); + g_AnimTextureRanges = game_malloc( + sizeof(int16_t) * num_ranges, GBUF_ANIMATING_TEXTURE_RANGES); + VFile_Read(file, g_AnimTextureRanges, sizeof(int16_t) * num_ranges); + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadCinematic(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + g_NumCineFrames = VFile_ReadS16(file); + if (g_NumCineFrames <= 0) { + g_CineLoaded = false; + goto finish; + } + + g_CineData = game_malloc( + sizeof(CINE_FRAME) * g_NumCineFrames, GBUF_CINEMATIC_FRAMES); + for (int32_t i = 0; i < g_NumCineFrames; i++) { + CINE_FRAME *const frame = &g_CineData[i]; + frame->tx = VFile_ReadS16(file); + frame->ty = VFile_ReadS16(file); + frame->tz = VFile_ReadS16(file); + frame->cx = VFile_ReadS16(file); + frame->cy = VFile_ReadS16(file); + frame->cz = VFile_ReadS16(file); + frame->fov = VFile_ReadS16(file); + frame->roll = VFile_ReadS16(file); + } + g_CineLoaded = true; + +finish: + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadDemo(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + g_DemoCount = 0; + + // TODO: is the allocation necessary if there's no demo data? + // TODO: do not hardcode the allocation size + g_DemoPtr = game_malloc(36000, GBUF_LOAD_DEMO_BUFFER); + + const int32_t demo_size = VFile_ReadU16(file); + LOG_DEBUG("demo input size: %d", demo_size); + if (!demo_size) { + g_IsDemoLoaded = false; + } else { + g_IsDemoLoaded = true; + VFile_Read(file, g_DemoPtr, demo_size); + } + + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadDemoExternal(const char *const level_name) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + char file_name[MAX_PATH]; + strcpy(file_name, level_name); + ChangeFileNameExtension(file_name, "DEM"); + + HANDLE handle = CreateFileA( + file_name, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + NULL); + if (handle == INVALID_HANDLE_VALUE) { + return; + } + + // TODO: do not hardcode the allocation size + DWORD bytes_read; + ReadFileSync(handle, g_DemoPtr, 36000, &bytes_read, 0); + g_IsDemoLoaded = bytes_read != 0; + CloseHandle(handle); + Benchmark_End(benchmark, NULL); +} + +static void __cdecl M_LoadSamples(VFILE *const file) +{ + BENCHMARK *const benchmark = Benchmark_Start(); + int32_t *sample_offsets = NULL; + + g_SoundIsActive = false; + if (!S_Audio_Sample_IsEnabled()) { + goto finish; + } + + S_Audio_Sample_CloseAllTracks(); + + VFile_Read(file, g_SampleLUT, sizeof(int16_t) * SFX_NUMBER_OF); + g_NumSampleInfos = VFile_ReadS32(file); + LOG_DEBUG("sample infos: %d", g_NumSampleInfos); + if (!g_NumSampleInfos) { + goto finish; + } + + g_SampleInfos = + game_malloc(sizeof(SAMPLE_INFO) * g_NumSampleInfos, GBUF_SAMPLE_INFOS); + for (int32_t i = 0; i < g_NumSampleInfos; i++) { + SAMPLE_INFO *const sample_info = &g_SampleInfos[i]; + sample_info->number = VFile_ReadS16(file); + sample_info->volume = VFile_ReadS16(file); + sample_info->randomness = VFile_ReadS16(file); + sample_info->flags = VFile_ReadS16(file); + } + + const int32_t num_samples = VFile_ReadS32(file); + LOG_DEBUG("samples: %d", num_samples); + if (!num_samples) { + goto finish; + } + + sample_offsets = Memory_Alloc(sizeof(int32_t) * num_samples); + VFile_Read(file, sample_offsets, sizeof(int32_t) * num_samples); + + const char *const file_name = "data\\main.sfx"; + const char *const full_path = GetFullPath(file_name); + LOG_DEBUG("Loading samples from %s", full_path); + HANDLE sfx_handle = CreateFileA( + full_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + NULL); + + if (sfx_handle == INVALID_HANDLE_VALUE) { + Shell_ExitSystemFmt( + "Could not open %s file: 0x%x", file_name, GetLastError()); + goto finish; + } + + // TODO: refactor these WAVE/RIFF shenanigans + int32_t sample_id = 0; + for (int32_t i = 0; sample_id < num_samples; i++) { + char header[0x2C]; + ReadFileSync(sfx_handle, header, 0x2C, NULL, NULL); + if (*(int32_t *)(header + 0) != 0x46464952 + || *(int32_t *)(header + 8) != 0x45564157 + || *(int32_t *)(header + 36) != 0x61746164) { + LOG_ERROR("Unexpected sample header for sample %d", i); + goto finish; + } + const int32_t data_size = *(int32_t *)(header + 0x28); + const int32_t aligned_size = (data_size + 1) & ~1; + + *(int16_t *)(header + 16) = 0; + if (sample_offsets[sample_id] != i) { + SetFilePointer(sfx_handle, aligned_size, NULL, FILE_CURRENT); + continue; + } + + char *sample_data = Memory_Alloc(aligned_size); + ReadFileSync(sfx_handle, sample_data, aligned_size, NULL, NULL); + // TODO: do not reconstruct the header in S_Audio_Sample_Load, just + // pass the entire sample directly + const bool result = S_Audio_Sample_Load( + sample_id, (LPWAVEFORMATEX)(header + 20), sample_data, data_size); + Memory_FreePointer(&sample_data); + if (!result) { + goto finish; + } + + sample_id++; + } + CloseHandle(sfx_handle); + + g_SoundIsActive = true; + +finish: + Memory_FreePointer(&sample_offsets); + Benchmark_End(benchmark, NULL); +} + +bool __cdecl Level_Load(const char *const file_name, const int32_t level_num) +{ + LOG_DEBUG("%s (num=%d)", g_GF_LevelNames[level_num], level_num); + init_game_malloc(); + + BENCHMARK *const benchmark = Benchmark_Start(); + bool result = false; + + const char *full_path = GetFullPath(file_name); + strcpy(g_LevelFileName, full_path); + + VFILE *file = VFile_CreateFromPath(full_path); + + const int32_t version = VFile_ReadS32(file); + if (version > 45) { + Shell_ExitSystemFmt( + "FATAL: Level %d (%s) requires a new TOMB2.EXE (version %d) to run", + level_num, full_path, file_name); + return false; + } + + if (version < 45) { + Shell_ExitSystemFmt( + "FATAL: Level %d (%s) is OUT OF DATE (version %d). COPY NEW " + "EDITORS AND REMAKE LEVEL", + level_num, full_path, file_name); + return false; + } + + g_LevelFilePalettesOffset = VFile_GetPos(file); + M_LoadPalettes(file); + + g_LevelFileTexPagesOffset = VFile_GetPos(file); + M_LoadTexturePages(file); + VFile_Skip(file, 4); + + M_LoadRooms(file); + + M_LoadMeshBase(file); + M_LoadMeshes(file); + + int32_t *frame_pointers = NULL; + const int32_t num_anims = M_LoadAnims(file, &frame_pointers); + M_LoadAnimChanges(file); + M_LoadAnimRanges(file); + M_LoadAnimCommands(file); + M_LoadAnimBones(file); + M_LoadAnimFrames(file); + + for (int32_t i = 0; i < num_anims; i++) { + ANIM *const anim = &g_Anims[i]; + // TODO: this is horrible + anim->frame_ptr = ((int16_t *)g_AnimFrames) + frame_pointers[i] / 2; + } + Memory_FreePointer(&frame_pointers); + + M_LoadObjects(file); + InitialiseObjects(); + M_LoadStaticObjects(file); + M_LoadTextures(file); + + M_LoadSprites(file); + M_LoadCameras(file); + M_LoadSoundEffects(file); + M_LoadBoxes(file); + M_LoadAnimatedTextures(file); + M_LoadItems(file); + + g_LevelFileDepthQOffset = VFile_GetPos(file); + M_LoadDepthQ(file); + M_LoadCinematic(file); + M_LoadDemo(file); + M_LoadSamples(file); + + M_LoadDemoExternal(full_path); + VFile_Close(file); + file = NULL; + + Benchmark_End(benchmark, NULL); + return true; +} diff --git a/src/tr2/game/level.h b/src/tr2/game/level.h new file mode 100644 index 000000000..52f672419 --- /dev/null +++ b/src/tr2/game/level.h @@ -0,0 +1,5 @@ +#pragma once + +#include "global/types.h" + +bool __cdecl Level_Load(const char *file_name, int32_t level_num); diff --git a/src/tr2/game/los.c b/src/tr2/game/los.c new file mode 100644 index 000000000..c67137c0a --- /dev/null +++ b/src/tr2/game/los.c @@ -0,0 +1,391 @@ +#include "game/los.h" + +#include "game/items.h" +#include "game/math.h" +#include "game/room.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#include + +int32_t __cdecl LOS_CheckX( + const GAME_VECTOR *const start, GAME_VECTOR *const target) +{ + const int32_t dx = target->x - start->x; + if (dx == 0) { + return 1; + } + + const int32_t dy = ((target->y - start->y) << WALL_SHIFT) / dx; + const int32_t dz = ((target->z - start->z) << WALL_SHIFT) / dx; + + int16_t room_num = start->room_num; + int16_t last_room_num = start->room_num; + + g_LOSRooms[0] = room_num; + g_LOSNumRooms = 1; + + if (dx < 0) { + int32_t x = start->x & (~(WALL_L - 1)); + int32_t y = start->y + ((dy * (x - start->x)) >> WALL_SHIFT); + int32_t z = start->z + ((dz * (x - start->x)) >> WALL_SHIFT); + + while (x > target->x) { + { + const SECTOR *const sector = Room_GetSector(x, y, z, &room_num); + const int32_t height = Room_GetHeight(sector, x, y, z); + const int32_t ceiling = Room_GetCeiling(sector, x, y, z); + if (y > height || y < ceiling) { + target->x = x; + target->y = y; + target->z = z; + target->room_num = room_num; + return -1; + } + } + + if (room_num != last_room_num) { + last_room_num = room_num; + g_LOSRooms[g_LOSNumRooms++] = room_num; + } + + { + const SECTOR *const sector = + Room_GetSector(x - 1, y, z, &room_num); + const int32_t height = Room_GetHeight(sector, x - 1, y, z); + const int32_t ceiling = Room_GetCeiling(sector, x - 1, y, z); + if (y > height || y < ceiling) { + target->x = x; + target->y = y; + target->z = z; + target->room_num = last_room_num; + return 0; + } + } + + x -= WALL_L; + y -= dy; + z -= dz; + } + } else { + int32_t x = start->x | (WALL_L - 1); + int32_t y = start->y + (((x - start->x) * dy) >> WALL_SHIFT); + int32_t z = start->z + (((x - start->x) * dz) >> WALL_SHIFT); + + while (x < target->x) { + { + const SECTOR *const sector = Room_GetSector(x, y, z, &room_num); + const int32_t height = Room_GetHeight(sector, x, y, z); + const int32_t ceiling = Room_GetCeiling(sector, x, y, z); + if (y > height || y < ceiling) { + target->z = z; + target->y = y; + target->x = x; + target->room_num = room_num; + return -1; + } + } + + if (room_num != last_room_num) { + last_room_num = room_num; + g_LOSRooms[g_LOSNumRooms++] = room_num; + } + + { + const SECTOR *const sector = + Room_GetSector(x + 1, y, z, &room_num); + const int32_t height = Room_GetHeight(sector, x + 1, y, z); + const int32_t ceiling = Room_GetCeiling(sector, x + 1, y, z); + if (y > height || y < ceiling) { + target->x = x; + target->y = y; + target->z = z; + target->room_num = last_room_num; + return 0; + } + } + + x += WALL_L; + y += dy; + z += dz; + } + } + + target->room_num = room_num; + return 1; +} + +int32_t __cdecl LOS_CheckZ( + const GAME_VECTOR *const start, GAME_VECTOR *const target) +{ + const int32_t dz = target->z - start->z; + if (dz == 0) { + return 1; + } + + const int32_t dx = ((target->x - start->x) << WALL_SHIFT) / dz; + const int32_t dy = ((target->y - start->y) << WALL_SHIFT) / dz; + + int16_t room_num = start->room_num; + int16_t last_room_num = start->room_num; + + g_LOSRooms[0] = room_num; + g_LOSNumRooms = 1; + + if (dz < 0) { + int32_t z = start->z & (~(WALL_L - 1)); + int32_t x = start->x + ((dx * (z - start->z)) >> WALL_SHIFT); + int32_t y = start->y + ((dy * (z - start->z)) >> WALL_SHIFT); + + while (z > target->z) { + { + const SECTOR *const sector = Room_GetSector(x, y, z, &room_num); + const int32_t height = Room_GetHeight(sector, x, y, z); + const int32_t ceiling = Room_GetCeiling(sector, x, y, z); + if (y > height || y < ceiling) { + target->x = x; + target->y = y; + target->z = z; + target->room_num = room_num; + return -1; + } + } + + if (room_num != last_room_num) { + last_room_num = room_num; + g_LOSRooms[g_LOSNumRooms++] = room_num; + } + + { + const SECTOR *const sector = + Room_GetSector(x, y, z - 1, &room_num); + const int32_t height = Room_GetHeight(sector, x, y, z - 1); + const int32_t ceiling = Room_GetCeiling(sector, x, y, z - 1); + if (y > height || y < ceiling) { + target->x = x; + target->y = y; + target->z = z; + target->room_num = last_room_num; + return 0; + } + } + + z -= WALL_L; + x -= dx; + y -= dy; + } + } else { + int32_t z = start->z | (WALL_L - 1); + int32_t x = start->x + ((dx * (z - start->z)) >> WALL_SHIFT); + int32_t y = start->y + ((dy * (z - start->z)) >> WALL_SHIFT); + + while (z < target->z) { + { + const SECTOR *const sector = Room_GetSector(x, y, z, &room_num); + const int32_t height = Room_GetHeight(sector, x, y, z); + const int32_t ceiling = Room_GetCeiling(sector, x, y, z); + if (y > height || y < ceiling) { + target->x = x; + target->y = y; + target->z = z; + target->room_num = room_num; + return -1; + } + } + + if (room_num != last_room_num) { + last_room_num = room_num; + g_LOSRooms[g_LOSNumRooms++] = room_num; + } + + { + const SECTOR *const sector = + Room_GetSector(x, y, z + 1, &room_num); + const int32_t height = Room_GetHeight(sector, x, y, z + 1); + const int32_t ceiling = Room_GetCeiling(sector, x, y, z + 1); + if (y > height || y < ceiling) { + target->x = x; + target->y = y; + target->z = z; + target->room_num = last_room_num; + return 0; + } + } + + z += WALL_L; + x += dx; + y += dy; + } + } + + target->room_num = room_num; + return 1; +} + +int32_t __cdecl LOS_ClipTarget( + const GAME_VECTOR *const start, GAME_VECTOR *const target, + const SECTOR *const sector) +{ + const int32_t dx = target->x - start->x; + const int32_t dy = target->y - start->y; + const int32_t dz = target->z - start->z; + + const int32_t height = + Room_GetHeight(sector, target->x, target->y, target->z); + if (target->y > height && start->y < height) { + target->y = height; + target->x = start->x + dx * (height - start->y) / dy; + target->z = start->z + dz * (height - start->y) / dy; + return 0; + } + + const int32_t ceiling = + Room_GetCeiling(sector, target->x, target->y, target->z); + if (target->y < ceiling && start->y > ceiling) { + target->y = ceiling; + target->x = start->x + dx * (ceiling - start->y) / dy; + target->z = start->z + dz * (ceiling - start->y) / dy; + return 0; + } + + return 1; +} + +int32_t __cdecl LOS_Check( + const GAME_VECTOR *const start, GAME_VECTOR *const target) +{ + int32_t los1; + int32_t los2; + + const int32_t dx = ABS(target->x - start->x); + const int32_t dz = ABS(target->z - start->z); + + if (dz > dx) { + los1 = LOS_CheckX(start, target); + los2 = LOS_CheckZ(start, target); + } else { + los1 = LOS_CheckZ(start, target); + los2 = LOS_CheckX(start, target); + } + + if (!los2) { + return 0; + } + + if (dx == 0 && dz == 0) { + target->room_num = start->room_num; + } + + const SECTOR *const sector = + Room_GetSector(target->x, target->y, target->z, &target->room_num); + + if (!LOS_ClipTarget(start, target, sector)) { + return 0; + } + if (los1 == 1 && los2 == 1) { + return 1; + } + return 0; +} + +int32_t __cdecl LOS_CheckSmashable( + const GAME_VECTOR *const start, const GAME_VECTOR *const target) +{ + const int32_t dx = target->x - start->x; + const int32_t dy = target->y - start->y; + const int32_t dz = target->z - start->z; + + for (int32_t i = 0; i < g_LOSNumRooms; i++) { + for (int16_t item_num = g_Rooms[g_LOSRooms[i]].item_num; + item_num != NO_ITEM; item_num = g_Items[item_num].next_item) { + const ITEM *const item = &g_Items[item_num]; + if (item->status == IS_DEACTIVATED) { + continue; + } + + if (!Item_IsSmashable(item)) { + continue; + } + + const DIRECTION direction = Math_GetDirection(item->rot.y); + const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); + const int16_t *x_extent; + const int16_t *z_extent = NULL; + switch (direction) { + case DIR_EAST: + case DIR_WEST: + x_extent = &bounds->min_z; + z_extent = &bounds->min_x; + break; + case DIR_NORTH: + case DIR_SOUTH: + x_extent = &bounds->min_x; + z_extent = &bounds->min_z; + break; + default: + break; + } + assert(z_extent != NULL); + + int32_t failure = 0; + if (ABS(dz) > ABS(dx)) { + int32_t distance = item->pos.z + z_extent[0] - start->z; + for (int32_t j = 0; j < 2; j++) { + if ((distance >= 0) == (dz >= 0)) { + const int32_t y = dy * distance / dz; + if (y <= item->pos.y + bounds->min_y - start->y + || y >= item->pos.y + bounds->max_y - start->y) { + continue; + } + + const int32_t x = dx * distance / dz; + if (x < item->pos.x + x_extent[0] - start->x) { + failure |= 1; + } else if (x > item->pos.x + x_extent[1] - start->x) { + failure |= 2; + } else { + return item_num; + } + } + + distance = item->pos.z + z_extent[1] - start->z; + } + + if (failure == 3) { + return item_num; + } + } else { + int32_t distance = item->pos.x + x_extent[0] - start->x; + for (int32_t j = 0; j < 2; j++) { + if ((distance >= 0) == (dx >= 0)) { + const int32_t y = dy * distance / dx; + if (y <= item->pos.y + bounds->min_y - start->y + || y >= item->pos.y + bounds->max_y - start->y) { + continue; + } + + const int32_t z = dz * distance / dx; + if (z < item->pos.z + z_extent[0] - start->z) { + failure |= 1; + } else if (z > item->pos.z + z_extent[1] - start->z) { + failure |= 2; + } else { + return item_num; + } + } + + distance = item->pos.x + x_extent[1] - start->x; + } + + if (failure == 3) { + return item_num; + } + } + } + } + + return NO_ITEM; +} diff --git a/src/tr2/game/los.h b/src/tr2/game/los.h new file mode 100644 index 000000000..f01892247 --- /dev/null +++ b/src/tr2/game/los.h @@ -0,0 +1,11 @@ +#pragma once + +#include "global/types.h" + +int32_t __cdecl LOS_CheckX(const GAME_VECTOR *start, GAME_VECTOR *target); +int32_t __cdecl LOS_CheckZ(const GAME_VECTOR *start, GAME_VECTOR *target); +int32_t __cdecl LOS_ClipTarget( + const GAME_VECTOR *start, GAME_VECTOR *target, const SECTOR *sector); +int32_t __cdecl LOS_Check(const GAME_VECTOR *start, GAME_VECTOR *target); +int32_t __cdecl LOS_CheckSmashable( + const GAME_VECTOR *start, const GAME_VECTOR *target); diff --git a/src/tr2/game/lot.c b/src/tr2/game/lot.c new file mode 100644 index 000000000..40629b1f1 --- /dev/null +++ b/src/tr2/game/lot.c @@ -0,0 +1,222 @@ +#include "game/lot.h" + +#include "game/box.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#include + +void __cdecl LOT_InitialiseArray(void) +{ + g_BaddieSlots = + game_malloc(NUM_SLOTS * sizeof(CREATURE), GBUF_CREATURE_DATA); + + for (int32_t i = 0; i < NUM_SLOTS; i++) { + CREATURE *const creature = &g_BaddieSlots[i]; + creature->item_num = NO_ITEM; + creature->lot.node = + game_malloc(g_BoxCount * sizeof(BOX_NODE), GBUF_CREATURE_LOT); + } + + g_SlotsUsed = 0; +} + +void __cdecl LOT_DisableBaddieAI(const int16_t item_num) +{ + CREATURE *creature; + + if (item_num == g_Lara.item_num) { + creature = g_Lara.creature; + g_Lara.creature = NULL; + } else { + ITEM *const item = &g_Items[item_num]; + creature = (CREATURE *)item->data; + item->data = NULL; + } + + if (creature != NULL) { + creature->item_num = NO_ITEM; + g_SlotsUsed--; + } +} + +bool __cdecl LOT_EnableBaddieAI(const int16_t item_num, const bool always) +{ + if (g_Lara.item_num == item_num) { + if (g_Lara.creature != NULL) { + return true; + } + } else if (g_Items[item_num].data != NULL) { + return true; + } + + if (g_SlotsUsed < NUM_SLOTS) { + for (int32_t slot = 0; slot < NUM_SLOTS; slot++) { + if (g_BaddieSlots[slot].item_num == NO_ITEM) { + LOT_InitialiseSlot(item_num, slot); + return true; + } + } + assert(false); + } + + int32_t worst_dist = 0; + if (!always) { + const ITEM *const item = &g_Items[item_num]; + const int32_t dx = (item->pos.x - g_Camera.pos.pos.x) >> 8; + const int32_t dy = (item->pos.y - g_Camera.pos.pos.y) >> 8; + const int32_t dz = (item->pos.z - g_Camera.pos.pos.z) >> 8; + worst_dist = SQUARE(dx) + SQUARE(dy) + SQUARE(dz); + } + + int32_t worst_slot = -1; + for (int32_t slot = 0; slot < NUM_SLOTS; slot++) { + const int32_t item_num = g_BaddieSlots[slot].item_num; + const ITEM *const item = &g_Items[item_num]; + const int32_t dx = (item->pos.x - g_Camera.pos.pos.x) >> 8; + const int32_t dy = (item->pos.y - g_Camera.pos.pos.y) >> 8; + const int32_t dz = (item->pos.z - g_Camera.pos.pos.z) >> 8; + const int32_t dist = SQUARE(dx) + SQUARE(dy) + SQUARE(dz); + if (dist > worst_dist) { + worst_dist = dist; + worst_slot = slot; + } + } + + if (worst_slot < 0) { + return false; + } + + const CREATURE *const creature = &g_BaddieSlots[worst_slot]; + g_Items[creature->item_num].status = IS_INVISIBLE; + LOT_DisableBaddieAI(creature->item_num); + LOT_InitialiseSlot(item_num, worst_slot); + return true; +} + +void __cdecl LOT_InitialiseSlot(const int16_t item_num, const int32_t slot) +{ + + CREATURE *const creature = &g_BaddieSlots[slot]; + ITEM *const item = &g_Items[item_num]; + + if (item_num == g_Lara.item_num) { + g_Lara.creature = &g_BaddieSlots[slot]; + } else { + item->data = creature; + } + + creature->item_num = item_num; + creature->mood = MOOD_BORED; + creature->neck_rotation = 0; + creature->head_rotation = 0; + creature->maximum_turn = PHD_DEGREE; + creature->flags = 0; + creature->enemy = 0; + creature->lot.step = STEP_L; + creature->lot.drop = -STEP_L * 2; + creature->lot.block_mask = BOX_BLOCKED; + creature->lot.fly = 0; + + switch (item->object_id) { + case O_LARA: + creature->lot.step = WALL_L * 20; + creature->lot.drop = -WALL_L * 20; + creature->lot.fly = STEP_L; + break; + + case O_SHARK: + case O_BARRACUDA: + case O_DIVER: + case O_JELLY: + case O_CROW: + case O_EAGLE: + creature->lot.step = WALL_L * 20; + creature->lot.drop = -WALL_L * 20; + creature->lot.fly = STEP_L / 16; + if (item->object_id == O_SHARK) { + creature->lot.block_mask = BOX_BLOCKABLE; + } + break; + + case O_WORKER_3: + case O_WORKER_4: + case O_YETI: + creature->lot.step = WALL_L; + creature->lot.drop = -WALL_L; + break; + + case O_SPIDER: + case O_SKIDOO_ARMED: + creature->lot.step = WALL_L / 2; + creature->lot.drop = -WALL_L; + break; + + case O_DINO: + creature->lot.block_mask = BOX_BLOCKABLE; + break; + + default: + break; + } + + LOT_ClearLOT(&creature->lot); + + if (item_num != g_Lara.item_num) { + LOT_CreateZone(item); + } + + g_SlotsUsed++; +} + +void __cdecl LOT_CreateZone(ITEM *const item) +{ + CREATURE *const creature = item->data; + + int16_t *zone; + int16_t *flip; + if (creature->lot.fly) { + zone = g_FlyZone[0]; + flip = g_FlyZone[1]; + } else { + zone = g_GroundZone[BOX_ZONE(creature->lot.step)][0]; + flip = g_GroundZone[BOX_ZONE(creature->lot.step)][1]; + } + + const ROOM *const r = &g_Rooms[item->room_num]; + const int32_t z_sector = (item->pos.z - r->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (item->pos.x - r->pos.x) >> WALL_SHIFT; + item->box_num = r->sectors[z_sector + x_sector * r->size.z].box; + + int16_t zone_num = zone[item->box_num]; + int16_t flip_num = flip[item->box_num]; + + creature->lot.zone_count = 0; + BOX_NODE *node = creature->lot.node; + for (int32_t i = 0; i < g_BoxCount; i++) { + if (zone[i] == zone_num || flip[i] == flip_num) { + node->box_num = i; + node++; + creature->lot.zone_count++; + } + } +} + +void __cdecl LOT_ClearLOT(LOT_INFO *const lot) +{ + lot->search_num = 0; + lot->head = NO_BOX; + lot->tail = NO_BOX; + lot->target_box = NO_BOX; + lot->required_box = NO_BOX; + + for (int32_t i = 0; i < g_BoxCount; i++) { + BOX_NODE *const node = &lot->node[i]; + node->next_expansion = NO_BOX; + node->exit_box = NO_BOX; + node->search_num = 0; + } +} diff --git a/src/tr2/game/lot.h b/src/tr2/game/lot.h new file mode 100644 index 000000000..f9c634254 --- /dev/null +++ b/src/tr2/game/lot.h @@ -0,0 +1,10 @@ +#pragma once + +#include "global/types.h" + +void __cdecl LOT_InitialiseArray(void); +void __cdecl LOT_DisableBaddieAI(int16_t item_num); +bool __cdecl LOT_EnableBaddieAI(int16_t item_num, bool always); +void __cdecl LOT_InitialiseSlot(int16_t item_num, int32_t slot); +void __cdecl LOT_CreateZone(ITEM *item); +void __cdecl LOT_ClearLOT(LOT_INFO *LOT); diff --git a/src/tr2/game/math.c b/src/tr2/game/math.c new file mode 100644 index 000000000..eff3bbdbd --- /dev/null +++ b/src/tr2/game/math.c @@ -0,0 +1,521 @@ +#include "game/math.h" + +#include "global/const.h" + +#include + +static const int16_t m_SinTable[0x402] = { + // clang-format off + 0x0000, 0x0019, 0x0032, 0x004B, 0x0065, 0x007E, 0x0097, 0x00B0, + 0x00C9, 0x00E2, 0x00FB, 0x0114, 0x012E, 0x0147, 0x0160, 0x0179, + 0x0192, 0x01AB, 0x01C4, 0x01DD, 0x01F7, 0x0210, 0x0229, 0x0242, + 0x025B, 0x0274, 0x028D, 0x02A6, 0x02C0, 0x02D9, 0x02F2, 0x030B, + 0x0324, 0x033D, 0x0356, 0x036F, 0x0388, 0x03A1, 0x03BB, 0x03D4, + 0x03ED, 0x0406, 0x041F, 0x0438, 0x0451, 0x046A, 0x0483, 0x049C, + 0x04B5, 0x04CE, 0x04E7, 0x0500, 0x051A, 0x0533, 0x054C, 0x0565, + 0x057E, 0x0597, 0x05B0, 0x05C9, 0x05E2, 0x05FB, 0x0614, 0x062D, + 0x0646, 0x065F, 0x0678, 0x0691, 0x06AA, 0x06C3, 0x06DC, 0x06F5, + 0x070E, 0x0727, 0x0740, 0x0759, 0x0772, 0x078B, 0x07A4, 0x07BD, + 0x07D6, 0x07EF, 0x0807, 0x0820, 0x0839, 0x0852, 0x086B, 0x0884, + 0x089D, 0x08B6, 0x08CF, 0x08E8, 0x0901, 0x0919, 0x0932, 0x094B, + 0x0964, 0x097D, 0x0996, 0x09AF, 0x09C7, 0x09E0, 0x09F9, 0x0A12, + 0x0A2B, 0x0A44, 0x0A5C, 0x0A75, 0x0A8E, 0x0AA7, 0x0AC0, 0x0AD8, + 0x0AF1, 0x0B0A, 0x0B23, 0x0B3B, 0x0B54, 0x0B6D, 0x0B85, 0x0B9E, + 0x0BB7, 0x0BD0, 0x0BE8, 0x0C01, 0x0C1A, 0x0C32, 0x0C4B, 0x0C64, + 0x0C7C, 0x0C95, 0x0CAE, 0x0CC6, 0x0CDF, 0x0CF8, 0x0D10, 0x0D29, + 0x0D41, 0x0D5A, 0x0D72, 0x0D8B, 0x0DA4, 0x0DBC, 0x0DD5, 0x0DED, + 0x0E06, 0x0E1E, 0x0E37, 0x0E4F, 0x0E68, 0x0E80, 0x0E99, 0x0EB1, + 0x0ECA, 0x0EE2, 0x0EFB, 0x0F13, 0x0F2B, 0x0F44, 0x0F5C, 0x0F75, + 0x0F8D, 0x0FA5, 0x0FBE, 0x0FD6, 0x0FEE, 0x1007, 0x101F, 0x1037, + 0x1050, 0x1068, 0x1080, 0x1099, 0x10B1, 0x10C9, 0x10E1, 0x10FA, + 0x1112, 0x112A, 0x1142, 0x115A, 0x1173, 0x118B, 0x11A3, 0x11BB, + 0x11D3, 0x11EB, 0x1204, 0x121C, 0x1234, 0x124C, 0x1264, 0x127C, + 0x1294, 0x12AC, 0x12C4, 0x12DC, 0x12F4, 0x130C, 0x1324, 0x133C, + 0x1354, 0x136C, 0x1384, 0x139C, 0x13B4, 0x13CC, 0x13E4, 0x13FB, + 0x1413, 0x142B, 0x1443, 0x145B, 0x1473, 0x148B, 0x14A2, 0x14BA, + 0x14D2, 0x14EA, 0x1501, 0x1519, 0x1531, 0x1549, 0x1560, 0x1578, + 0x1590, 0x15A7, 0x15BF, 0x15D7, 0x15EE, 0x1606, 0x161D, 0x1635, + 0x164C, 0x1664, 0x167C, 0x1693, 0x16AB, 0x16C2, 0x16DA, 0x16F1, + 0x1709, 0x1720, 0x1737, 0x174F, 0x1766, 0x177E, 0x1795, 0x17AC, + 0x17C4, 0x17DB, 0x17F2, 0x180A, 0x1821, 0x1838, 0x184F, 0x1867, + 0x187E, 0x1895, 0x18AC, 0x18C3, 0x18DB, 0x18F2, 0x1909, 0x1920, + 0x1937, 0x194E, 0x1965, 0x197C, 0x1993, 0x19AA, 0x19C1, 0x19D8, + 0x19EF, 0x1A06, 0x1A1D, 0x1A34, 0x1A4B, 0x1A62, 0x1A79, 0x1A90, + 0x1AA7, 0x1ABE, 0x1AD4, 0x1AEB, 0x1B02, 0x1B19, 0x1B30, 0x1B46, + 0x1B5D, 0x1B74, 0x1B8A, 0x1BA1, 0x1BB8, 0x1BCE, 0x1BE5, 0x1BFC, + 0x1C12, 0x1C29, 0x1C3F, 0x1C56, 0x1C6C, 0x1C83, 0x1C99, 0x1CB0, + 0x1CC6, 0x1CDD, 0x1CF3, 0x1D0A, 0x1D20, 0x1D36, 0x1D4D, 0x1D63, + 0x1D79, 0x1D90, 0x1DA6, 0x1DBC, 0x1DD3, 0x1DE9, 0x1DFF, 0x1E15, + 0x1E2B, 0x1E42, 0x1E58, 0x1E6E, 0x1E84, 0x1E9A, 0x1EB0, 0x1EC6, + 0x1EDC, 0x1EF2, 0x1F08, 0x1F1E, 0x1F34, 0x1F4A, 0x1F60, 0x1F76, + 0x1F8C, 0x1FA2, 0x1FB7, 0x1FCD, 0x1FE3, 0x1FF9, 0x200F, 0x2024, + 0x203A, 0x2050, 0x2065, 0x207B, 0x2091, 0x20A6, 0x20BC, 0x20D1, + 0x20E7, 0x20FD, 0x2112, 0x2128, 0x213D, 0x2153, 0x2168, 0x217D, + 0x2193, 0x21A8, 0x21BE, 0x21D3, 0x21E8, 0x21FE, 0x2213, 0x2228, + 0x223D, 0x2253, 0x2268, 0x227D, 0x2292, 0x22A7, 0x22BC, 0x22D2, + 0x22E7, 0x22FC, 0x2311, 0x2326, 0x233B, 0x2350, 0x2365, 0x237A, + 0x238E, 0x23A3, 0x23B8, 0x23CD, 0x23E2, 0x23F7, 0x240B, 0x2420, + 0x2435, 0x244A, 0x245E, 0x2473, 0x2488, 0x249C, 0x24B1, 0x24C5, + 0x24DA, 0x24EF, 0x2503, 0x2518, 0x252C, 0x2541, 0x2555, 0x2569, + 0x257E, 0x2592, 0x25A6, 0x25BB, 0x25CF, 0x25E3, 0x25F8, 0x260C, + 0x2620, 0x2634, 0x2648, 0x265C, 0x2671, 0x2685, 0x2699, 0x26AD, + 0x26C1, 0x26D5, 0x26E9, 0x26FD, 0x2711, 0x2724, 0x2738, 0x274C, + 0x2760, 0x2774, 0x2788, 0x279B, 0x27AF, 0x27C3, 0x27D6, 0x27EA, + 0x27FE, 0x2811, 0x2825, 0x2838, 0x284C, 0x2860, 0x2873, 0x2886, + 0x289A, 0x28AD, 0x28C1, 0x28D4, 0x28E7, 0x28FB, 0x290E, 0x2921, + 0x2935, 0x2948, 0x295B, 0x296E, 0x2981, 0x2994, 0x29A7, 0x29BB, + 0x29CE, 0x29E1, 0x29F4, 0x2A07, 0x2A1A, 0x2A2C, 0x2A3F, 0x2A52, + 0x2A65, 0x2A78, 0x2A8B, 0x2A9D, 0x2AB0, 0x2AC3, 0x2AD6, 0x2AE8, + 0x2AFB, 0x2B0D, 0x2B20, 0x2B33, 0x2B45, 0x2B58, 0x2B6A, 0x2B7D, + 0x2B8F, 0x2BA1, 0x2BB4, 0x2BC6, 0x2BD8, 0x2BEB, 0x2BFD, 0x2C0F, + 0x2C21, 0x2C34, 0x2C46, 0x2C58, 0x2C6A, 0x2C7C, 0x2C8E, 0x2CA0, + 0x2CB2, 0x2CC4, 0x2CD6, 0x2CE8, 0x2CFA, 0x2D0C, 0x2D1E, 0x2D2F, + 0x2D41, 0x2D53, 0x2D65, 0x2D76, 0x2D88, 0x2D9A, 0x2DAB, 0x2DBD, + 0x2DCF, 0x2DE0, 0x2DF2, 0x2E03, 0x2E15, 0x2E26, 0x2E37, 0x2E49, + 0x2E5A, 0x2E6B, 0x2E7D, 0x2E8E, 0x2E9F, 0x2EB0, 0x2EC2, 0x2ED3, + 0x2EE4, 0x2EF5, 0x2F06, 0x2F17, 0x2F28, 0x2F39, 0x2F4A, 0x2F5B, + 0x2F6C, 0x2F7D, 0x2F8D, 0x2F9E, 0x2FAF, 0x2FC0, 0x2FD0, 0x2FE1, + 0x2FF2, 0x3002, 0x3013, 0x3024, 0x3034, 0x3045, 0x3055, 0x3066, + 0x3076, 0x3087, 0x3097, 0x30A7, 0x30B8, 0x30C8, 0x30D8, 0x30E8, + 0x30F9, 0x3109, 0x3119, 0x3129, 0x3139, 0x3149, 0x3159, 0x3169, + 0x3179, 0x3189, 0x3199, 0x31A9, 0x31B9, 0x31C8, 0x31D8, 0x31E8, + 0x31F8, 0x3207, 0x3217, 0x3227, 0x3236, 0x3246, 0x3255, 0x3265, + 0x3274, 0x3284, 0x3293, 0x32A3, 0x32B2, 0x32C1, 0x32D0, 0x32E0, + 0x32EF, 0x32FE, 0x330D, 0x331D, 0x332C, 0x333B, 0x334A, 0x3359, + 0x3368, 0x3377, 0x3386, 0x3395, 0x33A3, 0x33B2, 0x33C1, 0x33D0, + 0x33DF, 0x33ED, 0x33FC, 0x340B, 0x3419, 0x3428, 0x3436, 0x3445, + 0x3453, 0x3462, 0x3470, 0x347F, 0x348D, 0x349B, 0x34AA, 0x34B8, + 0x34C6, 0x34D4, 0x34E2, 0x34F1, 0x34FF, 0x350D, 0x351B, 0x3529, + 0x3537, 0x3545, 0x3553, 0x3561, 0x356E, 0x357C, 0x358A, 0x3598, + 0x35A5, 0x35B3, 0x35C1, 0x35CE, 0x35DC, 0x35EA, 0x35F7, 0x3605, + 0x3612, 0x3620, 0x362D, 0x363A, 0x3648, 0x3655, 0x3662, 0x366F, + 0x367D, 0x368A, 0x3697, 0x36A4, 0x36B1, 0x36BE, 0x36CB, 0x36D8, + 0x36E5, 0x36F2, 0x36FF, 0x370C, 0x3718, 0x3725, 0x3732, 0x373F, + 0x374B, 0x3758, 0x3765, 0x3771, 0x377E, 0x378A, 0x3797, 0x37A3, + 0x37B0, 0x37BC, 0x37C8, 0x37D5, 0x37E1, 0x37ED, 0x37F9, 0x3805, + 0x3812, 0x381E, 0x382A, 0x3836, 0x3842, 0x384E, 0x385A, 0x3866, + 0x3871, 0x387D, 0x3889, 0x3895, 0x38A1, 0x38AC, 0x38B8, 0x38C3, + 0x38CF, 0x38DB, 0x38E6, 0x38F2, 0x38FD, 0x3909, 0x3914, 0x391F, + 0x392B, 0x3936, 0x3941, 0x394C, 0x3958, 0x3963, 0x396E, 0x3979, + 0x3984, 0x398F, 0x399A, 0x39A5, 0x39B0, 0x39BB, 0x39C5, 0x39D0, + 0x39DB, 0x39E6, 0x39F0, 0x39FB, 0x3A06, 0x3A10, 0x3A1B, 0x3A25, + 0x3A30, 0x3A3A, 0x3A45, 0x3A4F, 0x3A59, 0x3A64, 0x3A6E, 0x3A78, + 0x3A82, 0x3A8D, 0x3A97, 0x3AA1, 0x3AAB, 0x3AB5, 0x3ABF, 0x3AC9, + 0x3AD3, 0x3ADD, 0x3AE6, 0x3AF0, 0x3AFA, 0x3B04, 0x3B0E, 0x3B17, + 0x3B21, 0x3B2A, 0x3B34, 0x3B3E, 0x3B47, 0x3B50, 0x3B5A, 0x3B63, + 0x3B6D, 0x3B76, 0x3B7F, 0x3B88, 0x3B92, 0x3B9B, 0x3BA4, 0x3BAD, + 0x3BB6, 0x3BBF, 0x3BC8, 0x3BD1, 0x3BDA, 0x3BE3, 0x3BEC, 0x3BF5, + 0x3BFD, 0x3C06, 0x3C0F, 0x3C17, 0x3C20, 0x3C29, 0x3C31, 0x3C3A, + 0x3C42, 0x3C4B, 0x3C53, 0x3C5B, 0x3C64, 0x3C6C, 0x3C74, 0x3C7D, + 0x3C85, 0x3C8D, 0x3C95, 0x3C9D, 0x3CA5, 0x3CAD, 0x3CB5, 0x3CBD, + 0x3CC5, 0x3CCD, 0x3CD5, 0x3CDD, 0x3CE4, 0x3CEC, 0x3CF4, 0x3CFB, + 0x3D03, 0x3D0B, 0x3D12, 0x3D1A, 0x3D21, 0x3D28, 0x3D30, 0x3D37, + 0x3D3F, 0x3D46, 0x3D4D, 0x3D54, 0x3D5B, 0x3D63, 0x3D6A, 0x3D71, + 0x3D78, 0x3D7F, 0x3D86, 0x3D8D, 0x3D93, 0x3D9A, 0x3DA1, 0x3DA8, + 0x3DAF, 0x3DB5, 0x3DBC, 0x3DC2, 0x3DC9, 0x3DD0, 0x3DD6, 0x3DDD, + 0x3DE3, 0x3DE9, 0x3DF0, 0x3DF6, 0x3DFC, 0x3E03, 0x3E09, 0x3E0F, + 0x3E15, 0x3E1B, 0x3E21, 0x3E27, 0x3E2D, 0x3E33, 0x3E39, 0x3E3F, + 0x3E45, 0x3E4A, 0x3E50, 0x3E56, 0x3E5C, 0x3E61, 0x3E67, 0x3E6C, + 0x3E72, 0x3E77, 0x3E7D, 0x3E82, 0x3E88, 0x3E8D, 0x3E92, 0x3E98, + 0x3E9D, 0x3EA2, 0x3EA7, 0x3EAC, 0x3EB1, 0x3EB6, 0x3EBB, 0x3EC0, + 0x3EC5, 0x3ECA, 0x3ECF, 0x3ED4, 0x3ED8, 0x3EDD, 0x3EE2, 0x3EE7, + 0x3EEB, 0x3EF0, 0x3EF4, 0x3EF9, 0x3EFD, 0x3F02, 0x3F06, 0x3F0A, + 0x3F0F, 0x3F13, 0x3F17, 0x3F1C, 0x3F20, 0x3F24, 0x3F28, 0x3F2C, + 0x3F30, 0x3F34, 0x3F38, 0x3F3C, 0x3F40, 0x3F43, 0x3F47, 0x3F4B, + 0x3F4F, 0x3F52, 0x3F56, 0x3F5A, 0x3F5D, 0x3F61, 0x3F64, 0x3F68, + 0x3F6B, 0x3F6E, 0x3F72, 0x3F75, 0x3F78, 0x3F7B, 0x3F7F, 0x3F82, + 0x3F85, 0x3F88, 0x3F8B, 0x3F8E, 0x3F91, 0x3F94, 0x3F97, 0x3F99, + 0x3F9C, 0x3F9F, 0x3FA2, 0x3FA4, 0x3FA7, 0x3FAA, 0x3FAC, 0x3FAF, + 0x3FB1, 0x3FB4, 0x3FB6, 0x3FB8, 0x3FBB, 0x3FBD, 0x3FBF, 0x3FC1, + 0x3FC4, 0x3FC6, 0x3FC8, 0x3FCA, 0x3FCC, 0x3FCE, 0x3FD0, 0x3FD2, + 0x3FD4, 0x3FD5, 0x3FD7, 0x3FD9, 0x3FDB, 0x3FDC, 0x3FDE, 0x3FE0, + 0x3FE1, 0x3FE3, 0x3FE4, 0x3FE6, 0x3FE7, 0x3FE8, 0x3FEA, 0x3FEB, + 0x3FEC, 0x3FED, 0x3FEF, 0x3FF0, 0x3FF1, 0x3FF2, 0x3FF3, 0x3FF4, + 0x3FF5, 0x3FF6, 0x3FF7, 0x3FF7, 0x3FF8, 0x3FF9, 0x3FFA, 0x3FFA, + 0x3FFB, 0x3FFC, 0x3FFC, 0x3FFD, 0x3FFD, 0x3FFE, 0x3FFE, 0x3FFE, + 0x3FFF, 0x3FFF, 0x3FFF, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, + 0x4000, 0x4000, + // clang-format on +}; + +static const int32_t m_AtanBaseTable[8] = { + +0x0000, -0x4000, -0xFFFF, +0xC000, -0x8000, +0x4000, +0x8000, -0xC000, +}; + +static const int16_t m_AtanAngleTable[0x802] = { + // clang-format off + 0x0000, 0x0005, 0x000A, 0x000F, 0x0014, 0x0019, 0x001F, 0x0024, + 0x0029, 0x002E, 0x0033, 0x0038, 0x003D, 0x0042, 0x0047, 0x004C, + 0x0051, 0x0057, 0x005C, 0x0061, 0x0066, 0x006B, 0x0070, 0x0075, + 0x007A, 0x007F, 0x0084, 0x008A, 0x008F, 0x0094, 0x0099, 0x009E, + 0x00A3, 0x00A8, 0x00AD, 0x00B2, 0x00B7, 0x00BC, 0x00C2, 0x00C7, + 0x00CC, 0x00D1, 0x00D6, 0x00DB, 0x00E0, 0x00E5, 0x00EA, 0x00EF, + 0x00F4, 0x00FA, 0x00FF, 0x0104, 0x0109, 0x010E, 0x0113, 0x0118, + 0x011D, 0x0122, 0x0127, 0x012C, 0x0131, 0x0137, 0x013C, 0x0141, + 0x0146, 0x014B, 0x0150, 0x0155, 0x015A, 0x015F, 0x0164, 0x0169, + 0x016F, 0x0174, 0x0179, 0x017E, 0x0183, 0x0188, 0x018D, 0x0192, + 0x0197, 0x019C, 0x01A1, 0x01A6, 0x01AC, 0x01B1, 0x01B6, 0x01BB, + 0x01C0, 0x01C5, 0x01CA, 0x01CF, 0x01D4, 0x01D9, 0x01DE, 0x01E3, + 0x01E9, 0x01EE, 0x01F3, 0x01F8, 0x01FD, 0x0202, 0x0207, 0x020C, + 0x0211, 0x0216, 0x021B, 0x0220, 0x0226, 0x022B, 0x0230, 0x0235, + 0x023A, 0x023F, 0x0244, 0x0249, 0x024E, 0x0253, 0x0258, 0x025D, + 0x0262, 0x0268, 0x026D, 0x0272, 0x0277, 0x027C, 0x0281, 0x0286, + 0x028B, 0x0290, 0x0295, 0x029A, 0x029F, 0x02A4, 0x02A9, 0x02AF, + 0x02B4, 0x02B9, 0x02BE, 0x02C3, 0x02C8, 0x02CD, 0x02D2, 0x02D7, + 0x02DC, 0x02E1, 0x02E6, 0x02EB, 0x02F0, 0x02F6, 0x02FB, 0x0300, + 0x0305, 0x030A, 0x030F, 0x0314, 0x0319, 0x031E, 0x0323, 0x0328, + 0x032D, 0x0332, 0x0337, 0x033C, 0x0341, 0x0347, 0x034C, 0x0351, + 0x0356, 0x035B, 0x0360, 0x0365, 0x036A, 0x036F, 0x0374, 0x0379, + 0x037E, 0x0383, 0x0388, 0x038D, 0x0392, 0x0397, 0x039C, 0x03A2, + 0x03A7, 0x03AC, 0x03B1, 0x03B6, 0x03BB, 0x03C0, 0x03C5, 0x03CA, + 0x03CF, 0x03D4, 0x03D9, 0x03DE, 0x03E3, 0x03E8, 0x03ED, 0x03F2, + 0x03F7, 0x03FC, 0x0401, 0x0407, 0x040C, 0x0411, 0x0416, 0x041B, + 0x0420, 0x0425, 0x042A, 0x042F, 0x0434, 0x0439, 0x043E, 0x0443, + 0x0448, 0x044D, 0x0452, 0x0457, 0x045C, 0x0461, 0x0466, 0x046B, + 0x0470, 0x0475, 0x047A, 0x047F, 0x0484, 0x0489, 0x048E, 0x0494, + 0x0499, 0x049E, 0x04A3, 0x04A8, 0x04AD, 0x04B2, 0x04B7, 0x04BC, + 0x04C1, 0x04C6, 0x04CB, 0x04D0, 0x04D5, 0x04DA, 0x04DF, 0x04E4, + 0x04E9, 0x04EE, 0x04F3, 0x04F8, 0x04FD, 0x0502, 0x0507, 0x050C, + 0x0511, 0x0516, 0x051B, 0x0520, 0x0525, 0x052A, 0x052F, 0x0534, + 0x0539, 0x053E, 0x0543, 0x0548, 0x054D, 0x0552, 0x0557, 0x055C, + 0x0561, 0x0566, 0x056B, 0x0570, 0x0575, 0x057A, 0x057F, 0x0584, + 0x0589, 0x058E, 0x0593, 0x0598, 0x059D, 0x05A2, 0x05A7, 0x05AC, + 0x05B1, 0x05B6, 0x05BB, 0x05C0, 0x05C5, 0x05CA, 0x05CF, 0x05D4, + 0x05D9, 0x05DE, 0x05E3, 0x05E8, 0x05ED, 0x05F2, 0x05F7, 0x05FC, + 0x0601, 0x0606, 0x060B, 0x0610, 0x0615, 0x061A, 0x061F, 0x0624, + 0x0629, 0x062E, 0x0633, 0x0638, 0x063D, 0x0642, 0x0647, 0x064C, + 0x0651, 0x0656, 0x065B, 0x0660, 0x0665, 0x066A, 0x066E, 0x0673, + 0x0678, 0x067D, 0x0682, 0x0687, 0x068C, 0x0691, 0x0696, 0x069B, + 0x06A0, 0x06A5, 0x06AA, 0x06AF, 0x06B4, 0x06B9, 0x06BE, 0x06C3, + 0x06C8, 0x06CD, 0x06D2, 0x06D7, 0x06DC, 0x06E1, 0x06E5, 0x06EA, + 0x06EF, 0x06F4, 0x06F9, 0x06FE, 0x0703, 0x0708, 0x070D, 0x0712, + 0x0717, 0x071C, 0x0721, 0x0726, 0x072B, 0x0730, 0x0735, 0x0739, + 0x073E, 0x0743, 0x0748, 0x074D, 0x0752, 0x0757, 0x075C, 0x0761, + 0x0766, 0x076B, 0x0770, 0x0775, 0x077A, 0x077E, 0x0783, 0x0788, + 0x078D, 0x0792, 0x0797, 0x079C, 0x07A1, 0x07A6, 0x07AB, 0x07B0, + 0x07B5, 0x07B9, 0x07BE, 0x07C3, 0x07C8, 0x07CD, 0x07D2, 0x07D7, + 0x07DC, 0x07E1, 0x07E6, 0x07EB, 0x07EF, 0x07F4, 0x07F9, 0x07FE, + 0x0803, 0x0808, 0x080D, 0x0812, 0x0817, 0x081C, 0x0820, 0x0825, + 0x082A, 0x082F, 0x0834, 0x0839, 0x083E, 0x0843, 0x0848, 0x084C, + 0x0851, 0x0856, 0x085B, 0x0860, 0x0865, 0x086A, 0x086F, 0x0873, + 0x0878, 0x087D, 0x0882, 0x0887, 0x088C, 0x0891, 0x0896, 0x089A, + 0x089F, 0x08A4, 0x08A9, 0x08AE, 0x08B3, 0x08B8, 0x08BD, 0x08C1, + 0x08C6, 0x08CB, 0x08D0, 0x08D5, 0x08DA, 0x08DF, 0x08E3, 0x08E8, + 0x08ED, 0x08F2, 0x08F7, 0x08FC, 0x0901, 0x0905, 0x090A, 0x090F, + 0x0914, 0x0919, 0x091E, 0x0922, 0x0927, 0x092C, 0x0931, 0x0936, + 0x093B, 0x093F, 0x0944, 0x0949, 0x094E, 0x0953, 0x0958, 0x095C, + 0x0961, 0x0966, 0x096B, 0x0970, 0x0975, 0x0979, 0x097E, 0x0983, + 0x0988, 0x098D, 0x0992, 0x0996, 0x099B, 0x09A0, 0x09A5, 0x09AA, + 0x09AE, 0x09B3, 0x09B8, 0x09BD, 0x09C2, 0x09C6, 0x09CB, 0x09D0, + 0x09D5, 0x09DA, 0x09DE, 0x09E3, 0x09E8, 0x09ED, 0x09F2, 0x09F6, + 0x09FB, 0x0A00, 0x0A05, 0x0A0A, 0x0A0E, 0x0A13, 0x0A18, 0x0A1D, + 0x0A22, 0x0A26, 0x0A2B, 0x0A30, 0x0A35, 0x0A39, 0x0A3E, 0x0A43, + 0x0A48, 0x0A4D, 0x0A51, 0x0A56, 0x0A5B, 0x0A60, 0x0A64, 0x0A69, + 0x0A6E, 0x0A73, 0x0A77, 0x0A7C, 0x0A81, 0x0A86, 0x0A8B, 0x0A8F, + 0x0A94, 0x0A99, 0x0A9E, 0x0AA2, 0x0AA7, 0x0AAC, 0x0AB1, 0x0AB5, + 0x0ABA, 0x0ABF, 0x0AC4, 0x0AC8, 0x0ACD, 0x0AD2, 0x0AD7, 0x0ADB, + 0x0AE0, 0x0AE5, 0x0AE9, 0x0AEE, 0x0AF3, 0x0AF8, 0x0AFC, 0x0B01, + 0x0B06, 0x0B0B, 0x0B0F, 0x0B14, 0x0B19, 0x0B1E, 0x0B22, 0x0B27, + 0x0B2C, 0x0B30, 0x0B35, 0x0B3A, 0x0B3F, 0x0B43, 0x0B48, 0x0B4D, + 0x0B51, 0x0B56, 0x0B5B, 0x0B60, 0x0B64, 0x0B69, 0x0B6E, 0x0B72, + 0x0B77, 0x0B7C, 0x0B80, 0x0B85, 0x0B8A, 0x0B8F, 0x0B93, 0x0B98, + 0x0B9D, 0x0BA1, 0x0BA6, 0x0BAB, 0x0BAF, 0x0BB4, 0x0BB9, 0x0BBD, + 0x0BC2, 0x0BC7, 0x0BCB, 0x0BD0, 0x0BD5, 0x0BD9, 0x0BDE, 0x0BE3, + 0x0BE7, 0x0BEC, 0x0BF1, 0x0BF5, 0x0BFA, 0x0BFF, 0x0C03, 0x0C08, + 0x0C0D, 0x0C11, 0x0C16, 0x0C1B, 0x0C1F, 0x0C24, 0x0C29, 0x0C2D, + 0x0C32, 0x0C37, 0x0C3B, 0x0C40, 0x0C45, 0x0C49, 0x0C4E, 0x0C53, + 0x0C57, 0x0C5C, 0x0C60, 0x0C65, 0x0C6A, 0x0C6E, 0x0C73, 0x0C78, + 0x0C7C, 0x0C81, 0x0C86, 0x0C8A, 0x0C8F, 0x0C93, 0x0C98, 0x0C9D, + 0x0CA1, 0x0CA6, 0x0CAB, 0x0CAF, 0x0CB4, 0x0CB8, 0x0CBD, 0x0CC2, + 0x0CC6, 0x0CCB, 0x0CCF, 0x0CD4, 0x0CD9, 0x0CDD, 0x0CE2, 0x0CE6, + 0x0CEB, 0x0CF0, 0x0CF4, 0x0CF9, 0x0CFD, 0x0D02, 0x0D07, 0x0D0B, + 0x0D10, 0x0D14, 0x0D19, 0x0D1E, 0x0D22, 0x0D27, 0x0D2B, 0x0D30, + 0x0D34, 0x0D39, 0x0D3E, 0x0D42, 0x0D47, 0x0D4B, 0x0D50, 0x0D54, + 0x0D59, 0x0D5E, 0x0D62, 0x0D67, 0x0D6B, 0x0D70, 0x0D74, 0x0D79, + 0x0D7D, 0x0D82, 0x0D87, 0x0D8B, 0x0D90, 0x0D94, 0x0D99, 0x0D9D, + 0x0DA2, 0x0DA6, 0x0DAB, 0x0DAF, 0x0DB4, 0x0DB9, 0x0DBD, 0x0DC2, + 0x0DC6, 0x0DCB, 0x0DCF, 0x0DD4, 0x0DD8, 0x0DDD, 0x0DE1, 0x0DE6, + 0x0DEA, 0x0DEF, 0x0DF3, 0x0DF8, 0x0DFC, 0x0E01, 0x0E05, 0x0E0A, + 0x0E0F, 0x0E13, 0x0E18, 0x0E1C, 0x0E21, 0x0E25, 0x0E2A, 0x0E2E, + 0x0E33, 0x0E37, 0x0E3C, 0x0E40, 0x0E45, 0x0E49, 0x0E4E, 0x0E52, + 0x0E56, 0x0E5B, 0x0E5F, 0x0E64, 0x0E68, 0x0E6D, 0x0E71, 0x0E76, + 0x0E7A, 0x0E7F, 0x0E83, 0x0E88, 0x0E8C, 0x0E91, 0x0E95, 0x0E9A, + 0x0E9E, 0x0EA3, 0x0EA7, 0x0EAC, 0x0EB0, 0x0EB4, 0x0EB9, 0x0EBD, + 0x0EC2, 0x0EC6, 0x0ECB, 0x0ECF, 0x0ED4, 0x0ED8, 0x0EDC, 0x0EE1, + 0x0EE5, 0x0EEA, 0x0EEE, 0x0EF3, 0x0EF7, 0x0EFC, 0x0F00, 0x0F04, + 0x0F09, 0x0F0D, 0x0F12, 0x0F16, 0x0F1B, 0x0F1F, 0x0F23, 0x0F28, + 0x0F2C, 0x0F31, 0x0F35, 0x0F3A, 0x0F3E, 0x0F42, 0x0F47, 0x0F4B, + 0x0F50, 0x0F54, 0x0F58, 0x0F5D, 0x0F61, 0x0F66, 0x0F6A, 0x0F6E, + 0x0F73, 0x0F77, 0x0F7C, 0x0F80, 0x0F84, 0x0F89, 0x0F8D, 0x0F91, + 0x0F96, 0x0F9A, 0x0F9F, 0x0FA3, 0x0FA7, 0x0FAC, 0x0FB0, 0x0FB5, + 0x0FB9, 0x0FBD, 0x0FC2, 0x0FC6, 0x0FCA, 0x0FCF, 0x0FD3, 0x0FD7, + 0x0FDC, 0x0FE0, 0x0FE5, 0x0FE9, 0x0FED, 0x0FF2, 0x0FF6, 0x0FFA, + 0x0FFF, 0x1003, 0x1007, 0x100C, 0x1010, 0x1014, 0x1019, 0x101D, + 0x1021, 0x1026, 0x102A, 0x102E, 0x1033, 0x1037, 0x103B, 0x1040, + 0x1044, 0x1048, 0x104D, 0x1051, 0x1055, 0x105A, 0x105E, 0x1062, + 0x1067, 0x106B, 0x106F, 0x1073, 0x1078, 0x107C, 0x1080, 0x1085, + 0x1089, 0x108D, 0x1092, 0x1096, 0x109A, 0x109E, 0x10A3, 0x10A7, + 0x10AB, 0x10B0, 0x10B4, 0x10B8, 0x10BC, 0x10C1, 0x10C5, 0x10C9, + 0x10CE, 0x10D2, 0x10D6, 0x10DA, 0x10DF, 0x10E3, 0x10E7, 0x10EB, + 0x10F0, 0x10F4, 0x10F8, 0x10FD, 0x1101, 0x1105, 0x1109, 0x110E, + 0x1112, 0x1116, 0x111A, 0x111F, 0x1123, 0x1127, 0x112B, 0x1130, + 0x1134, 0x1138, 0x113C, 0x1140, 0x1145, 0x1149, 0x114D, 0x1151, + 0x1156, 0x115A, 0x115E, 0x1162, 0x1166, 0x116B, 0x116F, 0x1173, + 0x1177, 0x117C, 0x1180, 0x1184, 0x1188, 0x118C, 0x1191, 0x1195, + 0x1199, 0x119D, 0x11A1, 0x11A6, 0x11AA, 0x11AE, 0x11B2, 0x11B6, + 0x11BB, 0x11BF, 0x11C3, 0x11C7, 0x11CB, 0x11CF, 0x11D4, 0x11D8, + 0x11DC, 0x11E0, 0x11E4, 0x11E9, 0x11ED, 0x11F1, 0x11F5, 0x11F9, + 0x11FD, 0x1202, 0x1206, 0x120A, 0x120E, 0x1212, 0x1216, 0x121A, + 0x121F, 0x1223, 0x1227, 0x122B, 0x122F, 0x1233, 0x1237, 0x123C, + 0x1240, 0x1244, 0x1248, 0x124C, 0x1250, 0x1254, 0x1259, 0x125D, + 0x1261, 0x1265, 0x1269, 0x126D, 0x1271, 0x1275, 0x127A, 0x127E, + 0x1282, 0x1286, 0x128A, 0x128E, 0x1292, 0x1296, 0x129A, 0x129F, + 0x12A3, 0x12A7, 0x12AB, 0x12AF, 0x12B3, 0x12B7, 0x12BB, 0x12BF, + 0x12C3, 0x12C7, 0x12CC, 0x12D0, 0x12D4, 0x12D8, 0x12DC, 0x12E0, + 0x12E4, 0x12E8, 0x12EC, 0x12F0, 0x12F4, 0x12F8, 0x12FC, 0x1301, + 0x1305, 0x1309, 0x130D, 0x1311, 0x1315, 0x1319, 0x131D, 0x1321, + 0x1325, 0x1329, 0x132D, 0x1331, 0x1335, 0x1339, 0x133D, 0x1341, + 0x1345, 0x1349, 0x134D, 0x1351, 0x1355, 0x135A, 0x135E, 0x1362, + 0x1366, 0x136A, 0x136E, 0x1372, 0x1376, 0x137A, 0x137E, 0x1382, + 0x1386, 0x138A, 0x138E, 0x1392, 0x1396, 0x139A, 0x139E, 0x13A2, + 0x13A6, 0x13AA, 0x13AE, 0x13B2, 0x13B6, 0x13BA, 0x13BE, 0x13C2, + 0x13C6, 0x13CA, 0x13CE, 0x13D2, 0x13D6, 0x13DA, 0x13DE, 0x13E2, + 0x13E6, 0x13E9, 0x13ED, 0x13F1, 0x13F5, 0x13F9, 0x13FD, 0x1401, + 0x1405, 0x1409, 0x140D, 0x1411, 0x1415, 0x1419, 0x141D, 0x1421, + 0x1425, 0x1429, 0x142D, 0x1431, 0x1435, 0x1439, 0x143D, 0x1440, + 0x1444, 0x1448, 0x144C, 0x1450, 0x1454, 0x1458, 0x145C, 0x1460, + 0x1464, 0x1468, 0x146C, 0x1470, 0x1473, 0x1477, 0x147B, 0x147F, + 0x1483, 0x1487, 0x148B, 0x148F, 0x1493, 0x1497, 0x149B, 0x149E, + 0x14A2, 0x14A6, 0x14AA, 0x14AE, 0x14B2, 0x14B6, 0x14BA, 0x14BE, + 0x14C1, 0x14C5, 0x14C9, 0x14CD, 0x14D1, 0x14D5, 0x14D9, 0x14DD, + 0x14E0, 0x14E4, 0x14E8, 0x14EC, 0x14F0, 0x14F4, 0x14F8, 0x14FB, + 0x14FF, 0x1503, 0x1507, 0x150B, 0x150F, 0x1513, 0x1516, 0x151A, + 0x151E, 0x1522, 0x1526, 0x152A, 0x152D, 0x1531, 0x1535, 0x1539, + 0x153D, 0x1541, 0x1544, 0x1548, 0x154C, 0x1550, 0x1554, 0x1558, + 0x155B, 0x155F, 0x1563, 0x1567, 0x156B, 0x156E, 0x1572, 0x1576, + 0x157A, 0x157E, 0x1581, 0x1585, 0x1589, 0x158D, 0x1591, 0x1594, + 0x1598, 0x159C, 0x15A0, 0x15A4, 0x15A7, 0x15AB, 0x15AF, 0x15B3, + 0x15B7, 0x15BA, 0x15BE, 0x15C2, 0x15C6, 0x15C9, 0x15CD, 0x15D1, + 0x15D5, 0x15D8, 0x15DC, 0x15E0, 0x15E4, 0x15E8, 0x15EB, 0x15EF, + 0x15F3, 0x15F7, 0x15FA, 0x15FE, 0x1602, 0x1606, 0x1609, 0x160D, + 0x1611, 0x1614, 0x1618, 0x161C, 0x1620, 0x1623, 0x1627, 0x162B, + 0x162F, 0x1632, 0x1636, 0x163A, 0x163E, 0x1641, 0x1645, 0x1649, + 0x164C, 0x1650, 0x1654, 0x1658, 0x165B, 0x165F, 0x1663, 0x1666, + 0x166A, 0x166E, 0x1671, 0x1675, 0x1679, 0x167D, 0x1680, 0x1684, + 0x1688, 0x168B, 0x168F, 0x1693, 0x1696, 0x169A, 0x169E, 0x16A1, + 0x16A5, 0x16A9, 0x16AC, 0x16B0, 0x16B4, 0x16B7, 0x16BB, 0x16BF, + 0x16C2, 0x16C6, 0x16CA, 0x16CD, 0x16D1, 0x16D5, 0x16D8, 0x16DC, + 0x16E0, 0x16E3, 0x16E7, 0x16EB, 0x16EE, 0x16F2, 0x16F6, 0x16F9, + 0x16FD, 0x1700, 0x1704, 0x1708, 0x170B, 0x170F, 0x1713, 0x1716, + 0x171A, 0x171D, 0x1721, 0x1725, 0x1728, 0x172C, 0x1730, 0x1733, + 0x1737, 0x173A, 0x173E, 0x1742, 0x1745, 0x1749, 0x174C, 0x1750, + 0x1754, 0x1757, 0x175B, 0x175E, 0x1762, 0x1766, 0x1769, 0x176D, + 0x1770, 0x1774, 0x1778, 0x177B, 0x177F, 0x1782, 0x1786, 0x1789, + 0x178D, 0x1791, 0x1794, 0x1798, 0x179B, 0x179F, 0x17A2, 0x17A6, + 0x17AA, 0x17AD, 0x17B1, 0x17B4, 0x17B8, 0x17BB, 0x17BF, 0x17C2, + 0x17C6, 0x17C9, 0x17CD, 0x17D1, 0x17D4, 0x17D8, 0x17DB, 0x17DF, + 0x17E2, 0x17E6, 0x17E9, 0x17ED, 0x17F0, 0x17F4, 0x17F7, 0x17FB, + 0x17FE, 0x1802, 0x1806, 0x1809, 0x180D, 0x1810, 0x1814, 0x1817, + 0x181B, 0x181E, 0x1822, 0x1825, 0x1829, 0x182C, 0x1830, 0x1833, + 0x1837, 0x183A, 0x183E, 0x1841, 0x1845, 0x1848, 0x184C, 0x184F, + 0x1853, 0x1856, 0x185A, 0x185D, 0x1860, 0x1864, 0x1867, 0x186B, + 0x186E, 0x1872, 0x1875, 0x1879, 0x187C, 0x1880, 0x1883, 0x1887, + 0x188A, 0x188E, 0x1891, 0x1894, 0x1898, 0x189B, 0x189F, 0x18A2, + 0x18A6, 0x18A9, 0x18AD, 0x18B0, 0x18B3, 0x18B7, 0x18BA, 0x18BE, + 0x18C1, 0x18C5, 0x18C8, 0x18CC, 0x18CF, 0x18D2, 0x18D6, 0x18D9, + 0x18DD, 0x18E0, 0x18E3, 0x18E7, 0x18EA, 0x18EE, 0x18F1, 0x18F5, + 0x18F8, 0x18FB, 0x18FF, 0x1902, 0x1906, 0x1909, 0x190C, 0x1910, + 0x1913, 0x1917, 0x191A, 0x191D, 0x1921, 0x1924, 0x1928, 0x192B, + 0x192E, 0x1932, 0x1935, 0x1938, 0x193C, 0x193F, 0x1943, 0x1946, + 0x1949, 0x194D, 0x1950, 0x1953, 0x1957, 0x195A, 0x195D, 0x1961, + 0x1964, 0x1968, 0x196B, 0x196E, 0x1972, 0x1975, 0x1978, 0x197C, + 0x197F, 0x1982, 0x1986, 0x1989, 0x198C, 0x1990, 0x1993, 0x1996, + 0x199A, 0x199D, 0x19A0, 0x19A4, 0x19A7, 0x19AA, 0x19AE, 0x19B1, + 0x19B4, 0x19B8, 0x19BB, 0x19BE, 0x19C2, 0x19C5, 0x19C8, 0x19CC, + 0x19CF, 0x19D2, 0x19D5, 0x19D9, 0x19DC, 0x19DF, 0x19E3, 0x19E6, + 0x19E9, 0x19ED, 0x19F0, 0x19F3, 0x19F6, 0x19FA, 0x19FD, 0x1A00, + 0x1A04, 0x1A07, 0x1A0A, 0x1A0D, 0x1A11, 0x1A14, 0x1A17, 0x1A1B, + 0x1A1E, 0x1A21, 0x1A24, 0x1A28, 0x1A2B, 0x1A2E, 0x1A31, 0x1A35, + 0x1A38, 0x1A3B, 0x1A3E, 0x1A42, 0x1A45, 0x1A48, 0x1A4B, 0x1A4F, + 0x1A52, 0x1A55, 0x1A58, 0x1A5C, 0x1A5F, 0x1A62, 0x1A65, 0x1A69, + 0x1A6C, 0x1A6F, 0x1A72, 0x1A76, 0x1A79, 0x1A7C, 0x1A7F, 0x1A83, + 0x1A86, 0x1A89, 0x1A8C, 0x1A8F, 0x1A93, 0x1A96, 0x1A99, 0x1A9C, + 0x1A9F, 0x1AA3, 0x1AA6, 0x1AA9, 0x1AAC, 0x1AB0, 0x1AB3, 0x1AB6, + 0x1AB9, 0x1ABC, 0x1AC0, 0x1AC3, 0x1AC6, 0x1AC9, 0x1ACC, 0x1ACF, + 0x1AD3, 0x1AD6, 0x1AD9, 0x1ADC, 0x1ADF, 0x1AE3, 0x1AE6, 0x1AE9, + 0x1AEC, 0x1AEF, 0x1AF2, 0x1AF6, 0x1AF9, 0x1AFC, 0x1AFF, 0x1B02, + 0x1B05, 0x1B09, 0x1B0C, 0x1B0F, 0x1B12, 0x1B15, 0x1B18, 0x1B1C, + 0x1B1F, 0x1B22, 0x1B25, 0x1B28, 0x1B2B, 0x1B2E, 0x1B32, 0x1B35, + 0x1B38, 0x1B3B, 0x1B3E, 0x1B41, 0x1B44, 0x1B48, 0x1B4B, 0x1B4E, + 0x1B51, 0x1B54, 0x1B57, 0x1B5A, 0x1B5D, 0x1B61, 0x1B64, 0x1B67, + 0x1B6A, 0x1B6D, 0x1B70, 0x1B73, 0x1B76, 0x1B79, 0x1B7D, 0x1B80, + 0x1B83, 0x1B86, 0x1B89, 0x1B8C, 0x1B8F, 0x1B92, 0x1B95, 0x1B98, + 0x1B9C, 0x1B9F, 0x1BA2, 0x1BA5, 0x1BA8, 0x1BAB, 0x1BAE, 0x1BB1, + 0x1BB4, 0x1BB7, 0x1BBA, 0x1BBD, 0x1BC1, 0x1BC4, 0x1BC7, 0x1BCA, + 0x1BCD, 0x1BD0, 0x1BD3, 0x1BD6, 0x1BD9, 0x1BDC, 0x1BDF, 0x1BE2, + 0x1BE5, 0x1BE8, 0x1BEB, 0x1BEE, 0x1BF2, 0x1BF5, 0x1BF8, 0x1BFB, + 0x1BFE, 0x1C01, 0x1C04, 0x1C07, 0x1C0A, 0x1C0D, 0x1C10, 0x1C13, + 0x1C16, 0x1C19, 0x1C1C, 0x1C1F, 0x1C22, 0x1C25, 0x1C28, 0x1C2B, + 0x1C2E, 0x1C31, 0x1C34, 0x1C37, 0x1C3A, 0x1C3D, 0x1C40, 0x1C43, + 0x1C46, 0x1C49, 0x1C4C, 0x1C4F, 0x1C52, 0x1C55, 0x1C58, 0x1C5B, + 0x1C5E, 0x1C61, 0x1C64, 0x1C67, 0x1C6A, 0x1C6D, 0x1C70, 0x1C73, + 0x1C76, 0x1C79, 0x1C7C, 0x1C7F, 0x1C82, 0x1C85, 0x1C88, 0x1C8B, + 0x1C8E, 0x1C91, 0x1C94, 0x1C97, 0x1C9A, 0x1C9D, 0x1CA0, 0x1CA3, + 0x1CA6, 0x1CA9, 0x1CAC, 0x1CAF, 0x1CB2, 0x1CB5, 0x1CB8, 0x1CBB, + 0x1CBE, 0x1CC1, 0x1CC3, 0x1CC6, 0x1CC9, 0x1CCC, 0x1CCF, 0x1CD2, + 0x1CD5, 0x1CD8, 0x1CDB, 0x1CDE, 0x1CE1, 0x1CE4, 0x1CE7, 0x1CEA, + 0x1CED, 0x1CF0, 0x1CF3, 0x1CF5, 0x1CF8, 0x1CFB, 0x1CFE, 0x1D01, + 0x1D04, 0x1D07, 0x1D0A, 0x1D0D, 0x1D10, 0x1D13, 0x1D16, 0x1D18, + 0x1D1B, 0x1D1E, 0x1D21, 0x1D24, 0x1D27, 0x1D2A, 0x1D2D, 0x1D30, + 0x1D33, 0x1D35, 0x1D38, 0x1D3B, 0x1D3E, 0x1D41, 0x1D44, 0x1D47, + 0x1D4A, 0x1D4D, 0x1D4F, 0x1D52, 0x1D55, 0x1D58, 0x1D5B, 0x1D5E, + 0x1D61, 0x1D64, 0x1D66, 0x1D69, 0x1D6C, 0x1D6F, 0x1D72, 0x1D75, + 0x1D78, 0x1D7B, 0x1D7D, 0x1D80, 0x1D83, 0x1D86, 0x1D89, 0x1D8C, + 0x1D8E, 0x1D91, 0x1D94, 0x1D97, 0x1D9A, 0x1D9D, 0x1DA0, 0x1DA2, + 0x1DA5, 0x1DA8, 0x1DAB, 0x1DAE, 0x1DB1, 0x1DB3, 0x1DB6, 0x1DB9, + 0x1DBC, 0x1DBF, 0x1DC2, 0x1DC4, 0x1DC7, 0x1DCA, 0x1DCD, 0x1DD0, + 0x1DD3, 0x1DD5, 0x1DD8, 0x1DDB, 0x1DDE, 0x1DE1, 0x1DE3, 0x1DE6, + 0x1DE9, 0x1DEC, 0x1DEF, 0x1DF1, 0x1DF4, 0x1DF7, 0x1DFA, 0x1DFD, + 0x1DFF, 0x1E02, 0x1E05, 0x1E08, 0x1E0B, 0x1E0D, 0x1E10, 0x1E13, + 0x1E16, 0x1E19, 0x1E1B, 0x1E1E, 0x1E21, 0x1E24, 0x1E26, 0x1E29, + 0x1E2C, 0x1E2F, 0x1E32, 0x1E34, 0x1E37, 0x1E3A, 0x1E3D, 0x1E3F, + 0x1E42, 0x1E45, 0x1E48, 0x1E4A, 0x1E4D, 0x1E50, 0x1E53, 0x1E55, + 0x1E58, 0x1E5B, 0x1E5E, 0x1E60, 0x1E63, 0x1E66, 0x1E69, 0x1E6B, + 0x1E6E, 0x1E71, 0x1E74, 0x1E76, 0x1E79, 0x1E7C, 0x1E7F, 0x1E81, + 0x1E84, 0x1E87, 0x1E8A, 0x1E8C, 0x1E8F, 0x1E92, 0x1E94, 0x1E97, + 0x1E9A, 0x1E9D, 0x1E9F, 0x1EA2, 0x1EA5, 0x1EA8, 0x1EAA, 0x1EAD, + 0x1EB0, 0x1EB2, 0x1EB5, 0x1EB8, 0x1EBA, 0x1EBD, 0x1EC0, 0x1EC3, + 0x1EC5, 0x1EC8, 0x1ECB, 0x1ECD, 0x1ED0, 0x1ED3, 0x1ED5, 0x1ED8, + 0x1EDB, 0x1EDE, 0x1EE0, 0x1EE3, 0x1EE6, 0x1EE8, 0x1EEB, 0x1EEE, + 0x1EF0, 0x1EF3, 0x1EF6, 0x1EF8, 0x1EFB, 0x1EFE, 0x1F00, 0x1F03, + 0x1F06, 0x1F08, 0x1F0B, 0x1F0E, 0x1F10, 0x1F13, 0x1F16, 0x1F18, + 0x1F1B, 0x1F1E, 0x1F20, 0x1F23, 0x1F26, 0x1F28, 0x1F2B, 0x1F2E, + 0x1F30, 0x1F33, 0x1F36, 0x1F38, 0x1F3B, 0x1F3D, 0x1F40, 0x1F43, + 0x1F45, 0x1F48, 0x1F4B, 0x1F4D, 0x1F50, 0x1F53, 0x1F55, 0x1F58, + 0x1F5A, 0x1F5D, 0x1F60, 0x1F62, 0x1F65, 0x1F68, 0x1F6A, 0x1F6D, + 0x1F6F, 0x1F72, 0x1F75, 0x1F77, 0x1F7A, 0x1F7C, 0x1F7F, 0x1F82, + 0x1F84, 0x1F87, 0x1F8A, 0x1F8C, 0x1F8F, 0x1F91, 0x1F94, 0x1F97, + 0x1F99, 0x1F9C, 0x1F9E, 0x1FA1, 0x1FA4, 0x1FA6, 0x1FA9, 0x1FAB, + 0x1FAE, 0x1FB0, 0x1FB3, 0x1FB6, 0x1FB8, 0x1FBB, 0x1FBD, 0x1FC0, + 0x1FC3, 0x1FC5, 0x1FC8, 0x1FCA, 0x1FCD, 0x1FCF, 0x1FD2, 0x1FD5, + 0x1FD7, 0x1FDA, 0x1FDC, 0x1FDF, 0x1FE1, 0x1FE4, 0x1FE6, 0x1FE9, + 0x1FEC, 0x1FEE, 0x1FF1, 0x1FF3, 0x1FF6, 0x1FF8, 0x1FFB, 0x1FFD, + 0x2000, 0x2000, + // clang-format on +}; + +int32_t __fastcall Math_Cos(int32_t angle) +{ + return Math_Sin(angle + PHD_90); +} + +int32_t __fastcall Math_Sin(int32_t angle) +{ + uint16_t sector = (uint16_t)angle & (PHD_180 - 1); + if (sector > PHD_90) { + sector = PHD_180 - sector; + } + int16_t result = m_SinTable[sector >> 4]; + if ((uint16_t)angle >= PHD_180) { + result = -result; + } + return result; +} + +int32_t __fastcall Math_Atan(int32_t x, int32_t y) +{ + if (x == 0 && y == 0) { + return 0; + } + + int32_t base = 0; + if (x < 0) { + base |= 4; + x = -x; + } + + if (y < 0) { + base |= 2; + y = -y; + } + + if (y > x) { + base |= 1; + int32_t tmp = x; + x = y; + y = tmp; + } + + return ABS(m_AtanBaseTable[base] + m_AtanAngleTable[0x800 * y / x]); +} + +uint32_t __fastcall Math_Sqrt(uint32_t n) +{ + uint32_t result = 0; + uint32_t base = 0x40000000; + while (base) { + do { + uint32_t based_result = base + result; + result >>= 1; + if (based_result > n) { + break; + } + n -= based_result; + result |= base; + base >>= 2; + } while (base); + base >>= 2; + } + return result; +} + +DIRECTION Math_GetDirection(int16_t angle) +{ + return (uint16_t)(angle + PHD_45) / PHD_90; +} + +DIRECTION Math_GetDirectionCone(int16_t angle, int16_t cone) +{ + if (angle >= -cone && angle <= cone) { + return DIR_NORTH; + } else if (angle >= PHD_90 - cone && angle <= PHD_90 + cone) { + return DIR_WEST; + } else if (angle >= PHD_180 - cone || angle <= -PHD_180 + cone) { + return DIR_SOUTH; + } else if (angle >= -PHD_90 - cone && angle <= -PHD_90 + cone) { + return DIR_EAST; + } + return DIR_UNKNOWN; +} + +int16_t Math_DirectionToAngle(DIRECTION dir) +{ + switch (dir) { + case DIR_NORTH: + return 0; + case DIR_WEST: + return PHD_90; + case DIR_SOUTH: + return PHD_180; + case DIR_EAST: + return -PHD_90; + default: + return 0; + } +} + +int32_t XYZ_32_GetDistance(const XYZ_32 *const pos1, const XYZ_32 *const pos2) +{ + // clang-format off + return Math_Sqrt( + SQUARE(pos1->x - pos2->x) + + SQUARE(pos1->y - pos2->y) + + SQUARE(pos1->z - pos2->z) + ); + // clang-format on +} + +int32_t XYZ_32_GetDistance0(const XYZ_32 *const pos) +{ + return Math_Sqrt(SQUARE(pos->x) + SQUARE(pos->y) + SQUARE(pos->z)); +} diff --git a/src/tr2/game/math.h b/src/tr2/game/math.h new file mode 100644 index 000000000..24996f178 --- /dev/null +++ b/src/tr2/game/math.h @@ -0,0 +1,17 @@ +#pragma once + +#include "global/types.h" + +#include + +int32_t __fastcall Math_Cos(int32_t angle); +int32_t __fastcall Math_Sin(int32_t angle); +int32_t __fastcall Math_Atan(int32_t x, int32_t y); +uint32_t __fastcall Math_Sqrt(uint32_t n); + +DIRECTION Math_GetDirection(int16_t angle); +DIRECTION Math_GetDirectionCone(int16_t angle, int16_t cone); +int16_t Math_DirectionToAngle(DIRECTION dir); + +int32_t XYZ_32_GetDistance(const XYZ_32 *pos1, const XYZ_32 *pos2); +int32_t XYZ_32_GetDistance0(const XYZ_32 *pos); diff --git a/src/tr2/game/math_misc.c b/src/tr2/game/math_misc.c new file mode 100644 index 000000000..c40de706a --- /dev/null +++ b/src/tr2/game/math_misc.c @@ -0,0 +1,24 @@ +#include "game/math_misc.h" + +#include "game/math.h" + +#include + +void __cdecl Math_GetVectorAngles( + int32_t x, int32_t y, int32_t z, int16_t *dest) +{ + dest[0] = Math_Atan(z, x); + + while ((int16_t)x != x || (int16_t)y != y || (int16_t)z != z) { + x >>= 2; + y >>= 2; + z >>= 2; + } + + int16_t pitch = Math_Atan(Math_Sqrt(SQUARE(x) + SQUARE(z)), y); + if ((y > 0 && pitch > 0) || (y < 0 && pitch < 0)) { + pitch = -pitch; + } + + dest[1] = pitch; +} diff --git a/src/tr2/game/math_misc.h b/src/tr2/game/math_misc.h new file mode 100644 index 000000000..387fdd738 --- /dev/null +++ b/src/tr2/game/math_misc.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +void __cdecl Math_GetVectorAngles( + int32_t x, int32_t y, int32_t z, int16_t *dest); diff --git a/src/tr2/game/matrix.c b/src/tr2/game/matrix.c new file mode 100644 index 000000000..a0f6addc9 --- /dev/null +++ b/src/tr2/game/matrix.c @@ -0,0 +1,520 @@ +#include "game/matrix.h" + +#include "game/math.h" +#include "game/math_misc.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/types.h" +#include "global/vars.h" + +#include + +#include + +void __cdecl Matrix_Push(void) +{ + g_MatrixPtr++; + g_MatrixPtr[0] = g_MatrixPtr[-1]; +} + +void __cdecl Matrix_PushUnit(void) +{ + MATRIX *mptr = ++g_MatrixPtr; + mptr->_00 = (1 << W2V_SHIFT); + mptr->_01 = 0; + mptr->_02 = 0; + mptr->_10 = 0; + mptr->_11 = (1 << W2V_SHIFT); + mptr->_12 = 0; + mptr->_20 = 0; + mptr->_21 = 0; + mptr->_22 = (1 << W2V_SHIFT); + // mptr->_03 = 0; + // mptr->_13 = 0; + // mptr->_23 = 0; +} + +void __cdecl Matrix_Pop(void) +{ + g_MatrixPtr--; +} + +void __cdecl Matrix_GenerateW2V(const PHD_3DPOS *viewpos) +{ + g_MatrixPtr = &g_MatrixStack[0]; + int32_t sx = Math_Sin(viewpos->rot.x); + int32_t cx = Math_Cos(viewpos->rot.x); + int32_t sy = Math_Sin(viewpos->rot.y); + int32_t cy = Math_Cos(viewpos->rot.y); + int32_t sz = Math_Sin(viewpos->rot.z); + int32_t cz = Math_Cos(viewpos->rot.z); + + g_MatrixPtr->_00 = ((cy * cz) >> W2V_SHIFT) + + ((((sx * sy) >> W2V_SHIFT) * sz) >> W2V_SHIFT); + g_MatrixPtr->_01 = (sz * cx) >> W2V_SHIFT; + g_MatrixPtr->_02 = ((((sx * cy) >> W2V_SHIFT) * sz) >> W2V_SHIFT) + - ((sy * cz) >> W2V_SHIFT); + g_MatrixPtr->_10 = ((((sx * sy) >> W2V_SHIFT) * cz) >> W2V_SHIFT) + - ((sz * cy) >> W2V_SHIFT); + g_MatrixPtr->_11 = (cx * cz) >> W2V_SHIFT; + g_MatrixPtr->_12 = ((sy * sz) >> W2V_SHIFT) + + ((((sx * cy) >> W2V_SHIFT) * cz) >> W2V_SHIFT); + g_MatrixPtr->_20 = (sy * cx) >> W2V_SHIFT; + g_MatrixPtr->_21 = -sx; + g_MatrixPtr->_22 = (cx * cy) >> W2V_SHIFT; + g_MatrixPtr->_03 = viewpos->pos.x; + g_MatrixPtr->_13 = viewpos->pos.y; + g_MatrixPtr->_23 = viewpos->pos.z; + + g_MatrixPtr->_10 *= g_ViewportAspectRatio; + g_MatrixPtr->_11 *= g_ViewportAspectRatio; + g_MatrixPtr->_12 *= g_ViewportAspectRatio; + + g_W2VMatrix = *g_MatrixPtr; +} + +void __cdecl Matrix_LookAt( + int32_t xsrc, int32_t ysrc, int32_t zsrc, int32_t xtar, int32_t ytar, + int32_t ztar, int16_t roll) +{ + PHD_ANGLE angles[2]; + Math_GetVectorAngles(xtar - xsrc, ytar - ysrc, ztar - zsrc, angles); + + PHD_3DPOS viewer; + viewer.pos.x = xsrc; + viewer.pos.y = ysrc; + viewer.pos.z = zsrc; + viewer.rot.x = angles[1]; + viewer.rot.y = angles[0]; + viewer.rot.z = roll; + Matrix_GenerateW2V(&viewer); +} + +void __cdecl Matrix_RotX(PHD_ANGLE rx) +{ + if (!rx) { + return; + } + + MATRIX *mptr = g_MatrixPtr; + int32_t sx = Math_Sin(rx); + int32_t cx = Math_Cos(rx); + + int32_t r0, r1; + r0 = mptr->_01 * cx + mptr->_02 * sx; + r1 = mptr->_02 * cx - mptr->_01 * sx; + mptr->_01 = r0 >> W2V_SHIFT; + mptr->_02 = r1 >> W2V_SHIFT; + + r0 = mptr->_11 * cx + mptr->_12 * sx; + r1 = mptr->_12 * cx - mptr->_11 * sx; + mptr->_11 = r0 >> W2V_SHIFT; + mptr->_12 = r1 >> W2V_SHIFT; + + r0 = mptr->_21 * cx + mptr->_22 * sx; + r1 = mptr->_22 * cx - mptr->_21 * sx; + mptr->_21 = r0 >> W2V_SHIFT; + mptr->_22 = r1 >> W2V_SHIFT; +} + +void __cdecl Matrix_RotY(PHD_ANGLE ry) +{ + if (!ry) { + return; + } + + MATRIX *mptr = g_MatrixPtr; + int32_t sy = Math_Sin(ry); + int32_t cy = Math_Cos(ry); + + int32_t r0, r1; + r0 = mptr->_00 * cy - mptr->_02 * sy; + r1 = mptr->_02 * cy + mptr->_00 * sy; + mptr->_00 = r0 >> W2V_SHIFT; + mptr->_02 = r1 >> W2V_SHIFT; + + r0 = mptr->_10 * cy - mptr->_12 * sy; + r1 = mptr->_12 * cy + mptr->_10 * sy; + mptr->_10 = r0 >> W2V_SHIFT; + mptr->_12 = r1 >> W2V_SHIFT; + + r0 = mptr->_20 * cy - mptr->_22 * sy; + r1 = mptr->_22 * cy + mptr->_20 * sy; + mptr->_20 = r0 >> W2V_SHIFT; + mptr->_22 = r1 >> W2V_SHIFT; +} + +void __cdecl Matrix_RotZ(PHD_ANGLE rz) +{ + if (!rz) { + return; + } + + MATRIX *mptr = g_MatrixPtr; + int32_t sz = Math_Sin(rz); + int32_t cz = Math_Cos(rz); + + int32_t r0, r1; + r0 = mptr->_00 * cz + mptr->_01 * sz; + r1 = mptr->_01 * cz - mptr->_00 * sz; + mptr->_00 = r0 >> W2V_SHIFT; + mptr->_01 = r1 >> W2V_SHIFT; + + r0 = mptr->_10 * cz + mptr->_11 * sz; + r1 = mptr->_11 * cz - mptr->_10 * sz; + mptr->_10 = r0 >> W2V_SHIFT; + mptr->_11 = r1 >> W2V_SHIFT; + + r0 = mptr->_20 * cz + mptr->_21 * sz; + r1 = mptr->_21 * cz - mptr->_20 * sz; + mptr->_20 = r0 >> W2V_SHIFT; + mptr->_21 = r1 >> W2V_SHIFT; +} + +void __cdecl Matrix_RotYXZ(int16_t ry, int16_t rx, int16_t rz) +{ + int32_t r0, r1; + MATRIX *mptr = g_MatrixPtr; + + if (ry) { + int32_t sy = Math_Sin(ry); + int32_t cy = Math_Cos(ry); + r0 = mptr->_00 * cy - mptr->_02 * sy; + r1 = mptr->_02 * cy + mptr->_00 * sy; + mptr->_00 = r0 >> W2V_SHIFT; + mptr->_02 = r1 >> W2V_SHIFT; + + r0 = mptr->_10 * cy - mptr->_12 * sy; + r1 = mptr->_12 * cy + mptr->_10 * sy; + mptr->_10 = r0 >> W2V_SHIFT; + mptr->_12 = r1 >> W2V_SHIFT; + + r0 = mptr->_20 * cy - mptr->_22 * sy; + r1 = mptr->_22 * cy + mptr->_20 * sy; + mptr->_20 = r0 >> W2V_SHIFT; + mptr->_22 = r1 >> W2V_SHIFT; + } + + if (rx) { + int32_t sx = Math_Sin(rx); + int32_t cx = Math_Cos(rx); + + r0 = mptr->_01 * cx + mptr->_02 * sx; + r1 = mptr->_02 * cx - mptr->_01 * sx; + mptr->_01 = r0 >> W2V_SHIFT; + mptr->_02 = r1 >> W2V_SHIFT; + + r0 = mptr->_11 * cx + mptr->_12 * sx; + r1 = mptr->_12 * cx - mptr->_11 * sx; + mptr->_11 = r0 >> W2V_SHIFT; + mptr->_12 = r1 >> W2V_SHIFT; + + r0 = mptr->_21 * cx + mptr->_22 * sx; + r1 = mptr->_22 * cx - mptr->_21 * sx; + mptr->_21 = r0 >> W2V_SHIFT; + mptr->_22 = r1 >> W2V_SHIFT; + } + + if (rz) { + int32_t sz = Math_Sin(rz); + int32_t cz = Math_Cos(rz); + r0 = mptr->_00 * cz + mptr->_01 * sz; + r1 = mptr->_01 * cz - mptr->_00 * sz; + mptr->_00 = r0 >> W2V_SHIFT; + mptr->_01 = r1 >> W2V_SHIFT; + + r0 = mptr->_10 * cz + mptr->_11 * sz; + r1 = mptr->_11 * cz - mptr->_10 * sz; + mptr->_10 = r0 >> W2V_SHIFT; + mptr->_11 = r1 >> W2V_SHIFT; + + r0 = mptr->_20 * cz + mptr->_21 * sz; + r1 = mptr->_21 * cz - mptr->_20 * sz; + mptr->_20 = r0 >> W2V_SHIFT; + mptr->_21 = r1 >> W2V_SHIFT; + } +} + +void __cdecl Matrix_RotYXZpack(uint32_t rpack) +{ + MATRIX *mptr = g_MatrixPtr; + int32_t r0, r1; + + PHD_ANGLE rx = (rpack >> 14) & 0xFFC0; + PHD_ANGLE ry = (rpack >> 4) & 0xFFC0; + PHD_ANGLE rz = (rpack & 0x3FF) << 6; + + if (ry) { + int32_t sy = Math_Sin(ry); + int32_t cy = Math_Cos(ry); + r0 = mptr->_00 * cy - mptr->_02 * sy; + r1 = mptr->_00 * sy + mptr->_02 * cy; + mptr->_00 = r0 >> W2V_SHIFT; + mptr->_02 = r1 >> W2V_SHIFT; + + r0 = mptr->_10 * cy - mptr->_12 * sy; + r1 = mptr->_12 * cy + mptr->_10 * sy; + mptr->_10 = r0 >> W2V_SHIFT; + mptr->_12 = r1 >> W2V_SHIFT; + + r0 = mptr->_20 * cy - mptr->_22 * sy; + r1 = mptr->_22 * cy + mptr->_20 * sy; + mptr->_20 = r0 >> W2V_SHIFT; + mptr->_22 = r1 >> W2V_SHIFT; + } + + if (rx) { + int32_t sx = Math_Sin(rx); + int32_t cx = Math_Cos(rx); + r0 = mptr->_01 * cx + mptr->_02 * sx; + r1 = mptr->_02 * cx - mptr->_01 * sx; + mptr->_01 = r0 >> W2V_SHIFT; + mptr->_02 = r1 >> W2V_SHIFT; + + r0 = mptr->_11 * cx + mptr->_12 * sx; + r1 = mptr->_12 * cx - mptr->_11 * sx; + mptr->_11 = r0 >> W2V_SHIFT; + mptr->_12 = r1 >> W2V_SHIFT; + + r0 = mptr->_21 * cx + mptr->_22 * sx; + r1 = mptr->_22 * cx - mptr->_21 * sx; + mptr->_21 = r0 >> W2V_SHIFT; + mptr->_22 = r1 >> W2V_SHIFT; + } + + if (rz) { + int32_t sz = Math_Sin(rz); + int32_t cz = Math_Cos(rz); + r0 = mptr->_00 * cz + mptr->_01 * sz; + r1 = mptr->_01 * cz - mptr->_00 * sz; + mptr->_00 = r0 >> W2V_SHIFT; + mptr->_01 = r1 >> W2V_SHIFT; + + r0 = mptr->_10 * cz + mptr->_11 * sz; + r1 = mptr->_11 * cz - mptr->_10 * sz; + mptr->_10 = r0 >> W2V_SHIFT; + mptr->_11 = r1 >> W2V_SHIFT; + + r0 = mptr->_20 * cz + mptr->_21 * sz; + r1 = mptr->_21 * cz - mptr->_20 * sz; + mptr->_20 = r0 >> W2V_SHIFT; + mptr->_21 = r1 >> W2V_SHIFT; + } +} + +void __cdecl Matrix_RotYXZsuperpack(const int16_t **pprot, int32_t index) +{ + const uint16_t *prot = (const uint16_t *)*pprot; + + for (int32_t i = 0; i < index; i++) { + if ((*prot >> 14) == 0) { + prot += 2; + } else { + prot += 1; + } + } + + switch (*prot >> 14) { + case 0: { + uint32_t packed = (prot[0] << 16) + prot[1]; + Matrix_RotYXZpack(packed); + prot += 2; + break; + } + case 1: + Matrix_RotX((int16_t)((*prot & 1023) << 6)); + prot += 1; + break; + case 2: + Matrix_RotY((int16_t)((*prot & 1023) << 6)); + prot += 1; + break; + default: + Matrix_RotZ((int16_t)((*prot & 1023) << 6)); + prot += 1; + break; + } + + *pprot = (int16_t *)prot; +} + +bool __cdecl Matrix_TranslateRel(int32_t x, int32_t y, int32_t z) +{ + MATRIX *mptr = g_MatrixPtr; + mptr->_03 += z * mptr->_02 + y * mptr->_01 + x * mptr->_00; + mptr->_13 += z * mptr->_12 + y * mptr->_11 + x * mptr->_10; + mptr->_23 += z * mptr->_22 + y * mptr->_21 + x * mptr->_20; + + return ( + ABS(mptr->_03) <= g_PhdFarZ && ABS(mptr->_13) <= g_PhdFarZ + && ABS(mptr->_23) <= g_PhdFarZ); +} + +void __cdecl Matrix_TranslateAbs(int32_t x, int32_t y, int32_t z) +{ + MATRIX *mptr = g_MatrixPtr; + const int32_t dx = x - g_W2VMatrix._03; + const int32_t dy = y - g_W2VMatrix._13; + const int32_t dz = z - g_W2VMatrix._23; + mptr->_03 = dx * mptr->_00 + dy * mptr->_01 + dz * mptr->_02; + mptr->_13 = dx * mptr->_10 + dy * mptr->_11 + dz * mptr->_12; + mptr->_23 = dx * mptr->_20 + dy * mptr->_21 + dz * mptr->_22; +} + +void __cdecl Matrix_InitInterpolate(int32_t frac, int32_t rate) +{ + g_IMRate = rate; + g_IMFrac = frac; + g_IMMatrixPtr = g_IMMatrixStack; + *g_IMMatrixPtr = *g_MatrixPtr; +} + +void __cdecl Matrix_Interpolate(void) +{ + const int32_t frac = g_IMFrac; + const int32_t rate = g_IMRate; + const MATRIX *iptr = g_IMMatrixPtr; + MATRIX *mptr = g_MatrixPtr; + + if (g_IMRate == 2) { + mptr->_00 += (iptr->_00 - mptr->_00) / 2; + mptr->_01 += (iptr->_01 - mptr->_01) / 2; + mptr->_02 += (iptr->_02 - mptr->_02) / 2; + mptr->_03 += (iptr->_03 - mptr->_03) / 2; + mptr->_10 += (iptr->_10 - mptr->_10) / 2; + mptr->_11 += (iptr->_11 - mptr->_11) / 2; + mptr->_12 += (iptr->_12 - mptr->_12) / 2; + mptr->_13 += (iptr->_13 - mptr->_13) / 2; + mptr->_20 += (iptr->_20 - mptr->_20) / 2; + mptr->_21 += (iptr->_21 - mptr->_21) / 2; + mptr->_22 += (iptr->_22 - mptr->_22) / 2; + mptr->_23 += (iptr->_23 - mptr->_23) / 2; + } else { + mptr->_00 += (iptr->_00 - mptr->_00) * frac / rate; + mptr->_01 += (iptr->_01 - mptr->_01) * frac / rate; + mptr->_02 += (iptr->_02 - mptr->_02) * frac / rate; + mptr->_03 += (iptr->_03 - mptr->_03) * frac / rate; + mptr->_10 += (iptr->_10 - mptr->_10) * frac / rate; + mptr->_11 += (iptr->_11 - mptr->_11) * frac / rate; + mptr->_12 += (iptr->_12 - mptr->_12) * frac / rate; + mptr->_13 += (iptr->_13 - mptr->_13) * frac / rate; + mptr->_20 += (iptr->_20 - mptr->_20) * frac / rate; + mptr->_21 += (iptr->_21 - mptr->_21) * frac / rate; + mptr->_22 += (iptr->_22 - mptr->_22) * frac / rate; + mptr->_23 += (iptr->_23 - mptr->_23) * frac / rate; + } +} + +void __cdecl Matrix_InterpolateArm(void) +{ + const int32_t frac = g_IMFrac; + const int32_t rate = g_IMRate; + const MATRIX *iptr = g_IMMatrixPtr; + MATRIX *mptr = g_MatrixPtr; + + if (g_IMRate == 2) { + mptr->_00 = mptr[-2]._00; + mptr->_01 = mptr[-2]._01; + mptr->_02 = mptr[-2]._02; + mptr->_03 = (mptr->_03 + iptr->_03) / 2; + mptr->_10 = mptr[-2]._10; + mptr->_11 = mptr[-2]._11; + mptr->_12 = mptr[-2]._12; + mptr->_13 = (mptr->_13 + iptr->_13) / 2; + mptr->_20 = mptr[-2]._20; + mptr->_21 = mptr[-2]._21; + mptr->_22 = mptr[-2]._22; + mptr->_23 = (mptr->_23 + iptr->_23) / 2; + } else { + mptr->_00 = mptr[-2]._00; + mptr->_01 = mptr[-2]._01; + mptr->_02 = mptr[-2]._02; + mptr->_03 += frac * (iptr->_03 - mptr->_03) / rate; + mptr->_10 = mptr[-2]._10; + mptr->_11 = mptr[-2]._11; + mptr->_12 = mptr[-2]._12; + mptr->_13 += frac * (iptr->_13 - mptr->_13) / rate; + mptr->_20 = mptr[-2]._20; + mptr->_21 = mptr[-2]._21; + mptr->_22 = mptr[-2]._22; + mptr->_23 += frac * (iptr->_23 - mptr->_23) / rate; + } +} + +void __cdecl Matrix_Push_I(void) +{ + Matrix_Push(); + g_IMMatrixPtr[1] = g_IMMatrixPtr[0]; + g_IMMatrixPtr++; +} + +void __cdecl Matrix_Pop_I(void) +{ + g_MatrixPtr--; + g_IMMatrixPtr--; +} + +void __cdecl Matrix_RotX_I(int16_t ang) +{ + Matrix_RotX(ang); + MATRIX *old_matrix = g_MatrixPtr; + g_MatrixPtr = g_IMMatrixPtr; + Matrix_RotX(ang); + g_MatrixPtr = old_matrix; +} + +void __cdecl Matrix_RotY_I(int16_t ang) +{ + Matrix_RotY(ang); + MATRIX *old_matrix = g_MatrixPtr; + g_MatrixPtr = g_IMMatrixPtr; + Matrix_RotY(ang); + g_MatrixPtr = old_matrix; +} + +void __cdecl Matrix_RotZ_I(int16_t ang) +{ + Matrix_RotZ(ang); + MATRIX *old_matrix = g_MatrixPtr; + g_MatrixPtr = g_IMMatrixPtr; + Matrix_RotZ(ang); + g_MatrixPtr = old_matrix; +} + +void __cdecl Matrix_RotYXZ_I(int16_t y, int16_t x, int16_t z) +{ + Matrix_RotYXZ(y, x, z); + MATRIX *old_matrix = g_MatrixPtr; + g_MatrixPtr = g_IMMatrixPtr; + Matrix_RotYXZ(y, x, z); + g_MatrixPtr = old_matrix; +} + +void __cdecl Matrix_RotYXZsuperpack_I( + const int16_t **pprot1, const int16_t **pprot2, int32_t index) +{ + Matrix_RotYXZsuperpack(pprot1, index); + MATRIX *old_matrix = g_MatrixPtr; + g_MatrixPtr = g_IMMatrixPtr; + Matrix_RotYXZsuperpack(pprot2, index); + g_MatrixPtr = old_matrix; +} + +void __cdecl Matrix_TranslateRel_I(int32_t x, int32_t y, int32_t z) +{ + Matrix_TranslateRel(x, y, z); + MATRIX *old_matrix = g_MatrixPtr; + g_MatrixPtr = g_IMMatrixPtr; + Matrix_TranslateRel(x, y, z); + g_MatrixPtr = old_matrix; +} + +void __cdecl Matrix_TranslateRel_ID( + int32_t x, int32_t y, int32_t z, int32_t x2, int32_t y2, int32_t z2) +{ + Matrix_TranslateRel(x, y, z); + MATRIX *old_matrix = g_MatrixPtr; + g_MatrixPtr = g_IMMatrixPtr; + Matrix_TranslateRel(x2, y2, z2); + g_MatrixPtr = old_matrix; +} diff --git a/src/tr2/game/matrix.h b/src/tr2/game/matrix.h new file mode 100644 index 000000000..4330dc52e --- /dev/null +++ b/src/tr2/game/matrix.h @@ -0,0 +1,37 @@ +#pragma once + +#include "global/types.h" + +#include +#include + +void __cdecl Matrix_Push(void); +void __cdecl Matrix_PushUnit(void); +void __cdecl Matrix_Pop(void); +void __cdecl Matrix_GenerateW2V(const PHD_3DPOS *viewpos); +void __cdecl Matrix_LookAt( + int32_t xsrc, int32_t ysrc, int32_t zsrc, int32_t xtar, int32_t ytar, + int32_t ztar, int16_t roll); +void __cdecl Matrix_RotX(PHD_ANGLE rx); +void __cdecl Matrix_RotY(PHD_ANGLE ry); +void __cdecl Matrix_RotZ(PHD_ANGLE rz); +void __cdecl Matrix_RotYXZ(int16_t ry, int16_t rx, int16_t rz); +void __cdecl Matrix_RotYXZpack(uint32_t rpack); +void __cdecl Matrix_RotYXZsuperpack(const int16_t **pprot, int32_t index); +bool __cdecl Matrix_TranslateRel(int32_t x, int32_t y, int32_t z); +void __cdecl Matrix_TranslateAbs(int32_t x, int32_t y, int32_t z); + +void __cdecl Matrix_InitInterpolate(int32_t frac, int32_t rate); +void __cdecl Matrix_Interpolate(void); +void __cdecl Matrix_InterpolateArm(void); +void __cdecl Matrix_Push_I(void); +void __cdecl Matrix_Pop_I(void); +void __cdecl Matrix_RotX_I(int16_t ang); +void __cdecl Matrix_RotY_I(int16_t ang); +void __cdecl Matrix_RotZ_I(int16_t ang); +void __cdecl Matrix_RotYXZ_I(int16_t y, int16_t x, int16_t z); +void __cdecl Matrix_RotYXZsuperpack_I( + const int16_t **pprot1, const int16_t **pprot2, int32_t index); +void __cdecl Matrix_TranslateRel_I(int32_t x, int32_t y, int32_t z); +void __cdecl Matrix_TranslateRel_ID( + int32_t x, int32_t y, int32_t z, int32_t x2, int32_t y2, int32_t z2); diff --git a/src/tr2/game/music.h b/src/tr2/game/music.h new file mode 100644 index 000000000..584efb19f --- /dev/null +++ b/src/tr2/game/music.h @@ -0,0 +1,11 @@ +#pragma once + +#include "global/types.h" + +bool __cdecl Music_Init(void); +void __cdecl Music_Shutdown(void); +void __cdecl Music_Play(int16_t track_id, bool is_looped); +void __cdecl Music_Stop(void); +bool __cdecl Music_PlaySynced(int16_t track_id); +double __cdecl Music_GetTimestamp(void); +void __cdecl Music_SetVolume(int32_t volume); diff --git a/src/tr2/game/music/music_backend.h b/src/tr2/game/music/music_backend.h new file mode 100644 index 000000000..24e6819b4 --- /dev/null +++ b/src/tr2/game/music/music_backend.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +typedef struct MUSIC_BACKEND { + bool (*init)(struct MUSIC_BACKEND *backend); + const char *(*describe)(const struct MUSIC_BACKEND *backend); + int32_t (*play)(const struct MUSIC_BACKEND *backend, int32_t track_id); + void *data; +} MUSIC_BACKEND; diff --git a/src/tr2/game/music/music_backend_cdaudio.c b/src/tr2/game/music/music_backend_cdaudio.c new file mode 100644 index 000000000..038f54589 --- /dev/null +++ b/src/tr2/game/music/music_backend_cdaudio.c @@ -0,0 +1,194 @@ +#include "game/music/music_backend_cdaudio.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MAX_CD_TRACKS 60 + +typedef struct { + uint64_t from; + uint64_t to; + bool active; +} CDAUDIO_TRACK; + +typedef struct { + const char *path; + const char *description; + CDAUDIO_TRACK *tracks; +} BACKEND_DATA; + +static bool M_Parse(BACKEND_DATA *data); +static bool M_Init(MUSIC_BACKEND *backend); +static const char *M_Describe(const MUSIC_BACKEND *backend); +static int32_t M_Play(const MUSIC_BACKEND *backend, int32_t track_id); + +static bool M_Parse(BACKEND_DATA *const data) +{ + assert(data != NULL); + + char *track_content = NULL; + size_t track_content_size; + if (!File_Load("audio/cdaudio.dat", &track_content, &track_content_size)) { + LOG_WARNING("Cannot find CDAudio control file"); + return false; + } + + data->tracks = Memory_Alloc(sizeof(CDAUDIO_TRACK) * MAX_CD_TRACKS); + + size_t offset = 0; + while (offset < track_content_size) { + while (track_content[offset] == '\n' || track_content[offset] == '\r') { + if (++offset >= track_content_size) { + goto parse_end; + } + } + + uint64_t track_num; + uint64_t from; + uint64_t to; + int32_t result = sscanf( + &track_content[offset], "%" PRIu64 " %" PRIu64 " %" PRIu64, + &track_num, &from, &to); + + if (result == 3 && track_num > 0 && track_num <= MAX_CD_TRACKS) { + int32_t track_idx = track_num - 1; + data->tracks[track_idx].active = true; + data->tracks[track_idx].from = from; + data->tracks[track_idx].to = to; + } + + while (track_content[offset] != '\n' && track_content[offset] != '\r') { + if (++offset >= track_content_size) { + goto parse_end; + } + } + } + +parse_end: + Memory_Free(track_content); + + // reindex wrong track boundaries + for (int32_t i = 0; i < MAX_CD_TRACKS; i++) { + if (!data->tracks[i].active) { + continue; + } + + if (i < MAX_CD_TRACKS - 1 + && data->tracks[i].from >= data->tracks[i].to) { + for (int32_t j = i + 1; j < MAX_CD_TRACKS; j++) { + if (data->tracks[j].active) { + data->tracks[i].to = data->tracks[j].from; + break; + } + } + } + + if (data->tracks[i].from >= data->tracks[i].to && i > 0) { + for (int32_t j = i - 1; j >= 0; j--) { + if (data->tracks[j].active) { + data->tracks[i].from = data->tracks[j].to; + break; + } + } + } + } + + return true; +} + +static bool M_Init(MUSIC_BACKEND *const backend) +{ + assert(backend != NULL); + BACKEND_DATA *data = backend->data; + assert(data != NULL); + + MYFILE *const fp = File_Open(data->path, FILE_OPEN_READ); + if (fp == NULL) { + return false; + } + + if (!M_Parse(data)) { + LOG_ERROR("Failed to parse CDAudio data"); + return false; + } + + return true; +} + +static const char *M_Describe(const MUSIC_BACKEND *const backend) +{ + assert(backend != NULL); + const BACKEND_DATA *const data = backend->data; + assert(data != NULL); + return data->description; +} + +static int32_t M_Play( + const MUSIC_BACKEND *const backend, const int32_t track_id) +{ + assert(backend != NULL); + const BACKEND_DATA *const data = backend->data; + assert(data != NULL); + + const int32_t track_idx = track_id - 1; + const CDAUDIO_TRACK *track = &data->tracks[track_idx]; + if (track_idx < 0 || track_idx >= MAX_CD_TRACKS) { + LOG_ERROR("Invalid track: %d", track_id); + return -1; + } + + if (!track->active) { + LOG_ERROR("Invalid track: %d", track_id); + return -1; + } + + int32_t audio_stream_id = Audio_Stream_CreateFromFile(data->path); + Audio_Stream_SetStartTimestamp(audio_stream_id, track->from / 1000.0); + Audio_Stream_SetStopTimestamp(audio_stream_id, track->to / 1000.0); + Audio_Stream_SeekTimestamp(audio_stream_id, track->from / 1000.0); + return audio_stream_id; +} + +MUSIC_BACKEND *Music_Backend_CDAudio_Factory(const char *path) +{ + assert(path != NULL); + + const char *description_fmt = "CDAudio (path: %s)"; + const size_t description_size = snprintf(NULL, 0, description_fmt, path); + char *description = Memory_Alloc(description_size + 1); + sprintf(description, description_fmt, path); + + BACKEND_DATA *data = Memory_Alloc(sizeof(BACKEND_DATA)); + data->path = Memory_DupStr(path); + data->description = description; + + MUSIC_BACKEND *backend = Memory_Alloc(sizeof(MUSIC_BACKEND)); + backend->data = data; + backend->init = M_Init; + backend->describe = M_Describe; + backend->play = M_Play; + return backend; +} + +void Music_Backend_CDAudio_Destroy(MUSIC_BACKEND *backend) +{ + if (backend == NULL) { + return; + } + + if (backend->data != NULL) { + BACKEND_DATA *const data = backend->data; + Memory_FreePointer(&data->path); + Memory_FreePointer(&data->description); + Memory_FreePointer(&data->tracks); + } + Memory_FreePointer(&backend->data); + Memory_FreePointer(&backend); +} diff --git a/src/tr2/game/music/music_backend_cdaudio.h b/src/tr2/game/music/music_backend_cdaudio.h new file mode 100644 index 000000000..325c0e935 --- /dev/null +++ b/src/tr2/game/music/music_backend_cdaudio.h @@ -0,0 +1,6 @@ +#pragma once + +#include "game/music/music_backend.h" + +MUSIC_BACKEND *Music_Backend_CDAudio_Factory(const char *path); +void Music_Backend_CDAudio_Destroy(MUSIC_BACKEND *backend); diff --git a/src/tr2/game/music/music_backend_files.c b/src/tr2/game/music/music_backend_files.c new file mode 100644 index 000000000..636b76874 --- /dev/null +++ b/src/tr2/game/music/music_backend_files.c @@ -0,0 +1,103 @@ + +#include "game/music/music_backend_files.h" + +#include +#include +#include +#include + +#include + +typedef struct { + const char *dir; + const char *description; +} BACKEND_DATA; + +static const char *m_ExtensionsToTry[] = { ".flac", ".ogg", ".mp3", ".wav", + NULL }; + +static char *M_GetTrackFileName(const char *base_dir, int32_t track); +static const char *M_Describe(const MUSIC_BACKEND *backend); +static bool M_Init(MUSIC_BACKEND *backend); +static int32_t M_Play(const MUSIC_BACKEND *backend, int32_t track_id); + +static char *M_GetTrackFileName(const char *base_dir, int32_t track) +{ + char file_path[64]; + sprintf(file_path, "%s/track%02d.flac", base_dir, track); + char *result = File_GuessExtension(file_path, m_ExtensionsToTry); + if (!File_Exists(file_path)) { + Memory_FreePointer(&result); + sprintf(file_path, "%s/%d.flac", base_dir, track); + result = File_GuessExtension(file_path, m_ExtensionsToTry); + } + return result; +} + +static bool M_Init(MUSIC_BACKEND *const backend) +{ + assert(backend != NULL); + const BACKEND_DATA *data = backend->data; + assert(data->dir != NULL); + return File_DirExists(data->dir); +} + +static const char *M_Describe(const MUSIC_BACKEND *const backend) +{ + assert(backend != NULL); + const BACKEND_DATA *const data = backend->data; + assert(data != NULL); + return data->description; +} + +static int32_t M_Play( + const MUSIC_BACKEND *const backend, const int32_t track_id) +{ + assert(backend != NULL); + const BACKEND_DATA *const data = backend->data; + assert(data != NULL); + + char *file_path = M_GetTrackFileName(data->dir, track_id); + if (file_path == NULL) { + LOG_ERROR("Invalid track: %d", track_id); + return -1; + } + + return Audio_Stream_CreateFromFile(file_path); +} + +MUSIC_BACKEND *Music_Backend_Files_Factory(const char *path) +{ + assert(path != NULL); + + const char *description_fmt = "Directory (directory: %s)"; + const size_t description_size = snprintf(NULL, 0, description_fmt, path); + char *description = Memory_Alloc(description_size + 1); + sprintf(description, description_fmt, path); + + BACKEND_DATA *data = Memory_Alloc(sizeof(BACKEND_DATA)); + data->dir = Memory_DupStr(path); + data->description = description; + + MUSIC_BACKEND *backend = Memory_Alloc(sizeof(MUSIC_BACKEND)); + backend->data = data; + backend->init = M_Init; + backend->describe = M_Describe; + backend->play = M_Play; + return backend; +} + +void Music_Backend_Files_Destroy(MUSIC_BACKEND *backend) +{ + if (backend == NULL) { + return; + } + + if (backend->data != NULL) { + BACKEND_DATA *const data = backend->data; + Memory_FreePointer(&data->dir); + Memory_FreePointer(&data->description); + } + Memory_FreePointer(&backend->data); + Memory_FreePointer(&backend); +} diff --git a/src/tr2/game/music/music_backend_files.h b/src/tr2/game/music/music_backend_files.h new file mode 100644 index 000000000..92df3322c --- /dev/null +++ b/src/tr2/game/music/music_backend_files.h @@ -0,0 +1,6 @@ +#pragma once + +#include "game/music/music_backend.h" + +MUSIC_BACKEND *Music_Backend_Files_Factory(const char *path); +void Music_Backend_Files_Destroy(MUSIC_BACKEND *backend); diff --git a/src/tr2/game/music/music_main.c b/src/tr2/game/music/music_main.c new file mode 100644 index 000000000..405fb110b --- /dev/null +++ b/src/tr2/game/music/music_main.c @@ -0,0 +1,175 @@ +#include "game/music.h" + +#include "game/music/music_backend.h" +#include "game/music/music_backend_cdaudio.h" +#include "game/music/music_backend_files.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/types.h" +#include "global/vars.h" + +#include +#include + +#include + +static MUSIC_TRACK_ID m_TrackCurrent = MX_INACTIVE; +static MUSIC_TRACK_ID m_TrackLooped = MX_INACTIVE; + +static bool m_Initialized = false; +static float m_MusicVolume = 0.0f; +static int32_t m_AudioStreamID = -1; +static const MUSIC_BACKEND *m_Backend = NULL; + +static const MUSIC_BACKEND *M_FindBackend(void); +static void M_StreamFinished(int32_t stream_id, void *user_data); + +static const MUSIC_BACKEND *M_FindBackend(void) +{ + MUSIC_BACKEND *all_backends[] = { + Music_Backend_Files_Factory("music"), + Music_Backend_CDAudio_Factory("audio/cdaudio.wav"), + Music_Backend_CDAudio_Factory("audio/cdaudio.mp3"), + NULL, + }; + + MUSIC_BACKEND **backend_ptr = all_backends; + while (true) { + MUSIC_BACKEND *backend = *backend_ptr; + if (backend == NULL) { + break; + } + if (backend->init(backend)) { + return backend; + } + backend_ptr++; + } + return NULL; +} + +static void M_StreamFinished(const int32_t stream_id, void *const user_data) +{ + // When a stream finishes, play the remembered background BGM. + if (stream_id == m_AudioStreamID) { + m_AudioStreamID = -1; + if (m_TrackLooped >= 0) { + Music_Play(m_TrackLooped, true); + } + } +} + +bool __cdecl Music_Init(void) +{ + bool result = false; + + // TODO: remove this guard once Music_Init can be called in a proper place + if (m_Initialized) { + return true; + } + + m_Backend = M_FindBackend(); + if (m_Backend == NULL) { + LOG_ERROR("No music backend is available"); + goto finish; + } + + LOG_ERROR("Chosen music backend: %s", m_Backend->describe(m_Backend)); + result = true; + Music_SetVolume(25 * g_OptionMusicVolume + 5); + +finish: + m_TrackCurrent = MX_INACTIVE; + m_TrackLooped = MX_INACTIVE; + m_Initialized = true; + return result; +} + +void __cdecl Music_Shutdown(void) +{ + if (m_AudioStreamID < 0) { + return; + } + + // We are only interested in calling M_StreamFinished if a stream + // finished by itself. In cases where we end the streams early by hand, + // we clear the finish callback in order to avoid resuming the BGM playback + // just after we stop it. + Audio_Stream_SetFinishCallback(m_AudioStreamID, NULL, NULL); + Audio_Stream_Close(m_AudioStreamID); +} + +void __cdecl Music_Play(int16_t track_id, bool is_looped) +{ + if (track_id == m_TrackCurrent) { + return; + } + + // TODO: this should be called in shell instead, once per game launch + Music_Init(); + + Audio_Stream_Close(m_AudioStreamID); + if (g_OptionMusicVolume == 0) { + LOG_DEBUG("Not playing track %d because the game is silent", track_id); + goto finish; + } + + if (m_Backend == NULL) { + LOG_DEBUG( + "Not playing track %d because no backend is available", track_id); + goto finish; + } + + const int32_t real_track_id = Music_GetRealTrack(track_id); + LOG_DEBUG( + "Playing track %d (real: %d), looped: %d", track_id, real_track_id, + is_looped); + + m_AudioStreamID = m_Backend->play(m_Backend, real_track_id); + if (m_AudioStreamID < 0) { + LOG_ERROR("Failed to create music stream for track %d", track_id); + goto finish; + } + + Audio_Stream_SetIsLooped(m_AudioStreamID, is_looped); + Audio_Stream_SetVolume(m_AudioStreamID, m_MusicVolume); + Audio_Stream_SetFinishCallback(m_AudioStreamID, M_StreamFinished, NULL); + +finish: + g_CD_TrackID = track_id; + m_TrackCurrent = track_id; + if (is_looped) { + m_TrackLooped = track_id; + } +} + +void __cdecl Music_Stop(void) +{ + if (m_AudioStreamID < 0) { + return; + } + m_TrackCurrent = MX_INACTIVE; + m_TrackLooped = MX_INACTIVE; + Audio_Stream_Close(m_AudioStreamID); +} + +bool __cdecl Music_PlaySynced(int16_t track_id) +{ + Music_Play(track_id, false); + return true; +} + +double __cdecl Music_GetTimestamp(void) +{ + if (m_AudioStreamID < 0) { + return -1.0; + } + return Audio_Stream_GetTimestamp(m_AudioStreamID); +} + +void __cdecl Music_SetVolume(int32_t volume) +{ + m_MusicVolume = volume ? volume / 255.0f : 0.0f; + if (m_AudioStreamID >= 0) { + Audio_Stream_SetVolume(m_AudioStreamID, m_MusicVolume); + } +} diff --git a/src/tr2/game/objects/common.c b/src/tr2/game/objects/common.c new file mode 100644 index 000000000..47eab2e12 --- /dev/null +++ b/src/tr2/game/objects/common.c @@ -0,0 +1,83 @@ +#include "game/objects/common.h" + +#include "game/items.h" +#include "game/lara/misc.h" +#include "global/funcs.h" +#include "global/vars.h" + +OBJECT *Object_GetObject(GAME_OBJECT_ID object_id) +{ + return &g_Objects[object_id]; +} + +GAME_OBJECT_ID Object_GetCognate( + GAME_OBJECT_ID key_id, const GAME_OBJECT_PAIR *test_map) +{ + const GAME_OBJECT_PAIR *pair = &test_map[0]; + while (pair->key_id != NO_OBJECT) { + if (pair->key_id == key_id) { + return pair->value_id; + } + pair++; + } + + return NO_OBJECT; +} + +GAME_OBJECT_ID Object_GetCognateInverse( + GAME_OBJECT_ID value_id, const GAME_OBJECT_PAIR *test_map) +{ + const GAME_OBJECT_PAIR *pair = &test_map[0]; + while (pair->key_id != NO_OBJECT) { + if (pair->value_id == value_id) { + return pair->key_id; + } + pair++; + } + + return NO_OBJECT; +} + +bool Object_IsObjectType( + const GAME_OBJECT_ID object_id, const GAME_OBJECT_ID *const test_arr) +{ + for (int32_t i = 0; test_arr[i] != NO_OBJECT; i++) { + if (test_arr[i] == object_id) { + return true; + } + } + return false; +} + +void __cdecl Object_Collision( + const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) +{ + ITEM *const item = &g_Items[item_num]; + + if (!Item_TestBoundsCollide(item, lara_item, coll->radius)) { + return; + } + + if (!Collide_TestCollision(item, lara_item)) { + return; + } + + if (coll->enable_baddie_push) { + Lara_Push(item, lara_item, coll, false, true); + } +} + +void __cdecl Object_Collision_Trap( + const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) +{ + + ITEM *const item = &g_Items[item_num]; + + if (item->status == IS_ACTIVE) { + if (Item_TestBoundsCollide(item, lara_item, coll->radius)) { + Collide_TestCollision(item, lara_item); + } + } else if (item->status != IS_INVISIBLE) { + Object_Collision(item_num, lara_item, coll); + } +} diff --git a/src/tr2/game/objects/common.h b/src/tr2/game/objects/common.h new file mode 100644 index 000000000..6bb88d7b0 --- /dev/null +++ b/src/tr2/game/objects/common.h @@ -0,0 +1,19 @@ +#pragma once + +#include "global/types.h" + +typedef struct GAME_OBJECT_PAIR { + const GAME_OBJECT_ID key_id; + const GAME_OBJECT_ID value_id; +} GAME_OBJECT_PAIR; + +OBJECT *Object_Get(GAME_OBJECT_ID object_id); +GAME_OBJECT_ID Object_GetCognate( + GAME_OBJECT_ID key_id, const GAME_OBJECT_PAIR *test_map); +GAME_OBJECT_ID Object_GetCognateInverse( + GAME_OBJECT_ID value_id, const GAME_OBJECT_PAIR *test_map); +bool Object_IsObjectType( + GAME_OBJECT_ID object_id, const GAME_OBJECT_ID *test_arr); + +void __cdecl Object_Collision( + int16_t item_num, ITEM *lara_item, COLL_INFO *coll); diff --git a/src/tr2/game/objects/creatures/bird.c b/src/tr2/game/objects/creatures/bird.c new file mode 100644 index 000000000..61a9f40b1 --- /dev/null +++ b/src/tr2/game/objects/creatures/bird.c @@ -0,0 +1,155 @@ +#include "game/objects/creatures/bird.h" + +#include "game/creature.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/types.h" +#include "global/vars.h" + +#include + +typedef enum { + BIRD_ANIM_EMPTY = 0, + BIRD_ANIM_FLY = 1, + BIRD_ANIM_STOP = 2, + BIRD_ANIM_GLIDE = 3, + BIRD_ANIM_FALL = 4, + BIRD_ANIM_DEATH = 5, + BIRD_ANIM_ATTACK = 6, + BIRD_ANIM_EAT = 7, +} BIRD_ANIM; + +static const BITE m_BirdBite = { + .pos = { .x = 15, .y = 46, .z = 21 }, + .mesh_num = 6, +}; +static const BITE m_CrowBite = { + .pos = { .x = 2, .y = 10, .z = 60 }, + .mesh_num = 14, +}; + +#define BIRD_DAMAGE 20 +#define BIRD_ATTACK_RANGE SQUARE(WALL_L / 2) // = 262144 +#define BIRD_TURN (PHD_DEGREE * 3) // = 546 +#define BIRD_START_ANIM 5 +#define BIRD_DIE_ANIM 8 +#define CROW_START_ANIM 14 +#define CROW_DIE_ANIM 1 + +void __cdecl Bird_Initialise(const int16_t item_num) +{ + Creature_Initialise(item_num); + ITEM *const item = &g_Items[item_num]; + if (item->object_id == O_CROW) { + item->anim_num = g_Objects[O_CROW].anim_idx + CROW_START_ANIM; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->goal_anim_state = BIRD_ANIM_EAT; + item->current_anim_state = BIRD_ANIM_EAT; + } else { + item->anim_num = g_Objects[O_EAGLE].anim_idx + BIRD_START_ANIM; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->goal_anim_state = BIRD_ANIM_STOP; + item->current_anim_state = BIRD_ANIM_STOP; + } +} + +void __cdecl Bird_Control(const int16_t item_num) +{ + if (!Creature_Activate(item_num)) { + return; + } + + ITEM *const item = &g_Items[item_num]; + CREATURE *const bird = (CREATURE *)item->data; + + if (item->hit_points <= 0) { + switch (item->current_anim_state) { + case BIRD_ANIM_FALL: + if (item->pos.y > item->floor) { + item->pos.y = item->floor; + item->gravity = 0; + item->fall_speed = 0; + item->goal_anim_state = BIRD_ANIM_DEATH; + } + break; + + case BIRD_ANIM_DEATH: + item->pos.y = item->floor; + break; + + default: + if (item->object_id == O_CROW) { + item->anim_num = g_Objects[O_CROW].anim_idx + CROW_DIE_ANIM; + } else { + item->anim_num = g_Objects[O_EAGLE].anim_idx + BIRD_DIE_ANIM; + } + item->frame_num = g_Anims[item->anim_num].frame_base; + item->current_anim_state = BIRD_ANIM_FALL; + item->gravity = 1; + item->speed = 0; + break; + } + item->rot.x = 0; + Creature_Animate(item_num, 0, 0); + return; + } + + AI_INFO info; + Creature_AIInfo(item, &info); + Creature_Mood(item, &info, MOOD_BORED); + const int16_t angle = Creature_Turn(item, BIRD_TURN); + + switch (item->current_anim_state) { + case BIRD_ANIM_FLY: + bird->flags = 0; + if (item->required_anim_state != BIRD_ANIM_EMPTY) { + item->goal_anim_state = item->required_anim_state; + } + if (bird->mood == MOOD_BORED) { + item->goal_anim_state = BIRD_ANIM_STOP; + } else if (info.ahead && info.distance < BIRD_ATTACK_RANGE) { + item->goal_anim_state = BIRD_ANIM_ATTACK; + } else { + item->goal_anim_state = BIRD_ANIM_GLIDE; + } + break; + + case BIRD_ANIM_STOP: + item->pos.y = item->floor; + if (bird->mood != MOOD_BORED) { + item->goal_anim_state = BIRD_ANIM_FLY; + } + break; + + case BIRD_ANIM_GLIDE: + if (bird->mood == MOOD_BORED) { + item->required_anim_state = BIRD_ANIM_STOP; + item->goal_anim_state = BIRD_ANIM_FLY; + } else if (info.ahead && info.distance < BIRD_ATTACK_RANGE) { + item->goal_anim_state = BIRD_ANIM_ATTACK; + } + break; + + case BIRD_ANIM_ATTACK: + if (!bird->flags && item->touch_bits) { + g_LaraItem->hit_points -= BIRD_DAMAGE; + g_LaraItem->hit_status = 1; + if (item->object_id == O_CROW) { + Creature_Effect(item, &m_CrowBite, DoBloodSplat); + } else { + Creature_Effect(item, &m_BirdBite, DoBloodSplat); + } + bird->flags = 1; + } + break; + + case BIRD_ANIM_EAT: + item->pos.y = item->floor; + if (bird->mood != MOOD_BORED) { + item->goal_anim_state = BIRD_ANIM_FLY; + } + break; + } + + Creature_Animate(item_num, angle, 0); +} diff --git a/src/tr2/game/objects/creatures/bird.h b/src/tr2/game/objects/creatures/bird.h new file mode 100644 index 000000000..459e68585 --- /dev/null +++ b/src/tr2/game/objects/creatures/bird.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +void __cdecl Bird_Initialise(int16_t item_num); +void __cdecl Bird_Control(int16_t item_num); diff --git a/src/tr2/game/objects/creatures/diver.c b/src/tr2/game/objects/creatures/diver.c new file mode 100644 index 000000000..aa053b0d2 --- /dev/null +++ b/src/tr2/game/objects/creatures/diver.c @@ -0,0 +1,194 @@ +#include "game/objects/creatures/diver.h" + +#include "game/creature.h" +#include "game/los.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +static BITE m_DiverBite = { .pos = { .x = 17, .y = 164, .z = 44, }, .mesh_num = 18 }; + +typedef enum { + DIVER_ANIM_EMPTY = 0, + DIVER_ANIM_SWIM_1 = 1, + DIVER_ANIM_SWIM_2 = 2, + DIVER_ANIM_SHOOT_1 = 3, + DIVER_ANIM_AIM_1 = 4, + DIVER_ANIM_NULL_1 = 5, + DIVER_ANIM_AIM_2 = 6, + DIVER_ANIM_SHOOT_2 = 7, + DIVER_ANIM_NULL_2 = 8, + DIVER_ANIM_DEATH = 9, +} DIVER_ANIM; + +#define DIVER_SWIM_TURN (3 * PHD_DEGREE) // = 546 +#define DIVER_FRONT_ARC PHD_45 +#define DIVER_DIE_ANIM 16 + +void __cdecl Diver_Control(int16_t item_num) +{ + if (!Creature_Activate(item_num)) { + return; + } + + ITEM *const item = &g_Items[item_num]; + CREATURE *const creature = item->data; + + if (item->hit_points <= 0) { + if (item->current_anim_state != DIVER_ANIM_DEATH) { + item->anim_num = g_Objects[O_DIVER].anim_idx + DIVER_DIE_ANIM; + item->frame_num = g_Anims[item->anim_num].frame_base; + item->current_anim_state = DIVER_ANIM_DEATH; + } + Creature_Float(item_num); + return; + } + + AI_INFO info; + Creature_AIInfo(item, &info); + Creature_Mood(item, &info, MOOD_BORED); + + bool shoot; + if (g_Lara.water_status == LWS_ABOVE_WATER) { + GAME_VECTOR start; + start.pos.x = item->pos.x; + start.pos.y = item->pos.y - STEP_L; + start.pos.z = item->pos.z; + start.room_num = item->room_num; + + GAME_VECTOR target; + target.pos.x = g_LaraItem->pos.x; + target.pos.y = g_LaraItem->pos.y - (LARA_HEIGHT - 150); + target.pos.z = g_LaraItem->pos.z; + target.room_num = g_LaraItem->room_num; + shoot = LOS_Check(&start, &target); + + if (shoot) { + creature->target.x = g_LaraItem->pos.x; + creature->target.y = g_LaraItem->pos.y; + creature->target.z = g_LaraItem->pos.z; + } + + if (info.angle < -DIVER_FRONT_ARC || info.angle > DIVER_FRONT_ARC) { + shoot = false; + } + } else if (info.angle > -DIVER_FRONT_ARC && info.angle < DIVER_FRONT_ARC) { + GAME_VECTOR start; + start.pos.x = item->pos.x; + start.pos.y = item->pos.y; + start.pos.z = item->pos.z; + start.room_num = item->room_num; + + GAME_VECTOR target; + target.pos.x = g_LaraItem->pos.x; + target.pos.y = g_LaraItem->pos.y; + target.pos.z = g_LaraItem->pos.z; + target.room_num = g_LaraItem->room_num; + + shoot = LOS_Check(&start, &target); + } else { + shoot = false; + } + + int16_t head = 0; + int16_t neck = 0; + int16_t angle = Creature_Turn(item, creature->maximum_turn); + int32_t water_level = + Diver_GetWaterSurface( + item->pos.x, item->pos.y, item->pos.z, item->room_num) + + 512; + + switch (item->current_anim_state) { + case DIVER_ANIM_SWIM_1: + creature->maximum_turn = DIVER_SWIM_TURN; + if (shoot) { + neck = -info.angle; + } + if (creature->target.y < water_level + && item->pos.y < water_level + creature->lot.fly) { + item->goal_anim_state = DIVER_ANIM_SWIM_2; + } else if (creature->mood != MOOD_ESCAPE && shoot) { + item->goal_anim_state = DIVER_ANIM_AIM_1; + } + break; + + case DIVER_ANIM_SWIM_2: + creature->maximum_turn = DIVER_SWIM_TURN; + if (shoot) { + head = info.angle; + } + if (creature->target.y > water_level) { + item->goal_anim_state = DIVER_ANIM_SWIM_1; + } else if (creature->mood != MOOD_ESCAPE && shoot) { + item->goal_anim_state = DIVER_ANIM_AIM_2; + } + break; + + case DIVER_ANIM_SHOOT_1: + if (shoot) { + neck = -info.angle; + } + if (!creature->flags) { + Creature_Effect(item, &m_DiverBite, Diver_Harpoon); + creature->flags = 1; + } + break; + + case DIVER_ANIM_SHOOT_2: + if (shoot) { + head = info.angle; + } + if (!creature->flags) { + Creature_Effect(item, &m_DiverBite, Diver_Harpoon); + creature->flags = 1; + } + break; + + case DIVER_ANIM_AIM_1: + creature->flags = 0; + if (shoot) { + neck = -info.angle; + } + if (!shoot || creature->mood == MOOD_ESCAPE + || (creature->target.y < water_level + && item->pos.y < water_level + creature->lot.fly)) { + item->goal_anim_state = DIVER_ANIM_SWIM_1; + } else { + item->goal_anim_state = DIVER_ANIM_SHOOT_1; + } + break; + + case DIVER_ANIM_AIM_2: + creature->flags = 0; + if (shoot) { + head = info.angle; + } + if (!shoot || creature->mood == MOOD_ESCAPE + || creature->target.y > water_level) { + item->goal_anim_state = DIVER_ANIM_SWIM_2; + } else { + item->goal_anim_state = DIVER_ANIM_SHOOT_2; + } + break; + + default: + break; + } + + Creature_Head(item, head); + Creature_Neck(item, neck); + + Creature_Animate(item_num, angle, 0); + + switch (item->current_anim_state) { + case DIVER_ANIM_SWIM_1: + case DIVER_ANIM_AIM_1: + case DIVER_ANIM_SHOOT_1: + Creature_Underwater(item, WALL_L / 2); + break; + + default: + item->pos.y = water_level - WALL_L / 2; + break; + } +} diff --git a/src/tr2/game/objects/creatures/diver.h b/src/tr2/game/objects/creatures/diver.h new file mode 100644 index 000000000..477984439 --- /dev/null +++ b/src/tr2/game/objects/creatures/diver.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void __cdecl Diver_Control(int16_t item_num); diff --git a/src/tr2/game/objects/effects/ember.c b/src/tr2/game/objects/effects/ember.c new file mode 100644 index 000000000..6b00cedad --- /dev/null +++ b/src/tr2/game/objects/effects/ember.c @@ -0,0 +1,34 @@ +#include "game/objects/effects/ember.h" + +#include "game/effects.h" +#include "game/lara/control.h" +#include "game/lara/misc.h" +#include "game/math.h" +#include "game/room.h" +#include "global/vars.h" + +void __cdecl Ember_Control(const int16_t fx_num) +{ + FX *const fx = &g_Effects[fx_num]; + fx->fall_speed += GRAVITY; + fx->pos.z += (fx->speed * Math_Cos(fx->rot.y)) >> W2V_SHIFT; + fx->pos.x += (fx->speed * Math_Sin(fx->rot.y)) >> W2V_SHIFT; + fx->pos.y = fx->pos.y + fx->fall_speed; + + int16_t room_num = fx->room_num; + const SECTOR *const sector = + Room_GetSector(fx->pos.x, fx->pos.y, fx->pos.z, &room_num); + const int32_t ceiling = + Room_GetCeiling(sector, fx->pos.x, fx->pos.y, fx->pos.z); + const int32_t height = + Room_GetHeight(sector, fx->pos.x, fx->pos.y, fx->pos.z); + + if (fx->pos.y >= height || fx->pos.y < ceiling) { + Effect_Kill(fx_num); + } else if (Lara_IsNearItem(&fx->pos, 200)) { + Lara_TakeDamage(10, true); + Effect_Kill(fx_num); + } else if (room_num != fx->room_num) { + Effect_NewRoom(fx_num, room_num); + } +} diff --git a/src/tr2/game/objects/effects/ember.h b/src/tr2/game/objects/effects/ember.h new file mode 100644 index 000000000..635e38733 --- /dev/null +++ b/src/tr2/game/objects/effects/ember.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void __cdecl Ember_Control(int16_t fx_num); diff --git a/src/tr2/game/objects/effects/flame.c b/src/tr2/game/objects/effects/flame.c new file mode 100644 index 000000000..79065565d --- /dev/null +++ b/src/tr2/game/objects/effects/flame.c @@ -0,0 +1,66 @@ +#include "game/objects/effects/flame.h" + +#include "game/collide.h" +#include "game/effects.h" +#include "game/lara/control.h" +#include "game/lara/misc.h" +#include "game/room.h" +#include "game/sound.h" +#include "global/funcs.h" +#include "global/types.h" +#include "global/vars.h" + +#include + +void __cdecl Flame_Control(const int16_t fx_num) +{ + FX *const fx = &g_Effects[fx_num]; + + fx->frame_num--; + if (fx->frame_num <= g_Objects[O_FLAME].mesh_count) { + fx->frame_num = 0; + } + + if (fx->counter >= 0) { + Sound_Effect(SFX_LOOP_FOR_SMALL_FIRES, &fx->pos, SPM_ALWAYS); + if (fx->counter != 0) { + fx->counter--; + } else if (Lara_IsNearItem(&fx->pos, 600)) { + Lara_TakeDamage(5, true); + const int32_t dx = g_LaraItem->pos.x - fx->pos.x; + const int32_t dz = g_LaraItem->pos.z - fx->pos.z; + const int32_t dist = SQUARE(dx) + SQUARE(dz); + if (dist < SQUARE(450)) { + fx->counter = 100; + Lara_CatchFire(); + } + } + } else { + fx->pos.x = 0; + fx->pos.y = 0; + if (fx->counter == -1) { + fx->pos.z = -100; + } else { + fx->pos.z = 0; + } + + Collide_GetJointAbsPosition(g_LaraItem, &fx->pos, -1 - fx->counter); + const int16_t room_num = g_LaraItem->room_num; + if (room_num != fx->room_num) { + Effect_NewRoom(fx_num, room_num); + } + + const int32_t water_height = + Room_GetWaterHeight(fx->pos.x, fx->pos.y, fx->pos.z, fx->room_num); + if ((water_height != NO_HEIGHT && fx->pos.y > water_height) + || g_Lara.water_status == LWS_CHEAT) { + fx->counter = 0; + Effect_Kill(fx_num); + g_Lara.burn = 0; + } else { + Sound_Effect(SFX_LOOP_FOR_SMALL_FIRES, &fx->pos, SPM_ALWAYS); + Lara_TakeDamage(7, false); + g_Lara.burn = 1; + } + } +} diff --git a/src/tr2/game/objects/effects/flame.h b/src/tr2/game/objects/effects/flame.h new file mode 100644 index 000000000..1ebb2b1d2 --- /dev/null +++ b/src/tr2/game/objects/effects/flame.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void __cdecl Flame_Control(int16_t fx_num); diff --git a/src/tr2/game/objects/general/body_part.c b/src/tr2/game/objects/general/body_part.c new file mode 100644 index 000000000..c0538f799 --- /dev/null +++ b/src/tr2/game/objects/general/body_part.c @@ -0,0 +1,84 @@ +#include "game/objects/general/body_part.h" + +#include "game/effects.h" +#include "game/lara/control.h" +#include "game/lara/misc.h" +#include "game/math.h" +#include "game/room.h" +#include "game/sound.h" +#include "global/types.h" +#include "global/vars.h" + +void __cdecl BodyPart_Control(const int16_t fx_num) +{ + FX *const fx = &g_Effects[fx_num]; + fx->rot.x += 5 * PHD_DEGREE; + fx->rot.z += 10 * PHD_DEGREE; + fx->pos.x += (fx->speed * Math_Sin(fx->rot.y)) >> W2V_SHIFT; + fx->pos.z += (fx->speed * Math_Cos(fx->rot.y)) >> W2V_SHIFT; + fx->pos.y += fx->fall_speed; + fx->fall_speed += GRAVITY; + + int16_t room_num = fx->room_num; + const SECTOR *const sector = + Room_GetSector(fx->pos.x, fx->pos.y, fx->pos.z, &room_num); + + if (!(g_Rooms[fx->room_num].flags & RF_UNDERWATER) + && (g_Rooms[room_num].flags & RF_UNDERWATER)) { + const int16_t fx_num = Effect_Create(fx->room_num); + if (fx_num != NO_ITEM) { + FX *const splash_fx = &g_Effects[fx_num]; + splash_fx->pos.x = fx->pos.x; + splash_fx->pos.y = fx->pos.y; + splash_fx->pos.z = fx->pos.z; + splash_fx->rot.y = 0; + splash_fx->speed = 0; + splash_fx->frame_num = 0; + splash_fx->object_id = O_SPLASH; + } + } + + const int32_t ceiling = + Room_GetCeiling(sector, fx->pos.x, fx->pos.y, fx->pos.z); + if (fx->pos.y < ceiling) { + fx->pos.y = ceiling; + fx->fall_speed = -fx->fall_speed; + } + + const int32_t height = + Room_GetHeight(sector, fx->pos.x, fx->pos.y, fx->pos.z); + if (fx->pos.y >= height) { + if (fx->counter) { + fx->speed = 0; + fx->frame_num = 0; + fx->counter = 0; + fx->object_id = O_EXPLOSION; + fx->shade = HIGH_LIGHT; + Sound_Effect(SFX_EXPLOSION1, &fx->pos, SPM_NORMAL); + } else { + Effect_Kill(fx_num); + } + return; + } + + if (Lara_IsNearItem(&fx->pos, 2 * fx->counter)) { + Lara_TakeDamage(fx->counter, true); + + if (fx->counter == 0) { + fx->speed = 0; + fx->frame_num = 0; + fx->counter = 0; + fx->object_id = O_EXPLOSION; + fx->shade = HIGH_LIGHT; + Sound_Effect(SFX_EXPLOSION1, &fx->pos, SPM_NORMAL); + g_Lara.spaz_effect_count = 5; + g_Lara.spaz_effect = fx; + } else { + Effect_Kill(fx_num); + } + } + + if (room_num != fx->room_num) { + Effect_NewRoom(fx_num, room_num); + } +} diff --git a/src/tr2/game/objects/general/body_part.h b/src/tr2/game/objects/general/body_part.h new file mode 100644 index 000000000..2304f562d --- /dev/null +++ b/src/tr2/game/objects/general/body_part.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void __cdecl BodyPart_Control(int16_t fx_num); diff --git a/src/tr2/game/objects/general/door.c b/src/tr2/game/objects/general/door.c new file mode 100644 index 000000000..f0c2eaae5 --- /dev/null +++ b/src/tr2/game/objects/general/door.c @@ -0,0 +1,29 @@ +#include "game/objects/general/door.h" + +#include "game/items.h" +#include "game/lara/misc.h" +#include "global/funcs.h" +#include "global/vars.h" + +void __cdecl Door_Collision( + const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) +{ + ITEM *const item = &g_Items[item_num]; + + if (!Item_TestBoundsCollide(item, lara_item, coll->radius)) { + return; + } + + if (!Collide_TestCollision(item, lara_item)) { + return; + } + + if (coll->enable_baddie_push) { + Lara_Push( + item, lara_item, coll, + item->current_anim_state != item->goal_anim_state + ? coll->enable_spaz + : false, + true); + } +} diff --git a/src/tr2/game/objects/general/door.h b/src/tr2/game/objects/general/door.h new file mode 100644 index 000000000..f8c4b30f3 --- /dev/null +++ b/src/tr2/game/objects/general/door.h @@ -0,0 +1,7 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +void __cdecl Object_Collision_Trap( + int16_t item_num, ITEM *lara_item, COLL_INFO *coll); diff --git a/src/tr2/game/objects/general/final_level_counter.c b/src/tr2/game/objects/general/final_level_counter.c new file mode 100644 index 000000000..118f68eb1 --- /dev/null +++ b/src/tr2/game/objects/general/final_level_counter.c @@ -0,0 +1,90 @@ +#include "game/objects/general/final_level_counter.h" + +#include "game/creature.h" +#include "game/items.h" +#include "game/los.h" +#include "game/lot.h" +#include "global/vars.h" + +#include + +#define CUTSCENE_DELAY (5 * FRAMES_PER_SECOND) // = 150 + +static int16_t __cdecl M_FindBestBoss(void); +static void __cdecl M_ActivateLastBoss(void); +static void __cdecl M_PrepareCutscene(int16_t item_num); + +static int16_t __cdecl M_FindBestBoss(void) +{ + int32_t best_dist = 0; + int16_t best_item = g_FinalBossItem[0]; + for (int32_t i = 0; i < g_FinalBossCount; i++) { + const ITEM *const item = &g_Items[g_FinalBossItem[i]]; + + GAME_VECTOR start; + start.pos.x = g_LaraItem->pos.x; + start.pos.y = g_LaraItem->pos.y - STEP_L * 2; + start.pos.z = g_LaraItem->pos.z; + start.room_num = g_LaraItem->room_num; + + GAME_VECTOR target; + target.pos.x = item->pos.x; + target.pos.y = item->pos.y - STEP_L * 2; + target.pos.z = item->pos.z; + target.room_num = item->room_num; + + g_LaraItem = g_LaraItem; + if (!LOS_Check(&start, &target)) { + const int32_t dx = (g_LaraItem->pos.x - item->pos.x) >> 6; + const int32_t dy = (g_LaraItem->pos.y - item->pos.y) >> 6; + const int32_t dz = (g_LaraItem->pos.z - item->pos.z) >> 6; + const int32_t dist = SQUARE(dx) + SQUARE(dy) + SQUARE(dz); + if (dist < best_dist) { + best_dist = dist; + best_item = g_FinalBossItem[i]; + } + } + } + return best_item; +} + +static void __cdecl M_ActivateLastBoss(void) +{ + const int16_t item_num = M_FindBestBoss(); + ITEM *const item = &g_Items[item_num]; + item->touch_bits = 0; + item->status = IS_ACTIVE; + item->mesh_bits = 0xFFFF1FFF; + Item_AddActive(item_num); + LOT_EnableBaddieAI(item_num, true); + g_FinalBossActive = 1; +} + +static void __cdecl M_PrepareCutscene(const int16_t item_num) +{ + ITEM *const item = &g_Items[item_num]; + Creature_Kill(item, 0, 0, LA_EXTRA_FINAL_ANIM); + + g_Camera.type = CAM_CINEMATIC; + g_Lara.mesh_ptrs[LM_HAND_R] = + g_Meshes[g_Objects[O_LARA].mesh_idx + LM_HAND_R]; + g_CineFrameIdx = 428; + g_CinePos.pos = item->pos; + g_CinePos.rot = item->rot; +} + +void __cdecl FinalLevelCounter_Control(const int16_t item_num) +{ + if (g_SaveGame.statistics.kills == g_FinalLevelCount + && !g_FinalBossActive) { + M_ActivateLastBoss(); + return; + } + + if (g_SaveGame.statistics.kills > g_FinalLevelCount) { + g_FinalBossActive++; + if (g_FinalBossActive == CUTSCENE_DELAY) { + M_PrepareCutscene(item_num); + } + } +} diff --git a/src/tr2/game/objects/general/final_level_counter.h b/src/tr2/game/objects/general/final_level_counter.h new file mode 100644 index 000000000..f47b73df4 --- /dev/null +++ b/src/tr2/game/objects/general/final_level_counter.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void FinalLevelCounter_Control(int16_t item_num); diff --git a/src/tr2/game/objects/traps/ember_emitter.c b/src/tr2/game/objects/traps/ember_emitter.c new file mode 100644 index 000000000..7588dba00 --- /dev/null +++ b/src/tr2/game/objects/traps/ember_emitter.c @@ -0,0 +1,24 @@ +#include "game/objects/traps/ember_emitter.h" + +#include "game/effects.h" +#include "game/random.h" +#include "game/sound.h" +#include "global/vars.h" + +void __cdecl EmberEmitter_Control(const int16_t item_num) +{ + const ITEM *const item = &g_Items[item_num]; + const int16_t fx_num = Effect_Create(item->room_num); + if (fx_num != NO_ITEM) { + FX *const fx = &g_Effects[fx_num]; + fx->pos.x = item->pos.x; + fx->pos.y = item->pos.y; + fx->pos.z = item->pos.z; + fx->rot.y = 2 * Random_GetControl() + 0x8000; + fx->speed = Random_GetControl() >> 10; + fx->fall_speed = Random_GetControl() / -200; + fx->frame_num = (-4 * Random_GetControl()) / 0x7FFF; + fx->object_id = O_EMBER; + Sound_Effect(SFX_SANDBAG_HIT, &item->pos, SPM_NORMAL); + } +} diff --git a/src/tr2/game/objects/traps/ember_emitter.h b/src/tr2/game/objects/traps/ember_emitter.h new file mode 100644 index 000000000..222a63c89 --- /dev/null +++ b/src/tr2/game/objects/traps/ember_emitter.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void __cdecl EmberEmitter_Control(int16_t item_num); diff --git a/src/tr2/game/objects/traps/flame_emitter.c b/src/tr2/game/objects/traps/flame_emitter.c new file mode 100644 index 000000000..f6e504431 --- /dev/null +++ b/src/tr2/game/objects/traps/flame_emitter.c @@ -0,0 +1,30 @@ +#include "game/objects/traps/flame_emitter.h" + +#include "game/effects.h" +#include "game/items.h" +#include "global/vars.h" + +void __cdecl FlameEmitter_Control(const int16_t item_num) +{ + ITEM *const item = &g_Items[item_num]; + + if (!Item_IsTriggerActive(item)) { + if (item->data != NULL) { + const int32_t flame_num = ((int32_t)(intptr_t)item->data) - 1; + Effect_Kill(flame_num); + item->data = NULL; + } + } else if (item->data == NULL) { + const int16_t fx_num = Effect_Create(item->room_num); + if (fx_num != NO_ITEM) { + FX *const fx = &g_Effects[fx_num]; + fx->pos.x = item->pos.x; + fx->pos.y = item->pos.y; + fx->pos.z = item->pos.z; + fx->frame_num = 0; + fx->object_id = O_FLAME; + fx->counter = 0; + } + item->data = (void *)(fx_num + 1); + } +} diff --git a/src/tr2/game/objects/traps/flame_emitter.h b/src/tr2/game/objects/traps/flame_emitter.h new file mode 100644 index 000000000..ce951889a --- /dev/null +++ b/src/tr2/game/objects/traps/flame_emitter.h @@ -0,0 +1,3 @@ +#include + +void __cdecl FlameEmitter_Control(int16_t item_num); diff --git a/src/tr2/game/objects/vars.c b/src/tr2/game/objects/vars.c new file mode 100644 index 000000000..b93914146 --- /dev/null +++ b/src/tr2/game/objects/vars.c @@ -0,0 +1,246 @@ +#include "game/objects/vars.h" + +#include "global/const.h" + +const GAME_OBJECT_ID g_EnemyObjects[] = { + // clang-format off + O_DOG, + O_CULT_1, + O_CULT_1A, + O_CULT_1B, + O_CULT_2, + O_CULT_3, + O_MOUSE, + O_DRAGON_FRONT, + O_SHARK, + O_EEL, + O_BIG_EEL, + O_BARRACUDA, + O_DIVER, + O_WORKER_1, + O_WORKER_2, + O_WORKER_3, + O_WORKER_4, + O_WORKER_5, + O_JELLY, + O_SPIDER, + O_BIG_SPIDER, + O_CROW, + O_TIGER, + O_BARTOLI, + O_XIAN_SPEARMAN, + O_XIAN_SPEARMAN_STATUE, + O_XIAN_KNIGHT, + O_XIAN_KNIGHT_STATUE, + O_YETI, + O_GIANT_YETI, + O_EAGLE, + O_BANDIT_1, + O_BANDIT_2, + O_BANDIT_2B, + O_SKIDMAN, + O_DINO, + NO_OBJECT, + // clang-format on +}; + +const GAME_OBJECT_ID g_AllyObjects[] = { + // clang-format off + O_LARA, + O_WINSTON, + O_MONK_1, + O_MONK_2, + O_DYING_MONK, + NO_OBJECT, + // Lara's social skills: still loading... + // clang-format on +}; + +const GAME_OBJECT_ID g_PickupObjects[] = { + // clang-format off + O_PISTOL_ITEM, + O_SHOTGUN_ITEM, + O_MAGNUM_ITEM, + O_UZI_ITEM, + O_HARPOON_ITEM, + O_M16_ITEM, + O_GRENADE_ITEM, + O_PISTOL_AMMO_ITEM, + O_SHOTGUN_AMMO_ITEM, + O_MAGNUM_AMMO_ITEM, + O_UZI_AMMO_ITEM, + O_HARPOON_AMMO_ITEM, + O_M16_AMMO_ITEM, + O_GRENADE_AMMO_ITEM, + O_SMALL_MEDIPACK_ITEM, + O_LARGE_MEDIPACK_ITEM, + O_FLARES_ITEM, + O_PUZZLE_ITEM_1, + O_PUZZLE_ITEM_2, + O_PUZZLE_ITEM_3, + O_PUZZLE_ITEM_4, + O_KEY_ITEM_1, + O_KEY_ITEM_2, + O_KEY_ITEM_3, + O_KEY_ITEM_4, + O_PICKUP_ITEM_1, + O_PICKUP_ITEM_2, + O_SECRET_1, + O_SECRET_2, + O_SECRET_3, + NO_OBJECT, + // clang-format on +}; + +const GAME_OBJECT_ID g_DoorObjects[] = { + // clang-format off + O_DOOR_TYPE_1, + O_DOOR_TYPE_2, + O_DOOR_TYPE_3, + O_DOOR_TYPE_4, + O_DOOR_TYPE_5, + O_DOOR_TYPE_6, + O_DOOR_TYPE_7, + O_DOOR_TYPE_8, + NO_OBJECT, + // clang-format on +}; + +const GAME_OBJECT_ID g_TrapdoorObjects[] = { + // clang-format off + O_TRAPDOOR_TYPE_1, + O_TRAPDOOR_TYPE_2, + O_TRAPDOOR_TYPE_3, + NO_OBJECT, + // clang-format on +}; + +const GAME_OBJECT_ID g_AnimObjects[] = { + // clang-format off + O_LARA_PISTOLS, + O_LARA_HAIR, + O_LARA_SHOTGUN, + O_LARA_MAGNUMS, + O_LARA_UZIS, + O_LARA_M16, + O_LARA_GRENADE, + O_LARA_HARPOON, + O_LARA_FLARE, + O_LARA_SKIDOO, + O_LARA_BOAT, + O_LARA_EXTRA, + // clang-format on +}; + +const GAME_OBJECT_ID g_NullObjects[] = { + // clang-format off + O_WATER_SPRITE, + O_SNOW_SPRITE, + O_TEXT_BOX, + O_FLARE_ITEM, + O_SPHERE_OF_DOOM_1, + O_SPHERE_OF_DOOM_2, + O_SPHERE_OF_DOOM_3, + O_DRAGON_BONES_2, + O_DRAGON_BONES_3, + O_HOT_LIQUID, + O_INV_BACKGROUND,O_FX_RESERVED,O_GONG_BONGER,O_EXPLOSION,O_SPLASH, + O_BUBBLES, + O_BUBBLE_EMITTER, + O_BLOOD, + O_DART_EFFECT, + O_FLARE_FIRE, + O_GLOW, + O_GLOW_RESERVED, + O_RICOCHET, + O_TWINKLE, + O_GUN_FLASH, + O_M16_FLASH, + O_MISSILE_HARPOON, + O_MISSILE_FLAME, + O_MISSILE_KNIFE, + O_GRENADE, + O_HARPOON_BOLT, + O_SKYBOX, + O_ALPHABET, + O_ASSAULT_DIGITS, + O_FINAL_LEVEL_COUNTER, + O_CUT_SHOTGUN, + O_EARTHQUAKE, + // clang-format on +}; + +const GAME_OBJECT_ID g_InvObjects[] = { + // clang-format off + O_PISTOL_OPTION, + O_SHOTGUN_OPTION, + O_MAGNUM_OPTION, + O_UZI_OPTION, + O_HARPOON_OPTION, + O_M16_OPTION, + O_GRENADE_OPTION, + O_PISTOL_AMMO_OPTION, + O_SHOTGUN_AMMO_OPTION, + O_MAGNUM_AMMO_OPTION, + O_UZI_AMMO_OPTION, + O_HARPOON_AMMO_OPTION, + O_M16_AMMO_OPTION, + O_GRENADE_AMMO_OPTION, + O_SMALL_MEDIPACK_OPTION, + O_LARGE_MEDIPACK_OPTION, + O_FLARES_OPTION, + O_PUZZLE_OPTION_1, + O_PUZZLE_OPTION_2, + O_PUZZLE_OPTION_3, + O_PUZZLE_OPTION_4, + O_KEY_OPTION_1, + O_KEY_OPTION_2, + O_KEY_OPTION_3, + O_KEY_OPTION_4, + O_PICKUP_OPTION_1, + O_PICKUP_OPTION_2, + O_DETAIL_OPTION, + O_SOUND_OPTION, + O_CONTROL_OPTION, + O_GAMMA_OPTION, + O_PASSPORT_OPTION, + O_COMPASS_OPTION, + O_PHOTO_OPTION, + NO_OBJECT, + // clang-format on +}; + +const GAME_OBJECT_PAIR g_ItemToInvObjectMap[] = { + // clang-format off + { O_COMPASS_ITEM, O_COMPASS_OPTION }, + { O_PISTOL_ITEM, O_PISTOL_OPTION }, + { O_SHOTGUN_ITEM, O_SHOTGUN_OPTION }, + { O_MAGNUM_ITEM, O_MAGNUM_OPTION }, + { O_UZI_ITEM, O_UZI_OPTION }, + { O_HARPOON_ITEM, O_HARPOON_OPTION }, + { O_M16_ITEM, O_M16_OPTION }, + { O_GRENADE_ITEM, O_GRENADE_OPTION }, + { O_PISTOL_AMMO_ITEM, O_PISTOL_AMMO_OPTION }, + { O_SHOTGUN_AMMO_ITEM, O_SHOTGUN_AMMO_OPTION }, + { O_MAGNUM_AMMO_ITEM, O_MAGNUM_AMMO_OPTION }, + { O_UZI_AMMO_ITEM, O_UZI_AMMO_OPTION }, + { O_HARPOON_AMMO_ITEM,O_HARPOON_AMMO_OPTION }, + { O_M16_AMMO_ITEM, O_M16_AMMO_OPTION }, + { O_GRENADE_AMMO_ITEM, O_GRENADE_AMMO_OPTION }, + { O_SMALL_MEDIPACK_ITEM, O_SMALL_MEDIPACK_OPTION }, + { O_LARGE_MEDIPACK_ITEM, O_LARGE_MEDIPACK_OPTION }, + { O_FLARE_ITEM, O_FLARES_OPTION }, + { O_FLARES_ITEM, O_FLARES_OPTION }, + { O_PUZZLE_ITEM_1, O_PUZZLE_OPTION_1 }, + { O_PUZZLE_ITEM_2, O_PUZZLE_OPTION_2 }, + { O_PUZZLE_ITEM_3, O_PUZZLE_OPTION_3 }, + { O_PUZZLE_ITEM_4, O_PUZZLE_OPTION_4 }, + { O_KEY_ITEM_1, O_KEY_OPTION_1 }, + { O_KEY_ITEM_2, O_KEY_OPTION_2 }, + { O_KEY_ITEM_3, O_KEY_OPTION_3 }, + { O_KEY_ITEM_4, O_KEY_OPTION_4 }, + { O_PICKUP_ITEM_1, O_PICKUP_OPTION_1 }, + { O_PICKUP_ITEM_2, O_PICKUP_OPTION_2 }, + { NO_OBJECT, NO_OBJECT }, + // clang-format on +}; diff --git a/src/tr2/game/objects/vars.h b/src/tr2/game/objects/vars.h new file mode 100644 index 000000000..563ed6647 --- /dev/null +++ b/src/tr2/game/objects/vars.h @@ -0,0 +1,14 @@ +#pragma once + +#include "game/objects/common.h" +#include "global/types.h" + +extern const GAME_OBJECT_ID g_EnemyObjects[]; +extern const GAME_OBJECT_ID g_AllyObjects[]; +extern const GAME_OBJECT_ID g_PickupObjects[]; +extern const GAME_OBJECT_ID g_DoorObjects[]; +extern const GAME_OBJECT_ID g_TrapdoorObjects[]; +extern const GAME_OBJECT_ID g_AnimObjects[]; +extern const GAME_OBJECT_ID g_NullObjects[]; +extern const GAME_OBJECT_ID g_InvObjects[]; +extern const GAME_OBJECT_PAIR g_ItemToInvObjectMap[]; diff --git a/src/tr2/game/objects/vehicles/boat.c b/src/tr2/game/objects/vehicles/boat.c new file mode 100644 index 000000000..6a2c11429 --- /dev/null +++ b/src/tr2/game/objects/vehicles/boat.c @@ -0,0 +1,848 @@ +#include "game/objects/vehicles/boat.h" + +#include "decomp/effects.h" +#include "game/effects.h" +#include "game/input.h" +#include "game/items.h" +#include "game/lara/look.h" +#include "game/math.h" +#include "game/objects/common.h" +#include "game/random.h" +#include "game/room.h" +#include "game/sound.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#define BOAT_FALL_ANIM 15 +#define BOAT_DEATH_ANIM 18 +#define BOAT_GETON_LW_ANIM 0 +#define BOAT_GETON_RW_ANIM 8 +#define BOAT_GETON_J_ANIM 6 +#define BOAT_GETON_START 1 + +#define BOAT_RADIUS 500 +#define BOAT_SIDE 300 +#define BOAT_FRONT 750 +#define BOAT_TIP (BOAT_FRONT + 250) +#define BOAT_MIN_SPEED 20 +#define BOAT_MAX_SPEED 90 +#define BOAT_SLOW_SPEED (BOAT_MAX_SPEED / 3) // = 30 +#define BOAT_FAST_SPEED (BOAT_MAX_SPEED + 50) // = 140 +#define BOAT_MAX_BACK (-20) +#define BOAT_ACCELERATION 5 +#define BOAT_BRAKE 5 +#define BOAT_REVERSE (-5) +#define BOAT_SLOWDOWN 1 +#define BOAT_WAKE 700 +#define BOAT_UNDO_TURN (PHD_DEGREE / 4) // = 45 +#define BOAT_TURN (PHD_DEGREE / 8) // = 22 +#define BOAT_MAX_TURN (PHD_DEGREE * 4) // = 728 +#define BOAT_SOUND_CEILING (WALL_L * 5) // = 5120 + +#define GONDOLA_SINK_SPEED 50 + +typedef enum { + BOAT_GETON = 0, + BOAT_STILL = 1, + BOAT_MOVING = 2, + BOAT_JUMP_R = 3, + BOAT_JUMP_L = 4, + BOAT_HIT = 5, + BOAT_FALL = 6, + BOAT_DEATH = 8, +} BOAT_ANIM; + +typedef enum { + GONDOLA_EMPTY = 0, + GONDOLA_FLOATING = 1, + GONDOLA_CRASH = 2, + GONDOLA_SINK = 3, + GONDOLA_LAND = 4, +} GONDOLA_ANIM; + +void __cdecl Boat_Initialise(const int16_t item_num) +{ + BOAT_INFO *boat_data = game_malloc(sizeof(BOAT_INFO), GBUF_TEMP_ALLOC); + boat_data->boat_turn = 0; + boat_data->left_fallspeed = 0; + boat_data->right_fallspeed = 0; + boat_data->tilt_angle = 0; + boat_data->extra_rotation = 0; + boat_data->water = 0; + boat_data->pitch = 0; + + ITEM *const boat = &g_Items[item_num]; + boat->data = boat_data; +} + +int32_t __cdecl Boat_CheckGeton( + const int16_t item_num, const COLL_INFO *const coll) +{ + if (g_Lara.gun_status != LGS_ARMLESS) { + return 0; + } + + ITEM *const boat = &g_Items[item_num]; + const ITEM *const lara = g_LaraItem; + const int32_t dist = + ((lara->pos.z - boat->pos.z) * Math_Cos(-boat->rot.y) + - (lara->pos.x - boat->pos.x) * Math_Sin(-boat->rot.y)) + >> W2V_SHIFT; + + if (dist > 200) { + return 0; + } + + int32_t geton = 0; + const int16_t rot = boat->rot.y - lara->rot.y; + + if (g_Lara.water_status == LWS_SURFACE || g_Lara.water_status == LWS_WADE) { + if (!(g_Input & IN_ACTION) || lara->gravity || boat->speed) { + return 0; + } + + if (rot > PHD_45 && rot < PHD_135) { + geton = 1; + } else if (rot > -PHD_135 && rot < -PHD_45) { + geton = 2; + } + } else if (g_Lara.water_status == LWS_ABOVE_WATER) { + int16_t fall_speed = lara->fall_speed; + if (fall_speed > 0) { + if (rot > -PHD_135 && rot < PHD_135 && lara->pos.y > boat->pos.y) { + geton = 3; + } + } else if (!fall_speed && rot > -PHD_135 && rot < PHD_135) { + if (lara->pos.x == boat->pos.x && lara->pos.y == boat->pos.y + && lara->pos.z == boat->pos.z) { + geton = 4; + } else { + geton = 3; + } + } + } + + if (!geton) { + return 0; + } + + if (!Item_TestBoundsCollide(boat, lara, coll->radius)) { + return 0; + } + + if (!Collide_TestCollision(boat, lara)) { + return 0; + } + + return geton; +} + +void __cdecl Boat_Collision( + const int16_t item_num, ITEM *const lara, COLL_INFO *const coll) +{ + if (lara->hit_points < 0 || g_Lara.skidoo != NO_ITEM) { + return; + } + + const int32_t geton = Boat_CheckGeton(item_num, coll); + if (!geton) { + coll->enable_baddie_push = 1; + Object_Collision(item_num, lara, coll); + return; + } + + g_Lara.skidoo = item_num; + + switch (geton) { + case 1: + lara->anim_num = g_Objects[O_LARA_BOAT].anim_idx + BOAT_GETON_RW_ANIM; + break; + case 2: + lara->anim_num = g_Objects[O_LARA_BOAT].anim_idx + BOAT_GETON_LW_ANIM; + break; + case 3: + lara->anim_num = g_Objects[O_LARA_BOAT].anim_idx + BOAT_GETON_J_ANIM; + break; + default: + lara->anim_num = g_Objects[O_LARA_BOAT].anim_idx + BOAT_GETON_START; + break; + } + + g_Lara.water_status = LWS_ABOVE_WATER; + + ITEM *const boat = &g_Items[item_num]; + + lara->pos.x = boat->pos.x; + lara->pos.y = boat->pos.y - 5; + lara->pos.z = boat->pos.z; + lara->gravity = 0; + lara->rot.x = 0; + lara->rot.y = boat->rot.y; + lara->rot.z = 0; + lara->speed = 0; + lara->fall_speed = 0; + lara->goal_anim_state = 0; + lara->current_anim_state = 0; + lara->frame_num = g_Anims[lara->anim_num].frame_base; + + if (lara->room_num != boat->room_num) { + Item_NewRoom(g_Lara.item_num, boat->room_num); + } + + Item_Animate(lara); + if (boat->status != IS_ACTIVE) { + Item_AddActive(item_num); + boat->status = IS_ACTIVE; + } +} + +int32_t __cdecl Boat_TestWaterHeight( + const ITEM *const item, const int32_t z_off, const int32_t x_off, + XYZ_32 *const pos) +{ + // clang-format off + pos->y = item->pos.y + + ((x_off * Math_Sin(item->rot.z)) >> W2V_SHIFT) + - ((z_off * Math_Sin(item->rot.x)) >> W2V_SHIFT); + // clang-format on + + const int32_t c = Math_Cos(item->rot.y); + const int32_t s = Math_Sin(item->rot.y); + pos->x = item->pos.x + ((x_off * c + z_off * s) >> W2V_SHIFT); + pos->z = item->pos.z + ((z_off * c - x_off * s) >> W2V_SHIFT); + + int16_t room_num = item->room_num; + Room_GetSector(pos->x, pos->y, pos->z, &room_num); + int32_t height = Room_GetWaterHeight(pos->x, pos->y, pos->z, room_num); + if (height == NO_HEIGHT) { + const SECTOR *const sector = + Room_GetSector(pos->x, pos->y, pos->z, &room_num); + height = Room_GetHeight(sector, pos->x, pos->y, pos->z); + if (height != NO_HEIGHT) { + return height; + } + } + + return height - 5; +} + +void __cdecl Boat_DoShift(const int32_t boat_num) +{ + ITEM *const boat = &g_Items[boat_num]; + int16_t item_num = g_Rooms[boat->room_num].item_num; + + while (item_num != NO_ITEM) { + ITEM *item = &g_Items[item_num]; + + if (item->object_id == O_BOAT && item_num != boat_num + && g_Lara.skidoo != item_num) { + const int32_t dx = item->pos.x - boat->pos.x; + const int32_t dz = item->pos.z - boat->pos.z; + const int32_t dist = SQUARE(dx) + SQUARE(dz); + + if (dist < SQUARE(BOAT_RADIUS * 2)) { + boat->pos.x = item->pos.x - SQUARE(BOAT_RADIUS * 2) * dx / dist; + boat->pos.z = item->pos.z - SQUARE(BOAT_RADIUS * 2) * dz / dist; + } + break; + } + + if (item->object_id == O_GONDOLA + && item->current_anim_state == GONDOLA_FLOATING) { + const int32_t c = Math_Cos(item->rot.y); + const int32_t s = Math_Sin(item->rot.y); + const int32_t ix = item->pos.x - ((s * STEP_L * 2) >> W2V_SHIFT); + const int32_t iz = item->pos.z - ((c * STEP_L * 2) >> W2V_SHIFT); + const int32_t dx = ix - boat->pos.x; + const int32_t dz = iz - boat->pos.z; + const int32_t dist = SQUARE(dx) + SQUARE(dz); + + if (dist < SQUARE(BOAT_RADIUS * 2)) { + if (boat->speed < BOAT_MAX_SPEED - 10) { + boat->pos.x = ix - SQUARE(BOAT_RADIUS * 2) * dx / dist; + boat->pos.z = iz - SQUARE(BOAT_RADIUS * 2) * dz / dist; + } else if (item->pos.y - boat->pos.y < WALL_L * 2) { + Sound_Effect(SFX_BOAT_INTO_WATER, &item->pos, SPM_NORMAL); + item->goal_anim_state = GONDOLA_CRASH; + } + } + } + + item_num = item->next_item; + } +} + +void __cdecl Boat_DoWakeEffect(const ITEM *const boat) +{ + g_MatrixPtr->_23 = 0; + S_CalculateLight(boat->pos.x, boat->pos.y, boat->pos.z, boat->room_num); + + const int16_t frame = + (Random_GetDraw() * g_Objects[O_WATER_SPRITE].mesh_count) >> 15; + + for (int32_t i = 0; i < 3; i++) { + const int16_t fx_num = Effect_Create(boat->room_num); + if (fx_num == NO_ITEM) { + continue; + } + + FX *const fx = &g_Effects[fx_num]; + fx->object_id = O_WATER_SPRITE; + fx->room_num = boat->room_num; + fx->frame_num = frame; + + const int32_t c = Math_Cos(boat->rot.y); + const int32_t s = Math_Sin(boat->rot.y); + const int32_t w = (1 - i) * BOAT_SIDE; + const int32_t h = BOAT_WAKE; + fx->pos.x = boat->pos.x + ((-c * w - s * h) >> W2V_SHIFT); + fx->pos.y = boat->pos.y; + fx->pos.z = boat->pos.z + ((-c * h + s * w) >> W2V_SHIFT); + fx->rot.y = boat->rot.y + (i << W2V_SHIFT) - PHD_90; + + fx->counter = 20; + fx->speed = boat->speed >> 2; + if (boat->speed < 64) { + fx->fall_speed = (Random_GetDraw() * (ABS(boat->speed) - 64)) >> 15; + } else { + fx->fall_speed = 0; + } + + fx->shade = g_LsAdder - 768; + CLAMPL(fx->shade, 0); + } +} + +int32_t __cdecl Boat_DoDynamics( + const int32_t height, int32_t fall_speed, int32_t *const y) +{ + if (height > *y) { + *y = fall_speed + *y; + if (*y > height) { + *y = height; + fall_speed = 0; + } else { + fall_speed += GRAVITY; + } + } else { + fall_speed += ((height - fall_speed - *y) >> 3); + CLAMPL(fall_speed, -20); + CLAMPG(*y, height); + } + + return fall_speed; +} + +int32_t __cdecl Boat_Dynamics(const int16_t boat_num) +{ + ITEM *const boat = &g_Items[boat_num]; + BOAT_INFO *const boat_data = (BOAT_INFO *)boat->data; + boat->rot.z -= boat_data->tilt_angle; + + XYZ_32 fl_old; + XYZ_32 bl_old; + XYZ_32 fr_old; + XYZ_32 br_old; + XYZ_32 f_old; + const int32_t hfl_old = + Boat_TestWaterHeight(boat, BOAT_FRONT, -BOAT_SIDE, &fl_old); + const int32_t hfr_old = + Boat_TestWaterHeight(boat, BOAT_FRONT, BOAT_SIDE, &fr_old); + const int32_t hbl_old = + Boat_TestWaterHeight(boat, -BOAT_FRONT, -BOAT_SIDE, &bl_old); + const int32_t hbr_old = + Boat_TestWaterHeight(boat, -BOAT_FRONT, BOAT_SIDE, &br_old); + const int32_t hf_old = Boat_TestWaterHeight(boat, BOAT_TIP, 0, &f_old); + XYZ_32 old = boat->pos; + CLAMPG(bl_old.y, hbl_old); + CLAMPG(br_old.y, hbr_old); + CLAMPG(fl_old.y, hfl_old); + CLAMPG(fr_old.y, hfr_old); + CLAMPG(f_old.y, hf_old); + + boat->rot.y += boat_data->extra_rotation + boat_data->boat_turn; + boat_data->tilt_angle = boat_data->boat_turn * 6; + + boat->pos.z += (boat->speed * Math_Cos(boat->rot.y)) >> W2V_SHIFT; + boat->pos.x += (boat->speed * Math_Sin(boat->rot.y)) >> W2V_SHIFT; + + int32_t slip = (Math_Sin(boat->rot.z) * 30) >> W2V_SHIFT; + if (!slip && boat->rot.z) { + slip = boat->rot.z > 0 ? 1 : -1; + } + boat->pos.z -= (slip * Math_Sin(boat->rot.y)) >> W2V_SHIFT; + boat->pos.x += (slip * Math_Cos(boat->rot.y)) >> W2V_SHIFT; + + slip = (Math_Sin(boat->rot.x) * 10) >> W2V_SHIFT; + if (!slip && boat->rot.x) { + slip = boat->rot.x > 0 ? 1 : -1; + } + + boat->pos.z -= (slip * Math_Cos(boat->rot.y)) >> W2V_SHIFT; + boat->pos.x = boat->pos.x - ((slip * Math_Sin(boat->rot.y)) >> W2V_SHIFT); + + XYZ_32 moved = { + .x = boat->pos.x, + .y = 0, + .z = boat->pos.z, + }; + Boat_DoShift(boat_num); + + int32_t rot = 0; + + XYZ_32 bl; + const int32_t hbl = + Boat_TestWaterHeight(boat, -BOAT_FRONT, -BOAT_SIDE, &bl); + if (hbl < bl_old.y - STEP_L / 2) { + rot = DoShift(boat, &bl, &bl_old); + } + + XYZ_32 br; + const int32_t hbr = Boat_TestWaterHeight(boat, -BOAT_FRONT, BOAT_SIDE, &br); + if (hbr < br_old.y - STEP_L / 2) { + rot += DoShift(boat, &br, &br_old); + } + + XYZ_32 fl; + const int32_t hfl = Boat_TestWaterHeight(boat, BOAT_FRONT, -BOAT_SIDE, &fl); + if (hfl < fl_old.y - STEP_L / 2) { + rot += DoShift(boat, &fl, &fl_old); + } + + XYZ_32 fr; + const int32_t hfr = Boat_TestWaterHeight(boat, BOAT_FRONT, BOAT_SIDE, &fr); + if (hfr < fr_old.y - STEP_L / 2) { + rot += DoShift(boat, &fr, &fr_old); + } + + if (!slip) { + XYZ_32 f; + const int32_t hf = Boat_TestWaterHeight(boat, BOAT_TIP, 0, &f); + if (hf < f_old.y - STEP_L / 2) { + DoShift(boat, &f, &f_old); + } + } + + int16_t room_num = boat->room_num; + const SECTOR *const sector = + Room_GetSector(boat->pos.x, boat->pos.y, boat->pos.z, &room_num); + int32_t height = + Room_GetWaterHeight(boat->pos.x, boat->pos.y, boat->pos.z, room_num); + if (height == NO_HEIGHT) { + height = Room_GetHeight(sector, boat->pos.x, boat->pos.y, boat->pos.z); + } + if (height < boat->pos.y - STEP_L / 2) { + DoShift(boat, &boat->pos, &old); + } + + boat_data->extra_rotation = rot; + + const int32_t collide = GetCollisionAnim(boat, &moved); + if (slip || collide) { + // clang-format off + const int32_t new_speed = ( + (boat->pos.z - old.z) * Math_Cos(boat->rot.y) + + (boat->pos.x - old.x) * Math_Sin(boat->rot.y) + ) >> W2V_SHIFT; + // clang-format on + + if (g_Lara.skidoo == boat_num) { + if (boat->speed > BOAT_MAX_SPEED + BOAT_ACCELERATION + && new_speed < boat->speed - 10) { + g_LaraItem->hit_points -= (boat->speed - new_speed) / 2; + g_LaraItem->hit_status = 1; + Sound_Effect(SFX_LARA_INJURY, &g_LaraItem->pos, SPM_NORMAL); + } + } + + if (slip) { + if (boat->speed <= BOAT_MAX_SPEED + 10) { + boat->speed = new_speed; + } + } else { + if (boat->speed > 0 && new_speed < boat->speed) { + boat->speed = new_speed; + } else if (boat->speed < 0 && new_speed > boat->speed) { + boat->speed = new_speed; + } + } + + CLAMPL(boat->speed, BOAT_MAX_BACK); + } + + return collide; +} + +int32_t __cdecl Boat_UserControl(ITEM *const boat) +{ + int32_t no_turn = 1; + + BOAT_INFO *const boat_data = (BOAT_INFO *)boat->data; + if (boat->pos.y < boat_data->water - STEP_L / 2 + || boat_data->water == NO_HEIGHT) { + return no_turn; + } + + if ((g_Input & IN_LOOK) && !boat->speed) { + Lara_LookUpDown(); + return no_turn; + } + + if (g_Input & IN_JUMP) { + return no_turn; + } + + if (((g_Input & IN_LEFT) && !(g_Input & IN_BACK)) + || ((g_Input & IN_RIGHT) && (g_Input & IN_BACK))) { + if (boat_data->boat_turn > 0) { + boat_data->boat_turn -= BOAT_UNDO_TURN; + } else { + boat_data->boat_turn -= BOAT_TURN; + CLAMPL(boat_data->boat_turn, -BOAT_MAX_TURN); + } + no_turn = 0; + } else if ( + ((g_Input & IN_RIGHT) && !(g_Input & IN_BACK)) + || ((g_Input & IN_LEFT) && (g_Input & IN_BACK))) { + if (boat_data->boat_turn < 0) { + boat_data->boat_turn += BOAT_UNDO_TURN; + } else { + boat_data->boat_turn += BOAT_TURN; + CLAMPG(boat_data->boat_turn, BOAT_MAX_TURN); + } + no_turn = 0; + } + + if (g_Input & IN_BACK) { + if (boat->speed > 0) { + boat->speed -= BOAT_BRAKE; + } else if (boat->speed > BOAT_MAX_BACK) { + boat->speed += BOAT_REVERSE; + } + } else if (g_Input & IN_FORWARD) { + int32_t max_speed; + if ((g_Input & IN_ACTION)) { + max_speed = BOAT_FAST_SPEED; + } else { + max_speed = (g_Input & IN_SLOW) ? BOAT_SLOW_SPEED : BOAT_MAX_SPEED; + } + + if (boat->speed < max_speed) { + boat->speed += BOAT_ACCELERATION / 2 + + BOAT_ACCELERATION * boat->speed / (2 * max_speed); + } else if (boat->speed > max_speed + BOAT_SLOWDOWN) { + boat->speed -= BOAT_SLOWDOWN; + } + } else if ( + boat->speed >= 0 && boat->speed < BOAT_MIN_SPEED + && ((g_Input & IN_LEFT) || (g_Input & IN_RIGHT))) { + boat->speed = BOAT_MIN_SPEED; + } else if (boat->speed > BOAT_SLOWDOWN) { + boat->speed -= BOAT_SLOWDOWN; + } else { + boat->speed = 0; + } + + return no_turn; +} + +void __cdecl Boat_Animation(const ITEM *const boat, const int32_t collide) +{ + ITEM *const lara = g_LaraItem; + const BOAT_INFO *const boat_data = (const BOAT_INFO *)boat->data; + + if (lara->hit_points <= 0) { + if (lara->current_anim_state == BOAT_DEATH) { + return; + } + lara->anim_num = g_Objects[O_LARA_BOAT].anim_idx + BOAT_DEATH_ANIM; + lara->frame_num = g_Anims[lara->anim_num].frame_base; + lara->goal_anim_state = BOAT_DEATH; + lara->current_anim_state = BOAT_DEATH; + return; + } + + if (boat->pos.y < boat_data->water - STEP_L / 2 && boat->fall_speed > 0) { + if (lara->current_anim_state == BOAT_FALL) { + return; + } + lara->anim_num = g_Objects[O_LARA_BOAT].anim_idx + BOAT_FALL_ANIM; + lara->frame_num = g_Anims[lara->anim_num].frame_base; + lara->goal_anim_state = BOAT_FALL; + lara->current_anim_state = BOAT_FALL; + return; + } + + if (collide) { + if (lara->current_anim_state == BOAT_HIT) { + return; + } + lara->anim_num = g_Objects[O_LARA_BOAT].anim_idx + collide; + lara->frame_num = g_Anims[lara->anim_num].frame_base; + lara->goal_anim_state = BOAT_HIT; + lara->current_anim_state = BOAT_HIT; + return; + } + + switch (lara->current_anim_state) { + case BOAT_STILL: + if (g_Input & IN_JUMP) { + if (g_Input & IN_RIGHT) { + lara->goal_anim_state = BOAT_JUMP_R; + } else if (g_Input & IN_LEFT) { + lara->goal_anim_state = BOAT_JUMP_L; + } + } + + if (boat->speed > 0) { + lara->goal_anim_state = BOAT_MOVING; + } + break; + + case BOAT_MOVING: + if (g_Input & IN_JUMP) { + if (g_Input & IN_RIGHT) { + lara->goal_anim_state = BOAT_JUMP_R; + } else if (g_Input & IN_LEFT) { + lara->goal_anim_state = BOAT_JUMP_L; + } + } else if (boat->speed <= 0) { + lara->goal_anim_state = BOAT_STILL; + } + break; + + case BOAT_FALL: + lara->goal_anim_state = BOAT_MOVING; + break; + } +} + +void __cdecl Boat_Control(const int16_t item_num) +{ + ITEM *const lara = g_LaraItem; + ITEM *const boat = &g_Items[item_num]; + BOAT_INFO *const boat_data = (BOAT_INFO *)boat->data; + + bool drive = false; + int32_t no_turn = 1; + int32_t collide = Boat_Dynamics(item_num); + + XYZ_32 fl; + XYZ_32 fr; + const int32_t hfl = Boat_TestWaterHeight(boat, BOAT_FRONT, -BOAT_SIDE, &fl); + const int32_t hfr = Boat_TestWaterHeight(boat, BOAT_FRONT, BOAT_SIDE, &fr); + + int16_t room_num = boat->room_num; + const SECTOR *const sector = + Room_GetSector(boat->pos.x, boat->pos.y, boat->pos.z, &room_num); + int32_t height = + Room_GetHeight(sector, boat->pos.x, boat->pos.y, boat->pos.z); + const int32_t ceiling = + Room_GetCeiling(sector, boat->pos.x, boat->pos.y, boat->pos.z); + if (g_Lara.skidoo == item_num) { + Room_TestTriggers(g_TriggerIndex, 0); + Room_TestTriggers(g_TriggerIndex, 1); + } + + const int32_t water_height = + Room_GetWaterHeight(boat->pos.x, boat->pos.y, boat->pos.z, room_num); + boat_data->water = water_height; + + if (g_Lara.skidoo == item_num && lara->hit_points > 0) { + switch (lara->current_anim_state) { + case BOAT_GETON: + case BOAT_JUMP_R: + case BOAT_JUMP_L: + break; + + default: + drive = true; + no_turn = Boat_UserControl(boat); + break; + } + } else if (boat->speed > BOAT_SLOWDOWN) { + boat->speed -= BOAT_SLOWDOWN; + } else { + boat->speed = 0; + } + + if (no_turn) { + if (boat_data->boat_turn < -BOAT_UNDO_TURN) { + boat_data->boat_turn += BOAT_UNDO_TURN; + } else if (boat_data->boat_turn > BOAT_UNDO_TURN) { + boat_data->boat_turn -= BOAT_UNDO_TURN; + } else { + boat_data->boat_turn = 0; + } + } + + boat->floor = height - 5; + if (boat_data->water == NO_HEIGHT) { + boat_data->water = height; + } else { + boat_data->water -= 5; + } + + boat_data->left_fallspeed = + Boat_DoDynamics(hfl, boat_data->left_fallspeed, &fl.y); + boat_data->right_fallspeed = + Boat_DoDynamics(hfr, boat_data->right_fallspeed, &fr.y); + boat->fall_speed = + Boat_DoDynamics(boat_data->water, boat->fall_speed, &boat->pos.y); + + height = (fr.y + fl.y) / 2; + + const int16_t x_rot = Math_Atan(BOAT_FRONT, boat->pos.y - height); + const int16_t z_rot = Math_Atan(BOAT_SIDE, height - fl.y); + boat->rot.x += (x_rot - boat->rot.x) / 2; + boat->rot.z += (z_rot - boat->rot.z) / 2; + + if (x_rot == 0 && ABS(boat->rot.x) < 4) { + boat->rot.x = 0; + } + if (z_rot == 0 && ABS(boat->rot.z) < 4) { + boat->rot.z = 0; + } + + if (g_Lara.skidoo == item_num) { + Boat_Animation(boat, collide); + + if (room_num != boat->room_num) { + Item_NewRoom(item_num, room_num); + Item_NewRoom(g_Lara.item_num, room_num); + } + + boat->rot.z += boat_data->tilt_angle; + lara->pos.x = boat->pos.x; + lara->pos.y = boat->pos.y; + lara->pos.z = boat->pos.z; + lara->rot.x = boat->rot.x; + lara->rot.y = boat->rot.y; + lara->rot.z = boat->rot.z; + + Item_Animate(lara); + + if (lara->hit_points > 0) { + boat->anim_num = g_Objects[O_BOAT].anim_idx + + (lara->anim_num - g_Objects[O_LARA_BOAT].anim_idx); + boat->frame_num = g_Anims[boat->anim_num].frame_base + + lara->frame_num - g_Anims[lara->anim_num].frame_base; + } + + g_Camera.target_elevation = -20 * PHD_DEGREE; + g_Camera.target_distance = 2 * WALL_L; + } else { + if (room_num != boat->room_num) { + Item_NewRoom(item_num, room_num); + } + boat->rot.z += boat_data->tilt_angle; + } + + const int32_t pitch = water_height - ceiling < BOAT_SOUND_CEILING + ? boat->speed * (water_height - ceiling) / BOAT_SOUND_CEILING + : boat->speed; + + boat_data->pitch += ((pitch - boat_data->pitch) >> 2); + if (boat->speed != 0 && water_height - 5 != boat->pos.y) { + Sound_Effect(SFX_BOAT_ENGINE, &boat->pos, SPM_NORMAL); + } else if (boat->speed > 20) { + Sound_Effect( + SFX_BOAT_MOVING, &boat->pos, + PITCH_SHIFT + + ((0x10000 - (BOAT_MAX_SPEED - boat_data->pitch) * 100) << 8)); + + } else if (drive) { + Sound_Effect( + SFX_BOAT_IDLE, &boat->pos, + PITCH_SHIFT + + ((0x10000 - (BOAT_MAX_SPEED - boat_data->pitch) * 100) << 8)); + } + + if (boat->speed && water_height - 5 == boat->pos.y) { + Boat_DoWakeEffect(boat); + } + + if (g_Lara.skidoo != item_num) { + return; + } + + if ((lara->current_anim_state == BOAT_JUMP_R + || lara->current_anim_state == BOAT_JUMP_L) + && lara->frame_num == g_Anims[lara->anim_num].frame_end) { + if (lara->current_anim_state == BOAT_JUMP_L) { + lara->rot.y -= PHD_90; + } else { + lara->rot.y += PHD_90; + } + + lara->anim_num = LA_JUMP_FORWARD; + lara->frame_num = g_Anims[lara->anim_num].frame_base; + lara->goal_anim_state = LS_FORWARD_JUMP; + lara->current_anim_state = LS_FORWARD_JUMP; + lara->gravity = 1; + lara->rot.x = 0; + lara->rot.z = 0; + lara->speed = 20; + lara->fall_speed = -40; + g_Lara.skidoo = NO_ITEM; + + const XYZ_32 pos = { + .x = lara->pos.x + ((360 * Math_Sin(lara->rot.y)) >> W2V_SHIFT), + .y = lara->pos.y - 90, + .z = lara->pos.z + ((360 * Math_Cos(lara->rot.y)) >> W2V_SHIFT), + }; + + int16_t room_num = lara->room_num; + const SECTOR *const sector = + Room_GetSector(pos.x, pos.y, pos.z, &room_num); + if (Room_GetHeight(sector, pos.x, pos.y, pos.z) >= pos.y - STEP_L) { + lara->pos.x = pos.x; + lara->pos.z = pos.z; + if (room_num != lara->room_num) { + Item_NewRoom(g_Lara.item_num, room_num); + } + } + + lara->pos.y = pos.y; + boat->anim_num = g_Objects[O_BOAT].anim_idx; + boat->frame_num = g_Anims[boat->anim_num].frame_base; + } +} + +void __cdecl Gondola_Control(const int16_t item_num) +{ + ITEM *const gondola = &g_Items[item_num]; + + switch (gondola->current_anim_state) { + case GONDOLA_FLOATING: + if (gondola->goal_anim_state == GONDOLA_CRASH) { + gondola->mesh_bits = 0xFF; + Effect_ExplodingDeath(item_num, 240, 0); + } + break; + + case GONDOLA_SINK: { + gondola->pos.y = gondola->pos.y + GONDOLA_SINK_SPEED; + int16_t room_num = gondola->room_num; + const SECTOR *const sector = Room_GetSector( + gondola->pos.x, gondola->pos.y, gondola->pos.z, &room_num); + const int32_t height = Room_GetHeight( + sector, gondola->pos.x, gondola->pos.y, gondola->pos.z); + gondola->floor = height; + + if (gondola->pos.y >= height) { + gondola->goal_anim_state = GONDOLA_LAND; + gondola->pos.y = height; + } + break; + } + } + + Item_Animate(gondola); + + if (gondola->status == IS_DEACTIVATED) { + Item_RemoveActive(item_num); + } +} diff --git a/src/tr2/game/objects/vehicles/boat.h b/src/tr2/game/objects/vehicles/boat.h new file mode 100644 index 000000000..855e63774 --- /dev/null +++ b/src/tr2/game/objects/vehicles/boat.h @@ -0,0 +1,19 @@ +#pragma once + +#include "global/types.h" + +#include + +void __cdecl Boat_Initialise(int16_t item_num); +int32_t __cdecl Boat_CheckGeton(int16_t item_num, const COLL_INFO *coll); +void __cdecl Boat_Collision(int16_t item_num, ITEM *lara, COLL_INFO *coll); +int32_t __cdecl Boat_TestWaterHeight( + const ITEM *item, int32_t z_off, int32_t x_off, XYZ_32 *pos); +void __cdecl Boat_DoShift(int32_t boat_num); +void __cdecl Boat_DoWakeEffect(const ITEM *boat); +int32_t __cdecl Boat_DoDynamics(int32_t height, int32_t fall_speed, int32_t *y); +int32_t __cdecl Boat_Dynamics(int16_t boat_num); +int32_t __cdecl Boat_UserControl(ITEM *boat); +void __cdecl Boat_Animation(const ITEM *boat, int32_t collide); +void __cdecl Boat_Control(int16_t item_num); +void __cdecl Gondola_Control(int16_t item_num); diff --git a/src/tr2/game/option/option.c b/src/tr2/game/option/option.c new file mode 100644 index 000000000..3e767908c --- /dev/null +++ b/src/tr2/game/option/option.c @@ -0,0 +1,91 @@ +#include "game/option/option.h" + +#include "game/input.h" +#include "global/funcs.h" +#include "global/vars.h" + +void __cdecl Option_DoInventory(INVENTORY_ITEM *const item) +{ + switch (item->object_id) { + case O_PASSPORT_OPTION: + Option_Passport(item); + break; + case O_COMPASS_OPTION: + Option_Compass(item); + break; + case O_DETAIL_OPTION: + Option_Detail(item); + break; + case O_SOUND_OPTION: + Option_Sound(item); + break; + case O_CONTROL_OPTION: + Option_Controls(item); + break; + case O_GAMMA_OPTION: + break; + + case O_PISTOL_OPTION: + case O_SHOTGUN_OPTION: + case O_MAGNUM_OPTION: + case O_UZI_OPTION: + case O_HARPOON_OPTION: + case O_M16_OPTION: + case O_GRENADE_OPTION: + case O_SMALL_MEDIPACK_OPTION: + case O_LARGE_MEDIPACK_OPTION: + case O_PUZZLE_OPTION_1: + case O_PUZZLE_OPTION_2: + case O_PUZZLE_OPTION_3: + case O_PUZZLE_OPTION_4: + case O_KEY_OPTION_1: + case O_KEY_OPTION_2: + case O_KEY_OPTION_3: + case O_KEY_OPTION_4: + case O_PICKUP_OPTION_1: + case O_PICKUP_OPTION_2: + g_InputDB |= IN_SELECT; + break; + + case O_PISTOL_AMMO_OPTION: + case O_SHOTGUN_AMMO_OPTION: + case O_MAGNUM_AMMO_OPTION: + case O_UZI_AMMO_OPTION: + case O_HARPOON_AMMO_OPTION: + case O_M16_AMMO_OPTION: + case O_GRENADE_AMMO_OPTION: + return; + + default: + if ((g_InputDB & IN_SELECT) || (g_InputDB & IN_DESELECT)) { + item->goal_frame = 0; + item->anim_direction = -1; + } + break; + } +} + +void __cdecl Option_ShutdownInventory(INVENTORY_ITEM *const item) +{ + switch (item->object_id) { + case O_PASSPORT_OPTION: + Option_Passport_Shutdown(); + break; + + case O_DETAIL_OPTION: + Option_Detail_Shutdown(); + break; + + case O_SOUND_OPTION: + Option_Sound_Shutdown(); + break; + + case O_CONTROL_OPTION: + Option_Controls_Shutdown(); + break; + + case O_COMPASS_OPTION: + Option_Compass_Shutdown(); + break; + } +} diff --git a/src/tr2/game/option/option.h b/src/tr2/game/option/option.h new file mode 100644 index 000000000..79908809f --- /dev/null +++ b/src/tr2/game/option/option.h @@ -0,0 +1,25 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Option_DoInventory(INVENTORY_ITEM *item); +void __cdecl Option_ShutdownInventory(INVENTORY_ITEM *item); + +void __cdecl Option_Passport(INVENTORY_ITEM *item); +void __cdecl Option_Passport_Shutdown(void); + +void __cdecl Option_Detail(INVENTORY_ITEM *item); +void __cdecl Option_Detail_Shutdown(void); + +void __cdecl Option_Sound(INVENTORY_ITEM *item); +void __cdecl Option_Sound_Shutdown(void); + +void __cdecl Option_Controls_FlashConflicts(void); +void __cdecl Option_Controls_DefaultConflict(void); +void __cdecl Option_Controls(INVENTORY_ITEM *item); +void __cdecl Option_Controls_Shutdown(void); +void __cdecl Option_Controls_ShowControls(void); +void __cdecl Option_Controls_UpdateText(void); + +void __cdecl Option_Compass(INVENTORY_ITEM *item); +void __cdecl Option_Compass_Shutdown(void); diff --git a/src/tr2/game/option/option_compass.c b/src/tr2/game/option/option_compass.c new file mode 100644 index 000000000..14917bd21 --- /dev/null +++ b/src/tr2/game/option/option_compass.c @@ -0,0 +1,34 @@ +#include "decomp/stats.h" +#include "game/input.h" +#include "game/option/option.h" +#include "game/requester.h" +#include "game/sound.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +void __cdecl Option_Compass(INVENTORY_ITEM *const item) +{ + char buffer[32]; + const int32_t sec = g_SaveGame.statistics.timer / FRAMES_PER_SECOND; + sprintf(buffer, "%02d:%02d:%02d", sec / 3600, sec / 60 % 60, sec % 60); + + if (g_CurrentLevel == LV_GYM) { + ShowGymStatsText(buffer, 1); + } else { + ShowStatsText(buffer, 1); + } + + if ((g_InputDB & IN_SELECT) || (g_InputDB & IN_DESELECT)) { + item->anim_direction = 1; + item->goal_frame = item->frames_total - 1; + } + + Sound_Effect(SFX_MENU_STOPWATCH, 0, SPM_ALWAYS); +} + +void Option_Compass_Shutdown(void) +{ + Requester_Shutdown(&g_StatsRequester); +} diff --git a/src/tr2/game/option/option_controls.c b/src/tr2/game/option/option_controls.c new file mode 100644 index 000000000..665166686 --- /dev/null +++ b/src/tr2/game/option/option_controls.c @@ -0,0 +1,31 @@ +#include "game/option/option.h" +#include "game/ui/widgets/controls_dialog.h" +#include "global/vars.h" + +static UI_WIDGET *m_ControlsDialog; +static UI_CONTROLS_CONTROLLER m_ControlsDialogController; + +void Option_Controls_Shutdown(void) +{ + if (m_ControlsDialog != NULL) { + m_ControlsDialog->free(m_ControlsDialog); + m_ControlsDialog = NULL; + } + Input_CheckConflictsWithDefaults(); +} + +void __cdecl Option_Controls(INVENTORY_ITEM *const item) +{ + if (m_ControlsDialog == NULL) { + m_ControlsDialog = + UI_ControlsDialog_Create(&m_ControlsDialogController); + } + + m_ControlsDialog->control(m_ControlsDialog); + if (m_ControlsDialogController.state == UI_CONTROLS_STATE_EXIT) { + Option_Controls_Shutdown(); + } else { + g_Input = 0; + g_InputDB = 0; + } +} diff --git a/src/tr2/game/option/option_detail.c b/src/tr2/game/option/option_detail.c new file mode 100644 index 000000000..8803c87b7 --- /dev/null +++ b/src/tr2/game/option/option_detail.c @@ -0,0 +1,85 @@ +#include "game/input.h" +#include "game/option/option.h" +#include "game/text.h" +#include "global/funcs.h" +#include "global/vars.h" + +static void M_InitText(void); +static void M_ShutdownText(void); + +static void M_InitText(void) +{ + g_DetailText[0] = + Text_Create(0, 50, 0, g_GF_GameStrings[GF_S_GAME_DETAIL_LOW]); + g_DetailText[1] = + Text_Create(0, 25, 0, g_GF_GameStrings[GF_S_GAME_DETAIL_MEDIUM]); + g_DetailText[2] = + Text_Create(0, 0, 0, g_GF_GameStrings[GF_S_GAME_DETAIL_HIGH]); + g_DetailText[3] = Text_Create(0, -32, 0, " "); + g_DetailText[4] = Text_Create( + 0, -30, 0, g_GF_GameStrings[GF_S_GAME_DETAIL_SELECT_DETAIL]); + Text_AddBackground(g_DetailText[4], 156, 0, 0, 0, 8, 0, 0, 0); + Text_AddOutline(g_DetailText[4], 1, 4, 0, 0); + Text_AddBackground(g_DetailText[g_DetailLevel], 148, 0, 0, 0, 8, 0, 0, 0); + Text_AddOutline(g_DetailText[g_DetailLevel], 1, 4, 0, 0); + Text_AddBackground(g_DetailText[3], 160, 107, 0, 0, 16, 0, 0, 0); + Text_AddOutline(g_DetailText[3], 1, 15, 0, 0); + + for (int32_t i = 0; i < 5; i++) { + Text_CentreH(g_DetailText[i], 1); + Text_CentreV(g_DetailText[i], 1); + } +} + +static void M_ShutdownText(void) +{ + for (int32_t i = 0; i < 5; i++) { + Text_Remove(g_DetailText[i]); + g_DetailText[i] = NULL; + } +} + +void __cdecl Option_Detail(INVENTORY_ITEM *const item) +{ + if (g_DetailText[0] == NULL) { + M_InitText(); + } + + if ((g_InputDB & IN_BACK) && g_DetailLevel > 0) { + Text_RemoveOutline(g_DetailText[g_DetailLevel]); + Text_RemoveBackground(g_DetailText[g_DetailLevel]); + g_DetailLevel--; + Text_AddOutline(g_DetailText[g_DetailLevel], 1, 4, 0, 0); + Text_AddBackground( + g_DetailText[g_DetailLevel], 148, 0, 0, 0, 8, 0, 0, 0); + } + if ((g_InputDB & IN_FORWARD) && g_DetailLevel < 2) { + Text_RemoveOutline(g_DetailText[g_DetailLevel]); + Text_RemoveBackground(g_DetailText[g_DetailLevel]); + g_DetailLevel++; + Text_AddOutline(g_DetailText[g_DetailLevel], 1, 4, 0, 0); + Text_AddBackground( + g_DetailText[g_DetailLevel], 148, 0, 0, 0, 8, 0, 0, 0); + } + + switch (g_DetailLevel) { + case 0: + g_PerspectiveDistance = 0; + break; + case 1: + g_PerspectiveDistance = 0x3000000; + break; + case 2: + g_PerspectiveDistance = 0x14000000; + break; + } + + if (g_InputDB & (IN_SELECT | IN_DESELECT)) { + M_ShutdownText(); + } +} + +void Option_Detail_Shutdown(void) +{ + M_ShutdownText(); +} diff --git a/src/tr2/game/option/option_passport.c b/src/tr2/game/option/option_passport.c new file mode 100644 index 000000000..8e28a8a8e --- /dev/null +++ b/src/tr2/game/option/option_passport.c @@ -0,0 +1,288 @@ +#include "game/input.h" +#include "game/option/option.h" +#include "game/requester.h" +#include "game/sound.h" +#include "game/text.h" +#include "global/funcs.h" +#include "global/vars.h" + +typedef enum { + PASSPORT_MODE_BROWSE = 0, + PASSPORT_MODE_LOAD_GAME = 1, + PASSPORT_MODE_SELECT_LEVEL = 2, +} PASSPORT_MODE; + +void __cdecl Option_Passport(INVENTORY_ITEM *const item) +{ + Text_Remove(g_Inv_ItemText[IT_NAME]); + g_Inv_ItemText[IT_NAME] = NULL; + + const int32_t frame = item->goal_frame - item->open_frame; + const int32_t page = frame % 5 == 0 ? frame / 5 : -1; + + if (g_Inv_Mode == INV_LOAD_MODE || g_Inv_Mode == INV_SAVE_MODE + || g_GameFlow.load_save_disabled) { + g_InputDB &= ~IN_LEFT; + g_InputDB &= ~IN_RIGHT; + } + + switch (page) { + case 0: + if (g_GameFlow.load_save_disabled) { + g_InputDB = IN_RIGHT; + break; + } + + if (g_PassportMode == PASSPORT_MODE_LOAD_GAME) { + Requester_SetSize(&g_LoadGameRequester, 10, -32); + + const int32_t select = + Requester_Display(&g_LoadGameRequester, 1, 1); + if (select != 0) { + if (select > 0) { + g_Inv_ExtraData[1] = select - 1; + } + g_PassportMode = PASSPORT_MODE_BROWSE; + } else if (g_InputDB & IN_RIGHT) { + Requester_Shutdown(&g_LoadGameRequester); + g_PassportMode = PASSPORT_MODE_BROWSE; + } else { + g_Input = 0; + g_InputDB = 0; + } + } else if (g_PassportMode == PASSPORT_MODE_BROWSE) { + if (g_SavedGames != 0 && g_Inv_Mode != INV_SAVE_MODE) { + if (g_PasswordText1 == NULL) { + g_PasswordText1 = Text_Create( + 0, -16, 0, + g_GF_GameStrings[GF_S_GAME_PASSPORT_LOAD_GAME]); + Text_AlignBottom(g_PasswordText1, true); + Text_CentreH(g_PasswordText1, true); + } + + Text_Remove(g_Inv_LevelText); + g_Inv_LevelText = NULL; + Text_Remove(g_Inv_TagText); + g_Inv_TagText = NULL; + Text_Remove(g_Inv_RingText); + g_Inv_RingText = NULL; + Text_Remove(g_Inv_ItemText[IT_NAME]); + g_Inv_ItemText[IT_NAME] = NULL; + + GetSavedGamesList(&g_LoadGameRequester); + Requester_SetHeading( + &g_LoadGameRequester, + g_GF_GameStrings[GF_S_GAME_PASSPORT_LOAD_GAME], 0, NULL, 0); + + g_PassportMode = PASSPORT_MODE_LOAD_GAME; + g_Input = 0; + g_InputDB = 0; + } else { + g_InputDB = IN_RIGHT; + } + } + break; + + case 1: + if (g_GameFlow.load_save_disabled) { + g_InputDB = IN_RIGHT; + break; + } + + if (g_PassportMode == PASSPORT_MODE_LOAD_GAME + || g_PassportMode == PASSPORT_MODE_SELECT_LEVEL) { + int32_t select; + if (g_PassportMode == PASSPORT_MODE_LOAD_GAME) { + Requester_SetSize(&g_LoadGameRequester, 10, -32); + select = Requester_Display(&g_LoadGameRequester, 1, 1); + } else { + Requester_SetSize(&g_SaveGameRequester, 10, -32); + select = Requester_Display(&g_SaveGameRequester, 1, 1); + } + + if (select != 0) { + if (select > 0) { + g_Inv_ExtraData[1] = select - 1; + } + g_PassportMode = PASSPORT_MODE_BROWSE; + } else if (g_InputDB & (IN_LEFT | IN_RIGHT)) { + if (g_PassportMode == PASSPORT_MODE_LOAD_GAME) { + Requester_Shutdown(&g_LoadGameRequester); + } else { + Requester_Shutdown(&g_SaveGameRequester); + } + g_PassportMode = PASSPORT_MODE_BROWSE; + } else { + g_Input = 0; + g_InputDB = 0; + } + } else if (g_PassportMode == PASSPORT_MODE_BROWSE) { + if (g_Inv_Mode == INV_DEATH_MODE) { + g_InputDB = item->anim_direction == -1 ? IN_LEFT : IN_RIGHT; + break; + } + + if (g_PasswordText1 == NULL) { + if (g_Inv_Mode == INV_TITLE_MODE || g_CurrentLevel == LV_GYM) { + g_PasswordText1 = Text_Create( + 0, -16, 0, + g_GF_GameStrings[GF_S_GAME_PASSPORT_NEW_GAME]); + } else { + g_PasswordText1 = Text_Create( + 0, -16, 0, + g_GF_GameStrings[GF_S_GAME_PASSPORT_SAVE_GAME]); + } + Text_AlignBottom(g_PasswordText1, true); + Text_CentreH(g_PasswordText1, true); + } + + if (g_Inv_Mode != INV_TITLE_MODE && g_CurrentLevel != LV_GYM) { + Text_Remove(g_Inv_LevelText); + g_Inv_LevelText = NULL; + Text_Remove(g_Inv_TagText); + g_Inv_TagText = NULL; + Text_Remove(g_Inv_RingText); + g_Inv_RingText = NULL; + Text_Remove(g_Inv_ItemText[IT_NAME]); + g_Inv_ItemText[IT_NAME] = NULL; + + GetSavedGamesList(&g_LoadGameRequester); + Requester_SetHeading( + &g_LoadGameRequester, + g_GF_GameStrings[GF_S_GAME_PASSPORT_SAVE_GAME], 0, NULL, 0); + + g_PassportMode = PASSPORT_MODE_LOAD_GAME; + g_Input = 0; + g_InputDB = 0; + } else if (g_GameFlow.play_any_level) { + Text_Remove(g_Inv_LevelText); + g_Inv_LevelText = NULL; + Text_Remove(g_Inv_TagText); + g_Inv_TagText = NULL; + Text_Remove(g_Inv_ItemText[IT_NAME]); + g_Inv_ItemText[IT_NAME] = NULL; + + Requester_Init(&g_SaveGameRequester); + GetValidLevelsList(&g_SaveGameRequester); + Requester_SetHeading( + &g_SaveGameRequester, + g_GF_GameStrings[GF_S_GAME_PASSPORT_SELECT_LEVEL], 0, NULL, + 0); + + g_PassportMode = PASSPORT_MODE_SELECT_LEVEL; + g_Input = 0; + g_InputDB = 0; + } else if (g_InputDB & IN_SELECT) { + g_Inv_ExtraData[1] = LV_FIRST; + } + } + break; + + case 2: + if (g_PasswordText1 == NULL) { + if (g_Inv_Mode == INV_TITLE_MODE) { + g_PasswordText1 = Text_Create( + 0, -16, 0, g_GF_GameStrings[GF_S_GAME_PASSPORT_EXIT_GAME]); + } else if (g_GameFlow.demo_version) { + g_PasswordText1 = Text_Create( + 0, -16, 0, g_GF_GameStrings[GF_S_GAME_PASSPORT_EXIT_DEMO]); + } else { + g_PasswordText1 = Text_Create( + 0, -16, 0, + g_GF_GameStrings[GF_S_GAME_PASSPORT_EXIT_TO_TITLE]); + } + Text_AlignBottom(g_PasswordText1, true); + Text_CentreH(g_PasswordText1, true); + } + break; + } + + if ((g_InputDB & IN_LEFT) + && (g_Inv_Mode != INV_DEATH_MODE || g_SavedGames != 0)) { + item->anim_direction = -1; + item->goal_frame -= 5; + + if (g_SavedGames == 0) { + if (item->goal_frame < item->open_frame + 5) { + item->goal_frame = item->open_frame + 5; + } else { + Text_Remove(g_PasswordText1); + g_PasswordText1 = NULL; + } + } else { + if (item->goal_frame < item->open_frame) { + item->goal_frame = item->open_frame; + } else { + Sound_Effect(SFX_MENU_PASSPORT, NULL, SPM_ALWAYS); + Text_Remove(g_PasswordText1); + g_PasswordText1 = NULL; + } + } + g_Input = 0; + g_InputDB = 0; + } + + if (g_InputDB & IN_RIGHT) { + item->anim_direction = 1; + item->goal_frame += 5; + + if (item->goal_frame > item->frames_total - 6) { + item->goal_frame = item->frames_total - 6; + } else { + Sound_Effect(SFX_MENU_PASSPORT, NULL, SPM_ALWAYS); + Text_Remove(g_PasswordText1); + g_PasswordText1 = NULL; + } + g_Input = 0; + g_InputDB = 0; + } + + if (g_InputDB & IN_DESELECT) { + if (g_Inv_Mode == INV_DEATH_MODE) { + g_Input = 0; + g_InputDB = 0; + } else { + if (page == 2) { + item->anim_direction = 1; + item->goal_frame = item->frames_total - 1; + } else { + item->anim_direction = -1; + item->goal_frame = 0; + } + Text_Remove(g_PasswordText1); + g_PasswordText1 = NULL; + } + } + + if (g_InputDB & IN_SELECT) { + g_Inv_ExtraData[0] = page; + if (page == 2) { + item->anim_direction = 1; + item->goal_frame = item->frames_total - 1; + } else { + item->anim_direction = -1; + item->goal_frame = 0; + } + Text_Remove(g_PasswordText1); + g_PasswordText1 = NULL; + } +} + +void Option_Passport_Shutdown(void) +{ + Text_Remove(g_Inv_ItemText[IT_NAME]); + g_Inv_ItemText[IT_NAME] = NULL; + Text_Remove(g_Inv_LevelText); + g_Inv_LevelText = NULL; + Text_Remove(g_Inv_RingText); + g_Inv_RingText = NULL; + Text_Remove(g_Inv_TagText); + g_Inv_TagText = NULL; + Text_Remove(g_PasswordText1); + g_PasswordText1 = NULL; + + Requester_Shutdown(&g_LoadGameRequester); + Requester_Shutdown(&g_SaveGameRequester); + + g_PassportMode = PASSPORT_MODE_BROWSE; +} diff --git a/src/tr2/game/option/option_sound.c b/src/tr2/game/option/option_sound.c new file mode 100644 index 000000000..9b9a7af2d --- /dev/null +++ b/src/tr2/game/option/option_sound.c @@ -0,0 +1,143 @@ +#include "game/input.h" +#include "game/music.h" +#include "game/option/option.h" +#include "game/sound.h" +#include "game/text.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#include + +static void M_InitText(void); +static void M_ShutdownText(void); + +static void M_InitText(void) +{ + CLAMPG(g_OptionMusicVolume, 10); + CLAMPG(g_OptionSoundVolume, 10); + + char text[8]; + sprintf(text, "| %2d", g_OptionMusicVolume); + g_SoundText[0] = Text_Create(0, 0, 0, text); + Text_AddBackground(g_SoundText[0], 128, 0, 0, 0, 8, INV_COLOR_BLACK, 0, 0); + Text_AddOutline(g_SoundText[0], 1, INV_COLOR_ORANGE, 0, 0); + + sprintf(text, "} %2d", g_OptionSoundVolume); + g_SoundText[1] = Text_Create(0, 25, 0, text); + + g_SoundText[2] = Text_Create(0, -32, 0, " "); + Text_AddBackground( + g_SoundText[2], 140, 85, 0, 0, 48, INV_COLOR_BLACK, 0, 0); + Text_AddOutline(g_SoundText[2], 1, INV_COLOR_BLUE, 0, 0); + + g_SoundText[3] = + Text_Create(0, -30, 0, g_GF_PCStrings[GF_S_PC_SET_VOLUMES]); + Text_AddBackground(g_SoundText[3], 136, 0, 0, 0, 8, INV_COLOR_BLACK, 0, 0); + Text_AddOutline(g_SoundText[3], 1, INV_COLOR_BLUE, 0, 0); + + for (int32_t i = 0; i < 4; i++) { + Text_CentreH(g_SoundText[i], true); + Text_CentreV(g_SoundText[i], true); + } +} + +static void M_ShutdownText(void) +{ + for (int32_t i = 0; i < 4; i++) { + Text_Remove(g_SoundText[i]); + g_SoundText[i] = NULL; + } +} + +void Option_Sound_Shutdown(void) +{ + M_ShutdownText(); +} + +void __cdecl Option_Sound(INVENTORY_ITEM *const item) +{ + char text[8]; + + if (g_SoundText[0] == NULL) { + M_InitText(); + } + + if ((g_InputDB & IN_FORWARD) && g_SoundOptionLine > 0) { + Text_RemoveOutline(g_SoundText[g_SoundOptionLine]); + Text_RemoveBackground(g_SoundText[g_SoundOptionLine]); + g_SoundOptionLine--; + Text_AddBackground( + g_SoundText[g_SoundOptionLine], 128, 0, 0, 0, 8, INV_COLOR_BLACK, 0, + 0); + Text_AddOutline( + g_SoundText[g_SoundOptionLine], 1, INV_COLOR_ORANGE, 0, 0); + } + + if ((g_InputDB & IN_BACK) && g_SoundOptionLine < 1) { + Text_RemoveOutline(g_SoundText[g_SoundOptionLine]); + Text_RemoveBackground(g_SoundText[g_SoundOptionLine]); + g_SoundOptionLine++; + Text_AddBackground( + g_SoundText[g_SoundOptionLine], 128, 0, 0, 0, 8, INV_COLOR_BLACK, 0, + 0); + Text_AddOutline( + g_SoundText[g_SoundOptionLine], 1, INV_COLOR_ORANGE, 0, 0); + } + + if (g_SoundOptionLine) { + bool changed = false; + if ((g_Input & IN_LEFT) && g_OptionSoundVolume > 0) { + g_Inv_IsOptionsDelay = 1; + g_Inv_OptionsDelayCounter = 10; + g_OptionSoundVolume--; + changed = true; + } else if ((g_Input & IN_RIGHT) && g_OptionSoundVolume < 10) { + g_Inv_IsOptionsDelay = 1; + g_Inv_OptionsDelayCounter = 10; + g_OptionSoundVolume++; + changed = true; + } + + if (changed) { + sprintf(text, "} %2d", g_OptionSoundVolume); + Text_ChangeText(g_SoundText[1], text); + if (g_OptionSoundVolume) { + Sound_SetMasterVolume(6 * g_OptionSoundVolume + 4); + } else { + Sound_SetMasterVolume(0); + } + + Sound_Effect(SFX_MENU_PASSPORT, NULL, SPM_ALWAYS); + } + } else { + bool changed = false; + if ((g_Input & IN_LEFT) && g_OptionMusicVolume > 0) { + g_OptionMusicVolume--; + changed = true; + } else if ((g_Input & IN_RIGHT) && g_OptionMusicVolume < 10) { + g_OptionMusicVolume++; + changed = true; + } + + if (changed) { + g_Inv_IsOptionsDelay = 1; + g_Inv_OptionsDelayCounter = 10; + sprintf(text, "| %2d", g_OptionMusicVolume); + Text_ChangeText(g_SoundText[0], text); + + if (g_OptionMusicVolume) { + Music_SetVolume(25 * g_OptionMusicVolume + 5); + } else { + Music_SetVolume(0); + } + + Sound_Effect(SFX_MENU_PASSPORT, NULL, SPM_ALWAYS); + } + } + + if (g_InputDB & (IN_SELECT | IN_DESELECT)) { + Option_Sound_Shutdown(); + } +} diff --git a/src/tr2/game/output.c b/src/tr2/game/output.c new file mode 100644 index 000000000..e4026ac35 --- /dev/null +++ b/src/tr2/game/output.c @@ -0,0 +1,4403 @@ +#include "game/output.h" + +#include "game/hwr.h" +#include "game/math.h" +#include "game/matrix.h" +#include "game/shell.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#define VBUF_VISIBLE(a, b, c) \ + (((a).ys - (b).ys) * ((c).xs - (b).xs) \ + >= ((c).ys - (b).ys) * ((a).xs - (b).xs)) +#define MAKE_TEX_ID(v, u) ((((v >> 16) & 0xFF) << 8) | ((u >> 16) & 0xFF)) +#define MAKE_Q_ID(g) ((g >> 16) & 0xFF) +#define MAKE_ZSORT(z) ((uint32_t)(z)) + +static D3DCOLOR M_ShadeLight(uint32_t shade); +static D3DCOLOR M_ShadeColor( + uint32_t red, uint32_t green, uint32_t blue, uint8_t alpha); +static D3DCOLOR M_ShadeLightColor( + uint32_t shade, uint32_t red, uint32_t green, uint32_t blue, uint8_t alpha); +static double M_CalculatePolyZ( + SORT_TYPE sort_type, double z0, double z1, double z2, double z3); + +static void __fastcall M_FlatA(int32_t y1, int32_t y2, uint8_t color_idx); +static void __fastcall M_TransA(int32_t y1, int32_t y2, uint8_t depth_q); +static void __fastcall M_GourA(int32_t y1, int32_t y2, uint8_t color_idx); +static void __fastcall M_GTMapA( + int32_t y1, int32_t y2, const uint8_t *tex_page); +static void __fastcall M_WGTMapA( + int32_t y1, int32_t y2, const uint8_t *tex_page); +static inline void M_ClipG( + VERTEX_INFO *const buf, const VERTEX_INFO *const vtx1, + const VERTEX_INFO *const vtx2, const float clip); +static inline void M_ClipGUV( + VERTEX_INFO *const buf, const VERTEX_INFO *const vtx1, + const VERTEX_INFO *const vtx2, const float clip); + +static D3DCOLOR M_ShadeColor( + uint32_t red, uint32_t green, uint32_t blue, uint8_t alpha) +{ + if (g_IsShadeEffect) { + red /= 2; + green = green * 7 / 8; + } + return RGBA_MAKE(red, green, blue, alpha); +} + +static D3DCOLOR M_ShadeLight(uint32_t shade) +{ + uint32_t value = (uint32_t)(0x1FFF - shade) >> 4; + CLAMPG(value, 0xFF); + return M_ShadeColor(value, value, value, 0xFF); +} + +static D3DCOLOR M_ShadeLightColor( + uint32_t shade, uint32_t red, uint32_t green, uint32_t blue, uint8_t alpha) +{ + CLAMPG(shade, 0x1FFF); + shade = 0x1FFF - shade; + red = (red * shade) >> 12; + green = (green * shade) >> 12; + blue = (blue * shade) >> 12; + CLAMPG(red, 0xFF); + CLAMPG(green, 0xFF); + CLAMPG(blue, 0xFF); + return M_ShadeColor(red, green, blue, alpha); +} + +static double M_CalculatePolyZ( + SORT_TYPE sort_type, double z0, double z1, double z2, double z3) +{ + double zv = 0.0; + switch (sort_type) { + case ST_AVG_Z: + zv = (z3 > 0.0) ? (z0 + z1 + z2 + z3) / 4.0 : (z0 + z1 + z2) / 3.0; + break; + + case ST_MAX_Z: + zv = z0; + CLAMPL(zv, z1); + CLAMPL(zv, z2); + if (z3 > 0.0) { + CLAMPL(zv, z3); + } + break; + + case ST_FAR_Z: + default: + zv = 1000000000.0; + break; + } + return zv; +} + +static void __fastcall M_FlatA(int32_t y1, int32_t y2, uint8_t color_idx) +{ + int32_t y_size = y2 - y1; + if (y_size <= 0) { + return; + } + + const int32_t stride = g_PhdScreenWidth; + const XBUF_X *xbuf = (const XBUF_X *)g_XBuffer + y1; + uint8_t *draw_ptr = g_PrintSurfacePtr + y1 * stride; + + while (y_size > 0) { + const int32_t x = xbuf->x1 / PHD_ONE; + const int32_t x_size = (xbuf->x2 / PHD_ONE) - x; + if (x_size > 0) { + memset(draw_ptr + x, color_idx, x_size); + } + y_size--; + xbuf++; + draw_ptr += stride; + } +} + +static void __fastcall M_TransA(int32_t y1, int32_t y2, uint8_t depth_q) +{ + int32_t y_size = y2 - y1; + // TODO: depth_q should be at most 32 here + if (y_size <= 0 || depth_q > 32) { + return; + } + + const int32_t stride = g_PhdScreenWidth; + const XBUF_X *xbuf = (const XBUF_X *)g_XBuffer + y1; + uint8_t *draw_ptr = g_PrintSurfacePtr + y1 * stride; + const DEPTHQ_ENTRY *qt = g_DepthQTable + depth_q; + + while (y_size > 0) { + const int32_t x = xbuf->x1 / PHD_ONE; + int32_t x_size = (xbuf->x2 / PHD_ONE) - x; + if (x_size <= 0) { + goto loop_end; + } + + uint8_t *line_ptr = draw_ptr + x; + while (x_size > 0) { + *line_ptr = qt->index[*line_ptr]; + line_ptr++; + x_size--; + } + + loop_end: + y_size--; + xbuf++; + draw_ptr += stride; + } +} + +static void __fastcall M_GourA(int32_t y1, int32_t y2, uint8_t color_idx) +{ + int32_t y_size = y2 - y1; + if (y_size <= 0) { + return; + } + + const int32_t stride = g_PhdScreenWidth; + const XBUF_XG *xbuf = (const XBUF_XG *)g_XBuffer + y1; + uint8_t *draw_ptr = g_PrintSurfacePtr + y1 * stride; + const GOURAUD_ENTRY *gt = g_GouraudTable + color_idx; + + while (y_size > 0) { + const int32_t x = xbuf->x1 / PHD_ONE; + int32_t x_size = (xbuf->x2 / PHD_ONE) - x; + if (x_size <= 0) { + goto loop_end; + } + + int32_t g = xbuf->g1; + const int32_t g_add = (xbuf->g2 - g) / x_size; + + uint8_t *line_ptr = draw_ptr + x; + while (x_size > 0) { + *line_ptr = gt->index[MAKE_Q_ID(g)]; + line_ptr++; + g += g_add; + x_size--; + } + + loop_end: + y_size--; + xbuf++; + draw_ptr += stride; + } +} + +static void __fastcall M_GTMapA(int32_t y1, int32_t y2, const uint8_t *tex_page) +{ + int32_t y_size = y2 - y1; + if (y_size <= 0) { + return; + } + + const int32_t stride = g_PhdScreenWidth; + const XBUF_XGUV *xbuf = (const XBUF_XGUV *)g_XBuffer + y1; + uint8_t *draw_ptr = g_PrintSurfacePtr + y1 * stride; + + while (y_size > 0) { + const int32_t x = xbuf->x1 / PHD_ONE; + int32_t x_size = (xbuf->x2 / PHD_ONE) - x; + if (x_size <= 0) { + goto loop_end; + } + + int32_t g = xbuf->g1; + int32_t u = xbuf->u1; + int32_t v = xbuf->v1; + const int32_t g_add = (xbuf->g2 - g) / x_size; + const int32_t u_add = (xbuf->u2 - u) / x_size; + const int32_t v_add = (xbuf->v2 - v) / x_size; + + uint8_t *line_ptr = draw_ptr + x; + while (x_size > 0) { + uint8_t color_idx = tex_page[MAKE_TEX_ID(v, u)]; + *line_ptr = g_DepthQTable[MAKE_Q_ID(g)].index[color_idx]; + line_ptr++; + g += g_add; + u += u_add; + v += v_add; + x_size--; + } + + loop_end: + y_size--; + xbuf++; + draw_ptr += stride; + } +} + +static void __fastcall M_WGTMapA( + int32_t y1, int32_t y2, const uint8_t *tex_page) +{ + int32_t y_size = y2 - y1; + if (y_size <= 0) { + return; + } + + const int32_t stride = g_PhdScreenWidth; + const XBUF_XGUV *xbuf = (const XBUF_XGUV *)g_XBuffer + y1; + uint8_t *draw_ptr = g_PrintSurfacePtr + y1 * stride; + + while (y_size > 0) { + const int32_t x = xbuf->x1 / PHD_ONE; + int32_t x_size = (xbuf->x2 / PHD_ONE) - x; + if (x_size <= 0) { + goto loop_end; + } + + int32_t g = xbuf->g1; + int32_t u = xbuf->u1; + int32_t v = xbuf->v1; + const int32_t g_add = (xbuf->g2 - g) / x_size; + const int32_t u_add = (xbuf->u2 - u) / x_size; + const int32_t v_add = (xbuf->v2 - v) / x_size; + + uint8_t *line_ptr = draw_ptr + x; + while (x_size > 0) { + const uint8_t color_idx = tex_page[MAKE_TEX_ID(v, u)]; + if (color_idx != 0) { + *line_ptr = g_DepthQTable[MAKE_Q_ID(g)].index[color_idx]; + } + line_ptr++; + g += g_add; + u += u_add; + v += v_add; + x_size--; + } + + loop_end: + y_size--; + xbuf++; + draw_ptr += stride; + } +} + +static inline void M_ClipG( + VERTEX_INFO *const buf, const VERTEX_INFO *const vtx1, + const VERTEX_INFO *const vtx2, const float clip) +{ + buf->rhw = vtx2->rhw + (vtx1->rhw - vtx2->rhw) * clip; + buf->g = vtx2->g + (vtx1->g - vtx2->g) * clip; +} + +static inline void M_ClipGUV( + VERTEX_INFO *const buf, const VERTEX_INFO *const vtx1, + const VERTEX_INFO *const vtx2, const float clip) +{ + buf->rhw = vtx2->rhw + (vtx1->rhw - vtx2->rhw) * clip; + buf->g = vtx2->g + (vtx1->g - vtx2->g) * clip; + buf->u = vtx2->u + (vtx1->u - vtx2->u) * clip; + buf->v = vtx2->v + (vtx1->v - vtx2->v) * clip; +} + +void __cdecl Output_Init( + int16_t x, int16_t y, int32_t width, int32_t height, int32_t near_z, + int32_t far_z, int16_t view_angle, int32_t screen_width, + int32_t screen_height) +{ + g_PhdWinMinX = x; + g_PhdWinMinY = y; + g_PhdWinMaxX = width - 1; + g_PhdWinMaxY = height - 1; + + g_PhdWinWidth = width; + g_PhdWinHeight = height; + + g_PhdWinCenterX = width / 2; + g_PhdWinCenterY = height / 2; + g_FltWinCenterX = width / 2; + g_FltWinCenterY = height / 2; + + g_PhdNearZ = near_z << 14; + g_PhdFarZ = far_z << W2V_SHIFT; + g_PhdViewDistance = far_z; + + g_PhdWinLeft = 0; + g_PhdWinTop = 0; + g_PhdWinRight = g_PhdWinMaxX; + g_PhdWinBottom = g_PhdWinMaxY; + + g_PhdWinRect.left = g_PhdWinMinX; + g_PhdWinRect.bottom = g_PhdWinMinY + g_PhdWinHeight; + g_PhdWinRect.top = g_PhdWinMinY; + g_PhdWinRect.right = g_PhdWinMinX + g_PhdWinWidth; + + g_PhdScreenWidth = screen_width; + g_PhdScreenHeight = screen_height; + + Output_AlterFOV(182 * view_angle); + Output_SetNearZ(g_PhdNearZ); + Output_SetFarZ(g_PhdFarZ); + + g_MatrixPtr = g_MatrixStack; + + switch (g_SavedAppSettings.render_mode) { + case RM_SOFTWARE: + g_PerspectiveDistance = g_SavedAppSettings.perspective_correct + ? SW_DETAIL_HIGH + : SW_DETAIL_MEDIUM; + g_Output_DrawObjectGT3 = Output_InsertObjectGT3; + g_Output_DrawObjectGT4 = Output_InsertObjectGT4; + g_Output_DrawObjectG3 = Output_InsertObjectG3; + g_Output_DrawObjectG4 = Output_InsertObjectG4; + g_Output_InsertSprite = Output_InsertSprite; + g_Output_InsertFlatRect = Output_InsertFlatRect; + g_Output_InsertLine = Output_InsertLine; + g_Output_InsertTrans8 = Output_InsertTrans8; + g_Output_InsertTransQuad = Output_InsertTransQuad; + break; + + case RM_HARDWARE: + if (g_SavedAppSettings.zbuffer) { + g_Output_DrawObjectGT3 = Output_InsertObjectGT3_ZBuffered; + g_Output_DrawObjectGT4 = Output_InsertObjectGT4_ZBuffered; + g_Output_DrawObjectG3 = Output_InsertObjectG3_ZBuffered; + g_Output_DrawObjectG4 = Output_InsertObjectG4_ZBuffered; + g_Output_InsertFlatRect = Output_InsertFlatRect_ZBuffered; + g_Output_InsertLine = Output_InsertLine_ZBuffered; + } else { + g_Output_DrawObjectGT3 = Output_InsertObjectGT3_Sorted; + g_Output_DrawObjectGT4 = Output_InsertObjectGT4_Sorted; + g_Output_DrawObjectG3 = Output_InsertObjectG3_Sorted; + g_Output_DrawObjectG4 = Output_InsertObjectG4_Sorted; + g_Output_InsertFlatRect = Output_InsertFlatRect_Sorted; + g_Output_InsertLine = Output_InsertLine_Sorted; + } + g_Output_InsertSprite = Output_InsertSprite_Sorted; + g_Output_InsertTrans8 = Output_InsertTrans8_Sorted; + g_Output_InsertTransQuad = Output_InsertTransQuad_Sorted; + break; + + default: + Shell_ExitSystem("unknown render mode"); + } +} + +void __cdecl Output_InsertPolygons(const int16_t *obj_ptr, const int32_t clip) +{ + g_FltWinLeft = g_PhdWinMinX; + g_FltWinTop = g_PhdWinMinY; + g_FltWinRight = (g_PhdWinMaxX + g_PhdWinMinX + 1); + g_FltWinBottom = (g_PhdWinMaxY + g_PhdWinMinY + 1); + g_FltWinCenterX = (g_PhdWinMinX + g_PhdWinCenterX); + g_FltWinCenterY = (g_PhdWinMinY + g_PhdWinCenterY); + + obj_ptr = Output_CalcObjectVertices(obj_ptr + 4); + if (obj_ptr) { + obj_ptr = Output_CalcVerticeLight(obj_ptr); + obj_ptr = g_Output_DrawObjectGT4(obj_ptr + 1, *obj_ptr, ST_AVG_Z); + obj_ptr = g_Output_DrawObjectGT3(obj_ptr + 1, *obj_ptr, ST_AVG_Z); + obj_ptr = g_Output_DrawObjectG4(obj_ptr + 1, *obj_ptr, ST_AVG_Z); + obj_ptr = g_Output_DrawObjectG3(obj_ptr + 1, *obj_ptr, ST_AVG_Z); + } +} + +void __cdecl Output_InsertPolygons_I( + const int16_t *const ptr, const int32_t clip) +{ + Matrix_Push(); + Matrix_Interpolate(); + Output_InsertPolygons(ptr, clip); + Matrix_Pop(); +} + +void __cdecl Output_InsertRoom(const int16_t *obj_ptr, int32_t is_outside) +{ + g_FltWinLeft = (g_PhdWinMinX + g_PhdWinLeft); + g_FltWinTop = (g_PhdWinMinY + g_PhdWinTop); + g_FltWinRight = (g_PhdWinRight + g_PhdWinMinX + 1); + g_FltWinBottom = (g_PhdWinBottom + g_PhdWinMinY + 1); + g_FltWinCenterX = (g_PhdWinMinX + g_PhdWinCenterX); + g_FltWinCenterY = (g_PhdWinMinY + g_PhdWinCenterY); + obj_ptr = Output_CalcRoomVertices(obj_ptr, is_outside ? 0 : 16); + obj_ptr = g_Output_DrawObjectGT4(obj_ptr + 1, *obj_ptr, ST_MAX_Z); + obj_ptr = g_Output_DrawObjectGT3(obj_ptr + 1, *obj_ptr, ST_MAX_Z); + Output_InsertRoomSprite(obj_ptr + 1, *obj_ptr); +} + +void __cdecl Output_InsertSkybox(const int16_t *obj_ptr) +{ + g_FltWinLeft = (g_PhdWinMinX + g_PhdWinLeft); + g_FltWinTop = (g_PhdWinMinY + g_PhdWinTop); + g_FltWinRight = (g_PhdWinRight + g_PhdWinMinX + 1); + g_FltWinBottom = (g_PhdWinBottom + g_PhdWinMinY + 1); + g_FltWinCenterX = (g_PhdWinMinX + g_PhdWinCenterX); + g_FltWinCenterY = (g_PhdWinMinY + g_PhdWinCenterY); + + obj_ptr = Output_CalcObjectVertices(obj_ptr + 4); + if (obj_ptr) { + if (g_SavedAppSettings.render_mode == RM_HARDWARE) { + HWR_EnableZBuffer(0, 0); + } + + obj_ptr = Output_CalcSkyboxLight(obj_ptr); + obj_ptr = g_Output_DrawObjectGT4(obj_ptr + 1, *obj_ptr, ST_FAR_Z); + obj_ptr = g_Output_DrawObjectGT3(obj_ptr + 1, *obj_ptr, ST_FAR_Z); + obj_ptr = g_Output_DrawObjectG4(obj_ptr + 1, *obj_ptr, ST_FAR_Z); + obj_ptr = g_Output_DrawObjectG3(obj_ptr + 1, *obj_ptr, ST_FAR_Z); + + if (g_SavedAppSettings.render_mode == RM_HARDWARE) { + HWR_EnableZBuffer(1, 1); + } + } +} + +const int16_t *__cdecl Output_CalcObjectVertices(const int16_t *obj_ptr) +{ + const double base_z = + g_SavedAppSettings.zbuffer ? 0.0 : (g_MidSort << (W2V_SHIFT + 8)); + uint8_t total_clip = 0xFF; + + obj_ptr++; // skip poly counter + const int32_t vtx_count = *obj_ptr++; + + for (int32_t i = 0; i < vtx_count; i++) { + PHD_VBUF *const vbuf = &g_PhdVBuf[i]; + + // clang-format off + const MATRIX *const mptr = g_MatrixPtr; + const double xv = ( + mptr->_00 * obj_ptr[0] + + mptr->_01 * obj_ptr[1] + + mptr->_02 * obj_ptr[2] + + mptr->_03 + ); + const double yv = ( + mptr->_10 * obj_ptr[0] + + mptr->_11 * obj_ptr[1] + + mptr->_12 * obj_ptr[2] + + mptr->_13 + ); + double zv = ( + mptr->_20 * obj_ptr[0] + + mptr->_21 * obj_ptr[1] + + mptr->_22 * obj_ptr[2] + + mptr->_23 + ); + // clang-format on + + vbuf->xv = xv; + vbuf->yv = yv; + + uint8_t clip_flags; + if (zv < g_FltNearZ) { + vbuf->zv = zv; + clip_flags = 0x80; + } else { + + if (zv >= g_FltFarZ) { + zv = g_FltFarZ; + vbuf->zv = zv; + } else { + vbuf->zv = zv + base_z; + } + + const double persp = g_FltPersp / zv; + vbuf->xs = persp * xv + g_FltWinCenterX; + vbuf->ys = persp * yv + g_FltWinCenterY; + vbuf->rhw = persp * g_FltRhwOPersp; + + clip_flags = 0x00; + if (vbuf->xs < g_FltWinLeft) { + clip_flags |= 1; + } else if (vbuf->xs > g_FltWinRight) { + clip_flags |= 2; + } + + if (vbuf->ys < g_FltWinTop) { + clip_flags |= 4; + } else if (vbuf->ys > g_FltWinBottom) { + clip_flags |= 8; + } + } + + vbuf->clip = clip_flags; + total_clip &= clip_flags; + obj_ptr += 3; + } + + return total_clip == 0 ? obj_ptr : 0; +} + +const int16_t *__cdecl Output_CalcSkyboxLight(const int16_t *obj_ptr) +{ + int32_t count = *obj_ptr++; + if (count > 0) { + obj_ptr += 3 * count; + } else if (count < 0) { + count = -count; + obj_ptr += count; + } + + for (int32_t i = 0; i < count; i++) { + g_PhdVBuf[i].g = 0xFFF; + } + + return obj_ptr; +} + +const int16_t *__cdecl Output_CalcVerticeLight(const int16_t *obj_ptr) +{ + int32_t vtx_count = *obj_ptr++; + + if (vtx_count > 0) { + if (g_LsDivider) { + // clang-format off + const MATRIX *const mptr = g_MatrixPtr; + int32_t xv = ( + g_LsVectorView.x * mptr->_00 + + g_LsVectorView.y * mptr->_10 + + g_LsVectorView.z * mptr->_20 + ) / g_LsDivider; + int32_t yv = ( + g_LsVectorView.x * mptr->_01 + + g_LsVectorView.y * mptr->_11 + + g_LsVectorView.z * mptr->_21 + ) / g_LsDivider; + int32_t zv = ( + g_LsVectorView.x * mptr->_02 + + g_LsVectorView.y * mptr->_12 + + g_LsVectorView.z * mptr->_22 + ) / g_LsDivider; + // clang-format on + + for (int32_t i = 0; i < vtx_count; i++) { + int16_t shade = g_LsAdder + + ((xv * obj_ptr[0] + yv * obj_ptr[1] + zv * obj_ptr[2]) + >> 16); + CLAMP(shade, 0, 0x1FFF); + + g_PhdVBuf[i].g = shade; + obj_ptr += 3; + } + } else { + int16_t shade = g_LsAdder; + CLAMP(shade, 0, 0x1FFF); + obj_ptr += 3 * vtx_count; + for (int32_t i = 0; i < vtx_count; i++) { + g_PhdVBuf[i].g = shade; + } + } + } else if (vtx_count < 0) { + for (int32_t i = 0; i < -vtx_count; i++) { + int16_t shade = g_LsAdder + *obj_ptr++; + CLAMP(shade, 0, 0x1FFF); + g_PhdVBuf[i].g = shade; + } + } + + return obj_ptr; +} + +const int16_t *__cdecl Output_CalcRoomVertices( + const int16_t *obj_ptr, int32_t far_clip) +{ + const double base_z = + g_ZBufferSurface ? 0.0 : (g_MidSort << (W2V_SHIFT + 8)); + int32_t vtx_count = *obj_ptr++; + + for (int32_t i = 0; i < vtx_count; i++) { + PHD_VBUF *const vbuf = &g_PhdVBuf[i]; + + // clang-format off + const MATRIX *const mptr = g_MatrixPtr; + const double xv = ( + mptr->_00 * obj_ptr[0] + + mptr->_01 * obj_ptr[1] + + mptr->_02 * obj_ptr[2] + + mptr->_03 + ); + const double yv = ( + mptr->_10 * obj_ptr[0] + + mptr->_11 * obj_ptr[1] + + mptr->_12 * obj_ptr[2] + + mptr->_13 + ); + const int32_t zv_int = ( + mptr->_20 * obj_ptr[0] + + mptr->_21 * obj_ptr[1] + + mptr->_22 * obj_ptr[2] + + mptr->_23 + ); + const double zv = zv_int; + // clang-format on + + vbuf->xv = xv; + vbuf->yv = yv; + vbuf->zv = zv; + + int16_t shade = obj_ptr[5]; + if (g_IsWaterEffect) { + shade += g_ShadesTable + [((uint8_t)g_WibbleOffset + + (uint8_t)g_RandomTable[(vtx_count - i) % WIBBLE_SIZE]) + % WIBBLE_SIZE]; + } + + uint16_t clip_flags = 0; + if (zv < g_FltNearZ) { + clip_flags = 0xFF80; + } else { + const double persp = g_FltPersp / zv; + const int32_t depth = zv_int >> W2V_SHIFT; + vbuf->zv += base_z; + + if (depth < FOG_END) { + if (depth > FOG_START) { + shade += depth - FOG_START; + } + vbuf->rhw = persp * g_FltRhwOPersp; + } else { + clip_flags = far_clip; + shade = 0x1FFF; + vbuf->zv = g_FltFarZ; + vbuf->rhw = 0.0; // *(int32_t*)0x00478054 + } + + double xs = xv * persp + g_FltWinCenterX; + double ys = yv * persp + g_FltWinCenterY; + + if (g_IsWibbleEffect && obj_ptr[4] >= 0) { + xs += + g_WibbleTable[(g_WibbleOffset + (uint8_t)ys) % WIBBLE_SIZE]; + ys += + g_WibbleTable[(g_WibbleOffset + (uint8_t)xs) % WIBBLE_SIZE]; + } + + if (xs < g_FltWinLeft) { + clip_flags |= 1; + } else if (xs > g_FltWinRight) { + clip_flags |= 2; + } + + if (ys < g_FltWinTop) { + clip_flags |= 4; + } else if (ys > g_FltWinBottom) { + clip_flags |= 8; + } + + vbuf->xs = xs; + vbuf->ys = ys; + clip_flags |= (~((uint8_t)(vbuf->zv / 0x155555.p0))) << 8; + } + + CLAMP(shade, 0, 0x1FFF); + vbuf->g = shade; + vbuf->clip = clip_flags; + obj_ptr += 6; + } + + return obj_ptr; +} + +void __cdecl Output_RotateLight(int16_t pitch, int16_t yaw) +{ + g_LsYaw = yaw; + g_LsPitch = pitch; + + int32_t xcos = Math_Cos(pitch); + int32_t xsin = Math_Sin(pitch); + int32_t wcos = Math_Cos(yaw); + int32_t wsin = Math_Sin(yaw); + + int32_t x = (xcos * wsin) >> W2V_SHIFT; + int32_t y = -xsin; + int32_t z = (xcos * wcos) >> W2V_SHIFT; + + const MATRIX *const m = &g_W2VMatrix; + g_LsVectorView.x = (m->_00 * x + m->_01 * y + m->_02 * z) >> W2V_SHIFT; + g_LsVectorView.y = (m->_10 * x + m->_11 * y + m->_12 * z) >> W2V_SHIFT; + g_LsVectorView.z = (m->_20 * x + m->_21 * y + m->_22 * z) >> W2V_SHIFT; +} + +void __cdecl Output_InitPolyList(void) +{ + g_SurfaceCount = 0; + g_Sort3DPtr = g_SortBuffer; + g_Info3DPtr = g_Info3DBuffer; + if (g_SavedAppSettings.render_mode != RM_SOFTWARE) { + g_HWR_VertexPtr = g_HWR_VertexBuffer; + } +} + +void __cdecl Output_SortPolyList(void) +{ + if (g_SurfaceCount) { + for (int32_t i = 0; i < g_SurfaceCount; i++) { + g_SortBuffer[i]._1 += i; + } + Output_QuickSort(0, g_SurfaceCount - 1); + } +} + +void __cdecl Output_QuickSort(int32_t left, int32_t right) +{ + int32_t compare = g_SortBuffer[(left + right) / 2]._1; + int32_t i = left; + int32_t j = right; + + do { + while ((i < right) && (g_SortBuffer[i]._1 > compare)) { + i++; + } + while ((left < j) && (compare > g_SortBuffer[j]._1)) { + j--; + } + if (i > j) { + break; + } + + SORT_ITEM tmp_item; + SWAP(g_SortBuffer[i], g_SortBuffer[j], tmp_item); + + i++; + j--; + } while (i <= j); + + if (left < j) { + Output_QuickSort(left, j); + } + if (i < right) { + Output_QuickSort(i, right); + } +} + +void __cdecl Output_PrintPolyList(uint8_t *surface_ptr) +{ + g_PrintSurfacePtr = surface_ptr; + + for (int32_t i = 0; i < g_SurfaceCount; i++) { + const int16_t *obj_ptr = (const int16_t *)g_SortBuffer[i]._0; + const int16_t poly_type = *obj_ptr++; + g_PolyDrawRoutines[poly_type](obj_ptr); + } +} + +void __cdecl Output_SetNearZ(int32_t near_z) +{ + g_PhdNearZ = near_z; + g_FltNearZ = near_z; + g_FltRhwONearZ = g_RhwFactor / g_FltNearZ; + g_FltPerspONearZ = g_FltPersp / g_FltNearZ; + + double res_z = 0.99 * g_FltNearZ * g_FltFarZ / (g_FltFarZ - g_FltNearZ); + g_FltResZ = res_z; + g_FltResZORhw = res_z / g_RhwFactor; + g_FltResZBuf = 0.005 + res_z / g_FltNearZ; +} + +void __cdecl Output_SetFarZ(int32_t far_z) +{ + g_PhdFarZ = far_z; + g_FltFarZ = far_z; + + double res_z = g_FltFarZ * g_FltNearZ * 0.99 / (g_FltFarZ - g_FltNearZ); + g_FltResZ = res_z; + g_FltResZORhw = res_z / g_RhwFactor; + g_FltResZBuf = 0.005 + res_z / g_FltNearZ; +} + +void __cdecl Output_AlterFOV(int16_t fov) +{ + fov /= 2; + + g_PhdPersp = g_PhdWinWidth / 2 * Math_Cos(fov) / Math_Sin(fov); + + g_FltPersp = g_PhdPersp; + g_FltRhwOPersp = g_RhwFactor / g_FltPersp; + g_FltPerspONearZ = g_FltPersp / g_FltNearZ; + + double window_aspect_ratio = 4.0 / 3.0; + if (!g_SavedAppSettings.fullscreen + && g_SavedAppSettings.aspect_mode == AM_16_9) { + window_aspect_ratio = 16.0 / 9.0; + } + + g_ViewportAspectRatio = + window_aspect_ratio / ((double)g_PhdWinWidth / (double)g_PhdWinHeight); +} + +void __cdecl Output_DrawPolyLine(const int16_t *obj_ptr) +{ + int32_t x1 = *obj_ptr++; + int32_t y1 = *obj_ptr++; + int32_t x2 = *obj_ptr++; + int32_t y2 = *obj_ptr++; + uint8_t lcolor = (uint8_t)*obj_ptr; + + if (x2 < x1) { + int32_t tmp; + SWAP(x1, x2, tmp); + SWAP(y1, y2, tmp); + } + + if (x2 < 0 || x1 > g_PhdWinMaxX) { + return; + } + + if (x1 < 0) { + y1 -= x1 * (y2 - y1) / (x2 - x1); + x1 = 0; + } + + if (x2 > g_PhdWinMaxX) { + y2 = y1 + (y2 - y1) * (g_PhdWinMaxX - x1) / (x2 - x1); + x2 = g_PhdWinMaxX; + } + + if (y2 < y1) { + int32_t tmp; + SWAP(x1, x2, tmp); + SWAP(y1, y2, tmp); + } + + if (y2 < 0 || y1 > g_PhdWinMaxY) { + return; + } + + if (y1 < 0) { + x1 -= y1 * (x2 - x1) / (y2 - y1); + y1 = 0; + } + + if (y2 > g_PhdWinMaxY) { + x2 = x1 + (x2 - x1) * (g_PhdWinMaxY - y1) / (y2 - y1); + y2 = g_PhdWinMaxY; + } + + int32_t x_size = x2 - x1; + int32_t y_size = y2 - y1; + uint8_t *draw_ptr = &g_PrintSurfacePtr[x1 + g_PhdScreenWidth * y1]; + + if (!x_size && !y_size) { + *draw_ptr = lcolor; + return; + } + + int32_t x_add = 0; + if (x_size < 0) { + x_add = -1; + x_size = -x_size; + } else { + x_add = 1; + } + + int32_t y_add; + if (y_size < 0) { + y_add = -g_PhdScreenWidth; + y_size = -y_size; + } else { + y_add = g_PhdScreenWidth; + } + + int32_t col_add; + int32_t row_add; + int32_t cols; + int32_t rows; + if (x_size >= y_size) { + col_add = x_add; + row_add = y_add; + cols = x_size + 1; + rows = y_size + 1; + } else { + col_add = y_add; + row_add = x_add; + cols = y_size + 1; + rows = x_size + 1; + } + + int32_t part_sum = 0; + int32_t part = PHD_ONE * rows / cols; + for (int32_t i = 0; i < cols; i++) { + part_sum += part; + *draw_ptr = lcolor; + draw_ptr += col_add; + if (part_sum >= PHD_ONE) { + draw_ptr += row_add; + part_sum -= PHD_ONE; + } + } +} + +void __cdecl Output_DrawPolyFlat(const int16_t *obj_ptr) +{ + if (Output_XGenX(obj_ptr + 1)) { + M_FlatA(g_XGenY1, g_XGenY2, *obj_ptr); + } +} + +void __cdecl Output_DrawPolyTrans(const int16_t *obj_ptr) +{ + if (Output_XGenX(obj_ptr + 1)) { + M_TransA(g_XGenY1, g_XGenY2, *obj_ptr); + } +} + +void __cdecl Output_DrawPolyGouraud(const int16_t *obj_ptr) +{ + if (Output_XGenXG(obj_ptr + 1)) { + M_GourA(g_XGenY1, g_XGenY2, *obj_ptr); + } +} + +void __cdecl Output_DrawPolyGTMap(const int16_t *obj_ptr) +{ + if (Output_XGenXGUV(obj_ptr + 1)) { + M_GTMapA(g_XGenY1, g_XGenY2, g_TexturePageBuffer8[*obj_ptr]); + } +} + +void __cdecl Output_DrawPolyWGTMap(const int16_t *obj_ptr) +{ + if (Output_XGenXGUV(obj_ptr + 1)) { + M_WGTMapA(g_XGenY1, g_XGenY2, g_TexturePageBuffer8[*obj_ptr]); + } +} + +void __cdecl Output_DrawPolyGTMapPersp(const int16_t *obj_ptr) +{ + if (Output_XGenXGUVPerspFP(obj_ptr + 1)) { + Output_GTMapPersp32FP( + g_XGenY1, g_XGenY2, g_TexturePageBuffer8[*obj_ptr]); + } +} + +void __cdecl Output_DrawPolyWGTMapPersp(const int16_t *obj_ptr) +{ + if (Output_XGenXGUVPerspFP(obj_ptr + 1)) { + Output_WGTMapPersp32FP( + g_XGenY1, g_XGenY2, g_TexturePageBuffer8[*obj_ptr]); + } +} + +int32_t __cdecl Output_XGenX(const int16_t *obj_ptr) +{ + int32_t pt_count = *obj_ptr++; + const XGEN_X *pt2 = (const XGEN_X *)obj_ptr; + const XGEN_X *pt1 = pt2 + (pt_count - 1); + + int32_t y_min = pt1->y; + int32_t y_max = pt1->y; + + while (pt_count--) { + const int32_t x1 = pt1->x; + const int32_t y1 = pt1->y; + const int32_t x2 = pt2->x; + const int32_t y2 = pt2->y; + pt1 = pt2++; + + if (y1 < y2) { + CLAMPG(y_min, y1); + const int32_t x_size = x2 - x1; + int32_t y_size = y2 - y1; + + XBUF_X *x_ptr = (XBUF_X *)g_XBuffer + y1; + const int32_t x_add = PHD_ONE * x_size / y_size; + int32_t x = x1 * PHD_ONE + (PHD_ONE - 1); + + while (y_size--) { + x += x_add; + x_ptr->x2 = x; + x_ptr++; + } + } else if (y2 < y1) { + CLAMPL(y_max, y1); + const int32_t x_size = x1 - x2; + int32_t y_size = y1 - y2; + + XBUF_X *x_ptr = (XBUF_X *)g_XBuffer + y2; + const int32_t x_add = PHD_ONE * x_size / y_size; + int32_t x = x2 * PHD_ONE + 1; + + while (y_size--) { + x += x_add; + x_ptr->x1 = x; + x_ptr++; + } + } + } + + if (y_min == y_max) { + return 0; + } + + g_XGenY1 = y_min; + g_XGenY2 = y_max; + return 1; +} + +int32_t __cdecl Output_XGenXG(const int16_t *obj_ptr) +{ + int32_t pt_count = *obj_ptr++; + const XGEN_XG *pt2 = (const XGEN_XG *)obj_ptr; + const XGEN_XG *pt1 = pt2 + (pt_count - 1); + + int32_t y_min = pt1->y; + int32_t y_max = pt1->y; + + while (pt_count--) { + const int32_t x1 = pt1->x; + const int32_t y1 = pt1->y; + const int32_t g1 = pt1->g; + const int32_t x2 = pt2->x; + const int32_t y2 = pt2->y; + const int32_t g2 = pt2->g; + pt1 = pt2++; + + if (y1 < y2) { + CLAMPG(y_min, y1); + const int32_t g_size = g2 - g1; + const int32_t x_size = x2 - x1; + int32_t y_size = y2 - y1; + + XBUF_XG *xg_ptr = (XBUF_XG *)g_XBuffer + y1; + const int32_t x_add = PHD_ONE * x_size / y_size; + const int32_t g_add = PHD_HALF * g_size / y_size; + int32_t x = x1 * PHD_ONE + (PHD_ONE - 1); + int32_t g = g1 * PHD_HALF; + + while (y_size--) { + x += x_add; + g += g_add; + xg_ptr->x2 = x; + xg_ptr->g2 = g; + xg_ptr++; + } + } else if (y2 < y1) { + CLAMPL(y_max, y1); + const int32_t g_size = g1 - g2; + const int32_t x_size = x1 - x2; + int32_t y_size = y1 - y2; + + XBUF_XG *xg_ptr = (XBUF_XG *)g_XBuffer + y2; + const int32_t x_add = PHD_ONE * x_size / y_size; + const int32_t g_add = PHD_HALF * g_size / y_size; + int32_t x = x2 * PHD_ONE + 1; + int32_t g = g2 * PHD_HALF; + + while (y_size--) { + x += x_add; + g += g_add; + xg_ptr->x1 = x; + xg_ptr->g1 = g; + xg_ptr++; + } + } + } + + if (y_min == y_max) { + return 0; + } + + g_XGenY1 = y_min; + g_XGenY2 = y_max; + return 1; +} + +int32_t __cdecl Output_XGenXGUV(const int16_t *obj_ptr) +{ + int32_t pt_count = *obj_ptr++; + const XGEN_XGUV *pt2 = (const XGEN_XGUV *)obj_ptr; + const XGEN_XGUV *pt1 = pt2 + (pt_count - 1); + + int32_t y_min = pt1->y; + int32_t y_max = pt1->y; + + while (pt_count--) { + const int32_t x1 = pt1->x; + const int32_t y1 = pt1->y; + const int32_t g1 = pt1->g; + const int32_t u1 = pt1->u; + const int32_t v1 = pt1->v; + const int32_t x2 = pt2->x; + const int32_t y2 = pt2->y; + const int32_t g2 = pt2->g; + const int32_t u2 = pt2->u; + const int32_t v2 = pt2->v; + pt1 = pt2++; + + if (y1 < y2) { + CLAMPG(y_min, y1); + const int32_t g_size = g2 - g1; + const int32_t u_size = u2 - u1; + const int32_t v_size = v2 - v1; + const int32_t x_size = x2 - x1; + int32_t y_size = y2 - y1; + + XBUF_XGUV *xguv_ptr = (XBUF_XGUV *)g_XBuffer + y1; + const int32_t x_add = PHD_ONE * x_size / y_size; + const int32_t g_add = PHD_HALF * g_size / y_size; + const int32_t u_add = PHD_HALF * u_size / y_size; + const int32_t v_add = PHD_HALF * v_size / y_size; + int32_t x = x1 * PHD_ONE + (PHD_ONE - 1); + int32_t g = g1 * PHD_HALF; + int32_t u = u1 * PHD_HALF; + int32_t v = v1 * PHD_HALF; + + while (y_size--) { + x += x_add; + g += g_add; + u += u_add; + v += v_add; + xguv_ptr->x2 = x; + xguv_ptr->g2 = g; + xguv_ptr->u2 = u; + xguv_ptr->v2 = v; + xguv_ptr++; + } + } else if (y2 < y1) { + CLAMPL(y_max, y1); + const int32_t g_size = g1 - g2; + const int32_t u_size = u1 - u2; + const int32_t v_size = v1 - v2; + const int32_t x_size = x1 - x2; + int32_t y_size = y1 - y2; + + XBUF_XGUV *xguv_ptr = (XBUF_XGUV *)g_XBuffer + y2; + const int32_t x_add = PHD_ONE * x_size / y_size; + const int32_t g_add = PHD_HALF * g_size / y_size; + const int32_t u_add = PHD_HALF * u_size / y_size; + const int32_t v_add = PHD_HALF * v_size / y_size; + int32_t x = x2 * PHD_ONE + 1; + int32_t g = g2 * PHD_HALF; + int32_t u = u2 * PHD_HALF; + int32_t v = v2 * PHD_HALF; + + while (y_size--) { + x += x_add; + g += g_add; + u += u_add; + v += v_add; + xguv_ptr->x1 = x; + xguv_ptr->g1 = g; + xguv_ptr->u1 = u; + xguv_ptr->v1 = v; + xguv_ptr++; + } + } + } + + if (y_min == y_max) { + return 0; + } + + g_XGenY1 = y_min; + g_XGenY2 = y_max; + return 1; +} + +int32_t __cdecl Output_XGenXGUVPerspFP(const int16_t *obj_ptr) +{ + int32_t pt_count = *obj_ptr++; + const XGEN_XGUVP *pt2 = (const XGEN_XGUVP *)obj_ptr; + const XGEN_XGUVP *pt1 = pt2 + (pt_count - 1); + + int32_t y_min = pt1->y; + int32_t y_max = pt1->y; + + while (pt_count--) { + const int32_t x1 = pt1->x; + const int32_t y1 = pt1->y; + const int32_t g1 = pt1->g; + const float u1 = pt1->u; + const float v1 = pt1->v; + + const float rhw1 = pt1->rhw; + const int32_t x2 = pt2->x; + const int32_t y2 = pt2->y; + const int32_t g2 = pt2->g; + const float u2 = pt2->u; + const float v2 = pt2->v; + const float rhw2 = pt2->rhw; + + pt1 = pt2++; + + if (y1 < y2) { + CLAMPG(y_min, y1); + const int32_t g_size = g2 - g1; + const float u_size = u2 - u1; + const float v_size = v2 - v1; + const float rhw_size = rhw2 - rhw1; + const int32_t x_size = x2 - x1; + int32_t y_size = y2 - y1; + + XBUF_XGUVP *xguv_ptr = (XBUF_XGUVP *)g_XBuffer + y1; + const int32_t x_add = PHD_ONE * x_size / y_size; + const int32_t g_add = PHD_HALF * g_size / y_size; + const float u_add = u_size / (float)y_size; + const float v_add = v_size / (float)y_size; + const float rhw_add = rhw_size / (float)y_size; + int32_t x = x1 * PHD_ONE + (PHD_ONE - 1); + int32_t g = g1 * PHD_HALF; + float u = u1; + float v = v1; + float rhw = rhw1; + + while (y_size--) { + x += x_add; + g += g_add; + u += u_add; + v += v_add; + rhw += rhw_add; + xguv_ptr->x2 = x; + xguv_ptr->g2 = g; + xguv_ptr->u2 = u; + xguv_ptr->v2 = v; + xguv_ptr->rhw2 = rhw; + xguv_ptr++; + } + } else if (y2 < y1) { + CLAMPL(y_max, y1); + const int32_t g_size = g1 - g2; + const float u_size = u1 - u2; + const float v_size = v1 - v2; + const float rhw_size = rhw1 - rhw2; + const int32_t x_size = x1 - x2; + int32_t y_size = y1 - y2; + + XBUF_XGUVP *xguv_ptr = (XBUF_XGUVP *)g_XBuffer + y2; + const int32_t x_add = PHD_ONE * x_size / y_size; + const int32_t g_add = PHD_HALF * g_size / y_size; + const float u_add = u_size / (float)y_size; + const float v_add = v_size / (float)y_size; + const float rhw_add = rhw_size / (float)y_size; + int32_t x = x2 * PHD_ONE + 1; + int32_t g = g2 * PHD_HALF; + float u = u2; + float v = v2; + float rhw = rhw2; + + while (y_size--) { + x += x_add; + g += g_add; + u += u_add; + v += v_add; + rhw += rhw_add; + xguv_ptr->x1 = x; + xguv_ptr->g1 = g; + xguv_ptr->u1 = u; + xguv_ptr->v1 = v; + xguv_ptr->rhw1 = rhw; + xguv_ptr++; + } + } + } + + if (y_min == y_max) { + return 0; + } + + g_XGenY1 = y_min; + g_XGenY2 = y_max; + return 1; +} + +void __cdecl Output_GTMapPersp32FP( + const int32_t y1, const int32_t y2, const uint8_t *const tex_page) +{ + int32_t y_size = y2 - y1; + if (y_size <= 0) { + return; + } + + const int32_t stride = g_PhdScreenWidth; + const XBUF_XGUVP *xbuf = (const XBUF_XGUVP *)g_XBuffer + y1; + uint8_t *draw_ptr = g_PrintSurfacePtr + y1 * stride; + + while (y_size > 0) { + const int32_t x = xbuf->x1 / PHD_ONE; + int32_t x_size = (xbuf->x2 / PHD_ONE) - x; + if (x_size <= 0) { + goto loop_end; + } + + int32_t g = xbuf->g1; + double u = xbuf->u1; + double v = xbuf->v1; + double rhw = xbuf->rhw1; + + const int32_t g_add = (xbuf->g2 - g) / x_size; + + int32_t u0 = PHD_HALF * u / rhw; + int32_t v0 = PHD_HALF * v / rhw; + + uint8_t *line_ptr = draw_ptr + x; + int32_t batch_size = 32; + + if (x_size >= batch_size) { + const double u_add = + (xbuf->u2 - u) / (double)x_size * (double)batch_size; + const double v_add = + (xbuf->v2 - v) / (double)x_size * (double)batch_size; + const double rhw_add = + (xbuf->rhw2 - rhw) / (double)x_size * (double)batch_size; + + while (x_size >= batch_size) { + u += u_add; + v += v_add; + rhw += rhw_add; + + const int32_t u1 = PHD_HALF * u / rhw; + const int32_t v1 = PHD_HALF * v / rhw; + + const int32_t u0_add = (u1 - u0) / batch_size; + const int32_t v0_add = (v1 - v0) / batch_size; + + if ((ABS(u0_add) + ABS(v0_add)) < (PHD_ONE / 2)) { + int32_t batch_counter = batch_size / 2; + while (batch_counter--) { + const uint8_t color_idx = tex_page[MAKE_TEX_ID(v0, u0)]; + const uint8_t color = + g_DepthQTable[MAKE_Q_ID(g)].index[color_idx]; + *line_ptr++ = color; + *line_ptr++ = color; + g += g_add * 2; + u0 += u0_add * 2; + v0 += v0_add * 2; + } + } else { + int32_t batch_counter = batch_size; + while (batch_counter--) { + const uint8_t color_idx = tex_page[MAKE_TEX_ID(v0, u0)]; + const uint8_t color = + g_DepthQTable[MAKE_Q_ID(g)].index[color_idx]; + *line_ptr++ = color; + g += g_add; + u0 += u0_add; + v0 += v0_add; + } + } + + u0 = u1; + v0 = v1; + x_size -= batch_size; + } + } + + if (x_size > 1) { + const int32_t u1 = PHD_HALF * xbuf->u2 / xbuf->rhw2; + const int32_t v1 = PHD_HALF * xbuf->v2 / xbuf->rhw2; + const int32_t u0_add = (u1 - u0) / x_size; + const int32_t v0_add = (v1 - v0) / x_size; + + batch_size = x_size & ~1; + x_size -= batch_size; + + if ((ABS(u0_add) + ABS(v0_add)) < (PHD_ONE / 2)) { + int32_t batch_counter = batch_size / 2; + while (batch_counter--) { + const uint8_t color_idx = tex_page[MAKE_TEX_ID(v0, u0)]; + const uint8_t color = + g_DepthQTable[MAKE_Q_ID(g)].index[color_idx]; + *line_ptr++ = color; + *line_ptr++ = color; + g += g_add * 2; + u0 += u0_add * 2; + v0 += v0_add * 2; + } + } else { + int32_t batch_counter = batch_size; + while (batch_counter--) { + const uint8_t color_idx = tex_page[MAKE_TEX_ID(v0, u0)]; + const uint8_t color = + g_DepthQTable[MAKE_Q_ID(g)].index[color_idx]; + *line_ptr++ = color; + g += g_add; + u0 += u0_add; + v0 += v0_add; + } + } + } + + if (x_size == 1) { + const uint8_t color_idx = tex_page[MAKE_TEX_ID(v0, u0)]; + const uint8_t color = g_DepthQTable[MAKE_Q_ID(g)].index[color_idx]; + *line_ptr = color; + } + + loop_end: + y_size--; + xbuf++; + draw_ptr += stride; + } +} + +void __cdecl Output_WGTMapPersp32FP( + int32_t y1, int32_t y2, const uint8_t *const tex_page) +{ + int32_t y_size = y2 - y1; + if (y_size <= 0) { + return; + } + + const int32_t stride = g_PhdScreenWidth; + const XBUF_XGUVP *xbuf = (const XBUF_XGUVP *)g_XBuffer + y1; + uint8_t *draw_ptr = g_PrintSurfacePtr + y1 * stride; + + while (y_size > 0) { + const int32_t x = xbuf->x1 / PHD_ONE; + int32_t x_size = (xbuf->x2 / PHD_ONE) - x; + if (x_size <= 0) { + goto loop_end; + } + + int32_t g = xbuf->g1; + double u = xbuf->u1; + double v = xbuf->v1; + double rhw = xbuf->rhw1; + + const int32_t g_add = (xbuf->g2 - g) / x_size; + + int32_t u0 = PHD_HALF * u / rhw; + int32_t v0 = PHD_HALF * v / rhw; + + uint8_t *line_ptr = draw_ptr + x; + int32_t batch_size = 32; + + if (x_size >= batch_size) { + const double u_add = + (xbuf->u2 - u) / (double)x_size * (double)batch_size; + const double v_add = + (xbuf->v2 - v) / (double)x_size * (double)batch_size; + const double rhw_add = + (xbuf->rhw2 - rhw) / (double)x_size * (double)batch_size; + + while (x_size >= batch_size) { + u += u_add; + v += v_add; + rhw += rhw_add; + + const int32_t u1 = PHD_HALF * u / rhw; + const int32_t v1 = PHD_HALF * v / rhw; + + const int32_t u0_add = (u1 - u0) / batch_size; + const int32_t v0_add = (v1 - v0) / batch_size; + + if ((ABS(u0_add) + ABS(v0_add)) < (PHD_ONE / 2)) { + int32_t batch_counter = batch_size / 2; + while (batch_counter--) { + const uint8_t color_idx = tex_page[MAKE_TEX_ID(v0, u0)]; + if (color_idx != 0) { + const uint8_t color = + g_DepthQTable[MAKE_Q_ID(g)].index[color_idx]; + line_ptr[0] = color; + line_ptr[1] = color; + } + line_ptr += 2; + g += g_add * 2; + u0 += u0_add * 2; + v0 += v0_add * 2; + } + } else { + int32_t batch_counter = batch_size; + while (batch_counter--) { + const uint8_t color_idx = tex_page[MAKE_TEX_ID(v0, u0)]; + if (color_idx != 0) { + const uint8_t color = + g_DepthQTable[MAKE_Q_ID(g)].index[color_idx]; + *line_ptr = color; + } + line_ptr++; + g += g_add; + u0 += u0_add; + v0 += v0_add; + } + } + + u0 = u1; + v0 = v1; + x_size -= batch_size; + } + } + + if (x_size > 1) { + const int32_t u1 = PHD_HALF * xbuf->u2 / xbuf->rhw2; + const int32_t v1 = PHD_HALF * xbuf->v2 / xbuf->rhw2; + const int32_t u0_add = (u1 - u0) / x_size; + const int32_t v0_add = (v1 - v0) / x_size; + + batch_size = x_size & ~1; + x_size -= batch_size; + + if ((ABS(u0_add) + ABS(v0_add)) < (PHD_ONE / 2)) { + int32_t batch_counter = batch_size / 2; + while (batch_counter--) { + const uint8_t color_idx = tex_page[MAKE_TEX_ID(v0, u0)]; + if (color_idx != 0) { + const uint8_t color = + g_DepthQTable[MAKE_Q_ID(g)].index[color_idx]; + line_ptr[0] = color; + line_ptr[1] = color; + } + line_ptr += 2; + g += g_add * 2; + u0 += u0_add * 2; + v0 += v0_add * 2; + }; + } else { + int32_t batch_counter = batch_size; + while (batch_counter--) { + const uint8_t color_idx = tex_page[MAKE_TEX_ID(v0, u0)]; + if (color_idx != 0) { + const uint8_t color = + g_DepthQTable[MAKE_Q_ID(g)].index[color_idx]; + *line_ptr = color; + } + line_ptr++; + g += g_add; + u0 += u0_add; + v0 += v0_add; + } + } + } + + if (x_size == 1) { + const uint8_t color_idx = tex_page[MAKE_TEX_ID(v0, u0)]; + if (color_idx != 0) { + const uint8_t color = + g_DepthQTable[MAKE_Q_ID(g)].index[color_idx]; + *line_ptr = color; + } + } + + loop_end: + y_size--; + xbuf++; + draw_ptr += stride; + } +} + +int32_t __cdecl Output_VisibleZClip( + const PHD_VBUF *const vtx0, const PHD_VBUF *const vtx1, + const PHD_VBUF *const vtx2) +{ + // clang-format off + return ( + vtx1->xv * (vtx0->yv * vtx2->zv - vtx0->zv * vtx2->yv) + + vtx1->yv * (vtx0->zv * vtx2->xv - vtx0->xv * vtx2->zv) + + vtx1->zv * (vtx0->xv * vtx2->yv - vtx0->yv * vtx2->xv) < 0.0 + ); + // clang-format on +} + +int32_t __cdecl Output_ZedClipper( + const int32_t vtx_count, const POINT_INFO *const points, + VERTEX_INFO *const vtx) +{ + int32_t j = 0; + const POINT_INFO *pts0 = &points[0]; + const POINT_INFO *pts1 = &points[vtx_count - 1]; + + for (int32_t i = 0; i < vtx_count; i++) { + int32_t diff0 = g_FltNearZ - pts0->zv; + int32_t diff1 = g_FltNearZ - pts1->zv; + if ((diff0 | diff1) >= 0) { + goto loop_end; + } + + if ((diff0 ^ diff1) < 0) { + double clip = diff0 / (pts1->zv - pts0->zv); + vtx[j].x = + (pts0->xv + (pts1->xv - pts0->xv) * clip) * g_FltPerspONearZ + + g_FltWinCenterX; + vtx[j].y = + (pts0->yv + (pts1->yv - pts0->yv) * clip) * g_FltPerspONearZ + + g_FltWinCenterY; + vtx[j].rhw = g_FltRhwONearZ; + vtx[j].g = pts0->g + (pts1->g - pts0->g) * clip; + vtx[j].u = (pts0->u + (pts1->u - pts0->u) * clip) * g_FltRhwONearZ; + vtx[j].v = (pts0->v + (pts1->v - pts0->v) * clip) * g_FltRhwONearZ; + j++; + } + + if (diff0 < 0) { + vtx[j].x = pts0->xs; + vtx[j].y = pts0->ys; + vtx[j].rhw = pts0->rhw; + vtx[j].g = pts0->g; + vtx[j].u = pts0->u * pts0->rhw; + vtx[j].v = pts0->v * pts0->rhw; + j++; + } + + loop_end: + pts1 = pts0++; + } + + return (j < 3) ? 0 : j; +} + +int32_t __cdecl Output_XYClipper(int32_t vtx_count, VERTEX_INFO *vtx) +{ + int32_t j; + VERTEX_INFO vtx_buf[20]; + const VERTEX_INFO *vtx1; + const VERTEX_INFO *vtx2; + + if (vtx_count < 3) { + return 0; + } + + // horizontal clip + j = 0; + vtx2 = &vtx[vtx_count - 1]; + for (int32_t i = 0; i < vtx_count; i++) { + vtx1 = vtx2; + vtx2 = &vtx[i]; + + if (vtx1->x < g_FltWinLeft) { + if (vtx2->x < g_FltWinLeft) { + continue; + } + const float clip = (g_FltWinLeft - vtx2->x) / (vtx1->x - vtx2->x); + vtx_buf[j].x = g_FltWinLeft; + vtx_buf[j].y = vtx2->y + (vtx1->y - vtx2->y) * clip; + j++; + } else if (vtx1->x > g_FltWinRight) { + if (vtx2->x > g_FltWinRight) { + continue; + } + const float clip = (g_FltWinRight - vtx2->x) / (vtx1->x - vtx2->x); + vtx_buf[j].x = g_FltWinRight; + vtx_buf[j].y = vtx2->y + (vtx1->y - vtx2->y) * clip; + j++; + } + + if (vtx2->x < g_FltWinLeft) { + const float clip = (g_FltWinLeft - vtx2->x) / (vtx1->x - vtx2->x); + vtx_buf[j].x = g_FltWinLeft; + vtx_buf[j].y = vtx2->y + (vtx1->y - vtx2->y) * clip; + j++; + } else if (vtx2->x > g_FltWinRight) { + const float clip = (g_FltWinRight - vtx2->x) / (vtx1->x - vtx2->x); + vtx_buf[j].x = g_FltWinRight; + vtx_buf[j].y = vtx2->y + (vtx1->y - vtx2->y) * clip; + j++; + } else { + vtx_buf[j].x = vtx2->x; + vtx_buf[j].y = vtx2->y; + j++; + } + } + + vtx_count = j; + if (vtx_count < 3) { + return 0; + } + + // vertical clip + j = 0; + vtx2 = &vtx_buf[vtx_count - 1]; + for (int32_t i = 0; i < vtx_count; i++) { + vtx1 = vtx2; + vtx2 = &vtx_buf[i]; + + if (vtx1->y < g_FltWinTop) { + if (vtx2->y < g_FltWinTop) { + continue; + } + const float clip = (g_FltWinTop - vtx2->y) / (vtx1->y - vtx2->y); + vtx[j].x = vtx2->x + (vtx1->x - vtx2->x) * clip; + vtx[j].y = g_FltWinTop; + j++; + } else if (vtx1->y > g_FltWinBottom) { + if (vtx2->y > g_FltWinBottom) { + continue; + } + const float clip = (g_FltWinBottom - vtx2->y) / (vtx1->y - vtx2->y); + vtx[j].x = vtx2->x + (vtx1->x - vtx2->x) * clip; + vtx[j].y = g_FltWinBottom; + j++; + } + + if (vtx2->y < g_FltWinTop) { + const float clip = (g_FltWinTop - vtx2->y) / (vtx1->y - vtx2->y); + vtx[j].x = vtx2->x + (vtx1->x - vtx2->x) * clip; + vtx[j].y = g_FltWinTop; + j++; + } else if (vtx2->y > g_FltWinBottom) { + const float clip = (g_FltWinBottom - vtx2->y) / (vtx1->y - vtx2->y); + vtx[j].x = vtx2->x + (vtx1->x - vtx2->x) * clip; + vtx[j].y = g_FltWinBottom; + j++; + } else { + vtx[j].x = vtx2->x; + vtx[j].y = vtx2->y; + j++; + } + } + + return (j < 3) ? 0 : j; +} + +int32_t __cdecl Output_XYGClipper(int32_t vtx_count, VERTEX_INFO *vtx) +{ + VERTEX_INFO vtx_buf[8]; + const VERTEX_INFO *vtx1; + const VERTEX_INFO *vtx2; + int32_t j; + + if (vtx_count < 3) { + return 0; + } + + // horizontal clip + j = 0; + vtx2 = &vtx[vtx_count - 1]; + for (int32_t i = 0; i < vtx_count; i++) { + vtx1 = vtx2; + vtx2 = &vtx[i]; + + if (vtx1->x < g_FltWinLeft) { + if (vtx2->x < g_FltWinLeft) { + continue; + } + const float clip = (g_FltWinLeft - vtx2->x) / (vtx1->x - vtx2->x); + vtx_buf[j].x = g_FltWinLeft; + vtx_buf[j].y = vtx2->y + (vtx1->y - vtx2->y) * clip; + M_ClipG(&vtx_buf[j++], vtx1, vtx2, clip); + } else if (vtx1->x > g_FltWinRight) { + if (vtx2->x > g_FltWinRight) { + continue; + } + const float clip = (g_FltWinRight - vtx2->x) / (vtx1->x - vtx2->x); + vtx_buf[j].x = g_FltWinRight; + vtx_buf[j].y = vtx2->y + (vtx1->y - vtx2->y) * clip; + M_ClipG(&vtx_buf[j++], vtx1, vtx2, clip); + } + + if (vtx2->x < g_FltWinLeft) { + const float clip = (g_FltWinLeft - vtx2->x) / (vtx1->x - vtx2->x); + vtx_buf[j].x = g_FltWinLeft; + vtx_buf[j].y = vtx2->y + (vtx1->y - vtx2->y) * clip; + M_ClipG(&vtx_buf[j++], vtx1, vtx2, clip); + } else if (vtx2->x > g_FltWinRight) { + const float clip = (g_FltWinRight - vtx2->x) / (vtx1->x - vtx2->x); + vtx_buf[j].x = g_FltWinRight; + vtx_buf[j].y = vtx2->y + (vtx1->y - vtx2->y) * clip; + M_ClipG(&vtx_buf[j++], vtx1, vtx2, clip); + } else { + vtx_buf[j++] = *vtx2; + } + } + + vtx_count = j; + if (vtx_count < 3) { + return 0; + } + + // vertical clip + j = 0; + vtx2 = &vtx_buf[vtx_count - 1]; + for (int32_t i = 0; i < vtx_count; i++) { + vtx1 = vtx2; + vtx2 = &vtx_buf[i]; + + if (vtx1->y < g_FltWinTop) { + if (vtx2->y < g_FltWinTop) { + continue; + } + const float clip = (g_FltWinTop - vtx2->y) / (vtx1->y - vtx2->y); + vtx[j].x = vtx2->x + (vtx1->x - vtx2->x) * clip; + vtx[j].y = g_FltWinTop; + M_ClipG(&vtx[j++], vtx1, vtx2, clip); + } else if (vtx1->y > g_FltWinBottom) { + if (vtx2->y > g_FltWinBottom) { + continue; + } + const float clip = (g_FltWinBottom - vtx2->y) / (vtx1->y - vtx2->y); + vtx[j].x = vtx2->x + (vtx1->x - vtx2->x) * clip; + vtx[j].y = g_FltWinBottom; + M_ClipG(&vtx[j++], vtx1, vtx2, clip); + } + + if (vtx2->y < g_FltWinTop) { + const float clip = (g_FltWinTop - vtx2->y) / (vtx1->y - vtx2->y); + vtx[j].x = vtx2->x + (vtx1->x - vtx2->x) * clip; + vtx[j].y = g_FltWinTop; + M_ClipG(&vtx[j++], vtx1, vtx2, clip); + } else if (vtx2->y > g_FltWinBottom) { + const float clip = (g_FltWinBottom - vtx2->y) / (vtx1->y - vtx2->y); + vtx[j].x = vtx2->x + (vtx1->x - vtx2->x) * clip; + vtx[j].y = g_FltWinBottom; + M_ClipG(&vtx[j++], vtx1, vtx2, clip); + } else { + vtx[j++] = *vtx2; + } + } + + return (j < 3) ? 0 : j; +} + +int32_t __cdecl Output_XYGUVClipper(int32_t vtx_count, VERTEX_INFO *const vtx) +{ + VERTEX_INFO vtx_buf[8]; + const VERTEX_INFO *vtx1; + const VERTEX_INFO *vtx2; + int32_t j; + + if (vtx_count < 3) { + return 0; + } + + // horizontal clip + j = 0; + vtx2 = &vtx[vtx_count - 1]; + for (int32_t i = 0; i < vtx_count; i++) { + vtx1 = vtx2; + vtx2 = &vtx[i]; + + if (vtx1->x < g_FltWinLeft) { + if (vtx2->x < g_FltWinLeft) { + continue; + } + float clip = (g_FltWinLeft - vtx2->x) / (vtx1->x - vtx2->x); + vtx_buf[j].x = g_FltWinLeft; + vtx_buf[j].y = vtx2->y + (vtx1->y - vtx2->y) * clip; + M_ClipGUV(&vtx_buf[j++], vtx1, vtx2, clip); + } else if (vtx1->x > g_FltWinRight) { + if (vtx2->x > g_FltWinRight) { + continue; + } + float clip = (g_FltWinRight - vtx2->x) / (vtx1->x - vtx2->x); + vtx_buf[j].x = g_FltWinRight; + vtx_buf[j].y = vtx2->y + (vtx1->y - vtx2->y) * clip; + M_ClipGUV(&vtx_buf[j++], vtx1, vtx2, clip); + } + + if (vtx2->x < g_FltWinLeft) { + float clip = (g_FltWinLeft - vtx2->x) / (vtx1->x - vtx2->x); + vtx_buf[j].x = g_FltWinLeft; + vtx_buf[j].y = vtx2->y + (vtx1->y - vtx2->y) * clip; + M_ClipGUV(&vtx_buf[j++], vtx1, vtx2, clip); + } else if (vtx2->x > g_FltWinRight) { + float clip = (g_FltWinRight - vtx2->x) / (vtx1->x - vtx2->x); + vtx_buf[j].x = g_FltWinRight; + vtx_buf[j].y = vtx2->y + (vtx1->y - vtx2->y) * clip; + M_ClipGUV(&vtx_buf[j++], vtx1, vtx2, clip); + } else { + vtx_buf[j++] = *vtx2; + } + } + + vtx_count = j; + if (vtx_count < 3) { + return 0; + } + + // vertical clip + j = 0; + vtx2 = &vtx_buf[vtx_count - 1]; + for (int32_t i = 0; i < vtx_count; i++) { + vtx1 = vtx2; + vtx2 = &vtx_buf[i]; + + if (vtx1->y < g_FltWinTop) { + if (vtx2->y < g_FltWinTop) { + continue; + } + const float clip = (g_FltWinTop - vtx2->y) / (vtx1->y - vtx2->y); + vtx[j].x = vtx2->x + (vtx1->x - vtx2->x) * clip; + vtx[j].y = g_FltWinTop; + M_ClipGUV(&vtx[j++], vtx1, vtx2, clip); + } else if (vtx1->y > g_FltWinBottom) { + if (vtx2->y > g_FltWinBottom) { + continue; + } + const float clip = (g_FltWinBottom - vtx2->y) / (vtx1->y - vtx2->y); + vtx[j].x = vtx2->x + (vtx1->x - vtx2->x) * clip; + vtx[j].y = g_FltWinBottom; + M_ClipGUV(&vtx[j++], vtx1, vtx2, clip); + } + + if (vtx2->y < g_FltWinTop) { + const float clip = (g_FltWinTop - vtx2->y) / (vtx1->y - vtx2->y); + vtx[j].x = vtx2->x + (vtx1->x - vtx2->x) * clip; + vtx[j].y = g_FltWinTop; + M_ClipGUV(&vtx[j++], vtx1, vtx2, clip); + } else if (vtx2->y > g_FltWinBottom) { + const float clip = (g_FltWinBottom - vtx2->y) / (vtx1->y - vtx2->y); + vtx[j].x = vtx2->x + (vtx1->x - vtx2->x) * clip; + vtx[j].y = g_FltWinBottom; + M_ClipGUV(&vtx[j++], vtx1, vtx2, clip); + } else { + vtx[j++] = *vtx2; + } + } + + return (j < 3) ? 0 : j; +} + +const int16_t *__cdecl Output_InsertObjectG3( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type) +{ + for (int32_t i = 0; i < num; i++) { + const PHD_VBUF *const vtx[3] = { + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + }; + const uint8_t color_idx = *obj_ptr++; + int32_t num_points = 3; + + int8_t clip_or = vtx[0]->clip | vtx[1]->clip | vtx[2]->clip; + int8_t clip_and = vtx[0]->clip & vtx[1]->clip & vtx[2]->clip; + + if (clip_and != 0) { + continue; + } + + if (clip_or >= 0) { + if (!VBUF_VISIBLE(*vtx[0], *vtx[1], *vtx[2])) { + continue; + } + + g_VBuffer[0].x = vtx[0]->xs; + g_VBuffer[0].y = vtx[0]->ys; + g_VBuffer[0].rhw = vtx[0]->rhw; + g_VBuffer[0].g = (float)vtx[0]->g; + + g_VBuffer[1].x = vtx[1]->xs; + g_VBuffer[1].y = vtx[1]->ys; + g_VBuffer[1].rhw = vtx[1]->rhw; + g_VBuffer[1].g = (float)vtx[1]->g; + + g_VBuffer[2].x = vtx[2]->xs; + g_VBuffer[2].y = vtx[2]->ys; + g_VBuffer[2].rhw = vtx[2]->rhw; + g_VBuffer[2].g = (float)vtx[2]->g; + } else { + if (!Output_VisibleZClip(vtx[0], vtx[1], vtx[2])) { + continue; + } + + POINT_INFO points[3] = { + { + .xv = vtx[0]->xv, + .yv = vtx[0]->yv, + .zv = vtx[0]->zv, + .rhw = vtx[0]->rhw, + .xs = vtx[0]->xs, + .ys = vtx[0]->ys, + .g = (float)vtx[0]->g, + }, + { + .xv = vtx[1]->xv, + .yv = vtx[1]->yv, + .zv = vtx[1]->zv, + .rhw = vtx[1]->rhw, + .xs = vtx[1]->xs, + .ys = vtx[1]->ys, + .g = (float)vtx[1]->g, + }, + { + .xv = vtx[2]->xv, + .yv = vtx[2]->yv, + .zv = vtx[2]->zv, + .rhw = vtx[2]->rhw, + .xs = vtx[2]->xs, + .ys = vtx[2]->ys, + .g = (float)vtx[2]->g, + }, + }; + + num_points = Output_ZedClipper(num_points, points, g_VBuffer); + if (num_points == 0) { + continue; + } + } + + num_points = Output_XYGClipper(num_points, g_VBuffer); + if (num_points == 0) { + continue; + } + + const float zv = M_CalculatePolyZ( + sort_type, vtx[0]->zv, vtx[1]->zv, vtx[2]->zv, -1.0); + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(zv); + g_Sort3DPtr++; + + *g_Info3DPtr++ = POLY_GOURAUD; + *g_Info3DPtr++ = color_idx; + *g_Info3DPtr++ = num_points; + + for (int32_t j = 0; j < num_points; j++) { + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].x; + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].y; + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].g; + } + g_SurfaceCount++; + } + + return obj_ptr; +} + +const int16_t *__cdecl Output_InsertObjectGT3( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type) +{ + for (int32_t i = 0; i < num; i++) { + const PHD_VBUF *const vtx[3] = { + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + }; + const int16_t texture_idx = *obj_ptr++; + const PHD_TEXTURE *const texture = &g_PhdTextureInfo[texture_idx]; + const PHD_UV *const uv = texture->uv; + int32_t num_points = 3; + + const int8_t clip_or = vtx[0]->clip | vtx[1]->clip | vtx[2]->clip; + const int8_t clip_and = vtx[0]->clip & vtx[1]->clip & vtx[2]->clip; + + if (clip_and != 0) { + continue; + } + + if (clip_or >= 0) { + if (!VBUF_VISIBLE(*vtx[0], *vtx[1], *vtx[2])) { + continue; + } + + if (clip_or == 0) { + const float zv = M_CalculatePolyZ( + sort_type, vtx[0]->zv, vtx[1]->zv, vtx[2]->zv, -1.0); + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(zv); + g_Sort3DPtr++; + + if (zv >= (double)g_PerspectiveDistance) { + *g_Info3DPtr++ = (texture->draw_type == DRAW_OPAQUE) + ? POLY_GTMAP + : POLY_WGTMAP; + *g_Info3DPtr++ = texture->tex_page; + *g_Info3DPtr++ = 3; + + *g_Info3DPtr++ = (int32_t)vtx[0]->xs; + *g_Info3DPtr++ = (int32_t)vtx[0]->ys; + *g_Info3DPtr++ = (int32_t)vtx[0]->g; + *g_Info3DPtr++ = uv[0].u; + *g_Info3DPtr++ = uv[0].v; + + *g_Info3DPtr++ = (int32_t)vtx[1]->xs; + *g_Info3DPtr++ = (int32_t)vtx[1]->ys; + *g_Info3DPtr++ = (int32_t)vtx[1]->g; + *g_Info3DPtr++ = uv[1].u; + *g_Info3DPtr++ = uv[1].v; + + *g_Info3DPtr++ = (int32_t)vtx[2]->xs; + *g_Info3DPtr++ = (int32_t)vtx[2]->ys; + *g_Info3DPtr++ = (int32_t)vtx[2]->g; + *g_Info3DPtr++ = uv[2].u; + *g_Info3DPtr++ = uv[2].v; + } else { + *g_Info3DPtr++ = (texture->draw_type == DRAW_OPAQUE) + ? POLY_GTMAP_PERSP + : POLY_WGTMAP_PERSP; + *g_Info3DPtr++ = texture->tex_page; + *g_Info3DPtr++ = 3; + + *g_Info3DPtr++ = (int32_t)vtx[0]->xs; + *g_Info3DPtr++ = (int32_t)vtx[0]->ys; + *g_Info3DPtr++ = (int32_t)vtx[0]->g; + *(float *)g_Info3DPtr = vtx[0]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[0].u * vtx[0]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[0].v * vtx[0]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + + *g_Info3DPtr++ = (int32_t)vtx[1]->xs; + *g_Info3DPtr++ = (int32_t)vtx[1]->ys; + *g_Info3DPtr++ = (int32_t)vtx[1]->g; + *(float *)g_Info3DPtr = vtx[1]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[1].u * vtx[1]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[1].v * vtx[1]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + + *g_Info3DPtr++ = (int32_t)vtx[2]->xs; + *g_Info3DPtr++ = (int32_t)vtx[2]->ys; + *g_Info3DPtr++ = (int32_t)vtx[2]->g; + *(float *)g_Info3DPtr = vtx[2]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[2].u * vtx[2]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[2].v * vtx[2]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + } + g_SurfaceCount++; + continue; + } + + g_VBuffer[0].x = vtx[0]->xs; + g_VBuffer[0].y = vtx[0]->ys; + g_VBuffer[0].rhw = vtx[0]->rhw; + g_VBuffer[0].g = (float)vtx[0]->g; + g_VBuffer[0].u = (double)uv[0].u * vtx[0]->rhw; + g_VBuffer[0].v = (double)uv[0].v * vtx[0]->rhw; + + g_VBuffer[1].x = vtx[1]->xs; + g_VBuffer[1].y = vtx[1]->ys; + g_VBuffer[1].rhw = vtx[1]->rhw; + g_VBuffer[1].g = (float)vtx[1]->g; + g_VBuffer[1].u = (double)uv[1].u * vtx[1]->rhw; + g_VBuffer[1].v = (double)uv[1].v * vtx[1]->rhw; + + g_VBuffer[2].x = vtx[2]->xs; + g_VBuffer[2].y = vtx[2]->ys; + g_VBuffer[2].rhw = vtx[2]->rhw; + g_VBuffer[2].g = (float)vtx[2]->g; + g_VBuffer[2].u = (double)uv[2].u * vtx[2]->rhw; + g_VBuffer[2].v = (double)uv[2].v * vtx[2]->rhw; + } else { + if (!Output_VisibleZClip(vtx[0], vtx[1], vtx[2])) { + continue; + } + + const POINT_INFO points[3] = { + { + .xv = vtx[0]->xv, + .yv = vtx[0]->yv, + .zv = vtx[0]->zv, + .rhw = vtx[0]->rhw, + .xs = vtx[0]->xs, + .ys = vtx[0]->ys, + .g = (float)vtx[0]->g, + .u = (float)uv[0].u, + .v = (float)uv[0].v, + }, + { + .yv = vtx[1]->yv, + .xv = vtx[1]->xv, + .zv = vtx[1]->zv, + .rhw = vtx[1]->rhw, + .xs = vtx[1]->xs, + .ys = vtx[1]->ys, + .g = (float)vtx[1]->g, + .u = (float)uv[1].u, + .v = (float)uv[1].v, + }, + { + .xv = vtx[2]->xv, + .yv = vtx[2]->yv, + .zv = vtx[2]->zv, + .rhw = vtx[2]->rhw, + .xs = vtx[2]->xs, + .ys = vtx[2]->ys, + .g = (float)vtx[2]->g, + .u = (float)uv[2].u, + .v = (float)uv[2].v, + }, + }; + + num_points = Output_ZedClipper(num_points, points, g_VBuffer); + if (num_points == 0) { + continue; + } + } + + num_points = Output_XYGUVClipper(num_points, g_VBuffer); + if (num_points == 0) { + continue; + } + + const float zv = M_CalculatePolyZ( + sort_type, vtx[0]->zv, vtx[1]->zv, vtx[2]->zv, -1.0); + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(zv); + g_Sort3DPtr++; + + if (zv >= (double)g_PerspectiveDistance) { + *g_Info3DPtr++ = + (texture->draw_type == DRAW_OPAQUE) ? POLY_GTMAP : POLY_WGTMAP; + *g_Info3DPtr++ = texture->tex_page; + *g_Info3DPtr++ = num_points; + + for (int32_t j = 0; j < num_points; j++) { + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].x; + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].y; + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].g; + *g_Info3DPtr++ = (int32_t)(g_VBuffer[j].u / g_VBuffer[j].rhw); + *g_Info3DPtr++ = (int32_t)(g_VBuffer[j].v / g_VBuffer[j].rhw); + } + } else { + *g_Info3DPtr++ = (texture->draw_type == DRAW_OPAQUE) + ? POLY_GTMAP_PERSP + : POLY_WGTMAP_PERSP; + *g_Info3DPtr++ = texture->tex_page; + *g_Info3DPtr++ = num_points; + + for (int32_t j = 0; j < num_points; j++) { + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].x; + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].y; + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].g; + *(float *)g_Info3DPtr = g_VBuffer[j].rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = g_VBuffer[j].u; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = g_VBuffer[j].v; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + } + } + g_SurfaceCount++; + } + + return obj_ptr; +} + +const int16_t *__cdecl Output_InsertObjectG4( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type) +{ + for (int32_t i = 0; i < num; i++) { + const PHD_VBUF *const vtx[4] = { + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + }; + const uint8_t color_idx = *obj_ptr++; + int32_t num_points = 4; + + const int8_t clip_or = + vtx[0]->clip | vtx[1]->clip | vtx[2]->clip | vtx[3]->clip; + const int8_t clip_and = + vtx[0]->clip & vtx[1]->clip & vtx[2]->clip & vtx[3]->clip; + + if (clip_and != 0) { + continue; + } + + if (clip_or >= 0) { + if (!VBUF_VISIBLE(*vtx[0], *vtx[1], *vtx[2])) { + continue; + } + + g_VBuffer[0].x = vtx[0]->xs; + g_VBuffer[0].y = vtx[0]->ys; + g_VBuffer[0].rhw = vtx[0]->rhw; + g_VBuffer[0].g = (float)vtx[0]->g; + + g_VBuffer[1].x = vtx[1]->xs; + g_VBuffer[1].y = vtx[1]->ys; + g_VBuffer[1].rhw = vtx[1]->rhw; + g_VBuffer[1].g = (float)vtx[1]->g; + + g_VBuffer[2].x = vtx[2]->xs; + g_VBuffer[2].y = vtx[2]->ys; + g_VBuffer[2].rhw = vtx[2]->rhw; + g_VBuffer[2].g = (float)vtx[2]->g; + + g_VBuffer[3].x = vtx[3]->xs; + g_VBuffer[3].y = vtx[3]->ys; + g_VBuffer[3].rhw = vtx[3]->rhw; + g_VBuffer[3].g = (float)vtx[3]->g; + } else { + if (!Output_VisibleZClip(vtx[0], vtx[1], vtx[2])) { + continue; + } + + const POINT_INFO points[4] = { + { + .xv = vtx[0]->xv, + .yv = vtx[0]->yv, + .zv = vtx[0]->zv, + .rhw = vtx[0]->rhw, + .xs = vtx[0]->xs, + .ys = vtx[0]->ys, + .g = (float)vtx[0]->g, + }, + { + .xv = vtx[1]->xv, + .yv = vtx[1]->yv, + .zv = vtx[1]->zv, + .rhw = vtx[1]->rhw, + .xs = vtx[1]->xs, + .ys = vtx[1]->ys, + .g = (float)vtx[1]->g, + }, + { + .xv = vtx[2]->xv, + .yv = vtx[2]->yv, + .zv = vtx[2]->zv, + .rhw = vtx[2]->rhw, + .xs = vtx[2]->xs, + .ys = vtx[2]->ys, + .g = (float)vtx[2]->g, + }, + { + .xv = vtx[3]->xv, + .yv = vtx[3]->yv, + .zv = vtx[3]->zv, + .rhw = vtx[3]->rhw, + .xs = vtx[3]->xs, + .ys = vtx[3]->ys, + .g = (float)vtx[3]->g, + }, + }; + + num_points = Output_ZedClipper(num_points, points, g_VBuffer); + if (num_points == 0) { + continue; + } + } + + num_points = Output_XYGClipper(num_points, g_VBuffer); + if (num_points == 0) { + continue; + } + + const float zv = M_CalculatePolyZ( + sort_type, vtx[0]->zv, vtx[1]->zv, vtx[2]->zv, vtx[3]->zv); + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(zv); + g_Sort3DPtr++; + + *g_Info3DPtr++ = POLY_GOURAUD; + *g_Info3DPtr++ = color_idx; + *g_Info3DPtr++ = num_points; + + for (int32_t j = 0; j < num_points; j++) { + *g_Info3DPtr++ = g_VBuffer[j].x; + *g_Info3DPtr++ = g_VBuffer[j].y; + *g_Info3DPtr++ = g_VBuffer[j].g; + } + g_SurfaceCount++; + } + + return obj_ptr; +} + +const int16_t *__cdecl Output_InsertObjectGT4( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type) +{ + for (int32_t i = 0; i < num; i++) { + const PHD_VBUF *const vtx[4] = { + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + }; + const int16_t texture_idx = *obj_ptr++; + const PHD_TEXTURE *const texture = &g_PhdTextureInfo[texture_idx]; + const PHD_UV *const uv = texture->uv; + int32_t num_points = 4; + + const int8_t clip_or = + vtx[0]->clip | vtx[1]->clip | vtx[2]->clip | vtx[3]->clip; + const int8_t clip_and = + vtx[0]->clip & vtx[1]->clip & vtx[2]->clip & vtx[3]->clip; + + if (clip_and != 0) { + continue; + } + + if (clip_or >= 0) { + if (!VBUF_VISIBLE(*vtx[0], *vtx[1], *vtx[2])) { + continue; + } + + if (clip_or == 0) { + const float zv = M_CalculatePolyZ( + sort_type, vtx[0]->zv, vtx[1]->zv, vtx[2]->zv, vtx[3]->zv); + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(zv); + g_Sort3DPtr++; + + if (zv >= (double)g_PerspectiveDistance) { + *g_Info3DPtr++ = (texture->draw_type == DRAW_OPAQUE) + ? POLY_GTMAP + : POLY_WGTMAP; + *g_Info3DPtr++ = texture->tex_page; + *g_Info3DPtr++ = 4; + + *g_Info3DPtr++ = (int32_t)vtx[0]->xs; + *g_Info3DPtr++ = (int32_t)vtx[0]->ys; + *g_Info3DPtr++ = (int32_t)vtx[0]->g; + *g_Info3DPtr++ = uv[0].u; + *g_Info3DPtr++ = uv[0].v; + + *g_Info3DPtr++ = (int32_t)vtx[1]->xs; + *g_Info3DPtr++ = (int32_t)vtx[1]->ys; + *g_Info3DPtr++ = (int32_t)vtx[1]->g; + *g_Info3DPtr++ = uv[1].u; + *g_Info3DPtr++ = uv[1].v; + + *g_Info3DPtr++ = (int32_t)vtx[2]->xs; + *g_Info3DPtr++ = (int32_t)vtx[2]->ys; + *g_Info3DPtr++ = (int32_t)vtx[2]->g; + *g_Info3DPtr++ = uv[2].u; + *g_Info3DPtr++ = uv[2].v; + + *g_Info3DPtr++ = (int32_t)vtx[3]->xs; + *g_Info3DPtr++ = (int32_t)vtx[3]->ys; + *g_Info3DPtr++ = (int32_t)vtx[3]->g; + *g_Info3DPtr++ = uv[3].u; + *g_Info3DPtr++ = uv[3].v; + } else { + *g_Info3DPtr++ = (texture->draw_type == DRAW_OPAQUE) + ? POLY_GTMAP_PERSP + : POLY_WGTMAP_PERSP; + *g_Info3DPtr++ = texture->tex_page; + *g_Info3DPtr++ = 4; + + *g_Info3DPtr++ = (int32_t)vtx[0]->xs; + *g_Info3DPtr++ = (int32_t)vtx[0]->ys; + *g_Info3DPtr++ = (int32_t)vtx[0]->g; + *(float *)g_Info3DPtr = vtx[0]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[0].u * vtx[0]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[0].v * vtx[0]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + + *g_Info3DPtr++ = (int32_t)vtx[1]->xs; + *g_Info3DPtr++ = (int32_t)vtx[1]->ys; + *g_Info3DPtr++ = (int32_t)vtx[1]->g; + *(float *)g_Info3DPtr = vtx[1]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[1].u * vtx[1]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[1].v * vtx[1]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + + *g_Info3DPtr++ = (int32_t)vtx[2]->xs; + *g_Info3DPtr++ = (int32_t)vtx[2]->ys; + *g_Info3DPtr++ = (int32_t)vtx[2]->g; + *(float *)g_Info3DPtr = vtx[2]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[2].u * vtx[2]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[2].v * vtx[2]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + + *g_Info3DPtr++ = (int32_t)vtx[3]->xs; + *g_Info3DPtr++ = (int32_t)vtx[3]->ys; + *g_Info3DPtr++ = (int32_t)vtx[3]->g; + *(float *)g_Info3DPtr = vtx[3]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[3].u * vtx[3]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = (double)uv[3].v * vtx[3]->rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + } + g_SurfaceCount++; + continue; + } + + g_VBuffer[0].x = vtx[0]->xs; + g_VBuffer[0].y = vtx[0]->ys; + g_VBuffer[0].rhw = vtx[0]->rhw; + g_VBuffer[0].g = (float)vtx[0]->g; + g_VBuffer[0].u = (double)uv[0].u * vtx[0]->rhw; + g_VBuffer[0].v = (double)uv[0].v * vtx[0]->rhw; + + g_VBuffer[1].x = vtx[1]->xs; + g_VBuffer[1].y = vtx[1]->ys; + g_VBuffer[1].rhw = vtx[1]->rhw; + g_VBuffer[1].g = (float)vtx[1]->g; + g_VBuffer[1].u = (double)uv[1].u * vtx[1]->rhw; + g_VBuffer[1].v = (double)uv[1].v * vtx[1]->rhw; + + g_VBuffer[2].x = vtx[2]->xs; + g_VBuffer[2].y = vtx[2]->ys; + g_VBuffer[2].rhw = vtx[2]->rhw; + g_VBuffer[2].g = (float)vtx[2]->g; + g_VBuffer[2].u = (double)uv[2].u * vtx[2]->rhw; + g_VBuffer[2].v = (double)uv[2].v * vtx[2]->rhw; + + g_VBuffer[3].x = vtx[3]->xs; + g_VBuffer[3].y = vtx[3]->ys; + g_VBuffer[3].rhw = vtx[3]->rhw; + g_VBuffer[3].g = (float)vtx[3]->g; + g_VBuffer[3].u = (double)uv[3].u * vtx[3]->rhw; + g_VBuffer[3].v = (double)uv[3].v * vtx[3]->rhw; + } else { + if (!Output_VisibleZClip(vtx[0], vtx[1], vtx[2])) { + continue; + } + + const POINT_INFO points[4] = { + { + .xv = vtx[0]->xv, + .yv = vtx[0]->yv, + .zv = vtx[0]->zv, + .rhw = vtx[0]->rhw, + .xs = vtx[0]->xs, + .ys = vtx[0]->ys, + .g = (float)vtx[0]->g, + .u = (float)uv[0].u, + .v = (float)uv[0].v, + }, + { + .yv = vtx[1]->yv, + .xv = vtx[1]->xv, + .zv = vtx[1]->zv, + .rhw = vtx[1]->rhw, + .xs = vtx[1]->xs, + .ys = vtx[1]->ys, + .g = (float)vtx[1]->g, + .u = (float)uv[1].u, + .v = (float)uv[1].v, + }, + { + .xv = vtx[2]->xv, + .yv = vtx[2]->yv, + .zv = vtx[2]->zv, + .rhw = vtx[2]->rhw, + .xs = vtx[2]->xs, + .ys = vtx[2]->ys, + .g = (float)vtx[2]->g, + .u = (float)uv[2].u, + .v = (float)uv[2].v, + }, + { + .xv = vtx[3]->xv, + .yv = vtx[3]->yv, + .zv = vtx[3]->zv, + .rhw = vtx[3]->rhw, + .xs = vtx[3]->xs, + .ys = vtx[3]->ys, + .g = (float)vtx[3]->g, + .u = (float)uv[3].u, + .v = (float)uv[3].v, + }, + }; + + num_points = Output_ZedClipper(num_points, points, g_VBuffer); + if (num_points == 0) { + continue; + } + } + + num_points = Output_XYGUVClipper(num_points, g_VBuffer); + if (num_points == 0) { + continue; + } + + const float zv = M_CalculatePolyZ( + sort_type, vtx[0]->zv, vtx[1]->zv, vtx[2]->zv, vtx[3]->zv); + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(zv); + g_Sort3DPtr++; + + if (zv >= (double)g_PerspectiveDistance) { + *g_Info3DPtr++ = + (texture->draw_type == DRAW_OPAQUE) ? POLY_GTMAP : POLY_WGTMAP; + *g_Info3DPtr++ = texture->tex_page; + *g_Info3DPtr++ = num_points; + + for (int32_t j = 0; j < num_points; j++) { + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].x; + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].y; + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].g; + *g_Info3DPtr++ = (int32_t)(g_VBuffer[j].u / g_VBuffer[j].rhw); + *g_Info3DPtr++ = (int32_t)(g_VBuffer[j].v / g_VBuffer[j].rhw); + } + } else { + *g_Info3DPtr++ = (texture->draw_type == DRAW_OPAQUE) + ? POLY_GTMAP_PERSP + : POLY_WGTMAP_PERSP; + *g_Info3DPtr++ = texture->tex_page; + *g_Info3DPtr++ = num_points; + + for (int32_t j = 0; j < num_points; j++) { + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].x; + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].y; + *g_Info3DPtr++ = (int32_t)g_VBuffer[j].g; + *(float *)g_Info3DPtr = g_VBuffer[j].rhw; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = g_VBuffer[j].u; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + *(float *)g_Info3DPtr = g_VBuffer[j].v; + g_Info3DPtr += sizeof(float) / sizeof(int16_t); + } + } + g_SurfaceCount++; + } + + return obj_ptr; +} + +void __cdecl Output_InsertTrans8(const PHD_VBUF *vbuf, int16_t shade) +{ + const int32_t vtx_count = 8; + + int8_t clip_or = 0; + uint8_t clip_and = 0xFF; + for (int32_t i = 0; i < vtx_count; i++) { + clip_or |= vbuf[i].clip; + clip_and &= vbuf[i].clip; + } + + if (clip_or < 0 || clip_and || !VBUF_VISIBLE(vbuf[0], vbuf[1], vbuf[2])) { + return; + } + + int32_t num_points = vtx_count; + for (int32_t i = 0; i < num_points; i++) { + g_VBuffer[i].x = vbuf[i].xs; + g_VBuffer[i].y = vbuf[i].ys; + } + + if (clip_or != 0) { + g_FltWinTop = 0.0; + g_FltWinLeft = 0.0; + g_FltWinRight = (float)g_PhdWinMaxX; + g_FltWinBottom = (float)g_PhdWinMaxY; + + num_points = Output_XYClipper(vtx_count, g_VBuffer); + if (num_points == 0) { + return; + } + } + + double poly_z = 0.0; + for (int32_t i = 0; i < vtx_count; i++) { + poly_z += vbuf[i].zv; + } + poly_z /= vtx_count; + + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(poly_z); + g_Sort3DPtr++; + + *g_Info3DPtr++ = POLY_TRANS; + *g_Info3DPtr++ = shade; + *g_Info3DPtr++ = num_points; + for (int32_t i = 0; i < num_points; i++) { + *g_Info3DPtr++ = g_VBuffer[i].x; + *g_Info3DPtr++ = g_VBuffer[i].y; + } + g_SurfaceCount++; +} + +void __cdecl Output_InsertTransQuad( + int32_t x, int32_t y, int32_t width, int32_t height, int32_t z) +{ + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(g_PhdNearZ + 8 * z); + g_Sort3DPtr++; + + *g_Info3DPtr++ = POLY_TRANS; + *g_Info3DPtr++ = 32; + *g_Info3DPtr++ = 4; // number of vertices + *g_Info3DPtr++ = x; + *g_Info3DPtr++ = y; + *g_Info3DPtr++ = x + width; + *g_Info3DPtr++ = y; + *g_Info3DPtr++ = x + width; + *g_Info3DPtr++ = height + y; + *g_Info3DPtr++ = x; + *g_Info3DPtr++ = height + y; + + g_SurfaceCount++; +} + +void __cdecl Output_InsertFlatRect( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t z, + uint8_t color_idx) +{ + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(z); + g_Sort3DPtr++; + + *g_Info3DPtr++ = POLY_FLAT; + *g_Info3DPtr++ = color_idx; + *g_Info3DPtr++ = 4; + *g_Info3DPtr++ = x1; + *g_Info3DPtr++ = y1; + *g_Info3DPtr++ = x2; + *g_Info3DPtr++ = y1; + *g_Info3DPtr++ = x2; + *g_Info3DPtr++ = y2; + *g_Info3DPtr++ = x1; + *g_Info3DPtr++ = y2; + + g_SurfaceCount++; +} + +void __cdecl Output_InsertLine( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t z, + uint8_t color_idx) +{ + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(z); + g_Sort3DPtr++; + + *g_Info3DPtr++ = POLY_LINE; + *g_Info3DPtr++ = g_PhdWinMinX + x1; + *g_Info3DPtr++ = g_PhdWinMinY + y1; + *g_Info3DPtr++ = g_PhdWinMinX + x2; + *g_Info3DPtr++ = g_PhdWinMinY + y2; + *g_Info3DPtr++ = color_idx; + + g_SurfaceCount++; +} + +const int16_t *__cdecl Output_InsertObjectG3_ZBuffered( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type) +{ + HWR_TexSource(0); + HWR_EnableColorKey(0); + + if (num == 0) { + return obj_ptr; + } + + for (int32_t i = 0; i < num; i++) { + const PHD_VBUF *vtx[3] = { + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + }; + const int16_t color_idx = *obj_ptr++; + int32_t num_points = 3; + + const int8_t clip_or = vtx[0]->clip | vtx[1]->clip | vtx[2]->clip; + const int8_t clip_and = vtx[0]->clip & vtx[1]->clip & vtx[2]->clip; + + if (clip_and != 0) { + continue; + } + + if (clip_or >= 0) { + if (!VBUF_VISIBLE(*vtx[0], *vtx[1], *vtx[2])) { + continue; + } + + g_VBuffer[0].x = vtx[0]->xs; + g_VBuffer[0].y = vtx[0]->ys; + g_VBuffer[0].rhw = vtx[0]->rhw; + g_VBuffer[0].g = (float)vtx[0]->g; + + g_VBuffer[1].x = vtx[1]->xs; + g_VBuffer[1].y = vtx[1]->ys; + g_VBuffer[1].rhw = vtx[1]->rhw; + g_VBuffer[1].g = (float)vtx[1]->g; + + g_VBuffer[2].x = vtx[2]->xs; + g_VBuffer[2].y = vtx[2]->ys; + g_VBuffer[2].rhw = vtx[2]->rhw; + g_VBuffer[2].g = (float)vtx[2]->g; + } else { + if (!Output_VisibleZClip(vtx[0], vtx[1], vtx[2])) { + continue; + } + + const POINT_INFO points[3] = { + { + .xv = vtx[0]->xv, + .yv = vtx[0]->yv, + .zv = vtx[0]->zv, + .rhw = vtx[0]->rhw, + .xs = vtx[0]->xs, + .ys = vtx[0]->ys, + .g = (float)vtx[0]->g, + }, + { + .xv = vtx[1]->xv, + .yv = vtx[1]->yv, + .zv = vtx[1]->zv, + .rhw = vtx[1]->rhw, + .xs = vtx[1]->xs, + .ys = vtx[1]->ys, + .g = (float)vtx[1]->g, + }, + { + .xv = vtx[2]->xv, + .yv = vtx[2]->yv, + .zv = vtx[2]->zv, + .rhw = vtx[2]->rhw, + .xs = vtx[2]->xs, + .ys = vtx[2]->ys, + .g = (float)vtx[2]->g, + }, + }; + + num_points = Output_ZedClipper(num_points, points, g_VBuffer); + if (num_points == 0) { + continue; + } + } + + if (clip_or != 0) { + num_points = Output_XYGClipper(num_points, g_VBuffer); + } + if (num_points == 0) { + continue; + } + + const PALETTEENTRY *const color = &g_GamePalette16[color_idx >> 8]; + Output_DrawPoly_Gouraud( + num_points, color->peRed, color->peGreen, color->peBlue); + } + + return obj_ptr; +} + +const int16_t *__cdecl Output_InsertObjectG4_ZBuffered( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type) +{ + HWR_TexSource(0); + HWR_EnableColorKey(0); + + if (num == 0) { + return obj_ptr; + } + + for (int32_t i = 0; i < num; i++) { + const PHD_VBUF *const vtx[4] = { + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + }; + const int16_t color_idx = *obj_ptr++; + int32_t num_points = 4; + + const int8_t clip_or = + vtx[0]->clip | vtx[1]->clip | vtx[2]->clip | vtx[3]->clip; + const int8_t clip_and = + vtx[0]->clip & vtx[1]->clip & vtx[2]->clip & vtx[3]->clip; + + if (clip_and != 0) { + continue; + } + + if (clip_or >= 0) { + if (!VBUF_VISIBLE(*vtx[0], *vtx[1], *vtx[2])) { + continue; + } + + g_VBuffer[0].x = vtx[0]->xs; + g_VBuffer[0].y = vtx[0]->ys; + g_VBuffer[0].rhw = vtx[0]->rhw; + g_VBuffer[0].g = (float)vtx[0]->g; + + g_VBuffer[1].x = vtx[1]->xs; + g_VBuffer[1].y = vtx[1]->ys; + g_VBuffer[1].rhw = vtx[1]->rhw; + g_VBuffer[1].g = (float)vtx[1]->g; + + g_VBuffer[2].x = vtx[2]->xs; + g_VBuffer[2].y = vtx[2]->ys; + g_VBuffer[2].rhw = vtx[2]->rhw; + g_VBuffer[2].g = (float)vtx[2]->g; + + g_VBuffer[3].x = vtx[3]->xs; + g_VBuffer[3].y = vtx[3]->ys; + g_VBuffer[3].rhw = vtx[3]->rhw; + g_VBuffer[3].g = (float)vtx[3]->g; + } else { + if (!Output_VisibleZClip(vtx[0], vtx[1], vtx[2])) { + continue; + } + + const POINT_INFO points[4] = { + { + .xv = vtx[0]->xv, + .yv = vtx[0]->yv, + .zv = vtx[0]->zv, + .rhw = vtx[0]->rhw, + .xs = vtx[0]->xs, + .ys = vtx[0]->ys, + .g = (float)vtx[0]->g, + }, + { + .xv = vtx[1]->xv, + .yv = vtx[1]->yv, + .zv = vtx[1]->zv, + .rhw = vtx[1]->rhw, + .xs = vtx[1]->xs, + .ys = vtx[1]->ys, + .g = (float)vtx[1]->g, + }, + { + .xv = vtx[2]->xv, + .yv = vtx[2]->yv, + .zv = vtx[2]->zv, + .rhw = vtx[2]->rhw, + .xs = vtx[2]->xs, + .ys = vtx[2]->ys, + .g = (float)vtx[2]->g, + }, + { + .xv = vtx[3]->xv, + .yv = vtx[3]->yv, + .zv = vtx[3]->zv, + .rhw = vtx[3]->rhw, + .xs = vtx[3]->xs, + .ys = vtx[3]->ys, + .g = (float)vtx[3]->g, + }, + }; + + num_points = Output_ZedClipper(num_points, points, g_VBuffer); + if (num_points == 0) { + continue; + } + } + + if (clip_or != 0) { + num_points = Output_XYGClipper(num_points, g_VBuffer); + } + if (num_points == 0) { + continue; + } + + const PALETTEENTRY *const color = &g_GamePalette16[color_idx >> 8]; + Output_DrawPoly_Gouraud( + num_points, color->peRed, color->peGreen, color->peBlue); + } + + return obj_ptr; +} + +const int16_t *__cdecl Output_InsertObjectGT3_ZBuffered( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type) +{ + for (int32_t i = 0; i < num; i++) { + const PHD_VBUF *const vtx[3] = { + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + }; + const PHD_TEXTURE *const texture = &g_PhdTextureInfo[*obj_ptr++]; + const PHD_UV *const uv = texture->uv; + + if (texture->draw_type != DRAW_OPAQUE) { + Output_InsertGT3_Sorted( + vtx[0], vtx[1], vtx[2], texture, &uv[0], &uv[1], &uv[2], + sort_type); + } else { + Output_InsertGT3_ZBuffered( + vtx[0], vtx[1], vtx[2], texture, &uv[0], &uv[1], &uv[2]); + } + } + + return obj_ptr; +} + +const int16_t *__cdecl Output_InsertObjectGT4_ZBuffered( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type) +{ + for (int32_t i = 0; i < num; i++) { + const PHD_VBUF *const vtx[4] = { + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + }; + const PHD_TEXTURE *const texture = &g_PhdTextureInfo[*obj_ptr++]; + const PHD_UV *const uv = texture->uv; + + if (texture->draw_type != DRAW_OPAQUE) { + Output_InsertGT4_Sorted( + vtx[0], vtx[1], vtx[2], vtx[3], texture, sort_type); + } else { + Output_InsertGT4_ZBuffered(vtx[0], vtx[1], vtx[2], vtx[3], texture); + } + } + + return obj_ptr; +} + +void __cdecl Output_InsertGT3_ZBuffered( + const PHD_VBUF *const vtx0, const PHD_VBUF *const vtx1, + const PHD_VBUF *const vtx2, const PHD_TEXTURE *const texture, + const PHD_UV *const uv0, const PHD_UV *const uv1, const PHD_UV *const uv2) +{ + const int8_t clip_or = vtx0->clip | vtx1->clip | vtx2->clip; + const int8_t clip_and = vtx0->clip & vtx1->clip & vtx2->clip; + if (clip_and != 0) { + return; + } + + int32_t num_points = 3; + + if (clip_or >= 0) { + if (!VBUF_VISIBLE(*vtx0, *vtx1, *vtx2)) { + return; + } + + if (clip_or == 0) { + g_VBufferD3D[0].sx = vtx0->xs; + g_VBufferD3D[0].sy = vtx0->ys; + g_VBufferD3D[0].sz = g_FltResZBuf - g_FltResZORhw * vtx0->rhw; + g_VBufferD3D[0].rhw = vtx0->rhw; + g_VBufferD3D[0].color = M_ShadeLight(vtx0->g); + g_VBufferD3D[0].tu = (double)uv0->u / (double)PHD_ONE; + g_VBufferD3D[0].tv = (double)uv0->v / (double)PHD_ONE; + + g_VBufferD3D[1].sx = vtx1->xs; + g_VBufferD3D[1].sy = vtx1->ys; + g_VBufferD3D[1].sz = g_FltResZBuf - g_FltResZORhw * vtx1->rhw; + g_VBufferD3D[1].rhw = vtx1->rhw; + g_VBufferD3D[1].color = M_ShadeLight(vtx1->g); + g_VBufferD3D[1].tu = (double)uv1->u / (double)PHD_ONE; + g_VBufferD3D[1].tv = (double)uv1->v / (double)PHD_ONE; + + g_VBufferD3D[2].sx = vtx2->xs; + g_VBufferD3D[2].sy = vtx2->ys; + g_VBufferD3D[2].sz = g_FltResZBuf - g_FltResZORhw * vtx2->rhw; + g_VBufferD3D[2].rhw = vtx2->rhw; + g_VBufferD3D[2].color = M_ShadeLight(vtx2->g); + g_VBufferD3D[2].tu = (double)uv2->u / (double)PHD_ONE; + g_VBufferD3D[2].tv = (double)uv2->v / (double)PHD_ONE; + + HWR_TexSource(g_HWR_PageHandles[texture->tex_page]); + HWR_EnableColorKey(texture->draw_type != DRAW_OPAQUE); + + HWR_DrawPrimitive(D3DPT_TRIANGLELIST, g_VBufferD3D, 3, true); + return; + } + + g_VBuffer[0].x = vtx0->xs; + g_VBuffer[0].y = vtx0->ys; + g_VBuffer[0].rhw = vtx0->rhw; + g_VBuffer[0].g = (double)vtx0->g; + g_VBuffer[0].u = (double)uv0->u * vtx0->rhw; + g_VBuffer[0].v = (double)uv0->v * vtx0->rhw; + + g_VBuffer[1].x = vtx1->xs; + g_VBuffer[1].y = vtx1->ys; + g_VBuffer[1].rhw = vtx1->rhw; + g_VBuffer[1].g = (double)vtx1->g; + g_VBuffer[1].u = (double)uv1->u * vtx1->rhw; + g_VBuffer[1].v = (double)uv1->v * vtx1->rhw; + + g_VBuffer[2].x = vtx2->xs; + g_VBuffer[2].y = vtx2->ys; + g_VBuffer[2].rhw = vtx2->rhw; + g_VBuffer[2].g = (double)vtx2->g; + g_VBuffer[2].u = (double)uv2->u * vtx2->rhw; + g_VBuffer[2].v = (double)uv2->v * vtx2->rhw; + } else { + if (!Output_VisibleZClip(vtx0, vtx1, vtx2)) { + return; + } + + const POINT_INFO points[3] = { + { + .xv = vtx0->xv, + .yv = vtx0->yv, + .zv = vtx0->zv, + .rhw = vtx0->rhw, + .xs = vtx0->xs, + .ys = vtx0->ys, + .g = (float)vtx0->g, + .u = (float)uv0->u, + .v = (float)uv0->v, + }, + + { + .xv = vtx1->xv, + .yv = vtx1->yv, + .zv = vtx1->zv, + .rhw = vtx1->rhw, + .xs = vtx1->xs, + .ys = vtx1->ys, + .g = (float)vtx1->g, + .u = (float)uv1->u, + .v = (float)uv1->v, + }, + + { + .xv = vtx2->xv, + .yv = vtx2->yv, + .zv = vtx2->zv, + .rhw = vtx2->rhw, + .xs = vtx2->xs, + .ys = vtx2->ys, + .g = (float)vtx2->g, + .u = (float)uv2->u, + .v = (float)uv2->v, + }, + }; + + num_points = Output_ZedClipper(num_points, points, g_VBuffer); + if (num_points == 0) { + return; + } + } + + num_points = Output_XYGUVClipper(num_points, g_VBuffer); + if (num_points == 0) { + return; + } + + HWR_TexSource(g_HWR_PageHandles[texture->tex_page]); + HWR_EnableColorKey(texture->draw_type != DRAW_OPAQUE); + Output_DrawClippedPoly_Textured(num_points); +} + +void __cdecl Output_InsertGT4_ZBuffered( + const PHD_VBUF *const vtx0, const PHD_VBUF *const vtx1, + const PHD_VBUF *const vtx2, const PHD_VBUF *const vtx3, + const PHD_TEXTURE *const texture) +{ + const int8_t clip_and = vtx0->clip & vtx1->clip & vtx2->clip & vtx3->clip; + const int8_t clip_or = vtx0->clip | vtx1->clip | vtx2->clip | vtx3->clip; + + if (clip_and != 0) { + return; + } + + if (clip_or >= 0) { + if (!VBUF_VISIBLE(*vtx0, *vtx1, *vtx2)) { + return; + } + } else if (clip_or < 0) { + if (!Output_VisibleZClip(vtx0, vtx1, vtx2)) { + return; + } + } + + if (clip_or != 0) { + Output_InsertGT3_ZBuffered( + vtx0, vtx1, vtx2, texture, texture->uv, &texture->uv[1], + &texture->uv[2]); + Output_InsertGT3_ZBuffered( + vtx0, vtx2, vtx3, texture, texture->uv, &texture->uv[2], + &texture->uv[3]); + return; + } + + g_VBufferD3D[0].sx = vtx0->xs; + g_VBufferD3D[0].sy = vtx0->ys; + g_VBufferD3D[0].sz = g_FltResZBuf - g_FltResZORhw * vtx0->rhw; + g_VBufferD3D[0].rhw = vtx0->rhw; + g_VBufferD3D[0].color = M_ShadeLight(vtx0->g); + g_VBufferD3D[0].tu = (double)texture->uv[0].u / (double)PHD_ONE; + g_VBufferD3D[0].tv = (double)texture->uv[0].v / (double)PHD_ONE; + + g_VBufferD3D[1].sx = vtx1->xs; + g_VBufferD3D[1].sy = vtx1->ys; + g_VBufferD3D[1].sz = g_FltResZBuf - g_FltResZORhw * vtx1->rhw; + g_VBufferD3D[1].rhw = vtx1->rhw; + g_VBufferD3D[1].color = M_ShadeLight(vtx1->g); + g_VBufferD3D[1].tu = (double)texture->uv[1].u / (double)PHD_ONE; + g_VBufferD3D[1].tv = (double)texture->uv[1].v / (double)PHD_ONE; + + g_VBufferD3D[2].sx = vtx2->xs; + g_VBufferD3D[2].sy = vtx2->ys; + g_VBufferD3D[2].sz = g_FltResZBuf - g_FltResZORhw * vtx2->rhw; + g_VBufferD3D[2].rhw = vtx2->rhw; + g_VBufferD3D[2].color = M_ShadeLight(vtx2->g); + g_VBufferD3D[2].tu = (double)texture->uv[2].u / (double)PHD_ONE; + g_VBufferD3D[2].tv = (double)texture->uv[2].v / (double)PHD_ONE; + + g_VBufferD3D[3].sx = vtx3->xs; + g_VBufferD3D[3].sy = vtx3->ys; + g_VBufferD3D[3].sz = g_FltResZBuf - g_FltResZORhw * vtx3->rhw; + g_VBufferD3D[3].rhw = vtx3->rhw; + g_VBufferD3D[3].color = M_ShadeLight(vtx3->g); + g_VBufferD3D[3].tu = (double)texture->uv[3].u / (double)PHD_ONE; + g_VBufferD3D[3].tv = (double)texture->uv[3].v / (double)PHD_ONE; + + HWR_TexSource(g_HWR_PageHandles[texture->tex_page]); + HWR_EnableColorKey(texture->draw_type != DRAW_OPAQUE); + HWR_DrawPrimitive(D3DPT_TRIANGLEFAN, g_VBufferD3D, 4, true); +} + +void __cdecl Output_InsertFlatRect_ZBuffered( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t z, + const uint8_t color_idx) +{ + if (x2 <= x1 || y2 <= y1) { + return; + } + + CLAMPL(x1, g_PhdWinMinX); + CLAMPL(y1, g_PhdWinMinY); + CLAMPG(x2, g_PhdWinWidth + g_PhdWinMinX); + CLAMPG(y2, g_PhdWinMinY + g_PhdWinHeight); + CLAMP(z, g_PhdNearZ, g_PhdFarZ); + + const double rhw = g_RhwFactor / (double)z; + const double sz = g_FltResZBuf - rhw * g_FltResZORhw; + + const RGB_888 *const color = &g_GamePalette8[color_idx]; + const D3DCOLOR d3d_color = + M_ShadeColor(color->red, color->green, color->blue, 0xFF); + + g_VBufferD3D[0].sx = (float)x1; + g_VBufferD3D[0].sy = (float)y1; + g_VBufferD3D[1].sx = (float)x2; + g_VBufferD3D[1].sy = (float)y1; + g_VBufferD3D[2].sx = (float)x1; + g_VBufferD3D[2].sy = (float)y2; + g_VBufferD3D[3].sx = (float)x2; + g_VBufferD3D[3].sy = (float)y2; + for (int32_t i = 0; i < 4; i++) { + g_VBufferD3D[i].sz = sz; + g_VBufferD3D[i].rhw = rhw; + g_VBufferD3D[i].color = d3d_color; + } + + HWR_TexSource(0); + HWR_EnableColorKey(0); + HWR_DrawPrimitive(D3DPT_TRIANGLESTRIP, g_VBufferD3D, 4, true); +} + +void __cdecl Output_InsertLine_ZBuffered( + const int32_t x1, const int32_t y1, const int32_t x2, const int32_t y2, + int32_t z, const uint8_t color_idx) +{ + if (z >= g_PhdFarZ) { + return; + } + CLAMPL(z, g_PhdNearZ); + + const double rhw = g_RhwFactor / (double)z; + const double sz = g_FltResZBuf - rhw * g_FltResZORhw; + + const RGB_888 *const color = &g_GamePalette8[color_idx]; + const D3DCOLOR d3d_color = + M_ShadeColor(color->red, color->green, color->blue, 0xFF); + + g_VBufferD3D[0].sx = (float)(g_PhdWinMinX + x1); + g_VBufferD3D[0].sy = (float)(g_PhdWinMinY + y1); + g_VBufferD3D[1].sx = (float)(g_PhdWinMinX + x2); + g_VBufferD3D[1].sy = (float)(g_PhdWinMinY + y2); + + for (int32_t i = 0; i < 2; i++) { + g_VBufferD3D[i].sz = sz; + g_VBufferD3D[i].rhw = rhw; + g_VBufferD3D[i].color = d3d_color; + } + + HWR_TexSource(0); + HWR_EnableColorKey(0); + HWR_DrawPrimitive(D3DPT_LINESTRIP, g_VBufferD3D, 2, true); +} + +const int16_t *__cdecl Output_InsertObjectG3_Sorted( + const int16_t *obj_ptr, const int32_t num, const SORT_TYPE sort_type) +{ + for (int32_t i = 0; i < num; i++) { + if (HWR_VertexBufferFull()) { + obj_ptr += (num - i) * 4; + break; + } + + int32_t num_points = 3; + const PHD_VBUF *vtx[3] = { + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + }; + const int16_t color_idx = *obj_ptr++; + + const int8_t clip_or = vtx[0]->clip | vtx[1]->clip | vtx[2]->clip; + const int8_t clip_and = vtx[0]->clip & vtx[1]->clip & vtx[2]->clip; + if (clip_and != 0) { + continue; + } + + if (clip_or >= 0) { + if (!VBUF_VISIBLE(*vtx[0], *vtx[1], *vtx[2])) { + continue; + } + + g_VBuffer[0].x = vtx[0]->xs; + g_VBuffer[0].y = vtx[0]->ys; + g_VBuffer[0].rhw = vtx[0]->rhw; + g_VBuffer[0].g = (float)vtx[0]->g; + + g_VBuffer[1].x = vtx[1]->xs; + g_VBuffer[1].y = vtx[1]->ys; + g_VBuffer[1].rhw = vtx[1]->rhw; + g_VBuffer[1].g = (float)vtx[1]->g; + + g_VBuffer[2].x = vtx[2]->xs; + g_VBuffer[2].y = vtx[2]->ys; + g_VBuffer[2].rhw = vtx[2]->rhw; + g_VBuffer[2].g = (float)vtx[2]->g; + + if (clip_or > 0) { + num_points = Output_XYGClipper(num_points, g_VBuffer); + } + } else { + if (!Output_VisibleZClip(vtx[0], vtx[1], vtx[2])) { + continue; + } + + const POINT_INFO pts[3] = { + { + .xv = vtx[0]->xv, + .yv = vtx[0]->yv, + .zv = vtx[0]->zv, + .rhw = vtx[0]->rhw, + .xs = vtx[0]->xs, + .ys = vtx[0]->ys, + .g = (float)vtx[0]->g, + }, + + { + .xv = vtx[1]->xv, + .yv = vtx[1]->yv, + .zv = vtx[1]->zv, + .rhw = vtx[1]->rhw, + .xs = vtx[1]->xs, + .ys = vtx[1]->ys, + .g = (float)vtx[1]->g, + }, + + { + .xv = vtx[2]->xv, + .yv = vtx[2]->yv, + .zv = vtx[2]->zv, + .rhw = vtx[2]->rhw, + .xs = vtx[2]->xs, + .ys = vtx[2]->ys, + .g = (float)vtx[2]->g, + }, + }; + + num_points = Output_ZedClipper(num_points, pts, g_VBuffer); + if (num_points == 0) { + continue; + } + + num_points = Output_XYGClipper(num_points, g_VBuffer); + } + + if (num_points == 0) { + continue; + } + + const PALETTEENTRY *const color = &g_GamePalette16[color_idx >> 8]; + const double zv = M_CalculatePolyZ( + sort_type, vtx[0]->zv, vtx[1]->zv, vtx[2]->zv, -1.0); + Output_InsertPoly_Gouraud( + num_points, zv, color->peRed, color->peGreen, color->peBlue, + POLY_HWR_GOURAUD); + } + + return obj_ptr; +} + +const int16_t *__cdecl Output_InsertObjectG4_Sorted( + const int16_t *obj_ptr, const int32_t num, const SORT_TYPE sort_type) +{ + for (int32_t i = 0; i < num; i++) { + if (HWR_VertexBufferFull()) { + obj_ptr += (num - i) * 5; + break; + } + + int32_t num_points = 4; + const PHD_VBUF *const vtx[4] = { + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + }; + const int16_t color_idx = *obj_ptr++; + + const int8_t clip_or = + vtx[0]->clip | vtx[1]->clip | vtx[2]->clip | vtx[3]->clip; + const int8_t clip_and = + vtx[0]->clip & vtx[1]->clip & vtx[2]->clip & vtx[3]->clip; + if (clip_and != 0) { + continue; + } + + if (clip_or >= 0) { + if (!VBUF_VISIBLE(*vtx[0], *vtx[1], *vtx[2])) { + continue; + } + + g_VBuffer[0].x = vtx[0]->xs; + g_VBuffer[0].y = vtx[0]->ys; + g_VBuffer[0].rhw = vtx[0]->rhw; + g_VBuffer[0].g = (float)vtx[0]->g; + + g_VBuffer[1].x = vtx[1]->xs; + g_VBuffer[1].y = vtx[1]->ys; + g_VBuffer[1].rhw = vtx[1]->rhw; + g_VBuffer[1].g = (float)vtx[1]->g; + + g_VBuffer[2].x = vtx[2]->xs; + g_VBuffer[2].y = vtx[2]->ys; + g_VBuffer[2].rhw = vtx[2]->rhw; + g_VBuffer[2].g = (float)vtx[2]->g; + + g_VBuffer[3].x = vtx[3]->xs; + g_VBuffer[3].y = vtx[3]->ys; + g_VBuffer[3].rhw = vtx[3]->rhw; + g_VBuffer[3].g = (float)vtx[3]->g; + + if (clip_or > 0) { + num_points = Output_XYGClipper(num_points, g_VBuffer); + } + } else { + if (!Output_VisibleZClip(vtx[0], vtx[1], vtx[2])) { + continue; + } + + const POINT_INFO pts[4] = { + { + .xv = vtx[0]->xv, + .yv = vtx[0]->yv, + .zv = vtx[0]->zv, + .rhw = vtx[0]->rhw, + .xs = vtx[0]->xs, + .ys = vtx[0]->ys, + .g = (float)vtx[0]->g, + }, + + { + .xv = vtx[1]->xv, + .yv = vtx[1]->yv, + .zv = vtx[1]->zv, + .rhw = vtx[1]->rhw, + .xs = vtx[1]->xs, + .ys = vtx[1]->ys, + .g = (float)vtx[1]->g, + }, + + { + .xv = vtx[2]->xv, + .yv = vtx[2]->yv, + .zv = vtx[2]->zv, + .rhw = vtx[2]->rhw, + .xs = vtx[2]->xs, + .ys = vtx[2]->ys, + .g = (float)vtx[2]->g, + }, + + { + .xv = vtx[3]->xv, + .yv = vtx[3]->yv, + .zv = vtx[3]->zv, + .rhw = vtx[3]->rhw, + .xs = vtx[3]->xs, + .ys = vtx[3]->ys, + .g = (float)vtx[3]->g, + }, + }; + + num_points = Output_ZedClipper(num_points, pts, g_VBuffer); + if (num_points == 0) { + continue; + } + + num_points = Output_XYGClipper(num_points, g_VBuffer); + } + + if (num_points == 0) { + continue; + } + + const PALETTEENTRY *const color = &g_GamePalette16[color_idx >> 8]; + const double zv = M_CalculatePolyZ( + sort_type, vtx[0]->zv, vtx[1]->zv, vtx[2]->zv, vtx[3]->zv); + Output_InsertPoly_Gouraud( + num_points, zv, color->peRed, color->peGreen, color->peBlue, + POLY_HWR_GOURAUD); + } + + return obj_ptr; +} + +const int16_t *__cdecl Output_InsertObjectGT3_Sorted( + const int16_t *obj_ptr, const int32_t num, const SORT_TYPE sort_type) +{ + for (int32_t i = 0; i < num; i++) { + if (HWR_VertexBufferFull()) { + obj_ptr += (num - i) * 4; + break; + } + + const PHD_VBUF *const vtx[3] = { + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + }; + const int16_t texture_idx = *obj_ptr++; + const PHD_TEXTURE *const texture = &g_PhdTextureInfo[texture_idx]; + const PHD_UV *const uv = texture->uv; + + Output_InsertGT3_Sorted( + vtx[0], vtx[1], vtx[2], texture, &uv[0], &uv[1], &uv[2], sort_type); + } + + return obj_ptr; +} + +const int16_t *__cdecl Output_InsertObjectGT4_Sorted( + const int16_t *obj_ptr, const int32_t num, const SORT_TYPE sort_type) +{ + for (int32_t i = 0; i < num; i++) { + if (HWR_VertexBufferFull()) { + obj_ptr += (num - i) * 5; + break; + } + + const PHD_VBUF *const vtx[4] = { + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + &g_PhdVBuf[*obj_ptr++], + }; + const int16_t texture_idx = *obj_ptr++; + const PHD_TEXTURE *const texture = &g_PhdTextureInfo[texture_idx]; + + Output_InsertGT4_Sorted( + vtx[0], vtx[1], vtx[2], vtx[3], texture, sort_type); + } + + return obj_ptr; +} + +void __cdecl Output_InsertGT3_Sorted( + const PHD_VBUF *const vtx0, const PHD_VBUF *const vtx1, + const PHD_VBUF *const vtx2, const PHD_TEXTURE *const texture, + const PHD_UV *const uv0, const PHD_UV *const uv1, const PHD_UV *const uv2, + const SORT_TYPE sort_type) +{ + const int8_t clip_or = vtx0->clip | vtx1->clip | vtx2->clip; + const int8_t clip_and = vtx0->clip & vtx1->clip & vtx2->clip; + if (clip_and != 0) { + return; + } + + const double zv = + M_CalculatePolyZ(sort_type, vtx0->zv, vtx1->zv, vtx2->zv, -1.0); + const POLY_TYPE poly_type = + texture->draw_type == DRAW_OPAQUE ? POLY_HWR_GTMAP : POLY_HWR_WGTMAP; + + int32_t num_points = 3; + if (clip_or >= 0) { + if (!VBUF_VISIBLE(*vtx0, *vtx1, *vtx2)) { + return; + } + + if (clip_or == 0) { + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(zv); + g_Sort3DPtr++; + + *g_Info3DPtr++ = poly_type; + *g_Info3DPtr++ = texture->tex_page; + *g_Info3DPtr++ = num_points; + *(D3DTLVERTEX **)g_Info3DPtr = g_HWR_VertexPtr; + g_Info3DPtr += sizeof(D3DTLVERTEX *) / sizeof(int16_t); + + g_HWR_VertexPtr[0].sx = vtx0->xs; + g_HWR_VertexPtr[0].sy = vtx0->ys; + g_HWR_VertexPtr[0].rhw = vtx0->rhw; + g_HWR_VertexPtr[0].color = M_ShadeLight(vtx0->g); + g_HWR_VertexPtr[0].tu = (double)uv0->u / (double)PHD_ONE; + g_HWR_VertexPtr[0].tv = (double)uv0->v / (double)PHD_ONE; + + g_HWR_VertexPtr[1].sx = vtx1->xs; + g_HWR_VertexPtr[1].sy = vtx1->ys; + g_HWR_VertexPtr[1].rhw = vtx1->rhw; + g_HWR_VertexPtr[1].color = M_ShadeLight(vtx1->g); + g_HWR_VertexPtr[1].tu = (double)uv1->u / (double)PHD_ONE; + g_HWR_VertexPtr[1].tv = (double)uv1->v / (double)PHD_ONE; + + g_HWR_VertexPtr[2].sx = vtx2->xs; + g_HWR_VertexPtr[2].sy = vtx2->ys; + g_HWR_VertexPtr[2].rhw = vtx2->rhw; + g_HWR_VertexPtr[2].color = M_ShadeLight(vtx2->g); + g_HWR_VertexPtr[2].tu = (double)uv2->u / (double)PHD_ONE; + g_HWR_VertexPtr[2].tv = (double)uv2->v / (double)PHD_ONE; + + if (g_SavedAppSettings.zbuffer) { + g_HWR_VertexPtr[0].sz = + g_FltResZBuf - g_FltResZORhw * vtx0->rhw; + g_HWR_VertexPtr[1].sz = + g_FltResZBuf - g_FltResZORhw * vtx1->rhw; + g_HWR_VertexPtr[2].sz = + g_FltResZBuf - g_FltResZORhw * vtx2->rhw; + } + + g_HWR_VertexPtr += 3; + g_SurfaceCount++; + return; + } + + g_VBuffer[0].x = vtx0->xs; + g_VBuffer[0].y = vtx0->ys; + g_VBuffer[0].rhw = vtx0->rhw; + g_VBuffer[0].g = (double)vtx0->g; + g_VBuffer[0].u = (double)uv0->u * vtx0->rhw; + g_VBuffer[0].v = (double)uv0->v * vtx0->rhw; + + g_VBuffer[1].x = vtx1->xs; + g_VBuffer[1].y = vtx1->ys; + g_VBuffer[1].rhw = vtx1->rhw; + g_VBuffer[1].g = (double)vtx1->g; + g_VBuffer[1].u = (double)uv1->u * vtx1->rhw; + g_VBuffer[1].v = (double)uv1->v * vtx1->rhw; + + g_VBuffer[2].x = vtx2->xs; + g_VBuffer[2].y = vtx2->ys; + g_VBuffer[2].rhw = vtx2->rhw; + g_VBuffer[2].g = (double)vtx2->g; + g_VBuffer[2].u = (double)uv2->u * vtx2->rhw; + g_VBuffer[2].v = (double)uv2->v * vtx2->rhw; + } else { + if (!Output_VisibleZClip(vtx0, vtx1, vtx2)) { + return; + } + + const POINT_INFO pts[3] = { + { + .xv = vtx0->xv, + .yv = vtx0->yv, + .zv = vtx0->zv, + .rhw = vtx0->rhw, + .xs = vtx0->xs, + .ys = vtx0->ys, + .g = (float)vtx0->g, + .u = (float)uv0->u, + .v = (float)uv0->v, + }, + + { + .xv = vtx1->xv, + .yv = vtx1->yv, + .zv = vtx1->zv, + .rhw = vtx1->rhw, + .xs = vtx1->xs, + .ys = vtx1->ys, + .g = (float)vtx1->g, + .u = (float)uv1->u, + .v = (float)uv1->v, + }, + + { + .xv = vtx2->xv, + .yv = vtx2->yv, + .zv = vtx2->zv, + .rhw = vtx2->rhw, + .xs = vtx2->xs, + .ys = vtx2->ys, + .g = (float)vtx2->g, + .u = (float)uv2->u, + .v = (float)uv2->v, + }, + }; + + num_points = Output_ZedClipper(num_points, pts, g_VBuffer); + if (num_points == 0) { + return; + } + } + + num_points = Output_XYGUVClipper(num_points, g_VBuffer); + if (num_points == 0) { + return; + } + + Output_InsertClippedPoly_Textured( + num_points, zv, poly_type, texture->tex_page); +} + +void __cdecl Output_InsertGT4_Sorted( + const PHD_VBUF *const vtx0, const PHD_VBUF *const vtx1, + const PHD_VBUF *const vtx2, const PHD_VBUF *const vtx3, + const PHD_TEXTURE *const texture, const SORT_TYPE sort_type) +{ + const int8_t clip_or = vtx0->clip | vtx1->clip | vtx2->clip | vtx3->clip; + const int8_t clip_and = vtx0->clip & vtx1->clip & vtx2->clip & vtx3->clip; + if (clip_and != 0) { + return; + } + + const double zv = + M_CalculatePolyZ(sort_type, vtx0->zv, vtx1->zv, vtx2->zv, vtx3->zv); + const POLY_TYPE poly_type = + texture->draw_type == DRAW_OPAQUE ? POLY_HWR_GTMAP : POLY_HWR_WGTMAP; + + int32_t num_points = 4; + if (clip_or >= 0) { + if (!VBUF_VISIBLE(*vtx0, *vtx1, *vtx2)) { + return; + } + + if (clip_or == 0) { + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(zv); + g_Sort3DPtr++; + + g_Info3DPtr[0] = poly_type; + g_Info3DPtr[1] = texture->tex_page; + g_Info3DPtr[2] = num_points; + *(int32_t *)(&g_Info3DPtr[3]) = (int32_t)g_HWR_VertexPtr; + g_Info3DPtr += 5; + + g_HWR_VertexPtr[0].sx = vtx0->xs; + g_HWR_VertexPtr[0].sy = vtx0->ys; + g_HWR_VertexPtr[0].rhw = vtx0->rhw; + g_HWR_VertexPtr[0].color = M_ShadeLight(vtx0->g); + g_HWR_VertexPtr[0].tu = (double)texture->uv[0].u / (double)PHD_ONE; + g_HWR_VertexPtr[0].tv = (double)texture->uv[0].v / (double)PHD_ONE; + + g_HWR_VertexPtr[1].sx = vtx1->xs; + g_HWR_VertexPtr[1].sy = vtx1->ys; + g_HWR_VertexPtr[1].rhw = vtx1->rhw; + g_HWR_VertexPtr[1].color = M_ShadeLight(vtx1->g); + g_HWR_VertexPtr[1].tu = (double)texture->uv[1].u / (double)PHD_ONE; + g_HWR_VertexPtr[1].tv = (double)texture->uv[1].v / (double)PHD_ONE; + + g_HWR_VertexPtr[2].sx = vtx2->xs; + g_HWR_VertexPtr[2].sy = vtx2->ys; + g_HWR_VertexPtr[2].rhw = vtx2->rhw; + g_HWR_VertexPtr[2].color = M_ShadeLight(vtx2->g); + g_HWR_VertexPtr[2].tu = (double)texture->uv[2].u / (double)PHD_ONE; + g_HWR_VertexPtr[2].tv = (double)texture->uv[2].v / (double)PHD_ONE; + + g_HWR_VertexPtr[3].sx = vtx3->xs; + g_HWR_VertexPtr[3].sy = vtx3->ys; + g_HWR_VertexPtr[3].rhw = vtx3->rhw; + g_HWR_VertexPtr[3].color = M_ShadeLight(vtx3->g); + g_HWR_VertexPtr[3].tu = (double)texture->uv[3].u / (double)PHD_ONE; + g_HWR_VertexPtr[3].tv = (double)texture->uv[3].v / (double)PHD_ONE; + + if (g_SavedAppSettings.zbuffer) { + g_HWR_VertexPtr[0].sz = + g_FltResZBuf - g_FltResZORhw * vtx0->rhw; + g_HWR_VertexPtr[1].sz = + g_FltResZBuf - g_FltResZORhw * vtx1->rhw; + g_HWR_VertexPtr[2].sz = + g_FltResZBuf - g_FltResZORhw * vtx2->rhw; + g_HWR_VertexPtr[3].sz = + g_FltResZBuf - g_FltResZORhw * vtx3->rhw; + } + + g_HWR_VertexPtr += 4; + g_SurfaceCount++; + return; + } + + Output_InsertGT3_Sorted( + vtx0, vtx1, vtx2, texture, &texture->uv[0], &texture->uv[1], + &texture->uv[2], sort_type); + Output_InsertGT3_Sorted( + vtx0, vtx2, vtx3, texture, &texture->uv[0], &texture->uv[2], + &texture->uv[3], sort_type); + } else { + if (!Output_VisibleZClip(vtx0, vtx1, vtx2)) { + return; + } + + Output_InsertGT3_Sorted( + vtx0, vtx1, vtx2, texture, &texture->uv[0], &texture->uv[1], + &texture->uv[2], sort_type); + Output_InsertGT3_Sorted( + vtx0, vtx2, vtx3, texture, &texture->uv[0], &texture->uv[2], + &texture->uv[3], sort_type); + } +} + +void __cdecl Output_InsertFlatRect_Sorted( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, const int32_t z, + const uint8_t color_idx) +{ + if (x2 <= x1 || y2 <= y1) { + return; + } + + CLAMPL(x1, g_PhdWinMinX); + CLAMPL(y1, g_PhdWinMinY); + CLAMPG(x2, g_PhdWinWidth + g_PhdWinMinX); + CLAMPG(y2, g_PhdWinMinY + g_PhdWinHeight); + + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(z); + g_Sort3DPtr++; + + *g_Info3DPtr++ = POLY_HWR_GOURAUD; + *g_Info3DPtr++ = 4; + *(D3DTLVERTEX **)g_Info3DPtr = g_HWR_VertexPtr; + g_Info3DPtr += sizeof(D3DTLVERTEX *) / sizeof(int16_t); + + const RGB_888 *const color = &g_GamePalette8[color_idx]; + const D3DCOLOR d3d_color = + M_ShadeColor(color->red, color->green, color->blue, 0xFF); + + g_HWR_VertexPtr[0].sx = (float)x1; + g_HWR_VertexPtr[0].sy = (float)y1; + g_HWR_VertexPtr[1].sx = (float)x2; + g_HWR_VertexPtr[1].sy = (float)y1; + g_HWR_VertexPtr[2].sx = (float)x2; + g_HWR_VertexPtr[2].sy = (float)y2; + g_HWR_VertexPtr[3].sx = (float)x1; + g_HWR_VertexPtr[3].sy = (float)y2; + + for (int32_t i = 0; i < 4; i++) { + g_HWR_VertexPtr[i].color = d3d_color; + // TODO: missing sz and rhw initialization + } + + g_HWR_VertexPtr += 4; + g_SurfaceCount++; +} + +void __cdecl Output_InsertLine_Sorted( + const int32_t x1, const int32_t y1, const int32_t x2, const int32_t y2, + int32_t z, const uint8_t color_idx) +{ + const RGB_888 *const color = &g_GamePalette8[color_idx]; + const D3DCOLOR d3d_color = + M_ShadeColor(color->red, color->green, color->blue, 0xFF); + + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(z); + g_Sort3DPtr++; + + *g_Info3DPtr++ = POLY_HWR_LINE; + *g_Info3DPtr++ = 2; + *(D3DTLVERTEX **)g_Info3DPtr = g_HWR_VertexPtr; + g_Info3DPtr += sizeof(D3DTLVERTEX *) / sizeof(int16_t); + + g_HWR_VertexPtr[0].sx = (float)(g_PhdWinMinX + x1); + g_HWR_VertexPtr[0].sy = (float)(g_PhdWinMinY + y1); + g_HWR_VertexPtr[1].sx = (float)(g_PhdWinMinX + x2); + g_HWR_VertexPtr[1].sy = (float)(g_PhdWinMinY + y2); + + for (int32_t i = 0; i < 2; i++) { + g_HWR_VertexPtr[i].color = d3d_color; + // TODO: missing sz and rhw initialization + } + + g_HWR_VertexPtr += 2; + g_SurfaceCount++; +} + +void __cdecl Output_InsertSprite_Sorted( + int32_t z, int32_t x0, int32_t y0, int32_t x1, int32_t y1, + const int32_t sprite_idx, const int16_t shade) +{ + if (HWR_VertexBufferFull() || x0 >= x1 || y0 >= y1 || x1 <= 0 || y1 <= 0 + || x0 >= g_PhdWinMaxX || y0 >= g_PhdWinMaxY || z >= g_PhdFarZ) { + return; + } + + x0 += g_PhdWinMinX; + x1 += g_PhdWinMinX; + y0 += g_PhdWinMinY; + y1 += g_PhdWinMinY; + + CLAMPL(z, g_PhdNearZ); + + int32_t num_points = 4; + + const PHD_SPRITE *const sprite = &g_PhdSprites[sprite_idx]; + const double rhw = g_RhwFactor / (double)z; + const int32_t u_offset = (sprite->offset & 0xFF) * 256; + const int32_t v_offset = (sprite->offset >> 8) * 256; + + const int32_t adjustment = g_UVAdd; + const double u0 = (double)(u_offset - adjustment + sprite->width) * rhw; + const double u1 = (double)(u_offset + adjustment) * rhw; + const double v1 = (double)(v_offset - adjustment + sprite->height) * rhw; + const double v0 = (double)(v_offset + adjustment) * rhw; + + g_VBuffer[0].x = (float)x0; + g_VBuffer[0].y = (float)y0; + g_VBuffer[0].u = u1; + g_VBuffer[0].v = v0; + + g_VBuffer[1].x = (float)x1; + g_VBuffer[1].y = (float)y0; + g_VBuffer[1].u = u0; + g_VBuffer[1].v = v0; + + g_VBuffer[2].x = (float)x1; + g_VBuffer[2].y = (float)y1; + g_VBuffer[2].u = u0; + g_VBuffer[2].v = v1; + + g_VBuffer[3].x = (float)x0; + g_VBuffer[3].y = (float)y1; + g_VBuffer[3].u = u1; + g_VBuffer[3].v = v1; + + for (int32_t i = 0; i < 4; i++) { + g_VBuffer[i].rhw = rhw; + g_VBuffer[i].g = (float)shade; + } + + if (x0 < g_PhdWinMinX || y0 < g_PhdWinMinY + || x1 > g_PhdWinWidth + g_PhdWinMinX + || y1 > g_PhdWinHeight + g_PhdWinMinY) { + g_FltWinLeft = (float)g_PhdWinMinX; + g_FltWinTop = (float)g_PhdWinMinY; + g_FltWinRight = (float)(g_PhdWinMinX + g_PhdWinWidth); + g_FltWinBottom = (float)(g_PhdWinMinY + g_PhdWinHeight); + num_points = Output_XYGUVClipper(num_points, g_VBuffer); + if (num_points == 0) { + return; + } + } + + const bool old_shade = g_IsShadeEffect; + g_IsShadeEffect = 0; + Output_InsertClippedPoly_Textured( + num_points, z, POLY_HWR_WGTMAP, sprite->tex_page); + g_IsShadeEffect = old_shade; +} + +void __cdecl Output_InsertTrans8_Sorted( + const PHD_VBUF *const vbuf, const int16_t shade) +{ + int8_t clip_or = 0x00; + int8_t clip_and = 0xFF; + int32_t num_vtx = 8; + + for (int32_t i = 0; i < num_vtx; i++) { + clip_or |= vbuf[i].clip; + clip_and &= vbuf[i].clip; + } + + if (clip_or < 0 || clip_and != 0 + || !VBUF_VISIBLE(vbuf[0], vbuf[1], vbuf[2])) { + return; + } + + for (int32_t i = 0; i < num_vtx; i++) { + g_VBuffer[i].x = vbuf[i].xs; + g_VBuffer[i].y = vbuf[i].ys; + g_VBuffer[i].rhw = g_RhwFactor / (double)(vbuf[i].zv - 0x20000); + } + + int32_t num_points = num_vtx; + if (clip_or != 0) { + g_FltWinLeft = (float)g_PhdWinMinX; + g_FltWinTop = (float)g_PhdWinMinY; + g_FltWinRight = (float)(g_PhdWinMinX + g_PhdWinWidth); + g_FltWinBottom = (float)(g_PhdWinMinY + g_PhdWinHeight); + num_points = Output_XYClipper(num_points, g_VBuffer); + if (num_points == 0) { + return; + } + } + + int32_t poly_z = 0; + for (int32_t i = 0; i < num_vtx; i++) { + poly_z += vbuf[i].zv; + } + poly_z /= num_vtx; + + Output_InsertPoly_Gouraud( + num_points, (double)(poly_z - 0x20000), 0, 0, 0, POLY_HWR_TRANS); +} + +void __cdecl Output_InsertTransQuad_Sorted( + const int32_t x, const int32_t y, const int32_t width, const int32_t height, + const int32_t z) +{ + const double x0 = (double)x; + const double y0 = (double)y; + const double x1 = (double)(x + width); + const double y1 = (double)(y + height); + + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(z); + g_Sort3DPtr++; + + *g_Info3DPtr++ = POLY_HWR_TRANS; + *g_Info3DPtr++ = 4; + *(D3DTLVERTEX **)g_Info3DPtr = g_HWR_VertexPtr; + g_Info3DPtr += sizeof(D3DTLVERTEX *) / sizeof(int16_t); + + g_HWR_VertexPtr[0].sx = x0; + g_HWR_VertexPtr[0].sy = y0; + g_HWR_VertexPtr[1].sx = x1; + g_HWR_VertexPtr[1].sy = y0; + g_HWR_VertexPtr[2].sx = x1; + g_HWR_VertexPtr[2].sy = y1; + g_HWR_VertexPtr[3].sx = x0; + g_HWR_VertexPtr[3].sy = y1; + + for (int32_t i = 0; i < 4; i++) { + g_HWR_VertexPtr[i].color = 0x80000000; + } + + if (g_SavedAppSettings.zbuffer) { + const double rhw = g_RhwFactor / (double)z; + const double sz = g_FltResZBuf - rhw * g_FltResZORhw; + for (int32_t i = 0; i < 4; i++) { + g_HWR_VertexPtr[i].rhw = rhw; + g_HWR_VertexPtr[i].sz = sz; + } + } + + g_HWR_VertexPtr += 4; + g_SurfaceCount++; +} + +void __cdecl Output_InsertSprite( + const int32_t z, const int32_t x0, const int32_t y0, const int32_t x1, + const int32_t y1, const int32_t sprite_idx, const int16_t shade) +{ + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(z); + g_Sort3DPtr++; + + *g_Info3DPtr++ = POLY_SPRITE; + *g_Info3DPtr++ = x0; + *g_Info3DPtr++ = y0; + *g_Info3DPtr++ = x1; + *g_Info3DPtr++ = y1; + *g_Info3DPtr++ = sprite_idx; + *g_Info3DPtr++ = shade; + g_SurfaceCount++; +} + +const int16_t *__cdecl Output_InsertRoomSprite( + const int16_t *obj_ptr, const int32_t vtx_count) +{ + for (int32_t i = 0; i < vtx_count; i++) { + const PHD_VBUF *vbuf = &g_PhdVBuf[*obj_ptr++]; + const int16_t sprite_idx = *obj_ptr++; + if ((int8_t)vbuf->clip < 0) { + continue; + } + + const PHD_SPRITE *const sprite = &g_PhdSprites[sprite_idx]; + const double persp = (double)(vbuf->zv / g_PhdPersp); + const double x0 = + g_PhdWinCenterX + (vbuf->xv + (sprite->x0 << W2V_SHIFT)) / persp; + const double y0 = + g_PhdWinCenterY + (vbuf->yv + (sprite->y0 << W2V_SHIFT)) / persp; + const double x1 = + g_PhdWinCenterX + (vbuf->xv + (sprite->x1 << W2V_SHIFT)) / persp; + const double y1 = + g_PhdWinCenterY + (vbuf->yv + (sprite->y1 << W2V_SHIFT)) / persp; + if (x1 >= g_PhdWinLeft && y1 >= g_PhdWinTop && x0 < g_PhdWinRight + && y0 < g_PhdWinBottom) { + g_Output_InsertSprite( + vbuf->zv, x0, y0, x1, y1, sprite_idx, vbuf->g); + } + } + + return obj_ptr; +} + +void __cdecl Output_InsertClippedPoly_Textured( + const int32_t vtx_count, const float z, const int16_t poly_type, + const int16_t tex_page) +{ + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(z); + g_Sort3DPtr++; + + *g_Info3DPtr++ = poly_type; + *g_Info3DPtr++ = tex_page; + *g_Info3DPtr++ = vtx_count; + *(D3DTLVERTEX **)g_Info3DPtr = g_HWR_VertexPtr; + g_Info3DPtr += sizeof(D3DTLVERTEX *) / sizeof(int16_t); + + for (int32_t i = 0; i < vtx_count; i++) { + double tu = g_VBuffer[i].u / (double)PHD_ONE / g_VBuffer[i].rhw; + double tv = g_VBuffer[i].v / (double)PHD_ONE / g_VBuffer[i].rhw; + CLAMP(tu, 0.0, 1.0); + CLAMP(tv, 0.0, 1.0); + + g_HWR_VertexPtr[i].sx = g_VBuffer[i].x; + g_HWR_VertexPtr[i].sy = g_VBuffer[i].y; + if (g_SavedAppSettings.zbuffer) { + g_HWR_VertexPtr[i].sz = + g_FltResZBuf - g_FltResZORhw * g_VBuffer[i].rhw; + } + g_HWR_VertexPtr[i].rhw = g_VBuffer[i].rhw; + g_HWR_VertexPtr[i].color = M_ShadeLight(g_VBuffer[i].g); + g_HWR_VertexPtr[i].tu = tu; + g_HWR_VertexPtr[i].tv = tv; + } + + g_HWR_VertexPtr += vtx_count; + g_SurfaceCount++; +} + +void __cdecl Output_InsertPoly_Gouraud( + const int32_t vtx_count, const float z, const int32_t red, + const int32_t green, const int32_t blue, const int16_t poly_type) +{ + g_Sort3DPtr->_0 = (int32_t)g_Info3DPtr; + g_Sort3DPtr->_1 = MAKE_ZSORT(z); + g_Sort3DPtr++; + + *g_Info3DPtr++ = poly_type; + *g_Info3DPtr++ = vtx_count; + *(D3DTLVERTEX **)g_Info3DPtr = g_HWR_VertexPtr; + g_Info3DPtr += sizeof(D3DTLVERTEX *) / sizeof(int16_t); + + for (int32_t i = 0; i < vtx_count; i++) { + g_HWR_VertexPtr[i].sx = g_VBuffer[i].x; + g_HWR_VertexPtr[i].sy = g_VBuffer[i].y; + if (g_SavedAppSettings.zbuffer) { + g_HWR_VertexPtr[i].sz = + g_FltResZBuf - g_FltResZORhw * g_VBuffer[i].rhw; + } + g_HWR_VertexPtr[i].rhw = g_VBuffer[i].rhw; + g_HWR_VertexPtr[i].color = M_ShadeLightColor( + g_VBuffer[i].g, red, green, blue, + poly_type == POLY_HWR_TRANS ? 0x80 : 0xFF); + } + + g_HWR_VertexPtr += vtx_count; + g_SurfaceCount++; +} + +void __cdecl Output_DrawClippedPoly_Textured(const int32_t vtx_count) +{ + for (int32_t i = 0; i < vtx_count; i++) { + const VERTEX_INFO *const vbuf = &g_VBuffer[i]; + D3DTLVERTEX *const vbuf_d3d = &g_VBufferD3D[i]; + vbuf_d3d->sx = vbuf->x; + vbuf_d3d->sy = vbuf->y; + vbuf_d3d->sz = g_FltResZBuf - g_FltResZORhw * vbuf->rhw; + vbuf_d3d->rhw = vbuf->rhw; + vbuf_d3d->color = M_ShadeLight(vbuf->g); + + double tu = vbuf->u / (double)PHD_ONE / vbuf->rhw; + double tv = vbuf->v / (double)PHD_ONE / vbuf->rhw; + CLAMP(tu, 0.0, 1.0); + CLAMP(tv, 0.0, 1.0); + + vbuf_d3d->tv = tv; + vbuf_d3d->tu = tu; + } + + HWR_DrawPrimitive(D3DPT_TRIANGLEFAN, g_VBufferD3D, vtx_count, true); +} + +void __cdecl Output_DrawPoly_Gouraud( + const int32_t vtx_count, const int32_t red, const int32_t green, + const int32_t blue) +{ + for (int32_t i = 0; i < vtx_count; i++) { + const VERTEX_INFO *vbuf = &g_VBuffer[i]; + D3DTLVERTEX *vbuf_d3d = &g_VBufferD3D[i]; + vbuf_d3d->sx = vbuf->x; + vbuf_d3d->sy = vbuf->y; + vbuf_d3d->sz = g_FltResZBuf - g_FltResZORhw * vbuf->rhw; + vbuf_d3d->rhw = vbuf->rhw; + vbuf_d3d->color = M_ShadeLightColor(vbuf->g, red, green, blue, 0xFF); + } + + HWR_DrawPrimitive(D3DPT_TRIANGLEFAN, g_VBufferD3D, vtx_count, true); +} + +void __cdecl Output_DrawSprite( + const uint32_t flags, int32_t x, int32_t y, int32_t z, + const int16_t sprite_idx, int16_t shade, const int16_t scale) +{ + const MATRIX *const mptr = g_MatrixPtr; + + int32_t xv; + int32_t yv; + int32_t zv; + if (flags & SPRF_ABS) { + x -= g_W2VMatrix._03; + y -= g_W2VMatrix._13; + z -= g_W2VMatrix._23; + if (x < -g_PhdViewDistance || x > g_PhdViewDistance + || y < -g_PhdViewDistance || y > g_PhdViewDistance + || z < -g_PhdViewDistance || z > g_PhdViewDistance) { + return; + } + + zv = g_W2VMatrix._22 * z + g_W2VMatrix._21 * y + g_W2VMatrix._20 * x; + if (zv < g_PhdNearZ || zv >= g_PhdFarZ) { + return; + } + xv = g_W2VMatrix._02 * z + g_W2VMatrix._01 * y + g_W2VMatrix._00 * x; + yv = g_W2VMatrix._12 * z + g_W2VMatrix._11 * y + g_W2VMatrix._10 * x; + } else if (x | y | z) { + zv = x * mptr->_20 + y * mptr->_21 + z * mptr->_22 + mptr->_23; + if (zv < g_PhdNearZ || zv > g_PhdFarZ) { + return; + } + xv = x * mptr->_00 + y * mptr->_01 + z * mptr->_02 + mptr->_03; + yv = x * mptr->_10 + y * mptr->_11 + z * mptr->_12 + mptr->_13; + } else { + zv = mptr->_23; + if (zv < g_PhdNearZ || zv > g_PhdFarZ) { + return; + } + xv = mptr->_03; + yv = mptr->_13; + } + + const PHD_SPRITE *const sprite = &g_PhdSprites[sprite_idx]; + int32_t x0 = sprite->x0; + int32_t y0 = sprite->y0; + int32_t x1 = sprite->x1; + int32_t y1 = sprite->y1; + + if (flags & SPRF_SCALE) { + x0 = (x0 * scale) << (W2V_SHIFT - 8); + y0 = (y0 * scale) << (W2V_SHIFT - 8); + x1 = (x1 * scale) << (W2V_SHIFT - 8); + y1 = (y1 * scale) << (W2V_SHIFT - 8); + } else { + x0 <<= W2V_SHIFT; + y0 <<= W2V_SHIFT; + x1 <<= W2V_SHIFT; + y1 <<= W2V_SHIFT; + } + + int32_t zp = zv / g_PhdPersp; + + x0 = g_PhdWinCenterX + (x0 + xv) / zp; + if (x0 >= g_PhdWinWidth) { + return; + } + + y0 = g_PhdWinCenterY + (y0 + yv) / zp; + if (y0 >= g_PhdWinHeight) { + return; + } + + x1 = g_PhdWinCenterX + (x1 + xv) / zp; + if (x1 < 0) { + return; + } + + y1 = g_PhdWinCenterY + (y1 + yv) / zp; + if (y1 < 0) { + return; + } + + shade; + if (flags & SPRF_SHADE) { + const int32_t depth = zv >> W2V_SHIFT; + if (depth > FOG_START) { + shade += depth - FOG_START; + if (shade > 0x1FFF) { + return; + } + } + } else { + shade = 0x1000; + } + + g_Output_InsertSprite(zv, x0, y0, x1, y1, sprite_idx, shade); +} + +void __cdecl Output_DrawPickup( + const int32_t sx, const int32_t sy, const int32_t scale, + const int16_t sprite_idx, const int16_t shade) +{ + const PHD_SPRITE *const sprite = &g_PhdSprites[sprite_idx]; + const int32_t x0 = sx + ((sprite->x0 * scale) / PHD_ONE); + const int32_t y0 = sy + ((sprite->y0 * scale) / PHD_ONE); + const int32_t x1 = sx + ((sprite->x1 * scale) / PHD_ONE); + const int32_t y1 = sy + ((sprite->y1 * scale) / PHD_ONE); + if (x1 >= 0 && y1 >= 0 && x0 < g_PhdWinWidth && y0 < g_PhdWinHeight) { + g_Output_InsertSprite(200, x0, y0, x1, y1, sprite_idx, shade); + } +} + +void __cdecl Output_DrawScreenSprite2D( + const int32_t sx, const int32_t sy, const int32_t sz, const int32_t scale_h, + const int32_t scale_v, const int16_t sprite_idx, const int16_t shade, + const uint16_t flags) +{ + const PHD_SPRITE *const sprite = &g_PhdSprites[sprite_idx]; + const int32_t x0 = sx + ((sprite->x0 * scale_h) / PHD_ONE); + const int32_t y0 = sy + ((sprite->y0 * scale_v) / PHD_ONE); + const int32_t x1 = sx + ((sprite->x1 * scale_h) / PHD_ONE); + const int32_t y1 = sy + ((sprite->y1 * scale_v) / PHD_ONE); + const int32_t z = g_PhdNearZ + sz * 8; + if (x1 >= 0 && y1 >= 0 && x0 < g_PhdWinWidth && y0 < g_PhdWinHeight) { + g_Output_InsertSprite(z, x0, y0, x1, y1, sprite_idx, shade); + } +} + +void __cdecl Output_DrawScreenSprite( + const int32_t sx, const int32_t sy, const int32_t sz, const int32_t scale_h, + const int32_t scale_v, const int16_t sprite_idx, const int16_t shade, + const uint16_t flags) +{ + const PHD_SPRITE *const sprite = &g_PhdSprites[sprite_idx]; + const int32_t x0 = sx + (((sprite->x0 / 8) * scale_h) / PHD_ONE); + const int32_t x1 = sx + (((sprite->x1 / 8) * scale_h) / PHD_ONE); + const int32_t y0 = sy + (((sprite->y0 / 8) * scale_v) / PHD_ONE); + const int32_t y1 = sy + (((sprite->y1 / 8) * scale_v) / PHD_ONE); + const int32_t z = sz * 8; + if (x1 >= 0 && y1 >= 0 && x0 < g_PhdWinWidth && y0 < g_PhdWinHeight) { + g_Output_InsertSprite(z, x0, y0, x1, y1, sprite_idx, shade); + } +} + +void __cdecl Output_DrawScaledSpriteC(const int16_t *const obj_ptr) +{ + int32_t x0 = obj_ptr[0]; + int32_t y0 = obj_ptr[1]; + int32_t x1 = obj_ptr[2]; + int32_t y1 = obj_ptr[3]; + const int16_t sprite_idx = obj_ptr[4]; + const int16_t shade = obj_ptr[5]; + + if (x0 >= x1 || y0 >= y1 || x1 <= 0 || y1 <= 0 || x0 >= g_PhdWinMaxX + || y0 >= g_PhdWinMaxY) { + return; + } + + const DEPTHQ_ENTRY *const depth_q = &g_DepthQTable[shade >> 8]; + const PHD_SPRITE *const sprite = &g_PhdSprites[sprite_idx]; + + int32_t u_base = 0x4000; + int32_t v_base = 0x4000; + const int32_t u_add = ((sprite->width - 64) << 8) / (x1 - x0); + const int32_t v_add = ((sprite->height - 64) << 8) / (y1 - y0); + + if (x0 < 0) { + u_base -= x0 * u_add; + x0 = 0; + } + if (y0 < 0) { + v_base -= y0 * v_add; + y0 = 0; + } + CLAMPG(x1, g_PhdWinMaxX + 1); + CLAMPG(y1, g_PhdWinMaxY + 1); + + const int32_t stride = g_PhdScreenWidth; + const int32_t width = x1 - x0; + const int32_t height = y1 - y0; + + const uint8_t *const src_base = + &g_TexturePageBuffer8[sprite->tex_page][sprite->offset]; + uint8_t *dst = + &g_PrintSurfacePtr[(g_PhdWinMinY + y0) * stride + g_PhdWinMinX + x0]; + const int32_t dst_add = stride - width; + + const bool is_depth_q = + depth_q != &g_DepthQTable[16] || g_GameVid_IsWindowedVGA; + + for (int32_t i = 0; i < height; i++) { + int32_t u = u_base; + const uint8_t *const src = &src_base[(v_base >> 16) << 8]; + for (int32_t j = 0; j < width; j++) { + const uint8_t pix = src[u >> 16]; + if (pix != 0) { + *dst = is_depth_q ? depth_q->index[pix] : pix; + } + u += u_add; + dst++; + } + dst += dst_add; + v_base += v_add; + } +} diff --git a/src/tr2/game/output.h b/src/tr2/game/output.h new file mode 100644 index 000000000..b0c26315d --- /dev/null +++ b/src/tr2/game/output.h @@ -0,0 +1,173 @@ +#pragma once + +#include "global/types.h" + +#include + +void __cdecl Output_Init( + int16_t x, int16_t y, int32_t width, int32_t height, int32_t near_z, + int32_t far_z, int16_t view_angle, int32_t screen_width, + int32_t screen_height); + +void __cdecl Output_InsertPolygons(const int16_t *obj_ptr, int32_t clip); +void __cdecl Output_InsertPolygons_I(const int16_t *ptr, int32_t clip); +void __cdecl Output_InsertRoom(const int16_t *obj_ptr, int32_t is_outside); +void __cdecl Output_InsertSkybox(const int16_t *obj_ptr); +const int16_t *__cdecl Output_CalcObjectVertices(const int16_t *obj_ptr); +const int16_t *__cdecl Output_CalcSkyboxLight(const int16_t *obj_ptr); +const int16_t *__cdecl Output_CalcVerticeLight(const int16_t *obj_ptr); +const int16_t *__cdecl Output_CalcRoomVertices( + const int16_t *obj_ptr, int32_t far_clip); +void __cdecl Output_RotateLight(int16_t pitch, int16_t yaw); +void __cdecl Output_InitPolyList(void); +void __cdecl Output_SortPolyList(void); +void __cdecl Output_QuickSort(int32_t left, int32_t right); +void __cdecl Output_PrintPolyList(uint8_t *surface_ptr); +void __cdecl Output_SetNearZ(int32_t near_z); +void __cdecl Output_SetFarZ(int32_t far_z); +void __cdecl Output_AlterFOV(int16_t fov); + +void __cdecl Output_DrawPolyLine(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyFlat(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyTrans(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyGouraud(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyGTMap(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyWGTMap(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyGTMapPersp(const int16_t *obj_ptr); +void __cdecl Output_DrawPolyWGTMapPersp(const int16_t *obj_ptr); + +int32_t __cdecl Output_XGenX(const int16_t *obj_ptr); +int32_t __cdecl Output_XGenXG(const int16_t *obj_ptr); +int32_t __cdecl Output_XGenXGUV(const int16_t *obj_ptr); +int32_t __cdecl Output_XGenXGUVPerspFP(const int16_t *obj_ptr); +void __cdecl Output_GTMapPersp32FP( + int32_t y1, int32_t y2, const uint8_t *tex_page); +void __cdecl Output_WGTMapPersp32FP( + int32_t y1, int32_t y2, const uint8_t *tex_page); + +int32_t __cdecl Output_VisibleZClip( + const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2); +int32_t __cdecl Output_ZedClipper( + int32_t vtx_count, const POINT_INFO *pts, VERTEX_INFO *vtx); +int32_t __cdecl Output_XYClipper(int32_t vtx_count, VERTEX_INFO *vtx); +int32_t __cdecl Output_XYGClipper(int32_t vtx_count, VERTEX_INFO *vtx); +int32_t __cdecl Output_XYGUVClipper(int32_t vtx_count, VERTEX_INFO *vtx); + +const int16_t *__cdecl Output_InsertObjectG3( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectG4( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectGT3( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectGT4( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); + +void __cdecl Output_InsertTrans8(const PHD_VBUF *vbuf, int16_t shade); +void __cdecl Output_InsertTransQuad( + int32_t x, int32_t y, int32_t width, int32_t height, int32_t z); +void __cdecl Output_InsertFlatRect( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t z, + uint8_t color_idx); +void __cdecl Output_InsertLine( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t z, + uint8_t color_idx); + +const int16_t *__cdecl Output_InsertObjectG3_ZBuffered( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectG4_ZBuffered( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectGT3_ZBuffered( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); +const int16_t *__cdecl Output_InsertObjectGT4_ZBuffered( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); + +void __cdecl Output_InsertGT3_ZBuffered( + const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, + const PHD_TEXTURE *texture, const PHD_UV *uv0, const PHD_UV *uv1, + const PHD_UV *uv2); + +void __cdecl Output_InsertGT4_ZBuffered( + const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, + const PHD_VBUF *vtx3, const PHD_TEXTURE *texture); + +void __cdecl Output_InsertFlatRect_ZBuffered( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t z, + uint8_t color_idx); + +void __cdecl Output_InsertLine_ZBuffered( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t z, + uint8_t color_idx); + +const int16_t *__cdecl Output_InsertObjectG3_Sorted( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); + +const int16_t *__cdecl Output_InsertObjectG4_Sorted( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); + +const int16_t *__cdecl Output_InsertObjectGT3_Sorted( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); + +const int16_t *__cdecl Output_InsertObjectGT4_Sorted( + const int16_t *obj_ptr, int32_t num, SORT_TYPE sort_type); + +void __cdecl Output_InsertGT3_Sorted( + const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, + const PHD_TEXTURE *texture, const PHD_UV *uv0, const PHD_UV *uv1, + const PHD_UV *uv2, SORT_TYPE sort_type); + +void __cdecl Output_InsertGT4_Sorted( + const PHD_VBUF *vtx0, const PHD_VBUF *vtx1, const PHD_VBUF *vtx2, + const PHD_VBUF *vtx3, const PHD_TEXTURE *texture, SORT_TYPE sort_type); + +void __cdecl Output_InsertFlatRect_Sorted( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t z, + uint8_t color_idx); + +void __cdecl Output_InsertLine_Sorted( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t z, + uint8_t color_idx); + +void __cdecl Output_InsertSprite_Sorted( + int32_t z, int32_t x0, int32_t y0, int32_t x1, int32_t y1, + int32_t sprite_idx, int16_t shade); + +void __cdecl Output_InsertTrans8_Sorted(const PHD_VBUF *vbuf, int16_t shade); + +void __cdecl Output_InsertTransQuad_Sorted( + int32_t x, int32_t y, int32_t width, int32_t height, int32_t z); + +void __cdecl Output_InsertSprite( + int32_t z, int32_t x0, int32_t y0, int32_t x1, int32_t y1, + int32_t sprite_idx, int16_t shade); + +const int16_t *__cdecl Output_InsertRoomSprite( + const int16_t *obj_ptr, int32_t vtx_count); + +void __cdecl Output_InsertClippedPoly_Textured( + int32_t vtx_count, float z, int16_t poly_type, int16_t tex_page); + +void __cdecl Output_InsertPoly_Gouraud( + int32_t vtx_count, float z, int32_t red, int32_t green, int32_t blue, + int16_t poly_type); + +void __cdecl Output_DrawClippedPoly_Textured(int32_t vtx_count); + +void __cdecl Output_DrawPoly_Gouraud( + int32_t vtx_count, int32_t red, int32_t green, int32_t blue); + +void __cdecl Output_DrawSprite( + uint32_t flags, int32_t x, int32_t y, int32_t z, int16_t sprite_idx, + int16_t shade, int16_t scale); + +void __cdecl Output_DrawPickup( + int32_t sx, int32_t sy, int32_t scale, int16_t sprite_idx, int16_t shade); + +void __cdecl Output_DrawScreenSprite2D( + int32_t sx, int32_t sy, int32_t sz, int32_t scale_h, int32_t scale_v, + int16_t sprite_idx, int16_t shade, uint16_t flags); + +void __cdecl Output_DrawScreenSprite( + int32_t sx, int32_t sy, int32_t sz, int32_t scale_h, int32_t scale_v, + int16_t sprite_idx, int16_t shade, uint16_t flags); + +void __cdecl Output_DrawScaledSpriteC(const int16_t *obj_ptr); diff --git a/src/tr2/game/overlay.c b/src/tr2/game/overlay.c new file mode 100644 index 000000000..5b99ce50f --- /dev/null +++ b/src/tr2/game/overlay.c @@ -0,0 +1,302 @@ +#include "game/overlay.h" + +#include "game/music.h" +#include "game/output.h" +#include "game/text.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#include + +#define FLASH_FRAMES 5 +#define AMMO_X (-10) +#define AMMO_Y 35 +#define MODE_INFO_X (-16) +#define MODE_INFO_Y (-16) + +static int32_t m_OldGameTimer = 0; +static int32_t m_OldHitPoints = -1; +static bool m_FlashState = false; +static int32_t m_FlashCounter = 0; + +bool __cdecl Overlay_FlashCounter(void) +{ + if (m_FlashCounter > 0) { + m_FlashCounter--; + return m_FlashState; + } else { + m_FlashCounter = FLASH_FRAMES; + m_FlashState = !m_FlashState; + } + return m_FlashState; +} + +void __cdecl Overlay_DrawAssaultTimer(void) +{ + if (g_CurrentLevel != 0 || !g_IsAssaultTimerDisplay) { + return; + } + + static char buffer[8]; + const int32_t total_sec = g_SaveGame.statistics.timer / FRAMES_PER_SECOND; + const int32_t frame = g_SaveGame.statistics.timer % FRAMES_PER_SECOND; + sprintf( + buffer, "%d:%02d.%d", total_sec / 60, total_sec % 60, + frame * 10 / FRAMES_PER_SECOND); + + const int32_t scale_h = PHD_ONE; + const int32_t scale_v = PHD_ONE; + + const int32_t dot_offset = -6; + const int32_t dot_width = 14; + const int32_t colon_offset = -6; + const int32_t colon_width = 14; + const int32_t digit_offset = 0; + const int32_t digit_width = 20; + + const int32_t y = 36; + int32_t x = (g_PhdWinMaxX / 2) - 50; + + for (char *c = buffer; *c != '\0'; c++) { + if (*c == ':') { + x += colon_offset; + Output_DrawScreenSprite2D( + x, y, 0, scale_h, scale_v, + g_Objects[O_ASSAULT_DIGITS].mesh_idx + 10, 0x1000, 0); + x += colon_width; + } else if (*c == '.') { + x += dot_offset; + Output_DrawScreenSprite2D( + x, y, 0, scale_h, scale_v, + g_Objects[O_ASSAULT_DIGITS].mesh_idx + 11, 0x1000, 0); + x += dot_width; + } else { + x += digit_offset; + Output_DrawScreenSprite2D( + x, y, 0, scale_h, scale_v, + *c + g_Objects[O_ASSAULT_DIGITS].mesh_idx - '0', 0x1000, 0); + x += digit_width; + } + } +} + +void __cdecl Overlay_DrawGameInfo(const bool pickup_state) +{ + Overlay_DrawAmmoInfo(); + Overlay_DrawModeInfo(); + if (g_OverlayStatus > 0) { + bool flash = Overlay_FlashCounter(); + Overlay_DrawHealthBar(flash); + Overlay_DrawAirBar(flash); + Overlay_DrawPickups(pickup_state); + Overlay_DrawAssaultTimer(); + } + Text_Draw(); +} + +void __cdecl Overlay_DrawHealthBar(const bool flash_state) +{ + int32_t hit_points = g_LaraItem->hit_points; + CLAMP(hit_points, 0, LARA_MAX_HITPOINTS); + + if (m_OldHitPoints != hit_points) { + m_OldHitPoints = hit_points; + g_HealthBarTimer = 40; + } + CLAMPL(g_HealthBarTimer, 0); + + const int32_t percent = hit_points * 100 / LARA_MAX_HITPOINTS; + if (hit_points <= LARA_MAX_HITPOINTS / 4) { + S_DrawHealthBar(flash_state ? percent : 0); + } else if (g_HealthBarTimer > 0 || g_Lara.gun_status == LGS_READY) { + S_DrawHealthBar(percent); + } +} + +void __cdecl Overlay_DrawAirBar(const bool flash_state) +{ + if (g_Lara.water_status != LWS_UNDERWATER + && g_Lara.water_status != LWS_SURFACE) { + return; + } + + int32_t air = g_Lara.air; + CLAMP(air, 0, LARA_MAX_AIR); + const int32_t percent = air * 100 / LARA_MAX_AIR; + if (air <= 450) { + S_DrawAirBar(flash_state ? percent : 0); + } else { + S_DrawAirBar(percent); + } +} + +void Overlay_HideGameInfo(void) +{ + Text_Remove(g_AmmoTextInfo); + g_AmmoTextInfo = NULL; + + Text_Remove(g_DisplayModeTextInfo); + g_DisplayModeTextInfo = NULL; +} + +void __cdecl Overlay_MakeAmmoString(char *const string) +{ + for (char *c = string; *c != '\0'; c++) { + if (*c == ' ') { + continue; + } + + if (*c - 'A' >= 0) { + // ammo sprites + *c += 0xC - 'A'; + } else { + // digits + *c += 1 - '0'; + } + } +} + +void __cdecl Overlay_DrawAmmoInfo(void) +{ + if (g_Lara.gun_status != LGS_READY || g_OverlayStatus <= 0 + || g_SaveGame.bonus_flag) { + if (g_AmmoTextInfo != NULL) { + Text_Remove(g_AmmoTextInfo); + g_AmmoTextInfo = NULL; + } + return; + } + + char buffer[80] = ""; + switch (g_Lara.gun_type) { + case LGT_MAGNUMS: + sprintf(buffer, "%5d", g_Lara.magnum_ammo.ammo); + break; + + case LGT_UZIS: + sprintf(buffer, "%5d", g_Lara.uzi_ammo.ammo); + break; + + case LGT_SHOTGUN: + sprintf(buffer, "%5d", g_Lara.shotgun_ammo.ammo / 6); + break; + + case LGT_M16: + sprintf(buffer, "%5d", g_Lara.m16_ammo.ammo); + break; + + case LGT_GRENADE: + sprintf(buffer, "%5d", g_Lara.grenade_ammo.ammo); + break; + + case LGT_HARPOON: + sprintf(buffer, "%5d", g_Lara.harpoon_ammo.ammo); + break; + + default: + return; + } + + Overlay_MakeAmmoString(buffer); + if (g_AmmoTextInfo != NULL) { + Text_ChangeText(g_AmmoTextInfo, buffer); + } else { + g_AmmoTextInfo = Text_Create(AMMO_X, AMMO_Y, 0, buffer); + Text_AlignRight(g_AmmoTextInfo, true); + } +} + +void __cdecl Overlay_InitialisePickUpDisplay(void) +{ + for (int32_t i = 0; i < MAX_PICKUPS; i++) { + g_Pickups[i].timer = 0; + } +} + +void __cdecl Overlay_DrawPickups(const bool timed) +{ + const int32_t time = g_SaveGame.statistics.timer - m_OldGameTimer; + m_OldGameTimer = g_SaveGame.statistics.timer; + + if (time <= 0 || time >= 60) { + return; + } + + static const int32_t max_columns = 4; + const int32_t cell_h = g_PhdWinWidth / 10; + const int32_t cell_v = cell_h * 2 / 3; + int32_t x = g_PhdWinWidth - cell_h; + int32_t y = g_PhdWinHeight - cell_h; + + int32_t column = 0; + for (int32_t i = 0; i < 12; i++) { + PICKUP_INFO *const pickup = &g_Pickups[i]; + if (timed) { + pickup->timer -= time; + } + + if (pickup->timer <= 0) { + pickup->timer = 0; + } else { + if (column == max_columns) { + y -= cell_v; + x = g_PhdWinWidth - cell_h; + } + column++; + Output_DrawPickup(x, y, 12 * WALL_L, pickup->sprite, 0x1000); + } + x -= cell_h; + } +} + +void __cdecl Overlay_AddDisplayPickup(const int16_t object_id) +{ + if (object_id == O_SECRET_1 || object_id == O_SECRET_2 + || object_id == O_SECRET_3) { + Music_Play(g_GameFlow.secret_track, false); + } + + for (int32_t i = 0; i < MAX_PICKUPS; i++) { + PICKUP_INFO *const pickup = &g_Pickups[i]; + if (pickup->timer <= 0) { + pickup->timer = 2.5 * FRAMES_PER_SECOND; + pickup->sprite = g_Objects[object_id].mesh_idx; + return; + } + } +} + +void __cdecl Overlay_DisplayModeInfo(const char *const string) +{ + if (string == NULL) { + Text_Remove(g_DisplayModeTextInfo); + g_DisplayModeTextInfo = NULL; + return; + } + + if (g_DisplayModeTextInfo != NULL) { + Text_ChangeText(g_DisplayModeTextInfo, string); + } else { + g_DisplayModeTextInfo = + Text_Create(MODE_INFO_X, MODE_INFO_Y, 0, string); + Text_AlignRight(g_DisplayModeTextInfo, 1); + Text_AlignBottom(g_DisplayModeTextInfo, 1); + } + g_DisplayModeInfoTimer = 2.5 * FRAMES_PER_SECOND; +} + +void __cdecl Overlay_DrawModeInfo(void) +{ + if (g_DisplayModeTextInfo == NULL) { + return; + } + + g_DisplayModeInfoTimer--; + if (g_DisplayModeInfoTimer == 0) { + Text_Remove(g_DisplayModeTextInfo); + g_DisplayModeTextInfo = NULL; + } +} diff --git a/src/tr2/game/overlay.h b/src/tr2/game/overlay.h new file mode 100644 index 000000000..d8636f157 --- /dev/null +++ b/src/tr2/game/overlay.h @@ -0,0 +1,17 @@ +#pragma once + +#include "global/types.h" + +bool __cdecl Overlay_FlashCounter(void); +void __cdecl Overlay_DrawAssaultTimer(void); +void __cdecl Overlay_DrawGameInfo(bool pickup_state); +void __cdecl Overlay_DrawHealthBar(bool flash_state); +void __cdecl Overlay_DrawAirBar(bool flash_state); +void Overlay_HideGameInfo(void); +void __cdecl Overlay_MakeAmmoString(char *string); +void __cdecl Overlay_DrawAmmoInfo(void); +void __cdecl Overlay_InitialisePickUpDisplay(void); +void __cdecl Overlay_DrawPickups(bool pickup_state); +void __cdecl Overlay_AddDisplayPickup(int16_t object_id); +void __cdecl Overlay_DisplayModeInfo(const char *string); +void __cdecl Overlay_DrawModeInfo(void); diff --git a/src/tr2/game/random.c b/src/tr2/game/random.c new file mode 100644 index 000000000..2e48f3270 --- /dev/null +++ b/src/tr2/game/random.c @@ -0,0 +1,39 @@ +#include "game/random.h" + +#include "global/funcs.h" +#include "global/vars.h" + +#include + +static int32_t m_RandControl = 0xD371F947; +static int32_t m_RandDraw = 0xD371F947; + +void __cdecl Random_SeedControl(const int32_t seed) +{ + m_RandControl = seed; +} + +void __cdecl Random_SeedDraw(const int32_t seed) +{ + m_RandDraw = seed; +} + +int32_t __cdecl Random_GetControl(void) +{ + m_RandControl = 0x41C64E6D * m_RandControl + 12345; + return (m_RandControl >> 10) & 0x7FFF; +} + +int32_t __cdecl Random_GetDraw(void) +{ + m_RandDraw = 0x41C64E6D * m_RandDraw + 12345; + return (m_RandDraw >> 10) & 0x7FFF; +} + +void __cdecl Random_Seed(void) +{ + time_t lt = time(0); + struct tm *tptr = localtime(<); + Random_SeedControl(tptr->tm_sec + 57 * tptr->tm_min + 3543 * tptr->tm_hour); + Random_SeedDraw(tptr->tm_sec + 43 * tptr->tm_min + 3477 * tptr->tm_hour); +} diff --git a/src/tr2/game/random.h b/src/tr2/game/random.h new file mode 100644 index 000000000..6674c929a --- /dev/null +++ b/src/tr2/game/random.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +void __cdecl Random_SeedControl(int32_t seed); +void __cdecl Random_SeedDraw(int32_t seed); +int32_t __cdecl Random_GetControl(void); +int32_t __cdecl Random_GetDraw(void); +void __cdecl Random_Seed(void); diff --git a/src/tr2/game/requester.c b/src/tr2/game/requester.c new file mode 100644 index 000000000..9959b8929 --- /dev/null +++ b/src/tr2/game/requester.c @@ -0,0 +1,470 @@ +#include "game/requester.h" + +#include "game/input.h" +#include "game/text.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +static void M_ClearTextStrings(REQUEST_INFO *req); + +static void M_ClearTextStrings(REQUEST_INFO *const req) +{ + Text_Remove(req->heading_text1); + req->heading_text1 = NULL; + + Text_Remove(req->heading_text2); + req->heading_text2 = NULL; + + Text_Remove(req->background_text); + req->background_text = NULL; + + Text_Remove(req->moreup_text); + req->moreup_text = NULL; + + Text_Remove(req->moredown_text); + req->moredown_text = NULL; + + for (int32_t i = 0; i < MAX_REQUESTER_ITEMS; i++) { + Text_Remove(req->item_texts1[i]); + req->item_texts1[i] = NULL; + Text_Remove(req->item_texts2[i]); + req->item_texts2[i] = NULL; + } +} + +void __cdecl Requester_Init(REQUEST_INFO *const req) +{ + req->background_flags = 1; + req->moreup_flags = 1; + req->moredown_flags = 1; + req->items_count = 0; + + req->heading_text1 = NULL; + req->heading_text2 = NULL; + req->heading_flags1 = 0; + req->heading_flags2 = 0; + req->background_text = NULL; + req->moreup_text = NULL; + req->moredown_text = NULL; + + for (int32_t i = 0; i < MAX_REQUESTER_ITEMS; i++) { + req->item_texts1[i] = NULL; + req->item_texts2[i] = NULL; + req->item_flags1[i] = 0; + req->item_flags2[i] = 0; + } + + req->pitem_flags1 = g_RequesterFlags1; + req->pitem_flags2 = g_RequesterFlags2; + + req->render_width = GetRenderWidth(); + req->render_height = GetRenderHeight(); +} + +void __cdecl Requester_Shutdown(REQUEST_INFO *const req) +{ + M_ClearTextStrings(req); + req->ready = 0; +} + +int32_t __cdecl Requester_Display( + REQUEST_INFO *const req, const bool destroy, const bool backgrounds) +{ + int32_t line_qty = req->visible_count; + const int32_t lines_height = line_qty * req->line_height + 10; + const int32_t line_one_off = req->y_pos - lines_height; + + const uint32_t render_width = GetRenderWidth(); + const uint32_t render_height = GetRenderHeight(); + if (render_width != req->render_width + || render_height != req->render_height) { + Requester_Shutdown(req); + req->render_width = render_width; + req->render_height = render_height; + } + + req->pitem_flags1 = g_RequesterFlags1; + req->pitem_flags2 = g_RequesterFlags2; + if (req->items_count < req->visible_count) { + line_qty = req->items_count; + } + + /* heading */ + if (req->heading_flags1 & REQ_USE) { + if (req->heading_text1 == NULL) { + req->heading_text1 = Text_Create( + req->x_pos, line_one_off - req->line_height - 10, req->z_pos, + req->heading_string1); + Text_CentreH(req->heading_text1, true); + Text_AlignBottom(req->heading_text1, true); + if (backgrounds) { + Text_AddBackground( + req->heading_text1, req->pix_width - 4, 0, 0, 0, 8, + INV_COLOR_BLACK, g_Requester_MainGour1, 2); + Text_AddOutline( + req->heading_text1, 1, INV_COLOR_ORANGE, + g_Requester_MainGour2, 0); + } + } + + if (req->heading_flags1 & REQ_ALIGN_LEFT) { + Requester_Item_LeftAlign(req, req->heading_text1); + } + if (req->heading_flags1 & REQ_ALIGN_RIGHT) { + Requester_Item_RightAlign(req, req->heading_text1); + } + } + + /* heading 2 */ + if (req->heading_flags2 & REQ_USE) { + if (req->heading_text2) { + } else { + req->heading_text2 = Text_Create( + req->x_pos, line_one_off - req->line_height - 10, req->z_pos, + req->heading_string2); + Text_CentreH(req->heading_text2, true); + Text_AlignBottom(req->heading_text2, true); + if (backgrounds) { + Text_AddBackground( + req->heading_text2, req->pix_width - 4, 0, 0, 0, 8, + INV_COLOR_BLACK, g_Requester_MainGour1, 2); + Text_AddOutline( + req->heading_text2, 1, INV_COLOR_ORANGE, + g_Requester_MainGour2, 0); + } + } + + if (req->heading_flags2 & REQ_ALIGN_LEFT) { + Requester_Item_LeftAlign(req, req->heading_text2); + } + + if (req->heading_flags2 & REQ_ALIGN_RIGHT) { + Requester_Item_RightAlign(req, req->heading_text2); + } + } + + /* background */ + if (backgrounds && !req->background_text + && (req->background_flags & 1) != 0) { + req->background_text = Text_Create( + req->x_pos, line_one_off - req->line_height - 12, 0, " "); + Text_CentreH(req->background_text, true); + Text_AlignBottom(req->background_text, true); + Text_AddBackground( + req->background_text, req->pix_width, + req->line_height + lines_height + 12, 0, 0, 48, INV_COLOR_BLACK, + g_Requester_BackgroundGour1, 1); + Text_AddOutline( + req->background_text, 1, INV_COLOR_BLUE, + g_Requester_BackgroundGour2, 0); + } + + /* arrows */ + if (req->line_offset) { + if (!req->moreup_text && (req->moreup_flags & 1) != 0) { + Text_CentreH(req->moreup_text, true); + Text_AlignBottom(req->moreup_text, true); + } + } else { + Text_Remove(req->moreup_text); + req->moreup_text = NULL; + } + + if (req->items_count <= req->visible_count + req->line_offset) { + Text_Remove(req->moredown_text); + req->moredown_text = NULL; + } else if (!req->moredown_text && (req->moredown_flags & 1) != 0) { + Text_CentreH(req->moredown_text, true); + Text_AlignBottom(req->moredown_text, true); + } + + /* item texts */ + for (int32_t i = 0; i < line_qty; i++) { + const int32_t n = i + req->line_offset; + + TEXTSTRING **const text1 = &req->item_texts1[i]; + if (req->pitem_flags1[n] & REQ_USE) { + if (*text1 == NULL) { + *text1 = Text_Create( + 0, line_one_off + req->line_height * i, 0, + &req->pitem_strings1[n * req->item_string_len]); + Text_CentreH(*text1, true); + Text_AlignBottom(*text1, true); + } + if (req->no_selector || n != req->selected) { + Text_RemoveBackground(*text1); + Text_RemoveOutline(*text1); + } else { + Text_AddBackground( + *text1, req->pix_width - 12, 0, 0, 0, 16, INV_COLOR_BLACK, + g_Requester_UnselectionGour1, 1); + Text_AddOutline( + *text1, 1, INV_COLOR_ORANGE, g_Requester_SelectionGour2, 0); + } + + if (req->pitem_flags1[n] & REQ_ALIGN_LEFT) { + Requester_Item_LeftAlign(req, *text1); + } else if (req->pitem_flags1[n] & REQ_ALIGN_RIGHT) { + Requester_Item_RightAlign(req, *text1); + } else { + Requester_Item_CenterAlign(req, *text1); + } + } else { + Text_Remove(*text1); + Text_RemoveBackground(*text1); + Text_RemoveOutline(*text1); + *text1 = NULL; + } + + TEXTSTRING **const text2 = &req->item_texts2[i]; + if (req->pitem_flags2[n] & REQ_USE) { + if (*text2 == NULL) { + *text2 = Text_Create( + 0, line_one_off + req->line_height * i, 0, + &req->pitem_strings2[n * req->item_string_len]); + Text_CentreH(*text2, true); + Text_AlignBottom(*text2, true); + } + if (req->pitem_flags2[n] & REQ_ALIGN_LEFT) { + Requester_Item_LeftAlign(req, *text2); + } else if (req->pitem_flags2[n] & REQ_ALIGN_RIGHT) { + Requester_Item_RightAlign(req, *text2); + } else { + Requester_Item_CenterAlign(req, *text2); + } + } else { + Text_Remove(*text2); + Text_RemoveBackground(*text2); + Text_RemoveOutline(*text2); + *text2 = NULL; + } + } + + if (req->line_offset != req->line_old_offset) { + for (int32_t i = 0; i < line_qty; i++) { + const int32_t n = req->line_offset + i; + + TEXTSTRING **text1 = &req->item_texts1[i]; + if (*text1 != NULL && (req->pitem_flags1[n] & REQ_USE)) { + Text_ChangeText( + *text1, &req->pitem_strings1[n * req->item_string_len]); + } + if (req->pitem_flags1[n] & REQ_ALIGN_LEFT) { + Requester_Item_LeftAlign(req, *text1); + } else if (req->pitem_flags1[n] & REQ_ALIGN_RIGHT) { + Requester_Item_RightAlign(req, *text1); + } else { + Requester_Item_CenterAlign(req, *text1); + } + + TEXTSTRING **text2 = &req->item_texts2[i]; + if (*text2 != NULL && (req->pitem_flags2[n] & REQ_USE)) { + Text_ChangeText( + *text2, &req->pitem_strings2[n * req->item_string_len]); + } + if (req->pitem_flags2[n] & REQ_ALIGN_LEFT) { + Requester_Item_LeftAlign(req, *text2); + } else if (req->pitem_flags2[n] & REQ_ALIGN_RIGHT) { + Requester_Item_RightAlign(req, *text2); + } else { + Requester_Item_CenterAlign(req, *text2); + } + } + } + + if (g_InputDB & IN_BACK) { + if (req->no_selector) { + req->line_old_offset = req->line_offset; + if (req->visible_count + req->line_offset < req->items_count) { + req->line_offset++; + return 0; + } + } else { + if (req->selected < req->items_count - 1) { + req->selected++; + } + req->line_old_offset = req->line_offset; + if (req->selected > req->line_offset + req->visible_count - 1) { + req->line_offset++; + return 0; + } + } + return 0; + } + + if (g_InputDB & IN_FORWARD) { + if (req->no_selector) { + req->line_old_offset = req->line_offset; + if (req->line_offset > 0) { + req->line_offset--; + return 0; + } + } else { + if (req->selected > 0) { + req->selected--; + } + req->line_old_offset = req->line_offset; + if (req->selected < req->line_offset) { + req->line_offset--; + return 0; + } + } + return 0; + } + + if (!(g_InputDB & IN_SELECT)) { + if ((g_InputDB & IN_DESELECT) && destroy) { + Requester_Shutdown(req); + return -1; + } + return 0; + } + + // TODO: WTF!? + if (!strncmp( + req->item_texts1[req->selected - req->line_offset]->text, + g_GF_PCStrings[GF_S_PC_EMPTY_SLOT], 12) + && !strcmp( + g_PasswordText1->text, + g_GF_GameStrings[GF_S_GAME_PASSPORT_LOAD_GAME])) { + g_Input = 0; + return 0; + } + + Requester_Shutdown(req); + return req->selected + 1; +} + +void __cdecl Requester_RemoveAllItems(REQUEST_INFO *const req) +{ + req->items_count = 0; + req->line_offset = 0; + req->selected = 0; +} + +void __cdecl Requester_Item_CenterAlign( + REQUEST_INFO *const req, TEXTSTRING *const text) +{ + if (text == NULL) { + return; + } + text->bgnd_off.x = 0; + text->pos.x = req->x_pos; +} + +void __cdecl Requester_Item_LeftAlign( + REQUEST_INFO *const req, TEXTSTRING *const text) +{ + if (text == NULL) { + return; + } + const uint32_t scale_h = Text_GetScaleH(text->scale.h); + const int32_t x = ((scale_h * req->pix_width) >> 16) / 2 + - Text_GetWidth(text) / 2 - ((8 * scale_h) >> 16); + text->pos.x = req->x_pos - x; + text->bgnd_off.x = x; +} + +void __cdecl Requester_Item_RightAlign( + REQUEST_INFO *const req, TEXTSTRING *const text) +{ + const uint32_t scale_h = Text_GetScaleH(text->scale.h); + if (text == NULL) { + return; + } + const int32_t x = ((scale_h * req->pix_width) >> 16) / 2 + - Text_GetWidth(text) / 2 - ((8 * scale_h) >> 16); + text->pos.x = x + req->x_pos; + text->bgnd_off.x = -x; +} + +void __cdecl Requester_SetHeading( + REQUEST_INFO *const req, const char *const text1, const uint32_t flags1, + const char *const text2, const uint32_t flags2) +{ + Text_Remove(req->heading_text1); + req->heading_text1 = NULL; + + Text_Remove(req->heading_text2); + req->heading_text2 = NULL; + + if (text1 != NULL) { + strcpy(req->heading_string1, text1); + req->heading_flags1 = flags1 | REQ_USE; + } else { + strcpy(req->heading_string1, "u"); + req->heading_flags1 = 0; + } + + if (text2 != NULL) { + strcpy(req->heading_string2, text2); + req->heading_flags2 = flags2 | REQ_USE; + } else { + strcpy(req->heading_string2, "u"); + req->heading_flags2 = 0; + } +} + +void __cdecl Requester_ChangeItem( + REQUEST_INFO *const req, const int32_t item, const char *const text1, + const uint32_t flags1, const char *const text2, const uint32_t flags2) +{ + Text_Remove(req->item_texts1[item]); + req->item_texts1[item] = NULL; + + Text_Remove(req->item_texts2[item]); + req->item_texts2[item] = NULL; + + if (text1 != NULL) { + strcpy(&req->pitem_strings1[item * req->item_string_len], text1); + req->pitem_flags1[item] = flags1 | REQ_USE; + } else { + req->pitem_flags1[item] = 0; + } + + if (text2 != NULL) { + strcpy(&req->pitem_strings2[item * req->item_string_len], text2); + req->pitem_flags2[item] = flags2 | REQ_USE; + } else { + req->pitem_flags2[item] = 0; + } +} + +void __cdecl Requester_AddItem( + REQUEST_INFO *const req, const char *const text1, const uint32_t flags1, + const char *const text2, const uint32_t flags2) +{ + req->pitem_flags1 = g_RequesterFlags1; + req->pitem_flags2 = g_RequesterFlags2; + + if (text1 != NULL) { + strcpy( + &req->pitem_strings1[req->items_count * req->item_string_len], + text1); + req->pitem_flags1[req->items_count] = flags1 | REQ_USE; + } else { + g_RequesterFlags1[req->items_count] = 0; + } + + if (text2 != NULL) { + strcpy( + &req->pitem_strings2[req->items_count * req->item_string_len], + text2); + req->pitem_flags2[req->items_count] = flags2 | REQ_USE; + } else { + req->pitem_flags2[req->items_count] = 0; + } + + req->items_count++; +} + +void __cdecl Requester_SetSize( + REQUEST_INFO *const req, const int32_t max_lines, const int32_t y_pos) +{ + int32_t visible_lines = GetRenderHeight() / 2 / 18; + CLAMPG(visible_lines, max_lines); + req->y_pos = y_pos; + req->visible_count = visible_lines; +} diff --git a/src/tr2/game/requester.h b/src/tr2/game/requester.h new file mode 100644 index 000000000..f431374f6 --- /dev/null +++ b/src/tr2/game/requester.h @@ -0,0 +1,23 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Requester_Init(REQUEST_INFO *req); +void __cdecl Requester_Shutdown(REQUEST_INFO *req); +int32_t __cdecl Requester_Display( + REQUEST_INFO *req, bool destroy, bool backgrounds); +void __cdecl Requester_RemoveAllItems(REQUEST_INFO *req); +void __cdecl Requester_Item_CenterAlign(REQUEST_INFO *req, TEXTSTRING *text); +void __cdecl Requester_Item_LeftAlign(REQUEST_INFO *req, TEXTSTRING *text); +void __cdecl Requester_Item_RightAlign(REQUEST_INFO *req, TEXTSTRING *text); +void __cdecl Requester_SetHeading( + REQUEST_INFO *req, const char *text1, uint32_t flags1, const char *text2, + uint32_t flags2); +void __cdecl Requester_ChangeItem( + REQUEST_INFO *req, int32_t item, const char *text1, uint32_t flags1, + const char *text2, uint32_t flags2); +void __cdecl Requester_AddItem( + REQUEST_INFO *req, const char *text1, uint32_t flags1, const char *text2, + uint32_t flags2); +void __cdecl Requester_SetSize( + REQUEST_INFO *req, int32_t max_lines, int32_t y_pos); diff --git a/src/tr2/game/room.c b/src/tr2/game/room.c new file mode 100644 index 000000000..f2367bcf6 --- /dev/null +++ b/src/tr2/game/room.c @@ -0,0 +1,968 @@ +#include "game/room.h" + +#include "game/box.h" +#include "game/camera.h" +#include "game/items.h" +#include "game/lara/misc.h" +#include "game/lot.h" +#include "game/math.h" +#include "game/music.h" +#include "game/shell.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#include + +int16_t Room_GetIndexFromPos(const int32_t x, const int32_t y, const int32_t z) +{ + // TODO: merge this to Room_FindByPos + const int32_t room_num = Room_FindByPos(x, y, z); + if (room_num == NO_ROOM_NEG) { + return NO_ROOM; + } + return room_num; +} + +int32_t __cdecl Room_FindByPos( + const int32_t x, const int32_t y, const int32_t z) +{ + for (int32_t i = 0; i < g_RoomCount; i++) { + const ROOM *const room = &g_Rooms[i]; + const int32_t x1 = room->pos.x + WALL_L; + const int32_t x2 = room->pos.x + (room->size.x - 1) * WALL_L; + const int32_t y1 = room->max_ceiling; + const int32_t y2 = room->min_floor; + const int32_t z1 = room->pos.z + WALL_L; + const int32_t z2 = room->pos.z + (room->size.z - 1) * WALL_L; + if (x >= x1 && x < x2 && y >= y1 && y <= y2 && z >= z1 && z < z2) { + return i; + } + } + + return NO_ROOM_NEG; +} + +int32_t __cdecl Room_FindGridShift(int32_t src, const int32_t dst) +{ + const int32_t src_w = src >> WALL_SHIFT; + const int32_t dst_w = dst >> WALL_SHIFT; + if (src_w == dst_w) { + return 0; + } + + src &= WALL_L - 1; + if (dst_w > src_w) { + return WALL_L - (src - 1); + } else { + return -(src + 1); + } +} + +void __cdecl Room_GetNearbyRooms( + const int32_t x, const int32_t y, const int32_t z, const int32_t r, + const int32_t h, const int16_t room_num) +{ + g_DrawRoomsArray[0] = room_num; + g_DrawRoomsCount = 1; + + Room_GetNewRoom(r + x, y, r + z, room_num); + Room_GetNewRoom(x - r, y, r + z, room_num); + Room_GetNewRoom(r + x, y, z - r, room_num); + Room_GetNewRoom(x - r, y, z - r, room_num); + Room_GetNewRoom(r + x, y - h, r + z, room_num); + Room_GetNewRoom(x - r, y - h, r + z, room_num); + Room_GetNewRoom(r + x, y - h, z - r, room_num); + Room_GetNewRoom(x - r, y - h, z - r, room_num); +} + +void __cdecl Room_GetNewRoom( + const int32_t x, const int32_t y, const int32_t z, int16_t room_num) +{ + Room_GetSector(x, y, z, &room_num); + + for (int32_t i = 0; i < g_DrawRoomsCount; i++) { + if (g_DrawRoomsArray[i] == room_num) { + return; + } + } + + // TODO: fix crash when trying to draw too many rooms + g_DrawRoomsArray[g_DrawRoomsCount++] = room_num; +} + +int16_t __cdecl Room_GetTiltType( + const SECTOR *sector, const int32_t x, const int32_t y, const int32_t z) +{ + while (sector->pit_room != NO_ROOM) { + const ROOM *const room = &g_Rooms[sector->pit_room]; + const int32_t z_sector = (z - room->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (x - room->pos.x) >> WALL_SHIFT; + sector = &room->sectors[z_sector + x_sector * room->size.z]; + } + + if ((y + 512 >= (sector->floor << 8)) && sector->idx != 0) { + const int16_t *fd = &g_FloorData[sector->idx]; + if (FLOORDATA_TYPE(fd[0]) == FT_TILT) { + return fd[1]; + } + } + + return 0; +} + +SECTOR *__cdecl Room_GetSector( + const int32_t x, const int32_t y, const int32_t z, int16_t *const room_num) +{ + SECTOR *sector = NULL; + + while (true) { + const ROOM *r = &g_Rooms[*room_num]; + int32_t z_sector = (z - r->pos.z) >> WALL_SHIFT; + int32_t x_sector = (x - r->pos.x) >> WALL_SHIFT; + + if (z_sector <= 0) { + z_sector = 0; + if (x_sector < 1) { + x_sector = 1; + } else if (x_sector > r->size.x - 2) { + x_sector = r->size.x - 2; + } + } else if (z_sector >= r->size.z - 1) { + z_sector = r->size.z - 1; + if (x_sector < 1) { + x_sector = 1; + } else if (x_sector > r->size.x - 2) { + x_sector = r->size.x - 2; + } + } else if (x_sector < 0) { + x_sector = 0; + } else if (x_sector >= r->size.x) { + x_sector = r->size.x - 1; + } + + sector = &r->sectors[z_sector + x_sector * r->size.z]; + const int16_t door = Room_GetDoor(sector); + if (door == NO_ROOM) { + break; + } + *room_num = door; + } + + assert(sector != NULL); + + if (y >= (sector->floor << 8)) { + while (sector->pit_room != NO_ROOM) { + *room_num = sector->pit_room; + const ROOM *const r = &g_Rooms[*room_num]; + const int32_t z_sector = ((z - r->pos.z) >> WALL_SHIFT); + const int32_t x_sector = ((x - r->pos.x) >> WALL_SHIFT); + sector = &r->sectors[z_sector + x_sector * r->size.z]; + if (y < (sector->floor << 8)) { + break; + } + } + } else if (y < (sector->ceiling << 8)) { + while (sector->sky_room != NO_ROOM) { + *room_num = sector->sky_room; + const ROOM *const r = &g_Rooms[sector->sky_room]; + const int32_t z_sector = (z - r->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (x - r->pos.x) >> WALL_SHIFT; + sector = &r->sectors[z_sector + x_sector * r->size.z]; + if (y >= (sector->ceiling << 8)) { + break; + } + } + } + + return sector; +} + +int32_t __cdecl Room_GetWaterHeight( + const int32_t x, const int32_t y, const int32_t z, int16_t room_num) +{ + const SECTOR *sector = NULL; + const ROOM *r = NULL; + + do { + r = &g_Rooms[room_num]; + int32_t z_sector = (z - r->pos.z) >> WALL_SHIFT; + int32_t x_sector = (x - r->pos.x) >> WALL_SHIFT; + + if (z_sector <= 0) { + z_sector = 0; + if (x_sector < 1) { + x_sector = 1; + } else if (x_sector > r->size.x - 2) { + x_sector = r->size.x - 2; + } + } else if (z_sector >= r->size.z - 1) { + z_sector = r->size.z - 1; + if (x_sector < 1) { + x_sector = 1; + } else if (x_sector > r->size.x - 2) { + x_sector = r->size.x - 2; + } + } else if (x_sector < 0) { + x_sector = 0; + } else if (x_sector >= r->size.x) { + x_sector = r->size.x - 1; + } + + sector = &r->sectors[z_sector + x_sector * r->size.z]; + room_num = Room_GetDoor(sector); + } while (room_num != NO_ROOM); + + if (r->flags & RF_UNDERWATER) { + while (sector->sky_room != NO_ROOM) { + r = &g_Rooms[sector->sky_room]; + if (!(r->flags & RF_UNDERWATER)) { + break; + } + const int32_t z_sector = (z - r->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (x - r->pos.x) >> WALL_SHIFT; + sector = &r->sectors[z_sector + x_sector * r->size.z]; + } + return sector->ceiling << 8; + } else { + while (sector->pit_room != NO_ROOM) { + r = &g_Rooms[sector->pit_room]; + if (r->flags & RF_UNDERWATER) { + return sector->floor << 8; + } + const int32_t z_sector = (z - r->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (x - r->pos.x) >> WALL_SHIFT; + sector = &r->sectors[z_sector + x_sector * r->size.z]; + } + return NO_HEIGHT; + } +} + +int32_t __cdecl Room_GetHeight( + const SECTOR *sector, const int32_t x, const int32_t y, const int32_t z) +{ + g_HeightType = 0; + g_TriggerIndex = NULL; + + while (sector->pit_room != NO_ROOM) { + const ROOM *const r = &g_Rooms[sector->pit_room]; + const int32_t z_sector = (z - r->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (x - r->pos.x) >> WALL_SHIFT; + sector = &r->sectors[z_sector + x_sector * r->size.z]; + } + + int32_t height = sector->floor << 8; + if (g_GF_NoFloor && g_GF_NoFloor == height) { + height = 0x4000; + } + + if (!sector->idx) { + return height; + } + + int16_t *fd = &g_FloorData[sector->idx]; + while (true) { + const int16_t fd_cmd = *fd++; + + switch (FLOORDATA_TYPE(fd_cmd)) { + case FT_DOOR: + case FT_ROOF: + fd++; + break; + + case FT_TILT: { + const int32_t x_off = *fd >> 8; + const int32_t y_off = (int8_t)*fd; + fd++; + + if (!g_IsChunkyCamera || (ABS(x_off) <= 2 && ABS(y_off) <= 2)) { + if (ABS(x_off) > 2 || ABS(y_off) > 2) { + g_HeightType = HT_BIG_SLOPE; + } else { + g_HeightType = HT_SMALL_SLOPE; + } + + if (x_off < 0) { + height -= (x_off * (z & (WALL_L - 1))) >> 2; + } else { + height += (x_off * ((WALL_L - 1 - z) & (WALL_L - 1))) >> 2; + } + + if (y_off < 0) { + height -= (y_off * (x & (WALL_L - 1))) >> 2; + } else { + height += (y_off * ((WALL_L - 1 - x) & (WALL_L - 1))) >> 2; + } + } + break; + } + + case FT_TRIGGER: + if (g_TriggerIndex == NULL) { + g_TriggerIndex = fd - 1; + } + fd++; + + int16_t trigger; + do { + trigger = *fd++; + switch (TRIGGER_TYPE(trigger)) { + case TO_OBJECT: + const int16_t value = TRIGGER_VALUE(trigger); + const ITEM *const item = &g_Items[value]; + const OBJECT *const object = &g_Objects[item->object_id]; + if (object->floor) { + object->floor(item, x, y, z, &height); + } + break; + + case TO_CAMERA: + trigger = *fd++; + break; + } + } while (!TRIGGER_IS_END(trigger)); + break; + + case FT_LAVA: + g_TriggerIndex = fd - 1; + break; + + case FT_CLIMB: + if (g_TriggerIndex == NULL) { + g_TriggerIndex = fd - 1; + } + break; + + default: + Shell_ExitSystem("GetHeight(): Unknown floordata type"); + break; + } + + if (FLOORDATA_IS_END(fd_cmd)) { + break; + } + } + + return height; +} + +void __cdecl Room_TestTriggers(const int16_t *fd, bool heavy) +{ + ITEM *camera_item = NULL; + bool switch_off = false; + bool flip = false; + bool flip_available = false; + int32_t new_effect = -1; + + if (!heavy) { + g_Lara.climb_status = 0; + } + if (fd == NULL) { + return; + } + + if (FLOORDATA_TYPE(*fd) == FT_LAVA) { + if (!heavy + && (g_LaraItem->pos.y == g_LaraItem->floor + || g_Lara.water_status != LWS_ABOVE_WATER)) { + Lara_TouchLava(g_LaraItem); + } + + if (FLOORDATA_IS_END(*fd)) { + return; + } + + fd++; + } + + if (FLOORDATA_TYPE(*fd) == FT_CLIMB) { + if (!heavy) { + const int32_t quad = Math_GetDirection(g_LaraItem->rot.y); + if (*fd & (1 << (quad + 8))) { + g_Lara.climb_status = 1; + } + } + + if (FLOORDATA_IS_END(*fd)) { + return; + } + + fd++; + } + + const int16_t type = (*fd++ >> 8) & 0x3F; + const int16_t flags = *fd++; + const int16_t timer = flags & 0xFF; + + if (g_Camera.type != CAM_HEAVY) { + Camera_RefreshFromTrigger(type, fd); + } + + if (heavy) { + if (type != TT_HEAVY) { + return; + } + } else { + switch (type) { + case TT_PAD: + case TT_ANTIPAD: + if (g_LaraItem->pos.y != g_LaraItem->floor) { + return; + } + break; + + case TT_SWITCH: { + const int16_t value = TRIGGER_VALUE(*fd++); + if (!SwitchTrigger(value, timer)) { + return; + } + switch_off = g_Items[value].current_anim_state == LS_RUN; + break; + } + + case TT_KEY: { + const int16_t value = TRIGGER_VALUE(*fd++); + if (!KeyTrigger(value)) { + return; + } + break; + } + + case TT_PICKUP: { + const int16_t value = TRIGGER_VALUE(*fd++); + if (!PickupTrigger(value)) { + return; + } + break; + } + + case TT_HEAVY: + case TT_DUMMY: + return; + + case TT_COMBAT: + if (g_Lara.gun_status != LGS_READY) { + return; + } + break; + + default: + break; + } + } + + int16_t trigger; + do { + trigger = *fd++; + const int32_t value = TRIGGER_VALUE(trigger); + + switch (TRIGGER_TYPE(trigger)) { + case TO_OBJECT: { + ITEM *const item = &g_Items[value]; + if (item->flags & IF_ONE_SHOT) { + break; + } + + item->timer = timer; + if (timer != 1) { + item->timer *= FRAMES_PER_SECOND; + } + + if (type == TT_SWITCH) { + item->flags ^= flags & IF_CODE_BITS; + } else if (type == TT_ANTIPAD || type == TT_ANTITRIGGER) { + item->flags &= ~(flags & IF_CODE_BITS); + if (flags & IF_ONE_SHOT) { + item->flags |= IF_ONE_SHOT; + } + } else if ((flags & IF_CODE_BITS) != 0) { + item->flags |= flags & IF_CODE_BITS; + } + + if ((item->flags & IF_CODE_BITS) != IF_CODE_BITS) { + break; + } + + if (flags & IF_ONE_SHOT) { + item->flags |= IF_ONE_SHOT; + } + + if (!item->active) { + if (g_Objects[item->object_id].intelligent) { + if (item->status == IS_INACTIVE) { + item->touch_bits = 0; + item->status = IS_ACTIVE; + Item_AddActive(value); + LOT_EnableBaddieAI(value, true); + } else if (item->status == IS_INVISIBLE) { + item->touch_bits = 0; + if (LOT_EnableBaddieAI(value, false)) { + item->status = IS_ACTIVE; + } else { + item->status = IS_INVISIBLE; + } + Item_AddActive(value); + } + } else { + item->touch_bits = 0; + item->status = IS_ACTIVE; + Item_AddActive(value); + } + } + break; + } + + case TO_CAMERA: { + trigger = *fd++; + const int16_t camera_flags = trigger; + const int16_t camera_timer = trigger & 0xFF; + + if (g_Camera.fixed[value].flags & IF_ONE_SHOT) { + break; + } + + g_Camera.num = value; + + if (g_Camera.type == CAM_LOOK || g_Camera.type == CAM_COMBAT) { + break; + } + if (type == TT_COMBAT) { + break; + } + if (type == TT_SWITCH && timer && switch_off) { + break; + } + if (value == g_Camera.last && type != TT_SWITCH) { + break; + } + + g_Camera.timer = FRAMES_PER_SECOND * camera_timer; + if (camera_flags & IF_ONE_SHOT) { + g_Camera.fixed[value].flags |= IF_ONE_SHOT; + } + g_Camera.speed = ((camera_flags & IF_CODE_BITS) >> 6) + 1; + g_Camera.type = heavy ? CAM_HEAVY : CAM_FIXED; + break; + } + + case TO_SINK: { + const OBJECT_VECTOR object_vector = g_Camera.fixed[value]; + if (!g_Lara.creature) { + LOT_EnableBaddieAI(g_Lara.item_num, true); + } + g_Lara.creature->lot.target.x = object_vector.x; + g_Lara.creature->lot.target.y = object_vector.y; + g_Lara.creature->lot.target.z = object_vector.z; + g_Lara.creature->lot.required_box = object_vector.flags; + g_Lara.current_active = object_vector.data * 6; + break; + } + + case TO_FLIP_MAP: + flip_available = true; + if (g_FlipMaps[value] & IF_ONE_SHOT) { + break; + } + + if (type == TT_SWITCH) { + g_FlipMaps[value] ^= flags & IF_CODE_BITS; + } else if (flags & IF_CODE_BITS) { + g_FlipMaps[value] |= flags & IF_CODE_BITS; + } + + if ((g_FlipMaps[value] & IF_CODE_BITS) == IF_CODE_BITS) { + if (flags & IF_ONE_SHOT) { + g_FlipMaps[value] |= IF_ONE_SHOT; + } + + if (!g_FlipStatus) { + flip = 1; + } + } else if (g_FlipStatus) { + flip = 1; + } + break; + + case TO_FLIP_ON: + flip_available = true; + if ((g_FlipMaps[value] & IF_CODE_BITS) == IF_CODE_BITS + && !g_FlipStatus) { + flip = 1; + } + break; + + case TO_FLIP_OFF: + flip_available = true; + if ((g_FlipMaps[value] & IF_CODE_BITS) == IF_CODE_BITS + && g_FlipStatus) { + flip = 1; + } + break; + + case TO_TARGET: + camera_item = &g_Items[value]; + break; + + case TO_FINISH: + g_LevelComplete = true; + break; + + case TO_CD: + Room_TriggerMusicTrack(value, flags, type); + break; + + case TO_FLIP_EFFECT: + new_effect = value; + break; + + case TO_BODY_BAG: + Item_ClearKilled(); + break; + + default: + break; + } + } while (!TRIGGER_IS_END(trigger)); + + if (camera_item != NULL + && (g_Camera.type == CAM_FIXED || g_Camera.type == CAM_HEAVY)) { + g_Camera.item = camera_item; + } + + if (flip) { + Room_FlipMap(); + } + + if (new_effect != -1 && (flip || !flip_available)) { + g_FlipEffect = new_effect; + g_FlipTimer = 0; + } +} + +int32_t __cdecl Room_GetCeiling( + const SECTOR *const sector, const int32_t x, const int32_t y, + const int32_t z) +{ + const SECTOR *f = sector; + + while (f->sky_room != NO_ROOM) { + const ROOM *const r = &g_Rooms[f->sky_room]; + const int32_t z_sector = (z - r->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (x - r->pos.x) >> WALL_SHIFT; + f = &r->sectors[z_sector + x_sector * r->size.z]; + } + + int32_t height = f->ceiling << 8; + + if (f->idx) { + const int16_t *fd = &g_FloorData[f->idx]; + int16_t type = FLOORDATA_TYPE(*fd++); + + if (type == FT_TILT) { + fd++; + type = FLOORDATA_TYPE(*fd++); + } + + if (type == FT_ROOF) { + const int32_t x_off = *fd >> 8; + const int32_t y_off = (int8_t)*fd; + + if (!g_IsChunkyCamera || (ABS(x_off) <= 2 && ABS(y_off) <= 2)) { + if (x_off < 0) { + height += (x_off * (z & (WALL_L - 1))) >> 2; + } else { + height -= (x_off * ((WALL_L - 1 - z) & (WALL_L - 1))) >> 2; + } + + if (y_off < 0) { + height += (y_off * ((WALL_L - 1 - x) & (WALL_L - 1))) >> 2; + } else { + height -= (y_off * (x & (WALL_L - 1))) >> 2; + } + } + } + } + + f = sector; + while (f->pit_room != NO_ROOM) { + const ROOM *const r = &g_Rooms[f->pit_room]; + const int32_t z_sector = (z - r->pos.z) >> WALL_SHIFT; + const int32_t x_sector = (x - r->pos.x) >> WALL_SHIFT; + f = &r->sectors[z_sector + x_sector * r->size.z]; + } + + if (!f->idx) { + return height; + } + + const int16_t *fd = &g_FloorData[f->idx]; + while (true) { + const int16_t fd_cmd = *fd++; + + switch (FLOORDATA_TYPE(fd_cmd)) { + case FT_DOOR: + case FT_TILT: + case FT_ROOF: + fd++; + break; + + case FT_TRIGGER: { + fd++; + + int16_t trigger; + do { + trigger = *fd++; + switch (TRIGGER_TYPE(trigger)) { + case TO_OBJECT: + const int16_t value = TRIGGER_VALUE(trigger); + const ITEM *const item = &g_Items[value]; + const OBJECT *const object = &g_Objects[item->object_id]; + if (object->ceiling) { + object->ceiling(item, x, y, z, &height); + } + break; + + case TO_CAMERA: + trigger = *fd++; + break; + } + } while (!TRIGGER_IS_END(trigger)); + break; + } + + case FT_LAVA: + case FT_CLIMB: + break; + + default: + Shell_ExitSystem("GetCeiling(): Unknown type"); + break; + } + + if (FLOORDATA_IS_END(fd_cmd)) { + break; + } + } + + return height; +} + +int16_t __cdecl Room_GetDoor(const SECTOR *const sector) +{ + if (!sector->idx) { + return NO_ROOM; + } + + const int16_t *fd = &g_FloorData[sector->idx]; + while (true) { + const int16_t fd_cmd = *fd++; + + switch (FLOORDATA_TYPE(fd_cmd)) { + case FT_DOOR: + return *fd; + + case FT_ROOF: + case FT_TILT: + fd++; + break; + + case FT_TRIGGER: + fd++; + + while (true) { + int16_t trigger = *fd++; + switch (TRIGGER_TYPE(trigger)) { + case TO_CAMERA: + trigger = *fd++; + break; + + default: + break; + } + + if (TRIGGER_IS_END(trigger)) { + break; + } + } + break; + + case FT_LAVA: + case FT_CLIMB: + break; + + default: + Shell_ExitSystem("GetDoor(): Unknown floordata type"); + break; + } + + if (FLOORDATA_IS_END(fd_cmd)) { + break; + } + } + + return NO_ROOM; +} + +void __cdecl Room_AlterFloorHeight(const ITEM *const item, const int32_t height) +{ + int16_t room_num = item->room_num; + + SECTOR *const sector = + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + const SECTOR *ceiling = Room_GetSector( + item->pos.x, item->pos.y + height - WALL_L, item->pos.z, &room_num); + + if (sector->floor == NO_HEIGHT / 256) { + sector->floor = ceiling->ceiling + height / 256; + } else { + sector->floor += height / 256; + if (sector->floor == ceiling->ceiling) { + sector->floor = NO_HEIGHT / 256; + } + } + + BOX_INFO *const box = &g_Boxes[sector->box]; + if (box->overlap_index & BOX_BLOCKABLE) { + if (height < 0) { + box->overlap_index |= BOX_BLOCKED; + } else { + box->overlap_index &= ~BOX_BLOCKED; + } + } +} + +bool Room_GetFlipStatus(void) +{ + return g_FlipStatus; +} + +void __cdecl Room_FlipMap(void) +{ + for (int32_t i = 0; i < g_RoomCount; i++) { + ROOM *const r = &g_Rooms[i]; + if (r->flipped_room == NO_ROOM_NEG) { + continue; + } + + Room_RemoveFlipItems(r); + + ROOM *const flipped = &g_Rooms[r->flipped_room]; + ROOM temp = *r; + *r = *flipped; + *flipped = temp; + + r->flipped_room = flipped->flipped_room; + flipped->flipped_room = NO_ROOM_NEG; + + // TODO: is this really necessary given the assignments above? + r->item_num = flipped->item_num; + r->fx_num = flipped->fx_num; + + Room_AddFlipItems(r); + } + + g_FlipStatus = !g_FlipStatus; +} + +void __cdecl Room_RemoveFlipItems(const ROOM *const r) +{ + int16_t item_num = r->item_num; + + while (item_num != NO_ITEM) { + ITEM *const item = &g_Items[item_num]; + + switch (item->object_id) { + case O_MOVABLE_BLOCK_1: + case O_MOVABLE_BLOCK_2: + case O_MOVABLE_BLOCK_3: + case O_MOVABLE_BLOCK_4: + Room_AlterFloorHeight(item, WALL_L); + break; + + default: + break; + } + + if (item->flags & IF_ONE_SHOT && g_Objects[item->object_id].intelligent + && item->hit_points <= 0) { + Item_RemoveDrawn(item_num); + item->flags |= IF_KILLED; + } + + item_num = item->next_item; + } +} + +void __cdecl Room_AddFlipItems(const ROOM *const r) +{ + int16_t item_num = r->item_num; + while (item_num != NO_ITEM) { + const ITEM *const item = &g_Items[item_num]; + + switch (item->object_id) { + case O_MOVABLE_BLOCK_1: + case O_MOVABLE_BLOCK_2: + case O_MOVABLE_BLOCK_3: + case O_MOVABLE_BLOCK_4: + Room_AlterFloorHeight(item, -WALL_L); + break; + + default: + break; + } + + item_num = item->next_item; + } +} + +void __cdecl Room_TriggerMusicTrack( + const int16_t track, const int16_t flags, const int16_t type) +{ + if (track < MX_CUTSCENE_THE_GREAT_WALL || track >= MX_TITLE_THEME) { + return; + } + + if (type != TT_SWITCH) { + const int32_t code = flags & IF_CODE_BITS; + if (g_MusicTrackFlags[track] & code) { + return; + } + if (flags & IF_ONE_SHOT) { + g_MusicTrackFlags[track] |= code; + } + } + + if (track != g_CD_TrackID) { + const int32_t timer = flags & 0xFF; + if (timer) { + g_CD_TrackID = track; + g_MusicTrackFlags[track] = + (g_MusicTrackFlags[track] & 0xFF00) | ((30 * timer) & 0xFF); + } else { + Music_Play(track, false); + } + } else { + int32_t timer = g_MusicTrackFlags[track] & 0xFF; + if (timer) { + timer--; + if (timer == 0) { + g_CD_TrackID = -1; + Music_Play(track, false); + } + g_MusicTrackFlags[track] = + (g_MusicTrackFlags[track] & 0xFF00) | (timer & 0xFF); + } + } +} + +int32_t Room_GetTotalCount(void) +{ + return g_RoomCount; +} + +ROOM *Room_Get(const int32_t room_num) +{ + return &g_Rooms[room_num]; +} diff --git a/src/tr2/game/room.h b/src/tr2/game/room.h new file mode 100644 index 000000000..20436dff8 --- /dev/null +++ b/src/tr2/game/room.h @@ -0,0 +1,31 @@ +#pragma once + +#include "global/types.h" + +#include + +#include + +int16_t Room_GetIndexFromPos(int32_t x, int32_t y, int32_t z); +int32_t __cdecl Room_FindByPos(int32_t x, int32_t y, int32_t z); +int32_t __cdecl Room_FindGridShift(int32_t src, int32_t dst); +void __cdecl Room_GetNearbyRooms( + int32_t x, int32_t y, int32_t z, int32_t r, int32_t h, int16_t room_num); +void __cdecl Room_GetNewRoom(int32_t x, int32_t y, int32_t z, int16_t room_num); +int16_t __cdecl Room_GetTiltType( + const SECTOR *sector, int32_t x, int32_t y, int32_t z); +SECTOR *__cdecl Room_GetSector( + int32_t x, int32_t y, int32_t z, int16_t *room_num); +int32_t __cdecl Room_GetWaterHeight( + int32_t x, int32_t y, int32_t z, int16_t room_num); +int32_t __cdecl Room_GetHeight( + const SECTOR *sector, int32_t x, int32_t y, int32_t z); +int32_t __cdecl Room_GetCeiling( + const SECTOR *sector, int32_t x, int32_t y, int32_t z); +int16_t __cdecl Room_GetDoor(const SECTOR *sector); +void __cdecl Room_TestTriggers(const int16_t *fd, bool heavy); +void __cdecl Room_AlterFloorHeight(const ITEM *item, int32_t height); +void __cdecl Room_FlipMap(void); +void __cdecl Room_RemoveFlipItems(const ROOM *r); +void __cdecl Room_AddFlipItems(const ROOM *r); +void __cdecl Room_TriggerMusicTrack(int16_t value, int16_t flags, int16_t type); diff --git a/src/tr2/game/room_draw.c b/src/tr2/game/room_draw.c new file mode 100644 index 000000000..179a08631 --- /dev/null +++ b/src/tr2/game/room_draw.c @@ -0,0 +1,533 @@ +#include "game/room_draw.h" + +#include "game/effects.h" +#include "game/lara/draw.h" +#include "game/matrix.h" +#include "game/output.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +void __cdecl Room_GetBounds(void) +{ + while (g_BoundStart != g_BoundEnd) { + const int16_t room_num = g_BoundRooms[g_BoundStart++ % MAX_BOUND_ROOMS]; + ROOM *const r = &g_Rooms[room_num]; + r->bound_active &= ~2; + g_MidSort = (r->bound_active >> 8) + 1; + + if (r->test_left < r->bound_left) { + r->bound_left = r->test_left; + } + if (r->test_top < r->bound_top) { + r->bound_top = r->test_top; + } + if (r->test_right > r->bound_right) { + r->bound_right = r->test_right; + } + if (r->test_bottom > r->bound_bottom) { + r->bound_bottom = r->test_bottom; + } + + if (!(r->bound_active & 1)) { + // TODO: fix crash when drawing too many rooms + g_DrawRoomsArray[g_DrawRoomsCount++] = room_num; + r->bound_active |= 1; + if (r->flags & RF_OUTSIDE) { + g_Outside = RF_OUTSIDE; + } + } + + if (!(r->flags & RF_INSIDE) || (r->flags & RF_OUTSIDE)) { + if (r->bound_left < g_OutsideLeft) { + g_OutsideLeft = r->bound_left; + } + if (r->bound_right > g_OutsideRight) { + g_OutsideRight = r->bound_right; + } + if (r->bound_top < g_OutsideTop) { + g_OutsideTop = r->bound_top; + } + if (r->bound_bottom > g_OutsideBottom) { + g_OutsideBottom = r->bound_bottom; + } + } + + if (r->portals == NULL) { + continue; + } + + Matrix_Push(); + Matrix_TranslateAbs(r->pos.x, r->pos.y, r->pos.z); + for (int32_t i = 0; i < r->portals->count; i++) { + const PORTAL *const portal = &r->portals->portal[i]; + + // clang-format off + const XYZ_32 offset = { + .x = portal->normal.x * (r->pos.x + portal->vertex[0].x - g_W2VMatrix._03), + .y = portal->normal.y * (r->pos.y + portal->vertex[0].y - g_W2VMatrix._13), + .z = portal->normal.z * (r->pos.z + portal->vertex[0].z - g_W2VMatrix._23), + }; + // clang-format on + + if (offset.x + offset.y + offset.z >= 0) { + continue; + } + + Room_SetBounds(&portal->normal.x, portal->room_num, r); + } + Matrix_Pop(); + } +} + +void __cdecl Room_SetBounds( + const int16_t *obj_ptr, int32_t room_num, const ROOM *parent) +{ + ROOM *const r = &g_Rooms[room_num]; + const PORTAL *const portal = (const PORTAL *)(obj_ptr - 1); + + // clang-format off + if (r->bound_left <= parent->test_left && + r->bound_right >= parent->test_right && + r->bound_top <= parent->test_top && + r->bound_bottom >= parent->test_bottom + ) { + return; + } + // clang-format on + + const MATRIX *const m = g_MatrixPtr; + int32_t left = parent->test_right; + int32_t right = parent->test_left; + int32_t bottom = parent->test_top; + int32_t top = parent->test_bottom; + + PORTAL_VBUF portal_vbuf[4]; + int32_t z_behind = 0; + int32_t z_too_far = 0; + + for (int32_t i = 0; i < 4; i++) { + PORTAL_VBUF *const dvbuf = &portal_vbuf[i]; + const XYZ_16 *const dvtx = &portal->vertex[i]; + const int32_t xv = + dvtx->x * m->_00 + dvtx->y * m->_01 + dvtx->z * m->_02 + m->_03; + const int32_t yv = + dvtx->x * m->_10 + dvtx->y * m->_11 + dvtx->z * m->_12 + m->_13; + const int32_t zv = + dvtx->x * m->_20 + dvtx->y * m->_21 + dvtx->z * m->_22 + m->_23; + dvbuf->xv = xv; + dvbuf->yv = yv; + dvbuf->zv = zv; + + if (zv <= 0) { + z_behind++; + continue; + } + + if (zv > g_PhdFarZ) { + z_too_far++; + } + + int32_t xs; + int32_t ys; + const int32_t zp = zv / g_PhdPersp; + if (zp) { + xs = xv / zp + g_PhdWinCenterX; + ys = yv / zp + g_PhdWinCenterY; + } else { + xs = xv < 0 ? g_PhdWinLeft : g_PhdWinRight; + ys = yv < 0 ? g_PhdWinTop : g_PhdWinBottom; + } + + if (xs - 1 < left) { + left = xs - 1; + } + if (xs + 1 > right) { + right = xs + 1; + } + if (ys - 1 < top) { + top = ys - 1; + } + if (ys + 1 > bottom) { + bottom = ys + 1; + } + } + + if (z_behind == 4 || z_too_far == 4) { + return; + } + + if (z_behind > 0) { + const PORTAL_VBUF *dest = &portal_vbuf[0]; + const PORTAL_VBUF *last = &portal_vbuf[3]; + + for (int32_t i = 0; i < 4; i++, last = dest++) { + if ((dest->zv < 0) == (last->zv < 0)) { + continue; + } + + if (dest->xv < 0 && last->xv < 0) { + left = 0; + } else if (dest->xv > 0 && last->xv > 0) { + right = g_PhdWinMaxX; + } else { + left = 0; + right = g_PhdWinMaxX; + } + + if (dest->yv < 0 && last->yv < 0) { + top = 0; + } else if (dest->yv > 0 && last->yv > 0) { + bottom = g_PhdWinMaxY; + } else { + top = 0; + bottom = g_PhdWinMaxY; + } + } + } + + if (left < parent->test_left) { + left = parent->test_left; + } + if (right > parent->test_right) { + right = parent->test_right; + } + if (top < parent->test_top) { + top = parent->test_top; + } + if (bottom > parent->test_bottom) { + bottom = parent->test_bottom; + } + + if (left >= right || top >= bottom) { + return; + } + + if (r->bound_active & 2) { + if (left < r->test_left) { + r->test_left = left; + } + if (top < r->test_top) { + r->test_top = top; + } + if (right > r->test_right) { + r->test_right = right; + } + if (bottom > r->test_bottom) { + r->test_bottom = bottom; + } + } else { + g_BoundRooms[g_BoundEnd++ % MAX_BOUND_ROOMS] = room_num; + r->bound_active |= 2; + r->bound_active += (int16_t)(g_MidSort << 8); + r->test_left = left; + r->test_right = right; + r->test_top = top; + r->test_bottom = bottom; + } +} + +void __cdecl Room_Clip(const ROOM *const r) +{ + int32_t xv[8]; + int32_t yv[8]; + int32_t zv[8]; + + xv[0] = WALL_L; + yv[0] = r->max_ceiling - r->pos.y; + zv[0] = WALL_L; + + xv[1] = (r->size.x - 1) * WALL_L; + yv[1] = r->max_ceiling - r->pos.y; + zv[1] = WALL_L; + + xv[2] = (r->size.x - 1) * WALL_L; + yv[2] = r->max_ceiling - r->pos.y; + zv[2] = (r->size.z - 1) * WALL_L; + + xv[3] = WALL_L; + yv[3] = r->max_ceiling - r->pos.y; + zv[3] = (r->size.z - 1) * WALL_L; + + xv[4] = WALL_L; + yv[4] = r->min_floor - r->pos.y; + zv[4] = WALL_L; + + xv[5] = (r->size.x - 1) * WALL_L; + yv[5] = r->min_floor - r->pos.y; + zv[5] = WALL_L; + + xv[6] = (r->size.x - 1) * WALL_L; + yv[6] = r->min_floor - r->pos.y; + zv[6] = (r->size.z - 1) * WALL_L; + + xv[7] = WALL_L; + yv[7] = r->min_floor - r->pos.y; + zv[7] = (r->size.z - 1) * WALL_L; + + bool clip_room = false; + bool clip[8]; + + const MATRIX *const m = g_MatrixPtr; + for (int32_t i = 0; i < 8; i++) { + const int32_t x = xv[i]; + const int32_t y = yv[i]; + const int32_t z = zv[i]; + xv[i] = x * m->_00 + y * m->_01 + z * m->_02 + m->_03; + yv[i] = x * m->_10 + y * m->_11 + z * m->_12 + m->_13; + zv[i] = x * m->_20 + y * m->_21 + z * m->_22 + m->_23; + if (zv[i] > g_PhdFarZ) { + clip_room = true; + clip[i] = true; + } else { + clip[i] = false; + } + } + + if (!clip_room) { + return; + } + + int32_t min_x = 0x10000000; + int32_t min_y = 0x10000000; + int32_t max_x = -0x10000000; + int32_t max_y = -0x10000000; + for (int32_t i = 0; i < 12; i++) { + const int32_t p1 = g_BoxLines[i][0]; + const int32_t p2 = g_BoxLines[i][1]; + + if (clip[p1] == clip[p2]) { + continue; + } + + const int32_t zdiv = (zv[p2] - zv[p1]) >> W2V_SHIFT; + if (zdiv) { + const int32_t znom = (g_PhdFarZ - zv[p1]) >> W2V_SHIFT; + const int32_t x = xv[p1] + + ((((xv[p2] - xv[p1]) >> W2V_SHIFT) * znom / zdiv) + << W2V_SHIFT); + const int32_t y = yv[p1] + + ((((yv[p2] - yv[p1]) >> W2V_SHIFT) * znom / zdiv) + << W2V_SHIFT); + + CLAMPG(min_x, x); + CLAMPL(max_x, x); + CLAMPG(min_y, y); + CLAMPL(max_y, y); + } else { + CLAMPG(min_x, xv[p1]); + CLAMPG(min_x, xv[p2]); + CLAMPL(max_x, xv[p1]); + CLAMPL(max_x, xv[p2]); + CLAMPG(min_y, yv[p1]); + CLAMPG(min_y, yv[p2]); + CLAMPL(max_y, yv[p1]); + CLAMPL(max_y, yv[p2]); + } + } + + const int32_t zp = g_PhdFarZ / g_PhdPersp; + min_x = g_PhdWinCenterX + min_x / zp; + min_y = g_PhdWinCenterY + min_y / zp; + max_x = g_PhdWinCenterX + max_x / zp; + max_y = g_PhdWinCenterY + max_y / zp; + + // clang-format off + if (min_x > g_PhdWinRight || + min_y > g_PhdWinBottom || + max_x < g_PhdWinLeft || + max_y < g_PhdWinTop + ) { + return; + } + // clang-format on + + CLAMPL(min_x, g_PhdWinLeft); + CLAMPL(min_y, g_PhdWinTop); + CLAMPG(max_x, g_PhdWinRight); + CLAMPG(max_y, g_PhdWinBottom); + S_InsertBackPolygon(min_x, min_y, max_x, max_y); +} + +void __cdecl Room_DrawSingleRoomGeometry(const int16_t room_num) +{ + ROOM *const r = &g_Rooms[room_num]; + + if (r->flags & RF_UNDERWATER) { + S_SetupBelowWater(g_CameraUnderwater); + } else { + S_SetupAboveWater(g_CameraUnderwater); + } + + Matrix_TranslateAbs(r->pos.x, r->pos.y, r->pos.z); + g_PhdWinLeft = r->bound_left; + g_PhdWinRight = r->bound_right; + g_PhdWinTop = r->bound_top; + g_PhdWinBottom = r->bound_bottom; + + S_LightRoom(r); + if (g_Outside > 0 && !(r->flags & RF_INSIDE)) { + Output_InsertRoom(r->data, true); + } else { + if (g_Outside >= 0) { + Room_Clip(r); + } + Output_InsertRoom(r->data, false); + } +} + +void __cdecl Room_DrawSingleRoomObjects(const int16_t room_num) +{ + ROOM *const r = &g_Rooms[room_num]; + + if (r->flags & RF_UNDERWATER) { + S_SetupBelowWater(g_CameraUnderwater); + } else { + S_SetupAboveWater(g_CameraUnderwater); + } + + r->bound_active = 0; + + Matrix_Push(); + Matrix_TranslateAbs(r->pos.x, r->pos.y, r->pos.z); + + g_PhdWinLeft = r->bound_left; + g_PhdWinTop = r->bound_top; + g_PhdWinRight = r->bound_right; + g_PhdWinBottom = r->bound_bottom; + + for (int32_t i = 0; i < r->num_meshes; i++) { + const MESH *const mesh = &r->meshes[i]; + const STATIC_INFO *const static_obj = + &g_StaticObjects[mesh->static_num]; + if (static_obj->flags & 2) { + Matrix_Push(); + Matrix_TranslateAbs(mesh->pos.x, mesh->pos.y, mesh->pos.z); + Matrix_RotY(mesh->rot.y); + const int16_t bounds = S_GetObjectBounds(&static_obj->draw_bounds); + if (bounds) { + S_CalculateStaticMeshLight( + mesh->pos.x, mesh->pos.y, mesh->pos.z, mesh->shade_1, + mesh->shade_2, r); + Output_InsertPolygons(g_Meshes[static_obj->mesh_idx], bounds); + } + Matrix_Pop(); + } + } + + g_PhdWinLeft = 0; + g_PhdWinTop = 0; + g_PhdWinRight = g_PhdWinMaxX + 1; + g_PhdWinBottom = g_PhdWinMaxY + 1; + + int16_t item_num = r->item_num; + while (item_num != NO_ITEM) { + ITEM *const item = &g_Items[item_num]; + if (item->status != IS_INVISIBLE) { + const OBJECT *const object = &g_Objects[item->object_id]; + object->draw_routine(item); + } + item_num = item->next_item; + } + + int16_t fx_num = r->fx_num; + while (fx_num != NO_ITEM) { + const FX *const fx = &g_Effects[fx_num]; + Effect_Draw(fx_num); + fx_num = fx->next_free; + } + + Matrix_Pop(); + + r->bound_left = g_PhdWinMaxX; + r->bound_top = g_PhdWinMaxY; + r->bound_right = 0; + r->bound_bottom = 0; +} + +void __cdecl Room_DrawAllRooms(const int16_t current_room) +{ + ROOM *const r = &g_Rooms[current_room]; + r->test_left = 0; + r->test_top = 0; + r->test_right = g_PhdWinMaxX; + r->test_bottom = g_PhdWinMaxY; + r->bound_active = 2; + + g_PhdWinLeft = r->test_left; + g_PhdWinTop = r->test_top; + g_PhdWinRight = r->test_right; + g_PhdWinBottom = r->test_bottom; + + g_BoundRooms[0] = current_room; + g_BoundStart = 0; + g_BoundEnd = 1; + + g_DrawRoomsCount = 0; + g_Outside = r->flags & RF_OUTSIDE; + + if (g_Outside) { + g_OutsideTop = 0; + g_OutsideLeft = 0; + g_OutsideRight = g_PhdWinMaxX; + g_OutsideBottom = g_PhdWinMaxY; + } else { + g_OutsideLeft = g_PhdWinMaxX; + g_OutsideTop = g_PhdWinMaxY; + g_OutsideBottom = 0; + g_OutsideRight = 0; + } + + g_CameraUnderwater = r->flags & RF_UNDERWATER; + Room_GetBounds(); + + g_MidSort = 0; + if (g_Outside) { + g_PhdWinLeft = g_OutsideLeft; + g_PhdWinRight = g_OutsideRight; + g_PhdWinBottom = g_OutsideBottom; + g_PhdWinTop = g_OutsideTop; + if (g_Objects[O_SKYBOX].loaded) { + S_SetupAboveWater(g_CameraUnderwater); + Matrix_Push(); + g_MatrixPtr->_03 = 0; + g_MatrixPtr->_13 = 0; + g_MatrixPtr->_23 = 0; + const int16_t *frame = + g_Anims[g_Objects[O_SKYBOX].anim_idx].frame_ptr + FBBOX_ROT; + Matrix_RotYXZsuperpack(&frame, 0); + S_InitialisePolyList(0); + Output_InsertSkybox(g_Meshes[g_Objects[O_SKYBOX].mesh_idx]); + Matrix_Pop(); + } else { + S_InitialisePolyList(1); + g_Outside = -1; + } + } else { + S_InitialisePolyList(0); + } + + if (g_Objects[O_LARA].loaded && !(g_LaraItem->flags & IF_ONE_SHOT)) { + if (g_Rooms[g_LaraItem->room_num].flags & RF_UNDERWATER) { + S_SetupBelowWater(g_CameraUnderwater); + } else { + S_SetupAboveWater(g_CameraUnderwater); + } + g_MidSort = g_Rooms[g_LaraItem->room_num].bound_active >> 8; + if (g_MidSort) { + g_MidSort--; + } + Lara_Draw(g_LaraItem); + } + + for (int32_t i = 0; i < g_DrawRoomsCount; i++) { + const int16_t room_num = g_DrawRoomsArray[i]; + Room_DrawSingleRoomGeometry(room_num); + } + + for (int32_t i = 0; i < g_DrawRoomsCount; i++) { + const int16_t room_num = g_DrawRoomsArray[i]; + Room_DrawSingleRoomObjects(room_num); + } +} diff --git a/src/tr2/game/room_draw.h b/src/tr2/game/room_draw.h new file mode 100644 index 000000000..08579b026 --- /dev/null +++ b/src/tr2/game/room_draw.h @@ -0,0 +1,11 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Room_GetBounds(void); +void __cdecl Room_SetBounds( + const int16_t *obj_ptr, int32_t room_num, const ROOM *parent); +void __cdecl Room_Clip(const ROOM *r); +void __cdecl Room_DrawAllRooms(int16_t current_room); +void __cdecl Room_DrawSingleRoomGeometry(int16_t room_num); +void __cdecl Room_DrawSingleRoomObjects(int16_t room_num); diff --git a/src/tr2/game/savegame/common.c b/src/tr2/game/savegame/common.c new file mode 100644 index 000000000..de29fdcf6 --- /dev/null +++ b/src/tr2/game/savegame/common.c @@ -0,0 +1,23 @@ +#include "global/funcs.h" +#include "global/vars.h" + +#include +#include + +int32_t Savegame_GetSlotCount(void) +{ + return MAX_SAVE_SLOTS; +} + +bool Savegame_IsSlotFree(const int32_t slot_idx) +{ + return g_SavedLevels[slot_idx] == 0; +} + +bool Savegame_Save(const int32_t slot_idx) +{ + CreateSaveGameInfo(); + S_SaveGame(&g_SaveGame, sizeof(SAVEGAME_INFO), slot_idx); + GetSavedGamesList(&g_LoadGameRequester); + return true; +} diff --git a/src/tr2/game/shell.c b/src/tr2/game/shell.c new file mode 100644 index 000000000..7532d5dc4 --- /dev/null +++ b/src/tr2/game/shell.c @@ -0,0 +1,203 @@ +#include "game/shell.h" + +#include "config.h" +#include "decomp/decomp.h" +#include "game/console/common.h" +#include "game/demo.h" +#include "game/game_string.h" +#include "game/gameflow.h" +#include "game/gameflow/reader.h" +#include "game/input.h" +#include "game/music.h" +#include "game/sound.h" +#include "game/text.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include +#include +#include + +#include +#include + +// TODO: refactor the hell out of me +BOOL __cdecl Shell_Main(void) +{ + g_HiRes = 0; + g_ScreenSizer = 0; + g_GameSizer = 1.0; + g_GameSizerCopy = 1.0; + + GameString_Init(); + EnumMap_Init(); + Config_Init(); + Text_Init(); + UI_Init(); + Console_Init(); + + Config_Read(); + if (!S_InitialiseSystem()) { + return false; + } + + if (!GF_LoadScriptFile("data\\tombPC.dat")) { + Shell_ExitSystem("GameMain: could not load script file"); + return false; + } + + if (!GF_N_Load("cfg/TR2X_gameflow.json5")) { + Shell_ExitSystem("GameMain: could not load new script file"); + return false; + } + + InitialiseStartInfo(); + S_FrontEndCheck(); + S_LoadSettings(); + + g_HiRes = -1; + g_GameMemoryPtr = GlobalAlloc(0, 0x380000u); + + if (!g_GameMemoryPtr) { + strcpy(g_ErrorMessage, "GameMain: could not allocate malloc_buffer"); + return false; + } + + g_HiRes = 0; + TempVideoAdjust(1, 1.0); + Input_Update(); + + g_IsVidModeLock = 1; + S_DisplayPicture("data\\legal.pcx", 0); + S_InitialisePolyList(0); + S_CopyBufferToScreen(); + S_OutputPolyList(); + S_DumpScreen(); + FadeToPal(30, g_GamePalette8); + S_Wait(180, 1); + S_FadeToBlack(); + S_DontDisplayPicture(); + g_IsVidModeLock = 0; + + const bool is_frontend_fail = GF_DoFrontendSequence(); + if (g_IsGameToExit) { + Config_Write(); + return true; + } + + if (is_frontend_fail) { + strcpy(g_ErrorMessage, "GameMain: failed in GF_DoFrontendSequence()"); + return false; + } + + S_FadeToBlack(); + int16_t gf_option = g_GameFlow.first_option; + g_NoInputCounter = 0; + + bool is_loop_continued = true; + while (is_loop_continued) { + const int16_t gf_dir = gf_option & 0xFF00; + const int16_t gf_param = gf_option & 0x00FF; + + switch (gf_dir) { + case GFD_START_GAME: + if (g_GameFlow.single_level >= 0) { + gf_option = + GF_DoLevelSequence(g_GameFlow.single_level, GFL_NORMAL); + } else { + if (gf_param > g_GameFlow.num_levels) { + sprintf( + g_ErrorMessage, + "GameMain: STARTGAME with invalid level number (%d)", + gf_param); + return false; + } + gf_option = GF_DoLevelSequence(gf_param, GFL_NORMAL); + } + break; + + case GFD_START_SAVED_GAME: + S_LoadGame(&g_SaveGame, sizeof(SAVEGAME_INFO), gf_param); + if (g_SaveGame.current_level > g_GameFlow.num_levels) { + sprintf( + g_ErrorMessage, + "GameMain: STARTSAVEDGAME with invalid level number (%d)", + g_SaveGame.current_level); + return false; + } + gf_option = GF_DoLevelSequence(g_SaveGame.current_level, GFL_SAVED); + break; + + case GFD_START_CINE: + Game_Cutscene_Start(gf_param); + gf_option = GFD_EXIT_TO_TITLE; + break; + + case GFD_START_DEMO: + gf_option = Demo_Control(-1); + break; + + case GFD_LEVEL_COMPLETE: + gf_option = LevelCompleteSequence(); + break; + + case GFD_EXIT_TO_TITLE: + case GFD_EXIT_TO_OPTION: + if (g_GameFlow.title_disabled) { + if (g_GameFlow.title_replace < 0 + || g_GameFlow.title_replace == GFD_EXIT_TO_TITLE) { + strcpy( + g_ErrorMessage, + "GameMain Failed: Title disabled & no replacement"); + return false; + } + gf_option = g_GameFlow.title_replace; + } else { + gf_option = TitleSequence(); + g_GF_StartGame = 1; + } + break; + + default: + is_loop_continued = false; + break; + } + } + + S_SaveSettings(); + GameBuf_Shutdown(); + EnumMap_Shutdown(); + GameString_Shutdown(); + return true; +} + +void __cdecl Shell_Cleanup(void) +{ + Music_Shutdown(); +} + +void __cdecl Shell_ExitSystem(const char *message) +{ + GameBuf_Shutdown(); + strcpy(g_ErrorMessage, message); + Shell_Shutdown(); + Shell_Cleanup(); + exit(1); +} + +void __cdecl Shell_ExitSystemFmt(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + int32_t size = vsnprintf(NULL, 0, fmt, va) + 1; + char *message = Memory_Alloc(size); + va_end(va); + + va_start(va, fmt); + vsnprintf(message, size, fmt, va); + va_end(va); + + Shell_ExitSystem(message); + + Memory_FreePointer(&message); +} diff --git a/src/tr2/game/shell.h b/src/tr2/game/shell.h new file mode 100644 index 000000000..87c9bfd6a --- /dev/null +++ b/src/tr2/game/shell.h @@ -0,0 +1,8 @@ +#pragma once + +#include "global/types.h" + +BOOL __cdecl Shell_Main(void); +void __cdecl Shell_Cleanup(void); +void __cdecl Shell_ExitSystem(const char *message); +void __cdecl Shell_ExitSystemFmt(const char *fmt, ...); diff --git a/src/tr2/game/sound.c b/src/tr2/game/sound.c new file mode 100644 index 000000000..159c3f38b --- /dev/null +++ b/src/tr2/game/sound.c @@ -0,0 +1,402 @@ +#include "game/sound.h" + +#include "game/math.h" +#include "game/random.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include +#include +#include + +#include + +typedef enum { + SOUND_MODE_NORMAL = 0, + SOUND_MODE_WAIT = 1, + SOUND_MODE_RESTART = 2, + SOUND_MODE_LOOPED = 3, + SOUND_MODE_MASK = 3, +} SOUND_MODE; + +typedef struct { + int32_t volume; + int32_t pan; + int32_t sample_num; + int32_t pitch; + int32_t handle; +} SOUND_SLOT; + +typedef enum { + // clang-format off + SF_NO_PAN = 1 << 12, // = 0x1000 = 4096 + SF_PITCH_WIBBLE = 1 << 13, // = 0x2000 = 8192 + SF_VOLUME_WIBBLE = 1 << 14, // = 0x4000 = 16384 + // clang-format on +} SAMPLE_FLAG; + +#define SOUND_DEFAULT_PITCH 0x10000 + +#define SOUND_RANGE 10 +#define SOUND_RADIUS (SOUND_RANGE * WALL_L) // = 0x2800 = 10240 +#define SOUND_RADIUS_SQRD SQUARE(SOUND_RADIUS) // = 0x6400000 + +#define SOUND_MAX_SLOTS 32 +#define SOUND_MAX_VOLUME (SOUND_RADIUS - 1) +#define SOUND_MAX_VOLUME_CHANGE 0x2000 +#define SOUND_MAX_PITCH_CHANGE 6000 + +#define SOUND_MAXVOL_RANGE 1 +#define SOUND_MAXVOL_RADIUS (SOUND_MAXVOL_RANGE * WALL_L) // = 0x400 = 1024 +#define SOUND_MAXVOL_RADIUS_SQRD SQUARE(SOUND_MAXVOL_RADIUS) // = 0x100000 + +#define DECIBEL_LUT_SIZE 512 + +static int32_t m_DecibelLUT[DECIBEL_LUT_SIZE] = { 0 }; +static SOUND_SLOT m_SoundSlots[SOUND_MAX_SLOTS] = { 0 }; + +static int32_t M_ConvertVolumeToDecibel(int32_t volume); +static int32_t M_ConvertPanToDecibel(uint16_t pan); +static float M_ConvertPitch(float pitch); +static int32_t M_Play( + int32_t track_id, int32_t volume, float pitch, int32_t pan, bool is_looped); + +static void M_ClearSlot(SOUND_SLOT *const slot); +static void M_ClearAllSlots(void); +static void M_CloseSlot(SOUND_SLOT *const slot); +static void M_UpdateSlot(SOUND_SLOT *const slot); + +static int32_t M_ConvertVolumeToDecibel(const int32_t volume) +{ + const double adjusted_volume = g_MasterVolume * volume; + const double scaler = 0x1.p-21; // 2.0e-21 + return (adjusted_volume * scaler - 1.0) * 5000.0; +} + +static int32_t M_ConvertPanToDecibel(const uint16_t pan) +{ + const int32_t result = sin((pan / 32767.0) * M_PI) * (DECIBEL_LUT_SIZE / 2); + if (result > 0) { + return -m_DecibelLUT[DECIBEL_LUT_SIZE - result]; + } else if (result < 0) { + return m_DecibelLUT[DECIBEL_LUT_SIZE + result]; + } else { + return 0; + } +} + +static float M_ConvertPitch(const float pitch) +{ + return pitch / 0x10000.p0; +} + +static int32_t M_Play( + const int32_t sample_num, const int32_t volume, const float pitch, + const int32_t pan, const bool is_looped) +{ + const int32_t handle = Audio_Sample_Play( + sample_num, M_ConvertVolumeToDecibel(volume), M_ConvertPitch(pitch), + M_ConvertPanToDecibel(pan), is_looped); + return handle; +} + +static void M_ClearAllSlots(void) +{ + for (int32_t i = 0; i < SOUND_MAX_SLOTS; i++) { + SOUND_SLOT *const slot = &m_SoundSlots[i]; + M_ClearSlot(slot); + } +} + +static void M_ClearSlot(SOUND_SLOT *const slot) +{ + slot->sample_num = -1; + slot->handle = AUDIO_NO_SOUND; +} + +static void M_CloseSlot(SOUND_SLOT *const slot) +{ + Audio_Sample_Close(slot->handle); + M_ClearSlot(slot); +} + +static void M_UpdateSlot(SOUND_SLOT *const slot) +{ + Audio_Sample_SetPan(slot->handle, M_ConvertPanToDecibel(slot->pan)); + Audio_Sample_SetPitch(slot->handle, M_ConvertPitch(slot->pitch)); + Audio_Sample_SetVolume( + slot->handle, M_ConvertVolumeToDecibel(slot->volume)); +} + +void __cdecl Sound_Init(void) +{ + m_DecibelLUT[0] = -10000; + for (int32_t i = 1; i < DECIBEL_LUT_SIZE; i++) { + m_DecibelLUT[i] = (log2(1.0 / DECIBEL_LUT_SIZE) - log2(1.0 / i)) * 1000; + } + + if (!Audio_Init()) { + LOG_ERROR("Failed to initialize libtrx sound system"); + return; + } + + Sound_SetMasterVolume(32); + M_ClearAllSlots(); + g_SoundIsActive = true; +} + +void __cdecl Sound_Shutdown(void) +{ + if (!g_SoundIsActive) { + return; + } + + Audio_Shutdown(); + M_ClearAllSlots(); +} + +void __cdecl Sound_SetMasterVolume(int32_t volume) +{ + g_MasterVolume = volume; +} + +void __cdecl Sound_UpdateEffects(void) +{ + for (int32_t i = 0; i < g_SoundEffectCount; i++) { + OBJECT_VECTOR *sound = &g_SoundEffects[i]; + if ((g_FlipStatus && (sound->flags & SF_FLIP)) + || (!g_FlipStatus && (sound->flags & SF_UNFLIP))) { + Sound_Effect( + sound->data, (XYZ_32 *)sound, + SPM_NORMAL); // TODO: use proper pointer for this + } + } + + if (g_FlipEffect != -1) { + g_EffectRoutines[g_FlipEffect](NULL); + } + + Sound_EndScene(); +} + +void __cdecl Sound_Effect( + const SOUND_EFFECT_ID sample_id, const XYZ_32 *const pos, + const uint32_t flags) +{ + if (!g_SoundIsActive) { + return; + } + + if (flags != SPM_ALWAYS + && ((flags & SPM_UNDERWATER) + != (g_Rooms[g_Camera.pos.room_num].flags & RF_UNDERWATER))) { + return; + } + + const int32_t sample_num = g_SampleLUT[sample_id]; + if (sample_num == -1) { + g_SampleLUT[sample_id] = -2; + return; + } + if (sample_num == -2) { + return; + } + + SAMPLE_INFO *const s = &g_SampleInfos[sample_num]; + if (s->randomness && (Random_GetDraw() > s->randomness)) { + return; + } + + uint32_t distance = 0; + int32_t pan = 0; + if (pos) { + const int32_t dx = pos->x - g_Camera.mic_pos.x; + const int32_t dy = pos->y - g_Camera.mic_pos.y; + const int32_t dz = pos->z - g_Camera.mic_pos.z; + if (ABS(dx) > SOUND_RADIUS || ABS(dy) > SOUND_RADIUS + || ABS(dz) > SOUND_RADIUS) { + return; + } + distance = SQUARE(dx) + SQUARE(dy) + SQUARE(dz); + if (distance > SOUND_RADIUS_SQRD) { + return; + } else if (distance < SOUND_MAXVOL_RADIUS_SQRD) { + distance = 0; + } else { + distance = Math_Sqrt(distance) - SOUND_MAXVOL_RADIUS; + } + if (!(s->flags & SF_NO_PAN)) { + pan = (int16_t)Math_Atan(dz, dx) - g_Camera.actual_angle; + } + } + + int32_t volume = s->volume; + if (s->flags & SF_VOLUME_WIBBLE) { + volume -= Random_GetDraw() * SOUND_MAX_VOLUME_CHANGE >> 15; + } + const int32_t attenuation = + SQUARE(distance) / (SOUND_RADIUS_SQRD / 0x10000); + volume = (volume * (0x10000 - attenuation)) / 0x10000; + + if (volume <= 0) { + return; + } + + int32_t pitch = (flags & SPM_PITCH) != 0 ? (flags >> 8) & 0xFFFFFF + : SOUND_DEFAULT_PITCH; + if (s->flags & SF_PITCH_WIBBLE) { + pitch += ((Random_GetDraw() * SOUND_MAX_PITCH_CHANGE) / 0x4000) + - SOUND_MAX_PITCH_CHANGE; + } + + if (s->number < 0) { + return; + } + + const SOUND_MODE mode = s->flags & SOUND_MODE_MASK; + const int32_t num_samples = (s->flags >> 2) & 0xF; + const int32_t track_id = num_samples == 1 + ? s->number + : s->number + (int32_t)((num_samples * Random_GetDraw()) / 0x8000); + + switch (mode) { + case SOUND_MODE_NORMAL: + break; + + case SOUND_MODE_WAIT: + for (int32_t i = 0; i < SOUND_MAX_SLOTS; i++) { + SOUND_SLOT *const slot = &m_SoundSlots[i]; + if (slot->sample_num == sample_num) { + if (Audio_Sample_IsPlaying(i)) { + return; + } + M_ClearSlot(slot); + } + } + break; + + case SOUND_MODE_RESTART: + for (int32_t i = 0; i < SOUND_MAX_SLOTS; i++) { + SOUND_SLOT *const slot = &m_SoundSlots[i]; + if (slot->sample_num == sample_num) { + M_CloseSlot(slot); + break; + } + } + break; + + case SOUND_MODE_LOOPED: + for (int32_t i = 0; i < SOUND_MAX_SLOTS; i++) { + SOUND_SLOT *const slot = &m_SoundSlots[i]; + if (slot->sample_num == sample_num) { + if (volume > slot->volume) { + slot->volume = volume; + slot->pan = pan; + slot->pitch = pitch; + } + return; + } + } + break; + } + + const bool is_looped = mode == SOUND_MODE_LOOPED; + int32_t handle = M_Play(track_id, volume, pitch, pan, is_looped); + + if (handle == AUDIO_NO_SOUND) { + int32_t min_volume = 0x8000; + int32_t min_slot = -1; + for (int32_t i = 1; i < SOUND_MAX_SLOTS; i++) { + SOUND_SLOT *const slot = &m_SoundSlots[i]; + if (slot->sample_num >= 0 && slot->volume < min_volume) { + min_volume = slot->volume; + min_slot = i; + } + } + + if (min_slot >= 0 && volume >= min_volume) { + SOUND_SLOT *const slot = &m_SoundSlots[min_slot]; + M_CloseSlot(slot); + handle = M_Play(track_id, volume, pitch, pan, is_looped); + } + } + + if (handle == AUDIO_NO_SOUND) { + s->number = -1; + } else { + int32_t free_slot = -1; + for (int32_t i = 0; i < SOUND_MAX_SLOTS; i++) { + SOUND_SLOT *const slot = &m_SoundSlots[i]; + if (slot->sample_num < 0) { + free_slot = i; + break; + } + } + + if (free_slot != -1) { + SOUND_SLOT *const slot = &m_SoundSlots[free_slot]; + slot->volume = volume; + slot->pan = pan; + slot->pitch = pitch; + slot->sample_num = sample_num; + slot->handle = handle; + } + } +} + +void __cdecl Sound_StopEffect(const SOUND_EFFECT_ID sample_id) +{ + if (!g_SoundIsActive) { + return; + } + + const int32_t sample_num = g_SampleLUT[sample_id]; + const int32_t num_samples = (g_SampleInfos[sample_num].flags >> 2) & 0xF; + + for (int32_t i = 0; i < SOUND_MAX_SLOTS; i++) { + SOUND_SLOT *const slot = &m_SoundSlots[i]; + if (slot->sample_num >= sample_num + && slot->sample_num < sample_num + num_samples) { + M_CloseSlot(slot); + } + } +} + +void __cdecl Sound_StopAllSamples(void) +{ + Audio_Sample_CloseAll(); + M_ClearAllSlots(); +} + +void __cdecl Sound_EndScene(void) +{ + if (!g_SoundIsActive) { + return; + } + + for (int32_t i = 0; i < SOUND_MAX_SLOTS; i++) { + SOUND_SLOT *const slot = &m_SoundSlots[i]; + SAMPLE_INFO *const s = &g_SampleInfos[slot->sample_num]; + if (slot->sample_num < 0) { + continue; + } + + if ((s->flags & SOUND_MODE_MASK) == SOUND_MODE_LOOPED) { + if (slot->volume == 0) { + M_CloseSlot(slot); + } else { + M_UpdateSlot(slot); + slot->volume = 0; + } + } else if (!Audio_Sample_IsPlaying(slot->handle)) { + M_ClearSlot(slot); + } + } +} + +bool Sound_IsAvailable(const SOUND_EFFECT_ID sample_id) +{ + return sample_id >= 0 && sample_id < SFX_NUMBER_OF + && g_SampleLUT[sample_id] != -1; +} diff --git a/src/tr2/game/sound.h b/src/tr2/game/sound.h new file mode 100644 index 000000000..0b889f07d --- /dev/null +++ b/src/tr2/game/sound.h @@ -0,0 +1,14 @@ +#pragma once + +#include "global/types.h" + +void __cdecl Sound_Init(void); +void __cdecl Sound_Shutdown(void); + +void __cdecl Sound_SetMasterVolume(int32_t volume); +void __cdecl Sound_UpdateEffects(void); +void __cdecl Sound_Effect( + SOUND_EFFECT_ID sample_id, const XYZ_32 *pos, uint32_t flags); +void __cdecl Sound_StopEffect(SOUND_EFFECT_ID sample_id); +void __cdecl Sound_StopAllSamples(void); +void __cdecl Sound_EndScene(void); diff --git a/src/tr2/game/text.c b/src/tr2/game/text.c new file mode 100644 index 000000000..350dfd820 --- /dev/null +++ b/src/tr2/game/text.c @@ -0,0 +1,485 @@ +#include "game/text.h" + +#include "game/console/common.h" +#include "game/output.h" +#include "game/overlay.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#include +#include + +#define MAX_TEXTSTRINGS 128 +#define TEXT_Y_SPACING 3 +#define CHAR_SECRET_1 0x7Fu +#define CHAR_SECRET_2 0x80u +#define CHAR_SECRET_3 0x81u +#define IS_CHAR_LEGAL(c) ((c) <= CHAR_SECRET_3 && ((c) <= 18u || (c) >= 32u)) +#define IS_CHAR_SECRET(c) ((c) >= CHAR_SECRET_1 && (c) <= CHAR_SECRET_3) +#define IS_CHAR_DIACRITIC(c) \ + ((c) == '(' || (c) == ')' || (c) == '$' || (c) == '~') +#define IS_CHAR_SPACE(c) ((c) == 32) +#define IS_CHAR_DIGIT(c) ((c) <= 0xAu) + +// TODO: replace textstring == NULL checks with assertions + +static TEXTSTRING m_TextStrings[MAX_TEXTSTRINGS]; + +void __cdecl Text_Init(void) +{ + Overlay_DisplayModeInfo(NULL); + for (int32_t i = 0; i < MAX_TEXTSTRINGS; i++) { + m_TextStrings[i].flags.all = 0; + } +} + +void Text_Shutdown(void) +{ +} + +TEXTSTRING *__cdecl Text_Create( + const int32_t x, const int32_t y, const int32_t z, const char *const text) +{ + if (text == NULL) { + return NULL; + } + + int32_t free_idx = -1; + for (int32_t i = 0; i < MAX_TEXTSTRINGS; i++) { + TEXTSTRING *const string = &m_TextStrings[i]; + if (!string->flags.active) { + free_idx = i; + break; + } + } + + if (free_idx == -1) { + return NULL; + } + + TEXTSTRING *result = &m_TextStrings[free_idx]; + result->text = g_TextstringBuffers[free_idx]; + result->scale.h = PHD_ONE; + result->scale.v = PHD_ONE; + result->pos.x = (x * Text_GetScaleH(PHD_ONE)) / PHD_ONE; + result->pos.y = (y * Text_GetScaleV(PHD_ONE)) / PHD_ONE; + result->pos.z = z; + + result->letter_spacing = 1; + result->word_spacing = 6; + + result->text_flags = 0; + result->outl_flags = 0; + result->bgnd_flags = 0; + result->bgnd_size.x = 0; + result->bgnd_size.y = 0; + result->bgnd_off.x = 0; + result->bgnd_off.y = 0; + result->bgnd_off.z = 0; + result->flags.all = 0; + result->flags.active = 1; + + strncpy(result->text, text, TEXT_MAX_STRING_SIZE); + result->text[TEXT_MAX_STRING_SIZE - 1] = '\0'; + + return result; +} + +void __cdecl Text_ChangeText(TEXTSTRING *const string, const char *const text) +{ + if (string == NULL) { + return; + } + assert(text); + if (string->flags.active) { + strncpy(string->text, text, TEXT_MAX_STRING_SIZE); + string->text[TEXT_MAX_STRING_SIZE - 1] = '\0'; + } +} + +void __cdecl Text_SetPos(TEXTSTRING *const string, int16_t x, int16_t y) +{ + if (string == NULL) { + return; + } + string->pos.x = (x * Text_GetScaleH(PHD_ONE)) / PHD_ONE; + string->pos.y = (y * Text_GetScaleV(PHD_ONE)) / PHD_ONE; +} + +void __cdecl Text_SetScale( + TEXTSTRING *const string, const int32_t scale_h, const int32_t scale_v) +{ + if (string == NULL) { + return; + } + string->scale.h = scale_h; + string->scale.v = scale_v; +} + +void __cdecl Text_Flash( + TEXTSTRING *const string, const int16_t enable, const int16_t rate) +{ + if (string == NULL) { + return; + } + if (enable) { + string->flags.flash = 1; + string->flash.rate = rate; + string->flash.count = rate; + } else { + string->flags.flash = 0; + } +} + +void __cdecl Text_AddBackground( + TEXTSTRING *const string, const int16_t x_size, const int16_t y_size, + const int16_t x_off, const int16_t y_off, const int16_t z_off, + const INV_COLOR color, const uint16_t *const gour_ptr, const uint16_t flags) +{ + if (string == NULL) { + return; + } + uint32_t scale_h = Text_GetScaleH(string->scale.h); + uint32_t scale_v = Text_GetScaleV(string->scale.v); + string->flags.background = 1; + string->bgnd_size.x = (scale_h * x_size) / PHD_ONE; + string->bgnd_size.y = (scale_v * y_size) / PHD_ONE; + string->bgnd_off.x = (scale_h * x_off) / PHD_ONE; + string->bgnd_off.y = (scale_v * y_off) / PHD_ONE; + string->bgnd_off.z = z_off; + string->bgnd_color = color; + string->bgnd_gour = gour_ptr; + string->bgnd_flags = flags; +} + +void __cdecl Text_RemoveBackground(TEXTSTRING *const string) +{ + if (string == NULL) { + return; + } + string->flags.background = 0; +} + +void __cdecl Text_AddOutline( + TEXTSTRING *const string, const int16_t enable, const INV_COLOR color, + const uint16_t *const gour_ptr, const uint16_t flags) +{ + if (string == NULL) { + return; + } + string->flags.outline = 1; + string->outl_gour = gour_ptr; + string->outl_color = color; + string->outl_flags = flags; +} + +void __cdecl Text_RemoveOutline(TEXTSTRING *const string) +{ + if (string == NULL) { + return; + } + string->flags.outline = 0; +} + +void __cdecl Text_CentreH(TEXTSTRING *const string, const int16_t enable) +{ + if (string == NULL) { + return; + } + string->flags.centre_h = enable; +} + +void __cdecl Text_CentreV(TEXTSTRING *const string, const int16_t enable) +{ + if (string == NULL) { + return; + } + string->flags.centre_v = enable; +} + +void __cdecl Text_AlignRight(TEXTSTRING *const string, const int16_t enable) +{ + if (string == NULL) { + return; + } + string->flags.right = enable; +} + +void __cdecl Text_AlignBottom(TEXTSTRING *const string, const int16_t enable) +{ + if (string == NULL) { + return; + } + string->flags.bottom = enable; +} + +void __cdecl Text_SetMultiline(TEXTSTRING *string, bool enable) +{ + if (string == NULL) { + return; + } + string->flags.multiline = enable; +} + +int32_t __cdecl Text_Remove(TEXTSTRING *const string) +{ + if (string == NULL) { + return false; + } + if (!string->flags.active) { + return false; + } + string->flags.active = false; + return true; +} + +int32_t __cdecl Text_GetWidth(TEXTSTRING *const string) +{ + if (string == NULL) { + return 0; + } + + const uint32_t scale_h = Text_GetScaleH(string->scale.h); + const char *str = string->text; + int32_t width = 0; + + while (1) { + uint8_t c = *str++; + if (!c) { + break; + } + + if (!IS_CHAR_LEGAL(c) || IS_CHAR_DIACRITIC(c)) { + continue; + } + + int32_t spacing; + if (IS_CHAR_SPACE(c)) { + spacing = string->word_spacing; + } else if (IS_CHAR_SECRET(c)) { + spacing = 16; + } else { + int16_t sprite_num; + if (IS_CHAR_DIGIT(c)) { + sprite_num = c + 81; + } else { + sprite_num = g_TextASCIIMap[c]; + } + + // TODO: OG bug - this should check c, not sprite_num + if (sprite_num >= '0' && sprite_num <= '9') { + spacing = 12; + } else { + // TODO: OG bug - wrong letter spacing calculation + spacing = g_TextSpacing[sprite_num] + string->letter_spacing; + } + } + + width += spacing * scale_h / PHD_ONE; + } + + // TODO: OG bug - wrong letter spacing calculation; pointless ~1 + return ((int16_t)width - string->letter_spacing) & ~1; +} + +int32_t Text_GetHeight(const TEXTSTRING *const string) +{ + if (string == NULL) { + return 0; + } + int32_t height = TEXT_HEIGHT; + char *ptr = string->text; + for (char letter = *ptr; *ptr; letter = *ptr++) { + if (string->flags.multiline && *ptr == '\n') { + height += TEXT_HEIGHT + TEXT_Y_SPACING; + } + } + return height * Text_GetScaleV(string->scale.v) / PHD_ONE; +} + +void __cdecl Text_Draw(void) +{ + // TODO: move me outta here! + Console_Draw(); + for (int32_t i = 0; i < MAX_TEXTSTRINGS; i++) { + TEXTSTRING *const string = &m_TextStrings[i]; + if (string->flags.active) { + Text_DrawText(string); + } + } +} + +void __cdecl Text_DrawBorder( + const int32_t x, const int32_t y, const int32_t z, const int32_t width, + const int32_t height) +{ + const int32_t mesh_idx = g_Objects[O_TEXT_BOX].mesh_idx; + + const int32_t offset = 4; + const int32_t x0 = x + offset; + const int32_t y0 = y + offset; + const int32_t x1 = x0 + width - offset * 2; + const int32_t y1 = y0 + height - offset * 2; + const int32_t scale_h = PHD_ONE; + const int32_t scale_v = PHD_ONE; + + Output_DrawScreenSprite2D( + x0, y0, z, scale_h, scale_v, mesh_idx + 0, 0x1000, 0); + Output_DrawScreenSprite2D( + x1, y0, z, scale_h, scale_v, mesh_idx + 1, 0x1000, 0); + Output_DrawScreenSprite2D( + x1, y1, z, scale_h, scale_v, mesh_idx + 2, 0x1000, 0); + Output_DrawScreenSprite2D( + x0, y1, z, scale_h, scale_v, mesh_idx + 3, 0x1000, 0); + + int32_t w = (width - offset * 2) * PHD_ONE / 8; + int32_t h = (height - offset * 2) * PHD_ONE / 8; + + Output_DrawScreenSprite2D(x0, y0, z, w, scale_v, mesh_idx + 4, 0x1000, 0); + Output_DrawScreenSprite2D(x1, y0, z, scale_h, h, mesh_idx + 5, 0x1000, 0); + Output_DrawScreenSprite2D(x0, y1, z, w, scale_v, mesh_idx + 6, 0x1000, 0); + Output_DrawScreenSprite2D(x0, y0, z, scale_h, h, mesh_idx + 7, 0x1000, 0); +} + +void __cdecl Text_DrawText(TEXTSTRING *const string) +{ + int32_t box_w = 0; + int32_t box_h = 0; + const int32_t scale_h = Text_GetScaleH(string->scale.h); + const int32_t scale_v = Text_GetScaleV(string->scale.v); + + if (string->flags.flash) { + string->flash.count -= g_Camera.num_frames; + if (string->flash.count <= -string->flash.rate) { + string->flash.count = string->flash.rate; + } else if (string->flash.count < 0) { + return; + } + } + + int32_t x = string->pos.x; + int32_t y = string->pos.y; + int32_t z = string->pos.z; + int32_t text_width = Text_GetWidth(string); + + if (string->flags.centre_h) { + x += (GetRenderWidth() - text_width) / 2; + } else if (string->flags.right) { + x += GetRenderWidth() - text_width; + } + + if (string->flags.centre_v) { + y += GetRenderHeight() / 2; + } else if (string->flags.bottom) { + y += GetRenderHeight(); + } + + int32_t box_x = x + string->bgnd_off.x - ((2 * scale_h) / PHD_ONE); + int32_t box_y = y + string->bgnd_off.y - ((4 * scale_v) / PHD_ONE) + - ((11 * scale_v) / PHD_ONE); + const int32_t start_x = x; + + const char *str = string->text; + while (1) { + const uint8_t c = *str++; + if (!c) { + break; + } + + if (string->flags.multiline && c == '\n') { + y += (TEXT_HEIGHT + TEXT_Y_SPACING) * string->scale.v / PHD_ONE; + x = start_x; + continue; + } + + if (!IS_CHAR_LEGAL(c)) { + continue; + } + + if (IS_CHAR_SPACE(c)) { + const int32_t spacing = string->word_spacing; + x += spacing * scale_h / PHD_ONE; + } else if (IS_CHAR_SECRET(c)) { + Output_DrawPickup( + x + 10, y, 7144, + g_Objects[O_SECRET_1 + c - CHAR_SECRET_1].mesh_idx, 4096); + const int32_t spacing = 16; + x += spacing * scale_h / PHD_ONE; + } else { + int32_t sprite_num; + if (IS_CHAR_DIGIT(c)) { + sprite_num = c + 81; + } else if (c <= 0x12) { + sprite_num = c + 91; + } else { + sprite_num = g_TextASCIIMap[c]; + } + + if (c >= '0' && c <= '9') { + const int32_t spacing = (12 - g_TextSpacing[sprite_num]) / 2; + x += spacing * scale_h / PHD_ONE; + } + + if (x >= 0 && x < GetRenderWidth() && y >= 0 + && y < GetRenderHeight()) { + Output_DrawScreenSprite2D( + x, y, z, scale_h, scale_v, + g_Objects[O_ALPHABET].mesh_idx + sprite_num, 4096, + string->text_flags); + } + + if (IS_CHAR_DIACRITIC(c)) { + continue; + } + + if (c >= '0' && c <= '9') { + const int32_t x_off = (12 - g_TextSpacing[sprite_num]) / 2; + x += (12 - x_off) * scale_h / PHD_ONE; + } else { + const int32_t spacing = + g_TextSpacing[sprite_num] + string->letter_spacing; + x += spacing * scale_h / PHD_ONE; + } + } + } + + if (string->flags.outline || string->flags.background) { + if (string->bgnd_size.x) { + box_x += (text_width - string->bgnd_size.x) / 2; + box_w = string->bgnd_size.x + 4; + } else { + box_w = text_width + 4; + } + + box_h = string->bgnd_size.y ? string->bgnd_size.y + : ((16 * scale_v) / PHD_ONE); + } + + if (string->flags.background) { + S_DrawScreenFBox( + box_x, box_y, string->bgnd_off.z + z + 2, box_w, box_h, + string->bgnd_color, (const GOURAUD_FILL *)string->bgnd_gour, + string->bgnd_flags); + } + + if (string->flags.outline) { + Text_DrawBorder(box_x, box_y, z, box_w, box_h); + } +} + +uint32_t __cdecl Text_GetScaleH(const uint32_t value) +{ + const int32_t render_width = GetRenderWidth(); + const int32_t render_scale = MAX(render_width, 640) * PHD_ONE / 640; + return (value / PHD_HALF) * (render_scale / PHD_HALF); +} + +uint32_t __cdecl Text_GetScaleV(const uint32_t value) +{ + const int32_t render_height = GetRenderHeight(); + const int32_t render_scale = MAX(render_height, 480) * PHD_ONE / 480; + return (value / PHD_HALF) * (render_scale / PHD_HALF); +} diff --git a/src/tr2/game/text.h b/src/tr2/game/text.h new file mode 100644 index 000000000..d92043406 --- /dev/null +++ b/src/tr2/game/text.h @@ -0,0 +1,41 @@ +#pragma once + +#include "global/types.h" + +#define TEXT_HEIGHT 15 +#define TEXT_MAX_STRING_SIZE 64 + +void __cdecl Text_Init(void); +void Text_Shutdown(void); + +TEXTSTRING *__cdecl Text_Create( + int32_t x, int32_t y, int32_t z, const char *string); +void __cdecl Text_ChangeText(TEXTSTRING *string, const char *text); +void __cdecl Text_SetPos(TEXTSTRING *string, int16_t x, int16_t y); +void __cdecl Text_SetScale( + TEXTSTRING *string, int32_t scale_h, int32_t scale_v); +void __cdecl Text_Flash(TEXTSTRING *string, int16_t enable, int16_t rate); +void __cdecl Text_AddBackground( + TEXTSTRING *string, int16_t x_size, int16_t y_size, int16_t x_off, + int16_t y_off, int16_t z_off, INV_COLOR color, const uint16_t *gour_ptr, + uint16_t flags); +void __cdecl Text_RemoveBackground(TEXTSTRING *string); +void __cdecl Text_AddOutline( + TEXTSTRING *string, int16_t enable, INV_COLOR color, + const uint16_t *gour_ptr, uint16_t flags); +void __cdecl Text_RemoveOutline(TEXTSTRING *string); +void __cdecl Text_CentreH(TEXTSTRING *string, int16_t enable); +void __cdecl Text_CentreV(TEXTSTRING *string, int16_t enable); +void __cdecl Text_AlignRight(TEXTSTRING *string, int16_t enable); +void __cdecl Text_AlignBottom(TEXTSTRING *string, int16_t enable); +void __cdecl Text_SetMultiline(TEXTSTRING *textstring, bool enable); +int32_t __cdecl Text_Remove(TEXTSTRING *string); +int32_t __cdecl Text_GetWidth(TEXTSTRING *string); +int32_t __cdecl Text_GetHeight(const TEXTSTRING *string); + +void __cdecl Text_Draw(void); +void __cdecl Text_DrawBorder( + int32_t x, int32_t y, int32_t z, int32_t width, int32_t height); +void __cdecl Text_DrawText(TEXTSTRING *string); +uint32_t __cdecl Text_GetScaleH(uint32_t value); +uint32_t __cdecl Text_GetScaleV(uint32_t value); diff --git a/src/tr2/game/ui/common.c b/src/tr2/game/ui/common.c new file mode 100644 index 000000000..3833a20f7 --- /dev/null +++ b/src/tr2/game/ui/common.c @@ -0,0 +1,29 @@ +#include + +#include + +int32_t UI_GetCanvasWidth(void) +{ + return 640; +} + +int32_t UI_GetCanvasHeight(void) +{ + return 480; +} + +UI_INPUT UI_TranslateInput(uint32_t system_keycode) +{ + // clang-format off + switch (system_keycode) { + case VK_LEFT: return UI_KEY_LEFT; + case VK_RIGHT: return UI_KEY_RIGHT; + case VK_HOME: return UI_KEY_HOME; + case VK_END: return UI_KEY_END; + case VK_BACK: return UI_KEY_BACK; + case VK_RETURN: return UI_KEY_RETURN; + case VK_ESCAPE: return UI_KEY_ESCAPE; + } + // clang-format on + return -1; +} diff --git a/src/tr2/game/ui/controllers/controls.c b/src/tr2/game/ui/controllers/controls.c new file mode 100644 index 000000000..f2cf7aec9 --- /dev/null +++ b/src/tr2/game/ui/controllers/controls.c @@ -0,0 +1,210 @@ +#include "game/ui/controllers/controls.h" + +#include "game/input.h" +#include "global/vars.h" + +#include + +#include + +static const INPUT_ROLE m_LeftRoles[] = { + INPUT_ROLE_UP, INPUT_ROLE_DOWN, INPUT_ROLE_LEFT, + INPUT_ROLE_RIGHT, INPUT_ROLE_STEP_LEFT, INPUT_ROLE_STEP_RIGHT, + INPUT_ROLE_SLOW, (INPUT_ROLE)-1, +}; + +static const INPUT_ROLE m_RightRoles[] = { + INPUT_ROLE_JUMP, INPUT_ROLE_ACTION, INPUT_ROLE_DRAW_WEAPON, + INPUT_ROLE_FLARE, INPUT_ROLE_LOOK, INPUT_ROLE_ROLL, + INPUT_ROLE_OPTION, INPUT_ROLE_CONSOLE, (INPUT_ROLE)-1, +}; + +static const INPUT_ROLE *M_GetInputRoles(int32_t col); +static bool M_NavigateLayout(UI_CONTROLS_CONTROLLER *controller); +static bool M_NavigateInputs(UI_CONTROLS_CONTROLLER *controller); +static bool M_ListenDebounce(UI_CONTROLS_CONTROLLER *controller); +static bool M_Listen(UI_CONTROLS_CONTROLLER *controller); +static bool M_NavigateInputsDebounce(UI_CONTROLS_CONTROLLER *controller); + +static const INPUT_ROLE *M_GetInputRoles(const int32_t col) +{ + return col == 0 ? m_LeftRoles : m_RightRoles; +} + +static bool M_NavigateLayout(UI_CONTROLS_CONTROLLER *const controller) +{ + if ((g_InputDB & IN_DESELECT) || (g_InputDB & IN_SELECT)) { + controller->state = UI_CONTROLS_STATE_EXIT; + } else if (g_InputDB & IN_RIGHT) { + controller->active_layout++; + controller->active_layout %= INPUT_MAX_LAYOUT; + } else if (g_InputDB & IN_LEFT) { + if (controller->active_layout == 0) { + controller->active_layout = INPUT_MAX_LAYOUT - 1; + } else { + controller->active_layout--; + } + } else if ((g_InputDB & IN_BACK) && controller->active_layout != 0) { + controller->state = UI_CONTROLS_STATE_NAVIGATE_INPUTS; + controller->active_col = 0; + controller->active_row = 0; + } else if ((g_InputDB & IN_FORWARD) && controller->active_layout != 0) { + controller->state = UI_CONTROLS_STATE_NAVIGATE_INPUTS; + controller->active_col = 1; + controller->active_row = UI_ControlsController_GetInputRoleCount(1) - 1; + } else { + return false; + } + controller->active_role = + M_GetInputRoles(controller->active_col)[controller->active_row]; + return true; +} + +static bool M_NavigateInputs(UI_CONTROLS_CONTROLLER *const controller) +{ + if (g_InputDB & IN_DESELECT) { + controller->state = UI_CONTROLS_STATE_EXIT; + } else if (g_InputDB & (IN_LEFT | IN_RIGHT)) { + controller->active_col ^= 1; + CLAMP( + controller->active_row, 0, + UI_ControlsController_GetInputRoleCount(controller->active_col) + - 1); + } else if (g_InputDB & IN_FORWARD) { + controller->active_row--; + if (controller->active_row < 0) { + if (controller->active_col == 0) { + controller->state = UI_CONTROLS_STATE_NAVIGATE_LAYOUT; + } else { + controller->active_col = 0; + controller->active_row = + UI_ControlsController_GetInputRoleCount(0) - 1; + } + } + } else if (g_InputDB & IN_BACK) { + controller->active_row++; + if (controller->active_row >= UI_ControlsController_GetInputRoleCount( + controller->active_col)) { + if (controller->active_col == 0) { + controller->active_col = 1; + controller->active_row = 0; + } else { + controller->state = UI_CONTROLS_STATE_NAVIGATE_LAYOUT; + } + } + } else if (g_InputDB & IN_SELECT) { + controller->state = UI_CONTROLS_STATE_LISTEN_DEBOUNCE; + } else { + return false; + } + controller->active_role = + M_GetInputRoles(controller->active_col)[controller->active_row]; + return true; +} + +static bool M_ListenDebounce(UI_CONTROLS_CONTROLLER *const controller) +{ + if (Input_IsAnythingPressed()) { + return false; + } + Input_EnterListenMode(); + controller->state = UI_CONTROLS_STATE_LISTEN; + return true; +} + +static bool M_Listen(UI_CONTROLS_CONTROLLER *const controller) +{ + int32_t pressed = 0; + + if (g_JoyKeys != 0) { + for (int32_t i = 0; i < 32; i++) { + if (g_JoyKeys & (1 << i)) { + pressed = i; + break; + } + } + if (!pressed) { + return false; + } + pressed += 0x100; + } else { + for (int32_t i = 0; i < 256; i++) { + if (g_DIKeys[i] & 0x80) { + pressed = i; + break; + } + } + if (!pressed) { + return false; + } + } + + if (!pressed + // clang-format off + || Input_GetKeyName(pressed) == NULL + || pressed == DIK_RETURN + || pressed == DIK_LEFT + || pressed == DIK_RIGHT + || pressed == DIK_UP + || pressed == DIK_DOWN + // clang-format on + ) { + g_Input = 0; + g_InputDB = 0; + return false; + } + + if (pressed != DIK_ESCAPE) { + Input_AssignKey( + controller->active_layout, controller->active_role, pressed); + } + + controller->state = UI_CONTROLS_STATE_NAVIGATE_INPUTS_DEBOUNCE; + return true; +} + +static bool M_NavigateInputsDebounce(UI_CONTROLS_CONTROLLER *const controller) +{ + if (Input_IsAnythingPressed()) { + return false; + } + + Input_ExitListenMode(); + controller->state = UI_CONTROLS_STATE_NAVIGATE_INPUTS; + return true; +} + +bool UI_ControlsController_Control(UI_CONTROLS_CONTROLLER *const controller) +{ + switch (controller->state) { + case UI_CONTROLS_STATE_NAVIGATE_LAYOUT: + return M_NavigateLayout(controller); + case UI_CONTROLS_STATE_NAVIGATE_INPUTS: + return M_NavigateInputs(controller); + case UI_CONTROLS_STATE_LISTEN_DEBOUNCE: + return M_ListenDebounce(controller); + case UI_CONTROLS_STATE_LISTEN: + return M_Listen(controller); + case UI_CONTROLS_STATE_NAVIGATE_INPUTS_DEBOUNCE: + return M_NavigateInputsDebounce(controller); + default: + return false; + } + return false; +} + +INPUT_ROLE UI_ControlsController_GetInputRole( + const int32_t col, const int32_t row) +{ + return M_GetInputRoles(col)[row]; +} + +int32_t UI_ControlsController_GetInputRoleCount(const int32_t col) +{ + int32_t result = 0; + const INPUT_ROLE *const roles = M_GetInputRoles(col); + while (roles[result] != (INPUT_ROLE)-1) { + result++; + } + return result; +} diff --git a/src/tr2/game/ui/controllers/controls.h b/src/tr2/game/ui/controllers/controls.h new file mode 100644 index 000000000..c0dfe293c --- /dev/null +++ b/src/tr2/game/ui/controllers/controls.h @@ -0,0 +1,25 @@ +#pragma once + +#include "game/input.h" + +typedef enum { + UI_CONTROLS_STATE_NAVIGATE_LAYOUT, + UI_CONTROLS_STATE_NAVIGATE_INPUTS, + UI_CONTROLS_STATE_LISTEN_DEBOUNCE, + UI_CONTROLS_STATE_LISTEN, + UI_CONTROLS_STATE_NAVIGATE_INPUTS_DEBOUNCE, + UI_CONTROLS_STATE_EXIT, +} UI_CONTROLS_STATE; + +typedef struct { + int32_t active_layout; + int32_t active_col; + int32_t active_row; + INPUT_ROLE active_role; + UI_CONTROLS_STATE state; +} UI_CONTROLS_CONTROLLER; + +bool UI_ControlsController_Control(UI_CONTROLS_CONTROLLER *controller); + +INPUT_ROLE UI_ControlsController_GetInputRole(int32_t col, int32_t row); +int32_t UI_ControlsController_GetInputRoleCount(int32_t col); diff --git a/src/tr2/game/ui/widgets/controls_column.c b/src/tr2/game/ui/widgets/controls_column.c new file mode 100644 index 000000000..6d87a2daa --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_column.c @@ -0,0 +1,87 @@ +#include "game/ui/widgets/controls_column.h" + +#include "game/ui/widgets/controls_input_selector.h" + +#include +#include + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *container; + int32_t selector_count; + UI_WIDGET **selectors; +} UI_CONTROLS_COLUMN; + +static int32_t M_GetWidth(const UI_CONTROLS_COLUMN *self); +static int32_t M_GetHeight(const UI_CONTROLS_COLUMN *self); +static void M_SetPosition(UI_CONTROLS_COLUMN *self, int32_t x, int32_t y); +static void M_Control(UI_CONTROLS_COLUMN *self); +static void M_Draw(UI_CONTROLS_COLUMN *self); +static void M_Free(UI_CONTROLS_COLUMN *self); + +static int32_t M_GetWidth(const UI_CONTROLS_COLUMN *const self) +{ + return self->container->get_width(self->container); +} + +static int32_t M_GetHeight(const UI_CONTROLS_COLUMN *const self) +{ + return self->container->get_height(self->container); +} + +static void M_SetPosition( + UI_CONTROLS_COLUMN *const self, const int32_t x, const int32_t y) +{ + return self->container->set_position(self->container, x, y); +} + +static void M_Control(UI_CONTROLS_COLUMN *const self) +{ + if (self->container->control != NULL) { + self->container->control(self->container); + } +} + +static void M_Draw(UI_CONTROLS_COLUMN *const self) +{ + if (self->container->draw != NULL) { + self->container->draw(self->container); + } +} + +static void M_Free(UI_CONTROLS_COLUMN *const self) +{ + for (int32_t i = 0; i < self->selector_count; i++) { + self->selectors[i]->free(self->selectors[i]); + } + self->container->free(self->container); + Memory_FreePointer(&self->selectors); + Memory_Free(self); +} + +UI_WIDGET *UI_ControlsColumn_Create( + const int32_t column, UI_CONTROLS_CONTROLLER *const controller) +{ + UI_CONTROLS_COLUMN *const self = Memory_Alloc(sizeof(UI_CONTROLS_COLUMN)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->selector_count = UI_ControlsController_GetInputRoleCount(column); + self->container = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + self->selectors = Memory_Alloc(sizeof(UI_WIDGET *) * self->selector_count); + + for (int32_t i = 0; i < self->selector_count; i++) { + self->selectors[i] = UI_ControlsInputSelector_Create( + UI_ControlsController_GetInputRole(column, i), controller); + UI_Stack_AddChild(self->container, self->selectors[i]); + } + + return (UI_WIDGET *)self; +} diff --git a/src/tr2/game/ui/widgets/controls_column.h b/src/tr2/game/ui/widgets/controls_column.h new file mode 100644 index 000000000..3589dd486 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_column.h @@ -0,0 +1,8 @@ +#pragma once + +#include "game/ui/controllers/controls.h" + +#include + +UI_WIDGET *UI_ControlsColumn_Create( + int32_t column, UI_CONTROLS_CONTROLLER *controller); diff --git a/src/tr2/game/ui/widgets/controls_dialog.c b/src/tr2/game/ui/widgets/controls_dialog.c new file mode 100644 index 000000000..5aa5223af --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_dialog.c @@ -0,0 +1,117 @@ +#include "game/ui/widgets/controls_dialog.h" + +#include "game/ui/widgets/controls_column.h" +#include "game/ui/widgets/controls_layout_selector.h" + +#include +#include +#include +#include + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_CONTROLS_CONTROLLER *controller; + UI_WIDGET *window; + UI_WIDGET *layout_selector; + UI_WIDGET *outer_stack; + UI_WIDGET *column_stack; + UI_WIDGET *left_column; + UI_WIDGET *right_column; +} UI_CONTROLS_DIALOG; + +static void M_DoLayout(UI_CONTROLS_DIALOG *self); +static int32_t M_GetWidth(const UI_CONTROLS_DIALOG *self); +static int32_t M_GetHeight(const UI_CONTROLS_DIALOG *self); +static void M_SetPosition(UI_CONTROLS_DIALOG *self, int32_t x, int32_t y); +static void M_Control(UI_CONTROLS_DIALOG *self); +static void M_Draw(UI_CONTROLS_DIALOG *self); +static void M_Free(UI_CONTROLS_DIALOG *self); + +static void M_DoLayout(UI_CONTROLS_DIALOG *const self) +{ + M_SetPosition( + self, (UI_GetCanvasWidth() - M_GetWidth(self)) / 2, + (UI_GetCanvasHeight() - M_GetHeight(self)) * 2 / 3); +} + +static int32_t M_GetWidth(const UI_CONTROLS_DIALOG *const self) +{ + return self->window->get_width(self->window); +} + +static int32_t M_GetHeight(const UI_CONTROLS_DIALOG *const self) +{ + return self->window->get_height(self->window); +} + +static void M_SetPosition( + UI_CONTROLS_DIALOG *const self, const int32_t x, const int32_t y) +{ + return self->window->set_position(self->window, x, y); +} + +static void M_Control(UI_CONTROLS_DIALOG *const self) +{ + if (UI_ControlsController_Control(self->controller)) { + // Trigger the UI updates only if anything has changed. + if (self->window->control != NULL) { + self->window->control(self->window); + } + // Reposition the header. + UI_Stack_DoLayout(self->outer_stack); + } +} + +static void M_Draw(UI_CONTROLS_DIALOG *const self) +{ + if (self->window->draw != NULL) { + self->window->draw(self->window); + } +} + +static void M_Free(UI_CONTROLS_DIALOG *const self) +{ + self->left_column->free(self->left_column); + self->right_column->free(self->right_column); + self->column_stack->free(self->column_stack); + self->outer_stack->free(self->outer_stack); + self->layout_selector->free(self->layout_selector); + self->window->free(self->window); + Memory_Free(self); +} + +UI_WIDGET *UI_ControlsDialog_Create(UI_CONTROLS_CONTROLLER *const controller) +{ + UI_CONTROLS_DIALOG *const self = Memory_Alloc(sizeof(UI_CONTROLS_DIALOG)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->controller = controller; + self->controller->state = UI_CONTROLS_STATE_NAVIGATE_LAYOUT; + + self->layout_selector = UI_ControlsLayoutSelector_Create(self->controller); + self->left_column = UI_ControlsColumn_Create(0, self->controller); + self->right_column = UI_ControlsColumn_Create(1, self->controller); + + self->column_stack = UI_Stack_Create( + UI_STACK_LAYOUT_HORIZONTAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + UI_Stack_AddChild(self->column_stack, self->left_column); + UI_Stack_AddChild(self->column_stack, self->right_column); + + self->outer_stack = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + UI_Stack_SetHAlign(self->outer_stack, UI_STACK_H_ALIGN_CENTER); + UI_Stack_AddChild(self->outer_stack, self->layout_selector); + UI_Stack_AddChild(self->outer_stack, self->column_stack); + + self->window = UI_Window_Create(self->outer_stack, 5, 5, 15, 5); + + M_DoLayout(self); + return (UI_WIDGET *)self; +} diff --git a/src/tr2/game/ui/widgets/controls_dialog.h b/src/tr2/game/ui/widgets/controls_dialog.h new file mode 100644 index 000000000..8cec14bb8 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_dialog.h @@ -0,0 +1,7 @@ +#pragma once + +#include "game/ui/controllers/controls.h" + +#include + +UI_WIDGET *UI_ControlsDialog_Create(UI_CONTROLS_CONTROLLER *controller); diff --git a/src/tr2/game/ui/widgets/controls_input_selector.c b/src/tr2/game/ui/widgets/controls_input_selector.c new file mode 100644 index 000000000..a08356c37 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_input_selector.c @@ -0,0 +1,138 @@ +#include "game/ui/widgets/controls_input_selector.h" + +#include +#include +#include + +typedef struct { + UI_WIDGET_VTABLE vtable; + INPUT_ROLE input_role; + UI_WIDGET *label; + UI_WIDGET *choice; + UI_WIDGET *container; + UI_CONTROLS_CONTROLLER *controller; +} UI_CONTROLS_INPUT_SELECTOR; + +static void M_UpdateText(UI_CONTROLS_INPUT_SELECTOR *self); +static int32_t M_GetWidth(const UI_CONTROLS_INPUT_SELECTOR *self); +static int32_t M_GetHeight(const UI_CONTROLS_INPUT_SELECTOR *self); +static void M_SetPosition( + UI_CONTROLS_INPUT_SELECTOR *self, int32_t x, int32_t y); +static void M_Control(UI_CONTROLS_INPUT_SELECTOR *self); +static void M_Draw(UI_CONTROLS_INPUT_SELECTOR *self); +static void M_Free(UI_CONTROLS_INPUT_SELECTOR *self); + +static void M_UpdateText(UI_CONTROLS_INPUT_SELECTOR *const self) +{ + const uint16_t key = + Input_GetAssignedKey(self->controller->active_layout, self->input_role); + if (Input_GetKeyName(key) == NULL) { + UI_Label_ChangeText(self->choice, "BAD"); + } else { + UI_Label_ChangeText(self->choice, Input_GetKeyName(key)); + } + UI_Label_ChangeText(self->label, Input_GetRoleName(self->input_role)); +} + +static int32_t M_GetWidth(const UI_CONTROLS_INPUT_SELECTOR *const self) +{ + return self->container->get_width(self->container); +} + +static int32_t M_GetHeight(const UI_CONTROLS_INPUT_SELECTOR *const self) +{ + return self->container->get_height(self->container); +} + +static void M_SetPosition( + UI_CONTROLS_INPUT_SELECTOR *const self, const int32_t x, const int32_t y) +{ + return self->container->set_position(self->container, x, y); +} + +static void M_Control(UI_CONTROLS_INPUT_SELECTOR *const self) +{ + if (self->label->control != NULL) { + self->label->control(self->label); + } + if (self->choice->control != NULL) { + self->choice->control(self->choice); + } + + // Sync outlines + UI_Label_RemoveFrame(self->label); + UI_Label_RemoveFrame(self->choice); + if (self->controller->active_role == self->input_role) { + if (self->controller->state == UI_CONTROLS_STATE_NAVIGATE_INPUTS + || self->controller->state + == UI_CONTROLS_STATE_NAVIGATE_INPUTS_DEBOUNCE) { + UI_Label_AddFrame(self->label); + } else if ( + self->controller->state == UI_CONTROLS_STATE_LISTEN + || self->controller->state == UI_CONTROLS_STATE_LISTEN_DEBOUNCE) { + UI_Label_AddFrame(self->choice); + } + } + + M_UpdateText(self); + + // Flash conflicts + UI_Label_Flash(self->choice, false, 0); + const uint16_t key1 = + Input_GetAssignedKey(self->controller->active_layout, self->input_role); + for (INPUT_ROLE role = 0; role < INPUT_ROLE_NUMBER_OF; role++) { + const uint16_t key2 = + Input_GetAssignedKey(self->controller->active_layout, role); + if (role != self->input_role && key1 == key2) { + UI_Label_Flash(self->choice, true, 20); + } + } +} + +static void M_Draw(UI_CONTROLS_INPUT_SELECTOR *const self) +{ + if (self->label->draw != NULL) { + self->label->draw(self->label); + } + if (self->choice->draw != NULL) { + self->choice->draw(self->choice); + } +} + +static void M_Free(UI_CONTROLS_INPUT_SELECTOR *const self) +{ + self->label->free(self->label); + self->choice->free(self->choice); + self->container->free(self->container); + Memory_Free(self); +} + +UI_WIDGET *UI_ControlsInputSelector_Create( + const INPUT_ROLE input_role, UI_CONTROLS_CONTROLLER *const controller) +{ + UI_CONTROLS_INPUT_SELECTOR *const self = + Memory_Alloc(sizeof(UI_CONTROLS_INPUT_SELECTOR)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->controller = controller; + self->input_role = input_role; + + self->label = UI_Label_Create("", 140, 15); + self->choice = UI_Label_Create("", 70, 15); + self->container = UI_Stack_Create( + UI_STACK_LAYOUT_HORIZONTAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + UI_Stack_AddChild(self->container, self->choice); + UI_Stack_AddChild(self->container, self->label); + + // update the text on init + M_UpdateText(self); + + return (UI_WIDGET *)self; +} diff --git a/src/tr2/game/ui/widgets/controls_input_selector.h b/src/tr2/game/ui/widgets/controls_input_selector.h new file mode 100644 index 000000000..6c514c471 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_input_selector.h @@ -0,0 +1,8 @@ +#pragma once + +#include "game/ui/controllers/controls.h" + +#include + +UI_WIDGET *UI_ControlsInputSelector_Create( + INPUT_ROLE input_role, UI_CONTROLS_CONTROLLER *controller); diff --git a/src/tr2/game/ui/widgets/controls_layout_selector.c b/src/tr2/game/ui/widgets/controls_layout_selector.c new file mode 100644 index 000000000..30e613264 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_layout_selector.c @@ -0,0 +1,83 @@ +#include "game/ui/widgets/controls_layout_selector.h" + +#include +#include + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *label; + UI_CONTROLS_CONTROLLER *controller; +} UI_CONTROLS_LAYOUT_SELECTOR; + +static int32_t M_GetWidth(const UI_CONTROLS_LAYOUT_SELECTOR *self); +static int32_t M_GetHeight(const UI_CONTROLS_LAYOUT_SELECTOR *self); +static void M_SetPosition( + UI_CONTROLS_LAYOUT_SELECTOR *self, int32_t x, int32_t y); +static void M_Control(UI_CONTROLS_LAYOUT_SELECTOR *self); +static void M_Draw(UI_CONTROLS_LAYOUT_SELECTOR *self); +static void M_Free(UI_CONTROLS_LAYOUT_SELECTOR *self); + +static int32_t M_GetWidth(const UI_CONTROLS_LAYOUT_SELECTOR *const self) +{ + return self->label->get_width(self->label); +} + +static int32_t M_GetHeight(const UI_CONTROLS_LAYOUT_SELECTOR *const self) +{ + return self->label->get_height(self->label); +} + +static void M_SetPosition( + UI_CONTROLS_LAYOUT_SELECTOR *const self, const int32_t x, const int32_t y) +{ + self->label->set_position(self->label, x, y); +} + +static void M_Control(UI_CONTROLS_LAYOUT_SELECTOR *const self) +{ + if (self->controller->state == UI_CONTROLS_STATE_NAVIGATE_LAYOUT) { + UI_Label_AddFrame(self->label); + UI_Label_ChangeText( + self->label, Input_GetLayoutName(self->controller->active_layout)); + } else { + UI_Label_RemoveFrame(self->label); + } + if (self->label->control != NULL) { + self->label->control(self->label); + } +} + +static void M_Draw(UI_CONTROLS_LAYOUT_SELECTOR *const self) +{ + if (self->label->draw != NULL) { + self->label->draw(self->label); + } +} + +static void M_Free(UI_CONTROLS_LAYOUT_SELECTOR *const self) +{ + self->label->free(self->label); + Memory_Free(self); +} + +UI_WIDGET *UI_ControlsLayoutSelector_Create( + UI_CONTROLS_CONTROLLER *const controller) +{ + UI_CONTROLS_LAYOUT_SELECTOR *self = + Memory_Alloc(sizeof(UI_CONTROLS_LAYOUT_SELECTOR)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->controller = controller; + self->label = UI_Label_Create( + Input_GetLayoutName(self->controller->active_layout), + UI_LABEL_AUTO_SIZE, 25); + UI_Label_AddFrame(self->label); + return (UI_WIDGET *)self; +} diff --git a/src/tr2/game/ui/widgets/controls_layout_selector.h b/src/tr2/game/ui/widgets/controls_layout_selector.h new file mode 100644 index 000000000..a14cead06 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_layout_selector.h @@ -0,0 +1,7 @@ +#pragma once + +#include "game/ui/controllers/controls.h" + +#include + +UI_WIDGET *UI_ControlsLayoutSelector_Create(UI_CONTROLS_CONTROLLER *controller); diff --git a/src/tr2/game/ui/widgets/label.c b/src/tr2/game/ui/widgets/label.c new file mode 100644 index 000000000..1858a4a6e --- /dev/null +++ b/src/tr2/game/ui/widgets/label.c @@ -0,0 +1,135 @@ +#include "game/text.h" + +#include +#include + +typedef struct { + UI_WIDGET_VTABLE vtable; + TEXTSTRING *text; + int32_t width; + int32_t height; + bool has_frame; +} UI_LABEL; + +static int32_t M_GetWidth(const UI_LABEL *self); +static int32_t M_GetHeight(const UI_LABEL *self); +static void M_SetPosition(UI_LABEL *self, int32_t x, int32_t y); +static void M_Free(UI_LABEL *self); + +static int32_t M_GetWidth(const UI_LABEL *const self) +{ + if (self->width != UI_LABEL_AUTO_SIZE) { + return self->width; + } + return Text_GetWidth(self->text) * PHD_ONE / Text_GetScaleH(PHD_ONE); +} + +static int32_t M_GetHeight(const UI_LABEL *const self) +{ + if (self->height != UI_LABEL_AUTO_SIZE) { + return self->height; + } + return Text_GetHeight(self->text) * PHD_ONE / Text_GetScaleV(PHD_ONE); +} + +static void M_SetPosition( + UI_LABEL *const self, const int32_t x, const int32_t y) +{ + Text_SetPos(self->text, x, y + TEXT_HEIGHT); +} + +static void M_Free(UI_LABEL *const self) +{ + Text_Remove(self->text); + Memory_Free(self); +} + +UI_WIDGET *UI_Label_Create( + const char *const text, const int32_t width, const int32_t height) +{ + UI_LABEL *self = Memory_Alloc(sizeof(UI_LABEL)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = NULL, + .draw = NULL, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->width = width; + self->height = height; + self->has_frame = false; + + self->text = Text_Create(0, 0, 16, text); + Text_SetMultiline(self->text, true); + + return (UI_WIDGET *)self; +} + +void UI_Label_ChangeText(UI_WIDGET *const widget, const char *const text) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + Text_ChangeText(self->text, text); +} + +const char *UI_Label_GetText(UI_WIDGET *const widget) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + return self->text->text; +} + +void UI_Label_SetSize( + UI_WIDGET *const widget, const int32_t width, const int32_t height) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + self->width = width; + self->height = height; +} + +void UI_Label_AddFrame(UI_WIDGET *const widget) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + if (!self->has_frame) { + self->text->pos.z = 0; + Text_AddBackground(self->text, 0, 0, 0, 0, 0, INV_COLOR_BLACK, NULL, 0); + Text_AddOutline(self->text, true, INV_COLOR_BLUE, NULL, 0); + self->has_frame = true; + } +} + +void UI_Label_RemoveFrame(UI_WIDGET *const widget) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + if (self->has_frame) { + Text_RemoveBackground(self->text); + Text_RemoveOutline(self->text); + self->text->pos.z = 16; + self->has_frame = false; + } +} + +void UI_Label_Flash( + UI_WIDGET *const widget, const bool enable, const int32_t rate) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + Text_Flash(self->text, enable, rate); +} + +void UI_Label_SetScale(UI_WIDGET *const widget, const float scale) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + Text_SetScale(self->text, PHD_ONE * scale, PHD_ONE * scale); +} + +void UI_Label_SetZIndex(UI_WIDGET *const widget, const int32_t z_index) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + self->text->pos.z = z_index; +} + +int32_t UI_Label_MeasureTextWidth(UI_WIDGET *const widget) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + return Text_GetWidth(self->text) * PHD_ONE / Text_GetScaleH(PHD_ONE); +} diff --git a/src/tr2/game/ui/widgets/prompt.c b/src/tr2/game/ui/widgets/prompt.c new file mode 100644 index 000000000..d18023638 --- /dev/null +++ b/src/tr2/game/ui/widgets/prompt.c @@ -0,0 +1,11 @@ +#include + +const char *UI_Prompt_GetPromptChar(void) +{ + return "\x11"; +} + +int32_t UI_Prompt_GetCaretFlashRate(void) +{ + return 20; +} diff --git a/src/tr2/game/ui/widgets/window.c b/src/tr2/game/ui/widgets/window.c new file mode 100644 index 000000000..cfe45b5d8 --- /dev/null +++ b/src/tr2/game/ui/widgets/window.c @@ -0,0 +1,92 @@ +#include "game/text.h" + +#include +#include + +typedef struct { + UI_WIDGET_VTABLE vtable; + TEXTSTRING *text; + UI_WIDGET *root; + struct { + int32_t left; + int32_t right; + int32_t top; + int32_t bottom; + } border; +} UI_WINDOW; + +static int32_t M_GetWidth(const UI_WINDOW *self); +static int32_t M_GetHeight(const UI_WINDOW *self); +static void M_SetPosition(UI_WINDOW *self, int32_t x, int32_t y); +static void M_Control(UI_WINDOW *self); +static void M_Draw(UI_WINDOW *self); +static void M_Free(UI_WINDOW *self); + +static int32_t M_GetWidth(const UI_WINDOW *const self) +{ + return self->root->get_width(self->root) + self->border.left + + self->border.right; +} + +static int32_t M_GetHeight(const UI_WINDOW *const self) +{ + return self->root->get_height(self->root) + self->border.top + + self->border.bottom; +} + +static void M_SetPosition( + UI_WINDOW *const self, const int32_t x, const int32_t y) +{ + self->root->set_position( + self->root, x + self->border.left, y + self->border.top); + + Text_SetPos(self->text, x, y + TEXT_HEIGHT); + + const int32_t w = M_GetWidth(self); + const int32_t h = M_GetHeight(self); + Text_AddBackground(self->text, w, h, w / 2, 0, 0, INV_COLOR_BLACK, NULL, 0); + Text_AddOutline(self->text, true, INV_COLOR_BLUE, NULL, 0); +} + +static void M_Control(UI_WINDOW *const self) +{ + if (self->root->control != NULL) { + self->root->control(self->root); + } +} + +static void M_Draw(UI_WINDOW *const self) +{ + if (self->root->draw != NULL) { + self->root->draw(self->root); + } +} + +static void M_Free(UI_WINDOW *const self) +{ + Text_Remove(self->text); + Memory_Free(self); +} + +UI_WIDGET *UI_Window_Create( + UI_WIDGET *const root, const int32_t border_top, const int32_t border_right, + const int32_t border_bottom, const int32_t border_left) +{ + UI_WINDOW *const self = Memory_Alloc(sizeof(UI_WINDOW)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->root = root; + self->text = Text_Create(0, 0, 32, ""); + self->border.top = border_top; + self->border.right = border_right; + self->border.bottom = border_bottom; + self->border.left = border_left; + return (UI_WIDGET *)self; +} diff --git a/src/tr2/global/const.h b/src/tr2/global/const.h new file mode 100644 index 000000000..4ee20c9e8 --- /dev/null +++ b/src/tr2/global/const.h @@ -0,0 +1,229 @@ +#define FRAMES_PER_SECOND 30 +#define TICKS_PER_FRAME 2 + +#define PHD_ONE 0x10000 +#define PHD_HALF 0x100 +#define PHD_DEGREE (PHD_ONE / 360) // = 182 +#define PHD_360 (PHD_ONE) // = 65536 = 0x10000 +#define PHD_180 (PHD_ONE / 2) // = 32768 = 0x8000 +#define PHD_90 (PHD_ONE / 4) // = 16384 = 0x4000 +#define PHD_45 (PHD_ONE / 8) // = 8192 = 0x2000 +#define PHD_135 (PHD_45 * 3) // = 24576 = 0x6000 + +#define CLASS_NAME "Dude:TombRaiderII:DDWndClass" +#define WINDOW_NAME "Tomb Raider II" + +#define W2V_SHIFT 14 +#define WALL_L 1024 +#define WALL_SHIFT 10 +#define STEP_L (WALL_L / 4) // = 256 +#define NO_HEIGHT (-32512) +#define MAX_HEIGHT 32000 +#define NO_BAD_POS (-NO_HEIGHT) // = 32512 +#define NO_BAD_NEG (NO_HEIGHT) // = -32512 +#define BAD_JUMP_CEILING ((STEP_L * 3) / 4) // = 192 +#define STEPUP_HEIGHT ((STEP_L * 3) / 2) // = 384 +#define SLOPE_DIF 60 +#define WIBBLE_SIZE 32 + +#define GAME_FOV 80 +#define MIN_SQUARE SQUARE(WALL_L / 3) +#define NO_BOX (-1) +#define NO_ITEM (-1) +#define NO_ROOM 255 // TODO: merge this with NO_ROOM_NEG +#define NO_ROOM_NEG (-1) +#define NO_CAMERA (-1) + +#define GRAVITY 6 +#define MAX_FRAMES 10 + +#define MAX_AUDIO_SAMPLE_BUFFERS 256 +#define MAX_AUDIO_SAMPLE_TRACKS 32 +#define MAX_TEXTURE_PAGES 32 +#define MAX_TEXTURES 2048 +#define MAX_ROOMS 1024 +#define MAX_FLIPMAPS 10 +#define MAX_VERTICES 0x2000 +#define MAX_BOUND_ROOMS 128 +#define MAX_STATIC_OBJECTS 50 +#define MAX_ITEMS 256 +#define MAX_EFFECTS 100 +#define MAX_PICKUPS 12 +#define MAX_LEVELS 24 +#define MAX_LEVEL_NAME_SIZE 50 // TODO: get rid of this limit +#define MAX_DEMO_FILES MAX_LEVELS +#define MAX_DEMO_SIZE 9000 +#define MAX_REQUESTER_ITEMS 24 +#define MAX_SAVE_SLOTS 16 +#define MAX_ASSAULT_TIMES 10 + +#define TEXTURE_PAGE_WIDTH 256 +#define TEXTURE_PAGE_HEIGHT 256 +#define DEATH_WAIT (5 * 2 * FRAMES_PER_SECOND) // = 300 +#define DEATH_WAIT_INPUT (2 * FRAMES_PER_SECOND) // = 60 +#define DAMAGE_START 140 +#define DAMAGE_LENGTH 14 + +#define HEAD_TURN (2 * PHD_DEGREE) // = 364 +#define MAX_HEAD_TILT (22 * PHD_DEGREE) // = 4004 +#define MIN_HEAD_TILT (-42 * PHD_DEGREE) // = -7644 +#define MAX_HEAD_ROTATION (44 * PHD_DEGREE) // = 8008 +#define MIN_HEAD_ROTATION (-MAX_HEAD_ROTATION) // = -8008 +#define HEAD_TURN_CAM (4 * PHD_DEGREE) // = 728 +#define MAX_HEAD_TILT_CAM (85 * PHD_DEGREE) // = 15470 +#define MIN_HEAD_TILT_CAM (-MAX_HEAD_TILT_CAM) // = -15470 +#define MAX_HEAD_ROTATION_CAM (50 * PHD_DEGREE) // = 9100 +#define MIN_HEAD_ROTATION_CAM (-MAX_HEAD_ROTATION_CAM) // = -9100 +#define FAST_FALL_SPEED 128 +#define LARA_TURN_UNDO (2 * PHD_DEGREE) // = 364 +#define LARA_TURN_RATE ((PHD_DEGREE / 4) + LARA_TURN_UNDO) // = 409 +#define LARA_TURN_RATE_UW (2 * PHD_DEGREE) // = 364 +#define LARA_MED_TURN ((PHD_DEGREE * 4) + LARA_TURN_UNDO) // = 1092 +#define LARA_SLOW_TURN ((PHD_DEGREE * 2) + LARA_TURN_UNDO) // = 728 +#define LARA_SURF_TURN (LARA_SLOW_TURN / 2) // = 364 +#define LARA_FAST_TURN ((PHD_DEGREE * 6) + LARA_TURN_UNDO) // = 1456 +#define LARA_LEAN_UNDO PHD_DEGREE // = 182 +#define LARA_LEAN_UNDO_SURF (LARA_LEAN_UNDO * 2) // = 364 +#define LARA_LEAN_UNDO_UW LARA_LEAN_UNDO_SURF // = 364 +#define LARA_LEAN_RATE 273 +#define LARA_LEAN_RATE_SWIM (LARA_LEAN_RATE * 2) // = 546 +#define LARA_LEAN_MAX ((10 * PHD_DEGREE) + LARA_LEAN_UNDO) // = 2002 +#define LARA_LEAN_MAX_UW (LARA_LEAN_MAX * 2) // = 4004 +#define LARA_JUMP_TURN ((PHD_DEGREE * 1) + LARA_TURN_UNDO) // = 546 +#define LARA_FAST_FALL_SPEED (FAST_FALL_SPEED + 3) // = 131 +#define LARA_MAX_SURF_SPEED 60 +#define LARA_MAX_SWIM_SPEED 200 +#define LARA_UW_FRICTION 6 +#define LARA_UW_WALL_DEFLECT (2 * PHD_DEGREE) // = 364 +#define LARA_CLIMB_WIDTH_LEFT 80 +#define LARA_CLIMB_WIDTH_RIGHT 120 +#define LARA_CLIMB_HEIGHT (WALL_L / 2) // = 512 +#define LARA_WADE_DEPTH 384 +#define LARA_SWIM_DEPTH 730 +#define LARA_RADIUS 100 +#define LARA_RADIUS_UW 300 +#define LARA_HEIGHT 762 +#define LARA_HEIGHT_SURF 700 +#define LARA_HEIGHT_UW 400 +#define LARA_DEFLECT_ANGLE (5 * PHD_DEGREE) // = 910 +#define LARA_HANG_ANGLE (35 * PHD_DEGREE) // = 6370 +#define LARA_VAULT_ANGLE (30 * PHD_DEGREE) // = 5460 +#define LARA_MAX_AIR 1800 +#define LARA_MAX_HITPOINTS 1000 + +#define CAM_HANG_ANGLE 0 +#define CAM_HANG_ELEVATION (-60 * PHD_DEGREE) // = -10920 +#define CAM_REACH_ANGLE (85 * PHD_DEGREE) // = 15470 +#define CAM_SLIDE_ELEVATION (-45 * PHD_DEGREE) // = -8190 +#define CAM_BACK_JUMP_ANGLE (135 * PHD_DEGREE) // = 24570 +#define CAM_PUSH_BLOCK_ANGLE (35 * PHD_DEGREE) // = 6370 +#define CAM_PUSH_BLOCK_ELEVATION (-25 * PHD_DEGREE) // = -4550 +#define CAM_PP_READY_ANGLE (75 * PHD_DEGREE) // = 13650 +#define CAM_PICKUP_ANGLE (-130 * PHD_DEGREE) // = -23660 +#define CAM_PICKUP_ELEVATION (-15 * PHD_DEGREE) // = -2730 +#define CAM_PICKUP_DISTANCE WALL_L // = 1024 +#define CAM_SWITCH_ON_ANGLE (80 * PHD_DEGREE) // = 14560 +#define CAM_SWITCH_ON_ELEVATION (-25 * PHD_DEGREE) // = -4550 +#define CAM_SWITCH_ON_DISTANCE WALL_L // = 1024 +#define CAM_SWITCH_ON_SPEED 6 +#define CAM_USE_KEY_ANGLE (-CAM_SWITCH_ON_ANGLE) // = -14560 +#define CAM_USE_KEY_ELEVATION CAM_SWITCH_ON_ELEVATION // = -4550 +#define CAM_USE_KEY_DISTANCE WALL_L // = 1024 +#define CAM_SPECIAL_ANGLE (170 * PHD_DEGREE) // = 30940 +#define CAM_SPECIAL_ELEVATION (-25 * PHD_DEGREE) // = -4550 +#define CAM_WADE_ELEVATION (-22 * PHD_DEGREE) // = -4004 +#define CAM_DEATH_SLIDE_ANGLE (70 * PHD_DEGREE) // = 12740 +#define CAM_YETI_KILL_ANGLE (160 * PHD_DEGREE) // = 29120 +#define CAM_YETI_KILL_DISTANCE (3 * WALL_L) // = 3072 +#define CAM_SHARK_KILL_ANGLE (160 * PHD_DEGREE) // = 29120 +#define CAM_SHARK_KILL_DISTANCE (3 * WALL_L) // = 3072 +#define CAM_AIRLOCK_ANGLE (80 * PHD_DEGREE) // = 14560 +#define CAM_AIRLOCK_ELEVATION (-25 * PHD_DEGREE) // = -4550 +#define CAM_GONG_BONG_ANGLE (-25 * PHD_DEGREE) // = -4550 +#define CAM_GONG_BONG_ELEVATION (-20 * PHD_DEGREE) // = -3640 +#define CAM_GONG_BONG_DISTANCE (3 * WALL_L) // = 3072 +#define CAM_DINO_KILL_ANGLE (170 * PHD_DEGREE) // = 30940 +#define CAM_DINO_KILL_ELEVATION (-25 * PHD_DEGREE) // = -4550 +#define CAM_CLIMB_LEFT_ANGLE (-30 * PHD_DEGREE) // = -5460 +#define CAM_CLIMB_LEFT_ELEVATION (-15 * PHD_DEGREE) // = -2730 +#define CAM_CLIMB_RIGHT_ANGLE (-CAM_CLIMB_LEFT_ANGLE) // = 5460 +#define CAM_CLIMB_RIGHT_ELEVATION CAM_CLIMB_LEFT_ELEVATION // = -2730 +#define CAM_CLIMB_STANCE_ELEVATION (-20 * PHD_DEGREE) // = -3640 +#define CAM_CLIMBING_ELEVATION (30 * PHD_DEGREE) // = 5460 +#define CAM_CLIMB_END_ELEVATION (-45 * PHD_DEGREE) // = -8190 +#define CAM_CLIMB_DOWN_ELEVATION CAM_CLIMB_END_ELEVATION // = -8190 + +#define SW_DETAIL_LOW (0 * WALL_L << W2V_SHIFT) // = 0 +#define SW_DETAIL_MEDIUM (3 * WALL_L << W2V_SHIFT) // = 50331648 +#define SW_DETAIL_HIGH (6 * WALL_L << W2V_SHIFT) // = 100663296 +#define SW_DETAIL_ULTRA (20 * WALL_L << W2V_SHIFT) // = 335544320 + +#define SPRITE_REL 0x00000000 +#define SPRITE_ABS 0x01000000 +#define SPRITE_SEMITRANS 0x02000000 +#define SPRITE_SCALE 0x04000000 +#define SPRITE_SHADE 0x08000000 +#define SPRITE_TINT 0x10000000 +#define SPRITE_TRANS_HALF 0x00000000 +#define SPRITE_TRANS_ADD 0x20000000 +#define SPRITE_TRANS_SUB 0x40000000 +#define SPRITE_TRANS_QUARTER 0x60000000 +#define SPRITE_COLOUR(r, g, b) ((r) | ((g) << 8) | ((b) << 16)) + +#define VIEW_NEAR (20 * 1) // = 20 +#define VIEW_FAR (20 * WALL_L) // = 20480 + +#define FOG_START (12 * WALL_L) // = 12288 +#define FOG_END (20 * WALL_L) // = 20480 + +#define NUM_SLOTS 5 +#define DONT_TARGET (-16384) +#define PITCH_SHIFT 4 +#define TARGET_DIST (WALL_L * 4) // = 4096 + +#define IDS_DX5_REQUIRED 1 + +#define TRIGGER_TYPE(t) ((t & 0x3C00) >> 10) +#define TRIGGER_VALUE(t) (t & 0x3FF) +#define TRIGGER_IS_END(t) ((t & 0x8000) == 0x8000) + +#define FLOORDATA_TYPE(t) (t & 0xFF) +#define FLOORDATA_IS_END(t) ((t & 0x8000) == 0x8000) + +#define MONK_FRIENDLY_FIRE_THRESHOLD 10 + +#define GUN_AMMO_CLIP 16 +#define GUN_AMMO_QTY (GUN_AMMO_CLIP * 2) // = 32 + +#define MAGNUM_AMMO_CLIP 20 +#define MAGNUM_AMMO_QTY (MAGNUM_AMMO_CLIP * 2) // = 40 + +#define UZI_AMMO_CLIP 40 +#define UZI_AMMO_QTY (UZI_AMMO_CLIP * 2) // = 80 + +#define M16_AMMO_CLIP 40 +#define M16_AMMO_QTY M16_AMMO_CLIP // = 40 + +#define SHOTGUN_SHELL_COUNT 2 +#define SHOTGUN_AMMO_CLIP 6 +#define SHOTGUN_AMMO_QTY (SHOTGUN_AMMO_CLIP * SHOTGUN_SHELL_COUNT) // = 12 +#define SHOTGUN_PELLET_SCATTER (PHD_DEGREE * 20) // = 3640 + +#define HARPOON_BOLT_SPEED 150 +#define HARPOON_RECOIL 4 +#define HARPOON_AMMO_CLIP 3 +#define HARPOON_AMMO_QTY HARPOON_AMMO_CLIP // = 3 + +#define GRENADE_SPEED 150 +#define GRENADE_AMMO_CLIP 1 +#define GRENADE_AMMO_QTY (GRENADE_AMMO_CLIP * 2) // = 2 + +#if defined(PSX_VERSION) && defined(JAPAN) + #define FLARE_AMMO_BOX 8 +#else + #define FLARE_AMMO_BOX 6 +#endif +#define FLARE_AMMO_QTY FLARE_AMMO_BOX + +#define LOW_LIGHT 5120 +#define HIGH_LIGHT 4096 diff --git a/src/tr2/global/enum_map.c b/src/tr2/global/enum_map.c new file mode 100644 index 000000000..1acba19e4 --- /dev/null +++ b/src/tr2/global/enum_map.c @@ -0,0 +1,12 @@ +#include +#include + +void EnumMap_Init(void) +{ +#include "global/enum_map.def" + +#undef OBJ_ID_DEFINE +#define OBJ_ID_DEFINE(object_id, value) \ + EnumMap_Define("GAME_OBJECT_ID", object_id, #object_id); +#include +} diff --git a/src/tr2/global/enum_map.def b/src/tr2/global/enum_map.def new file mode 100644 index 000000000..e69de29bb diff --git a/src/tr2/global/funcs.h b/src/tr2/global/funcs.h new file mode 100644 index 000000000..10065f2ad --- /dev/null +++ b/src/tr2/global/funcs.h @@ -0,0 +1,442 @@ +// This file is autogenerated. To update it, run tools/generate_funcs. + +#pragma once + +#include "global/types.h" + +// clang-format off +#define Output_InsertInventoryBackground ((void __cdecl (*)(const int16_t *obj_ptr))0x00401D50) +#define Diver_Harpoon ((int16_t __cdecl (*)(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE y_rot, int16_t room_num))0x00416C20) +#define Diver_GetWaterSurface ((int32_t __cdecl (*)(int32_t x, int32_t y, int32_t z, int16_t room_num))0x00416CA0) +#define Dog_Control ((void __cdecl (*)(int16_t item_num))0x00417160) +#define Tiger_Control ((void __cdecl (*)(int16_t item_num))0x00417510) +#define ControlTwinkle ((void __cdecl (*)(int16_t fx_num))0x004177B0) +#define CreateBartoliLight ((void __cdecl (*)(int16_t item_num))0x00417930) +#define DragonFire ((int16_t __cdecl (*)(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num))0x00417A10) +#define DragonCollision ((void __cdecl (*)(int16_t item_num, ITEM *lara_item, COLL_INFO *coll))0x00417AC0) +#define DragonBones ((void __cdecl (*)(int16_t item_num))0x00417DB0) +#define DragonControl ((void __cdecl (*)(int16_t back_num))0x00417E90) +#define InitialiseBartoli ((void __cdecl (*)(int16_t item_num))0x004183E0) +#define BartoliControl ((void __cdecl (*)(int16_t item_num))0x00418500) +#define DinoControl ((void __cdecl (*)(int16_t item_num))0x004186A0) +#define Object_DrawSpriteItem ((void __cdecl (*)(ITEM *item))0x004199E0) +#define Object_DrawAnimatingItem ((void __cdecl (*)(ITEM *item))0x00419A70) +#define Gun_DrawFlash ((void __cdecl (*)(LARA_GUN_TYPE weapon_type, int32_t clip))0x0041BD30) +#define Output_CalculateObjectLighting ((void __cdecl (*)(const ITEM *item, const FRAME_INFO *frame))0x0041BEA0) +#define AddDynamicLight ((void __cdecl (*)(int32_t x, int32_t y, int32_t z, int32_t intensity, int32_t falloff))0x0041C0F0) +#define BigEelControl ((void __cdecl (*)(int16_t item_num))0x0041C140) +#define EelControl ((void __cdecl (*)(int16_t item_num))0x0041C2E0) +#define DoBloodSplat ((int16_t __cdecl (*)(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE direction, int16_t room_num))0x0041C5D0) +#define DoLotsOfBlood ((void __cdecl (*)(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE direction, int16_t room_num, int32_t num))0x0041C630) +#define ControlBlood1 ((void __cdecl (*)(int16_t fx_num))0x0041C6E0) +#define ControlExplosion1 ((void __cdecl (*)(int16_t fx_num))0x0041C770) +#define Richochet ((void __cdecl (*)(GAME_VECTOR *pos))0x0041C7F0) +#define ControlRichochet1 ((void __cdecl (*)(int16_t fx_num))0x0041C870) +#define CreateBubble ((void __cdecl (*)(PHD_3DPOS *pos, int16_t room_num))0x0041C8A0) +#define LaraBubbles ((void __cdecl (*)(ITEM *item))0x0041C910) +#define ControlBubble1 ((void __cdecl (*)(int16_t fx_num))0x0041C990) +#define Splash ((void __cdecl (*)(ITEM *item))0x0041CA90) +#define ControlSplash1 ((void __cdecl (*)(int16_t fx_num))0x0041CB60) +#define ControlWaterSprite ((void __cdecl (*)(int16_t fx_num))0x0041CBE0) +#define ControlSnowSprite ((void __cdecl (*)(int16_t fx_num))0x0041CC90) +#define ControlHotLiquid ((void __cdecl (*)(int16_t fx_num))0x0041CD20) +#define WaterFall ((void __cdecl (*)(int16_t fx_num))0x0041CE00) +#define finish_level_effect ((void __cdecl (*)(ITEM *item))0x0041CF40) +#define turn180_effect ((void __cdecl (*)(ITEM *item))0x0041CF50) +#define floor_shake_effect ((void __cdecl (*)(ITEM *item))0x0041CF70) +#define lara_normal_effect ((void __cdecl (*)(ITEM *item))0x0041D010) +#define BoilerFX ((void __cdecl (*)(ITEM *item))0x0041D050) +#define FloodFX ((void __cdecl (*)(ITEM *item))0x0041D070) +#define RubbleFX ((void __cdecl (*)(ITEM *item))0x0041D100) +#define ChandelierFX ((void __cdecl (*)(ITEM *item))0x0041D130) +#define ExplosionFX ((void __cdecl (*)(ITEM *item))0x0041D160) +#define PistonFX ((void __cdecl (*)(ITEM *item))0x0041D190) +#define CurtainFX ((void __cdecl (*)(ITEM *item))0x0041D1B0) +#define StatueFX ((void __cdecl (*)(ITEM *item))0x0041D1D0) +#define SetChangeFX ((void __cdecl (*)(ITEM *item))0x0041D1F0) +#define ControlDingDong ((void __cdecl (*)(int16_t item_num))0x0041D210) +#define ControlLaraAlarm ((void __cdecl (*)(int16_t item_num))0x0041D250) +#define ControlAlarmSound ((void __cdecl (*)(int16_t item_num))0x0041D290) +#define ControlBirdTweeter ((void __cdecl (*)(int16_t item_num))0x0041D300) +#define DoChimeSound ((void __cdecl (*)(ITEM *item))0x0041D360) +#define ControlClockChimes ((void __cdecl (*)(int16_t item_num))0x0041D3C0) +#define SphereOfDoomCollision ((void __cdecl (*)(int16_t item_num, ITEM *lara_item, COLL_INFO *coll))0x0041D430) +#define SphereOfDoom ((void __cdecl (*)(int16_t item_num))0x0041D560) +#define DrawSphereOfDoom ((void __cdecl (*)(ITEM *item))0x0041D650) +#define lara_hands_free ((void __cdecl (*)(ITEM *item))0x0041D780) +#define flip_map_effect ((void __cdecl (*)(ITEM *item))0x0041D790) +#define draw_right_gun ((void __cdecl (*)(ITEM *item))0x0041D7A0) +#define draw_left_gun ((void __cdecl (*)(ITEM *item))0x0041D7F0) +#define swap_meshes_with_meshswap1 ((void __cdecl (*)(ITEM *item))0x0041D840) +#define swap_meshes_with_meshswap2 ((void __cdecl (*)(ITEM *item))0x0041D8B0) +#define swap_meshes_with_meshswap3 ((void __cdecl (*)(ITEM *item))0x0041D920) +#define invisibility_on ((void __cdecl (*)(ITEM *item))0x0041D9C0) +#define invisibility_off ((void __cdecl (*)(ITEM *item))0x0041D9D0) +#define dynamic_light_on ((void __cdecl (*)(ITEM *item))0x0041D9F0) +#define dynamic_light_off ((void __cdecl (*)(ITEM *item))0x0041DA00) +#define reset_hair ((void __cdecl (*)(ITEM *item))0x0041DA10) +#define AssaultStart ((void __cdecl (*)(ITEM *item))0x0041DA20) +#define AssaultStop ((void __cdecl (*)(ITEM *item))0x0041DA50) +#define AssaultReset ((void __cdecl (*)(ITEM *item))0x0041DA70) +#define AssaultFinished ((void __cdecl (*)(ITEM *item))0x0041DA90) +#define Knife ((int16_t __cdecl (*)(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num))0x0041DB50) +#define Cult2Control ((void __cdecl (*)(int16_t item_num))0x0041DBD0) +#define MonkControl ((void __cdecl (*)(int16_t item_num))0x0041E000) +#define Worker3Control ((void __cdecl (*)(int16_t item_num))0x0041E4D0) +#define DrawXianLord ((void __cdecl (*)(ITEM *item))0x0041EAE0) +#define XianDamage ((void __cdecl (*)(ITEM *item, CREATURE *xian, int32_t damage))0x0041EEE0) +#define InitialiseXianLord ((void __cdecl (*)(int16_t item_num))0x0041EF90) +#define XianLordControl ((void __cdecl (*)(int16_t item_num))0x0041EFF0) +#define WarriorSparkleTrail ((void __cdecl (*)(ITEM *item))0x0041F5D0) +#define WarriorControl ((void __cdecl (*)(int16_t item_num))0x0041F670) +#define InitialiseHair ((void __cdecl (*)(void))0x00420EA0) +#define HairControl ((void __cdecl (*)(int32_t in_cutscene))0x00420F20) +#define DrawHair ((void __cdecl (*)(void))0x00421920) +#define HarpoonBolt_Control ((void __cdecl (*)(int16_t item_num))0x0042C0F0) +#define Rocket_Control ((void __cdecl (*)(int16_t item_num))0x0042C530) +#define Flare_DoLight ((int32_t __cdecl (*)(XYZ_32 *pos, int32_t flare_age))0x0042F7A0) +#define Flare_DoInHand ((void __cdecl (*)(int32_t flare_age))0x0042F840) +#define Flare_DrawInAir ((void __cdecl (*)(ITEM *item))0x0042F920) +#define Flare_Create ((void __cdecl (*)(int32_t thrown))0x0042FA20) +#define Flare_SetArm ((void __cdecl (*)(int32_t frame))0x0042FC00) +#define Flare_Draw ((void __cdecl (*)(void))0x0042FC50) +#define Flare_Undraw ((void __cdecl (*)(void))0x0042FDC0) +#define Flare_DrawMeshes ((void __cdecl (*)(void))0x0042FFF0) +#define Flare_UndrawMeshes ((void __cdecl (*)(void))0x00430010) +#define Flare_Ready ((void __cdecl (*)(void))0x00430030) +#define Flare_Control ((void __cdecl (*)(int16_t item_num))0x00430070) +#define ControlMissile ((void __cdecl (*)(int16_t fx_num))0x00432FE0) +#define ShootAtLara ((void __cdecl (*)(FX *fx))0x004332B0) +#define BodyPart_Control ((void __cdecl (*)(int16_t fx_num))0x004336F0) +#define InitialiseMovingBlock ((void __cdecl (*)(int16_t item_num))0x004338F0) +#define MovableBlock ((void __cdecl (*)(int16_t item_num))0x00433920) +#define MovableBlockCollision ((void __cdecl (*)(int16_t item_num, ITEM *lara_item, COLL_INFO *coll))0x00433A70) +#define TestBlockMovable ((int32_t __cdecl (*)(ITEM *item, int32_t block_height))0x00433CD0) +#define TestBlockPush ((int32_t __cdecl (*)(ITEM *item, int32_t block_height, uint16_t quadrant))0x00433D20) +#define TestBlockPull ((int32_t __cdecl (*)(ITEM *item, int32_t block_height, uint16_t quadrant))0x00433E70) +#define DrawMovableBlock ((void __cdecl (*)(ITEM *item))0x00434170) +#define DrawUnclippedItem ((void __cdecl (*)(ITEM *item))0x004341A0) +#define EarthQuake ((void __cdecl (*)(int16_t item_num))0x00434210) +#define ControlCutShotgun ((void __cdecl (*)(int16_t item_num))0x004342F0) +#define InitialiseFinalLevel ((void __cdecl (*)(void))0x00434330) +#define MiniCopterControl ((void __cdecl (*)(int16_t item_num))0x00434610) +#define InitialiseDyingMonk ((void __cdecl (*)(int16_t item_num))0x004346F0) +#define DyingMonk ((void __cdecl (*)(int16_t item_num))0x00434770) +#define ControlGongBonger ((void __cdecl (*)(int16_t item_num))0x00434800) +#define DeathSlideCollision ((void __cdecl (*)(int16_t item_num, ITEM *lara_item, COLL_INFO *coll))0x004348C0) +#define ControlDeathSlide ((void __cdecl (*)(int16_t item_num))0x00434980) +#define BigBowlControl ((void __cdecl (*)(int16_t item_num))0x00434C10) +#define BellControl ((void __cdecl (*)(int16_t item_num))0x00434D00) +#define InitialiseWindow ((void __cdecl (*)(int16_t item_num))0x00434D80) +#define SmashWindow ((void __cdecl (*)(int16_t item_num))0x00434E00) +#define WindowControl ((void __cdecl (*)(int16_t item_num))0x00434ED0) +#define SmashIceControl ((void __cdecl (*)(int16_t item_num))0x00434F70) +#define ShutThatDoor ((void __cdecl (*)(DOORPOS_DATA *d))0x00435050) +#define OpenThatDoor ((void __cdecl (*)(DOORPOS_DATA *d))0x004350A0) +#define InitialiseDoor ((void __cdecl (*)(int16_t item_num))0x004350E0) +#define DoorControl ((void __cdecl (*)(int16_t item_num))0x004354C0) +#define OnDrawBridge ((int32_t __cdecl (*)(ITEM *item, int32_t x, int32_t y))0x00435590) +#define DrawBridgeFloor ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x00435650) +#define DrawBridgeCeiling ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x00435690) +#define DrawBridgeCollision ((void __cdecl (*)(int16_t item_num, ITEM *lara_item, COLL_INFO *coll))0x004356D0) +#define InitialiseLift ((void __cdecl (*)(int16_t item_num))0x00435700) +#define LiftControl ((void __cdecl (*)(int16_t item_num))0x00435740) +#define LiftFloorCeiling ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *floor, int32_t *ceiling))0x00435820) +#define LiftFloor ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x004359A0) +#define LiftCeiling ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x004359E0) +#define BridgeFlatFloor ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x00435A20) +#define BridgeFlatCeiling ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x00435A40) +#define GetOffset ((int32_t __cdecl (*)(ITEM *item, int32_t x, int32_t z))0x00435A60) +#define BridgeTilt1Floor ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x00435AA0) +#define BridgeTilt1Ceiling ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x00435AD0) +#define BridgeTilt2Floor ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x00435B10) +#define BridgeTilt2Ceiling ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x00435B40) +#define CopterControl ((void __cdecl (*)(int16_t item_num))0x00435B80) +#define GeneralControl ((void __cdecl (*)(int16_t item_num))0x00435C90) +#define DetonatorControl ((void __cdecl (*)(int16_t item_num))0x00435D70) +#define ControlGlow ((void __cdecl (*)(int16_t fx_num))0x00435E90) +#define ControlGunShot ((void __cdecl (*)(int16_t fx_num))0x00435ED0) +#define GunShot ((int16_t __cdecl (*)(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num))0x00435F20) +#define GunHit ((int16_t __cdecl (*)(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num))0x00435F90) +#define GunMiss ((int16_t __cdecl (*)(int32_t x, int32_t y, int32_t z, int16_t speed, PHD_ANGLE yrot, int16_t room_num))0x00436050) +#define ShotLara ((int32_t __cdecl (*)(ITEM *item, AI_INFO *info, BITE *gun, int16_t extra_rotation, int32_t damage))0x00436100) +#define InitialiseCult1 ((void __cdecl (*)(int16_t item_num))0x004362D0) +#define Cult1Control ((void __cdecl (*)(int16_t item_num))0x00436320) +#define InitialiseCult3 ((void __cdecl (*)(int16_t item_num))0x00436750) +#define Cult3Control ((void __cdecl (*)(int16_t item_num))0x004367A0) +#define Worker1Control ((void __cdecl (*)(int16_t item_num))0x00436D10) +#define Worker2Control ((void __cdecl (*)(int16_t item_num))0x00437110) +#define BanditControl ((void __cdecl (*)(int16_t item_num))0x00437570) +#define Bandit2Control ((void __cdecl (*)(int16_t item_num))0x004378B0) +#define WinstonControl ((void __cdecl (*)(int16_t item_num))0x00437CF0) +#define PickUpCollision ((void __cdecl (*)(int16_t item_num, ITEM *lara_item, COLL_INFO *coll))0x00437E70) +#define SwitchCollision ((void __cdecl (*)(int16_t item_num, ITEM *lara_item, COLL_INFO *coll))0x004382F0) +#define SwitchCollision2 ((void __cdecl (*)(int16_t item_num, ITEM *lara_item, COLL_INFO *coll))0x00438500) +#define DetonatorCollision ((void __cdecl (*)(int16_t item_num, ITEM *lara_item, COLL_INFO *coll))0x00438600) +#define KeyHoleCollision ((void __cdecl (*)(int16_t item_num, ITEM *lara_item, COLL_INFO *coll))0x00438840) +#define PuzzleHoleCollision ((void __cdecl (*)(int16_t item_num, ITEM *lara_item, COLL_INFO *coll))0x00438A80) +#define SwitchControl ((void __cdecl (*)(int16_t item_num))0x00438D40) +#define SwitchTrigger ((int32_t __cdecl (*)(int16_t item_num, int16_t timer))0x00438D80) +#define KeyTrigger ((int32_t __cdecl (*)(int16_t item_num))0x00438E40) +#define PickupTrigger ((int32_t __cdecl (*)(int16_t item_num))0x00438E80) +#define SecretControl ((void __cdecl (*)(int16_t item_num))0x00438EC0) +#define MouseControl ((void __cdecl (*)(int16_t item_num))0x00438EF0) +#define InitialiseStartInfo ((void __cdecl (*)(void))0x004390E0) +#define ModifyStartInfo ((void __cdecl (*)(int32_t level_num))0x00439150) +#define CreateStartInfo ((void __cdecl (*)(int32_t level_num))0x00439230) +#define CreateSaveGameInfo ((void __cdecl (*)(void))0x00439440) +#define ExtractSaveGameInfo ((void __cdecl (*)(void))0x00439970) +#define ResetSG ((void __cdecl (*)(void))0x0043A1D0) +#define WriteSG ((void __cdecl (*)(void *pointer, int32_t size))0x0043A1F0) +#define ReadSG ((void __cdecl (*)(void *pointer, int32_t size))0x0043A240) +#define InitialiseGameFlags ((void __cdecl (*)(void))0x0043A3E0) +#define InitialiseLevelFlags ((void __cdecl (*)(void))0x0043A450) +#define BaddyObjects ((void __cdecl (*)(void))0x0043A480) +#define TrapObjects ((void __cdecl (*)(void))0x0043B4C0) +#define ObjectObjects ((void __cdecl (*)(void))0x0043BAC0) +#define InitialiseObjects ((void __cdecl (*)(void))0x0043C710) +#define GetCarriedItems ((void __cdecl (*)(void))0x0043C780) +#define JellyControl ((void __cdecl (*)(int16_t item_num))0x0043C850) +#define BaracuddaControl ((void __cdecl (*)(int16_t item_num))0x0043C970) +#define SharkControl ((void __cdecl (*)(int16_t item_num))0x0043CBA0) +#define InitialiseSkidoo ((void __cdecl (*)(int16_t item_num))0x0043CE30) +#define SkidooCheckGeton ((int32_t __cdecl (*)(int16_t item_num, COLL_INFO *coll))0x0043CE70) +#define SkidooCollision ((void __cdecl (*)(int16_t item_num, ITEM *litem, COLL_INFO *coll))0x0043CF60) +#define SkidooBaddieCollision ((void __cdecl (*)(ITEM *skidoo))0x0043D060) +#define TestHeight ((int32_t __cdecl (*)(ITEM *item, int32_t z_off, int32_t x_off, XYZ_32 *pos))0x0043D260) +#define DoShift ((int32_t __cdecl (*)(ITEM *skidoo, XYZ_32 *pos, XYZ_32 *old))0x0043D320) +#define DoDynamics ((int32_t __cdecl (*)(int32_t height, int32_t fall_speed, int32_t *y))0x0043D5A0) +#define GetCollisionAnim ((int32_t __cdecl (*)(ITEM *skidoo, XYZ_32 *moved))0x0043D600) +#define DoSnowEffect ((void __cdecl (*)(ITEM *skidoo))0x0043D690) +#define SkidooDynamics ((int32_t __cdecl (*)(ITEM *skidoo))0x0043D7D0) +#define SkidooUserControl ((int32_t __cdecl (*)(ITEM *skidoo, int32_t height, int32_t *pitch))0x0043DC70) +#define SkidooCheckGetOffOK ((int32_t __cdecl (*)(int32_t direction))0x0043DE30) +#define SkidooAnimation ((void __cdecl (*)(ITEM *skidoo, int32_t collide, int32_t dead))0x0043DF40) +#define SkidooExplode ((void __cdecl (*)(ITEM *skidoo))0x0043E220) +#define SkidooCheckGetOff ((int32_t __cdecl (*)(void))0x0043E2A0) +#define SkidooGuns ((void __cdecl (*)(void))0x0043E4E0) +#define SkidooControl ((int32_t __cdecl (*)(void))0x0043E600) +#define DrawSkidoo ((void __cdecl (*)(ITEM *item))0x0043EA60) +#define InitialiseSkidman ((void __cdecl (*)(int16_t item_num))0x0043ED40) +#define SkidManControl ((void __cdecl (*)(int16_t rider_num))0x0043EDD0) +#define SkidmanPush ((void __cdecl (*)(ITEM *item, ITEM *lara_item, int32_t radius))0x0043F1D0) +#define SkidmanCollision ((void __cdecl (*)(int16_t item_num, ITEM *lara_item, COLL_INFO *coll))0x0043F2F0) +#define Music_GetRealTrack ((int32_t __cdecl (*)(int32_t track))0x0043F380) +#define Collide_TestCollision ((int32_t __cdecl (*)(ITEM *item, const ITEM *lara_item))0x0043F9B0) +#define Collide_GetSpheres ((int32_t __cdecl (*)(const ITEM *item, SPHERE *spheres, bool world_space))0x0043FAE0) +#define Collide_GetJointAbsPosition ((void __cdecl (*)(const ITEM *item, const XYZ_32 *vec, int32_t joint))0x0043FDC0) +#define BaddieBiteEffect ((void __cdecl (*)(ITEM *item, BITE *bite))0x0043FF60) +#define SpiderLeap ((void __cdecl (*)(int16_t item_num, int16_t angle))0x0043FFC0) +#define SpiderControl ((void __cdecl (*)(int16_t item_num))0x00440070) +#define BigSpiderControl ((void __cdecl (*)(int16_t item_num))0x00440290) +#define MineControl ((void __cdecl (*)(int16_t mine_num))0x00440F10) +#define ControlSpikeWall ((void __cdecl (*)(int16_t item_num))0x00441110) +#define ControlCeilingSpikes ((void __cdecl (*)(int16_t item_num))0x00441250) +#define HookControl ((void __cdecl (*)(int16_t item_num))0x00441370) +#define PropellerControl ((void __cdecl (*)(int16_t item_num))0x00441400) +#define SpinningBlade ((void __cdecl (*)(int16_t item_num))0x00441590) +#define IcicleControl ((void __cdecl (*)(int16_t item_num))0x00441710) +#define InitialiseBlade ((void __cdecl (*)(int16_t item_num))0x00441810) +#define BladeControl ((void __cdecl (*)(int16_t item_num))0x00441850) +#define InitialiseKillerStatue ((void __cdecl (*)(int16_t item_num))0x004418F0) +#define KillerStatueControl ((void __cdecl (*)(int16_t item_num))0x00441940) +#define SpringBoardControl ((void __cdecl (*)(int16_t item_num))0x00441A50) +#define InitialiseRollingBall ((void __cdecl (*)(int16_t item_num))0x00441B30) +#define RollingBallControl ((void __cdecl (*)(int16_t item_num))0x00441B70) +#define RollingBallCollision ((void __cdecl (*)(int16_t item_num, ITEM *litem, COLL_INFO *coll))0x00441EC0) +#define SpikeCollision ((void __cdecl (*)(int16_t item_num, ITEM *litem, COLL_INFO *coll))0x00442110) +#define TrapDoorControl ((void __cdecl (*)(int16_t item_num))0x00442270) +#define TrapDoorFloor ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x004422C0) +#define TrapDoorCeiling ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x00442300) +#define OnTrapDoor ((int32_t __cdecl (*)(ITEM *item, int32_t x, int32_t z))0x00442340) +#define Pendulum ((void __cdecl (*)(int16_t item_num))0x004423F0) +#define FallingBlock ((void __cdecl (*)(int16_t item_num))0x00442500) +#define FallingBlockFloor ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x00442610) +#define FallingBlockCeiling ((void __cdecl (*)(ITEM *item, int32_t x, int32_t y, int32_t z, int32_t *height))0x00442650) +#define TeethTrap ((void __cdecl (*)(int16_t item_num))0x004426A0) +#define FallingCeiling ((void __cdecl (*)(int16_t item_num))0x00442760) +#define DartEmitterControl ((void __cdecl (*)(int16_t item_num))0x00442840) +#define DartsControl ((void __cdecl (*)(int16_t item_num))0x00442980) +#define DartEffectControl ((void __cdecl (*)(int16_t fx_num))0x00442AE0) +#define GiantYetiControl ((void __cdecl (*)(int16_t item_num))0x00443050) +#define YetiControl ((void __cdecl (*)(int16_t item_num))0x00443350) +#define WinInReadJoystick ((DWORD __cdecl (*)(int32_t *x, int32_t *y))0x004472A0) +#define WinInputInit ((bool __cdecl (*)(void))0x004473B0) +#define DInputEnumDevices ((bool __cdecl (*)(JOYSTICK_LIST *joystickList))0x00447430) +#define DInputEnumDevicesCallback ((BOOL __stdcall (*)(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef))0x00447460) +#define GetJoystick ((JOYSTICK_NODE *__cdecl (*)(GUID *lpGuid))0x00447570) +#define DInputKeyboardCreate ((void __cdecl (*)(void))0x004475C0) +#define DInputKeyboardRelease ((void __cdecl (*)(void))0x00447690) +#define DInputJoystickCreate ((bool __cdecl (*)(void))0x004476C0) +#define WinInStart ((void __cdecl (*)(void))0x004477B0) +#define WinInFinish ((void __cdecl (*)(void))0x004477E0) +#define WinInRunControlPanel ((void __cdecl (*)(HWND hWnd))0x004477F0) +#define IncreaseScreenSize ((void __cdecl (*)(void))0x00447810) +#define DecreaseScreenSize ((void __cdecl (*)(void))0x00447880) +#define setup_screen_size ((void __cdecl (*)(void))0x004478F0) +#define TempVideoAdjust ((void __cdecl (*)(int32_t hires, double sizer))0x00447990) +#define TempVideoRemove ((void __cdecl (*)(void))0x004479D0) +#define S_FadeInInventory ((void __cdecl (*)(BOOL isFade))0x00447A10) +#define S_FadeOutInventory ((void __cdecl (*)(BOOL isFade))0x00447A50) +#define ReadFileSync ((BOOL __cdecl (*)(HANDLE handle, LPVOID lpBuffer, DWORD nBytesToRead, LPDWORD lpnBytesRead, LPOVERLAPPED lpOverlapped))0x004498D0) +#define AdjustTextureUVs ((void __cdecl (*)(bool reset_uv_add))0x00449E50) +#define ChangeFileNameExtension ((void __cdecl (*)(char *file_name, const char *file_ext))0x0044B110) +#define GetFullPath ((LPCTSTR __cdecl (*)(LPCTSTR file_name))0x0044B150) +#define SelectDrive ((BOOL __cdecl (*)(void))0x0044B180) +#define S_LoadLevelFile ((BOOL __cdecl (*)(LPCTSTR file_name, int32_t level_num, GAMEFLOW_LEVEL_TYPE level_type))0x0044B4B0) +#define S_UnloadLevelFile ((void __cdecl (*)(void))0x0044B4D0) +#define S_AdjustTexelCoordinates ((void __cdecl (*)(void))0x0044B500) +#define S_ReloadLevelGraphics ((BOOL __cdecl (*)(BOOL reload_palettes, BOOL reload_tex_pages))0x0044B520) +#define PlayFMV ((bool __cdecl (*)(const char *file_name))0x0044BDA0) +#define WinPlayFMV ((void __cdecl (*)(const char *file_name, bool is_playback))0x0044BE10) +#define WinStopFMV ((void __cdecl (*)(bool is_playback))0x0044C0F0) +#define IntroFMV ((bool __cdecl (*)(const char *file_name1, const char *file_name2))0x0044C140) +#define S_COLOR ((uint16_t __cdecl (*)(int32_t red, int32_t green, int32_t blue))0x0044C1D0) +#define S_DrawScreenLine ((void __cdecl (*)(int32_t x, int32_t y, int32_t z, int32_t x_len, int32_t y_len, BYTE color_idx, D3DCOLOR *gour, uint16_t flags))0x0044C200) +#define S_DrawScreenBox ((void __cdecl (*)(int32_t sx, int32_t sy, int32_t z, int32_t width, int32_t height, BYTE color_idx, const GOURAUD_OUTLINE *gour, uint16_t flags))0x0044C240) +#define S_DrawScreenFBox ((void __cdecl (*)(int32_t sx, int32_t sy, int32_t z, int32_t width, int32_t height, BYTE color_idx, const GOURAUD_FILL *gour, uint16_t flags))0x0044C360) +#define S_FinishInventory ((void __cdecl (*)(void))0x0044C390) +#define S_FadeToBlack ((void __cdecl (*)(void))0x0044C3A0) +#define S_Wait ((void __cdecl (*)(int32_t timeout, BOOL input_check))0x0044C3F0) +#define S_PlayFMV ((bool __cdecl (*)(const char *file_name))0x0044C450) +#define S_IntroFMV ((bool __cdecl (*)(const char *file_name1, const char *file_name2))0x0044C460) +#define GetValidLevelsList ((void __cdecl (*)(REQUEST_INFO *req))0x0044C9D0) +#define GetSavedGamesList ((void __cdecl (*)(REQUEST_INFO *req))0x0044CA20) +#define DisplayCredits ((void __cdecl (*)(void))0x0044CA70) +#define S_FrontEndCheck ((BOOL __cdecl (*)(void))0x0044CCB0) +#define S_SaveGame ((int32_t __cdecl (*)(const void *save_data, uint32_t save_size, int32_t slot_num))0x0044CE20) +#define S_LoadGame ((int32_t __cdecl (*)(void *save_data, uint32_t save_size, int32_t slot_num))0x0044CF40) +#define S_InitialiseSystem ((BOOL __cdecl (*)(void))0x0044D610) +#define GameBuf_Shutdown ((void __cdecl (*)(void))0x0044D670) +#define init_game_malloc ((void __cdecl (*)(void))0x0044D690) +#define game_malloc ((void *__cdecl (*)(size_t alloc_size, GAME_BUFFER buf_index))0x0044D6C0) +#define game_free ((void __cdecl (*)(size_t free_size))0x0044D740) +#define CalculateWibbleTable ((void __cdecl (*)(void))0x0044D780) +#define GetRenderHeight ((int32_t __cdecl (*)(void))0x00450AE0) +#define GetRenderWidth ((int32_t __cdecl (*)(void))0x00450AF0) +#define S_InitialisePolyList ((void __cdecl (*)(BOOL clear_back_buffer))0x00450B00) +#define S_DumpScreen ((DWORD __cdecl (*)(void))0x00450BF0) +#define S_ClearScreen ((void __cdecl (*)(void))0x00450C30) +#define S_InitialiseScreen ((void __cdecl (*)(GAMEFLOW_LEVEL_TYPE level_type))0x00450C40) +#define S_OutputPolyList ((void __cdecl (*)(void))0x00450C80) +#define S_GetObjectBounds ((int32_t __cdecl (*)(const BOUNDS_16 *bounds))0x00450CC0) +#define S_InsertBackPolygon ((void __cdecl (*)(int32_t x0, int32_t y0, int32_t x1, int32_t y1))0x00450F30) +#define S_PrintShadow ((void __cdecl (*)(int16_t radius, const BOUNDS_16 *bounds, const ITEM *item))0x00450F80) +#define S_CalculateLight ((void __cdecl (*)(int32_t x, int32_t y, int32_t z, int16_t room_num))0x00451180) +#define S_CalculateStaticLight ((void __cdecl (*)(int16_t adder))0x00451480) +#define S_CalculateStaticMeshLight ((void __cdecl (*)(int32_t x, int32_t y, int32_t z, int32_t shade_1, int32_t shade_2, ROOM *room))0x004514C0) +#define S_LightRoom ((void __cdecl (*)(ROOM *room))0x004515F0) +#define S_DrawHealthBar ((void __cdecl (*)(int32_t percent))0x00451800) +#define S_DrawAirBar ((void __cdecl (*)(int32_t percent))0x004519D0) +#define AnimateTextures ((void __cdecl (*)(int32_t ticks))0x00451BD0) +#define S_SetupBelowWater ((void __cdecl (*)(BOOL underwater))0x00451C90) +#define S_SetupAboveWater ((void __cdecl (*)(BOOL underwater))0x00451CF0) +#define S_AnimateTextures ((void __cdecl (*)(int32_t ticks))0x00451D20) +#define S_DisplayPicture ((void __cdecl (*)(const char *file_name, BOOL is_title))0x00451DE0) +#define S_SyncPictureBufferPalette ((void __cdecl (*)(void))0x00451EF0) +#define S_DontDisplayPicture ((void __cdecl (*)(void))0x00451F70) +#define ScreenDump ((void __cdecl (*)(void))0x00451F80) +#define ScreenPartialDump ((void __cdecl (*)(void))0x00451F90) +#define FadeToPal ((void __cdecl (*)(int32_t fade_value, RGB_888 *palette))0x00451FA0) +#define ScreenClear ((void __cdecl (*)(bool is_phd_win_size))0x00452170) +#define S_CopyScreenToBuffer ((void __cdecl (*)(void))0x004521A0) +#define S_CopyBufferToScreen ((void __cdecl (*)(void))0x00452250) +#define DecompPCX ((BOOL __cdecl (*)(const uint8_t *pcx, size_t pcx_size, LPBYTE pic, RGB_888 *pal))0x004522A0) +#define OpenGameRegistryKey ((bool __cdecl (*)(LPCTSTR key))0x004523C0) +#define CloseGameRegistryKey ((LONG __cdecl (*)(void))0x00452410) +#define SE_WriteAppSettings ((bool __cdecl (*)(APP_SETTINGS *settings))0x00452420) +#define SE_ReadAppSettings ((int32_t __cdecl (*)(APP_SETTINGS *settings))0x00452690) +#define SE_GraphicsTestStart ((bool __cdecl (*)(void))0x004529E0) +#define SE_GraphicsTestFinish ((void __cdecl (*)(void))0x00452AB0) +#define SE_GraphicsTestExecute ((int32_t __cdecl (*)(void))0x00452AD0) +#define SE_GraphicsTest ((int32_t __cdecl (*)(void))0x00452AE0) +#define SE_SoundTestStart ((bool __cdecl (*)(void))0x00452B40) +#define SE_SoundTestFinish ((void __cdecl (*)(void))0x00452C00) +#define SE_SoundTestExecute ((int32_t __cdecl (*)(void))0x00452C10) +#define SE_SoundTest ((int32_t __cdecl (*)(void))0x00452C50) +#define SE_PropSheetCallback ((int32_t __stdcall (*)(HWND hwndDlg, UINT uMsg, LPARAM lParam))0x00452CB0) +#define SE_NewPropSheetWndProc ((LRESULT __stdcall (*)(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam))0x00452CF0) +#define SE_ShowSetupDialog ((bool __cdecl (*)(HWND hParent, bool isDefault))0x00452D50) +#define SE_GraphicsDlgProc ((INT_PTR __stdcall (*)(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam))0x00453030) +#define SE_GraphicsDlgFullScreenModesUpdate ((void __cdecl (*)(HWND hwndDlg))0x004533F0) +#define SE_GraphicsAdapterSet ((void __cdecl (*)(HWND hwndDlg, DISPLAY_ADAPTER_NODE *adapter))0x004535E0) +#define SE_GraphicsDlgUpdate ((void __cdecl (*)(HWND hwndDlg))0x00453600) +#define SE_GraphicsDlgInit ((void __cdecl (*)(HWND hwndDlg))0x00453D40) +#define SE_SoundDlgProc ((INT_PTR __stdcall (*)(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam))0x00453EC0) +#define SE_SoundAdapterSet ((void __cdecl (*)(HWND hwndDlg, SOUND_ADAPTER_NODE *adapter))0x00454050) +#define SE_SoundDlgUpdate ((void __cdecl (*)(HWND hwndDlg))0x00454060) +#define SE_SoundDlgInit ((void __cdecl (*)(HWND hwndDlg))0x00454180) +#define SE_ControlsDlgProc ((INT_PTR __stdcall (*)(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam))0x00454240) +#define SE_ControlsJoystickSet ((void __cdecl (*)(HWND hwndDlg, JOYSTICK_NODE *joystick))0x00454350) +#define SE_ControlsDlgUpdate ((void __cdecl (*)(HWND hwndDlg))0x00454360) +#define SE_ControlsDlgInit ((void __cdecl (*)(HWND hwndDlg))0x004543D0) +#define SE_OptionsDlgProc ((INT_PTR __stdcall (*)(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam))0x00454490) +#define SE_OptionsDlgUpdate ((void __cdecl (*)(HWND hwndDlg))0x00454520) +#define SE_OptionsStrCat ((void __cdecl (*)(LPTSTR *dstString, bool isEnabled, bool *isNext, LPCTSTR srcString))0x00454760) +#define SE_AdvancedDlgProc ((INT_PTR __stdcall (*)(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam))0x004547B0) +#define SE_AdvancedDlgUpdate ((void __cdecl (*)(HWND hwndDlg))0x004548B0) +#define SE_AdvancedDlgInit ((void __cdecl (*)(HWND hwndDlg))0x00454950) +#define SE_FindSetupDialog ((HWND __cdecl (*)(void))0x00454960) +#define CheckCheatMode ((void __cdecl (*)(void))0x00454D60) +#define CopyBitmapPalette ((void __cdecl (*)(RGB_888 *src_pal, BYTE *src_bitmap, int32_t bitmap_size, RGB_888 *dest_pal))0x004557A0) +#define FindNearestPaletteEntry ((BYTE __cdecl (*)(RGB_888 *palette, int32_t red, int32_t green, int32_t blue, bool ignore_sys_palette))0x004558E0) +#define SyncSurfacePalettes ((void __cdecl (*)(void *src_data, int32_t width, int32_t height, int32_t src_pitch, RGB_888 *src_palette, void *dst_data, int32_t dst_pitch, RGB_888 *dst_palette, bool preserve_sys_palette))0x004559B0) +#define CreateTexturePalette ((int32_t __cdecl (*)(const RGB_888 *pal))0x00455A60) +#define GetFreePaletteIndex ((int32_t __cdecl (*)(void))0x00455AF0) +#define FreePalette ((void __cdecl (*)(int32_t palette_idx))0x00455B10) +#define SafeFreePalette ((void __cdecl (*)(int32_t palette_idx))0x00455B40) +#define CreateTexturePage ((int32_t __cdecl (*)(int32_t width, int32_t height, bool alpha))0x00455B90) +#define GetFreeTexturePageIndex ((int32_t __cdecl (*)(void))0x00455C00) +#define CreateTexturePageSurface ((bool __cdecl (*)(TEXPAGE_DESC *desc))0x00455C20) +#define TexturePageInit ((bool __cdecl (*)(TEXPAGE_DESC *page))0x00455CC0) +#define Create3DTexture ((LPDIRECT3DTEXTURE2 __cdecl (*)(LPDDS surface))0x00455E40) +#define SafeFreeTexturePage ((void __cdecl (*)(int32_t page_idx))0x00455E70) +#define FreeTexturePage ((void __cdecl (*)(int32_t page_idx))0x00455E90) +#define TexturePageReleaseVidMemSurface ((void __cdecl (*)(TEXPAGE_DESC *page))0x00455ED0) +#define FreeTexturePages ((void __cdecl (*)(void))0x00455F10) +#define LoadTexturePage ((bool __cdecl (*)(int32_t page_idx, bool reset))0x00455F40) +#define ReloadTextures ((bool __cdecl (*)(bool reset))0x00455FF0) +#define GetTexturePageHandle ((HWR_TEXTURE_HANDLE __cdecl (*)(int32_t page_idx))0x00456030) +#define AddTexturePage8 ((int32_t __cdecl (*)(int32_t width, int32_t height, const uint8_t *page_buf, int32_t pal_idx))0x00456070) +#define AddTexturePage16 ((int32_t __cdecl (*)(int32_t width, int32_t height, const uint8_t *page_buf))0x00456170) +#define EnumTextureFormatsCallback ((HRESULT __stdcall (*)(LPDDSDESC lpDdsd, LPVOID lpContext))0x00456310) +#define EnumerateTextureFormats ((HRESULT __cdecl (*)(void))0x00456430) +#define CleanupTextures ((void __cdecl (*)(void))0x00456460) +#define InitTextures ((bool __cdecl (*)(void))0x00456470) +#define UpdateTicks ((void __cdecl (*)(void))0x00456490) +#define TIME_Init ((bool __cdecl (*)(void))0x004564D0) +#define Sync ((DWORD __cdecl (*)(void))0x00456530) +#define UT_LoadResource ((LPVOID __cdecl (*)(LPCTSTR lpName, LPCTSTR lpType))0x00456590) +#define UT_InitAccurateTimer ((void __cdecl (*)(void))0x004565D0) +#define UT_Microseconds ((double __cdecl (*)(void))0x00456630) +#define UT_CenterWindow ((BOOL __cdecl (*)(HWND hWnd))0x00456680) +#define UT_FindArg ((LPTSTR __cdecl (*)(LPCTSTR str))0x004566F0) +#define UT_MessageBox ((int32_t __cdecl (*)(LPCTSTR lpText, HWND hWnd))0x00456720) +#define UT_ErrorBox ((int32_t __cdecl (*)(UINT uID, HWND hWnd))0x00456740) +#define GuidBinaryToString ((LPCTSTR __cdecl (*)(GUID *guid))0x00456790) +#define GuidStringToBinary ((bool __cdecl (*)(LPCTSTR lpString, GUID *guid))0x004567F0) +#define OpenRegistryKey ((BOOL __cdecl (*)(LPCTSTR lpSubKey))0x004568A0) +#define IsNewRegistryKeyCreated ((bool __cdecl (*)(void))0x004568D0) +#define CloseRegistryKey ((LONG __cdecl (*)(void))0x004568E0) +#define SetRegistryDwordValue ((LONG __cdecl (*)(LPCTSTR lpValueName, DWORD value))0x004568F0) +#define SetRegistryBoolValue ((LONG __cdecl (*)(LPCTSTR lpValueName, bool value))0x00456910) +#define SetRegistryFloatValue ((LONG __cdecl (*)(LPCTSTR lpValueName, double value))0x00456940) +#define SetRegistryBinaryValue ((LONG __cdecl (*)(LPCTSTR lpValueName, LPBYTE value, DWORD valueSize))0x00456980) +#define SetRegistryStringValue ((LONG __cdecl (*)(LPCTSTR lpValueName, LPCTSTR value, int32_t length))0x004569C0) +#define DeleteRegistryValue ((LONG __cdecl (*)(LPCTSTR lpValueName))0x00456A10) +#define GetRegistryDwordValue ((bool __cdecl (*)(LPCTSTR lpValueName, DWORD *pValue, DWORD defaultValue))0x00456A30) +#define GetRegistryBoolValue ((bool __cdecl (*)(LPCTSTR lpValueName, bool *pValue, bool defaultValue))0x00456A90) +#define GetRegistryFloatValue ((bool __cdecl (*)(LPCTSTR lpValueName, double *value, double defaultValue))0x00456B10) +#define GetRegistryBinaryValue ((bool __cdecl (*)(LPCTSTR lpValueName, LPBYTE value, DWORD valueSize, LPBYTE defaultValue))0x00456B70) +#define GetRegistryStringValue ((bool __cdecl (*)(LPCTSTR lpValueName, LPTSTR value, DWORD maxSize, LPCTSTR defaultValue))0x00456BF0) +#define GetRegistryGuidValue ((bool __cdecl (*)(LPCTSTR lpValueName, GUID *value, GUID *defaultValue))0x00456C90) +#define SE_ReleaseBitmapResource ((void __thiscall (*)(BITMAP_RESOURCE *bmpRsrc))0x00456D30) +#define SE_LoadBitmapResource ((void __thiscall (*)(BITMAP_RESOURCE *bmpRsrc, LPCTSTR lpName))0x00456D70) +#define SE_DrawBitmap ((void __thiscall (*)(BITMAP_RESOURCE *bmpRsrc, HDC hdc, int32_t x, int32_t y))0x00456E40) +#define SE_UpdateBitmapPalette ((void __thiscall (*)(BITMAP_RESOURCE *bmpRsrc, HWND hWnd, HWND hSender))0x00456EB0) +#define SE_ChangeBitmapPalette ((void __thiscall (*)(BITMAP_RESOURCE *bmpRsrc, HWND hWnd))0x00456ED0) +#define SE_RegisterSetupWindowClass ((bool __cdecl (*)(void))0x00456F30) +#define SE_SetupWindowProc ((LRESULT __stdcall (*)(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam))0x00456FA0) +#define SE_PassMessageToImage ((void __cdecl (*)(HWND hWnd, UINT uMsg, WPARAM wParam))0x004571E0) +#define UT_MemBlt ((void __cdecl (*)(BYTE *dstBuf, DWORD dstX, DWORD dstY, DWORD width, DWORD height, DWORD dstPitch, BYTE *srcBuf, DWORD srcX, DWORD srcY, DWORD srcPitch))0x00457210) +// clang-format on diff --git a/src/tr2/global/types.h b/src/tr2/global/types.h new file mode 100644 index 000000000..aa3b0457d --- /dev/null +++ b/src/tr2/global/types.h @@ -0,0 +1,1779 @@ +// This file is autogenerated. To update it, run tools/generate_funcs. + +#pragma once + +#include "const.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#pragma pack(push, 1) + +// clang-format off +typedef IDirect3DDevice2 *LPDIRECT3DDEVICE2; +typedef IDirect3DTexture2 *LPDIRECT3DTEXTURE2; +typedef IDirect3DViewport2 *LPDIRECT3DVIEWPORT2; +typedef IDirect3DMaterial2 *LPDIRECT3DMATERIAL2; +typedef DDSURFACEDESC DDSDESC, *LPDDSDESC; +typedef LPDIRECTDRAWSURFACE3 LPDDS; +typedef LPDIRECTDRAW3 LPDD; +typedef D3DTEXTUREHANDLE HWR_TEXTURE_HANDLE; + +typedef int16_t PHD_ANGLE; + +typedef struct __unaligned { + int32_t _00; + int32_t _01; + int32_t _02; + int32_t _03; + int32_t _10; + int32_t _11; + int32_t _12; + int32_t _13; + int32_t _20; + int32_t _21; + int32_t _22; + int32_t _23; +} MATRIX; + +typedef enum { + VGA_NO_VGA = 0, + VGA_256_COLOR = 1, + VGA_MODEX = 2, + VGA_STANDARD = 3, +} VGA_MODE; + +typedef struct __unaligned { + LPBITMAPINFO bmp_info; + void *bmp_data; + HPALETTE hPalette; + DWORD flags; +} BITMAP_RESOURCE; + +typedef struct __unaligned { + int32_t width; + int32_t height; + int32_t bpp; + VGA_MODE vga; +} DISPLAY_MODE; + +typedef struct __unaligned DISPLAY_MODE_NODE { + struct DISPLAY_MODE_NODE *next; + struct DISPLAY_MODE_NODE *previous; + DISPLAY_MODE body; +} DISPLAY_MODE_NODE; + +typedef struct __unaligned { + DISPLAY_MODE_NODE *head; + DISPLAY_MODE_NODE *tail; + DWORD count; +} DISPLAY_MODE_LIST; + +typedef struct __unaligned { + char *content; + bool is_valid; +} STRING_FLAGGED; + +typedef struct __unaligned { + LPGUID adapter_guid_ptr; + GUID adapter_guid; + STRING_FLAGGED driver_desc; + STRING_FLAGGED driver_name; + DDCAPS_DX5 driver_caps; + DDCAPS_DX5 hel_caps; + GUID device_guid; + D3DDEVICEDESC_V2 hw_device_desc; + DISPLAY_MODE_LIST hw_disp_mode_list; + DISPLAY_MODE_LIST sw_disp_mode_list; + DISPLAY_MODE vga_mode1; + DISPLAY_MODE vga_mode2; + uint32_t screen_width; + bool hw_render_supported; + bool sw_windowed_supported; + bool hw_windowed_supported; + bool is_vga_mode1_presented; + bool is_vga_mode2_presented; + bool perspective_correct_supported; + bool dither_supported; + bool zbuffer_supported; + bool linear_filter_supported; + bool shade_restricted; +} DISPLAY_ADAPTER; + +typedef struct __unaligned DISPLAY_ADAPTER_NODE { + struct DISPLAY_ADAPTER_NODE *next; + struct DISPLAY_ADAPTER_NODE *previous; + DISPLAY_ADAPTER body; +} DISPLAY_ADAPTER_NODE; + +typedef struct __unaligned { + DISPLAY_ADAPTER_NODE *head; + DISPLAY_ADAPTER_NODE *tail; + DWORD count; +} DISPLAY_ADAPTER_LIST; + +typedef struct __unaligned { + GUID *adapter_guid_ptr; + GUID adapter_guid; + STRING_FLAGGED description; + STRING_FLAGGED module; +} SOUND_ADAPTER; + +typedef struct __unaligned SOUND_ADAPTER_NODE { + struct SOUND_ADAPTER_NODE *next; + struct SOUND_ADAPTER_NODE *previous; + SOUND_ADAPTER body; +} SOUND_ADAPTER_NODE; + +typedef struct __unaligned { + SOUND_ADAPTER_NODE *head; + SOUND_ADAPTER_NODE *tail; + DWORD count; +} SOUND_ADAPTER_LIST; + +typedef struct __unaligned { + GUID *lpJoystickGuid; + GUID joystickGuid; + STRING_FLAGGED productName; + STRING_FLAGGED instanceName; +} JOYSTICK; + +typedef struct __unaligned JOYSTICK_NODE { + struct JOYSTICK_NODE *next; + struct JOYSTICK_NODE *previous; + JOYSTICK body; +} JOYSTICK_NODE; + +typedef struct __unaligned JOYSTICK_LIST { + struct JOYSTICK_LIST *head; + struct JOYSTICK_LIST *tail; + DWORD count; +} JOYSTICK_LIST; + +typedef enum { + RM_UNKNOWN = 0, + RM_SOFTWARE = 1, + RM_HARDWARE = 2, +} RENDER_MODE; + +typedef enum { + AM_4_3 = 0, + AM_16_9 = 1, + AM_ANY = 2, +} ASPECT_MODE; + +typedef enum { + TAM_DISABLED = 0, + TAM_BILINEAR_ONLY = 1, + TAM_ALWAYS = 2, +} TEX_ADJUST_MODE; + +typedef struct __unaligned { + DISPLAY_ADAPTER_NODE *preferred_display_adapter; + SOUND_ADAPTER_NODE *preferred_sound_adapter; + JOYSTICK_NODE *preferred_joystick; + const DISPLAY_MODE_NODE *video_mode; + RENDER_MODE render_mode; + int32_t window_width; + int32_t window_height; + ASPECT_MODE aspect_mode; + bool perspective_correct; + bool dither; + bool zbuffer; + bool bilinear_filtering; + bool triple_buffering; + bool fullscreen; + bool sound_enabled; + bool lara_mic; + bool joystick_enabled; + bool disable_16bit_textures; + bool dont_sort_primitives; + bool flip_broken; + bool disable_fmv; + TEX_ADJUST_MODE texel_adjust_mode; + int32_t nearest_adjustment; + int32_t linear_adjustment; +} APP_SETTINGS; + +typedef struct __unaligned { + LPDDS sysMemSurface; + LPDDS vidMemSurface; + LPDIRECTDRAWPALETTE palette; + LPDIRECT3DTEXTURE2 texture3d; + HWR_TEXTURE_HANDLE texHandle; + int32_t width; + int32_t height; + int32_t status; +} TEXPAGE_DESC; + +typedef struct __unaligned { + uint8_t red; + uint8_t green; + uint8_t blue; +} RGB_888; + +typedef struct __unaligned { + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t alpha; +} RGBA_8888; + +typedef struct { + struct { + uint32_t r; + uint32_t g; + uint32_t b; + uint32_t a; + } mask, depth, offset; +} COLOR_BIT_MASKS; + +typedef struct __unaligned { + D3DCOLOR clr[4][4]; +} GOURAUD_FILL; + +typedef struct __unaligned { + D3DCOLOR clr[9]; +} GOURAUD_OUTLINE; + +typedef struct __unaligned { + uint8_t index[256]; +} DEPTHQ_ENTRY; + +typedef struct __unaligned { + uint8_t index[32]; +} GOURAUD_ENTRY; + +typedef struct __unaligned { + XYZ_32 pos; + XYZ_16 rot; +} PHD_3DPOS; + +typedef struct __unaligned { + int32_t x; + int32_t y; + int32_t z; + int32_t r; +} SPHERE; + +typedef struct __unaligned { + union { + uint32_t all; + struct { + uint32_t active: 1; + uint32_t flash: 1; + uint32_t rotate_h: 1; + uint32_t rotate_v: 1; + uint32_t centre_h: 1; + uint32_t centre_v: 1; + uint32_t hide: 1; + uint32_t right: 1; + uint32_t bottom: 1; + uint32_t background: 1; + uint32_t outline: 1; + uint32_t multiline: 1; + }; + } flags; + uint16_t text_flags; + uint16_t bgnd_flags; + uint16_t outl_flags; + XYZ_16 pos; + int16_t letter_spacing; + int16_t word_spacing; + struct { + int16_t rate; + int16_t count; + } flash; + int16_t bgnd_color; + const uint16_t *bgnd_gour; + int16_t outl_color; + const uint16_t *outl_gour; + struct { + int16_t x; + int16_t y; + } bgnd_size; + XYZ_16 bgnd_off; + struct { + int32_t h; + int32_t v; + } scale; + char *text; +} TEXTSTRING; + +typedef struct __unaligned { + float xv; + float yv; + float zv; + float rhw; + float xs; + float ys; + int16_t clip; + int16_t g; + int16_t u; + int16_t v; +} PHD_VBUF; + +typedef struct __unaligned { + uint16_t u; + uint16_t v; +} PHD_UV; + +typedef struct __unaligned { + uint16_t draw_type; + uint16_t tex_page; + PHD_UV uv[4]; +} PHD_TEXTURE; + +typedef struct __unaligned { + uint16_t tex_page; + uint16_t offset; + uint16_t width; + uint16_t height; + int16_t x0; + int16_t y0; + int16_t x1; + int16_t y1; +} PHD_SPRITE; + +typedef enum { + SHAPE_SPRITE = 1, + SHAPE_LINE = 2, + SHAPE_BOX = 3, + SHAPE_FBOX = 4, +} SHAPE; + +typedef enum { + SPRF_RGB = 0x00FFFFFF, + SPRF_ABS = 0x01000000, + SPRF_SEMITRANS = 0x02000000, + SPRF_SCALE = 0x04000000, + SPRF_SHADE = 0x08000000, + SPRF_TINT = 0x10000000, + SPRF_BLEND_ADD = 0x20000000, + SPRF_BLEND_SUB = 0x40000000, + SPRF_BLEND_QRT = SPRF_BLEND_ADD | SPRF_BLEND_SUB, + SPRF_BLEND = SPRF_BLEND_QRT, + SPRF_ITEM = 0x80000000, +} SPRITE_FLAG; + +typedef struct __unaligned { + float xv; + float yv; + float zv; + float rhw; + float xs; + float ys; + float u; + float v; + float g; +} POINT_INFO; + +typedef struct __unaligned { + float x; + float y; + float rhw; + float u; + float v; + float g; +} VERTEX_INFO; + +typedef struct __unaligned { + uint16_t no_selector : 1; + uint16_t ready : 1; // not present in the OG + uint16_t pad : 14; + uint16_t items_count; + uint16_t selected; + uint16_t visible_count; + uint16_t line_offset; + uint16_t line_old_offset; + uint16_t pix_width; + uint16_t line_height; + int16_t x_pos; + int16_t y_pos; + int16_t z_pos; + uint16_t item_string_len; + char *pitem_strings1; + char *pitem_strings2; + uint32_t *pitem_flags1; + uint32_t *pitem_flags2; + uint32_t heading_flags1; + uint32_t heading_flags2; + uint32_t background_flags; + uint32_t moreup_flags; + uint32_t moredown_flags; + uint32_t item_flags1[24]; // MAX_REQUESTER_ITEMS + uint32_t item_flags2[24]; // MAX_REQUESTER_ITEMS + TEXTSTRING *heading_text1; + TEXTSTRING *heading_text2; + TEXTSTRING *background_text; + TEXTSTRING *moreup_text; + TEXTSTRING *moredown_text; + TEXTSTRING *item_texts1[24]; // MAX_REQUESTER_ITEMS + TEXTSTRING *item_texts2[24]; // MAX_REQUESTER_ITEMS + char heading_string1[32]; + char heading_string2[32]; + uint32_t render_width; + uint32_t render_height; +} REQUEST_INFO; + +typedef enum { + POLY_GTMAP = 0, + POLY_WGTMAP = 1, + POLY_GTMAP_PERSP = 2, + POLY_WGTMAP_PERSP = 3, + POLY_LINE = 4, + POLY_FLAT = 5, + POLY_GOURAUD = 6, + POLY_TRANS = 7, + POLY_SPRITE = 8, + POLY_HWR_GTMAP = 9, + POLY_HWR_WGTMAP = 10, + POLY_HWR_GOURAUD = 11, + POLY_HWR_LINE = 12, + POLY_HWR_TRANS = 13, +} POLY_TYPE; + +typedef struct __unaligned { + uint32_t best_time[10]; + uint32_t best_finish[10]; + uint32_t finish_count; +} ASSAULT_STATS; + +typedef struct __unaligned { + int32_t _0; + int32_t _1; +} SORT_ITEM; + +typedef enum { + ST_AVG_Z = 0, + ST_MAX_Z = 1, + ST_FAR_Z = 2, +} SORT_TYPE; + +typedef enum { + DRAW_OPAQUE = 0, + DRAW_COLOR_KEY = 1, +} DRAW_TYPE; + +typedef struct __unaligned { + int16_t mesh_idx; + uint16_t flags; + BOUNDS_16 draw_bounds; + BOUNDS_16 collision_bounds; +} STATIC_INFO; + +typedef struct __unaligned { + uint32_t timer; + uint32_t shots; + uint32_t hits; + uint32_t distance; + uint16_t kills; + uint8_t secrets; + uint8_t medipacks; +} STATISTICS_INFO; + +typedef struct __unaligned { + uint16_t pistol_ammo; + uint16_t magnum_ammo; + uint16_t uzi_ammo; + uint16_t shotgun_ammo; + uint16_t m16_ammo; + uint16_t grenade_ammo; + uint16_t harpoon_ammo; + uint8_t small_medipacks; + uint8_t large_medipacks; + uint8_t reserved1; + uint8_t flares; + uint8_t gun_status; + uint8_t gun_type; + uint16_t available: 1; // 0x01 1 + uint16_t has_pistols: 1; // 0x02 2 + uint16_t has_magnums: 1; // 0x04 4 + uint16_t has_uzis: 1; // 0x08 8 + uint16_t has_shotgun: 1; // 0x10 16 + uint16_t has_m16: 1; // 0x20 32 + uint16_t has_grenade: 1; // 0x40 64 + uint16_t has_harpoon: 1; // 0x80 128 + uint16_t pad : 8; + uint16_t reserved2; + STATISTICS_INFO statistics; +} START_INFO; + +typedef struct __unaligned { + START_INFO start[24]; + STATISTICS_INFO statistics; + int16_t current_level; + bool bonus_flag; + uint8_t num_pickup[2]; + uint8_t num_puzzle[4]; + uint8_t num_key[4]; + uint16_t reserved; + uint8_t buffer[6272]; +} SAVEGAME_INFO; + +typedef struct __unaligned { + int16_t lock_angles[4]; + int16_t left_angles[4]; + int16_t right_angles[4]; + int16_t aim_speed; + int16_t shot_accuracy; + int32_t gun_height; + int32_t damage; + int32_t target_dist; + int16_t recoil_frame; + int16_t flash_time; + int16_t sample_num; +} WEAPON_INFO; + +typedef struct __unaligned { + int16_t zone_num; + int16_t enemy_zone_num; + int32_t distance; + int32_t ahead; + int32_t bite; + int16_t angle; + int16_t enemy_facing; +} AI_INFO; + +typedef struct __unaligned { + int16_t timer; + int16_t sprite; +} PICKUP_INFO; + +typedef struct __unaligned { + int16_t shape; + XYZ_16 pos; + int32_t param1; + int32_t param2; + void *grdptr; + int16_t sprite_num; +} INVENTORY_SPRITE; + +typedef struct __unaligned { + char *string; + int16_t object_id; + int16_t frames_total; + int16_t current_frame; + int16_t goal_frame; + int16_t open_frame; + int16_t anim_direction; + int16_t anim_speed; + int16_t anim_count; + int16_t x_rot_pt_sel; + int16_t x_rot_pt; + int16_t x_rot_sel; + int16_t x_rot_nosel; + int16_t x_rot; + int16_t y_rot_sel; + int16_t y_rot; + int32_t y_trans_sel; + int32_t y_trans; + int32_t z_trans_sel; + int32_t z_trans; + uint32_t meshes_sel; + uint32_t meshes_drawn; + int16_t inv_pos; + INVENTORY_SPRITE **sprite_list; + int32_t reserved[4]; +} INVENTORY_ITEM; + +typedef enum { + RNG_OPENING = 0, + RNG_OPEN = 1, + RNG_CLOSING = 2, + RNG_MAIN2OPTION = 3, + RNG_MAIN2KEYS = 4, + RNG_KEYS2MAIN = 5, + RNG_OPTION2MAIN = 6, + RNG_SELECTING = 7, + RNG_SELECTED = 8, + RNG_DESELECTING = 9, + RNG_DESELECT = 10, + RNG_CLOSING_ITEM = 11, + RNG_EXITING_INVENTORY = 12, + RNG_DONE = 13, +} RING_STATUS; + +typedef struct __unaligned { + int16_t count; + int16_t status; + int16_t status_target; + int16_t radius_target; + int16_t radius_rate; + int16_t camera_y_target; + int16_t camera_y_rate; + int16_t camera_pitch_target; + int16_t camera_pitch_rate; + int16_t rotate_target; + int16_t rotate_rate; + int16_t item_pt_x_rot_target; + int16_t item_pt_x_rot_rate; + int16_t item_x_rot_target; + int16_t item_x_rot_rate; + int32_t item_y_trans_target; + int32_t item_y_trans_rate; + int32_t item_z_trans_target; + int32_t item_z_trans_rate; + int32_t misc; +} IMOTION_INFO; + +typedef enum { + PM_SPINE = 1, + PM_FRONT = 2, + PM_IN_FRONT = 4, + PM_PAGE_2 = 8, + PM_BACK = 16, + PM_IN_BACK = 32, + PM_PAGE_1 = 64, + PM_COMMON = PM_SPINE | PM_BACK | PM_FRONT, +} PASS_MESH; + +typedef struct __unaligned { + INVENTORY_ITEM **list; + int16_t type; + int16_t radius; + int16_t camera_pitch; + int16_t rotating; + int16_t rot_count; + int16_t current_object; + int16_t target_object; + int16_t number_of_objects; + int16_t angle_adder; + int16_t rot_adder; + int16_t rot_adder_l; + int16_t rot_adder_r; + PHD_3DPOS ring_pos; + PHD_3DPOS camera; + XYZ_32 light; + IMOTION_INFO *imo; +} RING_INFO; + +typedef enum { + GFE_PICTURE = 0, + GFE_LIST_START = 1, + GFE_LIST_END = 2, + GFE_PLAY_FMV = 3, + GFE_START_LEVEL = 4, + GFE_CUTSCENE = 5, + GFE_LEVEL_COMPLETE = 6, + GFE_DEMO_PLAY = 7, + GFE_JUMP_TO_SEQ = 8, + GFE_END_SEQ = 9, + GFE_SET_TRACK = 10, + GFE_SUNSET = 11, + GFE_LOADING_PIC = 12, + GFE_DEADLY_WATER = 13, + GFE_REMOVE_WEAPONS = 14, + GFE_GAME_COMPLETE = 15, + GFE_CUT_ANGLE = 16, + GFE_NO_FLOOR = 17, + GFE_ADD_TO_INV = 18, + GFE_START_ANIM = 19, + GFE_NUM_SECRETS = 20, + GFE_KILL_TO_COMPLETE = 21, + GFE_REMOVE_AMMO = 22, +} GF_EVENTS; + +typedef enum { + TARGET_NONE = 0, + TARGET_PRIMARY = 1, + TARGET_SECONDARY = 2, +} TARGET_TYPE; + +typedef struct __unaligned { + XYZ_32 pos; + int32_t mesh_num; +} BITE; + +typedef enum { + RF_UNDERWATER = 0x01, + RF_OUTSIDE = 0x08, + RF_DYNAMIC_LIT = 0x10, + RF_INSIDE = 0x40, +} ROOM_FLAG; + +typedef struct __unaligned { + SECTOR *sector; + SECTOR data; + int16_t block; +} DOORPOS_DATA; + +typedef enum { + CAM_CHASE = 0, + CAM_FIXED = 1, + CAM_LOOK = 2, + CAM_COMBAT = 3, + CAM_CINEMATIC = 4, + CAM_HEAVY = 5, +} CAMERA_TYPE; + +typedef struct __unaligned { + union { + XYZ_32 pos; + struct { + int32_t x; + int32_t y; + int32_t z; + }; + }; + int16_t room_num; + int16_t box_num; +} GAME_VECTOR; + +typedef struct __unaligned { + int32_t x; + int32_t y; + int32_t z; + int16_t data; + int16_t flags; +} OBJECT_VECTOR; + +typedef struct __unaligned { + uint8_t left; + uint8_t right; + uint8_t top; + uint8_t bottom; + int16_t height; + int16_t overlap_index; +} BOX_INFO; + +typedef enum { + LV_GYM = 0, + LV_FIRST = 1, +} LEVEL_TYPE; + +typedef enum { + RT_MAIN = 0, + RT_OPTION = 1, + RT_KEYS = 2, +} RING_TYPE; + +typedef enum { + INV_COLOR_BLACK = 0, + INV_COLOR_GRAY = 1, + INV_COLOR_WHITE = 2, + INV_COLOR_RED = 3, + INV_COLOR_ORANGE = 4, + INV_COLOR_YELLOW = 5, + INV_COLOR_DARK_GREEN = 12, + INV_COLOR_GREEN = 13, + INV_COLOR_CYAN = 14, + INV_COLOR_BLUE = 15, + INV_COLOR_MAGENTA = 16, + INV_COLOR_NUMBER_OF = 17, +} INV_COLOR; + +typedef enum { + INV_GAME_MODE = 0, + INV_TITLE_MODE = 1, + INV_KEYS_MODE = 2, + INV_SAVE_MODE = 3, + INV_LOAD_MODE = 4, + INV_DEATH_MODE = 5, +} INVENTORY_MODE; + +typedef enum { + GAMEMODE_NOT_IN_GAME, + GAMEMODE_IN_GAME, + GAMEMODE_IN_DEMO, + GAMEMODE_IN_CUTSCENE +} GAMEMODE; + +typedef enum { + GFD_START_GAME = 0x0000, + GFD_START_SAVED_GAME = 0x0100, + GFD_START_CINE = 0x0200, + GFD_START_FMV = 0x0300, + GFD_START_DEMO = 0x0400, + GFD_EXIT_TO_TITLE = 0x0500, + GFD_LEVEL_COMPLETE = 0x0600, + GFD_EXIT_GAME = 0x0700, + GFD_EXIT_TO_OPTION = 0x0800, + GFD_TITLE_DESELECT = 0x0900, + GFD_OVERRIDE = 0x0A00, +} GAME_FLOW_DIR; + +typedef struct __unaligned { + int32_t first_option; + int32_t title_replace; + int32_t on_death_demo_mode; + int32_t on_death_in_game; + int32_t no_input_time; + int32_t on_demo_interrupt; + int32_t on_demo_end; + uint16_t reserved1[18]; + uint16_t num_levels; + uint16_t num_pictures; + uint16_t num_titles; + uint16_t num_fmvs; + uint16_t num_cutscenes; + uint16_t num_demos; + uint16_t title_track; + int16_t single_level; + uint16_t reserved2[16]; + + uint16_t demo_version: 1; // 0x0001 + uint16_t title_disabled: 1; // 0x0002 + uint16_t cheat_mode_check_disabled: 1; // 0x0004 + uint16_t no_input_timeout: 1; // 0x0008 + uint16_t load_save_disabled: 1; // 0x0010 + uint16_t screen_sizing_disabled: 1; // 0x0020 + uint16_t lockout_option_ring: 1; // 0x0040 + uint16_t dozy_cheat_enabled: 1; // 0x0080 + uint16_t cyphered_strings: 1; // 0x0100 + uint16_t gym_enabled: 1; // 0x0200 + uint16_t play_any_level: 1; // 0x0400 + uint16_t cheat_enable: 1; // 0x0800 + + uint16_t reserved3[3]; + uint8_t cypher_code; + uint8_t language; + uint8_t secret_track; + uint8_t level_complete_track; + uint16_t reserved4[2]; +} GAME_FLOW; + +typedef struct __unaligned { + GAME_VECTOR pos; + GAME_VECTOR target; + CAMERA_TYPE type; + int32_t shift; + uint32_t flags; + int32_t fixed_camera; + int32_t num_frames; + int32_t bounce; + int32_t underwater; + int32_t target_distance; + int32_t target_square; + int16_t target_angle; + int16_t actual_angle; + int16_t target_elevation; + int16_t box; + int16_t num; + int16_t last; + int16_t timer; + int16_t speed; + ITEM *item; + ITEM *last_item; + OBJECT_VECTOR *fixed; + int32_t is_lara_mic; + XYZ_32 mic_pos; +} CAMERA_INFO; + +typedef enum { + SFX_LARA_FEET = 0, + SFX_LARA_CLIMB2 = 1, + SFX_LARA_NO = 2, + SFX_LARA_SLIPPING = 3, + SFX_LARA_LAND = 4, + SFX_LARA_CLIMB1 = 5, + SFX_LARA_DRAW = 6, + SFX_LARA_HOLSTER = 7, + SFX_LARA_FIRE = 8, + SFX_LARA_RELOAD = 9, + SFX_LARA_RICOCHET = 10, + SFX_LARA_FLARE_IGNITE = 11, + SFX_LARA_FLARE_BURN = 12, + SFX_LARA_HARPOON_FIRE = 15, + SFX_LARA_HARPOON_LOAD = 16, + SFX_LARA_WET_FEET = 17, + SFX_LARA_WADE = 18, + SFX_LARA_TREAD = 20, + SFX_LARA_FIRE_MAGNUMS = 21, + SFX_LARA_HARPOON_LOAD_WATER = 22, + SFX_LARA_HARPOON_FIRE_WATER = 23, + SFX_MASSIVE_CRASH = 24, + SFX_PUSH_SWITCH = 25, + SFX_LARA_CLIMB3 = 26, + SFX_LARA_BODYSL = 27, + SFX_LARA_SHIMMY = 28, + SFX_LARA_JUMP = 29, + SFX_LARA_FALL = 30, + SFX_LARA_INJURY = 31, + SFX_LARA_ROLL = 32, + SFX_LARA_SPLASH = 33, + SFX_LARA_GETOUT = 34, + SFX_LARA_SWIM = 35, + SFX_LARA_BREATH = 36, + SFX_LARA_BUBBLES = 37, + SFX_LARA_SWITCH = 38, + SFX_LARA_KEY = 39, + SFX_LARA_OBJECT = 40, + SFX_LARA_GENERAL_DEATH = 41, + SFX_LARA_KNEES_DEATH = 42, + SFX_LARA_UZI_FIRE = 43, + SFX_LARA_UZI_STOP = 44, + SFX_LARA_SHOTGUN = 45, + SFX_LARA_BLOCK_PUSH1 = 46, + SFX_LARA_BLOCK_PUSH2 = 47, + SFX_CLICK = 48, + SFX_LARA_HIT = 49, + SFX_LARA_BULLETHIT = 50, + SFX_LARA_BLKPULL = 51, + SFX_LARA_FLOATING = 52, + SFX_LARA_FALLDETH = 53, + SFX_LARA_GRABHAND = 54, + SFX_LARA_GRABBODY = 55, + SFX_LARA_GRABFEET = 56, + SFX_LARA_SWITCHUP = 57, + SFX_GLASS_BREAK = 58, + SFX_WATER_LOOP = 59, + SFX_UNDERWATER = 60, + SFX_UNDERWATER_SWITCH = 61, + SFX_LARA_PICKUP = 62, + SFX_BLOCK_SOUND = 63, + SFX_DOOR = 64, + SFX_SWING = 65, + SFX_ROCK_FALL_CRUMBLE = 66, + SFX_ROCK_FALL_LAND = 67, + SFX_ROCK_FALL_SOLID = 68, + SFX_ENEMY_FEET = 69, + SFX_ENEMY_GRUNT = 70, + SFX_ENEMY_HIT1 = 71, + SFX_ENEMY_HIT2 = 72, + SFX_ENEMY_DEATH1 = 73, + SFX_ENEMY_JUMP = 74, + SFX_ENEMY_CLIMBUP = 75, + SFX_ENEMY_CLIMBDOWN = 76, + SFX_WEAPON_CLATTER = 77, + SFX_M16_FIRE = 78, + SFX_WATERFALL_LOOP = 79, + SFX_SWORD_STATUE_DROP = 80, + SFX_SWORD_STATUE_LIFT = 81, + SFX_PORTCULLIS_UP = 82, + SFX_PORTCULLIS_DOWN = 83, + SFX_DOG_FEET1 = 84, + SFX_BODY_SLAM = 85, + SFX_DOG_BARK1 = 86, + SFX_DOG_FEET2 = 87, + SFX_DOG_BARK2 = 88, + SFX_DOG_DEATH = 89, + SFX_DOG_PANT = 90, + SFX_LEOPARD_FEET = 91, + SFX_LEOPARD_ROAR = 92, + SFX_LEOPARD_BITE = 93, + SFX_LEOPARD_STRIKE = 94, + SFX_LEOPARD_DEATH = 95, + SFX_LEOPARD_GROWL = 96, + SFX_RAT_ATTACK = 97, + SFX_RAT_DEATH = 98, + SFX_TIGER_ROAR = 99, + SFX_TIGER_BITE = 100, + SFX_TIGER_STRIKE = 101, + SFX_TIGER_DEATH = 102, + SFX_TIGER_GROWL = 103, + SFX_M16_STOP = 104, + SFX_EXPLOSION1 = 105, + SFX_GROWL = 106, + SFX_SPIDER_JUMP = 107, + SFX_MENU_ROTATE = 108, + SFX_MENU_LARA_HOME = 109, + SFX_MENU_SPININ = 111, + SFX_MENU_SPINOUT = 112, + SFX_MENU_STOPWATCH = 113, + SFX_MENU_GUNS = 114, + SFX_MENU_PASSPORT = 115, + SFX_MENU_MEDI = 116, + SFX_ENEMY_HEELS = 117, + SFX_ENEMY_FIRE_SILENCER = 118, + SFX_ENEMY_AH_DYING = 119, + SFX_ENEMY_OOH_DYING = 120, + SFX_ENEMY_THUMP = 121, + SFX_SPIDER_MOVING = 122, + SFX_LARA_MINI_LOAD = 123, + SFX_LARA_MINI_LOCK = 124, + SFX_LARA_MINI_FIRE = 125, + SFX_SPIDER_BITE = 126, + SFX_SLAM_DOOR_SLIDE = 127, + SFX_SLAM_DOOR_CLOSE = 128, + SFX_EAGLE_SQUAWK = 129, + SFX_EAGLE_WING_FLAP = 130, + SFX_EAGLE_DEATH = 131, + SFX_CROW_CAW = 132, + SFX_CROW_WING_FLAP = 133, + SFX_CROW_DEATH = 134, + SFX_CROW_ATTACK = 135, + SFX_ENEMY_GUN_COCKING = 136, + SFX_ENEMY_FIRE1 = 137, + SFX_ENEMY_FIRE_TWIRL = 138, + SFX_ENEMY_HOLSTER = 139, + SFX_ENEMY_BREATH1 = 140, + SFX_ENEMY_CHUCKLE = 141, + SFX_MONK_POY = 142, + SFX_MONK_DEATH = 143, + SFX_LARA_SPIKE_DEATH = 145, + SFX_LARA_DEATH3 = 146, + SFX_ROLLING_BALL = 147, + SFX_SANDBAG_SNAP = 148, + SFX_SANDBAG_HIT = 149, + SFX_LOOP_FOR_SMALL_FIRES = 150, + SFX_SKIDOO_START = 152, + SFX_SKIDOO_IDLE = 153, + SFX_SKIDOO_ACCELERATE = 154, + SFX_SKIDOO_MOVING = 155, + SFX_SKIDOO_STOP = 156, + SFX_ENEMY_FIRE2 = 157, + SFX_ENEMY_DEATH2 = 158, + SFX_ENEMY_BREATH2 = 159, + SFX_STICK_TAP = 160, + SFX_TRAPDOOR_OPEN = 161, + SFX_TRAPDOOR_CLOSE = 162, + SFX_YETI_GROWL = 163, + SFX_YETI_CHEST_BEAT = 164, + SFX_YETI_THUMP = 165, + SFX_YETI_GRUNT1 = 166, + SFX_YETI_SCREAM = 167, + SFX_YETI_DEATH = 168, + SFX_YETI_GROWL1 = 169, + SFX_YETI_GROWL2 = 170, + SFX_YETI_GRUNT2 = 171, + SFX_YETI_GROWL3 = 172, + SFX_YETI_FEET = 173, + SFX_ENEMY_HEAVY_BREATH = 174, + SFX_ENEMY_FLAMETHROWER_FIRE = 175, + SFX_ENEMY_FLAMETHROWER_SCRAPE = 176, + SFX_ENEMY_FLAMETHROWER_CLICK = 177, + SFX_ENEMY_FLAMETHROWER_DEATH = 178, + SFX_ENEMY_FLAMETHROWER_FALL = 179, + SFX_ENEMY_BELT_JINGLE = 180, + SFX_ENEMY_WRENCH = 181, + SFX_FOOTSTEP = 182, + SFX_FOOTSTEP_HIT = 183, + SFX_ENEMY_COCKING_SHOTGUN = 184, + SFX_SCUBA_DIVER_FLIPPER = 186, + SFX_SCUBA_DIVER_BREATH = 188, + SFX_PULLEY_CRANE = 190, + SFX_CURTAIN = 191, + SFX_SCUBA_DIVER_DEATH = 192, + SFX_SCUBA_DIVER_DIVING = 193, + SFX_BOAT_START = 194, + SFX_BOAT_IDLE = 195, + SFX_BOAT_ACCELERATE = 196, + SFX_BOAT_MOVING = 197, + SFX_BOAT_STOP = 198, + SFX_BOAT_SLOW_DOWN = 199, + SFX_BOAT_HIT = 200, + SFX_CLATTER_1 = 201, + SFX_CLATTER_2 = 202, + SFX_CLATTER_3 = 203, + SFX_DOOR_SLIDE = 204, + SFX_LARA_FLESH_WOUND = 205, + SFX_SAW_REVVING = 206, + SFX_SAW_STOP = 207, + SFX_DOOR_CHIME = 208, + SFX_CHAIN_CREAK_SNAP = 209, + SFX_SWINGING = 210, + SFX_BREAKING1 = 211, + SFX_PULLEY_MOVE = 212, + SFX_AIRPLANE_IDLE = 213, + SFX_UNDERWATER_FAN_ON = 215, + SFX_SMALL_FAN_ON = 217, + SFX_SWINGING_BOX_BAG = 218, + SFX_JUMP_PAD_UP = 219, + SFX_JUMP_PAD_DOWN = 220, + SFX_BREAKING2 = 221, + SFX_SNOWBALL_ROLL = 222, + SFX_SNOWBALL_STOP = 223, + SFX_ROLLING = 224, + SFX_ROLLING_STOP1 = 225, + SFX_ROLLING_STOP2 = 226, + SFX_ROLLING2 = 227, + SFX_ROLLING2_HIT = 228, + SFX_SIDE_BLADE_SWING = 229, + SFX_SIDE_BLADE_BACK = 230, + SFX_ROLLING_BLADE = 231, + SFX_ICILE_DETACH = 232, + SFX_ICICLE_HIT = 233, + SFX_ROTATING_HANDLE_LOOSE = 234, + SFX_ROTATING_HANDLE_TURN = 235, + SFX_ROTATING_HANDLE_OPEN = 236, + SFX_ROTATING_HANDLE_CREAK = 237, + SFX_MONK_FEET = 238, + SFX_MONK_SWORD_SWING1 = 239, + SFX_MONK_SWORD_SWING2 = 240, + SFX_MONK_SHOUT1 = 241, + SFX_MONK_SHOUT2 = 242, + SFX_MONK_SHOUT3 = 243, + SFX_MONK_SHOUT4 = 244, + SFX_MONK_CRUNCH = 245, + SFX_MONK_BREATH = 246, + SFX_SPLASH_SURFACE = 247, + SFX_WATERFALL1 = 248, + SFX_ENEMY_FEET_SNOW = 249, + SFX_ENEMY_FIRE3 = 250, + SFX_ENEMY_FIRE_SEMIAUTO = 251, + SFX_ENEMY_DEATH3 = 252, + SFX_ENEMY_DEATH4 = 253, + SFX_CIRCLE_BLADE = 254, + SFX_KNIFETHROWER_FEET = 255, + SFX_MONK_OYE = 256, + SFX_MONK_AWEH = 257, + SFX_CIRCLE_BLADE_HIT = 258, + SFX_KNIFETHROWER_WARRIOR_FEET = 259, + SFX_WARRIOR_BLADE_SWING1 = 260, + SFX_WARRIOR_BLADE_SWING2 = 261, + SFX_WARRIOR_GROWL = 262, + SFX_KNIFETHROWER_HICCUP = 263, + SFX_WARROPR_BURP = 264, + SFX_WARRIOR_GROWL1 = 265, + SFX_WARRIOR_WAKE = 267, + SFX_WARRIOR_GROWL2 = 268, + SFX_SMALL_SWITCH = 269, + SFX_CHAIN_PULLEY = 278, + SFX_DEATH_SLIDE_GRAB = 279, + SFX_DEATH_SLIDE_GO = 280, + SFX_DEATH_SLIDE_STOP = 281, + SFX_BODY_SLUMP = 282, + SFX_BOWL_TIPPING = 283, + SFX_BOWL_POUR = 284, + SFX_WATERFALL2 = 285, + SFX_ELEVATOR_OPEN = 286, + SFX_ELEVATOR_CLOSE = 287, + SFX_MINISUB_CLATTER1 = 288, + SFX_MINISUB_CLATTER2 = 289, + SFX_MINISUB_CLATTER3 = 290, + SFX_BIRD_MONSTER_SCREAM = 291, + SFX_BIRD_MONSTER_GASP = 292, + SFX_BIRD_MONSTER_BREATH = 293, + SFX_BIRD_MONSTER_FEET = 294, + SFX_BIRD_MONSTER_DEATH = 295, + SFX_BIRD_MONSTER_SCRAPE = 296, + SFX_HELICOPTER_LOOP = 297, + SFX_DRAGON_FEET = 298, + SFX_DRAGON_GROWL1 = 299, + SFX_DRAGON_GROWL2 = 300, + SFX_DRAGON_FALL = 301, + SFX_DRAGON_BREATH = 302, + SFX_DRAGON_GROWL3 = 303, + SFX_DRAGON_GRUNT = 304, + SFX_DRAGON_FIRE = 305, + SFX_DRAGON_LEG_LIFT = 306, + SFX_DRAGON_LEG_HIT = 307, + SFX_WARRIOR_BLADE_SWING3 = 308, + SFX_WARRIOR_BLADE_SWING_FAST = 309, + SFX_WARRIOR_BREATH_ACTIVE = 311, + SFX_WARRIOR_HOVER = 312, + SFX_WARRIOR_LANDING = 313, + SFX_WARRIOR_SWORD_CLANK = 314, + SFX_WARRIOR_SWORD_SLICE = 315, + SFX_BIRDS_CHIRP = 316, + SFX_CRUNCH1 = 317, + SFX_CRUNCH2 = 318, + SFX_DOOR_CREAK = 319, + SFX_BREAKING3 = 320, + SFX_BIG_SPIDER_SNARL = 321, + SFX_BIG_SPIDER_FEET = 322, + SFX_BIG_SPIDER_DEATH = 323, + SFX_T_REX_ROAR = 324, + SFX_T_REX_FEET = 325, + SFX_T_REX_GROWL1 = 326, + SFX_T_REX_DEATH = 327, + SFX_DRIPS_REVERB = 329, + SFX_STAGE_BACKDROP = 330, + SFX_STONE_DOOR_SLIDE = 331, + SFX_PLATFORM_ALARM = 332, + SFX_TICK_TOCK = 333, + SFX_DOORBELL = 334, + SFX_BURGLAR_ALARM = 335, + SFX_BOAT_ENGINE = 336, + SFX_BOAT_INTO_WATER = 337, + SFX_UNKNOWN1 = 338, + SFX_UNKNOWN2 = 339, + SFX_UNKNOWN3 = 340, + SFX_MARCO_BARTOLLI_TRANSFORM = 341, + SFX_WINSTON_SHUFFLE = 342, + SFX_WINSTON_FEET = 343, + SFX_WINSTON_GRUNT1 = 344, + SFX_WINSTON_GRUNT2 = 345, + SFX_WINSTON_GRUNT3 = 346, + SFX_WINSTON_CUPS = 347, + SFX_BRITTLE_GROUND_BREAK = 348, + SFX_SPIDER_EXPLODE = 349, + SFX_SHARK_BITE = 350, + SFX_LAVA_BUBBLES = 351, + SFX_EXPLOSION2 = 352, + SFX_BURGLARS = 353, + SFX_ZIPPER = 354, + SFX_NUMBER_OF = 370, +} SOUND_EFFECT_ID; + +typedef enum { + SPM_NORMAL = 0, + SPM_UNDERWATER = 1, + SPM_ALWAYS = 2, + SPM_PITCH = 4, +} SOUND_PLAY_MODE; + +typedef enum { + CF_NORMAL = 0, + CF_FOLLOW_CENTRE = 1, + CF_NO_CHUNKY = 2, + CF_CHASE_OBJECT = 3, +} CAMERA_FLAGS; + +typedef enum { + FBBOX_MIN_X = 0, + FBBOX_MAX_X = 1, + FBBOX_MIN_Y = 2, + FBBOX_MAX_Y = 3, + FBBOX_MIN_Z = 4, + FBBOX_MAX_Z = 5, + FBBOX_X = 6, + FBBOX_Y = 7, + FBBOX_Z = 8, + FBBOX_ROT = 9, +} FRAME_BBOX_INFO; + +typedef enum { + BF_MATRIX_POP = 1, + BF_MATRIX_PUSH = 2, + BF_ROT_X = 4, + BF_ROT_Y = 8, + BF_ROT_Z = 16, +} BONE_FLAGS; + +typedef struct __unaligned { + int16_t tx; + int16_t ty; + int16_t tz; + int16_t cx; + int16_t cy; + int16_t cz; + int16_t fov; + int16_t roll; +} CINE_FRAME; + +typedef struct __unaligned { + uint16_t key[14]; // INPUT_ROLE_NUMBER_OF +} CONTROL_LAYOUT; + +typedef enum { + MX_INACTIVE = -1, + MX_UNUSED_0 = 0, // 2.mp3 + MX_UNUSED_1 = 1, // 2.mp3 + MX_CUTSCENE_THE_GREAT_WALL = 2, // 2.mp3 + MX_UNUSED_2 = 3, // 2.mp3 + MX_CUTSCENE_OPERA_HOUSE = 4, // 3.mp3 + MX_CUTSCENE_BROTHER_CHAN = 5, // 4.mp3 + MX_GYM_HINT_1 = 6, // 5.mp3 + MX_GYM_HINT_2 = 7, // 6.mp3 + MX_GYM_HINT_3 = 8, // 7.mp3 + MX_GYM_HINT_4 = 9, // 8.mp3 + MX_GYM_HINT_5 = 10, // 9.mp3 + MX_GYM_HINT_6 = 11, // 10.mp3 + MX_GYM_HINT_7 = 12, // 11.mp3 + MX_GYM_HINT_8 = 13, // 12.mp3 + MX_GYM_HINT_9 = 14, // 13.mp3 + MX_GYM_HINT_10 = 15, // 14.mp3 + MX_GYM_HINT_11 = 16, // 15.mp3 + MX_GYM_HINT_12 = 17, // 16.mp3 + MX_GYM_HINT_13 = 18, // 17.mp3 + MX_GYM_HINT_14 = 19, // 18.mp3 + MX_UNUSED_3 = 20, // 18.mp3 + MX_UNUSED_4 = 21, // 18.mp3 + MX_GYM_HINT_15 = 22, // 19.mp3 + MX_GYM_HINT_16 = 23, // 20.mp3 + MX_GYM_HINT_17 = 24, // 21.mp3 + MX_GYM_HINT_18 = 25, // 22.mp3 + MX_UNUSED_5 = 26, // 23.mp3 + MX_CUTSCENE_BATH = 27, // 23.mp3 + MX_DAGGER_PULL = 28, // 24.mp3 + MX_GYM_HINT_20 = 29, // 25.mp3 + MX_CUTSCENE_XIAN = 30, // 26.mp3 + MX_CAVES_AMBIENCE = 31, // 27.mp3 + MX_SEWERS_AMBIENCE = 32, // 28.mp3 + MX_WINDY_AMBIENCE = 33, // 29.mp3 + MX_HEARTBEAT_AMBIENCE = 34, // 30.mp3 + MX_SURPRISE_1 = 35, // 31.mp3 + MX_SURPRISE_2 = 36, // 32.mp3 + MX_SURPRISE_3 = 37, // 33.mp3 + MX_OOH_AAH_1 = 38, // 34.mp3 + MX_OOH_AAH_2 = 39, // 35.mp3 + MX_VENICE_VIOLINS = 40, // 36.mp3 + MX_END_OF_LEVEL = 41, // 37.mp3 + MX_SPOOKY_1 = 42, // 38.mp3 + MX_SPOOKY_2 = 43, // 39.mp3 + MX_SPOOKY_3 = 44, // 40.mp3 + MX_HARP_THEME = 45, // 41.mp3 + MX_MYSTERY_1 = 46, // 42.mp3 + MX_SECRET = 47, // 43.mp3 + MX_AMBUSH_1 = 48, // 44.mp3 + MX_AMBUSH_2 = 49, // 45.mp3 + MX_AMBUSH_3 = 50, // 46.mp3 + MX_AMBUSH_4 = 51, // 47.mp3 + MX_SKIDOO_THEME = 52, // 48.mp3 + MX_BATTLE_THEME = 53, // 49.mp3 + MX_MYSTERY_2 = 54, // 50.mp3 + MX_MYSTERY_3 = 55, // 51.mp3 + MX_MYSTERY_4 = 56, // 52.mp3 + MX_MYSTERY_5 = 57, // 53.mp3 + MX_RIG_AMBIENCE = 58, // 54.mp3 + MX_TOMB_AMBIENCE = 59, // 55.mp3 + MX_OOH_AAH_3 = 60, // 56.mp3 + MX_REVEAL_1 = 61, // 57.mp3 + MX_CUTSCENE_RIG = 62, // 58.mp3 + MX_REVEAL_2 = 63, // 59.mp3 + MX_TITLE_THEME = 64, // 60.mp3 + MX_UNUSED_6 = 65, // 61.mp3 +} MUSIC_TRACK_ID; + +typedef enum { + COLL_NONE = 0x00, + COLL_FRONT = 0x01, + COLL_LEFT = 0x02, + COLL_RIGHT = 0x04, + COLL_TOP = 0x08, + COLL_TOP_FRONT = 0x10, + COLL_CLAMP = 0x20, +} COLL_TYPE; + +typedef enum { + FT_FLOOR = 0, + FT_DOOR = 1, + FT_TILT = 2, + FT_ROOF = 3, + FT_TRIGGER = 4, + FT_LAVA = 5, + FT_CLIMB = 6, +} FLOOR_TYPE; + +typedef enum { + HT_WALL = 0, + HT_SMALL_SLOPE = 1, + HT_BIG_SLOPE = 2, +} HEIGHT_TYPE; + +typedef struct __unaligned { + uint16_t x; + uint16_t y; +} XGEN_X; + +typedef struct __unaligned { + int32_t x1; + int32_t x2; +} XBUF_X; + +typedef struct __unaligned { + int16_t x; + int16_t y; + int16_t g; +} XGEN_XG; + +typedef struct __unaligned { + int32_t x1; + int32_t g1; + int32_t x2; + int32_t g2; +} XBUF_XG; + +typedef struct __unaligned { + uint16_t x; + uint16_t y; + uint16_t g; + uint16_t u; + uint16_t v; +} XGEN_XGUV; + +typedef struct __unaligned { + int32_t x1; + int32_t g1; + int32_t u1; + int32_t v1; + int32_t x2; + int32_t g2; + int32_t u2; + int32_t v2; +} XBUF_XGUV; + +typedef struct __unaligned { + uint16_t x; + uint16_t y; + uint16_t g; + float rhw; + float u; + float v; +} XGEN_XGUVP; + +typedef struct __unaligned { + int32_t x1; + int32_t g1; + float u1; + float v1; + float rhw1; + int32_t x2; + int32_t g2; + float u2; + float v2; + float rhw2; +} XBUF_XGUVP; + +typedef struct __unaligned { + uint8_t manufacturer; + uint8_t version; + uint8_t rle; + uint8_t bpp; + uint16_t x_min; + uint16_t y_min; + uint16_t x_max; + uint16_t y_max; + uint16_t h_dpi; + uint16_t v_dpi; + RGB_888 palette[16]; + uint8_t reserved; + uint8_t planes; + uint16_t bytes_per_line; + uint16_t pal_pnterpret; + uint16_t h_res; + uint16_t v_res; + uint8_t reserved_data[54]; +} PCX_HEADER; + +typedef struct __unaligned { + uint8_t id_length; + uint8_t color_map_type; + uint8_t data_type_code; + uint16_t color_map_origin; + uint16_t color_map_length; + uint8_t color_map_depth; + uint16_t x_origin; + uint16_t y_origin; + uint16_t width; + uint16_t height; + uint8_t bpp; + uint8_t image_descriptor; +} TGA_HEADER; + +typedef struct __unaligned { + int16_t number; + int16_t volume; + int16_t randomness; + int16_t flags; +} SAMPLE_INFO; + +/* +typedef struct __unaligned { + int32_t volume; + int32_t pan; + int32_t sample_num; + int32_t pitch; +} SOUND_SLOT; +*/ + +typedef enum { + SF_FLIP = 0x40, + SF_UNFLIP = 0x80, +} SOUND_FLAG; + +typedef enum { + GBUF_TEMP_ALLOC = 0, + GBUF_TEXTURE_PAGES = 1, + GBUF_MESH_POINTERS = 2, + GBUF_MESHES = 3, + GBUF_ANIMS = 4, + GBUF_STRUCTS = 5, + GBUF_ANIM_RANGES = 6, + GBUF_ANIM_COMMANDS = 7, + GBUF_ANIM_BONES = 8, + GBUF_ANIM_FRAMES = 9, + GBUF_ROOM_TEXTURES = 10, + GBUF_ROOMS = 11, + GBUF_ROOM_MESH = 12, + GBUF_ROOM_PORTALS = 13, + GBUF_ROOM_FLOOR = 14, + GBUF_ROOM_LIGHTS = 15, + GBUF_ROOM_STATIC_MESHES = 16, + GBUF_FLOOR_DATA = 17, + GBUF_ITEMS = 18, + GBUF_CAMERAS = 19, + GBUF_SOUND_FX = 20, + GBUF_BOXES = 21, + GBUF_OVERLAPS = 22, + GBUF_GROUND_ZONE = 23, + GBUF_FLY_ZONE = 24, + GBUF_ANIMATING_TEXTURE_RANGES = 25, + GBUF_CINEMATIC_FRAMES = 26, + GBUF_LOAD_DEMO_BUFFER = 27, + GBUF_SAVE_DEMO_BUFFER = 28, + GBUF_CINEMATIC_EFFECTS = 29, + GBUF_MUMMY_HEAD_TURN = 30, + GBUF_EXTRA_DOOR_STUFF = 31, + GBUF_EFFECTS_ARRAY = 32, + GBUF_CREATURE_DATA = 33, + GBUF_CREATURE_LOT = 34, + GBUF_SAMPLE_INFOS = 35, + GBUF_SAMPLES = 36, + GBUF_SAMPLE_OFFSETS = 37, + GBUF_ROLLING_BALL_STUFF = 38, + GBUF_SKIDOO_STUFF = 39, + GBUF_LOAD_PICTURE_BUFFER = 40, + GBUF_FMV_BUFFERS = 41, + GBUF_POLYGON_BUFFERS = 42, + GBUF_ORDER_TABLES = 43, + GBUF_CLUTS = 44, + GBUF_TEXTURE_INFOS = 45, + GBUF_SPRITE_INFOS = 46, + GBUF_NUM_MALLOC_TYPES = 47, +} GAME_BUFFER; + +typedef enum { + CLRB_PRIMARY_BUFFER = 0x0001, + CLRB_BACK_BUFFER = 0x0002, + CLRB_THIRD_BUFFER = 0x0004, + CLRB_Z_BUFFER = 0x0008, + CLRB_RENDER_BUFFER = 0x0010, + CLRB_PICTURE_BUFFER = 0x0020, + CLRB_WINDOWED_PRIMARY_BUFFER = 0x0040, + CLRB_RESERVED = 0x0080, + CLRB_PHDWINSIZE = 0x0100, +} CLEAR_BUFFER_FLAGS; + +typedef enum { + AC_NULL = 0, + AC_MOVE_ORIGIN = 1, + AC_JUMP_VELOCITY = 2, + AC_ATTACK_READY = 3, + AC_DEACTIVATE = 4, + AC_SOUND_FX = 5, + AC_EFFECT = 6, +} ANIM_COMMAND; + +typedef enum { + ACE_ALL = 0, + ACE_LAND = 1, + ACE_WATER = 2, +} ANIM_COMMAND_ENVIRONMENT; + +typedef struct __unaligned { + DDPIXELFORMAT pixel_fmt; + COLOR_BIT_MASKS color_bit_masks; + DWORD bpp; +} TEXTURE_FORMAT; + +typedef struct __unaligned { + int32_t boat_turn; + int32_t left_fallspeed; + int32_t right_fallspeed; + int16_t tilt_angle; + int16_t extra_rotation; + int32_t water; + int32_t pitch; +} BOAT_INFO; + +typedef struct __unaligned { + struct { + XYZ_16 min; + XYZ_16 max; + } shift, rot; +} OBJECT_BOUNDS; + +typedef struct __unaligned { + int32_t xv; + int32_t yv; + int32_t zv; +} PORTAL_VBUF; + +typedef struct __unaligned { + BOUNDS_16 bounds; + XYZ_16 offset; + int16_t mesh_rots[]; +} FRAME_INFO; + +typedef enum { + TO_OBJECT = 0, + TO_CAMERA = 1, + TO_SINK = 2, + TO_FLIP_MAP = 3, + TO_FLIP_ON = 4, + TO_FLIP_OFF = 5, + TO_TARGET = 6, + TO_FINISH = 7, + TO_CD = 8, + TO_FLIP_EFFECT = 9, + TO_SECRET = 10, + TO_BODY_BAG = 11, +} TRIGGER_OBJECT; + +typedef enum { + TT_TRIGGER = 0, + TT_PAD = 1, + TT_SWITCH = 2, + TT_KEY = 3, + TT_PICKUP = 4, + TT_HEAVY = 5, + TT_ANTIPAD = 6, + TT_COMBAT = 7, + TT_DUMMY = 8, + TT_ANTITRIGGER = 9, +} TRIGGER_TYPE; + +typedef enum { + GF_S_PC_DETAIL_LEVELS = 0, + GF_S_PC_DEMO_MODE = 1, + GF_S_PC_SOUND = 2, + GF_S_PC_CONTROLS = 3, + GF_S_PC_GAMMA = 4, + GF_S_PC_SET_VOLUMES = 5, + GF_S_PC_USER_KEYS = 6, + GF_S_PC_SAVE_FILE_WARNING = 7, + GF_S_PC_TRY_AGAIN_QUESTION = 8, + GF_S_PC_YES = 9, + GF_S_PC_NO = 10, + GF_S_PC_SAVE_COMPLETE = 11, + GF_S_PC_NO_SAVE_GAMES = 12, + GF_S_PC_NONE_VALID = 13, + GF_S_PC_SAVE_GAME_QUESTION = 14, + GF_S_PC_EMPTY_SLOT = 15, + GF_S_PC_OFF = 16, + GF_S_PC_ON = 17, + GF_S_PC_SETUP_SOUND_CARD = 18, + GF_S_PC_DEFAULT_KEYS = 19, + GF_S_PC_DOZY = 20, + GF_S_PC_NUMBER_OF = 41, +} GF_PC_STRING; + +typedef enum { + GF_S_GAME_HEADING_INVENTORY = 0, + GF_S_GAME_HEADING_OPTION = 1, + GF_S_GAME_HEADING_ITEMS = 2, + GF_S_GAME_HEADING_GAME_OVER = 3, + GF_S_GAME_PASSPORT_LOAD_GAME = 4, + GF_S_GAME_PASSPORT_SAVE_GAME = 5, + GF_S_GAME_PASSPORT_NEW_GAME = 6, + GF_S_GAME_PASSPORT_RESTART_LEVEL = 7, + GF_S_GAME_PASSPORT_EXIT_TO_TITLE = 8, + GF_S_GAME_PASSPORT_EXIT_DEMO = 9, + GF_S_GAME_PASSPORT_EXIT_GAME = 10, + GF_S_GAME_PASSPORT_SELECT_LEVEL = 11, + GF_S_GAME_PASSPORT_SAVE_POSITION = 12, + GF_S_GAME_DETAIL_SELECT_DETAIL = 13, + GF_S_GAME_DETAIL_HIGH = 14, + GF_S_GAME_DETAIL_MEDIUM = 15, + GF_S_GAME_DETAIL_LOW = 16, + GF_S_GAME_KEYMAP_WALK = 17, + GF_S_GAME_KEYMAP_ROLL = 18, + GF_S_GAME_KEYMAP_RUN = 19, + GF_S_GAME_KEYMAP_LEFT = 20, + GF_S_GAME_KEYMAP_RIGHT = 21, + GF_S_GAME_KEYMAP_BACK = 22, + GF_S_GAME_KEYMAP_STEP_LEFT = 23, + GF_S_GAME_KEYMAP_RESERVED_1 = 24, + GF_S_GAME_KEYMAP_STEP_RIGHT = 25, + GF_S_GAME_KEYMAP_RESERVED_2 = 26, + GF_S_GAME_KEYMAP_LOOK = 27, + GF_S_GAME_KEYMAP_JUMP = 28, + GF_S_GAME_KEYMAP_ACTION = 29, + GF_S_GAME_KEYMAP_DRAW_WEAPON = 30, + GF_S_GAME_KEYMAP_RESERVED_3 = 31, + GF_S_GAME_KEYMAP_INVENTORY = 32, + GF_S_GAME_KEYMAP_FLARE = 33, + GF_S_GAME_KEYMAP_STEP = 34, + GF_S_GAME_INV_ITEM_STATISTICS = 35, + GF_S_GAME_INV_ITEM_PISTOLS = 36, + GF_S_GAME_INV_ITEM_SHOTGUN = 37, + GF_S_GAME_INV_ITEM_MAGNUMS = 38, + GF_S_GAME_INV_ITEM_UZIS = 39, + GF_S_GAME_INV_ITEM_HARPOON = 40, + GF_S_GAME_INV_ITEM_M16 = 41, + GF_S_GAME_INV_ITEM_GRENADE = 42, + GF_S_GAME_INV_ITEM_FLARE = 43, + GF_S_GAME_INV_ITEM_PISTOL_AMMO = 44, + GF_S_GAME_INV_ITEM_SHOTGUN_AMMO = 45, + GF_S_GAME_INV_ITEM_MAGNUM_AMMO = 46, + GF_S_GAME_INV_ITEM_UZI_AMMO = 47, + GF_S_GAME_INV_ITEM_HARPOON_AMMO = 48, + GF_S_GAME_INV_ITEM_M16_AMMO = 49, + GF_S_GAME_INV_ITEM_GRENADE_AMMO = 50, + GF_S_GAME_INV_ITEM_SMALL_MEDIPACK = 51, + GF_S_GAME_INV_ITEM_LARGE_MEDIPACK = 52, + GF_S_GAME_INV_ITEM_PICKUP = 53, + GF_S_GAME_INV_ITEM_PUZZLE = 54, + GF_S_GAME_INV_ITEM_KEY = 55, + GF_S_GAME_INV_ITEM_GAME = 56, + GF_S_GAME_INV_ITEM_LARA_HOME = 57, + GF_S_GAME_MISC_LOADING = 58, + GF_S_GAME_MISC_TIME_TAKEN = 59, + GF_S_GAME_MISC_SECRETS_FOUND = 60, + GF_S_GAME_MISC_LOCATION = 61, + GF_S_GAME_MISC_KILLS = 62, + GF_S_GAME_MISC_AMMO_USED = 63, + GF_S_GAME_MISC_HITS = 64, + GF_S_GAME_MISC_SAVES_PERFORMED = 65, + GF_S_GAME_MISC_DISTANCE_TRAVELLED = 66, + GF_S_GAME_MISC_HEALTH_PACKS_USED = 67, + GF_S_GAME_MISC_RELEASE_VERSION = 68, + GF_S_GAME_MISC_NONE = 69, + GF_S_GAME_MISC_FINISH = 70, + GF_S_GAME_MISC_BEST_TIMES = 71, + GF_S_GAME_MISC_NO_TIMES_SET = 72, + GF_S_GAME_MISC_NA = 73, + GF_S_GAME_MISC_CURRENT_POSITION = 74, + GF_S_GAME_MISC_FINAL_STATISTICS = 75, + GF_S_GAME_MISC_OF = 76, + GF_S_GAME_MISC_STORY_SO_FAR = 77, + GF_S_GAME_NUMBER_OF = 89, +} GF_GAME_STRING; + +typedef enum { + GF_ADD_INV_PISTOLS = 0, + GF_ADD_INV_SHOTGUN = 1, + GF_ADD_INV_MAGNUMS = 2, + GF_ADD_INV_UZIS = 3, + GF_ADD_INV_HARPOON = 4, + GF_ADD_INV_M16 = 5, + GF_ADD_INV_GRENADE = 6, + GF_ADD_INV_PISTOL_AMMO = 7, + GF_ADD_INV_SHOTGUN_AMMO = 8, + GF_ADD_INV_MAGNUM_AMMO = 9, + GF_ADD_INV_UZI_AMMO = 10, + GF_ADD_INV_HARPOON_AMMO = 11, + GF_ADD_INV_M16_AMMO = 12, + GF_ADD_INV_GRENADE_AMMO = 13, + GF_ADD_INV_FLARES = 14, + GF_ADD_INV_SMALL_MEDI = 15, + GF_ADD_INV_LARGE_MEDI = 16, + GF_ADD_INV_PICKUP_1 = 17, + GF_ADD_INV_PICKUP_2 = 18, + GF_ADD_INV_PUZZLE_1 = 19, + GF_ADD_INV_PUZZLE_2 = 20, + GF_ADD_INV_PUZZLE_3 = 21, + GF_ADD_INV_PUZZLE_4 = 22, + GF_ADD_INV_KEY_1 = 23, + GF_ADD_INV_KEY_2 = 24, + GF_ADD_INV_KEY_3 = 25, + GF_ADD_INV_KEY_4 = 26, + GF_ADD_INV_NUMBER_OF = 27, +} GF_ADD_INV; + +typedef enum { + IT_NAME = 0, + IT_QTY = 1, + IT_NUMBER_OF = 2, +} INV_TEXT; + +typedef enum { + REQ_CENTER = 0x00, + REQ_USE = 0x01, + REQ_ALIGN_LEFT = 0x02, + REQ_ALIGN_RIGHT = 0x04, + REQ_HEADING = 0x08, + REQ_BEST_TIME = 0x10, + REQ_NORMAL_TIME = 0x20, + REQ_NO_TIME = 0x40, +} REQUESTER_FLAGS; + +// clang-format on diff --git a/src/tr2/global/utils.h b/src/tr2/global/utils.h new file mode 100644 index 000000000..2af0b4987 --- /dev/null +++ b/src/tr2/global/utils.h @@ -0,0 +1,6 @@ +#pragma once + +#include "global/const.h" + +#define ROUND_TO_CLICK(V) ((V) & ~(STEP_L - 1)) +#define ROUND_TO_SECTOR(V) ((V) & ~(WALL_L - 1)) diff --git a/src/tr2/global/vars.c b/src/tr2/global/vars.c new file mode 100644 index 000000000..f30f7feab --- /dev/null +++ b/src/tr2/global/vars.c @@ -0,0 +1,7 @@ +#include "global/vars.h" + +#ifndef MESON_BUILD +const char *g_TR2XVersion = "TR2X (non-Docker build)"; +#endif + +GAME_FLOW_DIR g_GF_OverrideDir = (GAME_FLOW_DIR)-1; diff --git a/src/tr2/global/vars.h b/src/tr2/global/vars.h new file mode 100644 index 000000000..65ced5de3 --- /dev/null +++ b/src/tr2/global/vars.h @@ -0,0 +1,6 @@ +#pragma once + +#include "global/vars_decomp.h" + +extern const char *g_TR2XVersion; +extern GAME_FLOW_DIR g_GF_OverrideDir; diff --git a/src/tr2/global/vars_decomp.h b/src/tr2/global/vars_decomp.h new file mode 100644 index 000000000..9c97b63b6 --- /dev/null +++ b/src/tr2/global/vars_decomp.h @@ -0,0 +1,491 @@ +// This file is autogenerated. To update it, run tools/generate_funcs. + +#pragma once + +#include "global/types.h" +#include "inject_util.h" + +// clang-format off +#define g_IID_IDirectDrawSurface3 (*(GUID*)0x00463150) +#define g_PerspectiveDistance (*(uint32_t*)0x00464060) // = 0x3000000 +#define g_PolyDrawRoutines (*((void(__cdecl *(*)[9])(const int16_t *))0x00464068)) +#define g_RhwFactor (*(float*)0x0046408C) // = 335544320.0f +#define g_CineTrackID (*(int32_t*)0x004640B0) // = 1 +#define g_CineTickRate (*(int32_t*)0x004640B8) // = 0x8000 +#define g_CD_TrackID (*(int16_t*)0x004640BC) // = -1 +#define g_FlipEffect (*(int32_t*)0x004640C4) // = -1 +#define g_BoxLines (*(int32_t(*)[12][2])0x00464180) +#define g_AssaultBestTime (*(int32_t*)0x004641F0) // = -1 +#define g_EffectRoutines (*((void(__cdecl *(*)[32])(ITEM *item))0x004641F8)) +#define g_GF_NumSecrets (*(int16_t*)0x004642E8) // = 3 +#define g_GF_MusicTracks (*(int16_t(*)[16])0x004642F0) +#define g_CineTargetAngle (*(int16_t*)0x00464310) // = 0x4000 +#define g_OverlayStatus (*(int32_t*)0x004644E0) // = 1 +#define g_Inv_NFrames (*(int32_t*)0x004644F8) +#define g_Requester_BackgroundGour1 (*(const uint16_t(*)[])0x00464500) +#define g_Requester_BackgroundGour2 (*(const uint16_t(*)[])0x00464520) +#define g_Requester_MainGour1 (*(const uint16_t(*)[])0x00464538) +#define g_Requester_MainGour2 (*(const uint16_t(*)[])0x00464558) +#define g_Requester_SelectionGour2 (*(const uint16_t(*)[])0x00464590) +#define g_Requester_UnselectionGour1 (*(const uint16_t(*)[])0x004645A8) +#define g_Inv_Item_Stopwatch (*(INVENTORY_ITEM*)0x00464A90) +#define g_Inv_Item_Pistols (*(INVENTORY_ITEM*)0x00464AE0) +#define g_Inv_Item_Flare (*(INVENTORY_ITEM*)0x00464B30) +#define g_Inv_Item_Shotgun (*(INVENTORY_ITEM*)0x00464B80) +#define g_Inv_Item_Magnums (*(INVENTORY_ITEM*)0x00464BD0) +#define g_Inv_Item_Uzis (*(INVENTORY_ITEM*)0x00464C20) +#define g_Inv_Item_Harpoon (*(INVENTORY_ITEM*)0x00464C70) +#define g_Inv_Item_M16 (*(INVENTORY_ITEM*)0x00464CC0) +#define g_Inv_Item_Grenade (*(INVENTORY_ITEM*)0x00464D10) +#define g_Inv_Item_PistolAmmo (*(INVENTORY_ITEM*)0x00464D60) +#define g_Inv_Item_ShotgunAmmo (*(INVENTORY_ITEM*)0x00464DB0) +#define g_Inv_Item_MagnumAmmo (*(INVENTORY_ITEM*)0x00464E00) +#define g_Inv_Item_UziAmmo (*(INVENTORY_ITEM*)0x00464E50) +#define g_Inv_Item_HarpoonAmmo (*(INVENTORY_ITEM*)0x00464EA0) +#define g_Inv_Item_M16Ammo (*(INVENTORY_ITEM*)0x00464EF0) +#define g_Inv_Item_GrenadeAmmo (*(INVENTORY_ITEM*)0x00464F40) +#define g_Inv_Item_SmallMedi (*(INVENTORY_ITEM*)0x00464F90) +#define g_Inv_Item_LargeMedi (*(INVENTORY_ITEM*)0x00464FE0) +#define g_Inv_Item_Pickup1 (*(INVENTORY_ITEM*)0x00465030) +#define g_Inv_Item_Pickup2 (*(INVENTORY_ITEM*)0x00465080) +#define g_Inv_Item_Puzzle1 (*(INVENTORY_ITEM*)0x004650D0) +#define g_Inv_Item_Puzzle2 (*(INVENTORY_ITEM*)0x00465120) +#define g_Inv_Item_Puzzle3 (*(INVENTORY_ITEM*)0x00465170) +#define g_Inv_Item_Puzzle4 (*(INVENTORY_ITEM*)0x004651C0) +#define g_Inv_Item_Key1 (*(INVENTORY_ITEM*)0x00465210) +#define g_Inv_Item_Key2 (*(INVENTORY_ITEM*)0x00465260) +#define g_Inv_Item_Key3 (*(INVENTORY_ITEM*)0x004652B0) +#define g_Inv_Item_Key4 (*(INVENTORY_ITEM*)0x00465300) +#define g_Inv_Item_Passport (*(INVENTORY_ITEM*)0x00465350) +#define g_Inv_Item_Graphics (*(INVENTORY_ITEM*)0x004653A0) +#define g_Inv_Item_Sound (*(INVENTORY_ITEM*)0x004653F0) +#define g_Inv_Item_Controls (*(INVENTORY_ITEM*)0x00465440) +#define g_Inv_Item_Photo (*(INVENTORY_ITEM*)0x00465490) +#define g_Inv_MainObjectsCount (*(int16_t*)0x004654E0) // = 8 +#define g_Inv_MainQtys (*(int16_t(*)[])0x004654E8) +#define g_Inv_MainList (*(INVENTORY_ITEM *(*)[])0x00465518) +#define g_Inv_KeysQtys (*(int16_t(*)[])0x00465578) +#define g_Inv_KeysList (*(INVENTORY_ITEM *(*)[])0x004655A8) +#define g_GymInvOpenEnabled (*(BOOL*)0x00465618) // = TRUE +#define g_LoadGameRequester (*(REQUEST_INFO*)0x00465620) +#define g_SaveGameRequester (*(REQUEST_INFO*)0x00465838) +#define g_Inv_Chosen (*(int16_t*)0x00465A50) // = -1 +#define g_Inv_Mode (*(INVENTORY_MODE*)0x00465A54) // = INV_TITLE_MODE +#define g_OptionSoundVolume (*(int16_t*)0x00465A5C) // = 165 +#define g_OptionMusicVolume (*(int16_t*)0x00465A60) // = 255 +#define g_JumpPermitted (*(int32_t*)0x00465AD4) // = 1 +#define g_LaraOldSlideAngle (*(int16_t*)0x00465AD8) // = 1 +#define g_Weapons (*(WEAPON_INFO(*)[])0x00465AE0) +#define g_LaraControlRoutines (*((void(__cdecl *(*)[71])(ITEM *item, COLL_INFO *coll))0x00465CD0)) +#define g_ExtraControlRoutines (*((void(__cdecl *(*)[11])(ITEM *item, COLL_INFO *coll))0x00465DF0)) +#define g_LaraCollisionRoutines (*((void(__cdecl *(*)[71])(ITEM *item, COLL_INFO *coll))0x00465E20)) +#define g_TextSpacing (*(int8_t(*)[80])0x00466290) +#define g_TextASCIIMap (*(int8_t(*)[])0x004662E0) +#define g_BGND_PaletteIndex (*(int32_t*)0x00466400) // = -1 +#define g_GameClassName (*(const char(*)[])0x00466448) +#define g_GameWindowName (*(const char(*)[])0x00466468) +#define g_GameSizer (*(double*)0x00466480) // = 1.0 +#define g_GameSizerCopy (*(double*)0x00466488) // = 1.0 +#define g_FadeValue (*(int32_t*)0x00466490) // = 0x100000 +#define g_FadeLimit (*(int32_t*)0x00466494) // = 0x100000 +#define g_FadeAdder (*(int32_t*)0x00466498) // = 0x8000 +#define g_ErrorMessages (*(const char *(*)[43])0x004664E8) +#define g_SavedLevels (*(int16_t(*)[24])0x00466B80) +#define g_PaletteIndex (*(int32_t*)0x00466BDC) +#define g_DumpX (*(int16_t*)0x00466BE4) +#define g_DumpY (*(int16_t*)0x00466BE6) +#define g_DumpWidth (*(int16_t*)0x00466BE8) +#define g_DumpHeight (*(int16_t*)0x00466BEA) +#define g_DetailLevel (*(int32_t*)0x0046773C) +#define g_MidSort (*(int32_t*)0x0046C300) // = 0 +#define g_ViewportAspectRatio (*(float*)0x0046C304) // = 0.0f +#define g_XGenY1 (*(int32_t*)0x0046C308) +#define g_XGenY2 (*(int32_t*)0x0046C30C) +#define g_GouraudTable (*(GOURAUD_ENTRY(*)[256])0x0046C310) +#define g_PhdWinTop (*(int32_t*)0x0046E310) +#define g_PhdSprites (*(PHD_SPRITE(*)[512])0x0046E318) +#define g_LsAdder (*(int32_t*)0x00470318) +#define g_FltWinBottom (*(float*)0x0047031C) +#define g_FltResZBuf (*(float*)0x00470320) +#define g_FltResZ (*(float*)0x00470324) +#define g_Output_InsertTransQuad (*(void(__cdecl **)(int32_t x, int32_t y, int32_t width, int32_t height, int32_t z))0x00470328) +#define g_PhdWinHeight (*(int32_t*)0x0047032C) +#define g_PhdWinCenterX (*(int32_t*)0x00470330) +#define g_PhdWinCenterY (*(int32_t*)0x00470334) +#define g_LsYaw (*(int16_t*)0x00470338) +#define g_Output_InsertTrans8 (*(void(__cdecl **)(const PHD_VBUF *vbuf, int16_t shade))0x0047033C) +#define g_FltWinTop (*(float*)0x00470340) +#define g_SortBuffer (*(SORT_ITEM(*)[4000])0x00470348) +#define g_FltWinLeft (*(float*)0x00478048) +#define g_PhdWinMinY (*(int16_t*)0x0047804C) +#define g_PhdFarZ (*(int32_t*)0x00478058) +#define g_FltRhwOPersp (*(float*)0x0047805C) +#define g_PhdWinBottom (*(int32_t*)0x00478060) +#define g_PhdPersp (*(int32_t*)0x00478064) +#define g_PhdWinLeft (*(int32_t*)0x00478068) +#define g_Output_InsertFlatRect (*(void(__cdecl **)(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t z, uint8_t color_idx))0x0047806C) +#define g_Info3DBuffer (*(int16_t(*)[120000])0x00478070) +#define g_PhdWinMaxX (*(int32_t*)0x004B29F0) +#define g_PhdNearZ (*(int32_t*)0x004B29F4) +#define g_FltResZORhw (*(float*)0x004B29F8) +#define g_FltFarZ (*(float*)0x004B29FC) +#define g_FltWinCenterX (*(float*)0x004B2A00) +#define g_FltWinCenterY (*(float*)0x004B2A04) +#define g_PhdScreenHeight (*(int32_t*)0x004B2A08) +#define g_PrintSurfacePtr (*(uint8_t **)0x004B2A0C) +#define g_PhdWinMinX (*(int16_t*)0x004B2A10) +#define g_FltPerspONearZ (*(float*)0x004B2A14) +#define g_FltRhwONearZ (*(float*)0x004B2A18) +#define g_PhdWinMaxY (*(int32_t*)0x004B2A1C) +#define g_Output_InsertSprite (*(void(__cdecl **)(int32_t z, int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t sprite_idx, int16_t shade))0x004B2A20) +#define g_FltNearZ (*(float*)0x004B2A24) +#define g_MatrixPtr (*(MATRIX **)0x004B2A28) +#define g_Output_DrawObjectGT3 (*(const int16_t *(__cdecl **)(const int16_t*, int32_t, SORT_TYPE))0x004B2A2C) +#define g_Output_DrawObjectGT4 (*(const int16_t *(__cdecl **)(const int16_t*, int32_t, SORT_TYPE))0x004B2A30) +#define g_RandomTable (*(int32_t(*)[32])0x004B2A38) +#define g_FltPersp (*(float*)0x004B2AB8) +#define g_W2VMatrix (*(MATRIX*)0x004B2AC0) +#define g_Info3DPtr (*(int16_t **)0x004B2AF0) +#define g_PhdWinWidth (*(int32_t*)0x004B2AF4) +#define g_Output_InsertLine (*(void(__cdecl **)(int32_t, int32_t, int32_t, int32_t, int32_t, uint8_t))0x004B2AF8) +#define g_PhdTextureInfo (*(PHD_TEXTURE(*)[0x800])0x004B2B00) +#define g_TextureInfo (*(PHD_TEXTURE(*)[])0x004B2B00) +#define g_PhdViewDistance (*(int32_t*)0x004BCB00) +#define g_LsPitch (*(int16_t*)0x004BCB04) +#define g_Output_DrawObjectG4 (*(const int16_t *(__cdecl **)(const int16_t*,int32_t, SORT_TYPE))0x004BCB08) +#define g_ShadesTable (*(int16_t(*)[32])0x004BCB10) +#define g_Output_DrawObjectG3 (*(const int16_t *(__cdecl **)(const int16_t*,int32_t, SORT_TYPE))0x004BCB50) +#define g_MatrixStack (*(MATRIX(*)[])0x004BCB58) +#define g_DepthQTable (*(DEPTHQ_ENTRY(*)[32])0x004BD2D8) +#define g_DepthQIndex (*(uint8_t(*)[256])0x004BF2D8) +#define g_PhdScreenWidth (*(int32_t*)0x004BF3D8) +#define g_LsDivider (*(int32_t*)0x004BF3DC) +#define g_PhdVBuf (*(PHD_VBUF(*)[1500])0x004BF3E0) +#define g_XBuffer ((void *)0x004CAF60) +#define g_TexturePageBuffer8 (*(uint8_t *(*)[32])0x004D6AE0) +#define g_FltWinRight (*(float*)0x004D6B60) +#define g_LsVectorView (*(XYZ_32*)0x004D6B68) +#define g_WibbleTable (*(float(*)[32])0x004D6B78) +#define g_PhdWinRight (*(int32_t*)0x004D6BF8) +#define g_SurfaceCount (*(int32_t*)0x004D6BFC) +#define g_Sort3DPtr (*(SORT_ITEM **)0x004D6C00) +#define g_WibbleOffset (*(int32_t*)0x004D6C0C) +#define g_IsWibbleEffect (*(int32_t*)0x004D6C10) +#define g_IsWaterEffect (*(int32_t*)0x004D6C14) +#define g_VBuffer (*(VERTEX_INFO(*)[20])0x004D6CD8) +#define g_IsShadeEffect (*(int8_t*)0x004D6F78) +#define g_VBufferD3D (*(D3DTLVERTEX(*)[32])0x004D6F80) +#define g_GamePalette16 (*(PALETTEENTRY(*)[256])0x004D7380) +#define g_CineFrameCurrent (*(int32_t*)0x004D7780) +#define g_CineTickCount (*(int32_t*)0x004D7784) +#define g_OriginalRoom (*(int32_t*)0x004D7788) +#define g_IsChunkyCamera (*(int32_t*)0x004D778C) +#define g_HeightType (*(int32_t*)0x004D7790) +#define g_NoInputCounter (*(int32_t*)0x004D7794) +#define g_FlipTimer (*(int32_t*)0x004D779C) +#define g_LOSNumRooms (*(int32_t*)0x004D77A0) // = 0 +#define g_StopInventory (*(BOOL*)0x004D77A4) +#define g_IsDemoLevelType (*(BOOL*)0x004D77AC) +#define g_IsDemoLoaded (*(BOOL*)0x004D77B0) +#define g_BoundStart (*(int32_t*)0x004D77C0) +#define g_BoundEnd (*(int32_t*)0x004D77C4) +#define g_IsAssaultTimerDisplay (*(int32_t*)0x004D77E0) +#define g_IsAssaultTimerActive (*(BOOL*)0x004D77E4) +#define g_IsMonkAngry (*(BOOL*)0x004D77E8) +#define g_GF_ScriptVersion (*(int32_t*)0x004D77EC) +#define g_GF_LaraStartAnim (*(int32_t*)0x004D77F0) +#define g_GF_SunsetEnabled (*(int16_t*)0x004D77F4) +#define g_GF_DeadlyWater (*(int16_t*)0x004D77F8) +#define g_GF_NoFloor (*(int16_t*)0x004D77FC) +#define g_GF_RemoveWeapons (*(int16_t*)0x004D7800) +#define g_GF_RemoveAmmo (*(int16_t*)0x004D7804) +#define g_GF_Kill2Complete (*(char*)0x004D7808) +#define g_GF_StartGame (*(int8_t*)0x004D780C) +#define g_GF_Description (*(char(*)[256])0x004D7818) +#define g_AmmoTextInfo (*(TEXTSTRING **)0x004D792C) +#define g_DisplayModeTextInfo (*(TEXTSTRING **)0x004D7930) +#define g_DisplayModeInfoTimer (*(DWORD*)0x004D7934) +#define g_Inv_MainCurrent (*(UINT16*)0x004D7938) +#define g_Inv_KeyObjectsCount (*(UINT16*)0x004D793C) +#define g_Inv_KeysCurrent (*(UINT16*)0x004D7940) +#define g_Inv_OptionCurrent (*(UINT16*)0x004D7944) +#define g_Inv_ItemText (*(TEXTSTRING *(*)[3])0x004D7948) +#define g_Inv_LevelText (*(TEXTSTRING **)0x004D7950) +#define g_Inv_RingText (*(TEXTSTRING**)0x004D7954) +#define g_Inv_TagText (*(TEXTSTRING **)0x004D7958) +#define g_Inv_UpArrow1 (*(TEXTSTRING**)0x004D795C) +#define g_Inv_UpArrow2 (*(TEXTSTRING**)0x004D7960) +#define g_Inv_DownArrow1 (*(TEXTSTRING**)0x004D7964) +#define g_Inv_DownArrow2 (*(TEXTSTRING**)0x004D7968) +#define g_InputDB (*(uint32_t*)0x004D796C) +#define g_OldInputDB (*(int32_t*)0x004D7970) +#define g_Inv_IsActive (*(uint16_t*)0x004D7978) +#define g_Inv_ExtraData (*(int32_t(*)[8])0x004D7980) +#define g_Inv_DemoMode (*(BOOL*)0x004D79A0) +#define g_Inv_IsOptionsDelay (*(BOOL*)0x004D79B4) +#define g_Inv_OptionsDelayCounter (*(int32_t*)0x004D79B8) +#define g_SoundOptionLine (*(uint16_t*)0x004D79BC) +#define g_StatsRequester (*(REQUEST_INFO*)0x004D79C0) +#define g_Assault (*(ASSAULT_STATS*)0x004D7BD8) +#define g_LevelItemCount (*(int32_t*)0x004D7C38) +#define g_HealthBarTimer (*(int32_t*)0x004D7C3C) +#define g_SlotsUsed (*(int32_t*)0x004D7C50) +#define g_NumCameras (*(int32_t*)0x004D7C74) +#define g_SoundTrackIds (*(int32_t(*)[128])0x004D7C80) +#define g_BGND_PictureIsReady (*(bool*)0x004D7E88) +#define g_BGND_TexturePageIndexes (*(int32_t(*)[5])0x004D7E90) +#define g_BGND_PageHandles (*(HWR_TEXTURE_HANDLE(*)[5])0x004D7EA8) +#define g_D3DDev (*(LPDIRECT3DDEVICE2*)0x004D7EBC) +#define g_D3D (*(LPDIRECT3D2*)0x004D7EC0) +#define g_D3DView (*(LPDIRECT3DVIEWPORT2*)0x004D7EC4) +#define g_D3DMaterial (*(LPDIRECT3DMATERIAL2*)0x004D7EC8) +#define g_MinWindowClientHeight (*(int32_t*)0x004D7ED0) +#define g_DDrawInterface (*(LPDIRECTDRAW*)0x004D7ED4) +#define g_IsGameWindowChanging (*(bool*)0x004D7ED8) +#define g_MaxWindowHeight (*(int32_t*)0x004D7EDC) +#define g_DDraw (*(LPDIRECTDRAW3*)0x004D7EE0) +#define g_IsGameWindowCreated (*(bool*)0x004D7EE4) +#define g_IsGameWindowUpdating (*(bool*)0x004D7EE8) +#define g_IsDDrawGameWindowShow (*(bool*)0x004D7EEC) +#define g_MinWindowClientWidth (*(int32_t*)0x004D7EF0) +#define g_IsGameWindowShow (*(bool*)0x004D7EF4) +#define g_IsMinWindowSizeSet (*(bool*)0x004D7EF8) +#define g_MaxWindowClientWidth (*(int32_t*)0x004D7EFC) +#define g_GameWindowWidth (*(int32_t*)0x004D7F00) +#define g_IsMinMaxInfoSpecial (*(bool*)0x004D7F04) +#define g_IsGameFullScreen (*(bool*)0x004D7F08) +#define g_IsGameWindowMaximized (*(bool*)0x004D7F0C) +#define g_GameWindowHandle (*(HWND*)0x004D7F10) +#define g_GameWindowHeight (*(int32_t*)0x004D7F14) +#define g_PrimaryDisplayAdapter (*(DISPLAY_ADAPTER_NODE**)0x004D7F18) +#define g_CurrentDisplayAdapter (*(DISPLAY_ADAPTER*)0x004D7F20) +#define g_LockedBufferCount (*(uint32_t*)0x004D8338) +#define g_GameWindowPositionX (*(int32_t*)0x004D833C) +#define g_GameWindowPositionY (*(int32_t*)0x004D8340) +#define g_DisplayAdapterList (*(DISPLAY_ADAPTER_LIST*)0x004D8348) +#define g_MaxWindowClientHeight (*(int32_t*)0x004D8354) +#define g_IsMessageLoopClosed (*(bool*)0x004D8358) +#define g_MaxWindowWidth (*(int32_t*)0x004D835C) +#define g_IsMaxWindowSizeSet (*(bool*)0x004D8360) +#define g_AppResultCode (*(uint32_t*)0x004D8364) +#define g_FullScreenWidth (*(int32_t*)0x004D8368) +#define g_FullScreenHeight (*(int32_t*)0x004D836C) +#define g_FullScreenBPP (*(int32_t*)0x004D8370) +#define g_FullScreenVGA (*(int32_t*)0x004D8374) +#define g_IsGameToExit (*(uint8_t*)0x004D8378) +#define g_GameWindowY (*(int32_t*)0x004D837C) +#define g_GameWindowX (*(int32_t*)0x004D8380) +#define g_IsGameWindowMinimized (*(bool*)0x004D8384) +#define g_MinWindowWidth (*(int32_t*)0x004D8388) +#define g_MinWindowHeight (*(int32_t*)0x004D838C) +#define g_IsGameWindowActive (*(bool*)0x004D8390) +#define g_MessageLoopCounter (*(int32_t*)0x004D8394) +#define g_DInput (*(LPDIRECTINPUT*)0x004D855C) +#define IDID_SysKeyboard (*(LPDIRECTINPUTDEVICE*)0x004D8560) +#define g_ScreenSizer (*(int32_t*)0x004D8568) +#define g_IsVidSizeLock (*(int32_t*)0x004D856C) +#define g_SampleFreqs (*(DWORD(*)[256])0x004D8570) +#define g_SoundAdapterList (*(SOUND_ADAPTER_LIST*)0x004D8970) +#define g_SampleBuffers (*(LPDIRECTSOUNDBUFFER(*)[256])0x004D8980) +#define g_IsSoundEnabled (*(uint8_t*)0x004D8D80) +#define g_DSound (*(LPDIRECTSOUND*)0x004D8D84) +#define g_ChannelSamples (*(int32_t(*)[32])0x004D8D88) +#define g_ChannelBuffers (*(LPDIRECTSOUNDBUFFER(*)[32])0x004D8E08) +#define g_CurrentSoundAdapter (*(SOUND_ADAPTER*)0x004D8E8C) +#define g_PrimarySoundAdapter (*(SOUND_ADAPTER_NODE **)0x004D8EAC) +#define g_RenderBufferSurface (*(LPDDS*)0x004D8EB0) +#define g_DDrawClipper (*(LPDIRECTDRAWCLIPPER*)0x004D8EB4) +#define g_WinVid_Palette (*(PALETTEENTRY(*)[256])0x004D8EB8) +#define g_ThirdBufferSurface (*(LPDDS*)0x004D92B8) +#define g_PictureBufferSurface (*(LPDDS*)0x004D92BC) +#define g_ZBufferSurface (*(LPDDS*)0x004D92C0) +#define g_DDrawPalette (*(LPDIRECTDRAWPALETTE*)0x004D92C4) +#define g_PrimaryBufferSurface (*(LPDDS*)0x004D92C8) +#define g_ColorBitMasks (*(COLOR_BIT_MASKS*)0x004D92E8) +#define g_GameVid_BufRect (*(RECT*)0x004D9318) +#define g_GameVid_Rect (*(RECT*)0x004D9328) +#define g_GameVid_Width (*(int32_t*)0x004D9338) +#define g_GameVid_Height (*(int32_t*)0x004D933C) +#define g_GameVid_BPP (*(int32_t*)0x004D9340) +#define g_GameVid_BufWidth (*(int32_t*)0x004D9344) +#define g_GameVid_BufHeight (*(int32_t*)0x004D9348) +#define g_UVAdd (*(int32_t*)0x004D934C) +#define g_GameVid_IsVga (*(bool*)0x004D9350) +#define g_GameVid_IsWindowedVGA (*(int8_t*)0x004D9351) +#define g_GameVid_IsFullscreenVGA (*(bool*)0x004D9352) +#define g_IsWindowedVGA (*(bool*)0x004D9353) +#define g_Is16bitTextures (*(bool*)0x004D9354) +#define g_NeedToReloadTextures (*(bool*)0x004D9355) +#define g_BackBufferSurface (*(LPDDS*)0x004D9358) +#define g_TexturePageCount (*(int32_t*)0x004D9360) +#define g_LabTextureUVFlag (*(uint8_t(*)[2048])0x004D93F0) +#define g_LevelFilePalettesOffset (*(int32_t*)0x004D9BF4) +#define g_LevelFileTexPagesOffset (*(int32_t*)0x004D9BF8) +#define g_GF_LevelOffsets (*(int16_t(*)[200])0x004D9C00) +#define g_MeshBase (*(int16_t **)0x004D9D90) +#define g_FloorData (*(int16_t **)0x004D9D94) +#define g_LevelFileName (*(char(*)[256])0x004D9D98) +#define g_TextureInfoCount (*(int32_t*)0x004D9E98) +#define g_LevelFileDepthQOffset (*(int32_t*)0x004D9E9C) +#define g_IsFMVPlaying (*(int32_t*)0x004D9EAC) +#define g_SavedGames (*(int32_t*)0x004D9EBC) +#define g_CurrentLevel (*(int32_t*)0x004D9EC0) +#define g_LevelComplete (*(int32_t*)0x004D9EC4) +#define g_SaveCounter (*(int32_t*)0x004D9EC8) +#define g_GameMode (*(int32_t*)0x004D9ECC) +#define g_HWR_VertexBuffer (*(D3DTLVERTEX(*)[0x2000])0x004D9ED8) +#define g_CurrentTexSource (*(D3DTEXTUREHANDLE*)0x00519ED8) +#define g_HWR_PageHandles (*(HWR_TEXTURE_HANDLE(*)[32])0x00519EE0) +#define g_HWR_VertexPtr (*(D3DTLVERTEX **)0x00519F60) +#define g_ZEnableState (*(bool*)0x00519F64) +#define g_AlphaBlendEnabler (*(D3DRENDERSTATETYPE*)0x00519F68) +#define g_ColorKeyState (*(bool*)0x00519F6C) +#define g_ZWriteEnableState (*(bool*)0x00519F70) +#define g_HWR_TexturePageIndexes (*(int32_t(*)[32])0x00519F78) +#define g_GameMemoryPtr (*(void **)0x0051A0CC) +#define g_DIKeys (*(uint8_t(*)[256])0x0051A108) +#define g_Input (*(int32_t*)0x0051A208) +#define g_IsVidModeLock (*(int8_t*)0x0051A20C) +#define g_JoyKeys (*(int32_t*)0x0051A210) +#define g_GameModule (*(HINSTANCE*)0x0051A238) +#define g_CmdLine (*(char **)0x0051A23C) +#define g_ScreenshotCounter (*(int32_t*)0x0051A240) +#define g_PasswordText1 (*(TEXTSTRING **)0x0051A2CC) +#define g_PassportMode (*(int32_t*)0x0051A2D0) +#define g_DetailText (*(TEXTSTRING *(*)[5])0x0051A2D8) +#define g_SoundText (*(TEXTSTRING *(*)[4])0x0051A2F0) +#define g_WaterPalette (*(RGB_888(*)[256])0x0051B308) +#define g_PhdWinRect (*(RECT*)0x0051B918) +#define g_HiRes (*(int32_t*)0x0051B928) +#define g_AnimTextureRanges (*(int16_t **)0x0051B92C) +#define g_GamePalette8 (*(RGB_888(*)[256])0x0051B930) +#define g_WinVidNeedToResetBuffers (*(bool*)0x0051BC30) +#define g_IsWet (*(int32_t*)0x0051BC38) +#define g_SavedAppSettings (*(APP_SETTINGS*)0x0051BCC0) +#define g_ErrorMessage (*(char(*)[128])0x0051BD20) +#define g_IsTitleLoaded (*(BOOL*)0x0051BDA0) +#define g_MasterVolume (*(int32_t*)0x0051BDA8) +#define g_MciDeviceID (*(MCIDEVICEID*)0x0051BDAC) +#define g_CD_LoopTrack (*(int32_t*)0x0051BDB0) +#define g_TextureFormat (*(TEXTURE_FORMAT*)0x0051C1B8) +#define g_TexturesAlphaChannel (*(bool*)0x0051C20C) +#define g_TextstringCount (*(int16_t*)0x0051D6A0) // = 0 +#define g_TextstringBuffers (*(char(*)[64][64])0x0051D6C0) +#define g_NumSampleInfos (*(int32_t*)0x0051E6C0) +#define g_SoundIsActive (*(int32_t*)0x0051E6C4) +#define g_SampleLUT (*(int16_t(*)[])0x0051E6E0) +#define g_SampleInfos (*(SAMPLE_INFO **)0x0051E9C4) +#define g_SaveGame (*(SAVEGAME_INFO*)0x0051E9E0) +#define g_FinalBossActive (*(int16_t*)0x005206A8) +#define g_FinalBossItem (*(int16_t(*)[5])0x005206B0) +#define g_FinalLevelCount (*(int16_t*)0x005206BA) +#define g_FinalBossCount (*(int16_t*)0x005206BC) +#define g_BaddieSlots (*(CREATURE **)0x005206C0) +#define g_Lara (*(LARA_INFO*)0x005206E0) +#define g_LaraItem (*(ITEM **)0x005207BC) +#define g_Effects (*(FX **)0x005207C0) +#define g_NextEffectFree (*(int16_t*)0x005207C4) +#define g_NextItemFree (*(int16_t*)0x005207C6) +#define g_NextItemActive (*(int16_t*)0x005207C8) +#define g_NextEffectActive (*(int16_t*)0x005207CA) +#define g_PrevItemActive (*(int16_t*)0x005207CC) +#define g_ValidLevelStrings1 (*(char(*)[])0x005207E0) +#define g_RequesterFlags2 (*(uint32_t(*)[24])0x00520CA0) +#define g_RequesterFlags1 (*(uint32_t(*)[24])0x00520D00) +#define g_InvColors (*(uint16_t(*)[17])0x005216E0) +#define g_ValidLevelStrings2 (*(char(*)[])0x00521720) +#define g_SaveGameReqFlags2 (*(uint32_t(*)[24])0x00521BE0) +#define g_SaveGameReqFlags1 (*(uint32_t(*)[24])0x00521C40) +#define g_Pickups (*(PICKUP_INFO(*)[12])0x00521CA0) +#define g_GF_Puzzle1Strings (*(char ***)0x00521DC0) +#define g_GF_CutsceneFileNames (*(char ***)0x00521DC4) +#define g_GameFlow (*(GAME_FLOW*)0x00521DE0) +#define g_GF_Puzzle4Strings (*(char ***)0x00521E60) +#define g_GF_Pickup1StringsBuf (*(char **)0x00521E64) +#define g_GF_FMVFilenamesBuf (*(char **)0x00521E68) +#define g_GF_Key1StringsBuf (*(char **)0x00521E6C) +#define g_GF_FrontendSequence (*(int16_t **)0x00521E70) +#define g_GF_Key2Strings (*(char ***)0x00521E74) +#define g_GF_CutsceneFileNamesBuf (*(char **)0x00521E78) +#define g_GF_Key4StringsBuf (*(char **)0x00521E7C) +#define g_GF_SequenceBuf (*(int16_t **)0x00521E80) +#define g_GF_Key2StringsBuf (*(char **)0x00521E84) +#define g_GF_Pickup2StringsBuf (*(char **)0x00521E88) +#define g_GF_PicFilenamesBuf (*(char **)0x00521E8C) +#define g_GF_Key4Strings (*(char ***)0x00521E90) +#define g_GF_Pickup1Strings (*(char ***)0x00521E94) +#define g_GF_Puzzle2Strings (*(char ***)0x00521E98) +#define g_GF_LevelFileNamesBuf (*(char **)0x00521E9C) +#define g_GF_PicFilenames (*(char ***)0x00521EA0) +#define g_GF_Key1Strings (*(char ***)0x00521EA4) +#define g_GF_Puzzle1StringsBuf (*(char **)0x00521EA8) +#define g_GF_LevelNamesBuf (*(char **)0x00521EAC) +#define g_GF_GameStrings (*(char ***)0x00521EB0) +#define g_GF_PCStringsBuf (*(char **)0x00521EB4) +#define g_GF_GameStringsBuf (*(char **)0x00521EB8) +#define g_GF_Key3Strings (*(char ***)0x00521EBC) +#define g_GF_Puzzle3Strings (*(char ***)0x00521EC0) +#define g_GF_LevelNames (*(char ***)0x00521EC4) +#define g_GF_ScriptTable (*(int16_t *(*)[24])0x00521EE0) +#define g_GF_Puzzle2StringsBuf (*(char **)0x00521F40) +#define g_GF_Pickup2Strings (*(char ***)0x00521F44) +#define g_GF_TitleFileNames (*(char ***)0x00521F48) +#define g_GF_TitleFileNamesBuf (*(char **)0x00521F4C) +#define g_GF_PCStrings (*(char ***)0x00521F50) +#define g_GF_LevelFileNames (*(char ***)0x00521F54) +#define g_GF_ValidDemos (*(int16_t(*)[24])0x00521F60) +#define g_GF_Puzzle4StringsBuf (*(char **)0x00521F90) +#define g_GF_FMVFilenames (*(char ***)0x00521F94) +#define g_GF_Puzzle3StringsBuf (*(char **)0x00521F98) +#define g_GF_Key3StringsBuf (*(char **)0x00521F9C) +#define g_GF_SecretInvItems (*(char(*)[27])0x00521FA0) +#define g_GF_Add2InvItems (*(char(*)[27])0x00521FC0) +#define g_SoundEffectCount (*(int32_t*)0x00521FDC) +#define g_SoundEffects (*(OBJECT_VECTOR **)0x00521FE0) +#define g_Objects (*(OBJECT(*)[265])0x00522000) +#define g_AnimFrames (*(FRAME_INFO **)0x005251B0) +#define g_Meshes (*(int16_t ***)0x005252B0) +#define g_Outside (*(int32_t*)0x005252B4) +#define g_DrawRoomsCount (*(int32_t*)0x005252B8) +#define g_IMMatrixStack (*(MATRIX(*)[256])0x005252C0) +#define g_DoorVBuf (*(DOOR_VBUF(*)[4])0x005258C0) +#define g_IMFrac (*(int32_t*)0x005258F0) +#define g_Anims (*(ANIM **)0x005258F4) +#define g_BoundRooms (*(int32_t(*)[128])0x00525900) +#define g_OutsideBottom (*(int32_t*)0x00525B00) +#define g_AnimRanges (*(ANIM_RANGE **)0x00525B04) +#define g_AnimCommands (*(int16_t **)0x00525B08) +#define g_DrawRoomsArray (*(int16_t(*)[100])0x00525B20) +#define g_AnimBones (*(int32_t **)0x00525BE8) +#define g_DynamicLightCount (*(int32_t*)0x00525BEC) +#define g_StaticObjects (*(STATIC_INFO(*)[50])0x00525C00) +#define g_OutsideLeft (*(int32_t*)0x00526178) +#define g_AnimChanges (*(ANIM_CHANGE **)0x0052617C) +#define g_RoomCount (*(int32_t*)0x00526180) +#define g_IMRate (*(int32_t*)0x00526184) +#define g_IMMatrixPtr (*(MATRIX **)0x00526188) +#define g_Rooms (*(ROOM **)0x0052618C) +#define g_CameraUnderwater (*(BOOL*)0x00526190) +#define g_OutsideRight (*(int32_t*)0x00526198) +#define g_OutsideTop (*(int32_t*)0x005261AC) +#define g_DemoPtr (*(uint32_t **)0x005261B0) +#define g_DemoCount (*(int32_t*)0x005261B4) +#define g_MusicTrackFlags (*(uint16_t(*)[64])0x005261C0) +#define g_FlipStatus (*(int32_t*)0x00526240) +#define g_FlipMaps (*(int32_t(*)[10])0x00526260) +#define g_TriggerIndex (*(int16_t **)0x00526288) +#define g_LOSRooms (*(int32_t(*)[20])0x005262A0) +#define g_Items (*(ITEM **)0x005262F0) +#define g_CineLoaded (*(int16_t*)0x005262F4) +#define g_NumCineFrames (*(int16_t*)0x005262F6) +#define g_CineData (*(CINE_FRAME **)0x005262F8) // = NULL +#define g_CinePos (*(PHD_3DPOS*)0x00526300) +#define g_CineFrameIdx (*(int16_t*)0x00526314) +#define g_Camera (*(CAMERA_INFO*)0x00526320) +#define g_GroundZone (*(int16_t *(*)[][2])0x005263A0) +#define g_FlyZone (*(int16_t *(*)[2])0x005263C0) +#define g_Overlap (*(uint16_t **)0x005263C8) +#define g_Boxes (*(BOX_INFO **)0x005263CC) +#define g_BoxCount (*(int32_t*)0x005263D0) + +// clang-format on diff --git a/src/tr2/inject_exec.c b/src/tr2/inject_exec.c new file mode 100644 index 000000000..94045194b --- /dev/null +++ b/src/tr2/inject_exec.c @@ -0,0 +1,1053 @@ +#include "inject_exec.h" + +#include "decomp/decomp.h" +#include "decomp/effects.h" +#include "decomp/stats.h" +#include "game/background.h" +#include "game/box.h" +#include "game/camera.h" +#include "game/collide.h" +#include "game/creature.h" +#include "game/demo.h" +#include "game/effects.h" +#include "game/game.h" +#include "game/gameflow.h" +#include "game/gun/gun.h" +#include "game/gun/gun_misc.h" +#include "game/gun/gun_pistols.h" +#include "game/gun/gun_rifle.h" +#include "game/hwr.h" +#include "game/input.h" +#include "game/inventory/backpack.h" +#include "game/inventory/common.h" +#include "game/inventory/ring.h" +#include "game/items.h" +#include "game/lara/cheat.h" +#include "game/lara/col.h" +#include "game/lara/control.h" +#include "game/lara/draw.h" +#include "game/lara/look.h" +#include "game/lara/misc.h" +#include "game/lara/state.h" +#include "game/level.h" +#include "game/los.h" +#include "game/lot.h" +#include "game/math.h" +#include "game/math_misc.h" +#include "game/matrix.h" +#include "game/music.h" +#include "game/objects/common.h" +#include "game/objects/creatures/bird.h" +#include "game/objects/creatures/diver.h" +#include "game/objects/effects/ember.h" +#include "game/objects/effects/flame.h" +#include "game/objects/general/body_part.h" +#include "game/objects/general/door.h" +#include "game/objects/general/final_level_counter.h" +#include "game/objects/traps/ember_emitter.h" +#include "game/objects/traps/flame_emitter.h" +#include "game/objects/vehicles/boat.h" +#include "game/option/option.h" +#include "game/output.h" +#include "game/overlay.h" +#include "game/random.h" +#include "game/requester.h" +#include "game/room.h" +#include "game/room_draw.h" +#include "game/shell.h" +#include "game/sound.h" +#include "game/text.h" +#include "inject_util.h" +#include "specific/s_audio_sample.h" +#include "specific/s_flagged_string.h" +#include "specific/s_input.h" + +static void M_DecompGeneral(const bool enable); +static void M_DecompStats(const bool enable); +static void M_DecompEffects(const bool enable); +static void M_HWR(bool enable); + +static void M_Camera(bool enable); +static void M_Collide(bool enable); +static void M_Room(bool enable); +static void M_Math(bool enable); +static void M_Matrix(bool enable); +static void M_Shell(bool enable); +static void M_Requester(bool enable); +static void M_Option(bool enable); +static void M_Text(bool enable); +static void M_Input(bool enable); +static void M_Output(bool enable); +static void M_Music(bool enable); +static void M_Sound(bool enable); + +static void M_Demo(bool enable); +static void M_Gameflow(bool enable); +static void M_Overlay(bool enable); +static void M_Random(bool enable); +static void M_Items(bool enable); +static void M_Effects(bool enable); +static void M_LOS(bool enable); +static void M_People(bool enable); +static void M_Level(bool enable); +static void M_Inventory(bool enable); +static void M_Lara_Look(bool enable); +static void M_Lara_Draw(bool enable); +static void M_Lara_Misc(bool enable); +static void M_Lara_State(bool enable); +static void M_Lara_Col(bool enable); +static void M_Gun(bool enable); + +static void M_Creature(bool enable); +static void M_Box(bool enable); +static void M_Lot(bool enable); +static void M_Objects(bool enable); + +static void M_S_Audio_Sample(bool enable); +static void M_S_Input(bool enable); +static void M_S_FlaggedString(bool enable); + +static void M_DecompGeneral(const bool enable) +{ + INJECT(enable, 0x00411F50, Game_SetCutsceneTrack); + INJECT(enable, 0x00411F60, Game_Cutscene_Start); + INJECT(enable, 0x00412080, Misc_InitCinematicRooms); + INJECT(enable, 0x00412120, Game_Cutscene_Control); + INJECT(enable, 0x004123D0, Room_FindByPos); + INJECT(enable, 0x00412450, CutscenePlayer_Control); + INJECT(enable, 0x00412530, Lara_Control_Cutscene); + INJECT(enable, 0x004125D0, CutscenePlayer1_Initialise); + INJECT(enable, 0x00412660, CutscenePlayerGen_Initialise); + INJECT(enable, 0x0043A280, Level_Initialise); + INJECT(enable, 0x00444D60, WinVidSetMinWindowSize); + INJECT(enable, 0x00444DB0, WinVidClearMinWindowSize); + INJECT(enable, 0x00444DC0, WinVidSetMaxWindowSize); + INJECT(enable, 0x00444E10, WinVidClearMaxWindowSize); + INJECT(enable, 0x00444E20, CalculateWindowWidth); + INJECT(enable, 0x00444E70, CalculateWindowHeight); + INJECT(enable, 0x00444EA0, WinVidGetMinMaxInfo); + INJECT(enable, 0x00444FB0, WinVidFindGameWindow); + INJECT(enable, 0x00444FD0, WinVidSpinMessageLoop); + INJECT(enable, 0x004450C0, WinVidShowGameWindow); + INJECT(enable, 0x00445110, WinVidHideGameWindow); + INJECT(enable, 0x00445150, WinVidSetGameWindowSize); + INJECT(enable, 0x00445190, ShowDDrawGameWindow); + INJECT(enable, 0x00445240, HideDDrawGameWindow); + INJECT(enable, 0x004452D0, DDrawSurfaceCreate); + INJECT(enable, 0x00445320, DDrawSurfaceRestoreLost); + INJECT(enable, 0x00445370, WinVidClearBuffer); + INJECT(enable, 0x004453C0, WinVidBufferLock); + INJECT(enable, 0x00445400, WinVidBufferUnlock); + INJECT(enable, 0x00445430, WinVidCopyBitmapToBuffer); + INJECT(enable, 0x004454C0, GetRenderBitDepth); + INJECT(enable, 0x00445550, WinVidGetColorBitMasks); + INJECT(enable, 0x004455D0, BitMaskGetNumberOfBits); + INJECT(enable, 0x00445620, CalculateCompatibleColor); + INJECT(enable, 0x00445690, WinVidGetDisplayMode); + INJECT(enable, 0x00445720, WinVidGoFullScreen); + INJECT(enable, 0x004457B0, WinVidGoWindowed); + INJECT(enable, 0x004458C0, WinVidSetDisplayAdapter); + INJECT(enable, 0x004471F0, DInputCreate); + INJECT(enable, 0x00447220, DInputRelease); + INJECT(enable, 0x00447240, WinInReadKeyboard); + INJECT(enable, 0x00448430, CreateScreenBuffers); + INJECT(enable, 0x00448570, CreatePrimarySurface); + INJECT(enable, 0x00448610, CreateBackBuffer); + INJECT(enable, 0x004486B0, CreateClipper); + INJECT(enable, 0x00448750, CreateWindowPalette); + INJECT(enable, 0x00448830, CreateZBuffer); + INJECT(enable, 0x004488F0, GetZBufferDepth); + INJECT(enable, 0x00448920, CreateRenderBuffer); + INJECT(enable, 0x004489D0, CreatePictureBuffer); + INJECT(enable, 0x00448A40, ClearBuffers); + INJECT(enable, 0x00448BF0, RestoreLostBuffers); + INJECT(enable, 0x00448D30, UpdateFrame); + INJECT(enable, 0x00448E00, WaitPrimaryBufferFlip); + INJECT(enable, 0x00448E40, RenderInit); + INJECT(enable, 0x00448E50, RenderStart); + INJECT(enable, 0x00449200, RenderFinish); + INJECT(enable, 0x004492F0, ApplySettings); + INJECT(enable, 0x00449500, FmvBackToGame); + INJECT(enable, 0x00449610, GameApplySettings); + INJECT(enable, 0x00449850, UpdateGameResolution); + INJECT(enable, 0x004498C0, DecodeErrorMessage); + INJECT(enable, 0x0044E4E0, RenderErrorBox); + INJECT(enable, 0x0044E520, WinMain); + INJECT(enable, 0x0044E700, GameInit); + INJECT(enable, 0x0044E7A0, WinGameStart); + INJECT(enable, 0x0044E820, Shell_Shutdown); + INJECT(enable, 0x0044E8E0, ScreenshotPCX); + INJECT(enable, 0x0044E9F0, CompPCX); + INJECT(enable, 0x0044EAA0, EncodeLinePCX); + INJECT(enable, 0x0044EB80, EncodePutPCX); + INJECT(enable, 0x0044EBC0, Screenshot); + INJECT(enable, 0x00454C50, TitleSequence); + INJECT(enable, 0x00444540, Enumerate3DDevices); + INJECT(enable, 0x00444570, D3DCreate); + INJECT(enable, 0x004445B0, Enum3DDevicesCallback); + INJECT(enable, 0x00444670, D3DIsSupported); + INJECT(enable, 0x004446B0, D3DSetViewport); + INJECT(enable, 0x00444770, D3DDeviceCreate); + INJECT(enable, 0x00444930, Direct3DRelease); + INJECT(enable, 0x00444980, Direct3DInit); + INJECT(enable, 0x00444BD0, DDrawCreate); + INJECT(enable, 0x00444C30, DDrawRelease); + INJECT(enable, 0x00444C70, GameWindowCalculateSizeFromClient); + INJECT(enable, 0x00444CF0, GameWindowCalculateSizeFromClientByZero); + INJECT(enable, 0x004459A0, CompareVideoModes); + INJECT(enable, 0x004459F0, WinVidGetDisplayModes); + INJECT(enable, 0x00445A50, EnumDisplayModesCallback); + INJECT(enable, 0x00445E10, WinVidInit); + INJECT(enable, 0x00445E50, WinVidGetDisplayAdapters); + INJECT(enable, 0x00445F20, EnumerateDisplayAdapters); + INJECT(enable, 0x00446140, WinVidRegisterGameWindowClass); + INJECT(enable, 0x004461B0, WinVidGameWindowProc); + INJECT(enable, 0x00445F40, EnumDisplayAdaptersCallback); + INJECT(enable, 0x004467C0, WinVidResizeGameWindow); + INJECT(enable, 0x004469A0, WinVidCheckGameWindowPalette); + INJECT(enable, 0x00446A60, WinVidCreateGameWindow); + INJECT(enable, 0x00446B30, WinVidFreeWindow); + INJECT(enable, 0x00446B60, WinVidExitMessage); + INJECT(enable, 0x00446BB0, WinVidGetDisplayAdapter); + INJECT(enable, 0x00446C00, WinVidStart); + INJECT(enable, 0x00446F80, WinVidFinish); + INJECT(enable, 0x00414220, Misc_Move3DPosTo3DPos); + INJECT(enable, 0x00455140, S_LoadSettings); + INJECT(enable, 0x004550C0, S_SaveSettings); +} + +static void M_DecompStats(const bool enable) +{ + INJECT(enable, 0x004262B0, AddAssaultTime); + INJECT(enable, 0x00426340, ShowGymStatsText); + INJECT(enable, 0x00426520, ShowStatsText); + INJECT(enable, 0x004268C0, ShowEndStatsText); + INJECT(enable, 0x0044C680, LevelStats); + INJECT(enable, 0x0044C850, GameStats); +} + +static void M_DecompEffects(const bool enable) +{ + INJECT(enable, 0x00433360, Effect_ExplodingDeath); +} + +static void M_HWR(bool enable) +{ + INJECT(enable, 0x0044CFE0, HWR_InitState); + INJECT(enable, 0x0044D110, HWR_ResetTexSource); + INJECT(enable, 0x0044D140, HWR_ResetColorKey); + INJECT(enable, 0x0044D170, HWR_ResetZBuffer); + INJECT(enable, 0x0044D1D0, HWR_TexSource); + INJECT(enable, 0x0044D200, HWR_EnableColorKey); + INJECT(enable, 0x0044D250, HWR_EnableZBuffer); + INJECT(enable, 0x0044D2E0, HWR_BeginScene); + INJECT(enable, 0x0044D310, HWR_DrawPolyList); + INJECT(enable, 0x0044D490, HWR_LoadTexturePages); + INJECT(enable, 0x0044D520, HWR_FreeTexturePages); + INJECT(enable, 0x0044D570, HWR_GetPageHandles); + INJECT(enable, 0x0044D5B0, HWR_VertexBufferFull); + INJECT(enable, 0x0044D5E0, HWR_Init); +} + +static void M_Background(const bool enable) +{ + INJECT(enable, 0x00443990, BGND_Make640x480); + INJECT(enable, 0x00443B50, BGND_AddTexture); + INJECT(enable, 0x00443C10, BGND_GetPageHandles); + INJECT(enable, 0x00443C50, BGND_DrawInGameBlack); + INJECT(enable, 0x00443CB0, DrawQuad); + INJECT(enable, 0x00443D90, BGND_DrawInGameBackground); + INJECT(enable, 0x00443FB0, DrawTextureTile); + INJECT(enable, 0x00444210, BGND_CenterLighting); + INJECT(enable, 0x004444C0, BGND_Free); + INJECT(enable, 0x00444510, BGND_Init); +} + +static void M_Camera(const bool enable) +{ + INJECT(enable, 0x004105A0, Camera_Initialise); + INJECT(enable, 0x00410650, Camera_Move); + INJECT(enable, 0x004109D0, Camera_Clip); + INJECT(enable, 0x00410AB0, Camera_Shift); + INJECT(enable, 0x00410C10, Camera_GoodPosition); + INJECT(enable, 0x00410C60, Camera_SmartShift); + INJECT(enable, 0x004113F0, Camera_Chase); + INJECT(enable, 0x004114E0, Camera_ShiftClamp); + INJECT(enable, 0x00411680, Camera_Combat); + INJECT(enable, 0x00411810, Camera_Look); + INJECT(enable, 0x00411A00, Camera_Fixed); + INJECT(enable, 0x00411AA0, Camera_Update); + INJECT(enable, 0x004126A0, Camera_LoadCutsceneFrame); + INJECT(enable, 0x00412290, Camera_UpdateCutscene); + INJECT(enable, 0x00415100, Camera_RefreshFromTrigger); +} + +static void M_Collide(const bool enable) +{ + INJECT(enable, 0x004128F0, Collide_GetCollisionInfo); + INJECT(enable, 0x00412FE0, Collide_CollideStaticObjects); +} + +static void M_Game(const bool enable) +{ + INJECT(enable, 0x00414390, Game_Control); + INJECT(enable, 0x00418990, Game_Draw); + INJECT(enable, 0x00418950, Game_DrawCinematic); + INJECT(enable, 0x0044C480, Game_Start); + INJECT(enable, 0x0044C5D0, Game_Loop); +} + +static void M_Room(const bool enable) +{ + INJECT(enable, 0x00412FB0, Room_FindGridShift); + INJECT(enable, 0x004133D0, Room_GetNearbyRooms); + INJECT(enable, 0x004134A0, Room_GetNewRoom); + INJECT(enable, 0x004135A0, Room_GetTiltType); + INJECT(enable, 0x00414B70, Room_GetSector); + INJECT(enable, 0x00414D10, Room_GetWaterHeight); + INJECT(enable, 0x00414E80, Room_GetHeight); + INJECT(enable, 0x00415930, Room_GetCeiling); + INJECT(enable, 0x00415B90, Room_GetDoor); + INJECT(enable, 0x004151F0, Room_TestTriggers); + INJECT(enable, 0x004340B0, Room_AlterFloorHeight); + INJECT(enable, 0x00416640, Room_FlipMap); + INJECT(enable, 0x00416700, Room_RemoveFlipItems); + INJECT(enable, 0x004167A0, Room_AddFlipItems); + INJECT(enable, 0x00418C80, Room_GetBounds); + INJECT(enable, 0x00418E50, Room_SetBounds); + INJECT(enable, 0x004191D0, Room_Clip); + INJECT(enable, 0x004189D0, Room_DrawAllRooms); + INJECT(enable, 0x004195B0, Room_DrawSingleRoomGeometry); + INJECT(enable, 0x00419670, Room_DrawSingleRoomObjects); + INJECT(enable, 0x00416800, Room_TriggerMusicTrack); +} + +static void M_Matrix(const bool enable) +{ + INJECT(enable, 0x00401000, Matrix_GenerateW2V); + INJECT(enable, 0x004011D0, Matrix_LookAt); + INJECT(enable, 0x004012D0, Matrix_RotX); + INJECT(enable, 0x00401380, Matrix_RotY); + INJECT(enable, 0x00401430, Matrix_RotZ); + INJECT(enable, 0x004014E0, Matrix_RotYXZ); + INJECT(enable, 0x004016C0, Matrix_RotYXZpack); + INJECT(enable, 0x004018B0, Matrix_TranslateRel); + INJECT(enable, 0x00401960, Matrix_TranslateAbs); + INJECT(enable, 0x0041B710, Matrix_InitInterpolate); + INJECT(enable, 0x0041B750, Matrix_Pop_I); + INJECT(enable, 0x0041B780, Matrix_Push_I); + INJECT(enable, 0x0041B7B0, Matrix_RotY_I); + INJECT(enable, 0x0041B7F0, Matrix_RotX_I); + INJECT(enable, 0x0041B830, Matrix_RotZ_I); + INJECT(enable, 0x0041B870, Matrix_TranslateRel_I); + INJECT(enable, 0x0041B8C0, Matrix_TranslateRel_ID); + INJECT(enable, 0x0041B910, Matrix_RotYXZ_I); + INJECT(enable, 0x0041B960, Matrix_RotYXZsuperpack_I); + INJECT(enable, 0x0041B9A0, Matrix_RotYXZsuperpack); + INJECT(enable, 0x0041BA80, Matrix_Interpolate); + INJECT(enable, 0x0041BC30, Matrix_InterpolateArm); + INJECT(enable, 0x00457280, Matrix_Push); + INJECT(enable, 0x0045729E, Matrix_PushUnit); +} + +static void M_Math(const bool enable) +{ + INJECT(enable, 0x00457C10, Math_Atan); + INJECT(enable, 0x00457C58, Math_Cos); + INJECT(enable, 0x00457C5E, Math_Sin); + INJECT(enable, 0x00457C93, Math_Sqrt); + INJECT(enable, 0x00401250, Math_GetVectorAngles); +} + +static void M_Shell(const bool enable) +{ + INJECT(enable, 0x0044E770, Shell_Cleanup); + INJECT(enable, 0x0044E890, Shell_ExitSystem); + INJECT(enable, 0x00454980, Shell_Main); +} + +static void M_Requester(const bool enable) +{ + INJECT(enable, 0x004255A0, Requester_Init); + INJECT(enable, 0x00425630, Requester_Shutdown); + INJECT(enable, 0x004257C0, Requester_Display); + INJECT(enable, 0x004256E0, Requester_Item_CenterAlign); + INJECT(enable, 0x00425700, Requester_Item_LeftAlign); + INJECT(enable, 0x00425760, Requester_Item_RightAlign); + INJECT(enable, 0x00426030, Requester_SetHeading); + INJECT(enable, 0x004260E0, Requester_RemoveAllItems); + INJECT(enable, 0x00426100, Requester_ChangeItem); + INJECT(enable, 0x004261C0, Requester_AddItem); + INJECT(enable, 0x00426270, Requester_SetSize); +} + +static void M_Option(const bool enable) +{ + INJECT(enable, 0x0044EDC0, Option_DoInventory); + INJECT(enable, 0x0044EED0, Option_Passport); + INJECT(enable, 0x0044F520, Option_Detail); + INJECT(enable, 0x0044F800, Option_Sound); + INJECT(enable, 0x0044FCA0, Option_Compass); + INJECT(enable, 0x0044FE20, Option_Controls); +} + +static void M_Text(const bool enable) +{ + INJECT(enable, 0x00440450, Text_Init); + INJECT(enable, 0x00440480, Text_Create); + INJECT(enable, 0x00440590, Text_ChangeText); + INJECT(enable, 0x004405D0, Text_SetScale); + INJECT(enable, 0x004405F0, Text_Flash); + INJECT(enable, 0x00440620, Text_AddBackground); + INJECT(enable, 0x004406B0, Text_RemoveBackground); + INJECT(enable, 0x004406C0, Text_AddOutline); + INJECT(enable, 0x004406F0, Text_RemoveOutline); + INJECT(enable, 0x00440700, Text_CentreH); + INJECT(enable, 0x00440720, Text_CentreV); + INJECT(enable, 0x00440740, Text_AlignRight); + INJECT(enable, 0x00440760, Text_AlignBottom); + INJECT(enable, 0x00440780, Text_GetWidth); + INJECT(enable, 0x00440890, Text_Remove); + INJECT(enable, 0x004408F0, Text_Draw); + INJECT(enable, 0x00440920, Text_DrawBorder); + INJECT(enable, 0x00440AB0, Text_DrawText); + INJECT(enable, 0x00440E90, Text_GetScaleH); + INJECT(enable, 0x00440ED0, Text_GetScaleV); +} + +static void M_Input(const bool enable) +{ + INJECT(enable, 0x0044DA10, Input_Update); + INJECT(enable, 0x004239C0, Input_GetDebounced); +} + +static void M_Output(const bool enable) +{ + INJECT(enable, 0x004019E0, Output_InsertPolygons); + INJECT(enable, 0x00401AE0, Output_InsertRoom); + INJECT(enable, 0x00401BD0, Output_CalcSkyboxLight); + INJECT(enable, 0x00401C10, Output_InsertSkybox); + INJECT(enable, 0x00401D60, Output_CalcObjectVertices); + INJECT(enable, 0x00401F40, Output_CalcVerticeLight); + INJECT(enable, 0x004020B0, Output_CalcRoomVertices); + INJECT(enable, 0x00402330, Output_RotateLight); + INJECT(enable, 0x00402400, Output_InitPolyList); + INJECT(enable, 0x00402430, Output_SortPolyList); + INJECT(enable, 0x00402470, Output_QuickSort); + INJECT(enable, 0x00402540, Output_PrintPolyList); + INJECT(enable, 0x00402580, Output_AlterFOV); + INJECT(enable, 0x00402690, Output_SetNearZ); + INJECT(enable, 0x004026E0, Output_SetFarZ); + INJECT(enable, 0x00402700, Output_Init); + INJECT(enable, 0x00402970, Output_DrawPolyLine); + INJECT(enable, 0x00402B10, Output_DrawPolyFlat); + INJECT(enable, 0x00402B50, Output_DrawPolyTrans); + INJECT(enable, 0x00402B90, Output_DrawPolyGouraud); + INJECT(enable, 0x00402BD0, Output_DrawPolyGTMap); + INJECT(enable, 0x00402C10, Output_DrawPolyWGTMap); + INJECT(enable, 0x00402C50, Output_XGenX); + INJECT(enable, 0x00402D30, Output_XGenXG); + INJECT(enable, 0x00402E80, Output_XGenXGUV); + INJECT(enable, 0x004030A0, Output_XGenXGUVPerspFP); + INJECT(enable, 0x00403330, Output_GTMapPersp32FP); + INJECT(enable, 0x00404300, Output_WGTMapPersp32FP); + INJECT(enable, 0x004057D0, Output_DrawPolyGTMapPersp); + INJECT(enable, 0x00405810, Output_DrawPolyWGTMapPersp); + INJECT(enable, 0x00405850, Output_VisibleZClip); + INJECT(enable, 0x004058C0, Output_ZedClipper); + INJECT(enable, 0x00405A00, Output_XYGUVClipper); + INJECT(enable, 0x00405F20, Output_InsertObjectGT4); + INJECT(enable, 0x00406980, Output_InsertObjectGT3); + INJECT(enable, 0x00407200, Output_XYGClipper); + INJECT(enable, 0x00407630, Output_InsertObjectG4); + INJECT(enable, 0x00407A10, Output_InsertObjectG3); + INJECT(enable, 0x00407D30, Output_XYClipper); + INJECT(enable, 0x00408000, Output_InsertTrans8); + INJECT(enable, 0x004084B0, Output_InsertTransQuad); + INJECT(enable, 0x00408590, Output_InsertFlatRect); + INJECT(enable, 0x00408660, Output_InsertLine); + INJECT(enable, 0x00408720, Output_InsertGT3_ZBuffered); + INJECT(enable, 0x00408D70, Output_DrawClippedPoly_Textured); + INJECT(enable, 0x00408EB0, Output_InsertGT4_ZBuffered); + INJECT(enable, 0x00409300, Output_InsertObjectGT4_ZBuffered); + INJECT(enable, 0x004093A0, Output_InsertObjectGT3_ZBuffered); + INJECT(enable, 0x00409450, Output_InsertObjectG4_ZBuffered); + INJECT(enable, 0x004097F0, Output_DrawPoly_Gouraud); + INJECT(enable, 0x004098F0, Output_InsertObjectG3_ZBuffered); + INJECT(enable, 0x00409BD0, Output_InsertFlatRect_ZBuffered); + INJECT(enable, 0x00409DA0, Output_InsertLine_ZBuffered); + INJECT(enable, 0x00409EE0, Output_InsertGT3_Sorted); + INJECT(enable, 0x0040A5F0, Output_InsertClippedPoly_Textured); + INJECT(enable, 0x0040A7A0, Output_InsertGT4_Sorted); + INJECT(enable, 0x0040AC80, Output_InsertObjectGT4_Sorted); + INJECT(enable, 0x0040AD10, Output_InsertObjectGT3_Sorted); + INJECT(enable, 0x0040ADB0, Output_InsertObjectG4_Sorted); + INJECT(enable, 0x0040B1F0, Output_InsertPoly_Gouraud); + INJECT(enable, 0x0040B370, Output_InsertObjectG3_Sorted); + INJECT(enable, 0x0040B6C0, Output_InsertSprite_Sorted); + INJECT(enable, 0x0040BA10, Output_InsertFlatRect_Sorted); + INJECT(enable, 0x0040BB90, Output_InsertLine_Sorted); + INJECT(enable, 0x0040BCC0, Output_InsertTrans8_Sorted); + INJECT(enable, 0x0040BE60, Output_InsertTransQuad_Sorted); + INJECT(enable, 0x0040BFA0, Output_InsertSprite); + INJECT(enable, 0x0040C050, Output_DrawSprite); + INJECT(enable, 0x0040C320, Output_DrawPickup); + INJECT(enable, 0x0040C3B0, Output_InsertRoomSprite); + INJECT(enable, 0x0040C510, Output_DrawScreenSprite2D); + INJECT(enable, 0x0040C5B0, Output_DrawScreenSprite); + INJECT(enable, 0x0040C650, Output_DrawScaledSpriteC); + INJECT(enable, 0x0041BA50, Output_InsertPolygons_I); +} + +static void M_Music(const bool enable) +{ + INJECT(enable, 0x004553E0, Music_Init); + INJECT(enable, 0x00455460, Music_Shutdown); + INJECT(enable, 0x00455500, Music_Play); + INJECT(enable, 0x00455570, Music_Stop); + INJECT(enable, 0x004555B0, Music_PlaySynced); + INJECT(enable, 0x004556B0, Music_SetVolume); +} + +static void M_Sound(const bool enable) +{ + INJECT(enable, 0x00455380, Sound_SetMasterVolume); + INJECT(enable, 0x0041C560, Sound_UpdateEffects); + INJECT(enable, 0x0043F3C0, Sound_Effect); + INJECT(enable, 0x0043F860, Sound_StopEffect); + INJECT(enable, 0x0043F8C0, Sound_EndScene); + INJECT(enable, 0x0043F950, Sound_Shutdown); + INJECT(enable, 0x0043F980, Sound_Init); +} + +static void M_Demo(const bool enable) +{ + INJECT(enable, 0x00416910, Demo_Control); + INJECT(enable, 0x00416970, Demo_Start); + INJECT(enable, 0x00416B20, Demo_LoadLaraPos); + INJECT(enable, 0x00416BF0, Demo_GetInput); +} + +static void M_Gameflow(bool enable) +{ + INJECT(enable, 0x0041FA60, GF_LoadScriptFile); + INJECT(enable, 0x0041FC50, GF_DoFrontendSequence); + INJECT(enable, 0x0041FC70, GF_DoLevelSequence); + INJECT(enable, 0x0041FCE0, GF_InterpretSequence); + INJECT(enable, 0x004201C0, GF_ModifyInventory); + INJECT(enable, 0x0044B6C0, GF_LoadFromFile); +} + +static void M_Overlay(const bool enable) +{ + INJECT(enable, 0x004219A0, Overlay_FlashCounter); + INJECT(enable, 0x004219D0, Overlay_DrawAssaultTimer); + INJECT(enable, 0x00421B20, Overlay_DrawGameInfo); + INJECT(enable, 0x00421B70, Overlay_DrawHealthBar); + INJECT(enable, 0x00421C20, Overlay_DrawAirBar); + INJECT(enable, 0x00421CC0, Overlay_MakeAmmoString); + INJECT(enable, 0x00421CF0, Overlay_DrawAmmoInfo); + INJECT(enable, 0x00421E40, Overlay_InitialisePickUpDisplay); + INJECT(enable, 0x00421E60, Overlay_DrawPickups); + INJECT(enable, 0x00421F60, Overlay_AddDisplayPickup); + INJECT(enable, 0x00421FD0, Overlay_DisplayModeInfo); + INJECT(enable, 0x00422050, Overlay_DrawModeInfo); +} + +static void M_Random(const bool enable) +{ + INJECT(enable, 0x0044C970, Random_GetControl); + INJECT(enable, 0x0044C990, Random_SeedControl); + INJECT(enable, 0x0044C9A0, Random_GetDraw); + INJECT(enable, 0x0044C9C0, Random_SeedDraw); + INJECT(enable, 0x0044D870, Random_Seed); +} + +static void M_Items(const bool enable) +{ + INJECT(enable, 0x00426CF0, Item_InitialiseArray); + INJECT(enable, 0x00426D50, Item_Kill); + INJECT(enable, 0x00426E70, Item_Create); + INJECT(enable, 0x00426EB0, Item_Initialise); + INJECT(enable, 0x00427070, Item_RemoveActive); + INJECT(enable, 0x00427100, Item_RemoveDrawn); + INJECT(enable, 0x00427170, Item_AddActive); + INJECT(enable, 0x004271D0, Item_NewRoom); + INJECT(enable, 0x00427270, Item_GlobalReplace); + INJECT(enable, 0x00427520, Item_ClearKilled); + INJECT(enable, 0x00413500, Item_ShiftCol); + INJECT(enable, 0x00413540, Item_UpdateRoom); + INJECT(enable, 0x00413D40, Item_TestBoundsCollide); + INJECT(enable, 0x00413E10, Item_TestPosition); + INJECT(enable, 0x00413F50, Item_AlignPosition); + INJECT(enable, 0x004146F0, Item_Animate); + INJECT(enable, 0x00414A60, Item_GetAnimChange); + INJECT(enable, 0x00414B10, Item_Translate); + INJECT(enable, 0x004158D0, Item_IsTriggerActive); + INJECT(enable, 0x0041BF90, Item_GetFrames); + INJECT(enable, 0x0041C030, Item_GetBoundsAccurate); + INJECT(enable, 0x0041C0B0, Item_GetBestFrame); +} + +static void M_Effects(const bool enable) +{ + INJECT(enable, 0x004272F0, Effect_InitialiseArray); + INJECT(enable, 0x00427320, Effect_Create); + INJECT(enable, 0x00427390, Effect_Kill); + INJECT(enable, 0x00427480, Effect_NewRoom); + INJECT(enable, 0x00419890, Effect_Draw); +} + +static void M_LOS(const bool enable) +{ + INJECT(enable, 0x00415BE0, LOS_Check); + INJECT(enable, 0x00415C80, LOS_CheckZ); + INJECT(enable, 0x00415F70, LOS_CheckX); + INJECT(enable, 0x00416260, LOS_ClipTarget); + INJECT(enable, 0x00416340, LOS_CheckSmashable); +} + +static void M_People(const bool enable) +{ + INJECT(enable, 0x00435E00, Creature_CanTargetEnemy); +} + +static void M_Level(const bool enable) +{ + INJECT(enable, 0x0044B260, Level_Load); +} + +static void M_Inventory(const bool enable) +{ + INJECT(enable, 0x00422080, Inv_Display); + INJECT(enable, 0x00423310, Inv_Construct); + INJECT(enable, 0x00423470, Inv_SelectMeshes); + INJECT(enable, 0x00423500, Inv_AnimateInventoryItem); + INJECT(enable, 0x00423590, Inv_DrawInventoryItem); + INJECT(enable, 0x004239E0, Inv_DoInventoryPicture); + INJECT(enable, 0x004239F0, Inv_DoInventoryBackground); + INJECT(enable, 0x00423B30, Inv_InitColors); + INJECT(enable, 0x00423C40, Inv_RingIsOpen); + INJECT(enable, 0x00423DB0, Inv_RingIsNotOpen); + INJECT(enable, 0x00423E40, Inv_RingNotActive); + INJECT(enable, 0x004242B0, Inv_RingActive); + INJECT(enable, 0x004242F0, Inv_AddItem); + INJECT(enable, 0x00424B00, Inv_InsertItem); + INJECT(enable, 0x00424C30, Inv_RequestItem); + INJECT(enable, 0x00424CB0, Inv_RemoveAllItems); + INJECT(enable, 0x00424CD0, Inv_RemoveItem); + INJECT(enable, 0x00424DE0, Inv_GetItemOption); + INJECT(enable, 0x00425000, Inv_Ring_Init); + INJECT(enable, 0x00425110, Inv_Ring_GetView); + INJECT(enable, 0x00425170, Inv_Ring_Light); + INJECT(enable, 0x004251B0, Inv_Ring_CalcAdders); + INJECT(enable, 0x004251E0, Inv_Ring_DoMotions); + INJECT(enable, 0x00425320, Inv_Ring_RotateLeft); + INJECT(enable, 0x00425350, Inv_Ring_RotateRight); + INJECT(enable, 0x00425380, Inv_Ring_MotionInit); + INJECT(enable, 0x004253F0, Inv_Ring_MotionSetup); + INJECT(enable, 0x00425420, Inv_Ring_MotionRadius); + INJECT(enable, 0x00425450, Inv_Ring_MotionRotation); + INJECT(enable, 0x00425480, Inv_Ring_MotionCameraPos); + INJECT(enable, 0x004254B0, Inv_Ring_MotionCameraPitch); + INJECT(enable, 0x004254D0, Inv_Ring_MotionItemSelect); + INJECT(enable, 0x00425530, Inv_Ring_MotionItemDeselect); +} + +static void M_Lara_Control(const bool enable) +{ + INJECT(enable, 0x00427580, Lara_HandleAboveWater); + INJECT(enable, 0x00431670, Lara_HandleSurface); + INJECT(enable, 0x00431F50, Lara_HandleUnderwater); + INJECT(enable, 0x004302E0, Lara_Control); + INJECT(enable, 0x00430EF0, Lara_ControlExtra); + INJECT(enable, 0x00430970, Lara_Animate); + INJECT(enable, 0x00430C70, Lara_UseItem); + INJECT(enable, 0x00430E30, Lara_Cheat_GetStuff); + INJECT(enable, 0x00430F10, Lara_InitialiseLoad); + INJECT(enable, 0x00430F40, Lara_Initialise); + INJECT(enable, 0x00431200, Lara_InitialiseInventory); + INJECT(enable, 0x00431570, Lara_InitialiseMeshes); +} + +static void M_Lara_Draw(const bool enable) +{ + INJECT(enable, 0x00419DF0, Lara_Draw); + INJECT(enable, 0x0041AB20, Lara_Draw_I); +} + +static void M_Lara_Look(const bool enable) +{ + INJECT(enable, 0x00427720, Lara_LookUpDown); + INJECT(enable, 0x00427790, Lara_LookLeftRight); + INJECT(enable, 0x00427810, Lara_ResetLook); +} + +static void M_Lara_Misc(const bool enable) +{ + INJECT(enable, 0x0042A0A0, Lara_GetCollisionInfo); + INJECT(enable, 0x0042A0E0, Lara_SlideSlope); + INJECT(enable, 0x0042A1D0, Lara_HitCeiling); + INJECT(enable, 0x0042A240, Lara_DeflectEdge); + INJECT(enable, 0x0042A2C0, Lara_DeflectEdgeJump); + INJECT(enable, 0x0042A440, Lara_SlideEdgeJump); + INJECT(enable, 0x0042A530, Lara_TestWall); + INJECT(enable, 0x0042A640, Lara_TestHangOnClimbWall); + INJECT(enable, 0x0042A750, Lara_TestClimbStance); + INJECT(enable, 0x0042A810, Lara_HangTest); + INJECT(enable, 0x0042AB70, Lara_TestEdgeCatch); + INJECT(enable, 0x0042AC20, Lara_TestHangJumpUp); + INJECT(enable, 0x0042AD90, Lara_TestHangJump); + INJECT(enable, 0x0042AF30, Lara_TestHangSwingIn); + INJECT(enable, 0x0042AFF0, Lara_TestVault); + INJECT(enable, 0x0042B2E0, Lara_TestSlide); + INJECT(enable, 0x0042B410, Lara_FloorFront); + INJECT(enable, 0x0042B490, Lara_LandedBad); + INJECT(enable, 0x0042DF70, Lara_CheckForLetGo); + INJECT(enable, 0x0042B550, Lara_GetJointAbsPosition); + INJECT(enable, 0x0042B8E0, Lara_GetJointAbsPosition_I); + INJECT(enable, 0x00413640, Lara_BaddieCollision); + INJECT(enable, 0x004137E0, Lara_TakeHit); + INJECT(enable, 0x00413A30, Lara_Push); + INJECT(enable, 0x00414090, Lara_MovePosition); + INJECT(enable, 0x0041C4D0, Lara_IsNearItem); + INJECT(enable, 0x0042E020, Lara_TestClimb); + INJECT(enable, 0x0042E290, Lara_TestClimbPos); + INJECT(enable, 0x0042E360, Lara_DoClimbLeftRight); + INJECT(enable, 0x0042E450, Lara_TestClimbUpPos); + INJECT(enable, 0x004324A0, Lara_GetWaterDepth); + INJECT(enable, 0x00432640, Lara_TestWaterDepth); + INJECT(enable, 0x00432710, Lara_SwimCollision); + INJECT(enable, 0x00432870, Lara_WaterCurrent); + INJECT(enable, 0x00442D30, Lara_CatchFire); + INJECT(enable, 0x00442D80, Lara_TouchLava); +} + +static void M_Lara_State(const bool enable) +{ + INJECT(enable, 0x00432180, Lara_SwimTurn); + INJECT(enable, 0x004278A0, Lara_State_Walk); + INJECT(enable, 0x00427930, Lara_State_Run); + INJECT(enable, 0x00427A80, Lara_State_Stop); + INJECT(enable, 0x00427BD0, Lara_State_ForwardJump); + INJECT(enable, 0x00427CB0, Lara_State_FastBack); + INJECT(enable, 0x00427D10, Lara_State_TurnRight); + INJECT(enable, 0x00427DA0, Lara_State_TurnLeft); + INJECT(enable, 0x00427E30, Lara_State_Death); + INJECT(enable, 0x00427E50, Lara_State_FastFall); + INJECT(enable, 0x00427E90, Lara_State_Hang); + INJECT(enable, 0x00427EF0, Lara_State_Reach); + INJECT(enable, 0x00427F10, Lara_State_Splat); + INJECT(enable, 0x00427F20, Lara_State_Compress); + INJECT(enable, 0x00428030, Lara_State_Back); + INJECT(enable, 0x004280C0, Lara_State_Null); + INJECT(enable, 0x004280D0, Lara_State_FastTurn); + INJECT(enable, 0x00428120, Lara_State_StepRight); + INJECT(enable, 0x004281A0, Lara_State_StepLeft); + INJECT(enable, 0x00428220, Lara_State_Slide); + INJECT(enable, 0x00428250, Lara_State_BackJump); + INJECT(enable, 0x004282A0, Lara_State_RightJump); + INJECT(enable, 0x004282E0, Lara_State_LeftJump); + INJECT(enable, 0x00428320, Lara_State_UpJump); + INJECT(enable, 0x00428340, Lara_State_Fallback); + INJECT(enable, 0x00428370, Lara_State_HangLeft); + INJECT(enable, 0x004283B0, Lara_State_HangRight); + INJECT(enable, 0x004283F0, Lara_State_SlideBack); + INJECT(enable, 0x00428410, Lara_State_PushBlock); + INJECT(enable, 0x00428440, Lara_State_PPReady); + INJECT(enable, 0x00428470, Lara_State_Pickup); + INJECT(enable, 0x004284A0, Lara_State_PickupFlare); + INJECT(enable, 0x00428500, Lara_State_SwitchOn); + INJECT(enable, 0x00428540, Lara_State_UseKey); + INJECT(enable, 0x00428570, Lara_State_Special); + INJECT(enable, 0x00428590, Lara_State_SwanDive); + INJECT(enable, 0x004285C0, Lara_State_FastDive); + INJECT(enable, 0x00428620, Lara_State_WaterOut); + INJECT(enable, 0x00428640, Lara_State_Wade); + INJECT(enable, 0x00428710, Lara_State_DeathSlide); + INJECT(enable, 0x004287B0, Lara_State_Extra_Breath); + INJECT(enable, 0x00428800, Lara_State_Extra_YetiKill); + INJECT(enable, 0x00428850, Lara_State_Extra_SharkKill); + INJECT(enable, 0x004288F0, Lara_State_Extra_Airlock); + INJECT(enable, 0x00428910, Lara_State_Extra_GongBong); + INJECT(enable, 0x00428930, Lara_State_Extra_DinoKill); + INJECT(enable, 0x00428990, Lara_State_Extra_PullDagger); + INJECT(enable, 0x00428A50, Lara_State_Extra_StartAnim); + INJECT(enable, 0x00428AA0, Lara_State_Extra_StartHouse); + INJECT(enable, 0x00428B50, Lara_State_Extra_FinalAnim); + INJECT(enable, 0x0042D850, Lara_State_ClimbLeft); + INJECT(enable, 0x0042D890, Lara_State_ClimbRight); + INJECT(enable, 0x0042D8D0, Lara_State_ClimbStance); + INJECT(enable, 0x0042D950, Lara_State_Climbing); + INJECT(enable, 0x0042D970, Lara_State_ClimbEnd); + INJECT(enable, 0x0042D990, Lara_State_ClimbDown); + INJECT(enable, 0x004317D0, Lara_State_SurfSwim); + INJECT(enable, 0x00431840, Lara_State_SurfBack); + INJECT(enable, 0x004318A0, Lara_State_SurfLeft); + INJECT(enable, 0x00431900, Lara_State_SurfRight); + INJECT(enable, 0x00431960, Lara_State_SurfTread); + INJECT(enable, 0x00432210, Lara_State_Swim); + INJECT(enable, 0x00432280, Lara_State_Glide); + INJECT(enable, 0x00432300, Lara_State_Tread); + INJECT(enable, 0x00432390, Lara_State_Dive); + INJECT(enable, 0x004323B0, Lara_State_UWDeath); + INJECT(enable, 0x00432410, Lara_State_UWTwist); +} + +static void M_Lara_Col(const bool enable) +{ + INJECT(enable, 0x00428C00, Lara_Fallen); + INJECT(enable, 0x00428C60, Lara_CollideStop); + INJECT(enable, 0x00431B40, Lara_SurfaceCollision); + INJECT(enable, 0x00431C40, Lara_TestWaterStepOut); + INJECT(enable, 0x00431D30, Lara_TestWaterClimbOut); + + INJECT(enable, 0x00428D20, Lara_Col_Walk); + INJECT(enable, 0x00428EC0, Lara_Col_Run); + INJECT(enable, 0x00429040, Lara_Col_Stop); + INJECT(enable, 0x004290D0, Lara_Col_ForwardJump); + INJECT(enable, 0x004291B0, Lara_Col_FastBack); + INJECT(enable, 0x00429270, Lara_Col_TurnRight); + INJECT(enable, 0x00429310, Lara_Col_TurnLeft); + INJECT(enable, 0x00429330, Lara_Col_Death); + INJECT(enable, 0x004293A0, Lara_Col_FastFall); + INJECT(enable, 0x00429440, Lara_Col_Hang); + INJECT(enable, 0x00429570, Lara_Col_Reach); + INJECT(enable, 0x00429600, Lara_Col_Splat); + INJECT(enable, 0x00429660, Lara_Col_Land); + INJECT(enable, 0x00429680, Lara_Col_Compress); + INJECT(enable, 0x00429720, Lara_Col_Back); + INJECT(enable, 0x00429820, Lara_Col_StepRight); + INJECT(enable, 0x004298E0, Lara_Col_StepLeft); + INJECT(enable, 0x00429900, Lara_Col_Slide); + INJECT(enable, 0x00429920, Lara_Col_BackJump); + INJECT(enable, 0x00429950, Lara_Col_RightJump); + INJECT(enable, 0x00429980, Lara_Col_LeftJump); + INJECT(enable, 0x004299B0, Lara_Col_UpJump); + INJECT(enable, 0x00429AD0, Lara_Col_Fallback); + INJECT(enable, 0x00429B60, Lara_Col_HangLeft); + INJECT(enable, 0x00429BA0, Lara_Col_HangRight); + INJECT(enable, 0x00429BE0, Lara_Col_SlideBack); + INJECT(enable, 0x00429C10, Lara_Col_Null); + INJECT(enable, 0x00429C30, Lara_Col_Roll); + INJECT(enable, 0x00429CC0, Lara_Col_Roll2); + INJECT(enable, 0x00429D80, Lara_Col_SwanDive); + INJECT(enable, 0x00429DF0, Lara_Col_FastDive); + INJECT(enable, 0x00429E70, Lara_Col_Wade); + INJECT(enable, 0x00429FE0, Lara_Col_Default); + INJECT(enable, 0x0042A020, Lara_Col_Jumper); + INJECT(enable, 0x0042D9B0, Lara_Col_ClimbLeft); + INJECT(enable, 0x0042DA10, Lara_Col_ClimbRight); + INJECT(enable, 0x0042DA70, Lara_Col_ClimbStance); + INJECT(enable, 0x0042DC80, Lara_Col_Climbing); + INJECT(enable, 0x0042DDD0, Lara_Col_ClimbDown); + INJECT(enable, 0x00431A50, Lara_Col_SurfSwim); + INJECT(enable, 0x00431A90, Lara_Col_SurfBack); + INJECT(enable, 0x00431AC0, Lara_Col_SurfLeft); + INJECT(enable, 0x00431AF0, Lara_Col_SurfRight); + INJECT(enable, 0x00431B20, Lara_Col_SurfTread); + INJECT(enable, 0x00432420, Lara_Col_Swim); + INJECT(enable, 0x00432440, Lara_Col_UWDeath); +} + +static void M_Gun(bool enable) +{ + INJECT(enable, 0x0042BC00, Gun_Rifle_DrawMeshes); + INJECT(enable, 0x0042BC40, Gun_Rifle_UndrawMeshes); + INJECT(enable, 0x0042BC70, Gun_Rifle_Ready); + INJECT(enable, 0x0042BCE0, Gun_Rifle_Control); + INJECT(enable, 0x0042BDE0, Gun_Rifle_FireShotgun); + INJECT(enable, 0x0042BEE0, Gun_Rifle_FireM16); + INJECT(enable, 0x0042BF60, Gun_Rifle_FireHarpoon); + INJECT(enable, 0x0042C440, Gun_Rifle_FireGrenade); + INJECT(enable, 0x0042C930, Gun_Rifle_Draw); + INJECT(enable, 0x0042CAA0, Gun_Rifle_Undraw); + INJECT(enable, 0x0042CBB0, Gun_Rifle_Animate); + INJECT(enable, 0x0042CF60, Gun_Pistols_SetArmInfo); + INJECT(enable, 0x0042CFB0, Gun_Pistols_Draw); + INJECT(enable, 0x0042D030, Gun_Pistols_Undraw); + INJECT(enable, 0x0042D260, Gun_Pistols_Ready); + INJECT(enable, 0x0042D2C0, Gun_Pistols_DrawMeshes); + INJECT(enable, 0x0042D310, Gun_Pistols_UndrawMeshLeft); + INJECT(enable, 0x0042D350, Gun_Pistols_UndrawMeshRight); + INJECT(enable, 0x0042D390, Gun_Pistols_Control); + INJECT(enable, 0x0042D520, Gun_Pistols_Animate); + INJECT(enable, 0x0042E6A0, Gun_Control); + INJECT(enable, 0x0042EC10, Gun_CheckForHoldingState); + INJECT(enable, 0x0042EC50, Gun_InitialiseNewWeapon); + INJECT(enable, 0x0042ED90, Gun_TargetInfo); + INJECT(enable, 0x0042EF30, Gun_GetNewTarget); + INJECT(enable, 0x0042F150, Gun_FindTargetPoint); + INJECT(enable, 0x0042F200, Gun_AimWeapon); + INJECT(enable, 0x0042F2D0, Gun_FireWeapon); + INJECT(enable, 0x0042F640, Gun_HitTarget); + INJECT(enable, 0x0042F6E0, Gun_SmashItem); + INJECT(enable, 0x0042F740, Gun_GetWeaponAnim); +} + +static void M_Creature(const bool enable) +{ + INJECT(enable, 0x0040E1B0, Creature_Initialise); + INJECT(enable, 0x0040E1E0, Creature_Activate); + INJECT(enable, 0x0040E230, Creature_AIInfo); + INJECT(enable, 0x0040EA00, Creature_Mood); + INJECT(enable, 0x0040F2D0, Creature_CheckBaddieOverlap); + INJECT(enable, 0x0040F460, Creature_Die); + INJECT(enable, 0x0040F520, Creature_Animate); + INJECT(enable, 0x0040FDF0, Creature_Turn); + INJECT(enable, 0x0040FED0, Creature_Tilt); + INJECT(enable, 0x0040FF10, Creature_Head); + INJECT(enable, 0x0040FF60, Creature_Neck); + INJECT(enable, 0x0040FFB0, Creature_Float); + INJECT(enable, 0x00410060, Creature_Underwater); + INJECT(enable, 0x004100B0, Creature_Effect); + INJECT(enable, 0x00410110, Creature_Vault); + INJECT(enable, 0x00410250, Creature_Kill); + INJECT(enable, 0x004103C0, Creature_GetBaddieTarget); + INJECT(enable, 0x00413860, Creature_Collision); +} + +static void M_Box(const bool enable) +{ + INJECT(enable, 0x0040E490, Box_SearchLOT); + INJECT(enable, 0x0040E690, Box_UpdateLOT); + INJECT(enable, 0x0040E700, Box_TargetBox); + INJECT(enable, 0x0040E7A0, Box_StalkBox); + INJECT(enable, 0x0040E8A0, Box_EscapeBox); + INJECT(enable, 0x0040E950, Box_ValidBox); + INJECT(enable, 0x0040EE70, Box_CalculateTarget); + INJECT(enable, 0x0040F3D0, Box_BadFloor); +} + +static void M_Lot(const bool enable) +{ + INJECT(enable, 0x00432A60, LOT_InitialiseArray); + INJECT(enable, 0x00432AC0, LOT_DisableBaddieAI); + INJECT(enable, 0x00432B10, LOT_EnableBaddieAI); + INJECT(enable, 0x00432CC0, LOT_InitialiseSlot); + INJECT(enable, 0x00432ED0, LOT_CreateZone); + INJECT(enable, 0x00432F90, LOT_ClearLOT); +} + +static void M_Objects(const bool enable) +{ + INJECT(enable, 0x0040C880, Bird_Initialise); + INJECT(enable, 0x0040C910, Bird_Control); + INJECT(enable, 0x0040CB30, Boat_Initialise); + INJECT(enable, 0x0040CB70, Boat_CheckGeton); + INJECT(enable, 0x0040CCE0, Boat_Collision); + INJECT(enable, 0x0040CE40, Boat_TestWaterHeight); + INJECT(enable, 0x0040CF40, Boat_DoShift); + INJECT(enable, 0x0040D110, Boat_DoWakeEffect); + INJECT(enable, 0x0040D290, Boat_DoDynamics); + INJECT(enable, 0x0040D2E0, Boat_Dynamics); + INJECT(enable, 0x0040D7C0, Boat_UserControl); + INJECT(enable, 0x0040D950, Boat_Animation); + INJECT(enable, 0x0040DAC0, Boat_Control); + INJECT(enable, 0x0040E0F0, Gondola_Control); + INJECT(enable, 0x004138E0, Object_Collision); + INJECT(enable, 0x00413940, Door_Collision); + INJECT(enable, 0x004139C0, Object_Collision_Trap); + INJECT(enable, 0x00416DB0, Diver_Control); + INJECT(enable, 0x004336F0, BodyPart_Control); + INJECT(enable, 0x00434400, FinalLevelCounter_Control); + INJECT(enable, 0x00442B30, FlameEmitter_Control); + INJECT(enable, 0x00442BC0, Flame_Control); + INJECT(enable, 0x00442E70, EmberEmitter_Control); + INJECT(enable, 0x00442F40, Ember_Control); +} + +static void M_S_Audio_Sample(const bool enable) +{ + INJECT(enable, 0x00447BC0, S_Audio_Sample_GetAdapter); + INJECT(enable, 0x00447C10, S_Audio_Sample_CloseAllTracks); + INJECT(enable, 0x00447C40, S_Audio_Sample_Load); + INJECT(enable, 0x00447D50, S_Audio_Sample_IsTrackPlaying); + INJECT(enable, 0x00447DA0, S_Audio_Sample_Play); + INJECT(enable, 0x00447E90, S_Audio_Sample_GetFreeTrackIndex); + INJECT(enable, 0x00447ED0, S_Audio_Sample_AdjustTrackVolumeAndPan); + INJECT(enable, 0x00447F00, S_Audio_Sample_AdjustTrackPitch); + INJECT(enable, 0x00447F40, S_Audio_Sample_CloseTrack); + INJECT(enable, 0x00447FB0, S_Audio_Sample_Init); + INJECT(enable, 0x00448050, S_Audio_Sample_DSoundEnumerate); + INJECT(enable, 0x00448070, S_Audio_Sample_DSoundEnumCallback); + INJECT(enable, 0x00448160, S_Audio_Sample_Init2); + INJECT(enable, 0x004482E0, S_Audio_Sample_DSoundCreate); + INJECT(enable, 0x00448300, S_Audio_Sample_DSoundBufferTest); + INJECT(enable, 0x004483D0, S_Audio_Sample_Shutdown); + INJECT(enable, 0x00448400, S_Audio_Sample_IsEnabled); + INJECT(enable, 0x00455220, S_Audio_Sample_OutPlay); + INJECT(enable, 0x00455270, S_Audio_Sample_CalculateSampleVolume); + INJECT(enable, 0x004552A0, S_Audio_Sample_CalculateSamplePan); + INJECT(enable, 0x004552D0, S_Audio_Sample_OutPlayLooped); + INJECT(enable, 0x00455320, S_Audio_Sample_OutSetPanAndVolume); + INJECT(enable, 0x00455360, S_Audio_Sample_OutSetPitch); + INJECT(enable, 0x00455390, S_Audio_Sample_OutCloseTrack); + INJECT(enable, 0x004553B0, S_Audio_Sample_OutCloseAllTracks); + INJECT(enable, 0x004553C0, S_Audio_Sample_OutIsTrackPlaying); +} + +static void M_S_Input(const bool enable) +{ + INJECT(enable, 0x0044D8F0, S_Input_Key); +} + +static void M_S_FlaggedString(const bool enable) +{ + INJECT(enable, 0x00445F00, S_FlaggedString_Delete); + INJECT(enable, 0x00446100, S_FlaggedString_InitAdapter); + INJECT(enable, 0x00447550, S_FlaggedString_Create); +} + +void Inject_Exec(void) +{ + M_DecompGeneral(true); + M_DecompStats(true); + M_DecompEffects(true); + M_HWR(true); + M_Background(true); + + M_Camera(true); + M_Collide(true); + M_Game(true); + M_Room(true); + M_Math(true); + M_Matrix(true); + M_Shell(true); + M_Requester(true); + M_Option(true); + M_Text(true); + M_Input(true); + M_Output(true); + M_Music(true); + M_Sound(true); + + M_Demo(true); + M_Gameflow(true); + M_Overlay(true); + M_Random(true); + M_Items(true); + M_Effects(true); + M_LOS(true); + M_People(true); + M_Level(true); + + M_Inventory(true); + + M_Lara_Control(true); + M_Lara_Draw(true); + M_Lara_Look(true); + M_Lara_Misc(true); + M_Lara_State(true); + M_Lara_Col(true); + M_Gun(true); + + M_Creature(true); + M_Box(true); + M_Lot(true); + M_Objects(true); + + M_S_Audio_Sample(true); + M_S_Input(true); + M_S_FlaggedString(true); +} diff --git a/src/tr2/inject_exec.h b/src/tr2/inject_exec.h new file mode 100644 index 000000000..6cc93f9cb --- /dev/null +++ b/src/tr2/inject_exec.h @@ -0,0 +1,3 @@ +#pragma once + +void Inject_Exec(void); diff --git a/src/tr2/inject_util.c b/src/tr2/inject_util.c new file mode 100644 index 000000000..9baccd7ec --- /dev/null +++ b/src/tr2/inject_util.c @@ -0,0 +1,28 @@ +#include "inject_util.h" + +#include + +#include + +void InjectImpl(bool enable, void (*from)(void), void (*to)(void)) +{ + if (from == to) { + return; + } + + if (!enable) { + void (*aux)(void) = from; + from = to; + to = aux; + } + + DWORD tmp; + LOG_DEBUG("Patching %p to %p", from, to); + VirtualProtect(from, sizeof(JMP), PAGE_EXECUTE_READWRITE, &tmp); + HANDLE hCurrentProcess = GetCurrentProcess(); + JMP buf; + buf.opcode = 0xE9; + buf.offset = (DWORD)(to) - ((DWORD)(from) + sizeof(JMP)); + WriteProcessMemory(hCurrentProcess, from, &buf, sizeof(JMP), &tmp); + CloseHandle(hCurrentProcess); +} diff --git a/src/tr2/inject_util.h b/src/tr2/inject_util.h new file mode 100644 index 000000000..eb6b1e1e2 --- /dev/null +++ b/src/tr2/inject_util.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#pragma pack(push, 1) +typedef struct { + uint8_t opcode; // must be 0xE9 + uint32_t offset; +} JMP; +#pragma pack(pop) + +void InjectImpl(bool enable, void (*from)(void), void (*to)(void)); + +#define INJECT(enable, from, to) \ + { \ + InjectImpl(enable, (void (*)(void))from, (void (*)(void))to); \ + } diff --git a/src/tr2/lib/cpp.h b/src/tr2/lib/cpp.h new file mode 100644 index 000000000..10e7e5d24 --- /dev/null +++ b/src/tr2/lib/cpp.h @@ -0,0 +1,2 @@ +#define operator_delete ((void __cdecl (*)(void *mem))0x00459050) +#define operator_new ((void *__cdecl (*)(size_t size))0x00459060) diff --git a/src/tr2/lib/ddraw.h b/src/tr2/lib/ddraw.h new file mode 100644 index 000000000..b5568dcfa --- /dev/null +++ b/src/tr2/lib/ddraw.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#ifdef DirectDrawCreate + #undef DirectDrawCreate +#endif + +#define DirectDrawCreate \ + ((HRESULT(__stdcall *)( \ + GUID * driver_guid, LPDIRECTDRAW * ddraw, LPUNKNOWN outer))0x00458CF4) + +#ifdef DirectDrawEnumerate + #undef DirectDrawEnumerate +#endif + +#define DirectDrawEnumerate \ + ((HRESULT(__stdcall *)( \ + LPDDENUMCALLBACKA lpCallback, LPVOID lpContext))0x00458CFA) diff --git a/src/tr2/lib/dinput.h b/src/tr2/lib/dinput.h new file mode 100644 index 000000000..cb0abd4a5 --- /dev/null +++ b/src/tr2/lib/dinput.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#define DirectInputCreate \ + ((HRESULT(__stdcall *)( \ + HINSTANCE hinst, DWORD dwVersion, LPDIRECTINPUT * lpDirectInput, \ + LPUNKNOWN punkOuter))0x00457CC0) diff --git a/src/tr2/lib/dsound.h b/src/tr2/lib/dsound.h new file mode 100644 index 000000000..875275c46 --- /dev/null +++ b/src/tr2/lib/dsound.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#define DirectSoundCreate \ + ((HRESULT(__stdcall *)( \ + LPCGUID pcGuidDevice, LPDIRECTSOUND * ppDS, \ + LPUNKNOWN pUnkOuter))0x00458CEE) +#define DirectSoundEnumerateA \ + ((HRESULT(__stdcall *)( \ + LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext))0x00458CE8) diff --git a/src/tr2/lib/winmm.c b/src/tr2/lib/winmm.c new file mode 100644 index 000000000..67ec3cfbe --- /dev/null +++ b/src/tr2/lib/winmm.c @@ -0,0 +1,32 @@ +#include "lib/winmm.h" + +MMRESULT(__stdcall *g_MM_auxGetDevCapsA) +(UINT_PTR uDeviceID, LPAUXCAPSA pac, UINT cbac) = NULL; + +UINT(__stdcall *g_MM_auxGetNumDevs)(void) = NULL; + +MMRESULT(__stdcall *g_MM_auxSetVolume)(UINT uDeviceID, DWORD dwVolume) = NULL; + +MCIERROR(__stdcall *g_MM_mciSendCommandA) +(MCIDEVICEID mciId, UINT uMsg, DWORD_PTR dwParam1, DWORD_PTR dwParam2) = NULL; + +MCIERROR(__stdcall *g_MM_mciSendStringA) +(LPCSTR lpszCommand, LPSTR lpszReturnString, UINT cchReturn, + HANDLE hwndCallback) = NULL; + +void WinMM_Load(void) +{ + HANDLE winmm = LoadLibrary("winmm.dll"); + + auxGetNumDevs = (UINT(__stdcall *)())GetProcAddress(winmm, "auxGetNumDevs"); + + auxSetVolume = (MMRESULT(__stdcall *)(UINT, DWORD))GetProcAddress( + winmm, "auxSetVolume"); + + mciSendCommandA = + (MCIERROR(__stdcall *)(MCIDEVICEID, UINT, DWORD_PTR, DWORD_PTR)) + GetProcAddress(winmm, "mciSendCommandA"); + + mciSendStringA = (MCIERROR(__stdcall *)( + LPCSTR, LPSTR, UINT, HANDLE))GetProcAddress(winmm, "mciSendStringA"); +} diff --git a/src/tr2/lib/winmm.h b/src/tr2/lib/winmm.h new file mode 100644 index 000000000..fc3bb2eeb --- /dev/null +++ b/src/tr2/lib/winmm.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +extern MMRESULT(__stdcall *g_MM_auxGetDevCapsA)( + UINT_PTR uDeviceID, LPAUXCAPSA pac, UINT cbac); + +extern UINT(__stdcall *g_MM_auxGetNumDevs)(void); + +extern MMRESULT(__stdcall *g_MM_auxSetVolume)(UINT uDeviceID, DWORD dwVolume); + +extern MCIERROR(__stdcall *g_MM_mciSendCommandA)( + MCIDEVICEID mciId, UINT uMsg, DWORD_PTR dwParam1, DWORD_PTR dwParam2); + +extern MCIERROR(__stdcall *g_MM_mciSendStringA)( + LPCSTR lpszCommand, LPSTR lpszReturnString, UINT cchReturn, + HANDLE hwndCallback); + +void WinMM_Load(void); + +#define auxGetDevCapsA g_MM_auxGetDevCapsA +#define auxGetNumDevs g_MM_auxGetNumDevs +#define auxSetVolume g_MM_auxSetVolume +#define mciSendCommandA g_MM_mciSendCommandA +#define mciSendStringA g_MM_mciSendStringA diff --git a/src/tr2/main_dll.c b/src/tr2/main_dll.c new file mode 100644 index 000000000..06336f7b5 --- /dev/null +++ b/src/tr2/main_dll.c @@ -0,0 +1,33 @@ +#include "inject_exec.h" +#include "lib/winmm.h" + +#include +#include + +#include +#include +#include + +BOOL APIENTRY +DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: + Log_Init(File_GetFullPath("TR2X.log")); + LOG_DEBUG("Injected\n"); + + WinMM_Load(); + Inject_Exec(); + + break; + + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + + case DLL_PROCESS_DETACH: + LOG_DEBUG("Exiting\n"); + break; + } + return TRUE; +} diff --git a/src/tr2/main_exe.c b/src/tr2/main_exe.c new file mode 100644 index 000000000..0f6717b82 --- /dev/null +++ b/src/tr2/main_exe.c @@ -0,0 +1,160 @@ +#include +#include +#include +#include + +// The path to the legitimate host process +const char *m_HostProcessPath = "Tomb2.exe"; + +static bool M_FileExists(const char *path) +{ + DWORD fileAttributes = GetFileAttributes(path); + if (fileAttributes != INVALID_FILE_ATTRIBUTES + && !(fileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + return true; + } + return false; +} + +static bool M_InjectDLL(HANDLE process_handle, const char *dll_path) +{ + bool success = false; + LPVOID load_library_addr = + (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); + + fprintf(stderr, "Injecting %s\n", dll_path); + + if (!M_FileExists(dll_path)) { + fprintf(stderr, "DLL does not exist.\n"); + goto finish; + } + + LPVOID dll_path_adr = VirtualAllocEx( + process_handle, NULL, strlen(dll_path) + 1, MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE); + if (!dll_path_adr) { + fprintf(stderr, "Failed to allocate remote memory.\n"); + goto finish; + } + + if (!WriteProcessMemory( + process_handle, dll_path_adr, dll_path, strlen(dll_path) + 1, + NULL)) { + fprintf(stderr, "Failed to write remote memory.\n"); + goto finish; + } + + HANDLE remote_thread_handle = CreateRemoteThread( + process_handle, NULL, 0, (LPTHREAD_START_ROUTINE)load_library_addr, + dll_path_adr, 0, NULL); + if (remote_thread_handle == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Failed to create remote thread.\n"); + goto finish; + } + WaitForSingleObject(remote_thread_handle, INFINITE); + + VirtualFreeEx( + process_handle, dll_path_adr, strlen(dll_path) + 1, MEM_RELEASE); + CloseHandle(remote_thread_handle); + + success = true; +finish: + return success; +} + +const char *GetDLLPath(void) +{ + static char dll_path[MAX_PATH]; + GetModuleFileNameA(NULL, dll_path, MAX_PATH); + char *suffix = strstr(dll_path, ".exe"); + if (suffix != NULL) { + strcpy(suffix, ".dll"); + } + return dll_path; +} + +char *GetHostProcessArguments( + const char *host_process_path, const int32_t argc, const char *const argv[]) +{ + size_t length = 1; // null terminator + for (int32_t i = 0; i < argc; i++) { + if (i > 0) { + length++; + } + + const char *arg = i == 0 ? host_process_path : argv[i]; + if (strchr(arg, ' ')) { + length += 1; + length += strlen(arg); + length += 1; + } else { + length += strlen(arg); + } + } + + char *cmdline = malloc(length); + cmdline[0] = '\0'; + + for (int32_t i = 0; i < argc; i++) { + if (i > 0) { + strcat(cmdline, " "); + } + + const char *arg = i == 0 ? host_process_path : argv[i]; + if (strchr(arg, ' ')) { + strcat(cmdline, "\""); + strcat(cmdline, arg); + strcat(cmdline, "\""); + } else { + strcat(cmdline, arg); + } + } + + return cmdline; +} + +int32_t main(const int32_t argc, const char *const argv[]) +{ + bool success = false; + const char *dll_path = GetDLLPath(); + char *cmdline = GetHostProcessArguments(m_HostProcessPath, argc, argv); + + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + + if (!CreateProcess( + m_HostProcessPath, cmdline, NULL, NULL, FALSE, CREATE_SUSPENDED, + NULL, NULL, &si, &pi)) { + fprintf(stderr, "Failed to create the process.\n"); + goto finish; + } + + if (!M_InjectDLL(pi.hProcess, dll_path)) { + fprintf(stderr, "Failed to inject the DLL.\n"); + goto finish; + } + + if (ResumeThread(pi.hThread) == (DWORD)-1) { + fprintf(stderr, "Failed to resume the execution of the process.\n"); + goto finish; + } + + success = true; + +finish: + if (cmdline) { + free(cmdline); + cmdline = NULL; + } + if (pi.hThread) { + CloseHandle(pi.hThread); + } + if (pi.hProcess) { + CloseHandle(pi.hProcess); + } + return success ? 0 : 1; +} diff --git a/src/tr2/specific/s_audio_sample.c b/src/tr2/specific/s_audio_sample.c new file mode 100644 index 000000000..0adca3ec2 --- /dev/null +++ b/src/tr2/specific/s_audio_sample.c @@ -0,0 +1,230 @@ +#include "specific/s_audio_sample.h" + +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" +#include "lib/dsound.h" +#include "specific/s_flagged_string.h" + +#include +#include +#include + +#include +#include +#include + +typedef struct __unaligned +{ + char chunk_id[4]; + uint32_t chunk_size; + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; +} +WAVE_FMT_CHUNK; + +typedef struct { + char chunk_id[4]; + uint32_t chunk_size; +} WAVE_DATA_CHUNK; + +typedef struct { + char chunk_id[4]; + uint32_t chunk_size; +} WAVE_RIFF_CHUNK; + +typedef struct __unaligned +{ + char format[4]; + WAVE_FMT_CHUNK fmt; + WAVE_DATA_CHUNK data; +} +WAVE_MAIN; + +typedef struct __unaligned +{ + WAVE_RIFF_CHUNK riff; + WAVE_MAIN main; +} +WAVE_FILE_HEADER; + +static void M_CreateWAV( + const LPWAVEFORMATEX format, const void *data, size_t data_size, + char **buffer, size_t *buffer_size); + +static void M_CreateWAV( + const LPWAVEFORMATEX format, const void *const data, const size_t data_size, + char **const buffer, size_t *const buffer_size) +{ + + const WAVE_FILE_HEADER header = { + .riff = { + .chunk_id = {'R', 'I', 'F', 'F'}, + .chunk_size = sizeof(WAVE_MAIN) + data_size, + }, + .main = { + .format = {'W', 'A', 'V', 'E'}, + .fmt = { + .chunk_id = {'f', 'm', 't', ' '}, + .chunk_size = 16, + .audio_format = format->wFormatTag, + .num_channels = format->nChannels, + .sample_rate = format->nSamplesPerSec, + .byte_rate = format->nAvgBytesPerSec, + .block_align = format->nBlockAlign, + .bits_per_sample = format->wBitsPerSample + }, + .data = { + .chunk_id = {'d', 'a', 't', 'a'}, + .chunk_size = data_size, + } + } + }; + + *buffer_size = sizeof(header) + data_size; + *buffer = Memory_Alloc(*buffer_size); + + memcpy(*buffer, &header, sizeof(header)); + memcpy(*buffer + sizeof(header), data, data_size); +} + +const SOUND_ADAPTER_NODE *__cdecl S_Audio_Sample_GetAdapter(const GUID *guid) +{ + return NULL; +} + +void __cdecl S_Audio_Sample_CloseAllTracks(void) +{ + Audio_Sample_CloseAll(); + Audio_Sample_UnloadAll(); +} + +bool __cdecl S_Audio_Sample_Load( + int32_t sample_id, LPWAVEFORMATEX format, const void *data, + uint32_t data_size) +{ + char *wave = NULL; + size_t wave_size; + M_CreateWAV(format, data, data_size, &wave, &wave_size); + + const bool result = Audio_Sample_LoadSingle(sample_id, wave, wave_size); + Memory_FreePointer(&wave); + return result; +} + +bool __cdecl S_Audio_Sample_IsTrackPlaying(int32_t track_id) +{ + return false; +} + +int32_t __cdecl S_Audio_Sample_Play( + int32_t sample_id, int32_t volume, int32_t pitch, int32_t pan, + uint32_t flags) +{ + return -1; +} + +int32_t __cdecl S_Audio_Sample_GetFreeTrackIndex(void) +{ + return -1; +} + +void __cdecl S_Audio_Sample_AdjustTrackVolumeAndPan( + int32_t track_id, int32_t volume, int32_t pan) +{ +} + +void __cdecl S_Audio_Sample_AdjustTrackPitch(int32_t track_id, int32_t pitch) +{ +} + +void __cdecl S_Audio_Sample_CloseTrack(int32_t track_id) +{ +} + +bool __cdecl S_Audio_Sample_Init(void) +{ + return false; +} + +bool __cdecl S_Audio_Sample_DSoundEnumerate(SOUND_ADAPTER_LIST *adapter_list) +{ + return false; +} + +BOOL CALLBACK S_Audio_Sample_DSoundEnumCallback( + LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context) +{ + return TRUE; +} + +void __cdecl S_Audio_Sample_Init2(HWND hwnd) +{ +} + +bool __cdecl S_Audio_Sample_DSoundCreate(GUID *guid) +{ + return false; +} + +bool __cdecl S_Audio_Sample_DSoundBufferTest(void) +{ + return false; +} + +void __cdecl S_Audio_Sample_Shutdown(void) +{ +} + +bool __cdecl S_Audio_Sample_IsEnabled(void) +{ + return true; +} + +int32_t __cdecl S_Audio_Sample_OutPlay( + int32_t sample_id, int32_t volume, int32_t pitch, int32_t pan) +{ + return -1; +} + +int32_t __cdecl S_Audio_Sample_CalculateSampleVolume(int32_t volume) +{ + return 0; +} + +int32_t __cdecl S_Audio_Sample_CalculateSamplePan(int16_t pan) +{ + return 0; +} + +int32_t __cdecl S_Audio_Sample_OutPlayLooped( + int32_t sample_id, int32_t volume, int32_t pitch, int32_t pan) +{ + return -1; +} + +void __cdecl S_Audio_Sample_OutSetPanAndVolume( + int32_t track_id, int32_t pan, int32_t volume) +{ +} + +void __cdecl S_Audio_Sample_OutSetPitch(int32_t track_id, int32_t pitch) +{ +} + +void __cdecl S_Audio_Sample_OutCloseTrack(int32_t track_id) +{ +} + +void __cdecl S_Audio_Sample_OutCloseAllTracks(void) +{ +} + +bool __cdecl S_Audio_Sample_OutIsTrackPlaying(int32_t track_id) +{ + return false; +} diff --git a/src/tr2/specific/s_audio_sample.h b/src/tr2/specific/s_audio_sample.h new file mode 100644 index 000000000..064a0c214 --- /dev/null +++ b/src/tr2/specific/s_audio_sample.h @@ -0,0 +1,39 @@ +#pragma once + +#include "global/types.h" + +const SOUND_ADAPTER_NODE *__cdecl S_Audio_Sample_GetAdapter(const GUID *guid); +void __cdecl S_Audio_Sample_CloseAllTracks(void); +bool __cdecl S_Audio_Sample_Load( + int32_t sample_id, LPWAVEFORMATEX format, const void *data, + uint32_t data_size); +bool __cdecl S_Audio_Sample_IsTrackPlaying(int32_t track_id); +int32_t __cdecl S_Audio_Sample_Play( + int32_t sample_id, int32_t volume, int32_t pitch, int32_t pan, + uint32_t flags); +int32_t __cdecl S_Audio_Sample_GetFreeTrackIndex(void); +void __cdecl S_Audio_Sample_AdjustTrackVolumeAndPan( + int32_t track_id, int32_t volume, int32_t pan); +void __cdecl S_Audio_Sample_AdjustTrackPitch(int32_t track_id, int32_t pitch); +void __cdecl S_Audio_Sample_CloseTrack(int32_t track_id); +bool __cdecl S_Audio_Sample_Init(void); +bool __cdecl S_Audio_Sample_DSoundEnumerate(SOUND_ADAPTER_LIST *adapter_list); +BOOL CALLBACK S_Audio_Sample_DSoundEnumCallback( + LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context); +void __cdecl S_Audio_Sample_Init2(HWND hwnd); +bool __cdecl S_Audio_Sample_DSoundCreate(GUID *guid); +bool __cdecl S_Audio_Sample_DSoundBufferTest(void); +void __cdecl S_Audio_Sample_Shutdown(void); +bool __cdecl S_Audio_Sample_IsEnabled(void); +int32_t __cdecl S_Audio_Sample_OutPlay( + int32_t sample_id, int32_t volume, int32_t pitch, int32_t pan); +int32_t __cdecl S_Audio_Sample_CalculateSampleVolume(int32_t volume); +int32_t __cdecl S_Audio_Sample_CalculateSamplePan(int16_t pan); +int32_t __cdecl S_Audio_Sample_OutPlayLooped( + int32_t sample_id, int32_t volume, int32_t pitch, int32_t pan); +void __cdecl S_Audio_Sample_OutSetPanAndVolume( + int32_t track_id, int32_t pan, int32_t volume); +void __cdecl S_Audio_Sample_OutSetPitch(int32_t track_id, int32_t pitch); +void __cdecl S_Audio_Sample_OutCloseTrack(int32_t track_id); +void __cdecl S_Audio_Sample_OutCloseAllTracks(void); +bool __cdecl S_Audio_Sample_OutIsTrackPlaying(int32_t track_id); diff --git a/src/tr2/specific/s_flagged_string.c b/src/tr2/specific/s_flagged_string.c new file mode 100644 index 000000000..44833f240 --- /dev/null +++ b/src/tr2/specific/s_flagged_string.c @@ -0,0 +1,49 @@ +#include "specific/s_flagged_string.h" + +#include "global/types.h" + +void __thiscall S_FlaggedString_Create(STRING_FLAGGED *string, int32_t size) +{ + string->content = malloc(size); + if (string->content != NULL) { + *string->content = '\0'; + string->is_valid = true; + } +} + +void __thiscall S_FlaggedString_InitAdapter(DISPLAY_ADAPTER *adapter) +{ + S_FlaggedString_Create(&adapter->driver_desc, 256); + S_FlaggedString_Create(&adapter->driver_name, 256); +} + +void __thiscall S_FlaggedString_Delete(STRING_FLAGGED *string) +{ + if (string->is_valid && string->content) { + free(string->content); + string->content = NULL; + string->is_valid = false; + } +} + +bool S_FlaggedString_Copy(STRING_FLAGGED *dst, STRING_FLAGGED *src) +{ + if (dst == NULL || src == NULL || dst == src || !src->is_valid) { + return false; + } + + size_t src_len = lstrlen(src->content); + dst->is_valid = false; + dst->content = malloc(src_len + 1); + if (dst->content == NULL) { + return false; + } + + if (src_len > 0) { + lstrcpy(dst->content, src->content); + } else { + *dst->content = 0; + } + dst->is_valid = true; + return true; +} diff --git a/src/tr2/specific/s_flagged_string.h b/src/tr2/specific/s_flagged_string.h new file mode 100644 index 000000000..50cb325ea --- /dev/null +++ b/src/tr2/specific/s_flagged_string.h @@ -0,0 +1,10 @@ +#pragma once + +#include "global/types.h" + +#include + +void __thiscall S_FlaggedString_Create(STRING_FLAGGED *string, int32_t size); +void __thiscall S_FlaggedString_Delete(STRING_FLAGGED *string); +void __thiscall S_FlaggedString_InitAdapter(DISPLAY_ADAPTER *adapter); +bool S_FlaggedString_Copy(STRING_FLAGGED *dst, STRING_FLAGGED *src); diff --git a/src/tr2/specific/s_input.c b/src/tr2/specific/s_input.c new file mode 100644 index 000000000..135088972 --- /dev/null +++ b/src/tr2/specific/s_input.c @@ -0,0 +1,515 @@ +#include "specific/s_input.h" + +#include "decomp/decomp.h" +#include "game/console/common.h" +#include "game/gameflow/gameflow_new.h" +#include "game/input.h" +#include "game/inventory/backpack.h" +#include "game/lara/control.h" +#include "global/const.h" +#include "global/funcs.h" +#include "global/vars.h" + +#include + +#include + +#define KEY_DOWN(key) ((g_DIKeys[(key)] & 0x80)) + +INPUT_LAYOUT g_Layout[2] = { + { .key = { + DIK_UP, + DIK_DOWN, + DIK_LEFT, + DIK_RIGHT, + DIK_DELETE, + DIK_NEXT, + DIK_RSHIFT, + DIK_RMENU, + DIK_RCONTROL, + DIK_SPACE, + DIK_PERIOD, + DIK_NUMPAD0, + DIK_END, + DIK_ESCAPE, + DIK_SLASH, + } }, + { .key = { + DIK_NUMPAD8, + DIK_NUMPAD2, + DIK_NUMPAD4, + DIK_NUMPAD6, + DIK_NUMPAD7, + DIK_NUMPAD9, + DIK_NUMPAD1, + DIK_ADD, + DIK_NUMPADENTER, + DIK_NUMPAD3, + DIK_SUBTRACT, + DIK_NUMPAD0, + DIK_NUMPAD5, + DIK_DECIMAL, + DIK_SLASH, + } } +}; + +static bool m_IsF3Pressed = false; +static bool m_IsF4Pressed = false; +static bool m_IsF7Pressed = false; +static bool m_IsF8Pressed = false; +static bool m_IsF11Pressed = false; +static int32_t m_MediPackCooldown = 0; + +static bool M_KbdKey(int32_t layout_num, INPUT_ROLE role); +static bool M_JoyKey(int32_t layout_num, INPUT_ROLE role); + +static bool M_KbdKey(const int32_t layout_num, const INPUT_ROLE role) +{ + uint16_t key = Input_GetAssignedKey(layout_num, role); + if (key >= 0x100) { + return false; + } + + if (KEY_DOWN(key)) { + return true; + } + switch (key) { + case DIK_RCONTROL: + return KEY_DOWN(DIK_LCONTROL); + case DIK_LCONTROL: + return KEY_DOWN(DIK_RCONTROL); + case DIK_RSHIFT: + return KEY_DOWN(DIK_LSHIFT); + case DIK_LSHIFT: + return KEY_DOWN(DIK_RSHIFT); + case DIK_RMENU: + return KEY_DOWN(DIK_LMENU); + case DIK_LMENU: + return KEY_DOWN(DIK_RMENU); + } + return false; +} + +static bool M_JoyKey(const int32_t layout_num, const INPUT_ROLE role) +{ + uint16_t key = Input_GetAssignedKey(layout_num, role); + if (key < 0x100) { + return false; + } + return (g_JoyKeys & (1 << key)) != 0; +} + +bool __cdecl S_Input_Key(const INPUT_ROLE role) +{ + return M_KbdKey(1, role) || M_JoyKey(1, role) + || (!g_ConflictLayout[role] && M_KbdKey(0, role)); +} + +// TODO: refactor me!!! +bool __cdecl S_Input_Update(void) +{ + WinVidSpinMessageLoop(0); + WinInReadKeyboard(g_DIKeys); + + int32_t joy_xpos = 0; + int32_t joy_ypos = 0; + g_JoyKeys = WinInReadJoystick(&joy_xpos, &joy_ypos); + + INPUT_STATE input = 0; + if (joy_xpos < -8) { + input |= IN_LEFT; + } else if (joy_xpos > 8) { + input |= IN_RIGHT; + } + + if (joy_ypos > 8) { + input |= IN_BACK; + } else if (joy_ypos < -8) { + input |= IN_FORWARD; + } + + if (S_Input_Key(INPUT_ROLE_FORWARD)) { + input |= IN_FORWARD; + } + if (S_Input_Key(INPUT_ROLE_BACK)) { + input |= IN_BACK; + } + if (S_Input_Key(INPUT_ROLE_LEFT)) { + input |= IN_LEFT; + } + if (S_Input_Key(INPUT_ROLE_RIGHT)) { + input |= IN_RIGHT; + } + if (S_Input_Key(INPUT_ROLE_STEP_LEFT)) { + input |= IN_STEP_LEFT; + } + if (S_Input_Key(INPUT_ROLE_STEP_RIGHT)) { + input |= IN_STEP_RIGHT; + } + if (S_Input_Key(INPUT_ROLE_SLOW)) { + input |= IN_SLOW; + } + if (S_Input_Key(INPUT_ROLE_JUMP)) { + input |= IN_JUMP; + } + + if (S_Input_Key(INPUT_ROLE_ACTION)) { + input |= IN_ACTION; + } + if (S_Input_Key(INPUT_ROLE_DRAW_WEAPON)) { + input |= IN_DRAW; + } + if (S_Input_Key(INPUT_ROLE_FLARE)) { + input |= IN_FLARE; + } + if (S_Input_Key(INPUT_ROLE_LOOK)) { + input |= IN_LOOK; + } + if (S_Input_Key(INPUT_ROLE_ROLL)) { + input |= IN_ROLL; + } + + if (S_Input_Key(INPUT_ROLE_CONSOLE)) { + input |= IN_CONSOLE; + } + + if (S_Input_Key(INPUT_ROLE_OPTION) && g_Camera.type != CAM_CINEMATIC) { + input |= IN_OPTION; + } + if ((input & IN_FORWARD) && (input & IN_BACK)) { + input |= IN_ROLL; + } + + if (KEY_DOWN(DIK_RETURN) || (input & IN_ACTION)) { + input |= IN_SELECT; + } + if (KEY_DOWN(DIK_ESCAPE)) { + input |= IN_DESELECT; + } + + if ((input & IN_LEFT) && (input & IN_RIGHT)) { + input &= ~IN_LEFT; + input &= ~IN_RIGHT; + } + + if (g_IsFMVPlaying || Console_IsOpened()) { + g_Input = input; + return g_IsGameToExit; + } + + if (g_GameInfo.current_level.type != GFL_DEMO) { + if (KEY_DOWN(DIK_1) && Inv_RequestItem(O_PISTOL_OPTION)) { + g_Lara.request_gun_type = LGT_PISTOLS; + } else if (KEY_DOWN(DIK_2) && Inv_RequestItem(O_SHOTGUN_OPTION)) { + g_Lara.request_gun_type = LGT_SHOTGUN; + } else if (KEY_DOWN(DIK_3) && Inv_RequestItem(O_MAGNUM_OPTION)) { + g_Lara.request_gun_type = LGT_MAGNUMS; + } else if (KEY_DOWN(DIK_4) && Inv_RequestItem(O_UZI_OPTION)) { + g_Lara.request_gun_type = LGT_UZIS; + } else if (KEY_DOWN(DIK_5) && Inv_RequestItem(O_HARPOON_OPTION)) { + g_Lara.request_gun_type = LGT_HARPOON; + } else if (KEY_DOWN(DIK_6) && Inv_RequestItem(O_M16_OPTION)) { + g_Lara.request_gun_type = LGT_M16; + } else if (KEY_DOWN(DIK_7) && Inv_RequestItem(O_GRENADE_OPTION)) { + g_Lara.request_gun_type = LGT_GRENADE; + } else if (KEY_DOWN(DIK_0) && Inv_RequestItem(O_FLARES_OPTION)) { + g_Lara.request_gun_type = LGT_FLARE; + } + + if (KEY_DOWN(DIK_8) && Inv_RequestItem(O_SMALL_MEDIPACK_OPTION)) { + if (m_MediPackCooldown == 0) { + Lara_UseItem(O_SMALL_MEDIPACK_OPTION); + } + m_MediPackCooldown = 15; + } + if (KEY_DOWN(DIK_9) && Inv_RequestItem(O_LARGE_MEDIPACK_OPTION)) { + if (m_MediPackCooldown == 0) { + Lara_UseItem(O_LARGE_MEDIPACK_OPTION); + } + m_MediPackCooldown = 15; + } + + if (m_MediPackCooldown > 0) { + m_MediPackCooldown--; + } + } + + if (KEY_DOWN(DIK_S)) { + Screenshot(g_PrimaryBufferSurface); + } + + const bool is_shift_pressed = KEY_DOWN(DIK_LSHIFT) || KEY_DOWN(DIK_RSHIFT); + + // software renderer keys… + if (g_SavedAppSettings.render_mode == RM_SOFTWARE) { + // toggle triple buffering (F7) or perspective correction (Shift+F7) + if (KEY_DOWN(DIK_F7)) { + if (!m_IsF7Pressed) { + m_IsF7Pressed = true; + APP_SETTINGS new_settings = g_SavedAppSettings; + if (!is_shift_pressed) { + new_settings.perspective_correct = + !new_settings.perspective_correct; + g_PerspectiveDistance = + g_SavedAppSettings.perspective_correct + ? SW_DETAIL_HIGH + : SW_DETAIL_MEDIUM; + } else if (g_SavedAppSettings.fullscreen) { + new_settings.triple_buffering = + !new_settings.triple_buffering; + } + GameApplySettings(&new_settings); + } + } else { + m_IsF7Pressed = false; + } + } + + // hardware renderer keys… + if (g_SavedAppSettings.render_mode == RM_HARDWARE) { + // toggle triple buffering (Shift+F7) or zbuffer (F7) + if (KEY_DOWN(DIK_F7)) { + if (!m_IsF7Pressed) { + m_IsF7Pressed = true; + APP_SETTINGS new_settings = g_SavedAppSettings; + if (!is_shift_pressed) { + new_settings.zbuffer = !new_settings.zbuffer; + } else if (g_SavedAppSettings.fullscreen) { + new_settings.triple_buffering = + !new_settings.triple_buffering; + } + GameApplySettings(&new_settings); + } + } else { + m_IsF7Pressed = false; + } + + // toggle perspective correction (Shift+F8) or bilinear filter (F8) + if (KEY_DOWN(DIK_F8)) { + if (!m_IsF8Pressed) { + m_IsF8Pressed = true; + APP_SETTINGS new_settings = g_SavedAppSettings; + if (!is_shift_pressed) { + new_settings.bilinear_filtering = + !new_settings.bilinear_filtering; + } else { + new_settings.perspective_correct = + !new_settings.perspective_correct; + } + GameApplySettings(&new_settings); + } + } else { + m_IsF8Pressed = false; + } + + // toggle dither (F11) + if (KEY_DOWN(DIK_F11)) { + if (!m_IsF11Pressed) { + m_IsF11Pressed = true; + APP_SETTINGS new_settings = g_SavedAppSettings; + new_settings.dither = !new_settings.dither; + GameApplySettings(&new_settings); + } + } else { + m_IsF11Pressed = false; + } + } + + if (!g_IsVidModeLock && KEY_DOWN(DIK_F12)) { + APP_SETTINGS new_settings = g_SavedAppSettings; + // toggle fullscreen (F12) + if (!is_shift_pressed) { + new_settings.fullscreen = !new_settings.fullscreen; + if (g_SavedAppSettings.fullscreen) { + const int32_t win_width = MAX(g_PhdWinWidth, 320); + const int32_t win_height = MAX(g_PhdWinHeight, 240); + new_settings.window_height = win_height; + new_settings.window_width = + CalculateWindowWidth(win_width, win_height); + new_settings.triple_buffering = 0; + GameApplySettings(&new_settings); + + g_GameSizer = 1.0; + g_GameSizerCopy = 1.0; + setup_screen_size(); + } else { + const DISPLAY_MODE_LIST *const mode_list = + new_settings.render_mode == RM_HARDWARE + ? &g_CurrentDisplayAdapter.hw_disp_mode_list + : &g_CurrentDisplayAdapter.sw_disp_mode_list; + + if (mode_list->count > 0) { + const DISPLAY_MODE target_mode = { + .width = g_GameVid_Width, + .height = g_GameVid_Height, + .bpp = g_GameVid_BPP, + .vga = VGA_NO_VGA, + }; + + const DISPLAY_MODE_NODE *mode = NULL; + for (mode = mode_list->head; mode != NULL; + mode = mode->next) { + if (!CompareVideoModes(&mode->body, &target_mode)) { + break; + } + } + + if (mode == NULL) { + mode = mode_list->tail; + } + + new_settings.video_mode = mode; + GameApplySettings(&new_settings); + } + } + // toggle renderer mode (Shift+F12) + } else if (!g_Inv_IsActive) { + new_settings.render_mode = new_settings.render_mode == RM_HARDWARE + ? RM_SOFTWARE + : RM_HARDWARE; + + const DISPLAY_MODE_LIST *const mode_list = + new_settings.render_mode == RM_HARDWARE + ? &g_CurrentDisplayAdapter.hw_disp_mode_list + : &g_CurrentDisplayAdapter.sw_disp_mode_list; + + if (mode_list->count > 0) { + const DISPLAY_MODE target_mode = { + .width = g_GameVid_Width, + .height = g_GameVid_Height, + .bpp = new_settings.render_mode == RM_HARDWARE ? 16 : 8, + .vga = VGA_NO_VGA, + }; + + const DISPLAY_MODE_NODE *mode = NULL; + for (mode = mode_list->head; mode != NULL; mode = mode->next) { + if (!CompareVideoModes(&mode->body, &target_mode)) { + break; + } + } + + if (mode == NULL) { + mode = mode_list->tail; + } + + new_settings.video_mode = mode; + new_settings.fullscreen = 1; + GameApplySettings(&new_settings); + } + } + } + + if (!g_IsVidSizeLock && g_Camera.type != CAM_CINEMATIC + && !(g_GameFlow.screen_sizing_disabled)) { + APP_SETTINGS new_settings = g_SavedAppSettings; + + // decrease resolution or BPP (F1) + if (KEY_DOWN(DIK_F1) && new_settings.fullscreen) { + const DISPLAY_MODE_NODE *const current_mode = + new_settings.video_mode; + const DISPLAY_MODE_NODE *mode = current_mode; + if (mode != NULL) { + mode = mode->previous; + } + + if (new_settings.render_mode == RM_HARDWARE) { + for (; mode != NULL; mode = mode->previous) { + if (is_shift_pressed) { + if (mode->body.width == current_mode->body.width + && mode->body.height == current_mode->body.height + && mode->body.vga == current_mode->body.vga + && mode->body.bpp < current_mode->body.bpp) { + break; + } + } else if ( + mode->body.vga == current_mode->body.vga + && mode->body.bpp == current_mode->body.bpp) { + break; + } + } + } + + if (mode != NULL) { + new_settings.video_mode = mode; + GameApplySettings(&new_settings); + } + } + + // increase resolution (F2) + if (KEY_DOWN(DIK_F2) && new_settings.fullscreen) { + const DISPLAY_MODE_NODE *const current_mode = + new_settings.video_mode; + const DISPLAY_MODE_NODE *mode = current_mode; + if (mode != NULL) { + mode = mode->next; + } + + if (new_settings.render_mode == RM_HARDWARE) { + for (; mode != NULL; mode = mode->next) { + if (is_shift_pressed) { + if (mode->body.width == current_mode->body.width + && mode->body.height == current_mode->body.height + && mode->body.vga == current_mode->body.vga + && mode->body.bpp > current_mode->body.bpp) { + break; + } + } else if ( + mode->body.vga == current_mode->body.vga + && mode->body.bpp == current_mode->body.bpp) { + break; + } + } + } + if (mode != NULL) { + new_settings.video_mode = mode; + GameApplySettings(&new_settings); + } + } + + // decrease inner screen size (F3) + if (KEY_DOWN(DIK_F3)) { + if (!m_IsF3Pressed) { + m_IsF3Pressed = true; + if (g_SavedAppSettings.fullscreen) { + DecreaseScreenSize(); + } + } + } else { + m_IsF3Pressed = false; + } + + // increase inner screen size (F4) + if (KEY_DOWN(DIK_F4)) { + if (!m_IsF4Pressed) { + m_IsF4Pressed = true; + if (g_SavedAppSettings.fullscreen) { + IncreaseScreenSize(); + } + } + } else { + m_IsF4Pressed = false; + } + } + + if (!g_GameFlow.load_save_disabled) { + if (KEY_DOWN(DIK_F5)) { + input |= IN_SAVE; + } else if (KEY_DOWN(DIK_F6)) { + input |= IN_LOAD; + } + } + + g_Input = input; + return g_IsGameToExit; +} + +bool Input_IsAnythingPressed(void) +{ + WinInReadKeyboard(g_DIKeys); + for (int32_t i = 0; i < 256; i++) { + if (KEY_DOWN(i)) { + return true; + } + } + return false; +} diff --git a/src/tr2/specific/s_input.h b/src/tr2/specific/s_input.h new file mode 100644 index 000000000..14d316d80 --- /dev/null +++ b/src/tr2/specific/s_input.h @@ -0,0 +1,6 @@ +#pragma once + +#include "game/input.h" + +bool __cdecl S_Input_Key(INPUT_ROLE role); +bool __cdecl S_Input_Update(void);