diff --git a/DOCUMENTATIONS/HOD_PSX.pdf b/Docs/HOD_PSX.pdf
similarity index 100%
rename from DOCUMENTATIONS/HOD_PSX.pdf
rename to Docs/HOD_PSX.pdf
diff --git a/DOCUMENTATIONS/HOD_Win32.pdf.pdf b/Docs/HOD_Win32.pdf.pdf
similarity index 100%
rename from DOCUMENTATIONS/HOD_Win32.pdf.pdf
rename to Docs/HOD_Win32.pdf.pdf
diff --git a/DOCUMENTATIONS/_LINKS/LET'S_PLAY.url b/Docs/_Links/LET'S_PLAY.url
similarity index 100%
rename from DOCUMENTATIONS/_LINKS/LET'S_PLAY.url
rename to Docs/_Links/LET'S_PLAY.url
diff --git a/DOCUMENTATIONS/_LINKS/WEBSITE.url b/Docs/_Links/WEBSITE.url
similarity index 100%
rename from DOCUMENTATIONS/_LINKS/WEBSITE.url
rename to Docs/_Links/WEBSITE.url
diff --git a/CHANGELOG.MD b/GitHub/CHANGELOG.MD
similarity index 73%
rename from CHANGELOG.MD
rename to GitHub/CHANGELOG.MD
index fe07a0b..c1b565c 100644
--- a/CHANGELOG.MD
+++ b/GitHub/CHANGELOG.MD
@@ -1,4 +1,10 @@
# CHANGELOG
+> release 0.2.9.0
+* Clean up repo
+* Add Nintendo Wii and PSP executable
+* Add Xbox's port from Modern Vintage Gamer
+* Move to release 0.2.9C from Gregory Montoir
+
> release 0.2.5.1
* First Linux release
@@ -26,6 +32,39 @@
* Initial repo
## ORIGINAL CHANGELOG FROM GREGORY MONTOIR
+> release 0.2.9c
+* added original sound limiter (16 channels)
+* fixed checkpoint 7 for 'lar1' screen 19
+* fixed some bounding box checks
+* fixed sound glitches when resuming the game after a cutscene
+
+> release 0.2.9b
+* added PSX hint screens
+* added 'volume' submenu
+* fixed sounds volume and panning
+* fixed walk on chain bridge in 'lar1' screen 3
+* fixed gate not closing in 'lar2' screen 8
+
+> release 0.2.9
+* added PSP and Wii platforms specific code
+* added support for PC demo v1.0 data files
+* fixed screen state for 'fort' screens 16 and 17
+* fixed checkpoint for 'pwr1' screen 21
+* fixed PAF animation glitches with last frame
+
+> release 0.2.8
+* added PSX background overlays (MDEC)
+* fixed crash playing PAF animation #3 with Italian PC data files
+
+> release 0.2.7
+* added 'projection' submenu
+* added PSX backgrounds (MDEC)
+* fixed menu on big endian platforms
+
+> release 0.2.6
+* added initial code for menu
+* added PSX sounds (SPU ADPCM)
+* fixed skull animation in 'rock' screen 18
> release 0.2.5
* fixed spiders and worms movement
diff --git a/COMPATIBILITY_RELEASE.MD b/GitHub/COMPATIBILITY_RELEASE.MD
similarity index 98%
rename from COMPATIBILITY_RELEASE.MD
rename to GitHub/COMPATIBILITY_RELEASE.MD
index 30c6de0..535b033 100644
--- a/COMPATIBILITY_RELEASE.MD
+++ b/GitHub/COMPATIBILITY_RELEASE.MD
@@ -1,145 +1,145 @@
-# Compatibility release
-
This file lists released versions of 'Heart of Darkness' from Amazing Studio.
-
-## Heart of Darkness French (Win32) files:
-* dark_hod.mst: 6c2d93b2e31c299215d0fdf05ac1e8e8e95e9042
-* dark_hod.sss: 8d5b19842e551b8ec73bda411c210cc96e08569a
-* fort_hod.lvl: 82456ed6e29780b5b8031a67ba3dddb8da813c19
-* fort_hod.mst: ce55095902ade9f1d8a198f271d0946d6228b90d
-* fort_hod.sss: 6ff572b553d93040c9cad74891db05b2cc8267c6
-* hod.paf: 6df823a778ed0df275217692fd814a7408b725cb
-* hodwin32.exe: 02ae85c179f175c5a42fadb22ba9edaf4f62be14
- > securom: true
-
- > version_info:
- - Comments: Release Candidate 0
- - CompanyName: Amazing Studio - 9 rue d' Enhgien - 75010 Paris FRANCE - Email: hod@amazingstudio.com
- - FileDescription: Heart Of Darkness
- - FileVersion: 1, 1, 0, 0
- - InternalName: Heart Of Darkness
- - LegalCopyright: (c) 1998 by Amazing Studio & Infogrames
- - LegalTrademarks: by Amazing Studio - All rights reserved
- - OriginalFilename: Heart Of Darkness
- - PrivateBuild: HEARTMM5511
- - ProductName: Heart Of Darkness - Windows 95 / 98 & NT - DirectX 3.x or greater
- - ProductVersion: 1, 1, 0, 0
- - SpecialBuild: VC5.0 Sp3
-* isld_hod.lvl: f78e9316f2187c0ca9e42abe8a2242b3d3e6feaf
-* isld_hod.mst: 448e5dd5bbe59621279562d8695c9a864cb4c286
-* isld_hod.sss: 61c5da6dfcf80684c56baebddb04125ca0c1f209
-* lar1_hod.lvl: 84f7b33a9b57f2afa68063e0128512991f86dfa0
-* lar1_hod.mst: 9db53a4a75327eeca45ffa5b761a9f04af5e5204
-* lar1_hod.sss: 72667991fde3caf36978da1fa929695de80abe4b
-* lar2_hod.lvl: 999b2548490f586f955c9f5c6db95f88d49102f3
-* lar2_hod.mst: 333ea4e6be6ca9aad735fafe59177546aab41c4e
-* lar2_hod.sss: c5c655b718ead1d2eb460a058df7eb3b795dea9e
-* lava_hod.lvl: 2921eb2f78354e79c1257ef55543bb1a1492e914
-* lava_hod.mst: c05ce42c0abdbb2dbfc109cbb4ea7eee1937445d
-* lava_hod.sss: 8fc8d2c86f67e13ca840d604c603249b20723f85
-* pwr1_hod.lvl: cd6489e11914770d95034aa6afaa2fc67f035ce8
-* pwr1_hod.mst: f821e5686ae27b2ed5000ea4b38a464ecebe44a7
-* pwr1_hod.sss: cbb39e1bfd24dcbd5bf65f9e0b92faaa9b6b02a6
-* pwr2_hod.lvl: 7263ecf8d51d45c2c62dc11701eac03b67738fd8
-* pwr2_hod.mst: 132b29e758a28529009e979ef31a29a42d0a5c4b
-* pwr2_hod.sss: 24342241b4353777cc22696be8a684d635f9c809
-* rock_hod.lvl: cdb3d7ad1ae13f2234380f79bc8ec57e1e505cef
-* rock_hod.mst: 5b49637348f6c6a2737a64cacf01f9bcef8e83f6
-* rock_hod.sss: 55c841f6c091c2ae68f0636c075c66405d00cc6a
-* setup.dat: 1d476d896cafd3d4af00b15eb2eb695a804a0662
-
-## Heart of Darkness Demo V1.2 (Win32) files:
- - hod_demo.paf: 50a0942256b17ed26f3b0f59f4cf7570a6eb8688
- - hodwin32.exe: e305dbfc4d6c568fe2cb074cf61e0fb3a489c7ed
- securom: false
- version_info:
- - Comments: Release 1.2
- - CompanyName: Amazing Studio - 9 rue d' Enhgien - 75010 Paris FRANCE - Email: hod@amazingstudio.com
- - FileDescription: Heart Of Darkness
- - FileVersion: 1, 2, 0, 0
- - InternalName: Heart Of Darkness
- - LegalCopyright: (c) 1998 by Amazing Studio & Infogrames
- - LegalTrademarks: by Amazing Studio - All rights reserved
- - OriginalFilename: Heart Of Darkness
- - PrivateBuild: HEARTMM5511
- - ProductName: Heart Of Darkness - Windows 95 / 98 & NT - DirectX 3.x or greater
- - ProductVersion: 1, 2, 0, 0
- - SpecialBuild: VCpp 6.0
- - rock_hod.lvl: 9fddeac91b8de1cbbce35fa3330f674cb5e0e7c7
- - rock_hod.mst: e7fbfcde10c08aa64ae1398fc95257891863790b
- - rock_hod.sss: 982323a992c998e7c741d4d8fcaba11998d8072c
- - setup.dat: 24bbb82fe25c9c52c5a2c471db3646acd0dd4f1f
-
-## Heart of Darkness Mini-Game V1.3 Kellogg's (Win32) files:
- - hod_demo.paf: 2229d9743c1a4064561ff1a1530d6ba79ebab5a7
- - hod_demo.paf: e60599c35283d24a1938ba0c09963c6bf756ab4d
- - hodwin32.exe: af2837fdb99d8a35ec6b8732bc84e8e162ab7cfd
- securom: false
- version_info:
- - Comments: Release 1.3
- - CompanyName: Amazing Studio - 9 rue d' Enhgien - 75010 Paris FRANCE - Email: hod@amazingstudio.com
- - FileDescription: Heart Of Darkness
- - FileVersion: 1, 3, 0, 0
- - InternalName: Heart Of Darkness
- - LegalCopyright: (c) 1998 by Amazing Studio & Infogrames
- - LegalTrademarks: by Amazing Studio - All rights reserved
- - OriginalFilename: Heart Of Darkness
- - PrivateBuild: HEARTMM5511
- - ProductName: Heart Of Darkness - Windows 95 / 98 & NT - DirectX 3.x or greater
- - ProductVersion: 1, 3, 0, 0
- - SpecialBuild: VCpp 6.0
- - rock_hod.lvl: 9fddeac91b8de1cbbce35fa3330f674cb5e0e7c7
- - rock_hod.mst: 24dee01526c06b6f003b2c8e22e4d5787df333c8
- - rock_hod.sss: ec683718d26bc1e0b4a254215621dd6c6c3e60b0
- - setup.dat: 957567bcc4ff5650d85fe543d34ff2f773bb8aca
-
-## Heart of Darkness Coca Cola Edition (Win32) files:
- - fort_hod.lvl: d08d11b148786ef4b7391ac7c877186ec4721980
- - fort_hod.mst: 590a577830747d2ca1bee3fe3c5212e2a30e8dfa
- - fort_hod.sss: 93120414e8077a3a205092a99001457d221e0a11
- - hod_demo2.paf: a31d6df563f155147759de4730b7bcb4d5cb99c0
- - hod_res.dll: 0b85a0b96f14d0da7becb76fb7cff03e4f865165
- - hodwin32.exe: 6d1866ddd6310607afe9694b564d2fb81878183a
- securom: false
- version_info:
- - Comments: Release 1.4.2
- - CompanyName: Amazing Studio - 9 rue d' Enhgien - 75010 Paris FRANCE - Email:webmaster@amazingstudio.com
- - FileDescription: Heart Of Darkness
- - FileVersion: 1, 4, 2, 0
- - InternalName: Heart Of Darkness
- - LegalCopyright: (c) 1999 by Amazing Studio & Infogrames
- - LegalTrademarks: by Amazing Studio - All rights reserved
- - OriginalFilename: Heart Of Darkness
- - PrivateBuild: HEARTMMCOCA
- - ProductName: Heart Of Darkness - Windows 95 / 98 & NT - DirectX 3.x or greater
- - ProductVersion: 1, 4, 2, 0
- - SpecialBuild: VCpp 6.0 sp3
- - rock_hod.lvl: 9fddeac91b8de1cbbce35fa3330f674cb5e0e7c7
- - rock_hod.mst: 24dee01526c06b6f003b2c8e22e4d5787df333c8
- - rock_hod.sss: ec683718d26bc1e0b4a254215621dd6c6c3e60b0
- - setup.dat: e6d1ae87b991d3cf623b8ec9fb33cdd2a3d4a1b4
-
-## Heart of Darkness Demo (PSX) SLED_013.51 files:
- - rock_hod.lvl: 9cbc3bad9efe5f21f66c33d9872e29a1bd7b4c0c
- - rock_hod.mst: e7fbfcde10c08aa64ae1398fc95257891863790b
- - setup.dax: e091dddd74dc00ccf57a48dc8495ed95cde6a42d
-
-## Heart of Darkness (PSX) SLUS_006.96 files:
- - dark_hod.lvl: f6482575f1d4d2b6aa8910c709681a714edfba6b
- - dark_hod.mst: a97ac13edde17fd33cb06e9907139949c8b79cdb
- - fort_hod.lvl: e94a21bc4f779dba658ef5ab45f1ad15880913d8
- - fort_hod.mst: f7423c3d277650ac36378f09f59034cec28b6b34
- - isld_hod.lvl: c0fbd02deea136d57b60bc8a7c3df69741491335
- - isld_hod.mst: 9ff92346444a66833eb2f8a1b3853ad788d1777b
- - lar1_hod.lvl: 575dde976affeedfa5f1637a34d5efdda3218778
- - lar1_hod.mst: b36ec9ca6122edd72a52543afad4c8bcd23ca03a
- - lar2_hod.lvl: dc907525ef0859be25dca20746e43edaf06b073b
- - lar2_hod.mst: 266edb49e8846fc8ae883dfe5ca269e0b7651e82
- - lava_hod.lvl: 8412a41febf1ca5d8140a7344f758c8cad924951
- - lava_hod.mst: e4584f4c893a6977e01b3f4f3b2cdbd23a96bbac
- - pwr1_hod.lvl: f6c60f57de3ec1429286531451a6ca957c8f2cab
- - pwr1_hod.mst: 2383746cee299ef57b49db5367dfadb2a6a7bbe8
- - pwr2_hod.lvl: 46c30e728b28f0bb2c976832220cd6d3d4050b0e
- - pwr2_hod.mst: c82e435630be6799f469a460f5fc982a998854da
- - rock_hod.lvl: 9cbc3bad9efe5f21f66c33d9872e29a1bd7b4c0c
- - rock_hod.mst: e7fbfcde10c08aa64ae1398fc95257891863790b
+# Compatibility release
+ This file lists released versions of 'Heart of Darkness' from Amazing Studio.
+
+## Heart of Darkness French (Win32) files:
+* dark_hod.mst: 6c2d93b2e31c299215d0fdf05ac1e8e8e95e9042
+* dark_hod.sss: 8d5b19842e551b8ec73bda411c210cc96e08569a
+* fort_hod.lvl: 82456ed6e29780b5b8031a67ba3dddb8da813c19
+* fort_hod.mst: ce55095902ade9f1d8a198f271d0946d6228b90d
+* fort_hod.sss: 6ff572b553d93040c9cad74891db05b2cc8267c6
+* hod.paf: 6df823a778ed0df275217692fd814a7408b725cb
+* hodwin32.exe: 02ae85c179f175c5a42fadb22ba9edaf4f62be14
+ > securom: true
+
+ > version_info:
+ - Comments: Release Candidate 0
+ - CompanyName: Amazing Studio - 9 rue d' Enhgien - 75010 Paris FRANCE - Email: hod@amazingstudio.com
+ - FileDescription: Heart Of Darkness
+ - FileVersion: 1, 1, 0, 0
+ - InternalName: Heart Of Darkness
+ - LegalCopyright: (c) 1998 by Amazing Studio & Infogrames
+ - LegalTrademarks: by Amazing Studio - All rights reserved
+ - OriginalFilename: Heart Of Darkness
+ - PrivateBuild: HEARTMM5511
+ - ProductName: Heart Of Darkness - Windows 95 / 98 & NT - DirectX 3.x or greater
+ - ProductVersion: 1, 1, 0, 0
+ - SpecialBuild: VC5.0 Sp3
+* isld_hod.lvl: f78e9316f2187c0ca9e42abe8a2242b3d3e6feaf
+* isld_hod.mst: 448e5dd5bbe59621279562d8695c9a864cb4c286
+* isld_hod.sss: 61c5da6dfcf80684c56baebddb04125ca0c1f209
+* lar1_hod.lvl: 84f7b33a9b57f2afa68063e0128512991f86dfa0
+* lar1_hod.mst: 9db53a4a75327eeca45ffa5b761a9f04af5e5204
+* lar1_hod.sss: 72667991fde3caf36978da1fa929695de80abe4b
+* lar2_hod.lvl: 999b2548490f586f955c9f5c6db95f88d49102f3
+* lar2_hod.mst: 333ea4e6be6ca9aad735fafe59177546aab41c4e
+* lar2_hod.sss: c5c655b718ead1d2eb460a058df7eb3b795dea9e
+* lava_hod.lvl: 2921eb2f78354e79c1257ef55543bb1a1492e914
+* lava_hod.mst: c05ce42c0abdbb2dbfc109cbb4ea7eee1937445d
+* lava_hod.sss: 8fc8d2c86f67e13ca840d604c603249b20723f85
+* pwr1_hod.lvl: cd6489e11914770d95034aa6afaa2fc67f035ce8
+* pwr1_hod.mst: f821e5686ae27b2ed5000ea4b38a464ecebe44a7
+* pwr1_hod.sss: cbb39e1bfd24dcbd5bf65f9e0b92faaa9b6b02a6
+* pwr2_hod.lvl: 7263ecf8d51d45c2c62dc11701eac03b67738fd8
+* pwr2_hod.mst: 132b29e758a28529009e979ef31a29a42d0a5c4b
+* pwr2_hod.sss: 24342241b4353777cc22696be8a684d635f9c809
+* rock_hod.lvl: cdb3d7ad1ae13f2234380f79bc8ec57e1e505cef
+* rock_hod.mst: 5b49637348f6c6a2737a64cacf01f9bcef8e83f6
+* rock_hod.sss: 55c841f6c091c2ae68f0636c075c66405d00cc6a
+* setup.dat: 1d476d896cafd3d4af00b15eb2eb695a804a0662
+
+## Heart of Darkness Demo V1.2 (Win32) files:
+ - hod_demo.paf: 50a0942256b17ed26f3b0f59f4cf7570a6eb8688
+ - hodwin32.exe: e305dbfc4d6c568fe2cb074cf61e0fb3a489c7ed
+ securom: false
+ version_info:
+ - Comments: Release 1.2
+ - CompanyName: Amazing Studio - 9 rue d' Enhgien - 75010 Paris FRANCE - Email: hod@amazingstudio.com
+ - FileDescription: Heart Of Darkness
+ - FileVersion: 1, 2, 0, 0
+ - InternalName: Heart Of Darkness
+ - LegalCopyright: (c) 1998 by Amazing Studio & Infogrames
+ - LegalTrademarks: by Amazing Studio - All rights reserved
+ - OriginalFilename: Heart Of Darkness
+ - PrivateBuild: HEARTMM5511
+ - ProductName: Heart Of Darkness - Windows 95 / 98 & NT - DirectX 3.x or greater
+ - ProductVersion: 1, 2, 0, 0
+ - SpecialBuild: VCpp 6.0
+ - rock_hod.lvl: 9fddeac91b8de1cbbce35fa3330f674cb5e0e7c7
+ - rock_hod.mst: e7fbfcde10c08aa64ae1398fc95257891863790b
+ - rock_hod.sss: 982323a992c998e7c741d4d8fcaba11998d8072c
+ - setup.dat: 24bbb82fe25c9c52c5a2c471db3646acd0dd4f1f
+
+## Heart of Darkness Mini-Game V1.3 Kellogg's (Win32) files:
+ - hod_demo.paf: 2229d9743c1a4064561ff1a1530d6ba79ebab5a7
+ - hod_demo.paf: e60599c35283d24a1938ba0c09963c6bf756ab4d
+ - hodwin32.exe: af2837fdb99d8a35ec6b8732bc84e8e162ab7cfd
+ securom: false
+ version_info:
+ - Comments: Release 1.3
+ - CompanyName: Amazing Studio - 9 rue d' Enhgien - 75010 Paris FRANCE - Email: hod@amazingstudio.com
+ - FileDescription: Heart Of Darkness
+ - FileVersion: 1, 3, 0, 0
+ - InternalName: Heart Of Darkness
+ - LegalCopyright: (c) 1998 by Amazing Studio & Infogrames
+ - LegalTrademarks: by Amazing Studio - All rights reserved
+ - OriginalFilename: Heart Of Darkness
+ - PrivateBuild: HEARTMM5511
+ - ProductName: Heart Of Darkness - Windows 95 / 98 & NT - DirectX 3.x or greater
+ - ProductVersion: 1, 3, 0, 0
+ - SpecialBuild: VCpp 6.0
+ - rock_hod.lvl: 9fddeac91b8de1cbbce35fa3330f674cb5e0e7c7
+ - rock_hod.mst: 24dee01526c06b6f003b2c8e22e4d5787df333c8
+ - rock_hod.sss: ec683718d26bc1e0b4a254215621dd6c6c3e60b0
+ - setup.dat: 957567bcc4ff5650d85fe543d34ff2f773bb8aca
+
+## Heart of Darkness Coca Cola Edition (Win32) files:
+ - fort_hod.lvl: d08d11b148786ef4b7391ac7c877186ec4721980
+ - fort_hod.mst: 590a577830747d2ca1bee3fe3c5212e2a30e8dfa
+ - fort_hod.sss: 93120414e8077a3a205092a99001457d221e0a11
+ - hod_demo2.paf: a31d6df563f155147759de4730b7bcb4d5cb99c0
+ - hod_res.dll: 0b85a0b96f14d0da7becb76fb7cff03e4f865165
+ - hodwin32.exe: 6d1866ddd6310607afe9694b564d2fb81878183a
+ securom: false
+ version_info:
+ - Comments: Release 1.4.2
+ - CompanyName: Amazing Studio - 9 rue d' Enhgien - 75010 Paris FRANCE - Email:webmaster@amazingstudio.com
+ - FileDescription: Heart Of Darkness
+ - FileVersion: 1, 4, 2, 0
+ - InternalName: Heart Of Darkness
+ - LegalCopyright: (c) 1999 by Amazing Studio & Infogrames
+ - LegalTrademarks: by Amazing Studio - All rights reserved
+ - OriginalFilename: Heart Of Darkness
+ - PrivateBuild: HEARTMMCOCA
+ - ProductName: Heart Of Darkness - Windows 95 / 98 & NT - DirectX 3.x or greater
+ - ProductVersion: 1, 4, 2, 0
+ - SpecialBuild: VCpp 6.0 sp3
+ - rock_hod.lvl: 9fddeac91b8de1cbbce35fa3330f674cb5e0e7c7
+ - rock_hod.mst: 24dee01526c06b6f003b2c8e22e4d5787df333c8
+ - rock_hod.sss: ec683718d26bc1e0b4a254215621dd6c6c3e60b0
+ - setup.dat: e6d1ae87b991d3cf623b8ec9fb33cdd2a3d4a1b4
+
+## Heart of Darkness Demo (PSX) SLED_013.51 files:
+ - rock_hod.lvl: 9cbc3bad9efe5f21f66c33d9872e29a1bd7b4c0c
+ - rock_hod.mst: e7fbfcde10c08aa64ae1398fc95257891863790b
+ - setup.dax: e091dddd74dc00ccf57a48dc8495ed95cde6a42d
+
+## Heart of Darkness (PSX) SLUS_006.96 files:
+ - dark_hod.lvl: f6482575f1d4d2b6aa8910c709681a714edfba6b
+ - dark_hod.mst: a97ac13edde17fd33cb06e9907139949c8b79cdb
+ - fort_hod.lvl: e94a21bc4f779dba658ef5ab45f1ad15880913d8
+ - fort_hod.mst: f7423c3d277650ac36378f09f59034cec28b6b34
+ - isld_hod.lvl: c0fbd02deea136d57b60bc8a7c3df69741491335
+ - isld_hod.mst: 9ff92346444a66833eb2f8a1b3853ad788d1777b
+ - lar1_hod.lvl: 575dde976affeedfa5f1637a34d5efdda3218778
+ - lar1_hod.mst: b36ec9ca6122edd72a52543afad4c8bcd23ca03a
+ - lar2_hod.lvl: dc907525ef0859be25dca20746e43edaf06b073b
+ - lar2_hod.mst: 266edb49e8846fc8ae883dfe5ca269e0b7651e82
+ - lava_hod.lvl: 8412a41febf1ca5d8140a7344f758c8cad924951
+ - lava_hod.mst: e4584f4c893a6977e01b3f4f3b2cdbd23a96bbac
+ - pwr1_hod.lvl: f6c60f57de3ec1429286531451a6ca957c8f2cab
+ - pwr1_hod.mst: 2383746cee299ef57b49db5367dfadb2a6a7bbe8
+ - pwr2_hod.lvl: 46c30e728b28f0bb2c976832220cd6d3d4050b0e
+ - pwr2_hod.mst: c82e435630be6799f469a460f5fc982a998854da
+ - rock_hod.lvl: 9cbc3bad9efe5f21f66c33d9872e29a1bd7b4c0c
+ - rock_hod.mst: e7fbfcde10c08aa64ae1398fc95257891863790b
- setup.dax: 9d5708b6dedce70a17c76c10ea16f02121826aab
\ No newline at end of file
diff --git a/CONTRIBUTING.MD b/GitHub/CONTRIBUTING.MD
similarity index 100%
rename from CONTRIBUTING.MD
rename to GitHub/CONTRIBUTING.MD
diff --git a/COMM/GITHUB_BANNER.png b/GitHub/Graphics/Banner-GitHub.png
similarity index 100%
rename from COMM/GITHUB_BANNER.png
rename to GitHub/Graphics/Banner-GitHub.png
diff --git a/COMM/GITHUB_BANNER.psd b/GitHub/Graphics/Banner-GitHub.psd
similarity index 100%
rename from COMM/GITHUB_BANNER.psd
rename to GitHub/Graphics/Banner-GitHub.psd
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 2831d9e..0000000
--- a/LICENSE
+++ /dev/null
@@ -1 +0,0 @@
-License
diff --git a/README.md b/README.md
index e87a5a1..a179b61 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,15 @@

# HeartOfDarkness-SDL
-Release version : 0.2.5.1
+Release version : 0.2.9
We have a Wiki ! Please check it out.
+## Links
* Go see the [Wiki](https://github.com/MaximLopez/HeartOfDarkness-SDL/wiki)
* Go to [INSTALLATION](https://github.com/MaximLopez/HeartOfDarkness-SDL/wiki/Installation) for more informations about the installation.
* Go to [CONTROL](https://github.com/MaximLopez/HeartOfDarkness-SDL/wiki/How-to-control) for the control's scheme.
+* [CHANGELOG](./GitHub/CHANGELOG.md)
+* [CONTRIBUTING](./GitHub/CONTRIBUTING.md)
+* [COMPATIBILITY_RELEASE](./GitHub/COMPATIBILITY_RELEASE.md)
## About
The objectives of this port is to be 100% accurate to the real game and make it compatible with every devices (like PSP, Nintendo Switch, etc.) or any modern operating system (Linux, Windows, macOS, etc.) that can run SDL2.
@@ -18,8 +22,11 @@
* Microsoft Windows
* Linux
* Nintendo Switch
+* Nintendo Wii
* Google Android
* Sony PlayStation Vita
+* Sony PlayStation Portable
+* Microsoft Xbox
## Status
Menus are currently missing.
@@ -57,6 +64,7 @@ and start the game from the first level.
All the team at Amazing Studio for possiby the best cinematic platformer ever developed.
Gregory Montoir for the hode engine.
Usineur for the Switch and Vita port.
+Modern Vintage Gamer / Lantus for the Xbox port.
## URLs
* [Mobygames](https://www.mobygames.com/game/heart-of-darkness)
diff --git a/SRC/3p/inih/.travis.yml b/SRC/3p/inih/.travis.yml
deleted file mode 100644
index 15c8392..0000000
--- a/SRC/3p/inih/.travis.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-language: c
-
-# Setting sudo access to false will let Travis CI use containers
-# rather than VMs to run the tests. For more details see:
-# https://docs.travis-ci.com/user/reference/overview/
-sudo: false
-
-script:
- - cd tests
- - ./unittest.sh
- - cd ../examples
- - ./cpptest.sh
- - git diff --exit-code
diff --git a/SRC/3p/inih/README.md b/SRC/3p/inih/README.md
deleted file mode 100644
index 5b2e3f4..0000000
--- a/SRC/3p/inih/README.md
+++ /dev/null
@@ -1,133 +0,0 @@
-# inih (INI Not Invented Here)
-
-[](https://travis-ci.org/benhoyt/inih)
-
-**inih (INI Not Invented Here)** is a simple [.INI file](http://en.wikipedia.org/wiki/INI_file) parser written in C. It's only a couple of pages of code, and it was designed to be _small and simple_, so it's good for embedded systems. It's also more or less compatible with Python's [ConfigParser](http://docs.python.org/library/configparser.html) style of .INI files, including RFC 822-style multi-line syntax and `name: value` entries.
-
-To use it, just give `ini_parse()` an INI file, and it will call a callback for every `name=value` pair parsed, giving you strings for the section, name, and value. It's done this way ("SAX style") because it works well on low-memory embedded systems, but also because it makes for a KISS implementation.
-
-You can also call `ini_parse_file()` to parse directly from a `FILE*` object, `ini_parse_string()` to parse data from a string, or `ini_parse_stream()` to parse using a custom fgets-style reader function for custom I/O.
-
-Download a release, browse the source, or read about [how to use inih in a DRY style](http://blog.brush.co.nz/2009/08/xmacros/) with X-Macros.
-
-
-## Compile-time options ##
-
-You can control various aspects of inih using preprocessor defines:
-
-### Syntax options ###
-
- * **Multi-line entries:** By default, inih supports multi-line entries in the style of Python's ConfigParser. To disable, add `-DINI_ALLOW_MULTILINE=0`.
- * **UTF-8 BOM:** By default, inih allows a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of INI files. To disable, add `-DINI_ALLOW_BOM=0`.
- * **Inline comments:** By default, inih allows inline comments with the `;` character. To disable, add `-DINI_ALLOW_INLINE_COMMENTS=0`. You can also specify which character(s) start an inline comment using `INI_INLINE_COMMENT_PREFIXES`.
- * **Start-of-line comments:** By default, inih allows both `;` and `#` to start a comment at the beginning of a line. You can override this by changing `INI_START_COMMENT_PREFIXES`.
- * **Allow no value:** By default, inih treats a name with no value (no `=` or `:` on the line) as an error. To allow names with no values, add `-DINI_ALLOW_NO_VALUE=1`, and inih will call your handler function with value set to NULL.
-
-### Parsing options ###
-
- * **Stop on first error:** By default, inih keeps parsing the rest of the file after an error. To stop parsing on the first error, add `-DINI_STOP_ON_FIRST_ERROR=1`.
- * **Report line numbers:** By default, the `ini_handler` callback doesn't receive the line number as a parameter. If you need that, add `-DINI_HANDLER_LINENO=1`.
- * **Call handler on new section:** By default, inih only calls the handler on each `name=value` pair. To detect new sections (e.g., the INI file has multiple sections with the same name), add `-DINI_CALL_HANDLER_ON_NEW_SECTION=1`. Your handler function will then be called each time a new section is encountered, with `section` set to the new section name but `name` and `value` set to NULL.
-
-### Memory options ###
-
- * **Stack vs heap:** By default, inih creates a fixed-sized line buffer on the stack. To allocate on the heap using `malloc` instead, specify `-DINI_USE_STACK=0`.
- * **Maximum line length:** The default maximum line length (for stack or heap) is 200 bytes. To override this, add something like `-DINI_MAX_LINE=1000`. Note that `INI_MAX_LINE` must be 3 more than the longest line (due to `\r`, `\n`, and the NUL).
- * **Allow realloc:** By default when using the heap (`-DINI_USE_STACK=0`), inih allocates a fixed-sized buffer of `INI_INITIAL_ALLOC` bytes. To allow this to grow to `INI_MAX_LINE` bytes, doubling if needed, set `-DINI_ALLOW_REALLOC=1`.
- * **Initial malloc size:** `INI_INITIAL_ALLOC` specifies the initial malloc size when using the heap. It defaults to 200 bytes.
-
-## Simple example in C ##
-
-```c
-#include
-#include
-#include
-#include "../ini.h"
-
-typedef struct
-{
- int version;
- const char* name;
- const char* email;
-} configuration;
-
-static int handler(void* user, const char* section, const char* name,
- const char* value)
-{
- configuration* pconfig = (configuration*)user;
-
- #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
- if (MATCH("protocol", "version")) {
- pconfig->version = atoi(value);
- } else if (MATCH("user", "name")) {
- pconfig->name = strdup(value);
- } else if (MATCH("user", "email")) {
- pconfig->email = strdup(value);
- } else {
- return 0; /* unknown section/name, error */
- }
- return 1;
-}
-
-int main(int argc, char* argv[])
-{
- configuration config;
-
- if (ini_parse("test.ini", handler, &config) < 0) {
- printf("Can't load 'test.ini'\n");
- return 1;
- }
- printf("Config loaded from 'test.ini': version=%d, name=%s, email=%s\n",
- config.version, config.name, config.email);
- return 0;
-}
-```
-
-
-## C++ example ##
-
-If you're into C++ and the STL, there is also an easy-to-use [INIReader class](https://github.com/benhoyt/inih/blob/master/cpp/INIReader.h) that stores values in a `map` and lets you `Get()` them:
-
-```cpp
-#include
-#include "INIReader.h"
-
-int main()
-{
- INIReader reader("../examples/test.ini");
-
- if (reader.ParseError() < 0) {
- std::cout << "Can't load 'test.ini'\n";
- return 1;
- }
- std::cout << "Config loaded from 'test.ini': version="
- << reader.GetInteger("protocol", "version", -1) << ", name="
- << reader.Get("user", "name", "UNKNOWN") << ", email="
- << reader.Get("user", "email", "UNKNOWN") << ", pi="
- << reader.GetReal("user", "pi", -1) << ", active="
- << reader.GetBoolean("user", "active", true) << "\n";
- return 0;
-}
-```
-
-This simple C++ API works fine, but it's not very fully-fledged. I'm not planning to work more on the C++ API at the moment, so if you want a bit more power (for example `GetSections()` and `GetFields()` functions), see these forks:
-
- * https://github.com/Blandinium/inih
- * https://github.com/OSSystems/inih
-
-
-## Differences from ConfigParser ##
-
-Some differences between inih and Python's [ConfigParser](http://docs.python.org/library/configparser.html) standard library module:
-
-* INI name=value pairs given above any section headers are treated as valid items with no section (section name is an empty string). In ConfigParser having no section is an error.
-* Line continuations are handled with leading whitespace on continued lines (like ConfigParser). However, instead of concatenating continued lines together, they are treated as separate values for the same key (unlike ConfigParser).
-
-
-## Platform-specific notes ##
-
-* Windows/Win32 uses UTF-16 filenames natively, so to handle Unicode paths you need to call `_wfopen()` to open a file and then `ini_parse_file()` to parse it; inih does not include `wchar_t` or Unicode handling.
-
-## Related links ##
-
-* [Conan package for inih](https://github.com/mohamedghita/conan-inih) (Conan is a C/C++ package manager)
diff --git a/SRC/3p/inih/cpp/INIReader.cpp b/SRC/3p/inih/cpp/INIReader.cpp
deleted file mode 100644
index e752002..0000000
--- a/SRC/3p/inih/cpp/INIReader.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-// Read an INI file into easy-to-access name/value pairs.
-
-// SPDX-License-Identifier: BSD-3-Clause
-
-// Copyright (C) 2009-2019, Ben Hoyt
-
-// inih and INIReader are released under the New BSD license (see LICENSE.txt).
-// Go to the project home page for more info:
-//
-// https://github.com/benhoyt/inih
-
-#include
-#include
-#include
-#include "../ini.h"
-#include "INIReader.h"
-
-using std::string;
-
-INIReader::INIReader(const string& filename)
-{
- _error = ini_parse(filename.c_str(), ValueHandler, this);
-}
-
-int INIReader::ParseError() const
-{
- return _error;
-}
-
-string INIReader::Get(const string& section, const string& name, const string& default_value) const
-{
- string key = MakeKey(section, name);
- // Use _values.find() here instead of _values.at() to support pre C++11 compilers
- return _values.count(key) ? _values.find(key)->second : default_value;
-}
-
-string INIReader::GetString(const string& section, const string& name, const string& default_value) const
-{
- const string str = Get(section, name, "");
- return str.empty() ? default_value : str;
-}
-
-long INIReader::GetInteger(const string& section, const string& name, long default_value) const
-{
- string valstr = Get(section, name, "");
- const char* value = valstr.c_str();
- char* end;
- // This parses "1234" (decimal) and also "0x4D2" (hex)
- long n = strtol(value, &end, 0);
- return end > value ? n : default_value;
-}
-
-double INIReader::GetReal(const string& section, const string& name, double default_value) const
-{
- string valstr = Get(section, name, "");
- const char* value = valstr.c_str();
- char* end;
- double n = strtod(value, &end);
- return end > value ? n : default_value;
-}
-
-bool INIReader::GetBoolean(const string& section, const string& name, bool default_value) const
-{
- string valstr = Get(section, name, "");
- // Convert to lower case to make string comparisons case-insensitive
- std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower);
- if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
- return true;
- else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0")
- return false;
- else
- return default_value;
-}
-
-bool INIReader::HasSection(const string& section) const
-{
- const string key = MakeKey(section, "");
- std::map::const_iterator pos = _values.lower_bound(key);
- if (pos == _values.end())
- return false;
- // Does the key at the lower_bound pos start with "section"?
- return pos->first.compare(0, key.length(), key) == 0;
-}
-
-bool INIReader::HasValue(const string& section, const string& name) const
-{
- string key = MakeKey(section, name);
- return _values.count(key);
-}
-
-string INIReader::MakeKey(const string& section, const string& name)
-{
- string key = section + "=" + name;
- // Convert to lower case to make section/name lookups case-insensitive
- std::transform(key.begin(), key.end(), key.begin(), ::tolower);
- return key;
-}
-
-int INIReader::ValueHandler(void* user, const char* section, const char* name,
- const char* value)
-{
- INIReader* reader = static_cast(user);
- string key = MakeKey(section, name);
- if (reader->_values[key].size() > 0)
- reader->_values[key] += "\n";
- reader->_values[key] += value;
- return 1;
-}
diff --git a/SRC/3p/inih/examples/cpptest.sh b/SRC/3p/inih/examples/cpptest.sh
deleted file mode 100644
index 4640c24..0000000
--- a/SRC/3p/inih/examples/cpptest.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env bash
-
-g++ INIReaderExample.cpp ../cpp/INIReader.cpp ../ini.c -o INIReaderExample
-./INIReaderExample > cpptest.txt
-rm INIReaderExample
diff --git a/SRC/3p/inih/examples/cpptest.txt b/SRC/3p/inih/examples/cpptest.txt
deleted file mode 100644
index fac9c0d..0000000
--- a/SRC/3p/inih/examples/cpptest.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Config loaded from 'test.ini': version=6, name=Bob Smith, email=bob@smith.com, pi=3.14159, active=1
-Has values: user.name=1, user.nose=0
-Has sections: user=1, fizz=0
diff --git a/SRC/3p/inih/extra/Makefile.static b/SRC/3p/inih/extra/Makefile.static
deleted file mode 100644
index 0d6519e..0000000
--- a/SRC/3p/inih/extra/Makefile.static
+++ /dev/null
@@ -1,19 +0,0 @@
-# Simple makefile to build inih as a static library using g++
-
-SRC = ../ini.c
-OBJ = $(SRC:.c=.o)
-OUT = libinih.a
-INCLUDES = -I..
-CCFLAGS = -g -O2
-CC = g++
-
-default: $(OUT)
-
-.c.o:
- $(CC) $(INCLUDES) $(CCFLAGS) $(EXTRACCFLAGS) -c $< -o $@
-
-$(OUT): $(OBJ)
- ar rcs $(OUT) $(OBJ) $(EXTRAARFLAGS)
-
-clean:
- rm -f $(OBJ) $(OUT)
diff --git a/SRC/3p/inih/ini.c b/SRC/3p/inih/ini.c
deleted file mode 100644
index b4d5921..0000000
--- a/SRC/3p/inih/ini.c
+++ /dev/null
@@ -1,284 +0,0 @@
-/* inih -- simple .INI file parser
-
-SPDX-License-Identifier: BSD-3-Clause
-
-Copyright (C) 2009-2019, Ben Hoyt
-
-inih is released under the New BSD license (see LICENSE.txt). Go to the project
-home page for more info:
-
-https://github.com/benhoyt/inih
-
-*/
-
-#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
-#define _CRT_SECURE_NO_WARNINGS
-#endif
-
-#include
-#include
-#include
-
-#include "ini.h"
-
-#if !INI_USE_STACK
-#include
-#endif
-
-#define MAX_SECTION 50
-#define MAX_NAME 50
-
-/* Used by ini_parse_string() to keep track of string parsing state. */
-typedef struct {
- const char* ptr;
- size_t num_left;
-} ini_parse_string_ctx;
-
-/* Strip whitespace chars off end of given string, in place. Return s. */
-static char* rstrip(char* s)
-{
- char* p = s + strlen(s);
- while (p > s && isspace((unsigned char)(*--p)))
- *p = '\0';
- return s;
-}
-
-/* Return pointer to first non-whitespace char in given string. */
-static char* lskip(const char* s)
-{
- while (*s && isspace((unsigned char)(*s)))
- s++;
- return (char*)s;
-}
-
-/* Return pointer to first char (of chars) or inline comment in given string,
- or pointer to null at end of string if neither found. Inline comment must
- be prefixed by a whitespace character to register as a comment. */
-static char* find_chars_or_comment(const char* s, const char* chars)
-{
-#if INI_ALLOW_INLINE_COMMENTS
- int was_space = 0;
- while (*s && (!chars || !strchr(chars, *s)) &&
- !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
- was_space = isspace((unsigned char)(*s));
- s++;
- }
-#else
- while (*s && (!chars || !strchr(chars, *s))) {
- s++;
- }
-#endif
- return (char*)s;
-}
-
-/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
-static char* strncpy0(char* dest, const char* src, size_t size)
-{
- strncpy(dest, src, size - 1);
- dest[size - 1] = '\0';
- return dest;
-}
-
-/* See documentation in header file. */
-int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
- void* user)
-{
- /* Uses a fair bit of stack (use heap instead if you need to) */
-#if INI_USE_STACK
- char line[INI_MAX_LINE];
- int max_line = INI_MAX_LINE;
-#else
- char* line;
- size_t max_line = INI_INITIAL_ALLOC;
-#endif
-#if INI_ALLOW_REALLOC && !INI_USE_STACK
- char* new_line;
- size_t offset;
-#endif
- char section[MAX_SECTION] = "";
- char prev_name[MAX_NAME] = "";
-
- char* start;
- char* end;
- char* name;
- char* value;
- int lineno = 0;
- int error = 0;
-
-#if !INI_USE_STACK
- line = (char*)malloc(INI_INITIAL_ALLOC);
- if (!line) {
- return -2;
- }
-#endif
-
-#if INI_HANDLER_LINENO
-#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
-#else
-#define HANDLER(u, s, n, v) handler(u, s, n, v)
-#endif
-
- /* Scan through stream line by line */
- while (reader(line, (int)max_line, stream) != NULL) {
-#if INI_ALLOW_REALLOC && !INI_USE_STACK
- offset = strlen(line);
- while (offset == max_line - 1 && line[offset - 1] != '\n') {
- max_line *= 2;
- if (max_line > INI_MAX_LINE)
- max_line = INI_MAX_LINE;
- new_line = realloc(line, max_line);
- if (!new_line) {
- free(line);
- return -2;
- }
- line = new_line;
- if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
- break;
- if (max_line >= INI_MAX_LINE)
- break;
- offset += strlen(line + offset);
- }
-#endif
-
- lineno++;
-
- start = line;
-#if INI_ALLOW_BOM
- if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
- (unsigned char)start[1] == 0xBB &&
- (unsigned char)start[2] == 0xBF) {
- start += 3;
- }
-#endif
- start = lskip(rstrip(start));
-
- if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
- /* Start-of-line comment */
- }
-#if INI_ALLOW_MULTILINE
- else if (*prev_name && *start && start > line) {
- /* Non-blank line with leading whitespace, treat as continuation
- of previous name's value (as per Python configparser). */
- if (!HANDLER(user, section, prev_name, start) && !error)
- error = lineno;
- }
-#endif
- else if (*start == '[') {
- /* A "[section]" line */
- end = find_chars_or_comment(start + 1, "]");
- if (*end == ']') {
- *end = '\0';
- strncpy0(section, start + 1, sizeof(section));
- *prev_name = '\0';
-#if INI_CALL_HANDLER_ON_NEW_SECTION
- if (!HANDLER(user, section, NULL, NULL) && !error)
- error = lineno;
-#endif
- }
- else if (!error) {
- /* No ']' found on section line */
- error = lineno;
- }
- }
- else if (*start) {
- /* Not a comment, must be a name[=:]value pair */
- end = find_chars_or_comment(start, "=:");
- if (*end == '=' || *end == ':') {
- *end = '\0';
- name = rstrip(start);
- value = end + 1;
-#if INI_ALLOW_INLINE_COMMENTS
- end = find_chars_or_comment(value, NULL);
- if (*end)
- *end = '\0';
-#endif
- value = lskip(value);
- rstrip(value);
-
- /* Valid name[=:]value pair found, call handler */
- strncpy0(prev_name, name, sizeof(prev_name));
- if (!HANDLER(user, section, name, value) && !error)
- error = lineno;
- }
- else if (!error) {
- /* No '=' or ':' found on name[=:]value line */
-#if INI_ALLOW_NO_VALUE
- *end = '\0';
- name = rstrip(start);
- if (!HANDLER(user, section, name, NULL) && !error)
- error = lineno;
-#else
- error = lineno;
-#endif
- }
- }
-
-#if INI_STOP_ON_FIRST_ERROR
- if (error)
- break;
-#endif
- }
-
-#if !INI_USE_STACK
- free(line);
-#endif
-
- return error;
-}
-
-/* See documentation in header file. */
-int ini_parse_file(FILE* file, ini_handler handler, void* user)
-{
- return ini_parse_stream((ini_reader)fgets, file, handler, user);
-}
-
-/* See documentation in header file. */
-int ini_parse(const char* filename, ini_handler handler, void* user)
-{
- FILE* file;
- int error;
-
- file = fopen(filename, "r");
- if (!file)
- return -1;
- error = ini_parse_file(file, handler, user);
- fclose(file);
- return error;
-}
-
-/* An ini_reader function to read the next line from a string buffer. This
- is the fgets() equivalent used by ini_parse_string(). */
-static char* ini_reader_string(char* str, int num, void* stream) {
- ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
- const char* ctx_ptr = ctx->ptr;
- size_t ctx_num_left = ctx->num_left;
- char* strp = str;
- char c;
-
- if (ctx_num_left == 0 || num < 2)
- return NULL;
-
- while (num > 1 && ctx_num_left != 0) {
- c = *ctx_ptr++;
- ctx_num_left--;
- *strp++ = c;
- if (c == '\n')
- break;
- num--;
- }
-
- *strp = '\0';
- ctx->ptr = ctx_ptr;
- ctx->num_left = ctx_num_left;
- return str;
-}
-
-/* See documentation in header file. */
-int ini_parse_string(const char* string, ini_handler handler, void* user) {
- ini_parse_string_ctx ctx;
-
- ctx.ptr = string;
- ctx.num_left = strlen(string);
- return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
- user);
-}
diff --git a/SRC/3p/inih/ini.h b/SRC/3p/inih/ini.h
deleted file mode 100644
index d2e07e6..0000000
--- a/SRC/3p/inih/ini.h
+++ /dev/null
@@ -1,148 +0,0 @@
-/* inih -- simple .INI file parser
-
-SPDX-License-Identifier: BSD-3-Clause
-
-Copyright (C) 2009-2019, Ben Hoyt
-
-inih is released under the New BSD license (see LICENSE.txt). Go to the project
-home page for more info:
-
-https://github.com/benhoyt/inih
-
-*/
-
-#ifndef __INI_H__
-#define __INI_H__
-
-/* Make this header file easier to include in C++ code */
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include
-
-/* Nonzero if ini_handler callback should accept lineno parameter. */
-#ifndef INI_HANDLER_LINENO
-#define INI_HANDLER_LINENO 0
-#endif
-
-/* Typedef for prototype of handler function. */
-#if INI_HANDLER_LINENO
-typedef int (*ini_handler)(void* user, const char* section,
- const char* name, const char* value,
- int lineno);
-#else
-typedef int (*ini_handler)(void* user, const char* section,
- const char* name, const char* value);
-#endif
-
-/* Typedef for prototype of fgets-style reader function. */
-typedef char* (*ini_reader)(char* str, int num, void* stream);
-
-/* Parse given INI-style file. May have [section]s, name=value pairs
- (whitespace stripped), and comments starting with ';' (semicolon). Section
- is "" if name=value pair parsed before any section heading. name:value
- pairs are also supported as a concession to Python's configparser.
-
- For each name=value pair parsed, call handler function with given user
- pointer as well as section, name, and value (data only valid for duration
- of handler call). Handler should return nonzero on success, zero on error.
-
- Returns 0 on success, line number of first error on parse error (doesn't
- stop on first error), -1 on file open error, or -2 on memory allocation
- error (only when INI_USE_STACK is zero).
-*/
-int ini_parse(const char* filename, ini_handler handler, void* user);
-
-/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
- close the file when it's finished -- the caller must do that. */
-int ini_parse_file(FILE* file, ini_handler handler, void* user);
-
-/* Same as ini_parse(), but takes an ini_reader function pointer instead of
- filename. Used for implementing custom or string-based I/O (see also
- ini_parse_string). */
-int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
- void* user);
-
-/* Same as ini_parse(), but takes a zero-terminated string with the INI data
-instead of a file. Useful for parsing INI data from a network socket or
-already in memory. */
-int ini_parse_string(const char* string, ini_handler handler, void* user);
-
-/* Nonzero to allow multi-line value parsing, in the style of Python's
- configparser. If allowed, ini_parse() will call the handler with the same
- name for each subsequent line parsed. */
-#ifndef INI_ALLOW_MULTILINE
-#define INI_ALLOW_MULTILINE 1
-#endif
-
-/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
- the file. See https://github.com/benhoyt/inih/issues/21 */
-#ifndef INI_ALLOW_BOM
-#define INI_ALLOW_BOM 1
-#endif
-
-/* Chars that begin a start-of-line comment. Per Python configparser, allow
- both ; and # comments at the start of a line by default. */
-#ifndef INI_START_COMMENT_PREFIXES
-#define INI_START_COMMENT_PREFIXES ";#"
-#endif
-
-/* Nonzero to allow inline comments (with valid inline comment characters
- specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
- Python 3.2+ configparser behaviour. */
-#ifndef INI_ALLOW_INLINE_COMMENTS
-#define INI_ALLOW_INLINE_COMMENTS 1
-#endif
-#ifndef INI_INLINE_COMMENT_PREFIXES
-#define INI_INLINE_COMMENT_PREFIXES ";"
-#endif
-
-/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
-#ifndef INI_USE_STACK
-#define INI_USE_STACK 1
-#endif
-
-/* Maximum line length for any line in INI file (stack or heap). Note that
- this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
-#ifndef INI_MAX_LINE
-#define INI_MAX_LINE 200
-#endif
-
-/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
- fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
- zero. */
-#ifndef INI_ALLOW_REALLOC
-#define INI_ALLOW_REALLOC 0
-#endif
-
-/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
- is zero. */
-#ifndef INI_INITIAL_ALLOC
-#define INI_INITIAL_ALLOC 200
-#endif
-
-/* Stop parsing on first error (default is to keep parsing). */
-#ifndef INI_STOP_ON_FIRST_ERROR
-#define INI_STOP_ON_FIRST_ERROR 0
-#endif
-
-/* Nonzero to call the handler at the start of each new section (with
- name and value NULL). Default is to only call the handler on
- each name=value pair. */
-#ifndef INI_CALL_HANDLER_ON_NEW_SECTION
-#define INI_CALL_HANDLER_ON_NEW_SECTION 0
-#endif
-
-/* Nonzero to allow a name without a value (no '=' or ':' on the line) and
- call the handler with value NULL in this case. Default is to treat
- no-value lines as an error. */
-#ifndef INI_ALLOW_NO_VALUE
-#define INI_ALLOW_NO_VALUE 0
-#endif
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* __INI_H__ */
diff --git a/SRC/3p/inih/tests/baseline_allow_no_value.txt b/SRC/3p/inih/tests/baseline_allow_no_value.txt
deleted file mode 100644
index 1486c88..0000000
--- a/SRC/3p/inih/tests/baseline_allow_no_value.txt
+++ /dev/null
@@ -1,69 +0,0 @@
-no_file.ini: e=-1 user=0
-... [section1]
-... one=This is a test;
-... two=1234;
-... [ section 2 ]
-... happy=4;
-... sad=;
-... [comment_test]
-... test1=1;2;3;
-... test2=2;3;4;this won't be a comment, needs whitespace before ';';
-... test;3=345;
-... test4=4#5#6;
-... test7=;
-... test8=; not a comment, needs whitespace before ';';
-... [colon_tests]
-... Content-Type=text/html;
-... foo=bar;
-... adams=42;
-... funny1=with = equals;
-... funny2=with : colons;
-... funny3=two = equals;
-... funny4=two : colons;
-normal.ini: e=0 user=101
-... [section1]
-... name1=value1;
-... name2=value2;
-bad_section.ini: e=3 user=102
-... This is an error;
-bad_comment.ini: e=0 user=103
-... [section]
-... a=b;
-... user=parse_error;
-... c=d;
-user_error.ini: e=3 user=104
-... [section1]
-... single1=abc;
-... multi=this is a;
-... multi=multi-line value;
-... single2=xyz;
-... [section2]
-... multi=a;
-... multi=b;
-... multi=c;
-... [section3]
-... single=ghi;
-... multi=the quick;
-... multi=brown fox;
-... name=bob smith;
-multi_line.ini: e=0 user=105
-... indented;
-bad_multi.ini: e=0 user=106
-... [bom_section]
-... bom_name=bom_value;
-... key“=value“;
-bom.ini: e=0 user=107
-... [section1]
-... single1=abc;
-... single2=xyz;
-... single1=def;
-... single2=qrs;
-duplicate_sections.ini: e=0 user=108
-... [section_list]
-... section0;
-... section1;
-... [section0]
-... key0=val0;
-... [section1]
-... key1=val1;
-no_value.ini: e=0 user=109
diff --git a/SRC/3p/inih/tests/baseline_call_handler_on_new_section.txt b/SRC/3p/inih/tests/baseline_call_handler_on_new_section.txt
deleted file mode 100644
index d1eec9d..0000000
--- a/SRC/3p/inih/tests/baseline_call_handler_on_new_section.txt
+++ /dev/null
@@ -1,67 +0,0 @@
-no_file.ini: e=-1 user=0
-... [section1]
-... one=This is a test;
-... two=1234;
-... [ section 2 ]
-... happy=4;
-... sad=;
-... [empty]
-... [comment_test]
-... test1=1;2;3;
-... test2=2;3;4;this won't be a comment, needs whitespace before ';';
-... test;3=345;
-... test4=4#5#6;
-... test7=;
-... test8=; not a comment, needs whitespace before ';';
-... [colon_tests]
-... Content-Type=text/html;
-... foo=bar;
-... adams=42;
-... funny1=with = equals;
-... funny2=with : colons;
-... funny3=two = equals;
-... funny4=two : colons;
-normal.ini: e=0 user=101
-... [section1]
-... name1=value1;
-... name2=value2;
-bad_section.ini: e=3 user=102
-bad_comment.ini: e=1 user=102
-... [section]
-... a=b;
-... user=parse_error;
-... c=d;
-user_error.ini: e=3 user=104
-... [section1]
-... single1=abc;
-... multi=this is a;
-... multi=multi-line value;
-... single2=xyz;
-... [section2]
-... multi=a;
-... multi=b;
-... multi=c;
-... [section3]
-... single=ghi;
-... multi=the quick;
-... multi=brown fox;
-... name=bob smith;
-multi_line.ini: e=0 user=105
-bad_multi.ini: e=1 user=105
-... [bom_section]
-... bom_name=bom_value;
-... key“=value“;
-bom.ini: e=0 user=107
-... [section1]
-... single1=abc;
-... single2=xyz;
-... [section1]
-... single1=def;
-... single2=qrs;
-duplicate_sections.ini: e=0 user=108
-... [section_list]
-... [section0]
-... key0=val0;
-... [section1]
-... key1=val1;
-no_value.ini: e=2 user=109
diff --git a/SRC/3p/inih/tests/baseline_disallow_inline_comments.txt b/SRC/3p/inih/tests/baseline_disallow_inline_comments.txt
deleted file mode 100644
index 327a495..0000000
--- a/SRC/3p/inih/tests/baseline_disallow_inline_comments.txt
+++ /dev/null
@@ -1,65 +0,0 @@
-no_file.ini: e=-1 user=0
-... [section1]
-... one=This is a test ; name=value comment;
-... two=1234;
-... [ section 2 ]
-... happy=4;
-... sad=;
-... [comment_test]
-... test1=1;2;3 ; only this will be a comment;
-... test2=2;3;4;this won't be a comment, needs whitespace before ';';
-... test;3=345 ; key should be "test;3";
-... test4=4#5#6 ; '#' only starts a comment at start of line;
-... test7=; blank value, except if inline comments disabled;
-... test8=; not a comment, needs whitespace before ';';
-... [colon_tests]
-... Content-Type=text/html;
-... foo=bar;
-... adams=42;
-... funny1=with = equals;
-... funny2=with : colons;
-... funny3=two = equals;
-... funny4=two : colons;
-normal.ini: e=0 user=101
-... [section1]
-... name1=value1;
-... [section3 ; comment ]
-... name2=value2;
-bad_section.ini: e=3 user=102
-bad_comment.ini: e=1 user=102
-... [section]
-... a=b;
-... user=parse_error;
-... c=d;
-user_error.ini: e=3 user=104
-... [section1]
-... single1=abc;
-... multi=this is a;
-... multi=multi-line value;
-... single2=xyz;
-... [section2]
-... multi=a;
-... multi=b;
-... multi=c;
-... [section3]
-... single=ghi;
-... multi=the quick;
-... multi=brown fox;
-... name=bob smith ; comment line 1;
-multi_line.ini: e=0 user=105
-bad_multi.ini: e=1 user=105
-... [bom_section]
-... bom_name=bom_value;
-... key“=value“;
-bom.ini: e=0 user=107
-... [section1]
-... single1=abc;
-... single2=xyz;
-... single1=def;
-... single2=qrs;
-duplicate_sections.ini: e=0 user=108
-... [section0]
-... key0=val0;
-... [section1]
-... key1=val1;
-no_value.ini: e=2 user=109
diff --git a/SRC/3p/inih/tests/baseline_handler_lineno.txt b/SRC/3p/inih/tests/baseline_handler_lineno.txt
deleted file mode 100644
index b05fb6c..0000000
--- a/SRC/3p/inih/tests/baseline_handler_lineno.txt
+++ /dev/null
@@ -1,64 +0,0 @@
-no_file.ini: e=-1 user=0
-... [section1]
-... one=This is a test; line 3
-... two=1234; line 4
-... [ section 2 ]
-... happy=4; line 8
-... sad=; line 9
-... [comment_test]
-... test1=1;2;3; line 15
-... test2=2;3;4;this won't be a comment, needs whitespace before ';'; line 16
-... test;3=345; line 17
-... test4=4#5#6; line 18
-... test7=; line 21
-... test8=; not a comment, needs whitespace before ';'; line 22
-... [colon_tests]
-... Content-Type=text/html; line 25
-... foo=bar; line 26
-... adams=42; line 27
-... funny1=with = equals; line 28
-... funny2=with : colons; line 29
-... funny3=two = equals; line 30
-... funny4=two : colons; line 31
-normal.ini: e=0 user=101
-... [section1]
-... name1=value1; line 2
-... name2=value2; line 5
-bad_section.ini: e=3 user=102
-bad_comment.ini: e=1 user=102
-... [section]
-... a=b; line 2
-... user=parse_error; line 3
-... c=d; line 4
-user_error.ini: e=3 user=104
-... [section1]
-... single1=abc; line 2
-... multi=this is a; line 3
-... multi=multi-line value; line 4
-... single2=xyz; line 5
-... [section2]
-... multi=a; line 7
-... multi=b; line 8
-... multi=c; line 9
-... [section3]
-... single=ghi; line 11
-... multi=the quick; line 12
-... multi=brown fox; line 13
-... name=bob smith; line 14
-multi_line.ini: e=0 user=105
-bad_multi.ini: e=1 user=105
-... [bom_section]
-... bom_name=bom_value; line 2
-... key“=value“; line 3
-bom.ini: e=0 user=107
-... [section1]
-... single1=abc; line 2
-... single2=xyz; line 3
-... single1=def; line 5
-... single2=qrs; line 6
-duplicate_sections.ini: e=0 user=108
-... [section0]
-... key0=val0; line 6
-... [section1]
-... key1=val1; line 9
-no_value.ini: e=2 user=109
diff --git a/SRC/3p/inih/tests/baseline_heap.txt b/SRC/3p/inih/tests/baseline_heap.txt
deleted file mode 100644
index 40b39b2..0000000
--- a/SRC/3p/inih/tests/baseline_heap.txt
+++ /dev/null
@@ -1,64 +0,0 @@
-no_file.ini: e=-1 user=0
-... [section1]
-... one=This is a test;
-... two=1234;
-... [ section 2 ]
-... happy=4;
-... sad=;
-... [comment_test]
-... test1=1;2;3;
-... test2=2;3;4;this won't be a comment, needs whitespace before ';';
-... test;3=345;
-... test4=4#5#6;
-... test7=;
-... test8=; not a comment, needs whitespace before ';';
-... [colon_tests]
-... Content-Type=text/html;
-... foo=bar;
-... adams=42;
-... funny1=with = equals;
-... funny2=with : colons;
-... funny3=two = equals;
-... funny4=two : colons;
-normal.ini: e=0 user=101
-... [section1]
-... name1=value1;
-... name2=value2;
-bad_section.ini: e=3 user=102
-bad_comment.ini: e=1 user=102
-... [section]
-... a=b;
-... user=parse_error;
-... c=d;
-user_error.ini: e=3 user=104
-... [section1]
-... single1=abc;
-... multi=this is a;
-... multi=multi-line value;
-... single2=xyz;
-... [section2]
-... multi=a;
-... multi=b;
-... multi=c;
-... [section3]
-... single=ghi;
-... multi=the quick;
-... multi=brown fox;
-... name=bob smith;
-multi_line.ini: e=0 user=105
-bad_multi.ini: e=1 user=105
-... [bom_section]
-... bom_name=bom_value;
-... key“=value“;
-bom.ini: e=0 user=107
-... [section1]
-... single1=abc;
-... single2=xyz;
-... single1=def;
-... single2=qrs;
-duplicate_sections.ini: e=0 user=108
-... [section0]
-... key0=val0;
-... [section1]
-... key1=val1;
-no_value.ini: e=2 user=109
diff --git a/SRC/3p/inih/tests/baseline_heap_max_line.txt b/SRC/3p/inih/tests/baseline_heap_max_line.txt
deleted file mode 100644
index a9c2e8e..0000000
--- a/SRC/3p/inih/tests/baseline_heap_max_line.txt
+++ /dev/null
@@ -1,68 +0,0 @@
-no_file.ini: e=-1 user=0
-... [section1]
-... one=This is a test;
-... two=1234;
-... [ section 2 ]
-... happy=4;
-... sad=;
-... [comment_test]
-... test1=1;2;3;
-... test2=2;3;4;this;
-... test2=needs whitespace b;
-... test;3=345;
-... test4=4#5#6;
-... test4=only starts a comm;
-... test7=;
-... test8=; not a comm;
-... [colon_tests]
-... Content-Type=text/;
-... foo=bar;
-... adams=42;
-... funny1=with = equ;
-... funny2=with : col;
-... funny3=two = equa;
-... funny4=two : colo;
-normal.ini: e=2 user=101
-... [section1]
-... name1=value1;
-... name2=value2;
-bad_section.ini: e=3 user=102
-bad_comment.ini: e=1 user=102
-... [section]
-... a=b;
-... user=parse_error;
-... c=d;
-user_error.ini: e=3 user=104
-... [section1]
-... single1=abc;
-... multi=this is a;
-... multi=multi-line;
-... single2=xyz;
-... [section2]
-... multi=a;
-... multi=b;
-... multi=c;
-... [section3]
-... single=ghi;
-... multi=the quick;
-... multi=brown fox;
-... name=bob smith;
-... name=comment line 1;
-... name=comment line 2;
-multi_line.ini: e=5 user=105
-bad_multi.ini: e=1 user=105
-... [bom_section]
-... bom_name=bom_value;
-... key“=value“;
-bom.ini: e=0 user=107
-... [section1]
-... single1=abc;
-... single2=xyz;
-... single1=def;
-... single2=qrs;
-duplicate_sections.ini: e=0 user=108
-... [section0]
-... key0=val0;
-... [section1]
-... key1=val1;
-no_value.ini: e=2 user=109
diff --git a/SRC/3p/inih/tests/baseline_heap_realloc.txt b/SRC/3p/inih/tests/baseline_heap_realloc.txt
deleted file mode 100644
index 40b39b2..0000000
--- a/SRC/3p/inih/tests/baseline_heap_realloc.txt
+++ /dev/null
@@ -1,64 +0,0 @@
-no_file.ini: e=-1 user=0
-... [section1]
-... one=This is a test;
-... two=1234;
-... [ section 2 ]
-... happy=4;
-... sad=;
-... [comment_test]
-... test1=1;2;3;
-... test2=2;3;4;this won't be a comment, needs whitespace before ';';
-... test;3=345;
-... test4=4#5#6;
-... test7=;
-... test8=; not a comment, needs whitespace before ';';
-... [colon_tests]
-... Content-Type=text/html;
-... foo=bar;
-... adams=42;
-... funny1=with = equals;
-... funny2=with : colons;
-... funny3=two = equals;
-... funny4=two : colons;
-normal.ini: e=0 user=101
-... [section1]
-... name1=value1;
-... name2=value2;
-bad_section.ini: e=3 user=102
-bad_comment.ini: e=1 user=102
-... [section]
-... a=b;
-... user=parse_error;
-... c=d;
-user_error.ini: e=3 user=104
-... [section1]
-... single1=abc;
-... multi=this is a;
-... multi=multi-line value;
-... single2=xyz;
-... [section2]
-... multi=a;
-... multi=b;
-... multi=c;
-... [section3]
-... single=ghi;
-... multi=the quick;
-... multi=brown fox;
-... name=bob smith;
-multi_line.ini: e=0 user=105
-bad_multi.ini: e=1 user=105
-... [bom_section]
-... bom_name=bom_value;
-... key“=value“;
-bom.ini: e=0 user=107
-... [section1]
-... single1=abc;
-... single2=xyz;
-... single1=def;
-... single2=qrs;
-duplicate_sections.ini: e=0 user=108
-... [section0]
-... key0=val0;
-... [section1]
-... key1=val1;
-no_value.ini: e=2 user=109
diff --git a/SRC/3p/inih/tests/baseline_heap_realloc_max_line.txt b/SRC/3p/inih/tests/baseline_heap_realloc_max_line.txt
deleted file mode 100644
index a9c2e8e..0000000
--- a/SRC/3p/inih/tests/baseline_heap_realloc_max_line.txt
+++ /dev/null
@@ -1,68 +0,0 @@
-no_file.ini: e=-1 user=0
-... [section1]
-... one=This is a test;
-... two=1234;
-... [ section 2 ]
-... happy=4;
-... sad=;
-... [comment_test]
-... test1=1;2;3;
-... test2=2;3;4;this;
-... test2=needs whitespace b;
-... test;3=345;
-... test4=4#5#6;
-... test4=only starts a comm;
-... test7=;
-... test8=; not a comm;
-... [colon_tests]
-... Content-Type=text/;
-... foo=bar;
-... adams=42;
-... funny1=with = equ;
-... funny2=with : col;
-... funny3=two = equa;
-... funny4=two : colo;
-normal.ini: e=2 user=101
-... [section1]
-... name1=value1;
-... name2=value2;
-bad_section.ini: e=3 user=102
-bad_comment.ini: e=1 user=102
-... [section]
-... a=b;
-... user=parse_error;
-... c=d;
-user_error.ini: e=3 user=104
-... [section1]
-... single1=abc;
-... multi=this is a;
-... multi=multi-line;
-... single2=xyz;
-... [section2]
-... multi=a;
-... multi=b;
-... multi=c;
-... [section3]
-... single=ghi;
-... multi=the quick;
-... multi=brown fox;
-... name=bob smith;
-... name=comment line 1;
-... name=comment line 2;
-multi_line.ini: e=5 user=105
-bad_multi.ini: e=1 user=105
-... [bom_section]
-... bom_name=bom_value;
-... key“=value“;
-bom.ini: e=0 user=107
-... [section1]
-... single1=abc;
-... single2=xyz;
-... single1=def;
-... single2=qrs;
-duplicate_sections.ini: e=0 user=108
-... [section0]
-... key0=val0;
-... [section1]
-... key1=val1;
-no_value.ini: e=2 user=109
diff --git a/SRC/3p/inih/tests/baseline_heap_string.txt b/SRC/3p/inih/tests/baseline_heap_string.txt
deleted file mode 100644
index 4d3627f..0000000
--- a/SRC/3p/inih/tests/baseline_heap_string.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-empty string: e=0 user=0
-... [section]
-... foo=bar;
-... bazz=buzz quxx;
-basic: e=0 user=101
-... [section]
-... hello=world;
-... forty_two=42;
-crlf: e=0 user=102
-... [sec]
-... foo=0123456789012;
-... bar=4321;
-long line: e=3 user=103
-... [sec]
-... foo=0123456789012;
-... bix=1234;
-long continued: e=0 user=104
-... [s]
-... a=1;
-... c=3;
-error: e=3 user=105
diff --git a/SRC/3p/inih/tests/baseline_multi_max_line.txt b/SRC/3p/inih/tests/baseline_multi_max_line.txt
deleted file mode 100644
index a9c2e8e..0000000
--- a/SRC/3p/inih/tests/baseline_multi_max_line.txt
+++ /dev/null
@@ -1,68 +0,0 @@
-no_file.ini: e=-1 user=0
-... [section1]
-... one=This is a test;
-... two=1234;
-... [ section 2 ]
-... happy=4;
-... sad=;
-... [comment_test]
-... test1=1;2;3;
-... test2=2;3;4;this;
-... test2=needs whitespace b;
-... test;3=345;
-... test4=4#5#6;
-... test4=only starts a comm;
-... test7=;
-... test8=; not a comm;
-... [colon_tests]
-... Content-Type=text/;
-... foo=bar;
-... adams=42;
-... funny1=with = equ;
-... funny2=with : col;
-... funny3=two = equa;
-... funny4=two : colo;
-normal.ini: e=2 user=101
-... [section1]
-... name1=value1;
-... name2=value2;
-bad_section.ini: e=3 user=102
-bad_comment.ini: e=1 user=102
-... [section]
-... a=b;
-... user=parse_error;
-... c=d;
-user_error.ini: e=3 user=104
-... [section1]
-... single1=abc;
-... multi=this is a;
-... multi=multi-line;
-... single2=xyz;
-... [section2]
-... multi=a;
-... multi=b;
-... multi=c;
-... [section3]
-... single=ghi;
-... multi=the quick;
-... multi=brown fox;
-... name=bob smith;
-... name=comment line 1;
-... name=comment line 2;
-multi_line.ini: e=5 user=105
-bad_multi.ini: e=1 user=105
-... [bom_section]
-... bom_name=bom_value;
-... key“=value“;
-bom.ini: e=0 user=107
-... [section1]
-... single1=abc;
-... single2=xyz;
-... single1=def;
-... single2=qrs;
-duplicate_sections.ini: e=0 user=108
-... [section0]
-... key0=val0;
-... [section1]
-... key1=val1;
-no_value.ini: e=2 user=109
diff --git a/SRC/3p/inih/tests/baseline_string.txt b/SRC/3p/inih/tests/baseline_string.txt
deleted file mode 100644
index 4d3627f..0000000
--- a/SRC/3p/inih/tests/baseline_string.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-empty string: e=0 user=0
-... [section]
-... foo=bar;
-... bazz=buzz quxx;
-basic: e=0 user=101
-... [section]
-... hello=world;
-... forty_two=42;
-crlf: e=0 user=102
-... [sec]
-... foo=0123456789012;
-... bar=4321;
-long line: e=3 user=103
-... [sec]
-... foo=0123456789012;
-... bix=1234;
-long continued: e=0 user=104
-... [s]
-... a=1;
-... c=3;
-error: e=3 user=105
diff --git a/SRC/3p/inih/tests/duplicate_sections.ini b/SRC/3p/inih/tests/duplicate_sections.ini
deleted file mode 100644
index 68599fd..0000000
--- a/SRC/3p/inih/tests/duplicate_sections.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[section1]
-single1 = abc
-single2 = xyz
-[section1]
-single1 = def
-single2 = qrs
\ No newline at end of file
diff --git a/SRC/3p/inih/tests/no_value.ini b/SRC/3p/inih/tests/no_value.ini
deleted file mode 100644
index 2101ec9..0000000
--- a/SRC/3p/inih/tests/no_value.ini
+++ /dev/null
@@ -1,9 +0,0 @@
-[section_list]
-section0
-section1
-
-[section0]
-key0=val0
-
-[section1]
-key1=val1
diff --git a/SRC/3p/inih/tests/unittest.bat b/SRC/3p/inih/tests/unittest.bat
deleted file mode 100644
index b7e3e97..0000000
--- a/SRC/3p/inih/tests/unittest.bat
+++ /dev/null
@@ -1,13 +0,0 @@
-@call tcc ..\ini.c -I..\ -run unittest.c > baseline_multi.txt
-@call tcc ..\ini.c -I..\ -DINI_MAX_LINE=20 -run unittest.c > baseline_multi_max_line.txt
-@call tcc ..\ini.c -I..\ -DINI_ALLOW_MULTILINE=0 -run unittest.c > baseline_single.txt
-@call tcc ..\ini.c -I..\ -DINI_ALLOW_INLINE_COMMENTS=0 -run unittest.c > baseline_disallow_inline_comments.txt
-@call tcc ..\ini.c -I..\ -DINI_STOP_ON_FIRST_ERROR=1 -run unittest.c > baseline_stop_on_first_error.txt
-@call tcc ..\ini.c -I..\ -DINI_HANDLER_LINENO=1 -run unittest.c > baseline_handler_lineno.txt
-@call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -run unittest.c > baseline_heap.txt
-@call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_INITIAL_ALLOC=20 -run unittest.c > baseline_heap_max_line.txt
-@call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=5 -run unittest.c > baseline_heap_realloc.txt
-@call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=5 -run unittest.c > baseline_heap_realloc_max_line.txt
-@call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_INITIAL_ALLOC=20 -run unittest.c > baseline_heap_string.txt
-@call tcc ..\ini.c -I..\ -DINI_CALL_HANDLER_ON_NEW_SECTION=1 -run unittest.c > baseline_call_handler_on_new_section.txt
-@call tcc ..\ini.c -I..\ -DINI_ALLOW_NO_VALUE=1 -run unittest.c > baseline_allow_no_value.txt
diff --git a/SRC/3p/inih/tests/unittest.sh b/SRC/3p/inih/tests/unittest.sh
deleted file mode 100644
index e01f215..0000000
--- a/SRC/3p/inih/tests/unittest.sh
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env bash
-
-gcc ../ini.c unittest.c -o unittest_multi
-./unittest_multi > baseline_multi.txt
-rm -f unittest_multi
-
-gcc ../ini.c -DINI_MAX_LINE=20 unittest.c -o unittest_multi_max_line
-./unittest_multi_max_line > baseline_multi_max_line.txt
-rm -f unittest_multi_max_line
-
-gcc ../ini.c -DINI_ALLOW_MULTILINE=0 unittest.c -o unittest_single
-./unittest_single > baseline_single.txt
-rm -f unittest_single
-
-gcc ../ini.c -DINI_ALLOW_INLINE_COMMENTS=0 unittest.c -o unittest_disallow_inline_comments
-./unittest_disallow_inline_comments > baseline_disallow_inline_comments.txt
-rm -f unittest_disallow_inline_comments
-
-gcc ../ini.c -DINI_STOP_ON_FIRST_ERROR=1 unittest.c -o unittest_stop_on_first_error
-./unittest_stop_on_first_error > baseline_stop_on_first_error.txt
-rm -f unittest_stop_on_first_error
-
-gcc ../ini.c -DINI_HANDLER_LINENO=1 unittest.c -o unittest_handler_lineno
-./unittest_handler_lineno > baseline_handler_lineno.txt
-rm -f unittest_handler_lineno
-
-gcc ../ini.c -DINI_MAX_LINE=20 unittest_string.c -o unittest_string
-./unittest_string > baseline_string.txt
-rm -f unittest_string
-
-gcc ../ini.c -DINI_USE_STACK=0 unittest.c -o unittest_heap
-./unittest_heap > baseline_heap.txt
-rm -f unittest_heap
-
-gcc ../ini.c -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_INITIAL_ALLOC=20 unittest.c -o unittest_heap_max_line
-./unittest_heap_max_line > baseline_heap_max_line.txt
-rm -f unittest_heap_max_line
-
-gcc ../ini.c -DINI_USE_STACK=0 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=5 unittest.c -o unittest_heap_realloc
-./unittest_heap_realloc > baseline_heap_realloc.txt
-rm -f unittest_heap_realloc
-
-gcc ../ini.c -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=5 unittest.c -o unittest_heap_realloc_max_line
-./unittest_heap_realloc_max_line > baseline_heap_realloc_max_line.txt
-rm -f unittest_heap_realloc_max_line
-
-gcc ../ini.c -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_INITIAL_ALLOC=20 unittest_string.c -o unittest_heap_string
-./unittest_heap_string > baseline_heap_string.txt
-rm -f unittest_heap_string
-
-gcc ../ini.c -DINI_CALL_HANDLER_ON_NEW_SECTION=1 unittest.c -o unittest_call_handler_on_new_section
-./unittest_call_handler_on_new_section > baseline_call_handler_on_new_section.txt
-rm -f unittest_call_handler_on_new_section
-
-gcc ../ini.c -DINI_ALLOW_NO_VALUE=1 unittest.c -o unittest_allow_no_value
-./unittest_allow_no_value > baseline_allow_no_value.txt
-rm -f unittest_allow_no_value
diff --git a/SRC/3p/inih/tests/unittest_string.c b/SRC/3p/inih/tests/unittest_string.c
deleted file mode 100644
index 4200e6b..0000000
--- a/SRC/3p/inih/tests/unittest_string.c
+++ /dev/null
@@ -1,43 +0,0 @@
-/* inih -- unit tests for ini_parse_string() */
-
-#include
-#include
-#include
-#include "../ini.h"
-
-int User;
-char Prev_section[50];
-
-int dumper(void* user, const char* section, const char* name,
- const char* value)
-{
- User = *((int*)user);
- if (strcmp(section, Prev_section)) {
- printf("... [%s]\n", section);
- strncpy(Prev_section, section, sizeof(Prev_section));
- Prev_section[sizeof(Prev_section) - 1] = '\0';
- }
- printf("... %s=%s;\n", name, value);
- return 1;
-}
-
-void parse(const char* name, const char* string) {
- static int u = 100;
- int e;
-
- *Prev_section = '\0';
- e = ini_parse_string(string, dumper, &u);
- printf("%s: e=%d user=%d\n", name, e, User);
- u++;
-}
-
-int main(void)
-{
- parse("empty string", "");
- parse("basic", "[section]\nfoo = bar\nbazz = buzz quxx");
- parse("crlf", "[section]\r\nhello = world\r\nforty_two = 42\r\n");
- parse("long line", "[sec]\nfoo = 01234567890123456789\nbar=4321\n");
- parse("long continued", "[sec]\nfoo = 0123456789012bix=1234\n");
- parse("error", "[s]\na=1\nb\nc=3");
- return 0;
-}
diff --git a/SRC/3p/libxbr-standalone/README.md b/SRC/3p/libxbr-standalone/README.md
deleted file mode 100644
index 41f548b..0000000
--- a/SRC/3p/libxbr-standalone/README.md
+++ /dev/null
@@ -1,33 +0,0 @@
-Standalone XBR/hqx Library
-==========================
-
-This library implements the xBR pixel art scaling filter developed by Hyllian,
-and now also the hqx filter developed by Maxim Stepin.
-
-I needed a standalone implementation, but could not find any ready-to-use ones.
-I found versions of the algorithm used in various open-source projects and I
-eventually settled on a modified version of the one used in FFmpeg's
-libavfilter.
-
-I also wanted to use hqx and needed to make some changes to the code, so I
-wound up merging this into the same codebase/library. This project is the
-result.
-
-Original source for the xBR implementation: http://git.videolan.org/gitweb.cgi/ffmpeg.git/?p=ffmpeg.git;a=blob;f=libavfilter/vf_xbr.c;h=5c14565b3a03f66f1e0296623dc91373aeac1ed0;hb=HEAD
-
-Original source for the hqx implementation: https://code.google.com/p/hqx/
-
-This version is a pure C library that operates on 32-bit ARGB data. It scales
-images up to 2x, 3x and 4x sizes, and preserves and interpolates the alpha
-channel as necessary.
-
-Project files included to compile as a DLL with Visual Studio 2012. Code
-should also compile successfully under GCC on other platforms, and probably
-with other Visual Studio versions.
-
-A crude example application is included that uses libpng to apply the
-available filters to 32-bit images.
-
-Licensed under the GNU Lesser General Public License, version 2.1. (See the
-header in `xbr.h` and `xbr.c`.)
-
diff --git a/SRC/3p/libxbr-standalone/filters.h b/SRC/3p/libxbr-standalone/filters.h
deleted file mode 100644
index 22ac8f5..0000000
--- a/SRC/3p/libxbr-standalone/filters.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * XBR filter: from the FFmpeg project
- *
- * Copyright (c) 2011, 2012 Hyllian/Jararaca
- * Copyright (c) 2014 Arwa Arif
- * Copyright (c) 2015 Treeki
- *
- *
- * hqx filter: from the hqx project
- * Copyright (C) 2003 Maxim Stepin ( maxst@hiend3d.com )
- * Copyright (C) 2010 Cameron Zemek ( grom@zeminvaders.net)
- * Copyright (c) 2015 Treeki
- *
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#ifndef __LIBXBR_FILTERS_H_INCLUDED
-#define __LIBXBR_FILTERS_H_INCLUDED
-
-#include
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#ifdef _MSC_VER
- #define XBR_INLINE __inline
-
- #ifdef XBR_INTERNAL
- #define XBR_EXPORT __declspec(dllexport)
- #else
- #define XBR_EXPORT __declspec(dllimport)
- #endif
-#else
- #define XBR_INLINE inline
- #define XBR_EXPORT
-#endif
-
-typedef struct {
- uint32_t rgbtoyuv[1<<24];
-} xbr_data;
-
-typedef struct {
- const uint8_t *input;
- uint8_t *output;
- int inWidth, inHeight;
- int inPitch, outPitch;
- const xbr_data *data;
-} xbr_params;
-
-XBR_EXPORT void xbr_filter_xbr2x(const xbr_params *ctx);
-XBR_EXPORT void xbr_filter_xbr3x(const xbr_params *ctx);
-XBR_EXPORT void xbr_filter_xbr4x(const xbr_params *ctx);
-
-XBR_EXPORT void xbr_filter_hq2x(const xbr_params *ctx);
-XBR_EXPORT void xbr_filter_hq3x(const xbr_params *ctx);
-XBR_EXPORT void xbr_filter_hq4x(const xbr_params *ctx);
-
-XBR_EXPORT void xbr_init_data(xbr_data *data);
-
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
-
diff --git a/SRC/3p/libxbr-standalone/hq2x.c b/SRC/3p/libxbr-standalone/hq2x.c
deleted file mode 100644
index f2ce7ff..0000000
--- a/SRC/3p/libxbr-standalone/hq2x.c
+++ /dev/null
@@ -1,2807 +0,0 @@
-/*
- * Copyright (C) 2003 Maxim Stepin ( maxst@hiend3d.com )
- *
- * Copyright (C) 2010 Cameron Zemek ( grom@zeminvaders.net)
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "internal_hqx_common.h"
-
-#define PIXEL00_0 *dp = w[5];
-#define PIXEL00_10 Interp1(dp, w[5], w[1]);
-#define PIXEL00_11 Interp1(dp, w[5], w[4]);
-#define PIXEL00_12 Interp1(dp, w[5], w[2]);
-#define PIXEL00_20 Interp2(dp, w[5], w[4], w[2]);
-#define PIXEL00_21 Interp2(dp, w[5], w[1], w[2]);
-#define PIXEL00_22 Interp2(dp, w[5], w[1], w[4]);
-#define PIXEL00_60 Interp6(dp, w[5], w[2], w[4]);
-#define PIXEL00_61 Interp6(dp, w[5], w[4], w[2]);
-#define PIXEL00_70 Interp7(dp, w[5], w[4], w[2]);
-#define PIXEL00_90 Interp9(dp, w[5], w[4], w[2]);
-#define PIXEL00_100 Interp10(dp, w[5], w[4], w[2]);
-#define PIXEL01_0 *(dp+1) = w[5];
-#define PIXEL01_10 Interp1(dp+1, w[5], w[3]);
-#define PIXEL01_11 Interp1(dp+1, w[5], w[2]);
-#define PIXEL01_12 Interp1(dp+1, w[5], w[6]);
-#define PIXEL01_20 Interp2(dp+1, w[5], w[2], w[6]);
-#define PIXEL01_21 Interp2(dp+1, w[5], w[3], w[6]);
-#define PIXEL01_22 Interp2(dp+1, w[5], w[3], w[2]);
-#define PIXEL01_60 Interp6(dp+1, w[5], w[6], w[2]);
-#define PIXEL01_61 Interp6(dp+1, w[5], w[2], w[6]);
-#define PIXEL01_70 Interp7(dp+1, w[5], w[2], w[6]);
-#define PIXEL01_90 Interp9(dp+1, w[5], w[2], w[6]);
-#define PIXEL01_100 Interp10(dp+1, w[5], w[2], w[6]);
-#define PIXEL10_0 *(dp+dpL) = w[5];
-#define PIXEL10_10 Interp1(dp+dpL, w[5], w[7]);
-#define PIXEL10_11 Interp1(dp+dpL, w[5], w[8]);
-#define PIXEL10_12 Interp1(dp+dpL, w[5], w[4]);
-#define PIXEL10_20 Interp2(dp+dpL, w[5], w[8], w[4]);
-#define PIXEL10_21 Interp2(dp+dpL, w[5], w[7], w[4]);
-#define PIXEL10_22 Interp2(dp+dpL, w[5], w[7], w[8]);
-#define PIXEL10_60 Interp6(dp+dpL, w[5], w[4], w[8]);
-#define PIXEL10_61 Interp6(dp+dpL, w[5], w[8], w[4]);
-#define PIXEL10_70 Interp7(dp+dpL, w[5], w[8], w[4]);
-#define PIXEL10_90 Interp9(dp+dpL, w[5], w[8], w[4]);
-#define PIXEL10_100 Interp10(dp+dpL, w[5], w[8], w[4]);
-#define PIXEL11_0 *(dp+dpL+1) = w[5];
-#define PIXEL11_10 Interp1(dp+dpL+1, w[5], w[9]);
-#define PIXEL11_11 Interp1(dp+dpL+1, w[5], w[6]);
-#define PIXEL11_12 Interp1(dp+dpL+1, w[5], w[8]);
-#define PIXEL11_20 Interp2(dp+dpL+1, w[5], w[6], w[8]);
-#define PIXEL11_21 Interp2(dp+dpL+1, w[5], w[9], w[8]);
-#define PIXEL11_22 Interp2(dp+dpL+1, w[5], w[9], w[6]);
-#define PIXEL11_60 Interp6(dp+dpL+1, w[5], w[8], w[6]);
-#define PIXEL11_61 Interp6(dp+dpL+1, w[5], w[6], w[8]);
-#define PIXEL11_70 Interp7(dp+dpL+1, w[5], w[6], w[8]);
-#define PIXEL11_90 Interp9(dp+dpL+1, w[5], w[6], w[8]);
-#define PIXEL11_100 Interp10(dp+dpL+1, w[5], w[6], w[8]);
-
-static void hq2x_32_rb( const uint32_t * sp, uint32_t srb, uint32_t * dp, uint32_t drb, int Xres, int Yres, const xbr_data *data )
-{
- int i, j, k;
- int prevline, nextline;
- uint32_t w[10];
- int dpL = (drb >> 2);
- int spL = (srb >> 2);
- const uint8_t *sRowP = (const uint8_t *) sp;
- uint8_t *dRowP = (uint8_t *) dp;
- uint32_t yuv1, yuv2;
- int pattern, flag;
-
- // +----+----+----+
- // | | | |
- // | w1 | w2 | w3 |
- // +----+----+----+
- // | | | |
- // | w4 | w5 | w6 |
- // +----+----+----+
- // | | | |
- // | w7 | w8 | w9 |
- // +----+----+----+
-
- for (j=0; j0) prevline = -spL; else prevline = 0;
- if (j0)
- {
- w[1] = *(sp + prevline - 1);
- w[4] = *(sp - 1);
- w[7] = *(sp + nextline - 1);
- }
- else
- {
- w[1] = w[2];
- w[4] = w[5];
- w[7] = w[8];
- }
-
- if (iinput, ctx->inPitch, (uint32_t *)ctx->output, ctx->outPitch, ctx->inWidth, ctx->inHeight, ctx->data);
-}
-
diff --git a/SRC/3p/libxbr-standalone/hq3x.c b/SRC/3p/libxbr-standalone/hq3x.c
deleted file mode 100644
index cc8248d..0000000
--- a/SRC/3p/libxbr-standalone/hq3x.c
+++ /dev/null
@@ -1,3785 +0,0 @@
-/*
- * Copyright (C) 2003 Maxim Stepin ( maxst@hiend3d.com )
- *
- * Copyright (C) 2010 Cameron Zemek ( grom@zeminvaders.net)
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "internal_hqx_common.h"
-
-#define PIXEL00_1M Interp1(dp, w[5], w[1]);
-#define PIXEL00_1U Interp1(dp, w[5], w[2]);
-#define PIXEL00_1L Interp1(dp, w[5], w[4]);
-#define PIXEL00_2 Interp2(dp, w[5], w[4], w[2]);
-#define PIXEL00_4 Interp4(dp, w[5], w[4], w[2]);
-#define PIXEL00_5 Interp5(dp, w[4], w[2]);
-#define PIXEL00_C *dp = w[5];
-
-#define PIXEL01_1 Interp1(dp+1, w[5], w[2]);
-#define PIXEL01_3 Interp3(dp+1, w[5], w[2]);
-#define PIXEL01_6 Interp1(dp+1, w[2], w[5]);
-#define PIXEL01_C *(dp+1) = w[5];
-
-#define PIXEL02_1M Interp1(dp+2, w[5], w[3]);
-#define PIXEL02_1U Interp1(dp+2, w[5], w[2]);
-#define PIXEL02_1R Interp1(dp+2, w[5], w[6]);
-#define PIXEL02_2 Interp2(dp+2, w[5], w[2], w[6]);
-#define PIXEL02_4 Interp4(dp+2, w[5], w[2], w[6]);
-#define PIXEL02_5 Interp5(dp+2, w[2], w[6]);
-#define PIXEL02_C *(dp+2) = w[5];
-
-#define PIXEL10_1 Interp1(dp+dpL, w[5], w[4]);
-#define PIXEL10_3 Interp3(dp+dpL, w[5], w[4]);
-#define PIXEL10_6 Interp1(dp+dpL, w[4], w[5]);
-#define PIXEL10_C *(dp+dpL) = w[5];
-
-#define PIXEL11 *(dp+dpL+1) = w[5];
-
-#define PIXEL12_1 Interp1(dp+dpL+2, w[5], w[6]);
-#define PIXEL12_3 Interp3(dp+dpL+2, w[5], w[6]);
-#define PIXEL12_6 Interp1(dp+dpL+2, w[6], w[5]);
-#define PIXEL12_C *(dp+dpL+2) = w[5];
-
-#define PIXEL20_1M Interp1(dp+dpL+dpL, w[5], w[7]);
-#define PIXEL20_1D Interp1(dp+dpL+dpL, w[5], w[8]);
-#define PIXEL20_1L Interp1(dp+dpL+dpL, w[5], w[4]);
-#define PIXEL20_2 Interp2(dp+dpL+dpL, w[5], w[8], w[4]);
-#define PIXEL20_4 Interp4(dp+dpL+dpL, w[5], w[8], w[4]);
-#define PIXEL20_5 Interp5(dp+dpL+dpL, w[8], w[4]);
-#define PIXEL20_C *(dp+dpL+dpL) = w[5];
-
-#define PIXEL21_1 Interp1(dp+dpL+dpL+1, w[5], w[8]);
-#define PIXEL21_3 Interp3(dp+dpL+dpL+1, w[5], w[8]);
-#define PIXEL21_6 Interp1(dp+dpL+dpL+1, w[8], w[5]);
-#define PIXEL21_C *(dp+dpL+dpL+1) = w[5];
-
-#define PIXEL22_1M Interp1(dp+dpL+dpL+2, w[5], w[9]);
-#define PIXEL22_1D Interp1(dp+dpL+dpL+2, w[5], w[8]);
-#define PIXEL22_1R Interp1(dp+dpL+dpL+2, w[5], w[6]);
-#define PIXEL22_2 Interp2(dp+dpL+dpL+2, w[5], w[6], w[8]);
-#define PIXEL22_4 Interp4(dp+dpL+dpL+2, w[5], w[6], w[8]);
-#define PIXEL22_5 Interp5(dp+dpL+dpL+2, w[6], w[8]);
-#define PIXEL22_C *(dp+dpL+dpL+2) = w[5];
-
-static void hq3x_32_rb( const uint32_t * sp, uint32_t srb, uint32_t * dp, uint32_t drb, int Xres, int Yres, const xbr_data *data )
-{
- int i, j, k;
- int prevline, nextline;
- uint32_t w[10];
- int dpL = (drb >> 2);
- int spL = (srb >> 2);
- const uint8_t *sRowP = (const uint8_t *) sp;
- uint8_t *dRowP = (uint8_t *) dp;
- uint32_t yuv1, yuv2;
- int pattern, flag;
-
- // +----+----+----+
- // | | | |
- // | w1 | w2 | w3 |
- // +----+----+----+
- // | | | |
- // | w4 | w5 | w6 |
- // +----+----+----+
- // | | | |
- // | w7 | w8 | w9 |
- // +----+----+----+
-
- for (j=0; j0) prevline = -spL; else prevline = 0;
- if (j0)
- {
- w[1] = *(sp + prevline - 1);
- w[4] = *(sp - 1);
- w[7] = *(sp + nextline - 1);
- }
- else
- {
- w[1] = w[2];
- w[4] = w[5];
- w[7] = w[8];
- }
-
- if (iinput, ctx->inPitch, (uint32_t *)ctx->output, ctx->outPitch, ctx->inWidth, ctx->inHeight, ctx->data);
-}
-
diff --git a/SRC/3p/libxbr-standalone/hq4x.c b/SRC/3p/libxbr-standalone/hq4x.c
deleted file mode 100644
index dbcd4ad..0000000
--- a/SRC/3p/libxbr-standalone/hq4x.c
+++ /dev/null
@@ -1,5232 +0,0 @@
-/*
- * Copyright (C) 2003 Maxim Stepin ( maxst@hiend3d.com )
- *
- * Copyright (C) 2010 Cameron Zemek ( grom@zeminvaders.net)
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "internal_hqx_common.h"
-
-#define PIXEL00_0 *dp = w[5];
-#define PIXEL00_11 Interp1(dp, w[5], w[4]);
-#define PIXEL00_12 Interp1(dp, w[5], w[2]);
-#define PIXEL00_20 Interp2(dp, w[5], w[2], w[4]);
-#define PIXEL00_50 Interp5(dp, w[2], w[4]);
-#define PIXEL00_80 Interp8(dp, w[5], w[1]);
-#define PIXEL00_81 Interp8(dp, w[5], w[4]);
-#define PIXEL00_82 Interp8(dp, w[5], w[2]);
-#define PIXEL01_0 *(dp+1) = w[5];
-#define PIXEL01_10 Interp1(dp+1, w[5], w[1]);
-#define PIXEL01_12 Interp1(dp+1, w[5], w[2]);
-#define PIXEL01_14 Interp1(dp+1, w[2], w[5]);
-#define PIXEL01_21 Interp2(dp+1, w[2], w[5], w[4]);
-#define PIXEL01_31 Interp3(dp+1, w[5], w[4]);
-#define PIXEL01_50 Interp5(dp+1, w[2], w[5]);
-#define PIXEL01_60 Interp6(dp+1, w[5], w[2], w[4]);
-#define PIXEL01_61 Interp6(dp+1, w[5], w[2], w[1]);
-#define PIXEL01_82 Interp8(dp+1, w[5], w[2]);
-#define PIXEL01_83 Interp8(dp+1, w[2], w[4]);
-#define PIXEL02_0 *(dp+2) = w[5];
-#define PIXEL02_10 Interp1(dp+2, w[5], w[3]);
-#define PIXEL02_11 Interp1(dp+2, w[5], w[2]);
-#define PIXEL02_13 Interp1(dp+2, w[2], w[5]);
-#define PIXEL02_21 Interp2(dp+2, w[2], w[5], w[6]);
-#define PIXEL02_32 Interp3(dp+2, w[5], w[6]);
-#define PIXEL02_50 Interp5(dp+2, w[2], w[5]);
-#define PIXEL02_60 Interp6(dp+2, w[5], w[2], w[6]);
-#define PIXEL02_61 Interp6(dp+2, w[5], w[2], w[3]);
-#define PIXEL02_81 Interp8(dp+2, w[5], w[2]);
-#define PIXEL02_83 Interp8(dp+2, w[2], w[6]);
-#define PIXEL03_0 *(dp+3) = w[5];
-#define PIXEL03_11 Interp1(dp+3, w[5], w[2]);
-#define PIXEL03_12 Interp1(dp+3, w[5], w[6]);
-#define PIXEL03_20 Interp2(dp+3, w[5], w[2], w[6]);
-#define PIXEL03_50 Interp5(dp+3, w[2], w[6]);
-#define PIXEL03_80 Interp8(dp+3, w[5], w[3]);
-#define PIXEL03_81 Interp8(dp+3, w[5], w[2]);
-#define PIXEL03_82 Interp8(dp+3, w[5], w[6]);
-#define PIXEL10_0 *(dp+dpL) = w[5];
-#define PIXEL10_10 Interp1(dp+dpL, w[5], w[1]);
-#define PIXEL10_11 Interp1(dp+dpL, w[5], w[4]);
-#define PIXEL10_13 Interp1(dp+dpL, w[4], w[5]);
-#define PIXEL10_21 Interp2(dp+dpL, w[4], w[5], w[2]);
-#define PIXEL10_32 Interp3(dp+dpL, w[5], w[2]);
-#define PIXEL10_50 Interp5(dp+dpL, w[4], w[5]);
-#define PIXEL10_60 Interp6(dp+dpL, w[5], w[4], w[2]);
-#define PIXEL10_61 Interp6(dp+dpL, w[5], w[4], w[1]);
-#define PIXEL10_81 Interp8(dp+dpL, w[5], w[4]);
-#define PIXEL10_83 Interp8(dp+dpL, w[4], w[2]);
-#define PIXEL11_0 *(dp+dpL+1) = w[5];
-#define PIXEL11_30 Interp3(dp+dpL+1, w[5], w[1]);
-#define PIXEL11_31 Interp3(dp+dpL+1, w[5], w[4]);
-#define PIXEL11_32 Interp3(dp+dpL+1, w[5], w[2]);
-#define PIXEL11_70 Interp7(dp+dpL+1, w[5], w[4], w[2]);
-#define PIXEL12_0 *(dp+dpL+2) = w[5];
-#define PIXEL12_30 Interp3(dp+dpL+2, w[5], w[3]);
-#define PIXEL12_31 Interp3(dp+dpL+2, w[5], w[2]);
-#define PIXEL12_32 Interp3(dp+dpL+2, w[5], w[6]);
-#define PIXEL12_70 Interp7(dp+dpL+2, w[5], w[6], w[2]);
-#define PIXEL13_0 *(dp+dpL+3) = w[5];
-#define PIXEL13_10 Interp1(dp+dpL+3, w[5], w[3]);
-#define PIXEL13_12 Interp1(dp+dpL+3, w[5], w[6]);
-#define PIXEL13_14 Interp1(dp+dpL+3, w[6], w[5]);
-#define PIXEL13_21 Interp2(dp+dpL+3, w[6], w[5], w[2]);
-#define PIXEL13_31 Interp3(dp+dpL+3, w[5], w[2]);
-#define PIXEL13_50 Interp5(dp+dpL+3, w[6], w[5]);
-#define PIXEL13_60 Interp6(dp+dpL+3, w[5], w[6], w[2]);
-#define PIXEL13_61 Interp6(dp+dpL+3, w[5], w[6], w[3]);
-#define PIXEL13_82 Interp8(dp+dpL+3, w[5], w[6]);
-#define PIXEL13_83 Interp8(dp+dpL+3, w[6], w[2]);
-#define PIXEL20_0 *(dp+dpL+dpL) = w[5];
-#define PIXEL20_10 Interp1(dp+dpL+dpL, w[5], w[7]);
-#define PIXEL20_12 Interp1(dp+dpL+dpL, w[5], w[4]);
-#define PIXEL20_14 Interp1(dp+dpL+dpL, w[4], w[5]);
-#define PIXEL20_21 Interp2(dp+dpL+dpL, w[4], w[5], w[8]);
-#define PIXEL20_31 Interp3(dp+dpL+dpL, w[5], w[8]);
-#define PIXEL20_50 Interp5(dp+dpL+dpL, w[4], w[5]);
-#define PIXEL20_60 Interp6(dp+dpL+dpL, w[5], w[4], w[8]);
-#define PIXEL20_61 Interp6(dp+dpL+dpL, w[5], w[4], w[7]);
-#define PIXEL20_82 Interp8(dp+dpL+dpL, w[5], w[4]);
-#define PIXEL20_83 Interp8(dp+dpL+dpL, w[4], w[8]);
-#define PIXEL21_0 *(dp+dpL+dpL+1) = w[5];
-#define PIXEL21_30 Interp3(dp+dpL+dpL+1, w[5], w[7]);
-#define PIXEL21_31 Interp3(dp+dpL+dpL+1, w[5], w[8]);
-#define PIXEL21_32 Interp3(dp+dpL+dpL+1, w[5], w[4]);
-#define PIXEL21_70 Interp7(dp+dpL+dpL+1, w[5], w[4], w[8]);
-#define PIXEL22_0 *(dp+dpL+dpL+2) = w[5];
-#define PIXEL22_30 Interp3(dp+dpL+dpL+2, w[5], w[9]);
-#define PIXEL22_31 Interp3(dp+dpL+dpL+2, w[5], w[6]);
-#define PIXEL22_32 Interp3(dp+dpL+dpL+2, w[5], w[8]);
-#define PIXEL22_70 Interp7(dp+dpL+dpL+2, w[5], w[6], w[8]);
-#define PIXEL23_0 *(dp+dpL+dpL+3) = w[5];
-#define PIXEL23_10 Interp1(dp+dpL+dpL+3, w[5], w[9]);
-#define PIXEL23_11 Interp1(dp+dpL+dpL+3, w[5], w[6]);
-#define PIXEL23_13 Interp1(dp+dpL+dpL+3, w[6], w[5]);
-#define PIXEL23_21 Interp2(dp+dpL+dpL+3, w[6], w[5], w[8]);
-#define PIXEL23_32 Interp3(dp+dpL+dpL+3, w[5], w[8]);
-#define PIXEL23_50 Interp5(dp+dpL+dpL+3, w[6], w[5]);
-#define PIXEL23_60 Interp6(dp+dpL+dpL+3, w[5], w[6], w[8]);
-#define PIXEL23_61 Interp6(dp+dpL+dpL+3, w[5], w[6], w[9]);
-#define PIXEL23_81 Interp8(dp+dpL+dpL+3, w[5], w[6]);
-#define PIXEL23_83 Interp8(dp+dpL+dpL+3, w[6], w[8]);
-#define PIXEL30_0 *(dp+dpL+dpL+dpL) = w[5];
-#define PIXEL30_11 Interp1(dp+dpL+dpL+dpL, w[5], w[8]);
-#define PIXEL30_12 Interp1(dp+dpL+dpL+dpL, w[5], w[4]);
-#define PIXEL30_20 Interp2(dp+dpL+dpL+dpL, w[5], w[8], w[4]);
-#define PIXEL30_50 Interp5(dp+dpL+dpL+dpL, w[8], w[4]);
-#define PIXEL30_80 Interp8(dp+dpL+dpL+dpL, w[5], w[7]);
-#define PIXEL30_81 Interp8(dp+dpL+dpL+dpL, w[5], w[8]);
-#define PIXEL30_82 Interp8(dp+dpL+dpL+dpL, w[5], w[4]);
-#define PIXEL31_0 *(dp+dpL+dpL+dpL+1) = w[5];
-#define PIXEL31_10 Interp1(dp+dpL+dpL+dpL+1, w[5], w[7]);
-#define PIXEL31_11 Interp1(dp+dpL+dpL+dpL+1, w[5], w[8]);
-#define PIXEL31_13 Interp1(dp+dpL+dpL+dpL+1, w[8], w[5]);
-#define PIXEL31_21 Interp2(dp+dpL+dpL+dpL+1, w[8], w[5], w[4]);
-#define PIXEL31_32 Interp3(dp+dpL+dpL+dpL+1, w[5], w[4]);
-#define PIXEL31_50 Interp5(dp+dpL+dpL+dpL+1, w[8], w[5]);
-#define PIXEL31_60 Interp6(dp+dpL+dpL+dpL+1, w[5], w[8], w[4]);
-#define PIXEL31_61 Interp6(dp+dpL+dpL+dpL+1, w[5], w[8], w[7]);
-#define PIXEL31_81 Interp8(dp+dpL+dpL+dpL+1, w[5], w[8]);
-#define PIXEL31_83 Interp8(dp+dpL+dpL+dpL+1, w[8], w[4]);
-#define PIXEL32_0 *(dp+dpL+dpL+dpL+2) = w[5];
-#define PIXEL32_10 Interp1(dp+dpL+dpL+dpL+2, w[5], w[9]);
-#define PIXEL32_12 Interp1(dp+dpL+dpL+dpL+2, w[5], w[8]);
-#define PIXEL32_14 Interp1(dp+dpL+dpL+dpL+2, w[8], w[5]);
-#define PIXEL32_21 Interp2(dp+dpL+dpL+dpL+2, w[8], w[5], w[6]);
-#define PIXEL32_31 Interp3(dp+dpL+dpL+dpL+2, w[5], w[6]);
-#define PIXEL32_50 Interp5(dp+dpL+dpL+dpL+2, w[8], w[5]);
-#define PIXEL32_60 Interp6(dp+dpL+dpL+dpL+2, w[5], w[8], w[6]);
-#define PIXEL32_61 Interp6(dp+dpL+dpL+dpL+2, w[5], w[8], w[9]);
-#define PIXEL32_82 Interp8(dp+dpL+dpL+dpL+2, w[5], w[8]);
-#define PIXEL32_83 Interp8(dp+dpL+dpL+dpL+2, w[8], w[6]);
-#define PIXEL33_0 *(dp+dpL+dpL+dpL+3) = w[5];
-#define PIXEL33_11 Interp1(dp+dpL+dpL+dpL+3, w[5], w[6]);
-#define PIXEL33_12 Interp1(dp+dpL+dpL+dpL+3, w[5], w[8]);
-#define PIXEL33_20 Interp2(dp+dpL+dpL+dpL+3, w[5], w[8], w[6]);
-#define PIXEL33_50 Interp5(dp+dpL+dpL+dpL+3, w[8], w[6]);
-#define PIXEL33_80 Interp8(dp+dpL+dpL+dpL+3, w[5], w[9]);
-#define PIXEL33_81 Interp8(dp+dpL+dpL+dpL+3, w[5], w[6]);
-#define PIXEL33_82 Interp8(dp+dpL+dpL+dpL+3, w[5], w[8]);
-
-static void hq4x_32_rb( const uint32_t * sp, uint32_t srb, uint32_t * dp, uint32_t drb, int Xres, int Yres, const xbr_data *data )
-{
- int i, j, k;
- int prevline, nextline;
- uint32_t w[10];
- int dpL = (drb >> 2);
- int spL = (srb >> 2);
- const uint8_t *sRowP = (const uint8_t *) sp;
- uint8_t *dRowP = (uint8_t *) dp;
- uint32_t yuv1, yuv2;
- int pattern, flag;
-
- // +----+----+----+
- // | | | |
- // | w1 | w2 | w3 |
- // +----+----+----+
- // | | | |
- // | w4 | w5 | w6 |
- // +----+----+----+
- // | | | |
- // | w7 | w8 | w9 |
- // +----+----+----+
-
- for (j=0; j0) prevline = -spL; else prevline = 0;
- if (j0)
- {
- w[1] = *(sp + prevline - 1);
- w[4] = *(sp - 1);
- w[7] = *(sp + nextline - 1);
- }
- else
- {
- w[1] = w[2];
- w[4] = w[5];
- w[7] = w[8];
- }
-
- if (iinput, ctx->inPitch, (uint32_t *)ctx->output, ctx->outPitch, ctx->inWidth, ctx->inHeight, ctx->data);
-}
-
-
diff --git a/SRC/3p/libxbr-standalone/internal_hqx_common.h b/SRC/3p/libxbr-standalone/internal_hqx_common.h
deleted file mode 100644
index 3ecb861..0000000
--- a/SRC/3p/libxbr-standalone/internal_hqx_common.h
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2003 Maxim Stepin ( maxst@hiend3d.com )
- *
- * Copyright (C) 2010 Cameron Zemek ( grom@zeminvaders.net)
- * Copyright (C) 2011 Francois Gannaz
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#ifndef __INTERNAL_HQX_COMMON_H_
-#define __INTERNAL_HQX_COMMON_H_
-
-#include
-#include
-
-#define XBR_INTERNAL
-#include "filters.h"
-
-#define MASK_2 0x0000FF00
-#define MASK_13 0x00FF00FF
-#define MASK_RGB 0x00FFFFFF
-#define MASK_ALPHA 0xFF000000
-
-#define Ymask 0x00FF0000
-#define Umask 0x0000FF00
-#define Vmask 0x000000FF
-#define trY 0x00300000
-#define trU 0x00000700
-#define trV 0x00000006
-
-/* RGB to YUV lookup table */
-static XBR_INLINE uint32_t rgb_to_yuv(const xbr_data *data, uint32_t c)
-{
- // Mask against MASK_RGB to discard the alpha channel
- return data->rgbtoyuv[MASK_RGB & c];
-}
-
-/* Test if there is difference in color */
-static XBR_INLINE int yuv_diff(uint32_t yuv1, uint32_t yuv2) {
- return (( abs((yuv1 & Ymask) - (yuv2 & Ymask)) > trY ) ||
- ( abs((yuv1 & Umask) - (yuv2 & Umask)) > trU ) ||
- ( abs((yuv1 & Vmask) - (yuv2 & Vmask)) > trV ) );
-}
-
-static XBR_INLINE int Diff(const xbr_data *data, uint32_t c1, uint32_t c2)
-{
- return yuv_diff(rgb_to_yuv(data, c1), rgb_to_yuv(data, c2));
-}
-
-/* Interpolate functions */
-static XBR_INLINE uint32_t Interpolate_2(uint32_t c1, int w1, uint32_t c2, int w2, int s)
-{
- if (c1 == c2) {
- return c1;
- }
- return
- (((((c1 & MASK_ALPHA) >> 24) * w1 + ((c2 & MASK_ALPHA) >> 24) * w2) << (24-s)) & MASK_ALPHA) +
- ((((c1 & MASK_2) * w1 + (c2 & MASK_2) * w2) >> s) & MASK_2) +
- ((((c1 & MASK_13) * w1 + (c2 & MASK_13) * w2) >> s) & MASK_13);
-}
-
-static XBR_INLINE uint32_t Interpolate_3(uint32_t c1, int w1, uint32_t c2, int w2, uint32_t c3, int w3, int s)
-{
- return
- (((((c1 & MASK_ALPHA) >> 24) * w1 + ((c2 & MASK_ALPHA) >> 24) * w2 + ((c3 & MASK_ALPHA) >> 24) * w3) << (24-s)) & MASK_ALPHA) +
- ((((c1 & MASK_2) * w1 + (c2 & MASK_2) * w2 + (c3 & MASK_2) * w3) >> s) & MASK_2) +
- ((((c1 & MASK_13) * w1 + (c2 & MASK_13) * w2 + (c3 & MASK_13) * w3) >> s) & MASK_13);
-}
-
-static XBR_INLINE void Interp1(uint32_t * pc, uint32_t c1, uint32_t c2)
-{
- //*pc = (c1*3+c2) >> 2;
- *pc = Interpolate_2(c1, 3, c2, 1, 2);
-}
-
-static XBR_INLINE void Interp2(uint32_t * pc, uint32_t c1, uint32_t c2, uint32_t c3)
-{
- //*pc = (c1*2+c2+c3) >> 2;
- *pc = Interpolate_3(c1, 2, c2, 1, c3, 1, 2);
-}
-
-static XBR_INLINE void Interp3(uint32_t * pc, uint32_t c1, uint32_t c2)
-{
- //*pc = (c1*7+c2)/8;
- *pc = Interpolate_2(c1, 7, c2, 1, 3);
-}
-
-static XBR_INLINE void Interp4(uint32_t * pc, uint32_t c1, uint32_t c2, uint32_t c3)
-{
- //*pc = (c1*2+(c2+c3)*7)/16;
- *pc = Interpolate_3(c1, 2, c2, 7, c3, 7, 4);
-}
-
-static XBR_INLINE void Interp5(uint32_t * pc, uint32_t c1, uint32_t c2)
-{
- //*pc = (c1+c2) >> 1;
- *pc = Interpolate_2(c1, 1, c2, 1, 1);
-}
-
-static XBR_INLINE void Interp6(uint32_t * pc, uint32_t c1, uint32_t c2, uint32_t c3)
-{
- //*pc = (c1*5+c2*2+c3)/8;
- *pc = Interpolate_3(c1, 5, c2, 2, c3, 1, 3);
-}
-
-static XBR_INLINE void Interp7(uint32_t * pc, uint32_t c1, uint32_t c2, uint32_t c3)
-{
- //*pc = (c1*6+c2+c3)/8;
- *pc = Interpolate_3(c1, 6, c2, 1, c3, 1, 3);
-}
-
-static XBR_INLINE void Interp8(uint32_t * pc, uint32_t c1, uint32_t c2)
-{
- //*pc = (c1*5+c2*3)/8;
- *pc = Interpolate_2(c1, 5, c2, 3, 3);
-}
-
-static XBR_INLINE void Interp9(uint32_t * pc, uint32_t c1, uint32_t c2, uint32_t c3)
-{
- //*pc = (c1*2+(c2+c3)*3)/8;
- *pc = Interpolate_3(c1, 2, c2, 3, c3, 3, 3);
-}
-
-static XBR_INLINE void Interp10(uint32_t * pc, uint32_t c1, uint32_t c2, uint32_t c3)
-{
- //*pc = (c1*14+c2+c3)/16;
- *pc = Interpolate_3(c1, 14, c2, 1, c3, 1, 4);
-}
-
-#endif
diff --git a/SRC/3p/libxbr-standalone/test_app.c b/SRC/3p/libxbr-standalone/test_app.c
deleted file mode 100644
index 8ec1913..0000000
--- a/SRC/3p/libxbr-standalone/test_app.c
+++ /dev/null
@@ -1,170 +0,0 @@
-#include "filters.h"
-#include
-#include
-#include
-#include
-#include
-
-
-int main(int argc, char **argv) {
- FILE *fp;
- unsigned char pngHeader[8];
- png_structp png;
- png_infop info;
- png_bytep *rowPointers;
- uint8_t *inBuffer, *outBuffer;
- int width, height;
- int scaleType, scaleFactor;
- int i;
- xbr_data *xbrData;
- xbr_params xbrParams;
-
- if (argc != 4) {
- printf("Usage: %s [input.png] [output.png] [xbr2x|xbr3x|xbr4x|hq2x|hq3x|hq4x]\n", argv[0]);
- return EXIT_FAILURE;
- }
-
- if (strcmp(argv[3], "xbr2x") == 0) {
- scaleType = 1; scaleFactor = 2;
- } else if (strcmp(argv[3], "xbr3x") == 0) {
- scaleType = 1; scaleFactor = 3;
- } else if (strcmp(argv[3], "xbr4x") == 0) {
- scaleType = 1; scaleFactor = 4;
- } else if (strcmp(argv[3], "hq2x") == 0) {
- scaleType = 2; scaleFactor = 2;
- } else if (strcmp(argv[3], "hq3x") == 0) {
- scaleType = 2; scaleFactor = 3;
- } else if (strcmp(argv[3], "hq4x") == 0) {
- scaleType = 2; scaleFactor = 4;
- } else {
- printf("Unrecognised scale mode\n");
- return EXIT_FAILURE;
- }
-
-
- /* Try to read input file */
- fp = fopen(argv[1], "rb");
- if (!fp) {
- printf("Could not open input file\n");
- return EXIT_FAILURE;
- }
-
- fread(pngHeader, 1, 8, fp);
- if (png_sig_cmp(pngHeader, 0, 8)) {
- printf("Input file does not look like a PNG\n");
- return EXIT_FAILURE;
- }
-
-
- /* Make PNG */
- png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
- if (!png) {
- printf("Could not create PNG read struct\n");
- return EXIT_FAILURE;
- }
-
- info = png_create_info_struct(png);
- if (!info) {
- printf("Could not create PNG info struct\n");
- return EXIT_FAILURE;
- }
-
- png_init_io(png, fp);
- png_set_sig_bytes(png, 8);
- png_read_png(png, info, PNG_TRANSFORM_BGR, NULL);
-
- width = png_get_image_width(png, info);
- height = png_get_image_height(png, info);
-
- if (png_get_bit_depth(png, info) != 8 || png_get_channels(png, info) != 4) {
- printf("PNG must be 32-bit\n");
- return EXIT_FAILURE;
- }
-
-
- /* Extract data */
- inBuffer = malloc(width * height * 4);
-
- rowPointers = png_get_rows(png, info);
- for (i = 0; i < height; i++) {
- memcpy(inBuffer + (i * width * 4), rowPointers[i], width * 4);
- }
-
-
- png_destroy_read_struct(&png, &info, NULL);
- fclose(fp);
-
-
- /* CONVERT IT! */
- outBuffer = malloc(width * scaleFactor * height * scaleFactor * 4);
-
- xbrData = malloc(sizeof(xbr_data));
- xbr_init_data(xbrData);
-
- xbrParams.data = xbrData;
- xbrParams.input = inBuffer;
- xbrParams.output = outBuffer;
- xbrParams.inWidth = width;
- xbrParams.inHeight = height;
- xbrParams.inPitch = width * 4;
- xbrParams.outPitch = width * scaleFactor * 4;
-
- if (scaleType == 1) {
- switch (scaleFactor) {
- case 2: xbr_filter_xbr2x(&xbrParams); break;
- case 3: xbr_filter_xbr3x(&xbrParams); break;
- case 4: xbr_filter_xbr4x(&xbrParams); break;
- }
- } else if (scaleType == 2) {
- switch (scaleFactor) {
- case 2: xbr_filter_hq2x(&xbrParams); break;
- case 3: xbr_filter_hq3x(&xbrParams); break;
- case 4: xbr_filter_hq4x(&xbrParams); break;
- }
- }
-
-
- /* Generate a new PNG */
- fp = fopen(argv[2], "wb");
- if (!fp) {
- printf("Could not open output file for writing\n");
- return EXIT_FAILURE;
- }
-
- png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
- if (!png) {
- printf("Could not create PNG write struct\n");
- return EXIT_FAILURE;
- }
-
- info = png_create_info_struct(png);
- if (!info) {
- printf("Could not create info write struct\n");
- return EXIT_FAILURE;
- }
-
- if (setjmp(png_jmpbuf(png))) {
- png_destroy_write_struct(&png, &info);
- fclose(fp);
- printf("Error writing PNG\n");
- return EXIT_FAILURE;
- }
-
- png_init_io(png, fp);
- png_set_IHDR(png, info, width*scaleFactor, height*scaleFactor, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
-
- rowPointers = malloc(sizeof(png_bytep) * height * scaleFactor);
- for (i = 0; i < (height * scaleFactor); i++) {
- rowPointers[i] = outBuffer + (i * width * scaleFactor * 4);
- }
-
- png_set_rows(png, info, rowPointers);
- png_write_png(png, info, PNG_TRANSFORM_BGR, NULL);
- png_destroy_write_struct(&png, &info);
-
- fclose(fp);
-
- return EXIT_SUCCESS;
-}
-
-
diff --git a/SRC/3p/libxbr-standalone/vs2012/libxbr.sln b/SRC/3p/libxbr-standalone/vs2012/libxbr.sln
deleted file mode 100644
index 9cca227..0000000
--- a/SRC/3p/libxbr-standalone/vs2012/libxbr.sln
+++ /dev/null
@@ -1,20 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Express 2012 for Windows Desktop
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libxbr", "libxbr.vcxproj", "{D1CDD7AC-7B32-4716-9821-987B381E4409}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Win32 = Debug|Win32
- Release|Win32 = Release|Win32
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {D1CDD7AC-7B32-4716-9821-987B381E4409}.Debug|Win32.ActiveCfg = Debug|Win32
- {D1CDD7AC-7B32-4716-9821-987B381E4409}.Debug|Win32.Build.0 = Debug|Win32
- {D1CDD7AC-7B32-4716-9821-987B381E4409}.Release|Win32.ActiveCfg = Release|Win32
- {D1CDD7AC-7B32-4716-9821-987B381E4409}.Release|Win32.Build.0 = Release|Win32
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
diff --git a/SRC/3p/libxbr-standalone/vs2012/libxbr.vcxproj b/SRC/3p/libxbr-standalone/vs2012/libxbr.vcxproj
deleted file mode 100644
index fd69140..0000000
--- a/SRC/3p/libxbr-standalone/vs2012/libxbr.vcxproj
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
-
- Debug
- Win32
-
-
- Release
- Win32
-
-
-
- {D1CDD7AC-7B32-4716-9821-987B381E4409}
- libxbr
-
-
-
- DynamicLibrary
- true
- v110
- MultiByte
-
-
- DynamicLibrary
- false
- v110
- true
- MultiByte
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Level3
- Disabled
- true
-
-
- true
-
-
-
-
- Level3
- MaxSpeed
- true
- true
- true
-
-
- true
- true
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/SRC/3p/libxbr-standalone/vs2012/libxbr.vcxproj.filters b/SRC/3p/libxbr-standalone/vs2012/libxbr.vcxproj.filters
deleted file mode 100644
index d507695..0000000
--- a/SRC/3p/libxbr-standalone/vs2012/libxbr.vcxproj.filters
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
- {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
- cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
-
-
- {93995380-89BD-4b04-88EB-625FBE52EBFB}
- h;hpp;hxx;hm;inl;inc;xsd
-
-
- {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
- rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
-
-
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
-
-
- Header Files
-
-
- Header Files
-
-
-
\ No newline at end of file
diff --git a/SRC/3p/libxbr-standalone/xbr.c b/SRC/3p/libxbr-standalone/xbr.c
deleted file mode 100644
index ac3782e..0000000
--- a/SRC/3p/libxbr-standalone/xbr.c
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * XBR filter extracted from FFmpeg into a separate library.
- *
- *
- * Copyright (c) 2011, 2012 Hyllian/Jararaca
- * Copyright (c) 2014 Arwa Arif
- * Copyright (c) 2015 Treeki
- *
- * FFmpeg is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * FFmpeg is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with FFmpeg; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-/**
- * @file
- * XBR Filter is used for depixelization of image.
- * This is based on Hyllian's xBR shader.
- *
- * @see http://www.libretro.com/forums/viewtopic.php?f=6&t=134
- * @see https://github.com/yoyofr/iFBA/blob/master/fba_src/src/intf/video/scalers/xbr.cpp
- */
-
-#define XBR_INTERNAL
-#include "filters.h"
-#include
-
-#define LB_MASK 0x00FEFEFE
-#define RED_BLUE_MASK 0x00FF00FF
-#define GREEN_MASK 0x0000FF00
-#define PART_MASK 0x00FF00FF
-
-static uint32_t pixel_diff(uint32_t x, uint32_t y, const uint32_t *r2y)
-{
-#define YMASK 0xff0000
-#define UMASK 0x00ff00
-#define VMASK 0x0000ff
-
- uint32_t yuv1 = r2y[x & 0xffffff];
- uint32_t yuv2 = r2y[y & 0xffffff];
-
- return (abs((x >> 24) - (y >> 24))) +
- (abs((yuv1 & YMASK) - (yuv2 & YMASK)) >> 16) +
- (abs((yuv1 & UMASK) - (yuv2 & UMASK)) >> 8) +
- abs((yuv1 & VMASK) - (yuv2 & VMASK));
-}
-
-#define ALPHA_BLEND_BASE(a, b, m, s) ( (PART_MASK & (((a) & PART_MASK) + (((((b) & PART_MASK) - ((a) & PART_MASK)) * (m)) >> (s)))) \
- | ((PART_MASK & ((((a) >> 8) & PART_MASK) + ((((((b) >> 8) & PART_MASK) - (((a) >> 8) & PART_MASK)) * (m)) >> (s)))) << 8))
-
-#define ALPHA_BLEND_32_W(a, b) ALPHA_BLEND_BASE(a, b, 1, 3)
-#define ALPHA_BLEND_64_W(a, b) ALPHA_BLEND_BASE(a, b, 1, 2)
-#define ALPHA_BLEND_128_W(a, b) ALPHA_BLEND_BASE(a, b, 1, 1)
-#define ALPHA_BLEND_192_W(a, b) ALPHA_BLEND_BASE(a, b, 3, 2)
-#define ALPHA_BLEND_224_W(a, b) ALPHA_BLEND_BASE(a, b, 7, 3)
-
-
-
-#define df(A, B) pixel_diff(A, B, r2y)
-#define eq(A, B) (df(A, B) < 155)
-
-#define FILT2(PE, PI, PH, PF, PG, PC, PD, PB, PA, G5, C4, G0, D0, C1, B1, F4, I4, H5, I5, A0, A1, \
- N0, N1, N2, N3) do { \
- if (PE != PH && PE != PF) { \
- const unsigned e = df(PE,PC) + df(PE,PG) + df(PI,H5) + df(PI,F4) + (df(PH,PF)<<2); \
- const unsigned i = df(PH,PD) + df(PH,I5) + df(PF,I4) + df(PF,PB) + (df(PE,PI)<<2); \
- if (e <= i) { \
- const unsigned px = df(PE,PF) <= df(PE,PH) ? PF : PH; \
- if (e < i && (!eq(PF,PB) && !eq(PH,PD) || eq(PE,PI) \
- && (!eq(PF,I4) && !eq(PH,I5)) \
- || eq(PE,PG) || eq(PE,PC))) { \
- const unsigned ke = df(PF,PG); \
- const unsigned ki = df(PH,PC); \
- const int left = ke<<1 <= ki && PE != PG && PD != PG; \
- const int up = ke >= ki<<1 && PE != PC && PB != PC; \
- if (left && up) { \
- E[N3] = ALPHA_BLEND_224_W(E[N3], px); \
- E[N2] = ALPHA_BLEND_64_W( E[N2], px); \
- E[N1] = E[N2]; \
- } else if (left) { \
- E[N3] = ALPHA_BLEND_192_W(E[N3], px); \
- E[N2] = ALPHA_BLEND_64_W( E[N2], px); \
- } else if (up) { \
- E[N3] = ALPHA_BLEND_192_W(E[N3], px); \
- E[N1] = ALPHA_BLEND_64_W( E[N1], px); \
- } else { /* diagonal */ \
- E[N3] = ALPHA_BLEND_128_W(E[N3], px); \
- } \
- } else { \
- E[N3] = ALPHA_BLEND_128_W(E[N3], px); \
- } \
- } \
- } \
-} while (0)
-
-#define FILT3(PE, PI, PH, PF, PG, PC, PD, PB, PA, G5, C4, G0, D0, C1, B1, F4, I4, H5, I5, A0, A1, \
- N0, N1, N2, N3, N4, N5, N6, N7, N8) do { \
- if (PE != PH && PE != PF) { \
- const unsigned e = df(PE,PC) + df(PE,PG) + df(PI,H5) + df(PI,F4) + (df(PH,PF)<<2); \
- const unsigned i = df(PH,PD) + df(PH,I5) + df(PF,I4) + df(PF,PB) + (df(PE,PI)<<2); \
- if (e <= i) { \
- const unsigned px = df(PE,PF) <= df(PE,PH) ? PF : PH; \
- if (e < i && (!eq(PF,PB) && !eq(PF,PC) || !eq(PH,PD) && !eq(PH,PG) || eq(PE,PI) \
- && (!eq(PF,F4) && !eq(PF,I4) || !eq(PH,H5) && !eq(PH,I5)) \
- || eq(PE,PG) || eq(PE,PC))) { \
- const unsigned ke = df(PF,PG); \
- const unsigned ki = df(PH,PC); \
- const int left = ke<<1 <= ki && PE != PG && PD != PG; \
- const int up = ke >= ki<<1 && PE != PC && PB != PC; \
- if (left && up) { \
- E[N7] = ALPHA_BLEND_192_W(E[N7], px); \
- E[N6] = ALPHA_BLEND_64_W( E[N6], px); \
- E[N5] = E[N7]; \
- E[N2] = E[N6]; \
- E[N8] = px; \
- } else if (left) { \
- E[N7] = ALPHA_BLEND_192_W(E[N7], px); \
- E[N5] = ALPHA_BLEND_64_W( E[N5], px); \
- E[N6] = ALPHA_BLEND_64_W( E[N6], px); \
- E[N8] = px; \
- } else if (up) { \
- E[N5] = ALPHA_BLEND_192_W(E[N5], px); \
- E[N7] = ALPHA_BLEND_64_W( E[N7], px); \
- E[N2] = ALPHA_BLEND_64_W( E[N2], px); \
- E[N8] = px; \
- } else { /* diagonal */ \
- E[N8] = ALPHA_BLEND_224_W(E[N8], px); \
- E[N5] = ALPHA_BLEND_32_W( E[N5], px); \
- E[N7] = ALPHA_BLEND_32_W( E[N7], px); \
- } \
- } else { \
- E[N8] = ALPHA_BLEND_128_W(E[N8], px); \
- } \
- } \
- } \
-} while (0)
-
-#define FILT4(PE, PI, PH, PF, PG, PC, PD, PB, PA, G5, C4, G0, D0, C1, B1, F4, I4, H5, I5, A0, A1, \
- N15, N14, N11, N3, N7, N10, N13, N12, N9, N6, N2, N1, N5, N8, N4, N0) do { \
- if (PE != PH && PE != PF) { \
- const unsigned e = df(PE,PC) + df(PE,PG) + df(PI,H5) + df(PI,F4) + (df(PH,PF)<<2); \
- const unsigned i = df(PH,PD) + df(PH,I5) + df(PF,I4) + df(PF,PB) + (df(PE,PI)<<2); \
- if (e <= i) { \
- const unsigned px = df(PE,PF) <= df(PE,PH) ? PF : PH; \
- if (e < i && (!eq(PF,PB) && !eq(PH,PD) || eq(PE,PI) \
- && (!eq(PF,I4) && !eq(PH,I5)) \
- || eq(PE,PG) || eq(PE,PC))) { \
- const unsigned ke = df(PF,PG); \
- const unsigned ki = df(PH,PC); \
- const int left = ke<<1 <= ki && PE != PG && PD != PG; \
- const int up = ke >= ki<<1 && PE != PC && PB != PC; \
- if (left && up) { \
- E[N13] = ALPHA_BLEND_192_W(E[N13], px); \
- E[N12] = ALPHA_BLEND_64_W( E[N12], px); \
- E[N15] = E[N14] = E[N11] = px; \
- E[N10] = E[N3] = E[N12]; \
- E[N7] = E[N13]; \
- } else if (left) { \
- E[N11] = ALPHA_BLEND_192_W(E[N11], px); \
- E[N13] = ALPHA_BLEND_192_W(E[N13], px); \
- E[N10] = ALPHA_BLEND_64_W( E[N10], px); \
- E[N12] = ALPHA_BLEND_64_W( E[N12], px); \
- E[N14] = px; \
- E[N15] = px; \
- } else if (up) { \
- E[N14] = ALPHA_BLEND_192_W(E[N14], px); \
- E[N7 ] = ALPHA_BLEND_192_W(E[N7 ], px); \
- E[N10] = ALPHA_BLEND_64_W( E[N10], px); \
- E[N3 ] = ALPHA_BLEND_64_W( E[N3 ], px); \
- E[N11] = px; \
- E[N15] = px; \
- } else { /* diagonal */ \
- E[N11] = ALPHA_BLEND_128_W(E[N11], px); \
- E[N14] = ALPHA_BLEND_128_W(E[N14], px); \
- E[N15] = px; \
- } \
- } else { \
- E[N15] = ALPHA_BLEND_128_W(E[N15], px); \
- } \
- } \
- } \
-} while (0)
-
-static XBR_INLINE void xbr_filter(const xbr_params *params, int n)
-{
- int x, y;
- const uint32_t *r2y = params->data->rgbtoyuv;
- const int nl = params->outPitch >> 2;
- const int nl1 = nl + nl;
- const int nl2 = nl1 + nl;
-
- for (y = 0; y < params->inHeight; y++) {
-
- uint32_t *E = (uint32_t *)(params->output + y * params->outPitch * n);
- const uint32_t *sa2 = (uint32_t *)(params->input + y * params->inPitch - 8); /* center */
- const uint32_t *sa1 = sa2 - (params->inPitch>>2); /* up x1 */
- const uint32_t *sa0 = sa1 - (params->inPitch>>2); /* up x2 */
- const uint32_t *sa3 = sa2 + (params->inPitch>>2); /* down x1 */
- const uint32_t *sa4 = sa3 + (params->inPitch>>2); /* down x2 */
-
- if (y <= 1) {
- sa0 = sa1;
- if (y == 0) {
- sa0 = sa1 = sa2;
- }
- }
-
- if (y >= params->inHeight - 2) {
- sa4 = sa3;
- if (y == params->inHeight - 1) {
- sa4 = sa3 = sa2;
- }
- }
-
- for (x = 0; x < params->inWidth; x++) {
- const uint32_t B1 = sa0[2];
- const uint32_t PB = sa1[2];
- const uint32_t PE = sa2[2];
- const uint32_t PH = sa3[2];
- const uint32_t H5 = sa4[2];
-
- const int pprev = 2 - (x > 0);
- const uint32_t A1 = sa0[pprev];
- const uint32_t PA = sa1[pprev];
- const uint32_t PD = sa2[pprev];
- const uint32_t PG = sa3[pprev];
- const uint32_t G5 = sa4[pprev];
-
- const int pprev2 = pprev - (x > 1);
- const uint32_t A0 = sa1[pprev2];
- const uint32_t D0 = sa2[pprev2];
- const uint32_t G0 = sa3[pprev2];
-
- const int pnext = 3 - (x == params->inWidth - 1);
- const uint32_t C1 = sa0[pnext];
- const uint32_t PC = sa1[pnext];
- const uint32_t PF = sa2[pnext];
- const uint32_t PI = sa3[pnext];
- const uint32_t I5 = sa4[pnext];
-
- const int pnext2 = pnext + 1 - (x >= params->inWidth - 2);
- const uint32_t C4 = sa1[pnext2];
- const uint32_t F4 = sa2[pnext2];
- const uint32_t I4 = sa3[pnext2];
-
- if (n == 2) {
- E[0] = E[1] = // 0, 1
- E[nl] = E[nl + 1] = PE; // 2, 3
-
- FILT2(PE, PI, PH, PF, PG, PC, PD, PB, PA, G5, C4, G0, D0, C1, B1, F4, I4, H5, I5, A0, A1, 0, 1, nl, nl+1);
- FILT2(PE, PC, PF, PB, PI, PA, PH, PD, PG, I4, A1, I5, H5, A0, D0, B1, C1, F4, C4, G5, G0, nl, 0, nl+1, 1);
- FILT2(PE, PA, PB, PD, PC, PG, PF, PH, PI, C1, G0, C4, F4, G5, H5, D0, A0, B1, A1, I4, I5, nl+1, nl, 1, 0);
- FILT2(PE, PG, PD, PH, PA, PI, PB, PF, PC, A0, I5, A1, B1, I4, F4, H5, G5, D0, G0, C1, C4, 1, nl+1, 0, nl);
- } else if (n == 3) {
- E[0] = E[1] = E[2] = // 0, 1, 2
- E[nl] = E[nl+1] = E[nl+2] = // 3, 4, 5
- E[nl1] = E[nl1+1] = E[nl1+2] = PE; // 6, 7, 8
-
- FILT3(PE, PI, PH, PF, PG, PC, PD, PB, PA, G5, C4, G0, D0, C1, B1, F4, I4, H5, I5, A0, A1, 0, 1, 2, nl, nl+1, nl+2, nl1, nl1+1, nl1+2);
- FILT3(PE, PC, PF, PB, PI, PA, PH, PD, PG, I4, A1, I5, H5, A0, D0, B1, C1, F4, C4, G5, G0, nl1, nl, 0, nl1+1, nl+1, 1, nl1+2, nl+2, 2);
- FILT3(PE, PA, PB, PD, PC, PG, PF, PH, PI, C1, G0, C4, F4, G5, H5, D0, A0, B1, A1, I4, I5, nl1+2, nl1+1, nl1, nl+2, nl+1, nl, 2, 1, 0);
- FILT3(PE, PG, PD, PH, PA, PI, PB, PF, PC, A0, I5, A1, B1, I4, F4, H5, G5, D0, G0, C1, C4, 2, nl+2, nl1+2, 1, nl+1, nl1+1, 0, nl, nl1);
- } else if (n == 4) {
- E[0] = E[1] = E[2] = E[3] = // 0, 1, 2, 3
- E[nl] = E[nl+1] = E[nl+2] = E[nl+3] = // 4, 5, 6, 7
- E[nl1] = E[nl1+1] = E[nl1+2] = E[nl1+3] = // 8, 9, 10, 11
- E[nl2] = E[nl2+1] = E[nl2+2] = E[nl2+3] = PE; // 12, 13, 14, 15
-
- FILT4(PE, PI, PH, PF, PG, PC, PD, PB, PA, G5, C4, G0, D0, C1, B1, F4, I4, H5, I5, A0, A1, nl2+3, nl2+2, nl1+3, 3, nl+3, nl1+2, nl2+1, nl2, nl1+1, nl+2, 2, 1, nl+1, nl1, nl, 0);
- FILT4(PE, PC, PF, PB, PI, PA, PH, PD, PG, I4, A1, I5, H5, A0, D0, B1, C1, F4, C4, G5, G0, 3, nl+3, 2, 0, 1, nl+2, nl1+3, nl2+3, nl1+2, nl+1, nl, nl1, nl1+1, nl2+2, nl2+1, nl2);
- FILT4(PE, PA, PB, PD, PC, PG, PF, PH, PI, C1, G0, C4, F4, G5, H5, D0, A0, B1, A1, I4, I5, 0, 1, nl, nl2, nl1, nl+1, 2, 3, nl+2, nl1+1, nl2+1, nl2+2, nl1+2, nl+3, nl1+3, nl2+3);
- FILT4(PE, PG, PD, PH, PA, PI, PB, PF, PC, A0, I5, A1, B1, I4, F4, H5, G5, D0, G0, C1, C4, nl2, nl1, nl2+1, nl2+3, nl2+2, nl1+1, nl, 0, nl+1, nl1+2, nl1+3, nl+3, nl+2, 1, 2, 3);
- }
-
- sa0 += 1;
- sa1 += 1;
- sa2 += 1;
- sa3 += 1;
- sa4 += 1;
-
- E += n;
- }
- }
-}
-
-#define XBR_FUNC(size) \
-void xbr_filter_xbr##size##x(const xbr_params *params) \
-{ \
- xbr_filter(params, size); \
-}
-
-XBR_FUNC(2)
-XBR_FUNC(3)
-XBR_FUNC(4)
-
-
-static XBR_INLINE int _max(int a, int b)
-{
- return (a > b) ? a : b;
-}
-
-static XBR_INLINE int _min(int a, int b)
-{
- return (a < b) ? a : b;
-}
-
-
-void xbr_init_data(xbr_data *data)
-{
- uint32_t c;
- int bg, rg, g;
-
- for (bg = -255; bg < 256; bg++) {
- for (rg = -255; rg < 256; rg++) {
- const uint32_t u = (uint32_t)((-169*rg + 500*bg)/1000) + 128;
- const uint32_t v = (uint32_t)(( 500*rg - 81*bg)/1000) + 128;
- int startg = _max(-bg, _max(-rg, 0));
- int endg = _min(255-bg, _min(255-rg, 255));
- uint32_t y = (uint32_t)(( 299*rg + 1000*startg + 114*bg)/1000);
- c = bg + (rg<<16) + 0x010101 * startg;
- for (g = startg; g <= endg; g++) {
- data->rgbtoyuv[c] = ((y++) << 16) + (u << 8) + v;
- c+= 0x010101;
- }
- }
- }
-}
-
diff --git a/SRC/lzw.h b/SRC/lzw.h
deleted file mode 100644
index 1edf142..0000000
--- a/SRC/lzw.h
+++ /dev/null
@@ -1,8 +0,0 @@
-
-#ifndef LZW_H__
-#define LZW_H__
-
-int decodeLZW(const uint8_t *src, uint8_t *dst);
-
-#endif // LZW_H__
-
diff --git a/SRC/menu.cpp b/SRC/menu.cpp
deleted file mode 100644
index a9b58b3..0000000
--- a/SRC/menu.cpp
+++ /dev/null
@@ -1,180 +0,0 @@
-
-#include "game.h"
-#include "menu.h"
-#include "lzw.h"
-#include "paf.h"
-#include "resource.h"
-#include "system.h"
-#include "util.h"
-#include "video.h"
-
-Menu::Menu(Game *g, PafPlayer *paf, Resource *res, Video *video)
- : _g(g), _paf(paf), _res(res), _video(video) {
- _titleSprites = 0;
- _playerSprites = 0;
- _titleBitmapData = 0;
- _titleBitmapSize = 0;
- _playerBitmapData = 0;
- _playerBitmapSize = 0;
- _soundData = 0;
- _currentOption = 0;
-}
-
-void Menu::loadData() {
- _res->loadDatMenuBuffers();
-
- const int version = _res->_datHdr.version;
- const int options = 19;
-
- const uint8_t *ptr = _res->_menuBuffer1;
- uint32_t ptrOffset = 0;
-
- if (version == 10) {
-
- _titleSprites = (DatSpritesGroup *)(ptr + ptrOffset);
- _titleSprites->size = le32toh(_titleSprites->size);
- ptrOffset += sizeof(DatSpritesGroup) + _titleSprites->size;
-
- _playerSprites = (DatSpritesGroup *)(ptr + ptrOffset);
- _playerSprites->size = le32toh(_playerSprites->size);
- ptrOffset += sizeof(DatSpritesGroup) + _playerSprites->size;
-
- _titleBitmapSize = READ_LE_UINT32(ptr + ptrOffset);
- _titleBitmapData = ptr + ptrOffset + 8;
- ptrOffset += 8 + _titleBitmapSize + 768;
-
- _playerBitmapSize = READ_LE_UINT32(ptr + ptrOffset);
- _playerBitmapData = ptr + ptrOffset + 8;
- ptrOffset += 8 + _playerBitmapSize + 768;
-
- } else if (version == 11) {
-
- ptr = _res->_menuBuffer1;
- uint32_t hdrOffset = 4;
-
- ptrOffset = 4 + (2 + options) * 8; // pointers to bitmaps
- ptrOffset += _res->_datHdr.cutscenesCount * 12;
- for (int i = 0; i < 8; ++i) {
- ptrOffset += _res->_datHdr.levelCheckpointsCount[i] * 12;
- }
- ptrOffset += _res->_datHdr.levelsCount * 12;
-
- _titleBitmapSize = READ_LE_UINT32(ptr + hdrOffset);
- hdrOffset += 8;
- _titleBitmapData = ptr + ptrOffset;
- ptrOffset += _titleBitmapSize + 768;
-
- _playerBitmapSize = READ_LE_UINT32(ptr + hdrOffset);
- hdrOffset += 8;
- _playerBitmapData = ptr + ptrOffset;
- ptrOffset += _playerBitmapSize + 768;
- }
-
- ptr = _res->_menuBuffer0;
- ptrOffset = 0;
-
- if (version == 11) {
-
- _titleSprites = (DatSpritesGroup *)(ptr + ptrOffset);
- _titleSprites->size = le32toh(_titleSprites->size);
- ptrOffset += sizeof(DatSpritesGroup) + _titleSprites->size;
-
- _playerSprites = (DatSpritesGroup *)(ptr + ptrOffset);
- _playerSprites->size = le32toh(_playerSprites->size);
- ptrOffset += sizeof(DatSpritesGroup) + _playerSprites->size;
-
- ptrOffset += _res->_datHdr.menusCount * 8;
-
- const int size = READ_LE_UINT32(ptr + ptrOffset); ptrOffset += 4;
- assert((size % (16 * 10)) == 0);
- ptrOffset += size;
-
- _soundData = ptr + ptrOffset;
- ptrOffset += _res->_datHdr.soundDataSize;
- }
-}
-
-int Menu::getSoundNum(int num) const {
- assert((num & 7) == 0);
- num /= 8;
- if (_soundData) {
- const int count = READ_LE_UINT32(_soundData + 4);
- const uint8_t *p = _soundData + 8 + count * 8;
- for (int i = 0; i < count; ++i) {
- const int count2 = READ_LE_UINT32(_soundData + 8 + i * 8 + 4);
- if (i == num) {
- assert(count2 != 0);
- return (int16_t)READ_LE_UINT16(p);
- }
- p += count2 * 2;
- }
- assert((p - _soundData) == _res->_datHdr.soundDataSize);
- }
- return -1;
-}
-
-void Menu::playSound(int num) {
- num = getSoundNum(num);
- if (num != -1) {
- _g->playSound(num, 0, 0, 5);
- }
-}
-
-void Menu::drawSprite(const DatSpritesGroup *spriteGroup, uint32_t num) {
- const uint8_t *ptr = (const uint8_t *)&spriteGroup[1];
- for (uint32_t i = 0; i < spriteGroup->count; ++i) {
- const uint16_t size = READ_LE_UINT16(ptr + 2);
- if (num == i) {
- _video->decodeSPR(ptr + 8, _video->_frontLayer, ptr[0], ptr[1], 0, READ_LE_UINT16(ptr + 4), READ_LE_UINT16(ptr + 6));
- break;
- }
- ptr += size + 2;
- }
-}
-
-void Menu::drawBitmap(const uint8_t *bitmapData, uint32_t bitmapSize, const DatSpritesGroup *spritesGroup) {
- const uint32_t uncompressedSize = decodeLZW(bitmapData, _video->_frontLayer);
- assert(uncompressedSize == Video::W * Video::H);
- const uint8_t *palette = bitmapData + bitmapSize;
- g_system->setPalette(palette, 256, 6);
- drawSprite(spritesGroup, _currentOption);
- g_system->copyRect(0, 0, Video::W, Video::H, _video->_frontLayer, Video::W);
- g_system->updateScreen(false);
-}
-
-void Menu::mainLoop() {
- loadData();
- handleTitleScreen();
-}
-
-void Menu::handleTitleScreen() {
- while (!g_system->inp.quit) {
- if (g_system->inp.keyReleased(SYS_INP_UP)) {
- if (_currentOption > 0) {
- --_currentOption;
- }
- playSound(0x70);
- }
- if (g_system->inp.keyReleased(SYS_INP_DOWN)) {
- if (_currentOption < 3) {
- ++_currentOption;
- }
- playSound(0x70);
- }
- if (g_system->inp.keyReleased(SYS_INP_SHOOT) || g_system->inp.keyReleased(SYS_INP_JUMP)) {
- if (_currentOption == 0) {
- // assign player
- } else if (_currentOption == 1) {
- // play
- } else if (_currentOption == 2) {
- // options
- } else if (_currentOption == 3) {
- break;
- }
- playSound(0x78);
- }
- drawBitmap(_titleBitmapData, _titleBitmapSize, _titleSprites);
- g_system->processEvents();
- g_system->sleep(15);
- }
-}
diff --git a/SRC/menu.h b/SRC/menu.h
deleted file mode 100644
index 401a0d0..0000000
--- a/SRC/menu.h
+++ /dev/null
@@ -1,55 +0,0 @@
-
-#ifndef MENU_H__
-#define MENU_H__
-
-#include "intern.h"
-
-struct Game;
-struct PafPlayer;
-struct Resource;
-struct Video;
-
-struct DatSpritesGroup {
- uint32_t unk0; // 0
- uint32_t unk4; // 4
- uint32_t size; // 8 following this header
- uint32_t count; // 12
-} PACKED; // sizeof == 16
-
-struct DatBitmap {
- uint32_t size; // 0
- uint32_t unk4; // 4
- // 8 lzw + 768 palette
-} PACKED; // sizeof == 8
-
-struct Menu {
-
- Game *_g;
- PafPlayer *_paf;
- Resource *_res;
- Video *_video;
-
- DatSpritesGroup *_titleSprites;
- DatSpritesGroup *_playerSprites;
- const uint8_t *_titleBitmapData;
- uint32_t _titleBitmapSize;
- const uint8_t *_playerBitmapData;
- uint32_t _playerBitmapSize;
- const uint8_t *_soundData;
- int _currentOption;
-
- Menu(Game *g, PafPlayer *paf, Resource *res, Video *video);
-
- void loadData();
-
- int getSoundNum(int num) const;
- void playSound(int num);
-
- void drawSprite(const DatSpritesGroup *spriteGroup, uint32_t num);
- void drawBitmap(const uint8_t *bitmapData, uint32_t bitmapSize, const DatSpritesGroup *spritesGroup);
-
- void mainLoop();
- void handleTitleScreen();
-};
-
-#endif // MENU_H__
diff --git a/SRC/screenshot.cpp b/SRC/screenshot.cpp
deleted file mode 100644
index 89183ab..0000000
--- a/SRC/screenshot.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-
-#include
-#include
-#include "screenshot.h"
-
-static void fwriteUint16LE(FILE *fp, uint16_t n) {
- fputc(n & 0xFF, fp);
- fputc(n >> 8, fp);
-}
-
-static void fwriteUint32LE(FILE *fp, uint32_t n) {
- fwriteUint16LE(fp, n & 0xFFFF);
- fwriteUint16LE(fp, n >> 16);
-}
-
-static const uint16_t TAG_BM = 0x4D42;
-
-void saveBMP(const char *filename, const uint8_t *bits, const uint8_t *pal, int w, int h) {
- FILE *fp = fopen(filename, "wb");
- if (fp) {
- int alignWidth = (w + 3) & ~3;
- int imageSize = alignWidth * h;
-
- // Write file header
- fwriteUint16LE(fp, TAG_BM);
- fwriteUint32LE(fp, 14 + 40 + 4 * 256 + imageSize);
- fwriteUint16LE(fp, 0); // reserved1
- fwriteUint16LE(fp, 0); // reserved2
- fwriteUint32LE(fp, 14 + 40 + 4 * 256);
-
- // Write info header
- fwriteUint32LE(fp, 40);
- fwriteUint32LE(fp, w);
- fwriteUint32LE(fp, h);
- fwriteUint16LE(fp, 1); // planes
- fwriteUint16LE(fp, 8); // bit_count
- fwriteUint32LE(fp, 0); // compression
- fwriteUint32LE(fp, imageSize); // size_image
- fwriteUint32LE(fp, 0); // x_pels_per_meter
- fwriteUint32LE(fp, 0); // y_pels_per_meter
- fwriteUint32LE(fp, 0); // num_colors_used
- fwriteUint32LE(fp, 0); // num_colors_important
-
- // Write palette data
- for (int i = 0; i < 256; ++i) {
- fputc(pal[2], fp);
- fputc(pal[1], fp);
- fputc(pal[0], fp);
- fputc(0, fp);
- pal += 3;
- }
-
- // Write bitmap data
- const int pitch = w;
- bits += h * pitch;
- for (int i = 0; i < h; ++i) {
- bits -= pitch;
- fwrite(bits, w, 1, fp);
- int pad = alignWidth - w;
- while (pad--) {
- fputc(0, fp);
- }
- }
-
- fclose(fp);
- }
-}
diff --git a/SRC/screenshot.h b/SRC/screenshot.h
deleted file mode 100644
index b569b11..0000000
--- a/SRC/screenshot.h
+++ /dev/null
@@ -1,10 +0,0 @@
-
-#ifndef SCREENSHOT_H__
-#define SCREENSHOT_H__
-
-#include
-
-void saveTGA(const char *filename, const uint8_t *rgb, int w, int h);
-void saveBMP(const char *filename, const uint8_t *bits, const uint8_t *pal, int w, int h);
-
-#endif
diff --git a/SRC/CMakeLists.txt b/Src/Global/CMakeLists.txt
similarity index 100%
rename from SRC/CMakeLists.txt
rename to Src/Global/CMakeLists.txt
diff --git a/SRC/Makefile b/Src/Global/Makefile
similarity index 82%
rename from SRC/Makefile
rename to Src/Global/Makefile
index bb1e33e..a2eb586 100644
--- a/SRC/Makefile
+++ b/Src/Global/Makefile
@@ -7,8 +7,8 @@ CPPFLAGS += -g -Wall -Wpedantic $(SDL_CFLAGS) $(DEFINES) -MMD
SRCS = andy.cpp benchmark.cpp fileio.cpp fs_posix.cpp game.cpp \
level1_rock.cpp level2_fort.cpp level3_pwr1.cpp level4_isld.cpp \
level5_lava.cpp level6_pwr2.cpp level7_lar1.cpp level8_lar2.cpp level9_dark.cpp \
- lzw.cpp main.cpp menu.cpp mixer.cpp monsters.cpp paf.cpp random.cpp resource.cpp \
- screenshot.cpp sound.cpp staticres.cpp system_sdl2.cpp \
+ lzw.cpp main.cpp mdec.cpp menu.cpp mixer.cpp monsters.cpp paf.cpp random.cpp \
+ resource.cpp screenshot.cpp sound.cpp staticres.cpp system_sdl2.cpp \
util.cpp video.cpp
SCALERS := scaler_nearest.cpp scaler_xbr.cpp
diff --git a/SRC/andy.cpp b/Src/Global/andy.cpp
similarity index 99%
rename from SRC/andy.cpp
rename to Src/Global/andy.cpp
index b344016..e543912 100644
--- a/SRC/andy.cpp
+++ b/Src/Global/andy.cpp
@@ -1,3 +1,7 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
#include "game.h"
#include "random.h"
diff --git a/SRC/benchmark.cpp b/Src/Global/benchmark.cpp
similarity index 100%
rename from SRC/benchmark.cpp
rename to Src/Global/benchmark.cpp
diff --git a/Src/Global/defs.h b/Src/Global/defs.h
new file mode 100644
index 0000000..80f8f2a
--- /dev/null
+++ b/Src/Global/defs.h
@@ -0,0 +1,470 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef DEFS_H__
+#define DEFS_H__
+
+enum {
+ kPosTopScreen = 0,
+ kPosRightScreen = 1,
+ kPosBottomScreen = 2,
+ kPosLeftScreen = 3
+};
+
+enum {
+ kNone = 0xFFFFFFFF, // (uint32_t)-1
+ kNoScreen = 0xFF, // (uint8_t)-1
+ kFrameDuration = 80, // original engine frame duration is 80ms (12.5hz)
+ kLvlAnimHdrOffset = 0x2C,
+ kMaxScreens = 40,
+ kMaxSpriteTypes = 32,
+ kMonsterInfoDataSize = 948 // (32 * 28 + 52)
+};
+
+enum {
+ kActionKeyMaskRun = 0x1,
+ kActionKeyMaskJump = 0x2,
+ kActionKeyMaskShoot = 0x4
+};
+
+enum {
+ kDirectionKeyMaskUp = 1, // climb
+ kDirectionKeyMaskRight = 2,
+ kDirectionKeyMaskDown = 4, // crouch
+ kDirectionKeyMaskLeft = 8,
+ kDirectionKeyMaskHorizontal = kDirectionKeyMaskLeft | kDirectionKeyMaskRight, // 0xA
+ kDirectionKeyMaskVertical = kDirectionKeyMaskDown | kDirectionKeyMaskUp // 0x5
+};
+
+enum {
+ kLvl_rock, // 0
+ kLvl_fort,
+ kLvl_pwr1,
+ kLvl_isld,
+ kLvl_lava, // 4
+ kLvl_pwr2,
+ kLvl_lar1,
+ kLvl_lar2,
+ kLvl_dark, // 8
+ kLvl_test
+};
+
+struct SetupConfig {
+ struct {
+ uint8_t progress[10];
+ uint8_t levelNum;
+ uint8_t checkpointNum;
+ uint32_t cutscenesMask;
+ uint8_t controls[32];
+ uint8_t difficulty;
+ uint8_t stereo;
+ uint8_t volume;
+ uint8_t lastLevelNum;
+ } players[4]; // sizeof == 52
+ uint8_t unkD0;
+ uint8_t currentPlayer; // 0xD1
+ uint8_t unkD2;
+ uint8_t checksum;
+}; // sizeof == 212
+
+struct Point8_t {
+ int8_t x;
+ int8_t y;
+} PACKED;
+
+struct Point16_t {
+ int16_t x;
+ int16_t y;
+};
+
+struct AnimBackgroundData {
+ const uint8_t *currentSpriteData; // 0
+ uint8_t *nextSpriteData; // 4
+ uint8_t *otherSpriteData; // 8
+ uint16_t framesCount; // 12
+ uint16_t currentFrame; // 14
+};
+
+struct LvlAnimSeqHeader {
+ uint16_t firstFrame;
+ uint16_t unk2;
+ int8_t dx; // 4
+ int8_t dy; // 5
+ uint8_t count; // 6
+ uint8_t unk7; // 7
+ uint16_t sound;
+ uint16_t flags0;
+ uint16_t flags1;
+ uint16_t unkE;
+ uint32_t offset; // 0x10, LvlAnimSeqFrameHeader
+} PACKED; // sizeof == 20
+
+struct LvlAnimSeqFrameHeader {
+ uint16_t move; // 0
+ uint16_t anim; // 2
+ uint8_t frame; // 4
+ uint8_t unk5; // 5
+ int8_t unk6;
+ int8_t unk7;
+} PACKED; // sizeof == 8
+
+struct LvlAnimHeader {
+ uint16_t unk0;
+ uint8_t seqCount;
+ uint8_t unk2;
+ uint32_t seqOffset;
+} PACKED; // sizeof == 8
+
+struct LvlSprMoveData {
+ uint8_t op1;
+ uint8_t op2;
+ uint16_t op3;
+ uint16_t op4;
+ uint16_t unk0x6; // padding to 8 bytes
+} PACKED; // sizeof == 8
+
+struct LvlSprHotspotData {
+ Point8_t pts[8];
+} PACKED; // sizeof == 16
+
+struct LvlObjectData {
+ uint8_t unk0;
+ uint8_t spriteNum;
+ uint16_t framesCount;
+ uint16_t hotspotsCount;
+ uint16_t movesCount;
+ uint16_t coordsCount;
+ uint8_t refCount; // 0xA
+ uint8_t frame; // 0xB
+ uint16_t anim; // 0xC
+ uint8_t *animsInfoData; // 0x10, LevelSprAnimInfo
+ uint8_t *movesData; // 0x14, LvlSprMoveData
+ uint8_t *framesData; // 0x18
+ uint8_t *framesOffsetsTable; // 0x1C
+ uint8_t *coordsData; // 0x20
+ uint8_t *coordsOffsetsTable; // 0x24
+ uint8_t *hotspotsData; // 0x28, LvlSprHotspotData
+};
+
+struct Game;
+struct SssObject;
+
+struct LvlObject {
+ int32_t xPos; // 0
+ int32_t yPos; // 4
+ uint8_t screenNum; // 8
+ uint8_t screenState;
+ uint8_t dataNum;
+ uint8_t frame;
+ uint16_t anim;
+ uint8_t type;
+ uint8_t spriteNum;
+ uint16_t flags0;
+ uint16_t flags1;
+ uint16_t flags2;
+ uint8_t objectUpdateType;
+ uint8_t hitCount;
+ LvlObject *childPtr; // _andyObject : plasma cannon object or specialAnimation
+ uint16_t width;
+ uint16_t height;
+ uint8_t actionKeyMask;
+ uint8_t directionKeyMask;
+ uint16_t currentSprite;
+ uint16_t currentSound; // 24
+ const uint8_t *bitmapBits; // 28
+ int (Game::*callbackFuncPtr)(LvlObject *ptr); // 2C
+ void *dataPtr; // 30
+ SssObject *sssObject; // 34
+ LvlObjectData *levelData0x2988;
+ Point16_t posTable[8];
+ LvlObject *nextPtr;
+};
+
+struct SssFilter;
+struct SssPcm;
+
+struct SssObject {
+ SssPcm *pcm; // 0x0
+ uint16_t num; // 0x4
+ uint16_t bankIndex; // 0x6
+ int8_t priority; // 0x8
+ int8_t currentPriority; // 0x9
+ uint8_t flags; // 0xA
+ bool stereo; // 0xB
+ uint32_t flags0; // 0xC
+ uint32_t flags1; // 0x10
+ int32_t panning; // 0x14 panning default:64
+ int32_t volume; // 0x18 volume default:128
+ int panL; // 0x1C
+ int panR; // 0x20
+ int panType; // 0x24 : 0: silent, 1:right 2:left 3:center 4:balance
+ const int16_t *currentPcmPtr; // 0x28
+ int32_t pcmFramesCount; // 0x2C
+ SssObject *prevPtr; // 0x30
+ SssObject *nextPtr; // 0x34
+ const uint8_t *codeDataStage1; // 0x38
+ const uint8_t *codeDataStage2; // 0x3C
+ const uint8_t *codeDataStage3; // 0x40
+ const uint8_t *codeDataStage4; // 0x44
+ int32_t repeatCounter; // 0x48
+ int32_t pauseCounter; // 0x4C
+ int32_t delayCounter; // 0x50
+ int32_t volumeModulateSteps; // 0x54
+ int32_t panningModulateSteps; // 0x58
+ int32_t volumeModulateCurrent; // 0x5C
+ int32_t volumeModulateDelta; // 0x60
+ int32_t panningModulateCurrent; // 0x64
+ int32_t panningModulateDelta; // 0x68
+ int32_t currentPcmFrame; // 0x6C
+ int *panningPtr; // 0x70 if != 0, panning is relative to the object position
+ LvlObject *lvlObject; // 0x74
+ int32_t nextSoundBank; // 0x78
+ int32_t nextSoundSample; // 0x7C
+ SssFilter *filter;
+};
+
+struct Sprite {
+ int16_t xPos;
+ int16_t yPos;
+ const uint8_t *bitmapBits;
+ Sprite *nextPtr;
+ uint16_t num;
+ uint16_t w, h;
+};
+
+struct BoundingBox {
+ int32_t x1; // 0
+ int32_t y1; // 4
+ int32_t x2; // 8
+ int32_t y2; // C
+};
+
+struct AndyLvlObjectData {
+ uint8_t unk0;
+ uint8_t unk1;
+ uint8_t unk2;
+ uint8_t unk3;
+ uint8_t unk4;
+ uint8_t unk5;
+ uint16_t unk6;
+ BoundingBox boundingBox; // 0x8
+ int32_t dxPos; // 0x18
+ int32_t dyPos; // 0x1C
+ LvlObject *shootLvlObject; // 0x20 'cannon plasma' or 'special powers'
+};
+
+struct ShootLvlObjectData {
+ uint8_t type; // 0x0
+ uint8_t state; // 0x1
+ uint8_t counter; // 0x2
+ uint8_t unk3; // 0x3 value
+ int32_t dxPos; // 0x4
+ int32_t dyPos; // 0x8
+ int32_t xPosShoot; // 0xC
+ int32_t yPosShoot; // 0x10
+ int32_t xPosObject; // 0x14
+ int32_t yPosObject; // 0x18
+ LvlObject *o; // 0x1C
+ ShootLvlObjectData *nextPtr; // 0x20 pointer to the next free entry
+};
+
+struct ScreenMask { // ShadowScreenMask
+ uint32_t dataSize;
+ uint8_t *projectionDataPtr;
+ uint8_t *shadowPalettePtr;
+ int16_t x;
+ int16_t y;
+ uint16_t w;
+ uint16_t h;
+}; // sizeof == 0x14
+
+struct WormHoleSprite {
+ uint8_t screenNum;
+ uint8_t initData1;
+ int8_t xPos;
+ int8_t yPos;
+ uint32_t initData4;
+ uint8_t rect1_x1;
+ uint8_t rect1_y1;
+ uint8_t rect1_x2;
+ uint8_t rect1_y2;
+ uint8_t rect2_x1;
+ uint8_t rect2_y1;
+ uint8_t rect2_x2;
+ uint8_t rect2_y2;
+ uint32_t flags[7]; // 0x10
+}; // sizeof == 0x2C
+
+struct MonsterObject1;
+
+struct AndyShootData {
+ int32_t xPos;
+ int32_t yPos; // 4
+ BoundingBox boundingBox; // 8
+ int32_t size; // 0x18
+ int32_t width;
+ int32_t height;
+ int32_t directionMask; // 0x24
+ ShootLvlObjectData *shootObjectData; // 0x28
+ LvlObject *o; // 0x2C
+ MonsterObject1 *m; // 0x30
+ int32_t clipX; // 0x34
+ int32_t clipY; // 0x38
+ int32_t monsterDistance; // 0x3C
+ uint8_t type; // 0x40
+ uint8_t plasmaCannonPointsCount; // 0x41
+}; // sizeof == 0x44
+
+struct AndyMoveData {
+ int32_t xPos;
+ int32_t yPos;
+ uint16_t anim; // 8
+ uint16_t unkA;
+ uint16_t unkC;
+ uint16_t unkE;
+ uint8_t frame; // 0x10
+ uint8_t unk11;
+ uint16_t flags0;
+ uint16_t flags1;
+ uint16_t unk16;
+ uint16_t unk18;
+ uint16_t unk1A;
+ const uint8_t *unk1C;
+ const uint8_t *framesData;
+ const uint8_t *unk24;
+ const uint8_t *unk28;
+}; // sizeof == 0x2C
+
+struct MstBoundingBox {
+ int x1; // 0
+ int y1; // 4
+ int x2; // 8
+ int y2; // 0xC
+ int monster1Index; // 0x10
+}; // sizeof == 0x20
+
+struct MstCollision {
+ MonsterObject1 *monster1[32]; // 0x00
+ uint32_t count; // 0x80
+}; // sizeof == 132
+
+struct Task;
+
+struct MstWalkCode;
+struct MstWalkPath;
+struct MstInfoMonster2;
+struct MstBehavior;
+struct MstBehaviorState;
+struct MstWalkNode;
+struct MstMonsterAreaAction;
+struct MstMovingBounds;
+struct MstMovingBoundsUnk1;
+
+struct MonsterObject1 {
+ MstBehavior *m46; // 0x0
+ MstBehaviorState *behaviorState; // 0x4
+ const uint8_t *monsterInfos; // 0x8
+ MstWalkNode *walkNode; // 0xC
+ LvlObject *o16; // 0x10
+ LvlObject *o20; // 0x14
+ MstMonsterAreaAction *action; // 0x18
+ AndyShootData *shootData; // 0x1C
+ int monster1Index; // 0x20
+ int executeCounter; // 0x24
+ int32_t localVars[8]; // 0x28
+ uint8_t flags48; // 0x48 0x4:indexUnk51!=kNone, 0x10:attackBox!=kNone
+ uint8_t facingDirectionMask; // 0x49
+ uint8_t goalDirectionMask; // 0x4A
+ uint8_t goalScreenNum; // 0x4B
+ int xPos; // 0x4C
+ int yPos; // 0x50
+ int xMstPos; // 0x54 currentLevelPos_x
+ int yMstPos; // 0x58 currentLevelPos_y
+ int xDelta; // 0x5C
+ int yDelta; // 0x60
+ int goalDistance_x1; // 0x64
+ int goalDistance_x2; // 0x68
+ int goalDistance_y1; // 0x6C
+ int goalDistance_y2; // 0x70
+ int goalPos_x1; // 0x74
+ int goalPos_y1; // 0x78
+ int goalPos_x2; // 0x7C
+ int goalPos_y2; // 0x80
+ int goalPosBounds_x2; // 0x84
+ int goalPosBounds_x1; // 0x88
+ // int goalPosBounds_y2; // 0x8C unused
+ // int goalPosBounds_y1; // 0x90 unused
+ int levelPosBounds_x2; // 0x94
+ int levelPosBounds_x1; // 0x98
+ int levelPosBounds_y2; // 0x9C
+ int levelPosBounds_y1; // 0xA0
+ uint8_t o_flags0; // 0xA4
+ uint8_t flagsA5; // 0xA5
+ uint8_t flagsA6; // 0xA6 |1:turning |2:idle |4:colliding
+ uint8_t targetDirectionMask; // 0xA7
+ uint8_t bboxNum[2]; // 0xA8, 0xA9
+ uint8_t walkBoxNum; // 0xAA
+ uint8_t unkAB; // 0xAB
+ int32_t targetLevelPos_x; // 0xAC
+ int32_t targetLevelPos_y; // 0xB0
+ int32_t previousDxPos; // 0xB4 _xMstPos2
+ int32_t previousDyPos; // 0xB8 _yMstPos2
+ int32_t unkBC; // 0xBC _xMstPos1
+ int32_t unkC0; // 0xC0 _yMstPos1
+ Task *task; // 0xC4
+ uint8_t rnd_m49[4]; // 0xC8
+ uint8_t rnd_m35[4]; // 0xCC
+ MstWalkCode *walkCode; // 0xD0
+ MstMovingBoundsUnk1 *m49Unk1; // 0xD4
+ MstMovingBounds *m49; // 0xD8
+ int indexUnk49Unk1; // 0xDC
+ uint8_t unkE4;
+ uint8_t unkE5;
+ uint8_t lut4Index; // 0xE6
+ uint8_t directionKeyMask;
+ uint16_t o_flags2; // 0xE8
+ int collideDistance; // 0xEC
+ int shootActionIndex; // 0xF0 [0..8]
+ int shootSource; // 0xF4
+ uint8_t unkF8; // 0xF8
+ int shootDirection; // 0xFC
+}; // sizeof == 256
+
+struct MonsterObject2 {
+ MstInfoMonster2 *monster2Info;
+ LvlObject *o; // 4
+ MonsterObject1 *monster1; // 8
+ int monster2Index; // 10
+ int xPos; // 14
+ int yPos; // 18
+ int xMstPos; // 1C
+ int yMstPos; // 20
+ uint8_t flags24; // 24
+ int x1; // 28
+ int x2; // 2C
+ int y1; // 30
+ int y2; // 34
+ uint8_t hPosIndex; // 38
+ uint8_t vPosIndex; // 39
+ uint8_t hDir; // 3A
+ uint8_t vDir; // 3B
+ Task *task; // 0x3C
+}; // sizeof == 64
+
+struct Task {
+ const uint8_t *codeData;
+ Task *prevPtr, *nextPtr; // 4,8
+ MonsterObject1 *monster1; // 0xC
+ MonsterObject2 *monster2; // 0x10
+ int32_t localVars[8]; // 0x14
+ uint8_t flags; // 0x34
+ uint8_t state; // 0x35
+ int16_t arg1; // 0x36 delay/counter/type
+ uint32_t arg2; // 0x38 num/index
+ int (Game::*run)(Task *t); // 0x3C
+ Task *child; // 0x40
+}; // sizeof == 0x44
+
+#endif // DEFS_H__
diff --git a/Src/Global/fileio.cpp b/Src/Global/fileio.cpp
new file mode 100644
index 0000000..0b2e9d9
--- /dev/null
+++ b/Src/Global/fileio.cpp
@@ -0,0 +1,158 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include
+#include "fileio.h"
+#include "util.h"
+
+static const bool kCheckSectorFileCrc = false;
+
+#ifdef PSP
+static const bool kSeekAbsolutePosition = true;
+#else
+static const bool kSeekAbsolutePosition = false;
+#endif
+
+File::File()
+ : _fp(0) {
+}
+
+File::~File() {
+}
+
+void File::setFp(FILE *fp) {
+ _fp = fp;
+}
+
+void File::seekAlign(uint32_t pos) {
+ fseek(_fp, pos, SEEK_SET);
+}
+
+void File::seek(int pos, int whence) {
+ if (kSeekAbsolutePosition && whence == SEEK_CUR) {
+ pos += ftell(_fp);
+ whence = SEEK_SET;
+ }
+ fseek(_fp, pos, whence);
+}
+
+int File::read(uint8_t *ptr, int size) {
+ return fread(ptr, 1, size, _fp);
+}
+
+uint8_t File::readByte() {
+ uint8_t buf;
+ read(&buf, 1);
+ return buf;
+}
+
+uint16_t File::readUint16() {
+ uint8_t buf[2];
+ read(buf, 2);
+ return READ_LE_UINT16(buf);
+}
+
+uint32_t File::readUint32() {
+ uint8_t buf[4];
+ read(buf, 4);
+ return READ_LE_UINT32(buf);
+}
+
+SectorFile::SectorFile() {
+ memset(_buf, 0, sizeof(_buf));
+ _bufPos = 2044;
+}
+
+int fioAlignSizeTo2048(int size) {
+ return ((size + 2043) / 2044) * 2048;
+}
+
+uint32_t fioUpdateCRC(uint32_t sum, const uint8_t *buf, uint32_t size) {
+ assert((size & 3) == 0);
+ for (uint32_t offset = 0; offset < size; offset += 4) {
+ sum ^= READ_LE_UINT32(buf + offset);
+ }
+ return sum;
+}
+
+void SectorFile::refillBuffer(uint8_t *ptr) {
+ if (ptr) {
+ static const int kPayloadSize = kFioBufferSize - 4;
+ const int size = fread(ptr, 1, kPayloadSize, _fp);
+ assert(size == kPayloadSize);
+ uint8_t buf[4];
+ const int count = fread(buf, 1, 4, _fp);
+ assert(count == 4);
+ if (kCheckSectorFileCrc) {
+ const uint32_t crc = fioUpdateCRC(0, ptr, kPayloadSize);
+ assert(crc == READ_LE_UINT32(buf));
+ }
+ } else {
+ const int size = fread(_buf, 1, kFioBufferSize, _fp);
+ assert(size == kFioBufferSize);
+ if (kCheckSectorFileCrc) {
+ const uint32_t crc = fioUpdateCRC(0, _buf, kFioBufferSize);
+ assert(crc == 0);
+ }
+ _bufPos = 0;
+ }
+}
+
+void SectorFile::seekAlign(uint32_t pos) {
+ pos += (pos / 2048) * 4;
+ const long alignPos = pos & ~2047;
+ if (alignPos != (ftell(_fp) - 2048)) {
+ fseek(_fp, alignPos, SEEK_SET);
+ refillBuffer();
+ }
+ _bufPos = pos - alignPos;
+}
+
+void SectorFile::seek(int pos, int whence) {
+ if (whence == SEEK_SET) {
+ assert((pos & 2047) == 0);
+ _bufPos = 2044;
+ File::seek(pos, SEEK_SET);
+ } else {
+ assert(whence == SEEK_CUR && pos >= 0);
+ const int bufLen = 2044 - _bufPos;
+ if (pos < bufLen) {
+ _bufPos += pos;
+ } else {
+ pos -= bufLen;
+ const int count = (fioAlignSizeTo2048(pos) / 2048) - 1;
+ if (count > 0) {
+ const int alignPos = count * 2048;
+ fseek(_fp, alignPos, SEEK_CUR);
+ }
+ refillBuffer();
+ _bufPos = pos % 2044;
+ }
+ }
+}
+
+int SectorFile::read(uint8_t *ptr, int size) {
+ const int bufLen = 2044 - _bufPos;
+ if (size >= bufLen) {
+ if (bufLen) {
+ memcpy(ptr, _buf + _bufPos, bufLen);
+ ptr += bufLen;
+ size -= bufLen;
+ }
+ const int count = (fioAlignSizeTo2048(size) / 2048) - 1;
+ for (int i = 0; i < count; ++i) {
+ refillBuffer(ptr);
+ ptr += 2044;
+ size -= 2044;
+ }
+ refillBuffer();
+ }
+ if (size != 0) {
+ assert(size <= 2044 - _bufPos);
+ memcpy(ptr, _buf + _bufPos, size);
+ _bufPos += size;
+ }
+ return 0;
+}
diff --git a/Src/Global/fileio.h b/Src/Global/fileio.h
new file mode 100644
index 0000000..44d7fc9
--- /dev/null
+++ b/Src/Global/fileio.h
@@ -0,0 +1,53 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef FILEIO_H__
+#define FILEIO_H__
+
+#include "intern.h"
+
+struct File {
+
+ FILE *_fp;
+
+ File();
+ virtual ~File();
+
+ void setFp(FILE *fp);
+
+ virtual void seekAlign(uint32_t pos);
+ virtual void seek(int pos, int whence);
+ virtual int read(uint8_t *ptr, int size);
+ uint8_t readByte();
+ uint16_t readUint16();
+ uint32_t readUint32();
+
+ void skipByte() { seek(1, SEEK_CUR); }
+ void skipUint16() { seek(2, SEEK_CUR); }
+ void skipUint32() { seek(4, SEEK_CUR); }
+};
+
+struct SectorFile : File {
+
+ enum {
+ kFioBufferSize = 2048
+ };
+
+ uint8_t _buf[kFioBufferSize];
+ int _bufPos;
+
+ SectorFile();
+
+ void refillBuffer(uint8_t *ptr = 0);
+ virtual void seekAlign(uint32_t pos);
+ virtual void seek(int pos, int whence);
+ virtual int read(uint8_t *ptr, int size);
+};
+
+int fioAlignSizeTo2048(int size);
+uint32_t fioUpdateCRC(uint32_t sum, const uint8_t *buf, uint32_t size);
+
+#endif // FILEIO_H__
+
diff --git a/SRC/fs.h b/Src/Global/fs.h
similarity index 94%
rename from SRC/fs.h
rename to Src/Global/fs.h
index 2b39d44..0bb0217 100644
--- a/SRC/fs.h
+++ b/Src/Global/fs.h
@@ -16,7 +16,7 @@ struct FileSystem {
FILE *openAssetFile(const char *filename);
FILE *openSaveFile(const char *filename, bool write);
- void closeFile(FILE *);
+ int closeFile(FILE *);
void addFilePath(const char *path);
void listFiles(const char *dir);
diff --git a/SRC/fs_android.cpp b/Src/Global/fs_android.cpp
similarity index 97%
rename from SRC/fs_android.cpp
rename to Src/Global/fs_android.cpp
index c1e9a5d..1679c1a 100644
--- a/SRC/fs_android.cpp
+++ b/Src/Global/fs_android.cpp
@@ -114,8 +114,8 @@ FILE *FileSystem::openSaveFile(const char *filename, bool write) {
return fp;
}
-void FileSystem::closeFile(FILE *fp) {
- if (fp) {
- fclose(fp);
- }
+int FileSystem::closeFile(FILE *fp) {
+ const int err = ferror(fp);
+ fclose(fp);
+ return err;
}
diff --git a/SRC/fs_posix.cpp b/Src/Global/fs_posix.cpp
similarity index 95%
rename from SRC/fs_posix.cpp
rename to Src/Global/fs_posix.cpp
index 365b40d..e255e91 100644
--- a/SRC/fs_posix.cpp
+++ b/Src/Global/fs_posix.cpp
@@ -8,6 +8,7 @@
#include "util.h"
static const char *_suffixes[] = {
+ "hod.dem",
"setup.dat",
"setup.dax",
".paf",
@@ -59,8 +60,10 @@ FILE *FileSystem::openSaveFile(const char *filename, bool write) {
return fopen(path, write ? "wb" : "rb");
}
-void FileSystem::closeFile(FILE *fp) {
+int FileSystem::closeFile(FILE *fp) {
+ const int err = ferror(fp);
fclose(fp);
+ return err;
}
void FileSystem::addFilePath(const char *path) {
diff --git a/Src/Global/game.cpp b/Src/Global/game.cpp
new file mode 100644
index 0000000..408602f
--- /dev/null
+++ b/Src/Global/game.cpp
@@ -0,0 +1,4872 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "game.h"
+#include "fileio.h"
+#include "level.h"
+#include "lzw.h"
+#include "paf.h"
+#include "screenshot.h"
+#include "system.h"
+#include "util.h"
+#include "video.h"
+
+// starting level cutscene number
+static const uint8_t _cutscenes[] = { 0, 2, 4, 5, 6, 8, 10, 14, 19 };
+
+Game::Game(const char *dataPath, const char *savePath, uint32_t cheats)
+ : _fs(dataPath, savePath) {
+
+ _level = 0;
+ _res = new Resource(&_fs);
+ _paf = new PafPlayer(&_fs);
+ _rnd.setSeed();
+ _video = new Video();
+ _cheats = cheats;
+ _playDemo = false;
+
+ _frameMs = kFrameDuration;
+ _difficulty = 1; // normal
+
+ memset(_screenLvlObjectsList, 0, sizeof(_screenLvlObjectsList));
+ _andyObject = 0;
+ _plasmaExplosionObject = 0;
+ _plasmaCannonObject = 0;
+ memset(_spritesTable, 0, sizeof(_spritesTable));
+ memset(_typeSpritesList, 0, sizeof(_typeSpritesList));
+
+ _directionKeyMask = 0;
+ _actionKeyMask = 0;
+
+ _currentScreen = 0;
+
+ _lvlObjectsList0 = 0;
+ _lvlObjectsList1 = 0;
+ _lvlObjectsList2 = 0;
+ _lvlObjectsList3 = 0;
+ memset(_screenMaskBuffer, 0, sizeof(_screenMaskBuffer));
+ memset(_shootLvlObjectDataTable, 0, sizeof(_shootLvlObjectDataTable));
+ _mstAndyCurrentScreenNum = -1;
+ _plasmaCannonDirection = 0;
+ _andyActionKeyMaskAnd = 0xFF;
+ _andyActionKeyMaskOr = 0;
+ _andyDirectionKeyMaskAnd = 0xFF;
+ _andyDirectionKeyMaskOr = 0;
+
+ _mstDisabled = false;
+ _specialAnimMask = 0; // original only clears ~0x30
+ _mstCurrentAnim = 0;
+ _mstOriginPosX = Video::W / 2;
+ _mstOriginPosY = Video::H / 2;
+ memset(_declaredLvlObjectsList, 0, sizeof(_declaredLvlObjectsList));
+ _declaredLvlObjectsNextPtr = 0;
+ _declaredLvlObjectsListCount = 0;
+
+ memset(_animBackgroundDataTable, 0, sizeof(_animBackgroundDataTable));
+ _animBackgroundDataCount = 0;
+
+ memset(_monsterObjects1Table, 0, sizeof(_monsterObjects1Table));
+ memset(_monsterObjects2Table, 0, sizeof(_monsterObjects2Table));
+
+ _sssDisabled = false;
+ _snd_muted = false;
+ _snd_bufferOffset = _snd_bufferSize = 0;
+ _snd_masterPanning = kDefaultSoundPanning;
+ _snd_masterVolume = kDefaultSoundVolume;
+ _sssObjectsCount = 0;
+ _sssObjectsList1 = 0;
+ _sssObjectsList2 = 0;
+ _playingSssObjectsMax = 16; // 10 if (lowMemory || slowCPU)
+}
+
+Game::~Game() {
+ delete _paf;
+ delete _res;
+ delete _video;
+}
+
+void Game::resetShootLvlObjectDataTable() {
+ _shootLvlObjectDataNextPtr = &_shootLvlObjectDataTable[0];
+ for (int i = 0; i < kMaxShootLvlObjectData - 1; ++i) {
+ _shootLvlObjectDataTable[i].nextPtr = &_shootLvlObjectDataTable[i + 1];
+ }
+ _shootLvlObjectDataTable[kMaxShootLvlObjectData - 1].nextPtr = 0;
+}
+
+void Game::clearShootLvlObjectData(LvlObject *ptr) {
+ ShootLvlObjectData *dat = (ShootLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeShoot);
+ dat->nextPtr = _shootLvlObjectDataNextPtr;
+ _shootLvlObjectDataNextPtr = dat;
+ ptr->dataPtr = 0;
+}
+
+void Game::setShakeScreen(int type, int counter) {
+ static const uint8_t table1[] = { 1, 2, 3, 4 };
+ static const uint8_t table2[] = { 2, 3, 4, 5 };
+ static const uint8_t table3[] = { 3, 4, 5, 8 };
+ switch (type) {
+ case 1:
+ _shakeScreenTable = table1;
+ break;
+ case 2:
+ _shakeScreenTable = table2;
+ break;
+ case 3:
+ _shakeScreenTable = table3;
+ break;
+ default:
+ return;
+ }
+ _shakeScreenDuration = counter;
+}
+
+void Game::fadeScreenPalette() {
+ if (!_fadePalette) {
+ assert(_levelRestartCounter != 0);
+ for (int i = 0; i < 256 * 3; ++i) {
+ _video->_fadePaletteBuffer[i] = _video->_displayPaletteBuffer[i] / _levelRestartCounter;
+ }
+ _fadePalette = true;
+ } else {
+ if (_levelRestartCounter != 0) {
+ _snd_masterVolume -= _snd_masterVolume / _levelRestartCounter;
+ if (_snd_masterVolume < 0) {
+ _snd_masterVolume = 0;
+ }
+ }
+ --_levelRestartCounter;
+ }
+ for (int i = 0; i < 256 * 3; ++i) {
+ int color = _video->_displayPaletteBuffer[i] - _video->_fadePaletteBuffer[i];
+ if (color < 0) {
+ color = 0;
+ }
+ _video->_displayPaletteBuffer[i] = color;
+ }
+ _video->_paletteChanged = true;
+}
+
+void Game::shakeScreen() {
+ if (_video->_displayShadowLayer) {
+ const int num = (_currentLevel == kLvl_lava || _currentLevel == kLvl_lar1) ? 1 : 4;
+ transformShadowLayer(num);
+ }
+ if (_shakeScreenDuration != 0) {
+ --_shakeScreenDuration;
+ if (_shakeScreenDuration != 0) {
+ const int r1 = _rnd.update() & 3;
+ const int r2 = _rnd.update() & 3;
+ int dx = _shakeScreenTable[r2] & ~3;
+ if (r1 & 1) {
+ dx = -dx;
+ }
+ int dy = _shakeScreenTable[r1];
+ if (_shakeScreenDuration & 1) {
+ dy = -dy;
+ }
+ g_system->shakeScreen(dx, dy);
+ }
+ }
+ if (_levelRestartCounter != 0) {
+ fadeScreenPalette();
+ }
+}
+
+static BoundingBox _screenTransformRects[] = {
+ { 0, 0, 0, 0 },
+ { 0, 0, 255, 128 },
+ { 0, 10, 154, 126 },
+ { 0, 0, 107, 36 },
+ { 0, 1, 78, 29 },
+ { 14, 7, 249, 72 },
+ { 14, 0, 255, 72 },
+ { 0, 0, 255, 144 },
+ { 0, 0, 255, 144 },
+ { 0, 69, 255, 191 }
+};
+
+void Game::transformShadowLayer(int delta) {
+ const uint8_t *src = _video->_transformShadowBuffer + _video->_transformShadowLayerDelta;
+ uint8_t *dst = _video->_shadowLayer;
+ _video->_transformShadowLayerDelta += delta; // overflow/wrap at 255
+ for (int y = 0; y < Video::H; ++y) {
+ if (0) { // original clips the screen width to 250px
+ for (int x = 0; x < Video::W - 6; ++x) {
+ const int offset = x + *src++;
+ *dst++ = _video->_frontLayer[y * Video::W + offset];
+ }
+ memset(dst, Video::CLEAR_COLOR, 6);
+ dst += 6;
+ src += 6;
+ } else {
+ for (int x = 0; x < Video::W; ++x) {
+ const int offset = MIN(255, x + *src++);
+ *dst++ = _video->_frontLayer[y * Video::W + offset];
+ }
+ }
+ }
+ uint8_t r = 0;
+ if (_currentLevel == kLvl_pwr1) {
+ r = _pwr1_screenTransformLut[_res->_currentScreenResourceNum * 2 + 1];
+ } else if (_currentLevel == kLvl_pwr2) {
+ r = _pwr2_screenTransformLut[_res->_currentScreenResourceNum * 2 + 1];
+ }
+ if (r != 0) {
+ assert(r < ARRAYSIZE(_screenTransformRects));
+ const BoundingBox *b = &_screenTransformRects[r];
+ const int offset = b->y1 * Video::W + b->x1;
+ src = _video->_frontLayer + offset;
+ dst = _video->_shadowLayer + offset;
+ for (int y = b->y1; y < b->y2; ++y) {
+ for (int x = b->x1; x < b->x2; ++x) {
+ dst[x] = src[x];
+ }
+ dst += Video::W;
+ src += Video::W;
+ }
+ }
+}
+
+void Game::loadTransformLayerData(const uint8_t *data) {
+ assert(!_video->_transformShadowBuffer);
+ _video->_transformShadowBuffer = (uint8_t *)malloc(256 * 192 + 256);
+ const int size = decodeLZW(data, _video->_transformShadowBuffer);
+ assert(size == 256 * 192);
+ memcpy(_video->_transformShadowBuffer + 256 * 192, _video->_transformShadowBuffer, 256);
+}
+
+void Game::unloadTransformLayerData() {
+ free(_video->_transformShadowBuffer);
+ _video->_transformShadowBuffer = 0;
+}
+
+void Game::decodeShadowScreenMask(LvlBackgroundData *lvl) {
+ uint8_t *dst = _video->_shadowScreenMaskBuffer;
+ for (int i = lvl->currentShadowId; i < lvl->shadowCount; ++i) {
+ const uint8_t *src = lvl->backgroundMaskTable[i];
+ if (src) {
+ const int decodedSize = decodeLZW(src + 2, dst);
+
+ _shadowScreenMasksTable[i].dataSize = READ_LE_UINT32(dst);
+
+ // header : 20 bytes
+ // projectionData : w * h * sizeof(uint16_t) - for a given (x, y) returns the casted (x, y)
+ // paletteData : 256 (only the first 144 bytes are read)
+
+ _shadowScreenMasksTable[i].projectionDataPtr = dst + 0x14 + READ_LE_UINT32(dst + 4);
+ _shadowScreenMasksTable[i].shadowPalettePtr = dst + 0x14 + READ_LE_UINT32(dst + 8);
+ const int x = _shadowScreenMasksTable[i].x = READ_LE_UINT16(dst + 0xC);
+ const int y = _shadowScreenMasksTable[i].y = READ_LE_UINT16(dst + 0xE);
+ const int w = _shadowScreenMasksTable[i].w = READ_LE_UINT16(dst + 0x10);
+ const int h = _shadowScreenMasksTable[i].h = READ_LE_UINT16(dst + 0x12);
+
+ debug(kDebug_GAME, "shadow screen mask #%d pos %d,%d dim %d,%d size %d", i, x, y, w, h, decodedSize);
+
+ const int size = w * h;
+ uint8_t *p = _shadowScreenMasksTable[i].projectionDataPtr + 2;
+ for (int j = 1; j < size; ++j) {
+ const int16_t offset = (int16_t)READ_LE_UINT16(p - 2) + (int16_t)READ_LE_UINT16(p);
+ // fprintf(stdout, "shadow #%d offset #%d 0x%x 0x%x\n", i, j, READ_LE_UINT16(p), offset);
+ WRITE_LE_UINT16(p, offset);
+ p += 2;
+ }
+
+ const int shadowPaletteSize = decodedSize - 20 - w * h * sizeof(uint16_t);
+ assert(shadowPaletteSize >= 144);
+
+ _video->buildShadowColorLookupTable(_shadowScreenMasksTable[i].shadowPalettePtr, _video->_shadowColorLookupTable);
+ dst += decodedSize;
+ }
+ }
+}
+
+// a: type/source (0, 1, 2) b: num/index (3, monster1Index, monster2.monster1Index)
+SssObject *Game::playSound(int num, LvlObject *ptr, int a, int b) {
+ MixerLock ml(&_mix);
+ SssObject *so = 0;
+ if (num < _res->_sssHdr.infosDataCount) {
+ debug(kDebug_GAME, "playSound num %d/%d a=%d b=%d", num, _res->_sssHdr.infosDataCount, a, b);
+ _currentSoundLvlObject = ptr;
+ so = playSoundObject(&_res->_sssInfosData[num], a, b);
+ _currentSoundLvlObject = 0;
+ }
+ return so;
+}
+
+void Game::removeSound(LvlObject *ptr) {
+ MixerLock ml(&_mix);
+ for (int i = 0; i < _sssObjectsCount; ++i) {
+ if (_sssObjectsTable[i].lvlObject == ptr) {
+ _sssObjectsTable[i].lvlObject = 0;
+ }
+ }
+ ptr->sssObject = 0;
+}
+
+void Game::setupBackgroundBitmap() {
+ LvlBackgroundData *lvl = &_res->_resLvlScreenBackgroundDataTable[_res->_currentScreenResourceNum];
+ const int num = lvl->currentBackgroundId;
+ const uint8_t *pal = lvl->backgroundPaletteTable[num];
+ lvl->backgroundPaletteId = READ_LE_UINT16(pal); pal += 2;
+ const uint8_t *bmp = lvl->backgroundBitmapTable[num];
+ lvl->backgroundBitmapId = READ_LE_UINT16(bmp); bmp += 2;
+ if (lvl->backgroundPaletteId != 0xFFFF) {
+ playSound(lvl->backgroundPaletteId, 0, 0, 3);
+ }
+ if (lvl->backgroundBitmapId != 0xFFFF) {
+ playSound(lvl->backgroundBitmapId, 0, 0, 3);
+ }
+ if (_res->_isPsx) {
+ _video->decodeBackgroundPsx(bmp + 2, -1, Video::W, Video::H);
+ } else {
+ decodeLZW(bmp, _video->_backgroundLayer);
+ }
+ if (lvl->shadowCount != 0) {
+ decodeShadowScreenMask(lvl);
+ }
+ for (int i = 0; i < 256 * 3; ++i) {
+ _video->_displayPaletteBuffer[i] = pal[i] << 8;
+ }
+ _video->_paletteChanged = true;
+}
+
+void Game::addToSpriteList(Sprite *spr) {
+ _spritesNextPtr = spr->nextPtr;
+ const int index = spr->num & 0x1F;
+ spr->nextPtr = _typeSpritesList[index];
+ _typeSpritesList[index] = spr;
+}
+
+void Game::addToSpriteList(LvlObject *ptr) {
+ Sprite *spr = _spritesNextPtr;
+ if (spr) {
+ const uint8_t num = _res->_currentScreenResourceNum;
+ const uint8_t *grid = _res->_screensGrid[num];
+
+ LvlObjectData *dat = ptr->levelData0x2988;
+ LvlAnimHeader *ah = (LvlAnimHeader *)(dat->animsInfoData + kLvlAnimHdrOffset) + ptr->anim;
+ LvlAnimSeqHeader *ash = (LvlAnimSeqHeader *)(dat->animsInfoData + ah->seqOffset) + ptr->frame;
+
+ spr->num = (((ash->flags1 ^ ptr->flags1) & 0xFFF0) << 10) | (ptr->flags2 & 0x3FFF);
+
+ int index = ptr->screenNum;
+ spr->xPos = ptr->xPos;
+ spr->yPos = ptr->yPos;
+ if (index == grid[kPosTopScreen]) {
+ spr->yPos -= Video::H;
+ } else if (index == grid[kPosBottomScreen]) {
+ spr->yPos += Video::H;
+ } else if (index == grid[kPosRightScreen]) {
+ spr->xPos += Video::W;
+ } else if (index == grid[kPosLeftScreen]) {
+ spr->xPos -= Video::W;
+ } else if (index != num) {
+ return;
+ }
+ if (spr->xPos >= Video::W || spr->xPos + ptr->width < 0) {
+ return;
+ }
+ if (spr->yPos >= Video::H || spr->yPos + ptr->height < 0) {
+ return;
+ }
+ if (_currentLevel == kLvl_isld && ptr->spriteNum == 2) {
+ AndyLvlObjectData *dataPtr = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ spr->xPos += dataPtr->dxPos;
+ }
+ if (ptr->bitmapBits) {
+ spr->w = ptr->width;
+ spr->h = ptr->height;
+ spr->bitmapBits = ptr->bitmapBits;
+ addToSpriteList(spr);
+ }
+ }
+}
+
+int16_t Game::calcScreenMaskDy(int16_t xPos, int16_t yPos, int num) {
+ if (xPos < 0) {
+ xPos += Video::W;
+ num = _res->_screensGrid[num][kPosLeftScreen];
+ } else if (xPos >= Video::W) {
+ xPos -= Video::W;
+ num = _res->_screensGrid[num][kPosRightScreen];
+ }
+ if (num != kNoScreen) {
+ if (yPos < 0) {
+ yPos += Video::H;
+ num = _res->_screensGrid[num][kPosTopScreen];
+ } else if (yPos >= Video::H) {
+ yPos -= Video::H;
+ num = _res->_screensGrid[num][kPosBottomScreen];
+ }
+ }
+ uint8_t var1 = 0xFF - (yPos & 7);
+ if (num == kNoScreen) {
+ return var1;
+ }
+ int vg = screenMaskOffset(_res->_screensBasePos[num].u + xPos, _res->_screensBasePos[num].v + yPos);
+ int vf = screenGridOffset(xPos, yPos);
+ if (_screenMaskBuffer[vg - 512] & 1) {
+ vf -= 32;
+ var1 -= 8;
+ } else if (_screenMaskBuffer[vg] & 1) {
+ /* nothing to do */
+ } else if (_screenMaskBuffer[vg + 512] & 1) {
+ vf += 32;
+ var1 += 8;
+ } else if (_screenMaskBuffer[vg + 1024] & 1) {
+ vf += 64;
+ var1 += 16;
+ } else {
+ return 0;
+ }
+ int _dl = _res->findScreenGridIndex(num);
+ if (_dl < 0) {
+ return (int8_t)(var1 + 4);
+ }
+ const uint8_t *p = _res->_resLevelData0x470CTablePtrData + (xPos & 7);
+ return (int8_t)(var1 + p[_screenPosTable[_dl][vf] * 8]);
+}
+
+void Game::setupScreenPosTable(uint8_t num) {
+ const uint8_t *src = _res->_screensGrid[num];
+ for (int i = 0; i < 4; ++i) {
+ if (src[i] != kNoScreen) {
+ int index = _res->_resLvlScreenBackgroundDataTable[src[i]].currentMaskId;
+ const uint8_t *p = _res->getLvlScreenPosDataPtr(src[i] * 4 + index);
+ if (p) {
+ Video::decodeRLE(p, _screenPosTable[i], 32 * 24);
+ continue;
+ }
+ }
+ memset(_screenPosTable[i], 0, 32 * 24);
+ }
+ int index = _res->_resLvlScreenBackgroundDataTable[num].currentMaskId;
+ const uint8_t *p = _res->getLvlScreenPosDataPtr(num * 4 + index);
+ if (p) {
+ Video::decodeRLE(p, _screenPosTable[4], 32 * 24);
+ } else {
+ memset(_screenPosTable[4], 0, 32 * 24);
+ }
+}
+
+void Game::setupScreenMask(uint8_t num) {
+ if (num == kNoScreen) {
+ return;
+ }
+ int mask = _res->_resLvlScreenBackgroundDataTable[num].currentMaskId;
+ if (_res->_screensState[num].s3 != mask) {
+ debug(kDebug_GAME, "setupScreenMask num %d mask %d", num, mask);
+ _res->_screensState[num].s3 = mask;
+ const uint8_t *maskData = _res->getLvlScreenMaskDataPtr(num * 4 + mask);
+ if (maskData) {
+ Video::decodeRLE(maskData, _screenTempMaskBuffer, 32 * 24);
+ } else {
+ memset(_screenTempMaskBuffer, 0, 32 * 24);
+ }
+ uint8_t *p = _screenMaskBuffer + screenMaskOffset(_res->_screensBasePos[num].u, _res->_screensBasePos[num].v);
+ for (int i = 0; i < 24; ++i) {
+ memcpy(p, _screenTempMaskBuffer + i * 32, 32);
+ p += 512;
+ }
+ }
+ if (_res->_currentScreenResourceNum == num) {
+ setupScreenPosTable(num);
+ }
+}
+
+void Game::resetScreenMask() {
+ memset(_screenMaskBuffer, 0, sizeof(_screenMaskBuffer));
+ for (int i = 0; i < _res->_lvlHdr.screensCount; ++i) {
+ _res->_screensState[i].s3 = 0xFF;
+ setupScreenMask(i);
+ }
+}
+
+void Game::setScreenMaskRectHelper(int x1, int y1, int x2, int y2, int screenNum) {
+ if (screenNum != kNoScreen) {
+
+ int index = _res->_resLvlScreenBackgroundDataTable[screenNum].currentMaskId;
+ const uint8_t *p = _res->getLvlScreenMaskDataPtr(screenNum * 4 + index);
+ Video::decodeRLE(p, _screenTempMaskBuffer, 32 * 24);
+
+ int h = (y2 - y1 + 7) >> 3;
+ int w = (x2 - x1 + 7) >> 3;
+
+ const int x = x1 - _res->_screensBasePos[screenNum].u;
+ const int y = y1 - _res->_screensBasePos[screenNum].v;
+
+ const int u = _res->_screensBasePos[screenNum].u + Video::W;
+ if (x2 < u) {
+ ++w;
+ }
+
+ const uint8_t *src = _screenTempMaskBuffer + screenGridOffset(x, y);
+ uint8_t *dst = _screenMaskBuffer + screenMaskOffset(x1, y1);
+ for (int i = 0; i < h; ++i) {
+ memcpy(dst, src, w);
+ src += 32;
+ dst += 512;
+ }
+ }
+}
+
+void Game::setScreenMaskRect(int x1, int y1, int x2, int y2, int screenNum) {
+ const int u = _res->_screensBasePos[screenNum].u;
+ const int v = _res->_screensBasePos[screenNum].v;
+ const int topScreenNum = _res->_screensGrid[screenNum][kPosTopScreen];
+ if (x1 < u || y1 < v || y2 >= v + Video::H) {
+ if (topScreenNum != kNoScreen) {
+ const int u2 = _res->_screensBasePos[topScreenNum].u;
+ const int v2 = _res->_screensBasePos[topScreenNum].v;
+ if (x1 >= u2 && y1 >= v2 && y2 < v + Video::H) {
+ setScreenMaskRectHelper(x1, y1, x2, v2 + Video::H, topScreenNum);
+ }
+ }
+ }
+ setScreenMaskRectHelper(x1, v, x2, y2, screenNum);
+}
+
+void Game::updateScreenMaskBuffer(int x, int y, int type) {
+ uint8_t *p = _screenMaskBuffer + screenMaskOffset(x, y);
+ switch (type) {
+ case 0:
+ p[0] &= ~8;
+ break;
+ case 1:
+ p[0] |= 8;
+ break;
+ case 2:
+ p[0] |= 8;
+ p[-1] = 0;
+ p[-2] = 0;
+ p[-3] = 0;
+ break;
+ case 3:
+ p[0] |= 8;
+ p[1] = 0;
+ p[2] = 0;
+ p[3] = 0;
+ break;
+ }
+}
+
+void Game::setupLvlObjectBitmap(LvlObject *ptr) {
+ LvlObjectData *dat = ptr->levelData0x2988;
+ if (!dat) {
+ return;
+ }
+ LvlAnimHeader *ah = (LvlAnimHeader *)(dat->animsInfoData + kLvlAnimHdrOffset) + ptr->anim;
+ LvlAnimSeqHeader *ash = (LvlAnimSeqHeader *)(dat->animsInfoData + ah->seqOffset) + ptr->frame;
+
+ ptr->currentSound = ash->sound;
+ ptr->flags0 = merge_bits(ptr->flags0, ash->flags0, 0x3FF);
+ ptr->flags1 = merge_bits(ptr->flags1, ash->flags1, 6);
+ ptr->flags1 = merge_bits(ptr->flags1, ash->flags1, 8);
+ ptr->currentSprite = ash->firstFrame;
+
+ ptr->bitmapBits = _res->getLvlSpriteFramePtr(dat, ash->firstFrame, &ptr->width, &ptr->height);
+
+ const int w = ptr->width - 1;
+ const int h = ptr->height - 1;
+
+ if (ptr->type == 8 && (ptr->spriteNum == 2 || ptr->spriteNum == 0)) {
+ AndyLvlObjectData *dataPtr = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ dataPtr->boundingBox.x1 = ptr->xPos;
+ dataPtr->boundingBox.y1 = ptr->yPos;
+ dataPtr->boundingBox.x2 = ptr->xPos + w;
+ dataPtr->boundingBox.y2 = ptr->yPos + h;
+ }
+
+ const LvlSprHotspotData *hotspot = ((LvlSprHotspotData *)dat->hotspotsData) + ash->firstFrame;
+ const int type = (ptr->flags1 >> 4) & 3;
+ for (int i = 0; i < 8; ++i) {
+ switch (type) {
+ case 0:
+ ptr->posTable[i].x = hotspot->pts[i].x;
+ ptr->posTable[i].y = hotspot->pts[i].y;
+ break;
+ case 1:
+ ptr->posTable[i].x = w - hotspot->pts[i].x;
+ ptr->posTable[i].y = hotspot->pts[i].y;
+ break;
+ case 2:
+ ptr->posTable[i].x = hotspot->pts[i].x;
+ ptr->posTable[i].y = h - hotspot->pts[i].y;
+ break;
+ case 3:
+ ptr->posTable[i].x = w - hotspot->pts[i].x;
+ ptr->posTable[i].y = h - hotspot->pts[i].y;
+ break;
+ }
+ }
+}
+
+void Game::randomizeInterpolatePoints(int32_t *pts, int count) {
+ int32_t rnd = _rnd.update();
+ for (int i = 0; i < count; ++i) {
+ const int index = _pointDstIndexTable[i];
+ const int c1 = pts[_pointSrcIndex1Table[index]];
+ const int c2 = pts[_pointSrcIndex2Table[index]];
+ pts[index] = (c1 + c2 + (rnd >> _pointRandomizeShiftTable[i])) / 2;
+ rnd *= 2;
+ }
+}
+
+int Game::fixPlasmaCannonPointsScreenMask(int num) {
+ uint8_t _dl = ((~_plasmaCannonDirection) & 1) | 6;
+ int var1 = _plasmaCannonFirstIndex;
+ int vf = _res->_screensBasePos[num].u;
+ int ve = _res->_screensBasePos[num].v;
+ uint8_t _al = 0;
+ if (_andyObject->anim == 84) {
+ int yPos, xPos;
+ yPos = _plasmaCannonYPointsTable1[var1];
+ if (yPos < 0) {
+ yPos = 0;
+ }
+ yPos += ve;
+ while ((xPos = _plasmaCannonXPointsTable1[var1]) >= 0) {
+ xPos += vf;
+ _al = _screenMaskBuffer[screenMaskOffset(xPos, yPos)];
+ if ((_al & _dl) != 0) {
+ _plasmaCannonLastIndex1 = var1;
+ break;
+ }
+ var1 += 4;
+ if (var1 >= _plasmaCannonLastIndex2) {
+ break;
+ }
+ }
+ } else {
+ int xPos, yPos;
+ while ((xPos = _plasmaCannonXPointsTable1[var1]) >= 0 && (yPos = _plasmaCannonYPointsTable1[var1]) >= 0) {
+ yPos += ve;
+ xPos += vf;
+ _al = _screenMaskBuffer[screenMaskOffset(xPos, yPos)];
+ if ((_al & _dl) != 0) {
+ _plasmaCannonLastIndex1 = var1;
+ break;
+ }
+ var1 += 4;
+ if (var1 >= _plasmaCannonLastIndex2) {
+ break;
+ }
+ }
+ }
+ return _al;
+}
+
+void Game::setupPlasmaCannonPointsHelper() {
+ if (_plasmaCannonPointsSetupCounter == 0) {
+ int xR = _rnd.update();
+ for (int i = 0; i < 64; ++i) {
+ const int index = _pointDstIndexTable[i];
+ const int x1 = _plasmaCannonPosX[_pointSrcIndex1Table[index]];
+ const int x2 = _plasmaCannonPosX[_pointSrcIndex2Table[index]];
+ _plasmaCannonPosX[index] = (x1 + x2 + (xR >> _pointRandomizeShiftTable[i])) / 2;
+ xR *= 2;
+ }
+ int yR = _rnd.update();
+ for (int i = 0; i < 64; ++i) {
+ const int index = _pointDstIndexTable[i];
+ const int y1 = _plasmaCannonPosY[_pointSrcIndex1Table[index]];
+ const int y2 = _plasmaCannonPosY[_pointSrcIndex2Table[index]];
+ _plasmaCannonPosY[index] = (y1 + y2 + (yR >> _pointRandomizeShiftTable[i])) / 2;
+ yR *= 2;
+ }
+ if (_andyObject->anim == 84) {
+ for (int i = 0; i <= 496; i += 8) {
+ const int index = i / 4;
+ _plasmaCannonXPointsTable2[2 + index] = (_plasmaCannonPosX[4 + index] - _plasmaCannonXPointsTable1[4 + index]) / 2;
+ _plasmaCannonYPointsTable2[2 + index] = (_plasmaCannonPosY[4 + index] - _plasmaCannonYPointsTable1[4 + index]) / 8;
+ }
+ } else {
+ for (int i = 0; i <= 496; i += 8) {
+ const int index = i / 4;
+ _plasmaCannonXPointsTable2[2 + index] = (_plasmaCannonPosX[4 + index] - _plasmaCannonXPointsTable1[4 + index]) / 2;
+ _plasmaCannonYPointsTable2[2 + index] = (_plasmaCannonPosY[4 + index] - _plasmaCannonYPointsTable1[4 + index]) / 2;
+ }
+ }
+ for (int i = 0; i <= 504; i += 8) {
+ const int index = i / 4;
+ _plasmaCannonXPointsTable1[2 + index] = _plasmaCannonPosX[2 + index];
+ _plasmaCannonYPointsTable1[2 + index] = _plasmaCannonPosY[2 + index];
+ }
+ } else {
+ for (int i = 0; i <= 496; i += 8) {
+ const int index = i / 4;
+ _plasmaCannonXPointsTable1[4 + index] += _plasmaCannonXPointsTable2[2 + index];
+ _plasmaCannonYPointsTable1[4 + index] += _plasmaCannonYPointsTable2[2 + index];
+ }
+ }
+ ++_plasmaCannonPointsSetupCounter;
+ if (_plasmaCannonPointsSetupCounter >= 2) {
+ _plasmaCannonPointsSetupCounter = 0;
+ }
+ _plasmaCannonLastIndex2 = 128;
+ if (_plasmaCannonDirection == 0) {
+ _plasmaCannonLastIndex1 = _plasmaCannonPointsMask = 0;
+ _plasmaCannonFirstIndex = 128;
+ } else {
+ _plasmaCannonFirstIndex = 0;
+ _plasmaCannonPointsMask = fixPlasmaCannonPointsScreenMask(_res->_currentScreenResourceNum);
+ }
+}
+
+void Game::destroyLvlObjectPlasmaExplosion(LvlObject *o) {
+ AndyLvlObjectData *l = (AndyLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeAndy);
+ if (l->shootLvlObject) {
+ l->shootLvlObject = 0;
+
+ assert(_plasmaExplosionObject);
+
+ LvlObject *ptr = _plasmaExplosionObject->nextPtr;
+ removeLvlObjectFromList(&_plasmaExplosionObject, ptr);
+ destroyLvlObject(ptr);
+
+ ptr = _plasmaExplosionObject;
+ removeLvlObjectFromList(&_plasmaExplosionObject, ptr);
+ destroyLvlObject(ptr);
+ }
+}
+
+void Game::shuffleArray(uint8_t *p, int count) {
+ for (int i = 0; i < count * 2; ++i) {
+ const int index1 = _rnd.update() % count;
+ const int index2 = _rnd.update() % count;
+ SWAP(p[index1], p[index2]);
+ }
+}
+
+void Game::destroyLvlObject(LvlObject *o) {
+ assert(o);
+ if (o->type == 8) {
+ _res->decLvlSpriteDataRefCounter(o);
+ o->nextPtr = _declaredLvlObjectsNextPtr;
+ --_declaredLvlObjectsListCount;
+ _declaredLvlObjectsNextPtr = o;
+ switch (o->spriteNum) {
+ case 0:
+ case 2:
+ o->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (o->dataPtr) {
+ clearShootLvlObjectData(o);
+ }
+ break;
+ }
+ }
+ if (o->sssObject) {
+ removeSound(o);
+ }
+ o->sssObject = 0;
+ o->bitmapBits = 0;
+}
+
+void Game::setupPlasmaCannonPoints(LvlObject *ptr) {
+ _plasmaCannonDirection = 0;
+ if ((ptr->flags0 & 0x1F) == 4) {
+ if ((ptr->actionKeyMask & 4) == 0) { // not using plasma cannon
+ destroyLvlObjectPlasmaExplosion(ptr);
+ } else {
+ _plasmaCannonPosX[0] = _plasmaCannonPosX[128] = ptr->xPos + ptr->posTable[6].x;
+ _plasmaCannonPosY[0] = _plasmaCannonPosY[128] = ptr->yPos + ptr->posTable[6].y;
+ const int num = ((ptr->flags0 >> 5) & 7) - 3;
+ switch (num) {
+ case 0:
+ _plasmaCannonPosY[128] -= 176; // 192 - 16
+ _plasmaCannonDirection = 3;
+ break;
+ case 1:
+ _plasmaCannonPosY[128] += 176;
+ _plasmaCannonDirection = 6;
+ break;
+ case 3:
+ _plasmaCannonPosY[128] -= 176;
+ _plasmaCannonDirection = 1;
+ break;
+ case 4:
+ _plasmaCannonPosY[128] += 176;
+ _plasmaCannonDirection = 4;
+ break;
+ default:
+ _plasmaCannonDirection = 2;
+ break;
+ }
+ if (ptr->flags1 & 0x10) {
+ if (_plasmaCannonDirection != 1) {
+ _plasmaCannonDirection = (_plasmaCannonDirection & ~2) | 8;
+ _plasmaCannonPosX[128] -= 264; // 256 + 8
+ }
+ } else {
+ if (_plasmaCannonDirection != 1) {
+ _plasmaCannonPosX[128] += 264;
+ }
+ }
+ if (_plasmaCannonPrevDirection != _plasmaCannonDirection) {
+ _plasmaCannonXPointsTable1[0] = _plasmaCannonPosX[0];
+ _plasmaCannonXPointsTable1[128] = _plasmaCannonPosX[128];
+ randomizeInterpolatePoints(_plasmaCannonXPointsTable1, 64);
+ _plasmaCannonYPointsTable1[0] = _plasmaCannonPosY[0];
+ _plasmaCannonYPointsTable1[128] = _plasmaCannonPosY[128];
+ randomizeInterpolatePoints(_plasmaCannonYPointsTable1, 64);
+ _plasmaCannonPrevDirection = _plasmaCannonDirection;
+ }
+ }
+ }
+ if (_plasmaCannonPrevDirection != 0) {
+ setupPlasmaCannonPointsHelper();
+ if (_plasmaCannonFirstIndex >= _plasmaCannonLastIndex2) {
+ _plasmaCannonPrevDirection = 0;
+ _plasmaCannonLastIndex2 = 16;
+ }
+ }
+}
+
+int Game::testPlasmaCannonPointsDirection(int x1, int y1, int x2, int y2) {
+ int index1 = _plasmaCannonFirstIndex;
+ int vg = _plasmaCannonPosX[index1];
+ int ve = _plasmaCannonPosY[index1];
+ int index2 = _plasmaCannonLastIndex1;
+ if (index2 == 0) {
+ index2 = _plasmaCannonLastIndex2;
+ }
+ int va = _plasmaCannonXPointsTable1[index2];
+ int vf = _plasmaCannonYPointsTable1[index2];
+ if (vg > va) {
+ if (ve > vf) {
+ if (x1 > vg || x2 < va || y1 > ve || y2 < vf) {
+ return 0;
+ }
+ index1 += 4;
+ do {
+ va = _plasmaCannonXPointsTable1[index1];
+ vf = _plasmaCannonYPointsTable1[index1];
+ if (x1 <= vg && x2 >= va && y1 <= ve && y2 >= vf) {
+ goto endDir;
+ }
+ vg = va;
+ ve = vf;
+ index1 += 4;
+ } while (index1 < index2);
+ return 0;
+ } else {
+ if (x1 > vg || x2 < va || y1 > vf || y2 < ve) {
+ return 0;
+ }
+ index1 += 4;
+ do {
+ va = _plasmaCannonXPointsTable1[index1];
+ vf = _plasmaCannonYPointsTable1[index1];
+ if (x1 <= vg && x2 >= va && y1 <= vf && y2 >= ve) {
+ goto endDir;
+ }
+ vg = va;
+ ve = vf;
+ index1 += 4;
+ } while (index1 < index2);
+ return 0;
+ }
+ } else {
+ if (ve > vf) {
+ if (x1 > va || x2 < vg || y1 > ve || y2 < vf) {
+ return 0;
+ }
+ index1 += 4;
+ do {
+ va = _plasmaCannonXPointsTable1[index1];
+ vf = _plasmaCannonYPointsTable1[index1];
+ if (x1 <= va && x2 >= vg && y1 <= ve && y2 >= vf) {
+ goto endDir;
+ }
+ vg = va;
+ ve = vf;
+ index1 += 4;
+ } while (index1 < index2);
+ return 0;
+ } else {
+ if (x1 > va || x2 < vg || y1 > vf || y2 < ve) {
+ return 0;
+ }
+ index1 += 4;
+ do {
+ va = _plasmaCannonXPointsTable1[index1];
+ vf = _plasmaCannonYPointsTable1[index1];
+ if (x1 <= va && x2 >= vg && y1 <= vf && y2 >= ve) {
+ goto endDir;
+ }
+ vg = va;
+ ve = vf;
+ index1 += 4;
+ } while (index1 < index2);
+ return 0;
+ }
+ }
+endDir:
+ _plasmaCannonLastIndex1 = index1;
+ _plasmaCannonPointsMask = 0;
+ return 1;
+}
+
+void Game::preloadLevelScreenData(uint8_t num, uint8_t prev) {
+ assert(num != kNoScreen);
+ if (!_res->isLvlBackgroundDataLoaded(num)) {
+ _res->loadLvlScreenBackgroundData(num);
+ }
+ if (num < _res->_sssPreloadInfosData.count) {
+ const SssPreloadInfo *preloadInfo = &_res->_sssPreloadInfosData[num];
+ for (unsigned int i = 0; i < preloadInfo->count; ++i) {
+ const SssPreloadInfoData *preloadData = &preloadInfo->data[i];
+ if (preloadData->screenNum == prev) {
+ _res->preloadSssPcmList(preloadData);
+ break;
+ }
+ }
+ }
+}
+
+void Game::setLvlObjectPosRelativeToObject(LvlObject *ptr1, int num1, LvlObject *ptr2, int num2) {
+ ptr1->xPos = ptr2->posTable[num2].x - ptr1->posTable[num1].x + ptr2->xPos;
+ ptr1->yPos = ptr2->posTable[num2].y - ptr1->posTable[num1].y + ptr2->yPos;
+}
+
+void Game::setLvlObjectPosRelativeToPoint(LvlObject *ptr, int num, int x, int y) {
+ assert(num >= 0 && num < 8);
+ ptr->xPos = x - ptr->posTable[num].x;
+ ptr->yPos = y - ptr->posTable[num].y;
+}
+
+void Game::clearLvlObjectsList0() {
+ LvlObject *ptr = _lvlObjectsList0;
+ while (ptr) {
+ LvlObject *next = ptr->nextPtr;
+ if (ptr->type == 8) {
+ _res->decLvlSpriteDataRefCounter(ptr);
+ ptr->nextPtr = _declaredLvlObjectsNextPtr;
+ --_declaredLvlObjectsListCount;
+ _declaredLvlObjectsNextPtr = ptr;
+ switch (ptr->spriteNum) {
+ case 0:
+ case 2:
+ ptr->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (ptr->dataPtr) {
+ clearShootLvlObjectData(ptr);
+ }
+ break;
+ }
+ if (ptr->sssObject) {
+ removeSound(ptr);
+ }
+ ptr->sssObject = 0;
+ ptr->bitmapBits = 0;
+ }
+ ptr = next;
+ }
+ _lvlObjectsList0 = 0;
+}
+
+void Game::clearLvlObjectsList1() {
+ if (!_lvlObjectsList1) {
+ return;
+ }
+ for (int i = 0; i < kMaxMonsterObjects1; ++i) {
+ mstMonster1ResetData(&_monsterObjects1Table[i]);
+ }
+ for (int i = 0; i < kMaxMonsterObjects2; ++i) {
+ mstMonster2ResetData(&_monsterObjects2Table[i]);
+ }
+ LvlObject *ptr = _lvlObjectsList1;
+ while (ptr) {
+ LvlObject *next = ptr->nextPtr;
+ if (ptr->type == 8) {
+ _res->decLvlSpriteDataRefCounter(ptr);
+ ptr->nextPtr = _declaredLvlObjectsNextPtr;
+ --_declaredLvlObjectsListCount;
+ _declaredLvlObjectsNextPtr = ptr;
+ switch (ptr->spriteNum) {
+ case 0:
+ case 2:
+ ptr->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (ptr->dataPtr) {
+ clearShootLvlObjectData(ptr);
+ }
+ break;
+ }
+ if (ptr->sssObject) {
+ removeSound(ptr);
+ }
+ ptr->sssObject = 0;
+ ptr->bitmapBits = 0;
+ }
+ ptr = next;
+ }
+ _lvlObjectsList1 = 0;
+}
+
+void Game::clearLvlObjectsList2() {
+ LvlObject *ptr = _lvlObjectsList2;
+ while (ptr) {
+ LvlObject *next = ptr->nextPtr;
+ if (ptr->type == 8) {
+ _res->decLvlSpriteDataRefCounter(ptr);
+ ptr->nextPtr = _declaredLvlObjectsNextPtr;
+ --_declaredLvlObjectsListCount;
+ _declaredLvlObjectsNextPtr = ptr;
+ switch (ptr->spriteNum) {
+ case 0:
+ case 2:
+ ptr->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (ptr->dataPtr) {
+ clearShootLvlObjectData(ptr);
+ }
+ break;
+ }
+ if (ptr->sssObject) {
+ removeSound(ptr);
+ }
+ ptr->sssObject = 0;
+ ptr->bitmapBits = 0;
+ }
+ ptr = next;
+ }
+ _lvlObjectsList2 = 0;
+}
+
+void Game::clearLvlObjectsList3() {
+ LvlObject *ptr = _lvlObjectsList3;
+ while (ptr != 0) {
+ LvlObject *next = ptr->nextPtr;
+ if (ptr->type == 8) {
+ _res->decLvlSpriteDataRefCounter(ptr);
+ ptr->nextPtr = _declaredLvlObjectsNextPtr;
+ --_declaredLvlObjectsListCount;
+ _declaredLvlObjectsNextPtr = ptr;
+ switch (ptr->spriteNum) {
+ case 0:
+ case 2:
+ ptr->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (ptr->dataPtr) {
+ clearShootLvlObjectData(ptr);
+ }
+ break;
+ }
+ if (ptr->sssObject) {
+ removeSound(ptr);
+ }
+ ptr->sssObject = 0;
+ ptr->bitmapBits = 0;
+ }
+ ptr = next;
+ }
+ _lvlObjectsList3 = 0;
+}
+
+LvlObject *Game::addLvlObjectToList0(int num) {
+ if (_res->_resLevelData0x2988PtrTable[num] != 0 && _declaredLvlObjectsListCount < kMaxLvlObjects) {
+ assert(_declaredLvlObjectsNextPtr);
+ LvlObject *ptr = _declaredLvlObjectsNextPtr;
+ _declaredLvlObjectsNextPtr = _declaredLvlObjectsNextPtr->nextPtr;
+ ++_declaredLvlObjectsListCount;
+ ptr->spriteNum = num;
+ ptr->type = 8;
+ _res->incLvlSpriteDataRefCounter(ptr);
+ lvlObjectTypeCallback(ptr);
+ ptr->currentSprite = 0;
+ ptr->sssObject = 0;
+ ptr->nextPtr = 0;
+ ptr->bitmapBits = 0;
+ ptr->nextPtr = _lvlObjectsList0;
+ _lvlObjectsList0 = ptr;
+ return ptr;
+ }
+ return 0;
+}
+
+LvlObject *Game::addLvlObjectToList1(int type, int num) {
+ if ((type != 8 || _res->_resLevelData0x2988PtrTable[num] != 0) && _declaredLvlObjectsListCount < kMaxLvlObjects) {
+ assert(_declaredLvlObjectsNextPtr);
+ LvlObject *ptr = _declaredLvlObjectsNextPtr;
+ _declaredLvlObjectsNextPtr = _declaredLvlObjectsNextPtr->nextPtr;
+ ++_declaredLvlObjectsListCount;
+ ptr->spriteNum = num;
+ ptr->type = type;
+ if (type == 8) {
+ _res->incLvlSpriteDataRefCounter(ptr);
+ lvlObjectTypeCallback(ptr);
+ }
+ ptr->currentSprite = 0;
+ ptr->sssObject = 0;
+ ptr->nextPtr = 0;
+ ptr->bitmapBits = 0;
+ ptr->nextPtr = _lvlObjectsList1;
+ _lvlObjectsList1 = ptr;
+ return ptr;
+ }
+ return 0;
+}
+
+LvlObject *Game::addLvlObjectToList2(int num) {
+ if (_res->_resLevelData0x2988PtrTable[num] != 0 && _declaredLvlObjectsListCount < kMaxLvlObjects) {
+ assert(_declaredLvlObjectsNextPtr);
+ LvlObject *ptr = _declaredLvlObjectsNextPtr;
+ _declaredLvlObjectsNextPtr = _declaredLvlObjectsNextPtr->nextPtr;
+ ++_declaredLvlObjectsListCount;
+ ptr->spriteNum = num;
+ ptr->type = 8;
+ _res->incLvlSpriteDataRefCounter(ptr);
+ lvlObjectTypeCallback(ptr);
+ ptr->currentSprite = 0;
+ ptr->sssObject = 0;
+ ptr->nextPtr = 0;
+ ptr->bitmapBits = 0;
+ ptr->nextPtr = _lvlObjectsList2;
+ _lvlObjectsList2 = ptr;
+ return ptr;
+ }
+ return 0;
+}
+
+LvlObject *Game::addLvlObjectToList3(int num) {
+ if (_res->_resLevelData0x2988PtrTable[num] != 0 && _declaredLvlObjectsListCount < kMaxLvlObjects) {
+ assert(_declaredLvlObjectsNextPtr);
+ LvlObject *ptr = _declaredLvlObjectsNextPtr;
+ _declaredLvlObjectsNextPtr = _declaredLvlObjectsNextPtr->nextPtr;
+ ++_declaredLvlObjectsListCount;
+ ptr->spriteNum = num;
+ ptr->type = 8;
+ _res->incLvlSpriteDataRefCounter(ptr);
+ lvlObjectTypeCallback(ptr);
+ ptr->currentSprite = 0;
+ ptr->sssObject = 0;
+ ptr->nextPtr = 0;
+ ptr->bitmapBits = 0;
+ ptr->nextPtr = _lvlObjectsList3;
+ _lvlObjectsList3 = ptr;
+ ptr->callbackFuncPtr = &Game::lvlObjectList3Callback;
+ return ptr;
+ }
+ return 0;
+}
+
+void Game::removeLvlObject(LvlObject *ptr) {
+ AndyLvlObjectData *dataPtr = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ LvlObject *o = dataPtr->shootLvlObject;
+ if (o) {
+ dataPtr->shootLvlObject = 0;
+ removeLvlObjectFromList(&_lvlObjectsList0, o);
+ destroyLvlObject(o);
+ }
+}
+
+void Game::removeLvlObject2(LvlObject *o) {
+ if (o->type != 2) {
+ removeLvlObjectFromList(&_lvlObjectsList1, o);
+ }
+ o->dataPtr = 0;
+ if (o->type == 8) {
+ _res->decLvlSpriteDataRefCounter(o);
+ o->nextPtr = _declaredLvlObjectsNextPtr;
+ _declaredLvlObjectsNextPtr = o;
+ --_declaredLvlObjectsListCount;
+ } else {
+ switch (o->spriteNum) {
+ case 0:
+ case 2:
+ o->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (o->dataPtr) {
+ clearShootLvlObjectData(o);
+ }
+ break;
+ }
+ }
+ if (o->sssObject) {
+ removeSound(o);
+ o->sssObject = 0;
+ }
+ o->bitmapBits = 0;
+}
+
+void Game::setAndySprite(int num) {
+ switch (num) {
+ case 0: // Andy with plasma cannon and helmet
+ removeLvlObject(_andyObject);
+ setLvlObjectSprite(_andyObject, 8, 0);
+ _andyObject->anim = 48;
+ break;
+ case 2: // Andy
+ destroyLvlObjectPlasmaExplosion(_andyObject);
+ _plasmaCannonDirection = 0;
+ _plasmaCannonLastIndex1 = 0;
+ _plasmaCannonExplodeFlag = false;
+ _plasmaCannonPointsMask = 0;
+ _plasmaCannonObject = 0;
+ setLvlObjectSprite(_andyObject, 8, 2);
+ _andyObject->anim = 232;
+ break;
+ }
+ _andyObject->frame = 0;
+}
+
+void Game::setupAndyLvlObject() {
+ LvlObject *ptr = _andyObject;
+ _fallingAndyFlag = false;
+ _andyActionKeysFlags = 0;
+ _hideAndyObjectFlag = false;
+ const CheckpointData *dat = _level->getCheckpointData(_level->_checkpoint);
+ _plasmaCannonFlags = 0;
+ _actionDirectionKeyMaskIndex = 0;
+ _mstAndyCurrentScreenNum = ptr->screenNum;
+ if (dat->spriteNum != ptr->spriteNum) {
+ setAndySprite(dat->spriteNum);
+ }
+ ptr->childPtr = 0;
+ ptr->xPos = dat->xPos;
+ ptr->yPos = dat->yPos;
+ ptr->flags2 = dat->flags2;
+ ptr->anim = dat->anim;
+ ptr->flags1 = ((ptr->flags2 >> 10) & 0x30) | (ptr->flags1 & ~0x30);
+ ptr->screenNum = dat->screenNum;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ _currentScreen = dat->screenNum;
+ _currentLeftScreen = _res->_screensGrid[_currentScreen][kPosLeftScreen];
+ _currentRightScreen = _res->_screensGrid[_currentScreen][kPosRightScreen];
+ ptr->frame = 0;
+ setupLvlObjectBitmap(ptr);
+ AndyLvlObjectData *dataPtr = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ dataPtr->unk6 = 0;
+ if (ptr->spriteNum == 2) {
+ removeLvlObject(ptr);
+ } else {
+ destroyLvlObjectPlasmaExplosion(ptr);
+ }
+}
+
+void Game::setupScreenLvlObjects(int num) {
+ _res->_screensState[num].s2 = 1;
+ for (LvlObject *ptr = _screenLvlObjectsList[num]; ptr; ptr = ptr->nextPtr) {
+ switch (ptr->type) {
+ case 0: {
+ AnimBackgroundData *p = (AnimBackgroundData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAnimBackgroundData);
+ uint8_t *data = _res->_resLvlScreenBackgroundDataTable[num].backgroundAnimationTable[ptr->dataNum];
+ if (!data) {
+ warning("No backgroundAnimationData num %d screen %d", ptr->dataNum, num);
+ break;
+ }
+ if (_res->_isPsx) {
+ p->framesCount = READ_LE_UINT32(data); data += 4;
+ ptr->currentSound = READ_LE_UINT32(data); data += 4;
+ p->nextSpriteData = READ_LE_UINT16(data + 6) + data + 6;
+ } else {
+ p->framesCount = READ_LE_UINT16(data); data += 2;
+ ptr->currentSound = READ_LE_UINT16(data); data += 2;
+ p->nextSpriteData = READ_LE_UINT16(data + 4) + data + 4;
+ }
+ p->currentSpriteData = p->otherSpriteData = data;
+ p->currentFrame = 0;
+ }
+ break;
+ case 1: {
+ uint8_t *data = _res->_resLvlScreenBackgroundDataTable[num].backgroundSoundTable[ptr->dataNum];
+ if (!data) {
+ warning("No backgroundSoundData num %d screen %d", ptr->dataNum, num);
+ break;
+ }
+ ptr->currentSound = READ_LE_UINT16(data); data += 2;
+ ptr->dataPtr = data;
+ }
+ break;
+ case 2:
+ ptr->levelData0x2988 = _res->_resLvlScreenBackgroundDataTable[num].backgroundLvlObjectDataTable[ptr->dataNum];
+ if (!ptr->levelData0x2988) {
+ warning("No backgroundLvlObjectData num %d screen %d", ptr->dataNum, num);
+ break;
+ }
+ if (_currentLevel == kLvl_rock) {
+ switch (ptr->objectUpdateType) {
+ case 0:
+ case 5:
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case0;
+ break;
+ case 1: // shadow screen2
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case1;
+ break;
+ case 2: // shadow screen3
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case2;
+ break;
+ case 3:
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case3;
+ break;
+ case 4:
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case4;
+ break;
+ default:
+ warning("setupScreenLvlObjects unimplemented for level %d, state %d", _currentLevel, ptr->objectUpdateType);
+ break;
+ }
+ } else {
+ // other levels use two callbacks
+ switch (ptr->objectUpdateType) {
+ case 0:
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case0;
+ break;
+ case 1:
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case3;
+ break;
+ default:
+ warning("setupScreenLvlObjects unimplemented for level %d, state %d", _currentLevel, ptr->objectUpdateType);
+ break;
+ }
+ }
+ setupLvlObjectBitmap(ptr);
+ break;
+ }
+ }
+}
+
+void Game::resetDisplay() {
+// _video_blitSrcPtr = _video_blitSrcPtr2;
+ _video->_displayShadowLayer = false;
+ _shakeScreenDuration = 0;
+ _levelRestartCounter = 0;
+ _fadePalette = false;
+ memset(_video->_fadePaletteBuffer, 0, sizeof(_video->_fadePaletteBuffer));
+ _snd_masterVolume = _setupConfig.players[_setupConfig.currentPlayer].volume;
+}
+
+void Game::setupScreen(uint8_t num) {
+ uint8_t i, prev;
+
+ if (num == kNoScreen) {
+ return;
+ }
+ prev = _res->_currentScreenResourceNum;
+ _res->_currentScreenResourceNum = num;
+ setupScreenLvlObjects(num);
+ callLevel_preScreenUpdate(num);
+ if (_res->_screensState[num].s0 >= _res->_screensState[num].s1) {
+ _res->_screensState[num].s0 = _res->_screensState[num].s1 - 1;
+ }
+ callLevel_postScreenUpdate(num);
+ i = _res->_screensGrid[num][kPosTopScreen];
+ if (i != kNoScreen && prev != i) {
+ callLevel_preScreenUpdate(i);
+ setupScreenMask(i);
+ callLevel_postScreenUpdate(i);
+ }
+ i = _res->_screensGrid[num][kPosRightScreen];
+ if (i != kNoScreen && _res->_resLevelData0x2B88SizeTable[i] != 0 && prev != i) {
+ setupScreenLvlObjects(i);
+ callLevel_preScreenUpdate(i);
+ setupScreenMask(i);
+ callLevel_postScreenUpdate(i);
+ }
+ i = _res->_screensGrid[num][kPosBottomScreen];
+ if (i != kNoScreen && prev != i) {
+ callLevel_preScreenUpdate(i);
+ setupScreenMask(i);
+ callLevel_postScreenUpdate(i);
+ }
+ i = _res->_screensGrid[num][kPosLeftScreen];
+ if (i != kNoScreen && _res->_resLevelData0x2B88SizeTable[i] != 0 && prev != i) {
+ setupScreenLvlObjects(i);
+ callLevel_preScreenUpdate(i);
+ setupScreenMask(i);
+ callLevel_postScreenUpdate(i);
+ }
+ callLevel_postScreenUpdate(num);
+ setupBackgroundBitmap();
+ setupScreenMask(num);
+ resetDisplay();
+}
+
+void Game::resetScreen() {
+ for (int i = 0; i < _res->_lvlHdr.screensCount; ++i) {
+ _res->_screensState[i].s0 = 0;
+ _level->_screenCounterTable[i] = 0;
+ }
+ const uint8_t *dat2 = _level->getScreenRestartData();
+ const int screenNum = _level->getCheckpointData(_level->_checkpoint)->screenNum;
+ for (int i = 0; i < screenNum; ++i) {
+ _res->_screensState[i].s0 = *dat2++;
+ _level->_screenCounterTable[i] = *dat2++;
+ }
+ resetScreenMask();
+ for (int i = screenNum; i < _res->_lvlHdr.screensCount; ++i) {
+ _level->setupScreenCheckpoint(i);
+ }
+ resetWormHoleSprites();
+}
+
+void Game::restartLevel() {
+ setupAndyLvlObject();
+ clearLvlObjectsList2();
+ clearLvlObjectsList3();
+ if (!_mstDisabled) {
+ resetMstCode();
+ startMstCode();
+ } else {
+ _mstFlags = 0;
+ }
+ if (_res->_sssHdr.infosDataCount != 0) {
+ resetSound();
+ }
+ const int screenNum = _level->getCheckpointData(_level->_checkpoint)->screenNum;
+ preloadLevelScreenData(screenNum, kNoScreen);
+ _andyObject->levelData0x2988 = _res->_resLevelData0x2988PtrTable[_andyObject->spriteNum];
+ memset(_video->_backgroundLayer, 0, Video::W * Video::H);
+ resetScreen();
+ if (_andyObject->screenNum != screenNum) {
+ preloadLevelScreenData(_andyObject->screenNum, kNoScreen);
+ }
+ setupScreen(_andyObject->screenNum);
+}
+
+void Game::playAndyFallingCutscene(int type) {
+ bool play = false;
+ if (type == 0) {
+ play = true;
+ } else if (_fallingAndyFlag) {
+ ++_fallingAndyCounter;
+ if (_fallingAndyCounter >= 2) {
+ play = true;
+ }
+ }
+ if (!_paf->_skipCutscenes && play) {
+ switch (_currentLevel) {
+ case kLvl_rock:
+ if (_andyObject->spriteNum == 0) {
+ _paf->play(kPafAnimation_CanyonAndyFallingCannon); // Andy falls with cannon and helmet
+ } else {
+ _paf->play(kPafAnimation_CanyonAndyFalling); // Andy falls without cannon
+ }
+ break;
+ case kLvl_fort:
+ if (_res->_currentScreenResourceNum == 0) {
+ _paf->play(kPafAnimation_CanyonAndyFalling);
+ }
+ break;
+ case kLvl_isld:
+ _paf->play(kPafAnimation_IslandAndyFalling);
+ break;
+ }
+ }
+ if (type != 0 && play) {
+ restartLevel();
+ }
+}
+
+int8_t Game::updateLvlObjectScreen(LvlObject *ptr) {
+ int8_t ret = 0;
+
+ if ((_plasmaCannonFlags & 1) == 0 && _plasmaCannonDirection == 0) {
+ int xPosPrev = ptr->xPos;
+ int xPos = ptr->xPos + ptr->posTable[3].x;
+ int yPosPrev = ptr->yPos;
+ int yPos = ptr->yPos + ptr->posTable[3].y;
+ uint8_t num = ptr->screenNum;
+ if (xPos < 0) {
+ ptr->screenNum = _res->_screensGrid[num][kPosLeftScreen];
+ ptr->xPos = xPosPrev + Video::W;
+ } else if (xPos > Video::W) {
+ ptr->screenNum = _res->_screensGrid[num][kPosRightScreen];
+ ptr->xPos = xPosPrev - Video::W;
+ }
+ if (ptr->screenNum != kNoScreen) {
+ if (yPos < 0) {
+ ptr->screenNum = _res->_screensGrid[ptr->screenNum][kPosTopScreen];
+ ptr->yPos = yPosPrev + Video::H;
+ } else if (yPos > Video::H) {
+ ptr->screenNum = _res->_screensGrid[ptr->screenNum][kPosBottomScreen];
+ ptr->yPos = yPosPrev - Video::H;
+ }
+ }
+ if (ptr->screenNum == kNoScreen) {
+ debug(kDebug_GAME, "Changing screen from -1 to %d, pos=%d,%d (%d,%d)", num, xPos, yPos, xPosPrev, yPosPrev);
+ ptr->screenNum = num;
+ ptr->xPos = xPosPrev;
+ ptr->yPos = yPosPrev;
+ ret = -1;
+ } else if (ptr->screenNum != num) {
+ debug(kDebug_GAME, "Changing screen from %d to %d, pos=%d,%d", num, ptr->screenNum, xPos, yPos);
+ ret = 1;
+ AndyLvlObjectData *data = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ data->boundingBox.x1 = ptr->xPos;
+ data->boundingBox.x2 = ptr->xPos + ptr->width - 1;
+ data->boundingBox.y1 = ptr->yPos;
+ data->boundingBox.y2 = ptr->yPos + ptr->height - 1;
+ }
+ }
+ _currentScreen = ptr->screenNum;
+ _currentLeftScreen = _res->_screensGrid[_currentScreen][kPosLeftScreen];
+ _currentRightScreen = _res->_screensGrid[_currentScreen][kPosRightScreen];
+ return ret;
+}
+
+void Game::setAndyAnimationForArea(BoundingBox *box, int dx) {
+ static uint8_t _prevAndyFlags0 = 0;
+ BoundingBox objBox;
+ objBox.x1 = _andyObject->xPos;
+ objBox.x2 = _andyObject->xPos + _andyObject->posTable[3].x;
+ objBox.y1 = _andyObject->yPos;
+ objBox.y2 = _andyObject->yPos + _andyObject->posTable[3].y;
+ const uint8_t _bl = _andyObject->flags0 & 0x1F;
+ if (clipBoundingBox(box, &objBox)) {
+ if ((_andyObject->actionKeyMask & 1) == 0) {
+ _andyObject->actionKeyMask |= 8;
+ }
+ if (objBox.x2 >= box->x1 + dx && objBox.x2 <= box->x2 - dx) {
+ uint8_t _al = 0;
+ if (_currentLevel != kLvl_rock) {
+ if (_bl == 1) {
+ if (((_andyObject->flags0 >> 5) & 7) != 3) {
+ _al = 0x80;
+ }
+ }
+ } else {
+ _andyObject->actionKeyMask &= ~1;
+ _andyObject->directionKeyMask &= ~4;
+ _andyObject->actionKeyMask |= 8;
+ }
+ if (_bl != 2) {
+ if (_prevAndyFlags0 == 2) {
+ _al = 0x80;
+ }
+ if (_al != 0 && _al > _actionDirectionKeyMaskIndex) {
+ _actionDirectionKeyMaskIndex = _al;
+ _actionDirectionKeyMaskCounter = 0;
+ }
+ }
+ }
+ }
+ _prevAndyFlags0 = _bl;
+}
+
+void Game::setAndyLvlObjectPlasmaCannonKeyMask() {
+ if (_actionDirectionKeyMaskCounter == 0) {
+ switch (_actionDirectionKeyMaskIndex >> 4) {
+ case 0:
+ _actionDirectionKeyMaskCounter = 6;
+ break;
+ case 1:
+ case 8:
+ case 10:
+ _actionDirectionKeyMaskCounter = 2;
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ _actionDirectionKeyMaskCounter = 1;
+ break;
+ case 6:
+ case 7:
+ case 9:
+ break;
+ }
+ }
+ if (_actionDirectionKeyMaskIndex != 0) {
+ if (_actionDirectionKeyMaskIndex == 0xA4 && !_fadePalette) { // game over
+ _levelRestartCounter = 10;
+ _plasmaCannonFlags |= 1;
+ } else {
+ if (_andyObject->spriteNum == 2 && _actionDirectionKeyMaskIndex >= 16) {
+ removeLvlObject(_andyObject);
+ }
+ _andyActionKeysFlags = 0;
+ }
+ _andyObject->actionKeyMask = _actionDirectionKeyMaskTable[_actionDirectionKeyMaskIndex * 2];
+ _andyObject->directionKeyMask = _actionDirectionKeyMaskTable[_actionDirectionKeyMaskIndex * 2 + 1];
+ }
+ --_actionDirectionKeyMaskCounter;
+ if (_actionDirectionKeyMaskCounter == 0) {
+ _actionDirectionKeyMaskIndex = 0;
+ }
+}
+
+int Game::setAndySpecialAnimation(uint8_t mask) {
+ if (mask > _actionDirectionKeyMaskIndex) {
+ _actionDirectionKeyMaskIndex = mask;
+ _actionDirectionKeyMaskCounter = 0;
+ return 1;
+ }
+ return 0;
+}
+
+int Game::clipBoundingBox(BoundingBox *coords, BoundingBox *box) {
+ if (coords->x1 > coords->x2) {
+ SWAP(coords->x1, coords->x2);
+ }
+ if (coords->y1 > coords->y2) {
+ SWAP(coords->y1, coords->y2);
+ }
+ if (box->x1 > box->x2) {
+ SWAP(box->x1, box->x2);
+ }
+ if (box->y1 > box->y2) {
+ SWAP(box->y1, box->y2);
+ }
+ if (coords->x1 > box->x2 || coords->x2 < box->x1 || coords->y1 > box->y2 || coords->y2 < box->y1) {
+ return 0;
+ }
+ _clipBoxOffsetX = (box->x2 - coords->x1) / 2 + coords->x1;
+ _clipBoxOffsetY = (box->y2 - coords->y1) / 2 + coords->y1;
+ return 1;
+}
+
+int Game::updateBoundingBoxClippingOffset(BoundingBox *vc, BoundingBox *ve, const uint8_t *coords, int direction) {
+ int ret = 0;
+ int count = *coords++;
+ if (count == 0) {
+ return clipBoundingBox(vc, ve);
+ }
+ switch (direction) {
+ case 1:
+ for (; count-- != 0; coords += 4) {
+ if (vc->x1 > ve->x2 - coords[0] || vc->x2 < ve->x2 - coords[2]) {
+ continue;
+ }
+ if (vc->y1 > ve->y1 + coords[3] || vc->y2 < ve->y1 + coords[1]) {
+ continue;
+ }
+ break;
+ }
+ break;
+ case 2:
+ for (; count-- != 0; coords += 4) {
+ if (vc->x1 > coords[2] + ve->x1 || vc->x2 < coords[0] + ve->x1) {
+ continue;
+ }
+ if (vc->y1 > ve->y2 - coords[1] || vc->y2 < ve->y2 - coords[3]) {
+ continue;
+ }
+ break;
+ }
+ break;
+ case 3:
+ for (; count-- != 0; coords += 4) {
+ if (vc->x1 > ve->x2 - coords[0] || vc->x2 < ve->x2 - coords[2]) {
+ continue;
+ }
+ if (vc->y1 > ve->y2 - coords[1] || vc->y2 < ve->y2 - coords[3]) {
+ continue;
+ }
+ break;
+ }
+ break;
+ default:
+ for (; count-- != 0; coords += 4) {
+ if (vc->x1 > coords[2] + ve->x1 || vc->x2 < coords[0] + ve->x1) {
+ continue;
+ }
+ if (vc->y1 > coords[3] + ve->y1 || vc->y2 < coords[1] + ve->y1) {
+ continue;
+ }
+ break;
+ }
+ break;
+ }
+ if (count != 0) {
+ _clipBoxOffsetX = (vc->x2 - vc->x1) / 2 + vc->x1;
+ _clipBoxOffsetY = (vc->y2 - vc->y1) / 2 + vc->y1;
+ ret = 1;
+ }
+ return ret;
+}
+
+int Game::clipLvlObjectsBoundingBoxHelper(LvlObject *o1, BoundingBox *box1, LvlObject *o2, BoundingBox *box2) {
+ int ret = 0;
+ const uint8_t *coords1 = _res->getLvlSpriteCoordPtr(o1->levelData0x2988, o1->currentSprite);
+ const uint8_t *coords2 = _res->getLvlSpriteCoordPtr(o2->levelData0x2988, o2->currentSprite);
+ if (clipBoundingBox(box1, box2)) {
+ int count = *coords1++;
+ if (count != 0) {
+ const int direction = (o1->flags1 >> 4) & 3;
+ switch (direction) {
+ case 1:
+ for (; count-- != 0 && !ret; coords1 += 4) {
+ BoundingBox tmp;
+ tmp.x1 = box1->x2 - coords1[2];
+ tmp.x2 = box1->x2 - coords1[0];
+ tmp.y1 = box1->y1 + coords1[1];
+ tmp.y2 = box1->y2 + coords1[3];
+ ret = updateBoundingBoxClippingOffset(&tmp, box2, coords2, (o2->flags1 >> 4) & 3);
+ }
+ break;
+ case 2:
+ for (; count-- != 0 && !ret; coords1 += 4) {
+ BoundingBox tmp;
+ tmp.x1 = box1->x1 + coords1[0];
+ tmp.x2 = box1->x1 + coords1[2];
+ tmp.y1 = box1->y2 - coords1[3];
+ tmp.y2 = box1->y2 - coords1[1];
+ ret = updateBoundingBoxClippingOffset(&tmp, box2, coords2, (o2->flags1 >> 4) & 3);
+ }
+ break;
+ case 3:
+ for (; count-- != 0 && !ret; coords1 += 4) {
+ BoundingBox tmp;
+ tmp.x1 = box1->x2 - coords1[2];
+ tmp.x2 = box1->x2 - coords1[0];
+ tmp.y1 = box1->y2 - coords1[3];
+ tmp.y2 = box1->y2 - coords1[1];
+ ret = updateBoundingBoxClippingOffset(&tmp, box2, coords2, (o2->flags1 >> 4) & 3);
+ }
+ break;
+ default:
+ for (; count-- != 0 && !ret; coords1 += 4) {
+ BoundingBox tmp;
+ tmp.x1 = box1->x1 + coords1[0];
+ tmp.x2 = box1->x1 + coords1[2];
+ tmp.y1 = box1->y1 + coords1[1];
+ tmp.y2 = box1->y1 + coords1[3];
+ ret = updateBoundingBoxClippingOffset(&tmp, box2, coords2, (o2->flags1 >> 4) & 3);
+ }
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+int Game::clipLvlObjectsBoundingBox(LvlObject *o, LvlObject *ptr, int type) {
+ BoundingBox obj1, obj2;
+
+ obj1.x1 = o->xPos + _res->_screensBasePos[o->screenNum].u;
+ obj1.y1 = obj1.y2 = o->yPos + _res->_screensBasePos[o->screenNum].v;
+
+ obj2.x1 = ptr->xPos + _res->_screensBasePos[ptr->screenNum].u;
+ obj2.y1 = obj2.y2 = ptr->yPos + _res->_screensBasePos[ptr->screenNum].v;
+
+ switch (type - 17) {
+ case 1:
+ obj1.x2 = obj1.x1 + o->posTable[1].x;
+ obj1.x1 += o->posTable[0].x;
+ obj1.y2 += o->posTable[1].y;
+ obj1.y1 += o->posTable[0].y;
+ obj2.x2 = obj2.x1 + ptr->width - 1;
+ obj2.y2 += ptr->height - 1;
+ return clipBoundingBox(&obj1, &obj2);
+ case 17:
+ obj1.x2 = obj1.x1 + o->posTable[1].x;
+ obj1.x1 += o->posTable[0].x;
+ obj1.y2 += o->posTable[1].y;
+ obj1.y1 += o->posTable[0].y;
+ obj2.x2 = obj2.x1 + ptr->posTable[1].x;
+ obj2.x1 += ptr->posTable[0].x;
+ obj2.y2 += ptr->posTable[1].y;
+ obj2.y1 += ptr->posTable[0].y;
+ return clipBoundingBox(&obj1, &obj2);
+ case 49:
+ obj1.x2 = obj1.x1 + o->posTable[1].x;
+ obj1.x1 += o->posTable[0].x;
+ obj1.y2 += o->posTable[1].y;
+ obj1.y1 += o->posTable[0].y;
+ obj2.x2 = obj2.x1 + ptr->width - 1;
+ obj2.y2 += ptr->height - 1;
+ if (clipBoundingBox(&obj1, &obj2)) {
+ updateBoundingBoxClippingOffset(&obj1, &obj2, _res->getLvlSpriteCoordPtr(ptr->levelData0x2988, ptr->currentSprite), (ptr->flags1 >> 4) & 3);
+ }
+ break;
+ case 16:
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ obj2.y1 += ptr->posTable[0].y;
+ obj2.x2 = obj2.x1 + ptr->posTable[1].x;
+ obj2.x1 += ptr->posTable[0].x;
+ obj2.y2 += ptr->posTable[1].y;
+ return clipBoundingBox(&obj1, &obj2);
+ case 0:
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ obj2.x2 = obj2.x1 + ptr->width - 1;
+ obj2.y2 += ptr->height - 1;
+ return clipBoundingBox(&obj1, &obj2);
+ case 48:
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ obj2.x2 = obj2.x1 + ptr->width - 1;
+ obj2.y2 += ptr->height - 1;
+ if (clipBoundingBox(&obj1, &obj2)) {
+ return updateBoundingBoxClippingOffset(&obj1, &obj2, _res->getLvlSpriteCoordPtr(ptr->levelData0x2988, ptr->currentSprite), (ptr->flags1 >> 4) & 3);
+ }
+ break;
+ case 19:
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ obj2.x2 = obj2.x1 + ptr->posTable[1].x;
+ obj2.x1 += ptr->posTable[0].x;
+ obj2.y1 += ptr->posTable[0].y;
+ obj2.y2 += ptr->posTable[1].y;
+ if (clipBoundingBox(&obj2, &obj1)) {
+ return updateBoundingBoxClippingOffset(&obj2, &obj1, _res->getLvlSpriteCoordPtr(o->levelData0x2988, o->currentSprite), (o->flags1 >> 4) & 3);
+ }
+ break;
+ case 3:
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ obj2.x2 = obj2.x1 + ptr->width - 1;
+ obj2.y2 += ptr->height - 1;
+ if (clipBoundingBox(&obj2, &obj1)) {
+ return updateBoundingBoxClippingOffset(&obj2, &obj1, _res->getLvlSpriteCoordPtr(o->levelData0x2988, o->currentSprite), (o->flags1 >> 4) & 3);
+ }
+ break;
+ case 51:
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ obj2.x2 = obj2.x1 + ptr->width - 1;
+ obj2.y2 += ptr->height - 1;
+ return clipLvlObjectsBoundingBoxHelper(o, &obj1, ptr, &obj2);
+ case 115:
+ if (o->width == 3) {
+ obj1.y2 += 7;
+ obj1.x2 = obj1.x1 + 7;
+ } else {
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ }
+ obj2.x2 = obj2.x1 + ptr->width - 9;
+ obj2.x1 += 4;
+ obj2.y2 += ptr->height - 13;
+ obj2.y1 += 6;
+ if (clipBoundingBox(&obj2, &obj1)) {
+ return updateBoundingBoxClippingOffset(&obj2, &obj1, _res->getLvlSpriteCoordPtr(o->levelData0x2988, o->currentSprite), (o->flags1 >> 4) & 3);
+ }
+ break;
+ default:
+ warning("Unhandled clipLvlObjectsBoundingBox type %d (%d)", type, type - 17);
+ break;
+ }
+ return 0;
+}
+
+int Game::clipLvlObjectsSmall(LvlObject *o1, LvlObject *o2, int type) {
+ if (o1->width > 3 || o1->height > 3) {
+ return clipLvlObjectsBoundingBox(o1, o2, type);
+ }
+ LvlObject tmpObject;
+ memcpy(&tmpObject, o1, sizeof(LvlObject));
+ tmpObject.type = 2;
+ if (o1->frame == 0) {
+ updateAndyObject(&tmpObject);
+ }
+ updateAndyObject(&tmpObject);
+ return clipLvlObjectsBoundingBox(&tmpObject, o2, type);
+}
+
+int Game::restoreAndyCollidesLava() {
+ int ret = 0;
+ if (_lvlObjectsList1 && !_hideAndyObjectFlag && (_mstFlags & 0x80000000) == 0) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ for (LvlObject *o = _lvlObjectsList1; o; o = o->nextPtr) {
+ if (o->spriteNum != 21 || o->screenNum != _res->_currentScreenResourceNum) {
+ continue;
+ }
+ BoundingBox b;
+ b.x1 = o->xPos + o->posTable[0].x;
+ b.x2 = o->xPos + o->posTable[1].x;
+ b.y1 = o->yPos + o->posTable[0].y;
+ b.y2 = o->yPos + o->posTable[1].y;
+ if (!clipBoundingBox(&data->boundingBox, &b)) {
+ ret = clipLvlObjectsBoundingBox(_andyObject, o, 68);
+ if (ret) {
+ setAndySpecialAnimation(0xA3);
+ }
+ }
+ }
+ }
+ return ret;
+}
+
+int Game::updateAndyLvlObject() {
+ if (!_andyObject) {
+ return 0;
+ }
+ if (_actionDirectionKeyMaskIndex != 0) {
+ setAndyLvlObjectPlasmaCannonKeyMask();
+ }
+ assert(_andyObject->callbackFuncPtr);
+ (this->*_andyObject->callbackFuncPtr)(_andyObject);
+ if (_currentLevel != kLvl_isld) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ _andyObject->xPos += data->dxPos;
+ _andyObject->yPos += data->dyPos;
+ }
+ const uint8_t flags = _andyObject->flags0 & 255;
+ if ((flags & 0x1F) == 0xB) {
+ if (_andyObject->spriteNum == 2) {
+ removeLvlObject(_andyObject);
+ }
+ if ((flags & 0xE0) == 0x40) {
+ setAndySpecialAnimation(0xA4);
+ }
+ }
+ const int ret = updateLvlObjectScreen(_andyObject);
+ if (ret > 0) {
+ // changed screen
+ return 1;
+ } else if (ret == 0) {
+ if (_currentLevel != kLvl_rock && _currentLevel != kLvl_lar2 && _currentLevel != kLvl_test) {
+ return 0;
+ }
+ if (_plasmaExplosionObject) {
+ _plasmaExplosionObject->screenNum = _andyObject->screenNum;
+ lvlObjectType1Callback(_plasmaExplosionObject);
+ if (_andyObject->actionKeyMask & 4) {
+ addToSpriteList(_plasmaExplosionObject);
+ }
+ } else if (_andyObject->spriteNum == 0) {
+ lvlObjectType1Init(_andyObject);
+ }
+ return 0;
+ }
+ // moved to invalid screen (-1), restart
+ if ((_andyObject->flags0 & 0x1F) != 0xB) {
+ playAndyFallingCutscene(0);
+ }
+ restartLevel();
+ return 1;
+}
+
+void Game::drawPlasmaCannon() {
+ int index = _plasmaCannonFirstIndex;
+ int lastIndex = _plasmaCannonLastIndex1;
+ if (lastIndex == 0) {
+ lastIndex = _plasmaCannonLastIndex2;
+ }
+ int x1 = _plasmaCannonPosX[index];
+ int y1 = _plasmaCannonPosY[index];
+ index += 4;
+ do {
+ int x2 = _plasmaCannonXPointsTable1[index];
+ int y2 = _plasmaCannonYPointsTable1[index];
+ if (_plasmaCannonDirection == 1) {
+ _video->drawLine(x1 - 1, y1, x2 - 1, y2, 0xA9);
+ _video->drawLine(x1 + 1, y1, x2 + 1, y2, 0xA9);
+ } else {
+ _video->drawLine(x1, y1 - 1, x2, y2 - 1, 0xA9);
+ _video->drawLine(x1, y1 + 1, x2, y2 + 1, 0xA9);
+ }
+ _video->drawLine(x1, y1, x2, y2, 0xA6);
+ x1 = x2;
+ y1 = y2;
+ index += 4;
+ } while (index <= lastIndex);
+ _plasmaCannonLastIndex1 = 0;
+ _plasmaCannonPointsMask = 0;
+ _plasmaCannonExplodeFlag = false;
+ _plasmaCannonObject = 0;
+}
+
+void Game::drawScreen() {
+ memcpy(_video->_frontLayer, _video->_backgroundLayer, Video::W * Video::H);
+
+ // redraw background animation sprites
+ LvlBackgroundData *dat = &_res->_resLvlScreenBackgroundDataTable[_res->_currentScreenResourceNum];
+ if (_res->_isPsx) {
+ for (Sprite *spr = _typeSpritesList[0]; spr; spr = spr->nextPtr) {
+ assert((spr->num & 0x1F) == 0);
+ assert(spr->w == 0xFFFF && spr->h == 0xFFFF);
+ _video->decodeBackgroundOverlayPsx(spr->bitmapBits);
+ }
+ } else {
+ for (Sprite *spr = _typeSpritesList[0]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x1F) == 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_backgroundLayer, spr->xPos, spr->yPos, 0, spr->w, spr->h);
+ }
+ }
+ }
+ memset(_video->_shadowLayer, 0, Video::W * Video::H + 1);
+ for (int i = 1; i < 8; ++i) {
+ for (Sprite *spr = _typeSpritesList[i]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x2000) != 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_shadowLayer, spr->xPos, spr->yPos, (spr->num >> 0xE) & 3, spr->w, spr->h);
+ }
+ }
+ }
+ for (int i = 1; i < 4; ++i) {
+ for (Sprite *spr = _typeSpritesList[i]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x1000) != 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_frontLayer, spr->xPos, spr->yPos, (spr->num >> 0xE) & 3, spr->w, spr->h);
+ }
+ }
+ }
+ if (_andyObject->spriteNum == 0 && (_andyObject->flags2 & 0x1F) == 4) {
+ if (_plasmaCannonFirstIndex < _plasmaCannonLastIndex2) {
+ drawPlasmaCannon();
+ }
+ }
+ for (int i = 4; i < 8; ++i) {
+ for (Sprite *spr = _typeSpritesList[i]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x1000) != 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_frontLayer, spr->xPos, spr->yPos, (spr->num >> 0xE) & 3, spr->w, spr->h);
+ }
+ }
+ }
+ for (int i = 0; i < 24; ++i) {
+ for (Sprite *spr = _typeSpritesList[i]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x2000) != 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_shadowLayer, spr->xPos, spr->yPos, (spr->num >> 0xE) & 3, spr->w, spr->h);
+ }
+ }
+ }
+ for (int i = 0; i < dat->shadowCount; ++i) {
+ _video->applyShadowColors(_shadowScreenMasksTable[i].x,
+ _shadowScreenMasksTable[i].y,
+ _shadowScreenMasksTable[i].w,
+ _shadowScreenMasksTable[i].h,
+ 256,
+ _shadowScreenMasksTable[i].w,
+ _video->_shadowLayer,
+ _video->_frontLayer,
+ _shadowScreenMasksTable[i].projectionDataPtr,
+ _shadowScreenMasksTable[i].shadowPalettePtr);
+ }
+ for (int i = 1; i < 12; ++i) {
+ for (Sprite *spr = _typeSpritesList[i]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x1000) != 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_frontLayer, spr->xPos, spr->yPos, (spr->num >> 0xE) & 3, spr->w, spr->h);
+ }
+ }
+ }
+ if (_andyObject->spriteNum == 0 && (_andyObject->flags2 & 0x1F) == 0xC) {
+ if (_plasmaCannonFirstIndex < _plasmaCannonLastIndex2) {
+ drawPlasmaCannon();
+ }
+ }
+ for (int i = 12; i <= 24; ++i) {
+ for (Sprite *spr = _typeSpritesList[i]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x1000) != 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_frontLayer, spr->xPos, spr->yPos, (spr->num >> 0xE) & 3, spr->w, spr->h);
+ }
+ }
+ }
+}
+
+static void gamePafCallback(void *userdata) {
+ ((Game *)userdata)->resetSound();
+}
+
+void Game::mainLoop(int level, int checkpoint, bool levelChanged) {
+ if (_playDemo && _res->loadHodDem()) {
+ _rnd._rndSeed = _res->_dem.randSeed;
+ level = _res->_dem.level;
+ checkpoint = _res->_dem.checkpoint;
+ _difficulty = _res->_dem.difficulty;
+ _res->_demOffset = 0;
+ } else if (_resumeGame) {
+ const int num = _setupConfig.currentPlayer;
+ level = _setupConfig.players[num].levelNum;
+ if (level > kLvl_dark) {
+ level = kLvl_dark;
+ }
+ checkpoint = _setupConfig.players[num].checkpointNum;
+ if (checkpoint != 0 && checkpoint >= _res->_datHdr.levelCheckpointsCount[level]) {
+ checkpoint = _setupConfig.players[num].progress[level];
+ }
+ debug(kDebug_GAME, "Restart at level %d checkpoint %d cutscenes 0x%x", level, checkpoint, _paf->_playedMask);
+ _paf->_playedMask = _setupConfig.players[num].cutscenesMask;
+ _snd_masterVolume = _setupConfig.players[num].volume;
+ _paf->setVolume(_snd_masterVolume);
+ _difficulty = _setupConfig.players[num].difficulty;
+ // resume once, on the starting level
+ _resumeGame = false;
+ }
+
+ PafCallback pafCb;
+ pafCb.frameProc = 0;
+ pafCb.endProc = gamePafCallback;
+ pafCb.userdata = this;
+ _paf->setCallback(&pafCb);
+
+ _video->_font = _res->_fontBuffer;
+ assert(level < kLvl_test);
+ _currentLevel = level;
+ createLevel();
+ assert(checkpoint < _res->_datHdr.levelCheckpointsCount[level]);
+ _level->_checkpoint = checkpoint;
+ _mix._lock(1);
+ _res->loadLevelData(_currentLevel);
+ clearSoundObjects();
+ _mix._lock(0);
+ _mstAndyCurrentScreenNum = -1;
+ const int rounds = _playDemo ? _res->_dem.randRounds : ((g_system->getTimeStamp() & 15) + 1);
+ _rnd.initTable(rounds);
+ const int screenNum = _level->getCheckpointData(checkpoint)->screenNum;
+ if (_mstDisabled) {
+ _specialAnimMask = 0;
+ _mstCurrentAnim = 0;
+ _mstOriginPosX = Video::W / 2;
+ _mstOriginPosY = Video::H / 2;
+ } else {
+ _currentScreen = screenNum; // bugfix: clear previous level screen number
+ initMstCode();
+ }
+ memset(_level->_screenCounterTable, 0, sizeof(_level->_screenCounterTable));
+ clearDeclaredLvlObjectsList();
+ initLvlObjects();
+ resetPlasmaCannonState();
+ for (int i = 0; i < _res->_lvlHdr.screensCount; ++i) {
+ _res->_screensState[i].s2 = 0;
+ }
+ _res->_currentScreenResourceNum = _andyObject->screenNum;
+ _currentRightScreen = _res->_screensGrid[_res->_currentScreenResourceNum][kPosRightScreen];
+ _currentLeftScreen = _res->_screensGrid[_res->_currentScreenResourceNum][kPosLeftScreen];
+ if (!_mstDisabled) {
+ startMstCode();
+ }
+ if (!_paf->_skipCutscenes && _level->_checkpoint == 0 && !levelChanged) {
+ const uint8_t num = _cutscenes[_currentLevel];
+ _paf->preload(num);
+ _paf->play(num);
+ _paf->unload(num);
+ if (g_system->inp.quit) {
+ return;
+ }
+ }
+ _endLevel = false;
+ resetShootLvlObjectDataTable();
+ callLevel_initialize();
+ restartLevel();
+ while (true) {
+ const int frameTimeStamp = g_system->getTimeStamp() + _frameMs;
+ levelMainLoop();
+ if (g_system->inp.quit || _endLevel) {
+ break;
+ }
+ const int delay = MAX(10, frameTimeStamp - g_system->getTimeStamp());
+ g_system->sleep(delay);
+ }
+ _animBackgroundDataCount = 0;
+ callLevel_terminate();
+}
+
+void Game::mixAudio(int16_t *buf, int len) {
+
+ if (_snd_muted) {
+ return;
+ }
+
+ const int kStereoSamples = _res->_isPsx ? 1792 * 2 : 1764 * 2; // stereo
+
+ static int count = 0;
+
+ // flush samples from previous run
+ if (_snd_bufferSize > 0) {
+ const int count = len < _snd_bufferSize ? len : _snd_bufferSize;
+ memcpy(buf, _snd_buffer + _snd_bufferOffset, count * sizeof(int16_t));
+ buf += count;
+ len -= count;
+ _snd_bufferOffset += count;
+ _snd_bufferSize -= count;
+ }
+
+ while (len > 0) {
+ // this enqueues 1764*2 bytes for mono samples and 3528*2 bytes for stereo
+ _mix._mixingQueueSize = 0;
+ // 17640 + 17640 * 25 / 100 == 22050 (1.25x)
+ if (count == 4) {
+ mixSoundObjects17640(true);
+ count = 0;
+ } else {
+ mixSoundObjects17640(false);
+ ++count;
+ }
+
+ if (len >= kStereoSamples) {
+ _mix.mix(buf, kStereoSamples);
+ buf += kStereoSamples;
+ len -= kStereoSamples;
+ } else {
+ memset(_snd_buffer, 0, sizeof(_snd_buffer));
+ _mix.mix(_snd_buffer, kStereoSamples);
+ memcpy(buf, _snd_buffer, len * sizeof(int16_t));
+ _snd_bufferOffset = len;
+ _snd_bufferSize = kStereoSamples - len;
+ break;
+ }
+ }
+}
+
+void Game::updateLvlObjectList(LvlObject **list) {
+ LvlObject *ptr = *list;
+ while (ptr) {
+ LvlObject *next = ptr->nextPtr; // get 'next' as callback can modify linked list (eg. remove)
+ if (ptr->callbackFuncPtr == &Game::lvlObjectList3Callback && list != &_lvlObjectsList3) {
+ warning("lvlObject %p has callbackType3 and not in _lvlObjectsList3", ptr);
+ ptr = next;
+ continue;
+ }
+ if (ptr->callbackFuncPtr) {
+ (this->*(ptr->callbackFuncPtr))(ptr);
+ }
+ if (ptr->bitmapBits && list != &_lvlObjectsList3) {
+ addToSpriteList(ptr);
+ }
+ ptr = next;
+ }
+}
+
+void Game::updateLvlObjectLists() {
+ updateLvlObjectList(&_lvlObjectsList0);
+ updateLvlObjectList(&_lvlObjectsList1);
+ updateLvlObjectList(&_lvlObjectsList2);
+ updateLvlObjectList(&_lvlObjectsList3);
+}
+
+LvlObject *Game::updateAnimatedLvlObjectType0(LvlObject *ptr) {
+ const bool isPsx = _res->_isPsx;
+ const int soundDataLen = isPsx ? sizeof(uint32_t) : sizeof(uint16_t);
+ AnimBackgroundData *vg = (AnimBackgroundData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAnimBackgroundData);
+ const uint8_t *data = vg->currentSpriteData + soundDataLen;
+ if (_res->_currentScreenResourceNum == ptr->screenNum) {
+ if (ptr->currentSound != 0xFFFF) {
+ playSound(ptr->currentSound, ptr, 0, 3);
+ ptr->currentSound = 0xFFFF;
+ }
+ Sprite *spr = _spritesNextPtr;
+ if (spr && READ_LE_UINT16(data + 2) > 8) {
+ if (isPsx) {
+ assert((ptr->flags2 & 0x1F) == 0);
+ spr->bitmapBits = data;
+ spr->w = spr->h = 0xFFFF;
+ } else {
+ spr->xPos = data[0];
+ spr->yPos = data[1];
+ spr->w = READ_LE_UINT16(data + 4);
+ spr->h = READ_LE_UINT16(data + 6);
+ spr->bitmapBits = data + 8;
+ }
+ spr->num = ptr->flags2;
+ addToSpriteList(spr);
+ }
+ }
+ int16_t soundNum = -1;
+ const int len = READ_LE_UINT16(data + 2);
+ const uint8_t *nextSpriteData = len + data + 2;
+ switch (ptr->objectUpdateType - 1) {
+ case 6:
+ vg->currentSpriteData = vg->nextSpriteData;
+ if (vg->currentFrame == 0) {
+ vg->currentFrame = 1;
+ soundNum = isPsx ? READ_LE_UINT32(vg->nextSpriteData) : READ_LE_UINT16(vg->nextSpriteData);
+ }
+ ptr->objectUpdateType = 4;
+ break;
+ case 5:
+ vg->currentFrame = 0;
+ vg->currentSpriteData = vg->otherSpriteData;
+ ptr->objectUpdateType = 1;
+ break;
+ case 3:
+ ++vg->currentFrame;
+ if (vg->currentFrame < vg->framesCount) {
+ vg->currentSpriteData = nextSpriteData;
+ } else {
+ vg->currentFrame = 0;
+ vg->currentSpriteData = vg->otherSpriteData;
+ ptr->objectUpdateType = 1;
+ }
+ soundNum = isPsx ? READ_LE_UINT32(vg->currentSpriteData) : READ_LE_UINT16(vg->currentSpriteData);
+ break;
+ case 4:
+ ++vg->currentFrame;
+ if (vg->currentFrame < vg->framesCount) { // bugfix: original uses '<=' (out of bounds)
+ vg->currentSpriteData = nextSpriteData;
+ } else {
+ vg->currentFrame = 0;
+ vg->currentSpriteData = vg->otherSpriteData;
+ ptr->objectUpdateType = 1;
+ }
+ soundNum = isPsx ? READ_LE_UINT32(vg->currentSpriteData) : READ_LE_UINT16(vg->currentSpriteData);
+ break;
+ case 2:
+ while (vg->currentFrame < vg->framesCount - 2) {
+ ++vg->currentFrame;
+ vg->currentSpriteData = nextSpriteData;
+ nextSpriteData += soundDataLen;
+ const int len = READ_LE_UINT16(nextSpriteData + 2);
+ nextSpriteData += len + 2;
+ }
+ data = vg->currentSpriteData + soundDataLen;
+ if (_res->_currentScreenResourceNum == ptr->screenNum) {
+ Sprite *spr = _spritesNextPtr;
+ if (spr && READ_LE_UINT16(data + 2) > 8) {
+ if (isPsx) {
+ assert((ptr->flags2 & 0x1F) == 0);
+ spr->bitmapBits = data;
+ spr->w = spr->h = 0xFFFF;
+ } else {
+ spr->w = READ_LE_UINT16(data + 4);
+ spr->h = READ_LE_UINT16(data + 6);
+ spr->bitmapBits = data + 8;
+ spr->xPos = data[0];
+ spr->yPos = data[1];
+ }
+ spr->num = ptr->flags2;
+ addToSpriteList(spr);
+ }
+ }
+ ptr->objectUpdateType = 1;
+ return ptr->nextPtr;
+ case 1:
+ ++vg->currentFrame;
+ if (vg->currentFrame < vg->framesCount - 1) {
+ vg->currentSpriteData = nextSpriteData;
+ soundNum = isPsx ? READ_LE_UINT32(vg->currentSpriteData) : READ_LE_UINT16(vg->currentSpriteData);
+ } else {
+ if (vg->currentFrame > vg->framesCount) {
+ vg->currentFrame = vg->framesCount;
+ }
+ ptr->objectUpdateType = 1;
+ return ptr->nextPtr;
+ }
+ break;
+ case 0:
+ return ptr->nextPtr;
+ default:
+ soundNum = isPsx ? READ_LE_UINT32(vg->currentSpriteData) : READ_LE_UINT16(vg->currentSpriteData);
+ if (ptr->hitCount == 0) {
+ ++vg->currentFrame;
+ if (vg->currentFrame >= vg->framesCount) {
+ vg->currentSpriteData = vg->nextSpriteData;
+ vg->currentFrame = 1;
+ } else {
+ vg->currentSpriteData = nextSpriteData;
+ }
+ } else {
+ --ptr->hitCount;
+ }
+ break;
+ }
+ if (soundNum != -1) {
+ playSound(soundNum, ptr, 0, 3);
+ }
+ return ptr->nextPtr;
+}
+
+LvlObject *Game::updateAnimatedLvlObjectType1(LvlObject *ptr) {
+ if (ptr->screenNum == _res->_currentScreenResourceNum) {
+ if (_res->_screensState[_res->_currentScreenResourceNum].s0 == ptr->screenState || ptr->screenState == 0xFF) {
+ if (ptr->currentSound != 0xFFFF) {
+ playSound(ptr->currentSound, 0, 0, 3);
+ ptr->currentSound = 0xFFFF;
+ }
+ const uint8_t *data = (const uint8_t *)getLvlObjectDataPtr(ptr, kObjectDataTypeLvlBackgroundSound);
+ Sprite *spr = _spritesNextPtr;
+ if (spr && READ_LE_UINT16(data + 2) > 8) {
+ spr->w = READ_LE_UINT16(data + 4);
+ spr->h = READ_LE_UINT16(data + 6);
+ spr->bitmapBits = data + 8;
+ spr->xPos = data[0];
+ spr->yPos = data[1];
+ spr->num = ptr->flags2;
+ addToSpriteList(spr);
+ }
+ }
+ }
+ return ptr->nextPtr;
+}
+
+LvlObject *Game::updateAnimatedLvlObjectType2(LvlObject *ptr) {
+ LvlObject *next, *o;
+
+ o = next = ptr->nextPtr;
+ if ((ptr->spriteNum > 15 && ptr->dataPtr == 0) || ptr->levelData0x2988 == 0) {
+ if (ptr->childPtr) {
+ o = ptr->childPtr->nextPtr;
+ }
+ return o;
+ }
+ const int num = ptr->screenNum;
+ if (_currentScreen != num && _currentRightScreen != num && _currentLeftScreen != num) {
+ return o;
+ }
+ if (!ptr->callbackFuncPtr) {
+ warning("updateAnimatedLvlObjectType2: no callback ptr");
+ } else {
+ if ((this->*(ptr->callbackFuncPtr))(ptr) == 0) {
+ return o;
+ }
+ }
+ if ((ptr->flags1 & 6) == 2) {
+ const int index = (15 < ptr->spriteNum) ? 5 : 7;
+ ptr->yPos += calcScreenMaskDy(ptr->xPos + ptr->posTable[index].x, ptr->yPos + ptr->posTable[index].y, ptr->screenNum);
+ }
+ if (!ptr->bitmapBits) {
+ return o;
+ }
+ if (_currentScreen == ptr->screenNum) {
+ const uint8_t *bitmap = ptr->bitmapBits;
+
+ LvlObjectData *dat = ptr->levelData0x2988;
+ LvlAnimHeader *ah = (LvlAnimHeader *)(dat->animsInfoData + kLvlAnimHdrOffset) + ptr->anim;
+ LvlAnimSeqHeader *ash = (LvlAnimSeqHeader *)(dat->animsInfoData + ah->seqOffset) + ptr->frame;
+
+ const int f1 = (ptr->flags1 >> 4) & 3;
+ const int f2 = (ash->flags1 >> 4) & 3;
+ const int num = ((f1 ^ f2) << 14) | ptr->flags2;
+ Sprite *spr = _spritesNextPtr;
+ if (spr && bitmap) {
+ spr->yPos = ptr->yPos;
+ spr->xPos = ptr->xPos;
+ spr->w = ptr->width;
+ spr->h = ptr->height;
+ spr->bitmapBits = bitmap;
+ spr->num = num;
+ addToSpriteList(spr);
+ }
+ }
+ if (ptr->spriteNum <= 15 || ptr->dataPtr == 0) {
+ if (ptr->currentSound != 0xFFFF) {
+ playSound(ptr->currentSound, ptr, 0, 3);
+ }
+ return o;
+ }
+ int a, c;
+ if (ptr->dataPtr >= &_monsterObjects1Table[0] && ptr->dataPtr < &_monsterObjects1Table[kMaxMonsterObjects1]) {
+ MonsterObject1 *m = (MonsterObject1 *)ptr->dataPtr;
+ if (m->flagsA6 & 2) {
+ assert(ptr == m->o16);
+ ptr->actionKeyMask = _mstCurrentActionKeyMask;
+ ptr->directionKeyMask = _andyObject->directionKeyMask;
+ }
+ a = m->monster1Index;
+ c = 1;
+ } else {
+ assert(ptr->dataPtr >= &_monsterObjects2Table[0] && ptr->dataPtr < &_monsterObjects2Table[kMaxMonsterObjects2]);
+ MonsterObject1 *m = ((MonsterObject2 *)ptr->dataPtr)->monster1;
+ if (m) {
+ a = m->monster1Index;
+ c = 2;
+ } else {
+ a = 4;
+ c = 0;
+ }
+ }
+ if (ptr->currentSound != 0xFFFF) {
+ playSound(ptr->currentSound, ptr, c, a);
+ }
+ return o;
+}
+
+LvlObject *Game::updateAnimatedLvlObjectTypeDefault(LvlObject *ptr) {
+ return ptr->nextPtr;
+}
+
+LvlObject *Game::updateAnimatedLvlObject(LvlObject *o) {
+ switch (o->type) {
+ case 0:
+ o = updateAnimatedLvlObjectType0(o);
+ break;
+ case 1:
+ o = updateAnimatedLvlObjectType1(o);
+ break;
+ case 2:
+ o = updateAnimatedLvlObjectType2(o);
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ o = updateAnimatedLvlObjectTypeDefault(o);
+ break;
+ default:
+ error("updateAnimatedLvlObject unhandled type %d", o->type);
+ break;
+ }
+ return o;
+}
+
+void Game::updateAnimatedLvlObjectsLeftRightCurrentScreens() {
+ LvlObject *ptr = _screenLvlObjectsList[_res->_currentScreenResourceNum];
+ while (ptr) {
+ if (ptr->screenState == 0xFF || ptr->screenState == _res->_screensState[_res->_currentScreenResourceNum].s0) {
+ ptr = updateAnimatedLvlObject(ptr);
+ } else {
+ ptr = ptr->nextPtr;
+ }
+ }
+ int index = _res->_screensGrid[_res->_currentScreenResourceNum][kPosRightScreen];
+ if (index != kNoScreen && _res->_screensState[index].s2 != 0) {
+ ptr = _screenLvlObjectsList[index];
+ while (ptr) {
+ if (ptr->screenState == 0xFF || ptr->screenState == _res->_screensState[index].s0) {
+ ptr = updateAnimatedLvlObject(ptr);
+ } else {
+ ptr = ptr->nextPtr;
+ }
+ }
+ }
+ index = _res->_screensGrid[_res->_currentScreenResourceNum][kPosLeftScreen];
+ if (index != kNoScreen && _res->_screensState[index].s2 != 0) {
+ ptr = _screenLvlObjectsList[index];
+ while (ptr) {
+ if (ptr->screenState == 0xFF || ptr->screenState == _res->_screensState[index].s0) {
+ ptr = updateAnimatedLvlObject(ptr);
+ } else {
+ ptr = ptr->nextPtr;
+ }
+ }
+ }
+}
+
+void Game::updatePlasmaCannonExplosionLvlObject(LvlObject *ptr) {
+ ptr->actionKeyMask = 0;
+ ptr->directionKeyMask = 0;
+ if (_plasmaCannonDirection != 0 && _plasmaCannonLastIndex1 != 0) {
+ if (_plasmaCannonObject) {
+ const int _al = (_plasmaCannonObject->xPos <= _andyObject->xPos) ? 0 : 0xFF;
+ ptr->directionKeyMask = (_al & ~5) | 8;
+ if (_plasmaCannonObject->yPos > _andyObject->yPos) {
+ ptr->directionKeyMask |= 4;
+ } else {
+ ptr->directionKeyMask |= 1;
+ }
+ } else {
+ ptr->directionKeyMask = 2;
+ if (_plasmaCannonPointsMask != 0) {
+ if ((_plasmaCannonPointsMask & 1) != 0 || (_plasmaCannonDirection & 4) != 0) {
+ ptr->directionKeyMask = 4;
+ } else if ((_plasmaCannonDirection & 8) != 0) {
+ ptr->directionKeyMask = 8;
+ }
+ }
+ }
+ if ((_andyObject->flags0 & 0x1F) == 4 && (_andyObject->flags0 & 0xE0) == 0xC0) {
+ ptr->directionKeyMask = 1;
+ }
+ if (_plasmaCannonExplodeFlag) {
+ ptr->actionKeyMask = 4;
+ if ((_rnd._rndSeed & 1) != 0 && addLvlObjectToList3(1)) {
+ _lvlObjectsList3->flags0 = _andyObject->flags0;
+ _lvlObjectsList3->flags1 = _andyObject->flags1;
+ _lvlObjectsList3->screenNum = _andyObject->screenNum;
+ _lvlObjectsList3->flags2 = _andyObject->flags2 & ~0x2000;
+ if ((ptr->directionKeyMask & 1) == 0) {
+ _lvlObjectsList3->anim = 12;
+ _lvlObjectsList3->flags1 ^= 0x20;
+ } else {
+ _lvlObjectsList3->anim = 11;
+ }
+ _lvlObjectsList3->frame = 0;
+ setLvlObjectPosRelativeToPoint(_lvlObjectsList3, 0, _plasmaCannonXPointsTable1[_plasmaCannonLastIndex1], _plasmaCannonYPointsTable1[_plasmaCannonLastIndex1]);
+ }
+ }
+ setLvlObjectPosRelativeToPoint(ptr, 0, _plasmaCannonXPointsTable1[_plasmaCannonLastIndex1], _plasmaCannonYPointsTable1[_plasmaCannonLastIndex1]);
+ }
+ updateAndyObject(ptr);
+ ptr->screenNum = _andyObject->screenNum;
+ ptr->flags2 = merge_bits(ptr->flags2, _andyObject->flags2, 0x18);
+ ptr->flags2 = merge_bits(ptr->flags2, _andyObject->flags2 + 1, 7);
+ addToSpriteList(ptr);
+}
+
+void Game::resetPlasmaCannonState() {
+ _plasmaCannonDirection = 0;
+ _plasmaCannonPrevDirection = 0;
+ _plasmaCannonPointsSetupCounter = 0;
+ _plasmaCannonLastIndex1 = 0;
+ _plasmaCannonExplodeFlag = false;
+ _plasmaCannonPointsMask = 0;
+ _plasmaCannonFirstIndex = 16;
+ _plasmaCannonLastIndex2 = 16;
+}
+
+void Game::updateAndyMonsterObjects() {
+ uint8_t _dl = 3;
+ LvlObject *ptr = _andyObject;
+ switch (_actionDirectionKeyMaskIndex >> 4) {
+ case 6:
+ _hideAndyObjectFlag = false;
+ if (_actionDirectionKeyMaskIndex == 0x61) {
+ assert(_specialAnimLvlObject);
+ _mstOriginPosX += _specialAnimLvlObject->posTable[6].x + _specialAnimLvlObject->xPos;
+ _mstOriginPosY += _specialAnimLvlObject->posTable[6].y + _specialAnimLvlObject->yPos;
+ }
+ ptr->childPtr = 0;
+ break;
+ case 7: // replace Andy sprite with a custom animation
+ _hideAndyObjectFlag = true;
+ if (_actionDirectionKeyMaskIndex == 0x71) {
+ assert(_specialAnimLvlObject);
+ _mstOriginPosX += _specialAnimLvlObject->posTable[6].x + _specialAnimLvlObject->xPos;
+ _mstOriginPosY += _specialAnimLvlObject->posTable[6].y + _specialAnimLvlObject->yPos;
+ ptr->childPtr = _specialAnimLvlObject;
+ ptr->screenNum = _specialAnimLvlObject->screenNum;
+ } else {
+ ptr->childPtr = 0;
+ }
+ break;
+ case 10:
+ if (_actionDirectionKeyMaskIndex != 0xA3) {
+ return;
+ }
+ ptr->actionKeyMask = _actionDirectionKeyMaskTable[0x146];
+ ptr->directionKeyMask = _actionDirectionKeyMaskTable[_actionDirectionKeyMaskIndex * 2 + 1];
+ updateAndyObject(ptr);
+ _actionDirectionKeyMaskIndex = 0;
+ _hideAndyObjectFlag = false;
+ _mstFlags |= 0x80000000;
+ _dl = 1;
+ break;
+ default:
+ return;
+ }
+ if (_dl & 2) {
+ _actionDirectionKeyMaskIndex = 0;
+ ptr->anim = _mstCurrentAnim;
+ ptr->frame = 0;
+ ptr->flags1 = merge_bits(ptr->flags1, _specialAnimMask, 0x30);
+ setupLvlObjectBitmap(ptr);
+ setLvlObjectPosRelativeToPoint(ptr, 3, _mstOriginPosX, _mstOriginPosY);
+ }
+ _andyActionKeysFlags = 0;
+ if (ptr->spriteNum == 2) {
+ removeLvlObject(ptr);
+ }
+}
+
+void Game::updateInput() {
+ const uint8_t inputMask = g_system->inp.mask;
+ if (inputMask & SYS_INP_RUN) {
+ _actionKeyMask |= kActionKeyMaskRun;
+ }
+ if (inputMask & SYS_INP_JUMP) {
+ _actionKeyMask |= kActionKeyMaskJump;
+ }
+ if (inputMask & SYS_INP_SHOOT) {
+ _actionKeyMask |= kActionKeyMaskShoot;
+ }
+ if (inputMask & SYS_INP_UP) {
+ _directionKeyMask |= kDirectionKeyMaskUp;
+ } else if (inputMask & SYS_INP_DOWN) {
+ _directionKeyMask |= kDirectionKeyMaskDown;
+ }
+ if (inputMask & SYS_INP_RIGHT) {
+ _directionKeyMask |= kDirectionKeyMaskRight;
+ } else if (inputMask & SYS_INP_LEFT) {
+ _directionKeyMask |= kDirectionKeyMaskLeft;
+ }
+}
+
+void Game::levelMainLoop() {
+ memset(_typeSpritesList, 0, sizeof(_typeSpritesList));
+ _spritesNextPtr = &_spritesTable[0];
+ for (int i = 0; i < kMaxSprites - 1; ++i) {
+ _spritesTable[i].nextPtr = &_spritesTable[i + 1];
+ }
+ _spritesTable[kMaxSprites - 1].nextPtr = 0;
+ _directionKeyMask = 0;
+ _actionKeyMask = 0;
+ updateInput();
+ if (_playDemo && _res->_demOffset < _res->_dem.keyMaskLen) {
+ _andyObject->actionKeyMask = _res->_dem.actionKeyMask[_res->_demOffset];
+ _andyObject->directionKeyMask = _res->_dem.directionKeyMask[_res->_demOffset];
+ ++_res->_demOffset;
+ } else {
+ _andyObject->directionKeyMask = _directionKeyMask;
+ _andyObject->actionKeyMask = _actionKeyMask;
+ }
+ _video->clearBackBuffer();
+ if (_andyObject->screenNum != _res->_currentScreenResourceNum) {
+ preloadLevelScreenData(_andyObject->screenNum, _res->_currentScreenResourceNum);
+ setupScreen(_andyObject->screenNum);
+ } else if (_fadePalette && _levelRestartCounter == 0) {
+ restartLevel();
+ } else {
+ callLevel_postScreenUpdate(_res->_currentScreenResourceNum);
+ if (_currentLeftScreen != kNoScreen) {
+ callLevel_postScreenUpdate(_currentLeftScreen);
+ }
+ if (_currentRightScreen != kNoScreen) {
+ callLevel_postScreenUpdate(_currentRightScreen);
+ }
+ }
+ if (_endLevel) {
+ return;
+ }
+ _currentLevelCheckpoint = _level->_checkpoint;
+ if (updateAndyLvlObject()) {
+ callLevel_tick();
+ return;
+ }
+ executeMstCode();
+ updateLvlObjectLists();
+ callLevel_tick();
+ updateAndyMonsterObjects();
+ if (!_hideAndyObjectFlag) {
+ addToSpriteList(_andyObject);
+ }
+ ((AndyLvlObjectData *)_andyObject->dataPtr)->dxPos = 0;
+ ((AndyLvlObjectData *)_andyObject->dataPtr)->dyPos = 0;
+ updateAnimatedLvlObjectsLeftRightCurrentScreens();
+ if (_currentLevel == kLvl_rock || _currentLevel == kLvl_lar2 || _currentLevel == kLvl_test) {
+ if (_andyObject->spriteNum == 0 && _plasmaExplosionObject && _plasmaExplosionObject->nextPtr != 0) {
+ updatePlasmaCannonExplosionLvlObject(_plasmaExplosionObject->nextPtr);
+ }
+ }
+ if (_res->_sssHdr.infosDataCount != 0) {
+ // sound thread signaling
+ }
+ if (_video->_paletteChanged) {
+ _video->_paletteChanged = false;
+ _video->updateGamePalette(_video->_displayPaletteBuffer);
+ g_system->copyRectWidescreen(Video::W, Video::H, _video->_backgroundLayer, _video->_palette);
+ }
+ drawScreen();
+ if (g_system->inp.screenshot) {
+ g_system->inp.screenshot = false;
+ captureScreenshot();
+ }
+ if (_cheats != 0) {
+ char buffer[256];
+ snprintf(buffer, sizeof(buffer), "P%d S%02d %d R%d", _currentLevel, _andyObject->screenNum, _res->_screensState[_andyObject->screenNum].s0, _level->_checkpoint);
+ _video->drawString(buffer, (Video::W - strlen(buffer) * 8) / 2, 8, _video->findWhiteColor(), _video->_frontLayer);
+ }
+ if (_shakeScreenDuration != 0 || _levelRestartCounter != 0 || _video->_displayShadowLayer) {
+ shakeScreen();
+ _video->updateGameDisplay(_video->_displayShadowLayer ? _video->_shadowLayer : _video->_frontLayer);
+ } else {
+ _video->updateGameDisplay(_video->_frontLayer);
+ }
+ _rnd.update();
+ g_system->processEvents();
+ if (g_system->inp.keyPressed(SYS_INP_ESC)) {
+ if (displayHintScreen(-1, 0)) { // pause/exit screen
+ g_system->inp.quit = true;
+ }
+ } else {
+ // displayHintScreen(1, 0);
+ _video->updateScreen();
+ }
+}
+
+void Game::callLevel_postScreenUpdate(int num) {
+ _level->postScreenUpdate(num);
+}
+
+void Game::callLevel_preScreenUpdate(int num) {
+ _level->preScreenUpdate(num);
+}
+
+Level *Game::createLevel() {
+ switch (_currentLevel) {
+ case 0:
+ _level = Level_rock_create();
+ break;
+ case 1:
+ _level = Level_fort_create();
+ break;
+ case 2:
+ _level = Level_pwr1_create();
+ break;
+ case 3:
+ _level = Level_isld_create();
+ break;
+ case 4:
+ _level = Level_lava_create();
+ break;
+ case 5:
+ _level = Level_pwr2_create();
+ break;
+ case 6:
+ _level = Level_lar1_create();
+ break;
+ case 7:
+ _level = Level_lar2_create();
+ break;
+ case 8:
+ _level = Level_dark_create();
+ break;
+ }
+ return _level;
+}
+
+void Game::callLevel_initialize() {
+ _level->setPointers(this, _andyObject, _paf, _res, _video);
+ _level->initialize();
+}
+
+void Game::callLevel_tick() {
+ _level->tick();
+}
+
+void Game::callLevel_terminate() {
+ _level->terminate();
+ delete _level;
+ _level = 0;
+}
+
+void Game::displayLoadingScreen() {
+ if (_res->_isPsx) {
+ static const int kHintPsxLoading = 39;
+ if (_res->loadDatHintImage(kHintPsxLoading, _video->_frontLayer, 0)) {
+ _video->decodeBackgroundPsx(_video->_frontLayer, _res->_datHdr.hintsImageSizeTable[kHintPsxLoading], Video::W, Video::H);
+ g_system->fillRect(0, 0, Video::W, Video::H, 0);
+ _video->updateYuvDisplay();
+ g_system->updateScreen(false);
+ }
+ } else {
+ if (_res->loadDatLoadingImage(_video->_frontLayer, _video->_palette)) {
+ g_system->setPalette(_video->_palette, 256, 6);
+ g_system->copyRect(0, 0, Video::W, Video::H, _video->_frontLayer, 256);
+ g_system->updateScreen(false);
+ }
+ }
+}
+
+int Game::displayHintScreen(int num, int pause) {
+ static const int kQuitYes = 0;
+ static const int kQuitNo = 1;
+ int quit = kQuitYes;
+ bool confirmQuit = false;
+ uint8_t *quitBuffers[] = {
+ _video->_frontLayer,
+ _video->_shadowLayer,
+ };
+ const bool isPsx = _res->_isPsx;
+ muteSound();
+ if (num == -1) {
+ if (isPsx) {
+ num = 35; // 'Pause' on PSX
+ } else {
+ num = _res->_datHdr.yesNoQuitImage; // 'Yes'
+ _res->loadDatHintImage(num + 1, _video->_shadowLayer, _video->_palette); // 'No'
+ confirmQuit = true;
+ }
+ }
+ if (_res->loadDatHintImage(num, _video->_frontLayer, _video->_palette)) {
+ if (isPsx) {
+ _video->decodeBackgroundPsx(_video->_frontLayer, _res->_datHdr.hintsImageSizeTable[num], Video::W, Video::H);
+ g_system->fillRect(0, 0, Video::W, Video::H, 0);
+ _video->updateYuvDisplay();
+ } else {
+ g_system->setPalette(_video->_palette, 256, 6);
+ g_system->copyRect(0, 0, Video::W, Video::H, _video->_frontLayer, 256);
+ }
+ g_system->updateScreen(false);
+ do {
+ g_system->processEvents();
+ if (confirmQuit) {
+ const int currentQuit = quit;
+ if (g_system->inp.keyReleased(SYS_INP_LEFT)) {
+ quit = kQuitNo;
+ }
+ if (g_system->inp.keyReleased(SYS_INP_RIGHT)) {
+ quit = kQuitYes;
+ }
+ if (currentQuit != quit) {
+ g_system->copyRect(0, 0, Video::W, Video::H, quitBuffers[quit], 256);
+ g_system->updateScreen(false);
+ }
+ }
+ g_system->sleep(30);
+ } while (!g_system->inp.quit && !g_system->inp.keyReleased(SYS_INP_JUMP));
+ _video->_paletteChanged = true;
+ }
+ unmuteSound();
+ if (isPsx) { // restore level screen bitmap
+ LvlBackgroundData *lvl = &_res->_resLvlScreenBackgroundDataTable[_res->_currentScreenResourceNum];
+ const uint8_t *bmp = lvl->backgroundBitmapTable[lvl->currentBackgroundId];
+ _video->decodeBackgroundPsx(bmp + 4, -1, Video::W, Video::H);
+ }
+ return confirmQuit && quit == kQuitYes;
+}
+
+void Game::prependLvlObjectToList(LvlObject **list, LvlObject *ptr) {
+ ptr->nextPtr = *list;
+ *list = ptr;
+}
+
+void Game::removeLvlObjectFromList(LvlObject **list, LvlObject *ptr) {
+ LvlObject *current = *list;
+ if (current && ptr) {
+ if (current == ptr) {
+ *list = ptr->nextPtr;
+ } else {
+ LvlObject *prev = 0;
+ do {
+ prev = current;
+ current = current->nextPtr;
+ if (!current) {
+ warning("LvlObject %p not found for removal", ptr);
+ return;
+ }
+ } while (current != ptr);
+ prev->nextPtr = current->nextPtr;
+ }
+ }
+}
+
+void *Game::getLvlObjectDataPtr(LvlObject *o, int type) const {
+ switch (type) {
+ case kObjectDataTypeAndy:
+ assert(o == _andyObject);
+ assert(o->dataPtr == &_andyObjectScreenData);
+ break;
+ case kObjectDataTypeAnimBackgroundData:
+ assert(o->dataPtr >= &_animBackgroundDataTable[0] && o->dataPtr < &_animBackgroundDataTable[kMaxBackgroundAnims]);
+ break;
+ case kObjectDataTypeShoot:
+ assert(o->dataPtr >= &_shootLvlObjectDataTable[0] && o->dataPtr < &_shootLvlObjectDataTable[kMaxShootLvlObjectData]);
+ break;
+ case kObjectDataTypeLvlBackgroundSound:
+ assert(o->type == 1);
+ // dataPtr is _res->_resLvlScreenBackgroundDataTable[num].backgroundSoundTable + 2
+ assert(o->dataPtr);
+ break;
+ case kObjectDataTypeMonster1:
+ assert(o->dataPtr >= &_monsterObjects1Table[0] && o->dataPtr < &_monsterObjects1Table[kMaxMonsterObjects1]);
+ break;
+ case kObjectDataTypeMonster2:
+ assert(o->dataPtr >= &_monsterObjects2Table[0] && o->dataPtr < &_monsterObjects2Table[kMaxMonsterObjects2]);
+ break;
+ }
+ return o->dataPtr;
+}
+
+// Andy
+void Game::lvlObjectType0Init(LvlObject *ptr) {
+ uint8_t num = ptr->spriteNum;
+ if (_currentLevel == kLvl_rock && _level->_checkpoint >= 5) {
+ num = 2; // sprite without 'plasma cannon'
+ }
+ _andyObject = declareLvlObject(ptr->type, num);
+ assert(_andyObject);
+ _andyObject->xPos = ptr->xPos;
+ _andyObject->yPos = ptr->yPos;
+ _andyObject->screenNum = ptr->screenNum;
+ _andyObject->anim = ptr->anim;
+ _andyObject->frame = ptr->frame;
+ _andyObject->flags2 = ptr->flags2;
+ _andyObject->dataPtr = &_andyObjectScreenData;
+ memset(&_andyObjectScreenData, 0, sizeof(_andyObjectScreenData));
+}
+
+// Plasma cannon explosion
+void Game::lvlObjectType1Init(LvlObject *ptr) {
+ AndyLvlObjectData *dataPtr = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ if (dataPtr->shootLvlObject) {
+ return;
+ }
+ LvlObject *o = declareLvlObject(8, 1);
+ assert(o);
+ o->xPos = ptr->xPos;
+ o->yPos = ptr->yPos;
+ o->anim = 13;
+ o->frame = 0;
+ o->screenNum = ptr->screenNum;
+ o->flags1 = merge_bits(o->flags1, ptr->flags1, 0x30);
+ o->flags2 = ptr->flags2 & ~0x2000;
+ setupLvlObjectBitmap(o);
+ prependLvlObjectToList(&_plasmaExplosionObject, o);
+
+ o = declareLvlObject(8, 1);
+ assert(o);
+ dataPtr->shootLvlObject = o;
+ o->xPos = ptr->xPos;
+ o->yPos = ptr->yPos;
+ o->anim = 5;
+ o->frame = 0;
+ o->screenNum = ptr->screenNum;
+ o->flags1 = merge_bits(o->flags1, ptr->flags1, 0x30);
+ o->flags2 = ptr->flags2 & ~0x2000;
+ setupLvlObjectBitmap(o);
+ prependLvlObjectToList(&_plasmaExplosionObject, o);
+}
+
+void Game::lvlObjectTypeInit(LvlObject *o) {
+ switch (o->spriteNum) {
+ case 0: // Andy with plasma cannon and helmet
+ case 2: // Andy
+ lvlObjectType0Init(o);
+ break;
+ case 1: // plasma cannon explosion
+ lvlObjectType1Init(o);
+ break;
+ default:
+ error("lvlObjectTypeInit unhandled case %d", o->spriteNum);
+ break;
+ }
+}
+
+void Game::lvlObjectType0CallbackHelper1() {
+ uint8_t _bl, _cl, _dl;
+
+ _cl = _dl = _andyObject->flags0;
+ _bl = _andyObject->actionKeyMask;
+
+ _dl &= 0x1F;
+ _cl >>= 5;
+ _cl &= 7;
+
+ if (_currentLevel == kLvl_dark && (_bl & 4) != 0) {
+ _bl &= ~4;
+ _bl |= 8;
+ }
+ if (_dl == 3) {
+ if (_cl == _dl) {
+ _andyActionKeysFlags |= 2;
+ }
+ } else if (_dl == 7) {
+ if (_cl == 5) {
+ _andyActionKeysFlags |= _bl & 4;
+ } else {
+ _andyActionKeysFlags &= ~4;
+ }
+ }
+ if ((_andyActionKeysFlags & 2) != 0) {
+ if (_bl & 2) {
+ _bl &= ~2;
+ } else {
+ _andyActionKeysFlags &= ~2;
+ }
+ }
+ if (_andyActionKeysFlags & 4) {
+ _bl |= 4;
+ }
+ if (_andyObject->spriteNum == 2 && (_bl & 5) == 5) {
+ AndyLvlObjectData *andyData = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ LvlObject *o = andyData->shootLvlObject;
+ if (o) {
+ ShootLvlObjectData *dat = (ShootLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ if (dat->type < 4) {
+ _bl |= 0xC0;
+ }
+ }
+ }
+ if (_plasmaCannonFlags & 2) {
+ _bl &= ~4;
+ }
+ _andyObject->actionKeyMask = (_bl & _andyActionKeyMaskAnd) | _andyActionKeyMaskOr;
+ _bl = _andyObject->directionKeyMask;
+ _andyObject->directionKeyMask = (_bl & _andyDirectionKeyMaskAnd) | _andyDirectionKeyMaskOr;
+}
+
+int Game::calcScreenMaskDx(int x, int y, int num) {
+ const uint32_t offset = screenMaskOffset(x, y);
+ int ret = -(x & 7);
+ if (num & 1) {
+ ret += 8;
+ if (_screenMaskBuffer[offset] & 2) {
+ return ret;
+ } else if (_screenMaskBuffer[offset - 1] & 2) {
+ return ret - 8;
+ }
+ } else {
+ --ret;
+ if (_screenMaskBuffer[offset] & 2) {
+ return ret;
+ } else if (_screenMaskBuffer[offset + 1] & 2) {
+ return ret + 8;
+ } else if (_screenMaskBuffer[offset + 2] & 2) {
+ return ret + 16;
+ }
+ }
+ return 0;
+}
+
+void Game::lvlObjectType0CallbackBreathBubbles(LvlObject *ptr) {
+
+ AndyLvlObjectData *vf = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+
+ BoundingBox b;
+ b.x1 = b.x2 = ptr->xPos + ptr->posTable[7].x;
+ b.y1 = b.y2 = ptr->yPos + ptr->posTable[7].y;
+
+ const int num = _pwr1_screenTransformLut[_res->_currentScreenResourceNum * 2 + 1];
+ if (!clipBoundingBox(&_screenTransformRects[num], &b)) {
+ ++vf->unk6; // apnea counter/time
+ } else {
+ vf->unk6 = 0;
+ }
+ b.y1 -= 24;
+ if (vf->unk2 == 0 && !clipBoundingBox(&_screenTransformRects[num], &b)) {
+
+ if (addLvlObjectToList3(4)) {
+ _lvlObjectsList3->xPos = b.x1;
+ _lvlObjectsList3->yPos = b.y1 + 24;
+ _lvlObjectsList3->screenNum = ptr->screenNum;
+ _lvlObjectsList3->anim = 0;
+ _lvlObjectsList3->frame = 0;
+ _lvlObjectsList3->flags2 = ptr->flags2 + 1;
+ _lvlObjectsList3->flags0 = (_lvlObjectsList3->flags0 & ~0x19) | 6;
+ _lvlObjectsList3->flags1 &= ~0x20;
+ }
+
+ int currentApneaLevel = vf->unk3;
+ static const int16_t _pwr1_apneaDuration[] = { 625, 937, 1094, 1250 };
+ int newApneaLevel = 0;
+ while (newApneaLevel < 4 && vf->unk6 >= _pwr1_apneaDuration[newApneaLevel]) {
+ ++newApneaLevel;
+ }
+ vf->unk3 = newApneaLevel;
+ static const uint8_t _pwr1_apneaBubble[] = { 22, 20, 18, 16, 14 };
+ vf->unk2 = _pwr1_apneaBubble[newApneaLevel];
+ // play Andy animation when apnea level changes
+ switch (currentApneaLevel) {
+ case 2:
+ if (newApneaLevel == 3) {
+ if (_actionDirectionKeyMaskIndex < 1) {
+ _actionDirectionKeyMaskIndex = 1;
+ _actionDirectionKeyMaskCounter = 0;
+ }
+ }
+ break;
+ case 3:
+ if (newApneaLevel == 4) {
+ if (_actionDirectionKeyMaskIndex < 2) {
+ _actionDirectionKeyMaskIndex = 2;
+ _actionDirectionKeyMaskCounter = 0;
+ }
+ }
+ break;
+ case 4:
+ if (vf->unk6 >= 1250) {
+ if (_actionDirectionKeyMaskIndex < 160) {
+ _actionDirectionKeyMaskIndex = 160;
+ _actionDirectionKeyMaskCounter = 0;
+ }
+ }
+ break;
+ }
+ assert(_lvlObjectsList3);
+ switch (newApneaLevel) {
+ case 0:
+ _lvlObjectsList3->actionKeyMask = 1;
+ break;
+ case 1:
+ _lvlObjectsList3->actionKeyMask = 2;
+ break;
+ case 2:
+ _lvlObjectsList3->actionKeyMask = 4;
+ break;
+ case 3:
+ _lvlObjectsList3->actionKeyMask = 8;
+ break;
+ default:
+ _lvlObjectsList3->actionKeyMask = 16;
+ break;
+ }
+ }
+ if (vf->unk2 != 0) {
+ --vf->unk2;
+ }
+}
+
+void Game::setupSpecialPowers(LvlObject *ptr) {
+ assert(ptr == _andyObject);
+ AndyLvlObjectData *vf = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ LvlObject *vg = vf->shootLvlObject;
+ const uint8_t pos = ptr->flags0 & 0x1F;
+ uint8_t var1 = (ptr->flags0 >> 5) & 7;
+ if (pos == 4) { // release
+ assert(vg->dataPtr);
+ ShootLvlObjectData *va = (ShootLvlObjectData *)getLvlObjectDataPtr(vg, kObjectDataTypeShoot);
+ vg->callbackFuncPtr = &Game::lvlObjectSpecialPowersCallback;
+ uint8_t _cl = (ptr->flags1 >> 4) & 3;
+ if (va->type == 4) {
+ va->counter = 33;
+ switch (var1) {
+ case 0:
+ va->state = ((_cl & 1) != 0) ? 5 : 0;
+ break;
+ case 1:
+ va->state = ((_cl & 1) != 0) ? 3 : 1;
+ break;
+ case 2:
+ va->state = ((_cl & 1) != 0) ? 4 : 2;
+ break;
+ case 3:
+ va->state = ((_cl & 1) != 0) ? 1 : 3;
+ break;
+ case 4:
+ va->state = ((_cl & 1) != 0) ? 2 : 4;
+ break;
+ case 5:
+ va->state = ((_cl & 1) != 0) ? 0 : 5;
+ break;
+ case 6:
+ va->state = 6;
+ break;
+ case 7:
+ va->state = 7;
+ break;
+ }
+ // dx, dy
+ static const uint8_t _byte_43E670[16] = {
+ 0x0F, 0x00, 0xF1, 0xF6, 0xF1, 0x0A, 0x0F, 0xF6, 0x0F, 0x0A, 0xF1, 0x00, 0x00, 0xF1, 0x00, 0x0F
+ };
+ va->dxPos = (int8_t)_byte_43E670[va->state * 2];
+ va->dyPos = (int8_t)_byte_43E670[va->state * 2 + 1];
+ vg->anim = 10;
+ } else {
+ va->counter = 17;
+ switch (var1) {
+ case 0:
+ vg->anim = 13;
+ va->state = ((_cl & 1) != 0) ? 5 : 0;
+ break;
+ case 1:
+ vg->anim = 12;
+ va->state = ((_cl & 1) != 0) ? 3 : 1;
+ _cl ^= 1;
+ break;
+ case 2:
+ vg->anim = 12;
+ va->state = ((_cl & 1) != 0) ? 4 : 2;
+ _cl ^= 3;
+ break;
+ case 3:
+ vg->anim = 12;
+ va->state = ((_cl & 1) != 0) ? 1 : 3;
+ break;
+ case 4:
+ vg->anim = 12;
+ va->state = ((_cl & 1) != 0) ? 2 : 4;
+ _cl ^= 2;
+ break;
+ case 5:
+ vg->anim = 13;
+ va->state = ((_cl & 1) != 0) ? 0 : 5;
+ _cl ^= 1;
+ break;
+ case 6:
+ vg->anim = 11;
+ va->state = 6;
+ break;
+ case 7:
+ vg->anim = 11;
+ va->state = 7;
+ _cl ^= 2;
+ break;
+ }
+ va->dxPos = (int8_t)_specialPowersDxDyTable[va->state * 2];
+ va->dyPos = (int8_t)_specialPowersDxDyTable[va->state * 2 + 1];
+ }
+ vg->frame = 0;
+ vg->flags1 = (vg->flags1 & ~0x30) | ((_cl & 3) << 4);
+ setupLvlObjectBitmap(vg);
+ vg->screenNum = ptr->screenNum;
+ setLvlObjectPosRelativeToObject(vg, 7, ptr, 6);
+ if (_currentLevel == kLvl_isld) {
+ AndyLvlObjectData *vc = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ vg->xPos += vc->dxPos;
+ }
+ vf->shootLvlObject = 0;
+ } else if (pos == 7) { // hold
+ switch (var1) {
+ case 0:
+ if (!vg) {
+ if (!vf->shootLvlObject) {
+ LvlObject *vd = declareLvlObject(8, 3);
+ vf->shootLvlObject = vd;
+ vd->dataPtr = _shootLvlObjectDataNextPtr;
+ if (_shootLvlObjectDataNextPtr) {
+ _shootLvlObjectDataNextPtr = _shootLvlObjectDataNextPtr->nextPtr;
+ memset(vd->dataPtr, 0, sizeof(ShootLvlObjectData));
+ } else {
+ warning("Nothing free in _shootLvlObjectDataNextPtr");
+ }
+ vd->xPos = ptr->xPos;
+ vd->yPos = ptr->yPos;
+ vd->flags1 &= ~0x30;
+ vd->screenNum = ptr->screenNum;
+ vd->anim = 7;
+ vd->frame = 0;
+ vd->bitmapBits = 0;
+ vd->flags2 = (ptr->flags2 & ~0x2000) - 1;
+ prependLvlObjectToList(&_lvlObjectsList0, vd);
+ }
+ AndyLvlObjectData *vc = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ LvlObject *va = vc->shootLvlObject;
+ if (va) {
+ if (!va->dataPtr) {
+ warning("lvlObject %p with NULL dataPtr", va);
+ break;
+ }
+ ShootLvlObjectData *vd = (ShootLvlObjectData *)getLvlObjectDataPtr(va, kObjectDataTypeShoot);
+ vd->type = 0;
+ }
+ } else {
+ if (!vg->dataPtr) {
+ warning("lvlObject %p with NULL dataPtr", vg);
+ break;
+ }
+ ShootLvlObjectData *va = (ShootLvlObjectData *)getLvlObjectDataPtr(vg, kObjectDataTypeShoot);
+ vg->anim = (va->type == 4) ? 14 : 15;
+ updateAndyObject(vg);
+ setLvlObjectPosRelativeToObject(vg, 0, ptr, 6);
+ if (_currentLevel == kLvl_isld) {
+ AndyLvlObjectData *vd = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ assert(vd == vf);
+ vg->xPos += vd->dxPos;
+ }
+ }
+ break;
+ case 2: {
+ if (!vf->shootLvlObject) {
+ LvlObject *vd = declareLvlObject(8, 3);
+ vf->shootLvlObject = vd;
+ vd->dataPtr = _shootLvlObjectDataNextPtr;
+ if (_shootLvlObjectDataNextPtr) {
+ _shootLvlObjectDataNextPtr = _shootLvlObjectDataNextPtr->nextPtr;
+ memset(vd->dataPtr, 0, sizeof(ShootLvlObjectData));
+ } else {
+ warning("Nothing free in _shootLvlObjectDataNextPtr");
+ }
+ vd->xPos = ptr->xPos;
+ vd->yPos = ptr->yPos;
+ vd->flags1 &= ~0x30;
+ vd->screenNum = ptr->screenNum;
+ vd->anim = 7;
+ vd->frame = 0;
+ vd->bitmapBits = 0;
+ vd->flags2 = (ptr->flags2 & ~0x2000) - 1;
+ prependLvlObjectToList(&_lvlObjectsList0, vd);
+ }
+ AndyLvlObjectData *vc = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ assert(vc == vf);
+ LvlObject *va = vc->shootLvlObject;
+ if (va) {
+ if (!va->dataPtr) {
+ warning("lvlObject %p with NULL dataPtr", va);
+ break;
+ }
+ ShootLvlObjectData *vd = (ShootLvlObjectData *)getLvlObjectDataPtr(va, kObjectDataTypeShoot);
+ vd->type = 0;
+ }
+ }
+ break;
+ case 1:
+ if (vg) {
+ updateAndyObject(vg);
+ vg->bitmapBits = 0;
+ }
+ break;
+ case 3: {
+ if (!vf->shootLvlObject) {
+ LvlObject *vd = declareLvlObject(8, 3);
+ vf->shootLvlObject = vd;
+ vd->dataPtr = _shootLvlObjectDataNextPtr;
+ if (_shootLvlObjectDataNextPtr) {
+ _shootLvlObjectDataNextPtr = _shootLvlObjectDataNextPtr->nextPtr;
+ memset(vd->dataPtr, 0, sizeof(ShootLvlObjectData));
+ } else {
+ warning("Nothing free in _shootLvlObjectDataNextPtr");
+ }
+ vd->xPos = ptr->xPos;
+ vd->yPos = ptr->yPos;
+ vd->flags1 &= ~0x30;
+ vd->screenNum = ptr->screenNum;
+ vd->anim = 7;
+ vd->frame = 0;
+ vd->bitmapBits = 0;
+ vd->flags2 = (ptr->flags2 & ~0x2000) - 1;
+ prependLvlObjectToList(&_lvlObjectsList0, vd);
+ }
+ AndyLvlObjectData *vc = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ LvlObject *va = vc->shootLvlObject;
+ if (va) {
+ if (!va->dataPtr) {
+ warning("lvlObject %p with NULL dataPtr", va);
+ break;
+ }
+ ShootLvlObjectData *vd = (ShootLvlObjectData *)getLvlObjectDataPtr(va, kObjectDataTypeShoot);
+ vd->type = 4; // large power
+ }
+ }
+ break;
+ case 4:
+ if (vg) {
+ vf->shootLvlObject = 0;
+ removeLvlObjectFromList(&_lvlObjectsList0, vg);
+ destroyLvlObject(vg);
+ }
+ break;
+ }
+ }
+}
+
+int Game::lvlObjectType0Callback(LvlObject *ptr) {
+ AndyLvlObjectData *vf = 0;
+ if (!_hideAndyObjectFlag) {
+ vf = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ vf->unk4 = ptr->flags0 & 0x1F;
+ vf->unk5 = (ptr->flags0 >> 5) & 7;
+ lvlObjectType0CallbackHelper1();
+ updateAndyObject(ptr);
+ if (vf->unk4 == (ptr->flags0 & 0x1F) && vf->unk4 == 5) {
+ _fallingAndyFlag = true;
+ } else {
+ _fallingAndyFlag = false;
+ _fallingAndyCounter = 0;
+ }
+ vf->boundingBox.x1 = ptr->xPos;
+ vf->boundingBox.x2 = ptr->xPos + ptr->width - 1;
+ vf->boundingBox.y1 = ptr->yPos;
+ vf->boundingBox.y2 = ptr->yPos + ptr->height - 1;
+ if ((ptr->flags0 & 0x300) == 0x100) {
+ const int y = _res->_screensBasePos[_res->_currentScreenResourceNum].v + ptr->posTable[3].y + ptr->yPos;
+ const int x = _res->_screensBasePos[_res->_currentScreenResourceNum].u + ptr->posTable[3].x + ptr->xPos;
+ ptr->xPos += calcScreenMaskDx(x, y, (ptr->flags1 >> 4) & 3);
+ } else if ((ptr->flags1 & 6) == 2) {
+ ptr->yPos += calcScreenMaskDy(ptr->posTable[7].x + ptr->xPos, ptr->posTable[7].y + ptr->yPos, ptr->screenNum);
+ }
+ } else if (ptr->childPtr) {
+ assert(_specialAnimLvlObject);
+ if (_specialAnimLvlObject->screenNum != ptr->screenNum) {
+ setLvlObjectPosRelativeToObject(ptr, 3, _specialAnimLvlObject, 6);
+ }
+ }
+ switch (_currentLevel) {
+ case 0:
+ case 7:
+ if (ptr->spriteNum == 0) {
+ setupPlasmaCannonPoints(ptr);
+ }
+ break;
+ case 9: // test_hod
+ if (ptr->spriteNum == 0) {
+ setupPlasmaCannonPoints(ptr);
+ } else {
+ setupSpecialPowers(ptr);
+ }
+ break;
+ case 2: // pwr1_hod
+ if (!_hideAndyObjectFlag && vf->unk4 == 6) {
+ lvlObjectType0CallbackBreathBubbles(ptr);
+ }
+ // fall through
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ setupSpecialPowers(ptr);
+ break;
+ case 1:
+ case 8:
+ break;
+ }
+ if (!_hideAndyObjectFlag) {
+ assert(vf);
+ vf->unk0 = ptr->actionKeyMask;
+ }
+ return _hideAndyObjectFlag;
+}
+
+int Game::lvlObjectType1Callback(LvlObject *ptr) {
+ if (ptr) {
+ ptr->actionKeyMask = 0;
+ switch (_plasmaCannonDirection - 1) {
+ case 0:
+ ptr->directionKeyMask = 1;
+ break;
+ case 2:
+ ptr->directionKeyMask = 3;
+ break;
+ case 1:
+ ptr->directionKeyMask = 2;
+ break;
+ case 5:
+ ptr->directionKeyMask = 6;
+ break;
+ case 3:
+ ptr->directionKeyMask = 4;
+ break;
+ case 11:
+ ptr->directionKeyMask = 12;
+ break;
+ case 7:
+ ptr->directionKeyMask = 8;
+ break;
+ case 8:
+ ptr->directionKeyMask = 9;
+ break;
+ default:
+ ptr->directionKeyMask = 0;
+ break;
+ }
+ setLvlObjectPosRelativeToPoint(ptr, 0, _plasmaCannonPosX[_plasmaCannonFirstIndex], _plasmaCannonPosY[_plasmaCannonFirstIndex]);
+ updateAndyObject(ptr);
+ ptr->flags2 = merge_bits(ptr->flags2, _andyObject->flags2, 0x18);
+ ptr->flags2 = merge_bits(ptr->flags2, _andyObject->flags2 + 1, 7);
+ }
+ return 0;
+}
+
+int Game::lvlObjectType7Callback(LvlObject *ptr) {
+ if (!ptr->dataPtr) {
+ return 0;
+ }
+ ShootLvlObjectData *dat = (ShootLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeShoot);
+ if ((ptr->flags0 & 0x1F) == 1) {
+ dat->xPosObject = ptr->posTable[7].x + ptr->xPos;
+ dat->yPosObject = ptr->posTable[7].y + ptr->yPos;
+ ptr->xPos += dat->dxPos;
+ ptr->yPos += dat->dyPos;
+ if (!_hideAndyObjectFlag && ptr->screenNum == _andyObject->screenNum && (_andyObject->flags0 & 0x1F) != 0xB && clipLvlObjectsBoundingBox(_andyObject, ptr, 68) && (_mstFlags & 0x80000000) == 0 && (_cheats & kCheatSpectreFireballNoHit) == 0) {
+ dat->unk3 = 0x80;
+ dat->xPosShoot = _clipBoxOffsetX;
+ dat->yPosShoot = _clipBoxOffsetY;
+ setAndySpecialAnimation(0xA3);
+ }
+ if (dat->unk3 != 0x80 && dat->counter != 0) {
+ if (dat->type == 7) {
+ ptr->yPos += calcScreenMaskDy(ptr->xPos + ptr->posTable[7].x, ptr->yPos + ptr->posTable[7].y + 4, ptr->screenNum);
+
+ }
+ const uint8_t ret = lvlObjectCallbackCollideScreen(ptr);
+ if (ret != 0 && (dat->type != 7 || (ret & 1) == 0)) {
+ dat->unk3 = 0x80;
+ }
+ }
+ assert(dat->state < 8);
+ static const uint8_t animData1[16] = {
+ 0x06, 0x0B, 0x05, 0x09, 0x05, 0x09, 0x05, 0x09, 0x05, 0x09, 0x06, 0x0B, 0x04, 0x07, 0x04, 0x07
+ };
+ static const uint8_t animData2[16] = {
+ 0x06, 0x0E, 0x05, 0x0E, 0x05, 0x0E, 0x05, 0x0E, 0x05, 0x0E, 0x06, 0x0E, 0x04, 0x0E, 0x04, 0x0E
+ };
+ const uint8_t *anim = (dat->type >= 7) ? &animData2[dat->state * 2] : &animData1[dat->state * 2];
+ if (dat->counter != 0 && dat->unk3 != 0x80) {
+ if (addLvlObjectToList3(ptr->spriteNum)) {
+ LvlObject *o = _lvlObjectsList3;
+ o->flags0 = ptr->flags0;
+ o->flags1 = ptr->flags1;
+ o->screenNum = ptr->screenNum;
+ o->flags2 = ptr->flags2;
+ o->anim = anim[1];
+ if (_rnd._rndSeed & 1) {
+ ++o->anim;
+ }
+ o->frame = 0;
+ if (dat->xPosShoot >= Video::W) {
+ dat->xPosShoot -= _res->_screensBasePos[ptr->screenNum].u;
+ }
+ if (dat->yPosShoot >= Video::H) {
+ dat->yPosShoot -= _res->_screensBasePos[ptr->screenNum].v;
+ }
+ setupLvlObjectBitmap(o);
+ setLvlObjectPosRelativeToPoint(o, 0, dat->xPosObject, dat->yPosObject);
+ }
+ } else {
+ dat->type = (dat->type < 7) ? 3 : 9;
+ if (dat->counter != 0 && _actionDirectionKeyMaskIndex != 0xA3) {
+ ptr->anim = anim[0];
+ } else {
+ ptr->anim = 3;
+ }
+ ptr->frame = 0;
+ if (dat->xPosShoot >= Video::W) {
+ dat->xPosShoot -= _res->_screensBasePos[ptr->screenNum].u;
+ }
+ if (dat->yPosShoot >= Video::H) {
+ dat->yPosShoot -= _res->_screensBasePos[ptr->screenNum].v;
+ }
+ setupLvlObjectBitmap(ptr);
+ setLvlObjectPosRelativeToPoint(ptr, 0, dat->xPosShoot, dat->yPosShoot);
+ return 0;
+ }
+ } else if ((ptr->flags0 & 0x1F) == 11) {
+ dat->counter = ((ptr->flags0 & 0xE0) == 0x40) ? 0 : 1;
+ }
+ if (dat->counter == 0) {
+ if (ptr->spriteNum == 3) {
+ removeLvlObjectFromList(&_lvlObjectsList0, ptr);
+ } else {
+ removeLvlObjectFromList(&_lvlObjectsList2, ptr);
+ }
+ destroyLvlObject(ptr);
+ } else {
+ --dat->counter;
+ updateAndyObject(ptr);
+ }
+ if (setLvlObjectPosInScreenGrid(ptr, 3) < 0) {
+ dat->counter = 0;
+ }
+ return 0;
+}
+
+int Game::lvlObjectType8Callback(LvlObject *ptr) {
+ if (_mstDisabled) {
+ ptr->actionKeyMask = _andyObject->actionKeyMask;
+ ptr->directionKeyMask = _andyObject->directionKeyMask;
+ if (_andyObject->spriteNum == 2 && _lvlObjectsList0) {
+ warning("lvlObjectType8CallbackHelper unimplemented");
+ // lvlObjectType8CallbackHelper(ptr);
+ }
+ updateAndyObject(ptr);
+ setLvlObjectPosInScreenGrid(ptr, 7);
+ } else {
+ const void *dataPtr = ptr->dataPtr;
+ if (!dataPtr) {
+ ptr->bitmapBits = 0;
+ return 0;
+ }
+ int vb, var4;
+ MonsterObject1 *m = 0;
+ if (dataPtr >= &_monsterObjects1Table[0] && dataPtr < &_monsterObjects1Table[kMaxMonsterObjects1]) {
+ m = (MonsterObject1 *)ptr->dataPtr;
+ vb = 1;
+ var4 = m->monster1Index;
+ if (m->flagsA6 & 2) {
+ assert(ptr == m->o16);
+ m->o16->actionKeyMask = _mstCurrentActionKeyMask;
+ m->o16->directionKeyMask = _andyObject->directionKeyMask;
+ }
+ if (m->flagsA6 & 8) {
+ ptr->bitmapBits = 0;
+ return 0;
+ }
+ } else {
+ assert(dataPtr >= &_monsterObjects2Table[0] && dataPtr < &_monsterObjects2Table[kMaxMonsterObjects2]);
+ MonsterObject2 *mo = (MonsterObject2 *)dataPtr;
+ m = mo->monster1;
+ if (m) {
+ vb = 2;
+ var4 = m->monster1Index;
+ } else {
+ vb = 0;
+ var4 = 4;
+ }
+ m = 0;
+ if (mo->flags24 & 8) {
+ ptr->bitmapBits = 0;
+ return 0;
+ }
+ }
+ LvlObject *o = 0;
+ updateAndyObject(ptr);
+ if (m && m->o20) {
+ o = m->o20;
+ o->actionKeyMask = ptr->actionKeyMask;
+ o->directionKeyMask = ptr->directionKeyMask;
+ updateAndyObject(o);
+ setLvlObjectPosRelativeToObject(ptr, 6, o, 6);
+ addToSpriteList(o);
+ setLvlObjectPosInScreenGrid(o, 7);
+ }
+ setLvlObjectPosInScreenGrid(ptr, 7);
+ if (ptr->screenNum == _currentScreen || ptr->screenNum == _currentLeftScreen || ptr->screenNum == _currentRightScreen || o || (_currentLevel == kLvl_lar2 && ptr->spriteNum == 27) || (_currentLevel == kLvl_isld && ptr->spriteNum == 26)) {
+ if (ptr->currentSound != 0xFFFF) {
+ playSound(ptr->currentSound, ptr, vb, var4);
+ if (o && o->currentSound != 0xFFFF) {
+ playSound(o->currentSound, o, vb, var4);
+ }
+ }
+ }
+ }
+ if ((ptr->flags1 & 6) == 2) {
+ ptr->yPos += calcScreenMaskDy(ptr->xPos + ptr->posTable[5].x, ptr->yPos + ptr->posTable[5].y, ptr->screenNum);
+ }
+ return 0;
+}
+
+int Game::lvlObjectList3Callback(LvlObject *o) {
+ const uint8_t flags = o->flags0 & 0xFF;
+ if ((o->spriteNum <= 7 && (flags & 0x1F) == 0xB) || (o->spriteNum > 7 && flags == 0x1F)) {
+ removeLvlObjectFromList(&_lvlObjectsList3, o);
+ if (o->type == 8) {
+ _res->decLvlSpriteDataRefCounter(o);
+ o->nextPtr = _declaredLvlObjectsNextPtr;
+ --_declaredLvlObjectsListCount;
+ _declaredLvlObjectsNextPtr = o;
+ switch (o->spriteNum) {
+ case 0:
+ case 2:
+ o->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (o->dataPtr) {
+ clearShootLvlObjectData(o);
+ }
+ break;
+ }
+ }
+ if (o->sssObject) {
+ removeSound(o);
+ }
+ o->sssObject = 0;
+ o->bitmapBits = 0;
+ } else {
+ updateAndyObject(o);
+ o->actionKeyMask = 0;
+ o->directionKeyMask = 0;
+ if (o->currentSound != 0xFFFF) {
+ playSound(o->currentSound, o, 0, 3);
+ }
+ if (o->bitmapBits) {
+ addToSpriteList(o);
+ }
+ }
+ return 0;
+}
+
+void Game::lvlObjectSpecialPowersCallbackHelper1(LvlObject *o) {
+ int xPos = o->xPos + o->posTable[3].x;
+ int yPos = o->yPos + o->posTable[3].y;
+ ShootLvlObjectData *dat = (ShootLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ const uint8_t val = dat->unk3;
+ if (val == 0x80) {
+ dat->xPosShoot = xPos;
+ dat->yPosShoot = yPos;
+ }
+ uint8_t screenNum = o->screenNum;
+ if (xPos < 0) {
+ xPos += Video::W;
+ screenNum = _res->_screensGrid[screenNum][kPosLeftScreen];
+ } else if (xPos >= Video::W) {
+ xPos -= Video::W;
+ screenNum = _res->_screensGrid[screenNum][kPosRightScreen];
+ }
+ if (screenNum != kNoScreen) {
+ if (yPos < 0) {
+ yPos += Video::H;
+ screenNum = _res->_screensGrid[screenNum][kPosTopScreen];
+ } else if (yPos >= Video::H) {
+ yPos -= Video::H;
+ screenNum = _res->_screensGrid[screenNum][kPosBottomScreen];
+ }
+ }
+ int8_t dy = 255 - (yPos & 7);
+ if (screenNum != kNoScreen) {
+ const int xLevelPos = _res->_screensBasePos[screenNum].u + xPos;
+ const int yLevelPos = _res->_screensBasePos[screenNum].v + yPos + 8;
+ int offset = screenMaskOffset(xLevelPos, yLevelPos);
+ if (_screenMaskBuffer[offset] & 1) {
+ dy = -8;
+ goto set_dat03;
+ } else if (_screenMaskBuffer[offset + 512] & 1) {
+ const int vg = screenGridOffset(xPos, yPos);
+ int i = _res->findScreenGridIndex(screenNum);
+ if (i < 0) {
+ goto set_dat03;
+ }
+ const uint8_t *p = _res->_resLevelData0x470CTablePtrData + (xPos & 7);
+ dy += (int8_t)p[_screenPosTable[i][vg] * 8];
+ goto set_dat03;
+ } else if (_screenMaskBuffer[offset - 1024] & 1) {
+ dy -= 16;
+ goto set_dat03;
+ } else {
+ dy = val;
+ if (val < 0x18) {
+ dat->unk3 = val + 4;
+ }
+ goto set_dxpos;
+ }
+ }
+set_dat03:
+ if (val == 0x18) {
+ dat->type = 6;
+ } else {
+ dat->unk3 = 8;
+ }
+set_dxpos:
+ if (dat->type != 6 && dat->unk3 == 0x80) {
+ dat->yPosShoot += dy;
+ setLvlObjectPosRelativeToPoint(o, 3, dat->xPosShoot, dat->yPosShoot);
+ } else {
+ dat->unk3 = 0x80;
+ dat->dxPos = 0;
+ dat->dyPos = 0;
+ }
+}
+
+uint8_t Game::lvlObjectCallbackCollideScreen(LvlObject *o) {
+ uint8_t ret = 0;
+ uint8_t screenNum = o->screenNum;
+ uint8_t var30 = 0;
+
+ int yPos = o->yPos;
+ if ((o->flags0 & 0xE0) != 0x20) {
+ yPos += o->posTable[6].y;
+ } else {
+ yPos += o->posTable[3].y;
+ }
+ int xPos = o->xPos + o->posTable[3].x;
+
+ int var1C = 0;
+ int var20 = 0;
+ if (xPos < 0) {
+ xPos += Video::W;
+ var20 = -Video::W;
+ screenNum = _res->_screensGrid[screenNum][kPosLeftScreen];
+ } else if (xPos >= Video::W) {
+ xPos -= Video::W;
+ var20 = Video::W;
+ screenNum = _res->_screensGrid[screenNum][kPosRightScreen];
+ }
+ if (screenNum != kNoScreen) {
+ if (yPos < 0) {
+ yPos += Video::H;
+ var1C = -Video::H;
+ screenNum = _res->_screensGrid[screenNum][kPosTopScreen];
+ } else if (yPos >= Video::H) {
+ yPos -= Video::H;
+ var1C = Video::H;
+ screenNum = _res->_screensGrid[screenNum][kPosBottomScreen];
+ }
+ }
+ if (screenNum == kNoScreen) {
+ return 0;
+ }
+ ShootLvlObjectData *dat = (ShootLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ static const uint8_t data1[] = {
+ 0xFF, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x17, 0x00, 0x08, 0x08, 0x00, 0x00, 0xF8, 0xF8, 0xF0, 0xF0,
+ 0x08, 0xF8, 0x00, 0xFF, 0xF8, 0x07, 0xF0, 0x0F, 0xFF, 0x08, 0x07, 0x00, 0x0F, 0xF8, 0x17, 0xF0,
+ 0xFF, 0xF8, 0x07, 0xFF, 0x0F, 0x07, 0x17, 0x0F, 0x08, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xF0, 0x00,
+ 0x00, 0x08, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0xFF, 0x00, 0x07, 0x00, 0x0F
+ };
+ static const uint8_t data2[] = {
+ 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x08, 0xF8, 0x08,
+ 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0x08, 0xF8, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xF8, 0xF8, 0xF8
+ };
+ static const int offsets1[] = {
+ 0, 1, 1, 1, 0, -513, -513, -513, 0, 511, 511, 511, 0, -511, -511, -511, 0, 513, 513, 513, 0, -1, -1, -1, 0, -512, -512, -512, 0, 512, 512, 512
+ };
+ static const int offsets2[] = {
+ 0, 1, 512, -1, 0, -1, 512, 1, 0, 1, -512, -1, 0, -1, -512, 1
+ };
+ uint8_t _bl, _cl = dat->type;
+ const uint8_t *var10;
+ const int *vg;
+ if (_cl == 4) {
+ _bl = _cl;
+ const uint8_t num = (o->flags1 >> 4) & 3;
+ var10 = data2 + num * 8;
+ vg = offsets2 + num * 4;
+ } else {
+ const uint8_t num = dat->state;
+ var10 = data1 + num * 8;
+ vg = offsets1 + num * 4;
+ _bl = (_cl != 2) ? 4 : 2;
+ }
+ int num;
+ int var2E = _bl;
+ int vd = _res->_screensBasePos[screenNum].v + yPos;
+ int vf = _res->_screensBasePos[screenNum].u + xPos;
+ vd = screenMaskOffset(vf, vd);
+ int var4 = screenGridOffset(xPos, yPos);
+ if (_cl >= 4) {
+ num = 0;
+ vd += vg[num];
+ int ve = vd;
+ uint8_t _cl, _al = 0;
+ while (1) {
+ _cl = _screenMaskBuffer[vd];
+ if ((_cl & 6) != 0) {
+ ret = _al = 1;
+ break;
+ }
+ ++num;
+ if (num >= var2E) {
+ _al = ret;
+ break;
+ }
+ vd += vg[num];
+ }
+
+ _bl = dat->state; // var18
+ if (_bl != 6 && _bl != 1 && _bl != 3) {
+ vd = ve;
+ ++vg;
+ num = 0;
+ while (1) {
+ var30 = _screenMaskBuffer[vd];
+ if ((var30 & 1) != 0) {
+ _al = 1;
+ break;
+ }
+ ++num;
+ if (num >= var2E) {
+ _al = ret;
+ break;
+ }
+ vd += *vg++;
+ }
+ }
+ if (_cl & 6) {
+ var30 = _cl;
+ }
+ if (_al == 0) {
+ return 0;
+ }
+
+ } else {
+ _bl = dat->state;
+ const uint8_t mask = (_bl == 6 || _bl == 1 || _bl == 3) ? 6 : 7;
+ num = 0;
+ vd += *vg++;
+ while (1) {
+ var30 = _screenMaskBuffer[vd];
+ if ((var30 & mask) != 0) {
+ break;
+ }
+ ++num;
+ if (num >= var2E) {
+ return ret;
+ }
+ vd += *vg++;
+ }
+ }
+ dat->xPosShoot = (int8_t)var10[num * 2 ] + var20 + (xPos & ~7);
+ dat->yPosShoot = (int8_t)var10[num * 2 + 1] + var1C + (yPos & ~7);
+ _bl = dat->state;
+ if (_bl != 2 && _bl != 4 && _bl != 7) {
+ return var30;
+ }
+ num = _res->findScreenGridIndex(screenNum);
+ if (num < 0) {
+ dat->yPosShoot += 4;
+ return var30;
+ }
+ const int vc = (o->posTable[3].x + o->xPos) & 7;
+ const uint8_t *p = _res->_resLevelData0x470CTablePtrData + vc;
+ dat->yPosShoot += (int8_t)p[_screenPosTable[num][var4] * 8];
+ return var30;
+}
+
+int Game::lvlObjectSpecialPowersCallback(LvlObject *o) {
+ if (!o->dataPtr) {
+ return 0;
+ }
+ ShootLvlObjectData *dat = (ShootLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ const uint16_t fl = o->flags0 & 0x1F;
+ if (fl == 1) {
+ if (dat->unk3 != 0x80 && dat->counter != 0) {
+ uint8_t _al = lvlObjectCallbackCollideScreen(o);
+ if (_al != 0) {
+ if (dat->type == 4 && (_al & 1) != 0 && (dat->state == 4 || dat->state == 2)) {
+ dat->type = 5;
+ _al -= 4;
+ dat->state = (_al != 0) ? 5 : 0;
+ } else {
+ dat->unk3 = 0x80;
+ }
+ }
+ }
+ if (dat->type == 5) {
+ dat->dyPos = 0;
+ if (dat->unk3 != 0x80) {
+ lvlObjectSpecialPowersCallbackHelper1(o);
+ }
+ }
+ static const uint8_t animData1[] = {
+ 0x04, 0x14, 0x03, 0x16, 0x03, 0x16, 0x03, 0x16, 0x03, 0x16, 0x04, 0x14, 0x02, 0x18, 0x02, 0x18
+ };
+ static const uint8_t animData2[] = {
+ 0x04, 0x08, 0x03, 0x08, 0x03, 0x08, 0x03, 0x08, 0x03, 0x08, 0x04, 0x08, 0x02, 0x08, 0x02, 0x08
+ };
+ const uint8_t *p = (dat->type >= 4) ? &animData2[dat->state * 2] : &animData1[dat->state * 2];
+ if (dat->unk3 != 0x80 && dat->counter != 0) {
+ if (addLvlObjectToList3(o->spriteNum)) {
+ LvlObject *ptr = _lvlObjectsList3;
+ ptr->flags0 = o->flags0;
+ ptr->flags1 = o->flags1;
+ ptr->flags2 = o->flags2;
+ ptr->screenNum = o->screenNum;
+ ptr->anim = p[1];
+ if (_rnd._rndSeed & 1) {
+ ++ptr->anim;
+ }
+ ptr->frame = 0;
+ setupLvlObjectBitmap(ptr);
+ setLvlObjectPosRelativeToObject(ptr, 0, o, 7);
+ o->xPos += dat->dxPos;
+ o->yPos += dat->dyPos;
+ }
+ } else {
+ o->anim = p[0];
+ if (dat->xPosShoot >= Video::W) {
+ dat->xPosShoot -= _res->_screensBasePos[o->screenNum].u;
+ }
+ if (dat->yPosShoot >= Video::H) {
+ dat->yPosShoot -= _res->_screensBasePos[o->screenNum].v;
+ }
+ if (dat->o && (dat->o->actionKeyMask & 7) == 7) {
+ const uint8_t num = dat->o->spriteNum;
+ if ((num >= 8 && num <= 16) || num == 28) {
+ o->anim = 16;
+ }
+ } else {
+ if (dat->counter == 0) {
+ o->anim = 16;
+ }
+ }
+ if (dat->type >= 4) {
+ dat->type = 6;
+ if (dat->dxPos <= 0) {
+ dat->xPosShoot += 8;
+ }
+ if (dat->dyPos <= 0) {
+ dat->yPosShoot += 8;
+ }
+ } else {
+ dat->type = 1;
+ }
+ dat->dxPos = 0;
+ dat->dyPos = 0;
+ o->frame = 0;
+ setupLvlObjectBitmap(o);
+ setLvlObjectPosRelativeToPoint(o, 0, dat->xPosShoot, dat->yPosShoot);
+ return 0;
+ }
+ } else if (fl == 11) {
+ if ((o->flags0 & 0xE0) == 0x40) {
+ dat->counter = 0;
+ } else {
+ dat->counter = 1;
+ }
+ }
+ if (dat->counter == 0) {
+ if (o->spriteNum == 3) {
+ removeLvlObjectFromList(&_lvlObjectsList0, o);
+ } else {
+ removeLvlObjectFromList(&_lvlObjectsList2, o);
+ }
+ destroyLvlObject(o);
+ } else {
+ --dat->counter;
+ updateAndyObject(o);
+ }
+ if (setLvlObjectPosInScreenGrid(o, 3) < 0) {
+ dat->counter = 0;
+ }
+ return 0;
+}
+
+void Game::lvlObjectTypeCallback(LvlObject *o) {
+ switch (o->spriteNum) {
+ case 0:
+ case 2:
+ o->callbackFuncPtr = &Game::lvlObjectType0Callback;
+ break;
+ case 1: // plasma cannon explosion
+ o->callbackFuncPtr = &Game::lvlObjectType1Callback;
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ o->callbackFuncPtr = 0;
+ break;
+ case 7: // flying spectre fireball
+ o->callbackFuncPtr = &Game::lvlObjectType7Callback;
+ break;
+ case 8: // lizard
+ case 9: // spider
+ case 10: // flying spectre
+ case 11: // spectre
+ case 12: // spectre
+ case 13: // spectre
+ case 14:
+ case 15:
+ case 16: // double spectre
+ case 17:
+ case 18:
+ case 19:
+ case 20:
+ case 21: // lava
+ case 22:
+ case 23:
+ case 24: // rope
+ case 25: // swamp snake
+ case 26:
+ case 27: // green fire-fly
+ case 28:
+ case 29:
+ o->callbackFuncPtr = &Game::lvlObjectType8Callback;
+ break;
+ default:
+ warning("lvlObjectTypeCallback unhandled case %d", o->spriteNum);
+ break;
+ }
+}
+
+LvlObject *Game::addLvlObject(int type, int x, int y, int screen, int num, int o_anim, int o_flags1, int o_flags2, int actionKeyMask, int directionKeyMask) {
+ LvlObject *ptr = 0;
+ switch (type) {
+ case 0:
+ addLvlObjectToList1(8, num);
+ ptr = _lvlObjectsList1;
+ break;
+ case 1:
+ if (screen != _currentScreen && screen != _currentLeftScreen && screen != _currentRightScreen) {
+ return 0;
+ }
+ ptr = findLvlObjectType2(num, screen);
+ break;
+ case 2:
+ addLvlObjectToList3(num);
+ ptr = _lvlObjectsList3;
+ break;
+ }
+ if (!ptr) {
+ return 0;
+ }
+ ptr->anim = o_anim;
+ ptr->flags2 = o_flags2;
+ ptr->frame = 0;
+ ptr->flags1 = ((o_flags1 & 3) << 4) | (ptr->flags1 & ~0x30);
+ ptr->actionKeyMask = actionKeyMask;
+ ptr->directionKeyMask = directionKeyMask;
+ setupLvlObjectBitmap(ptr);
+ ptr->xPos = x - ptr->posTable[7].x;
+ ptr->yPos = y - ptr->posTable[7].y;
+ ptr->screenNum = screen;
+ return ptr;
+}
+
+int Game::setLvlObjectPosInScreenGrid(LvlObject *o, int pos) {
+ int ret = 0;
+ if (o->screenNum < _res->_lvlHdr.screensCount) {
+ int xPrev = o->xPos;
+ int x = o->xPos + o->posTable[pos].x;
+ int yPrev = o->yPos;
+ int y = o->yPos + o->posTable[pos].y;
+ int numPrev = o->screenNum;
+ int screenNum = o->screenNum;
+ if (x < 0) {
+ o->screenNum = _res->_screensGrid[screenNum][kPosLeftScreen];
+ o->xPos = xPrev + Video::W;
+ } else if (x >= Video::W) {
+ o->screenNum = _res->_screensGrid[screenNum][kPosRightScreen];
+ o->xPos = xPrev - Video::W;
+ }
+ screenNum = o->screenNum;
+ if (screenNum != kNoScreen) {
+ if (y < 0) {
+ o->screenNum = _res->_screensGrid[screenNum][kPosTopScreen];
+ o->yPos = yPrev + Video::H;
+ } else if (y >= Video::H) {
+ o->screenNum = _res->_screensGrid[screenNum][kPosBottomScreen];
+ o->yPos = yPrev - Video::H;
+ }
+ }
+ screenNum = o->screenNum;
+ if (screenNum == kNoScreen) {
+ o->xPos = xPrev;
+ o->yPos = yPrev;
+ o->screenNum = numPrev;
+ ret = -1;
+ } else if (screenNum != numPrev) {
+ ret = 1;
+ }
+ }
+ return ret;
+}
+
+LvlObject *Game::declareLvlObject(uint8_t type, uint8_t num) {
+ if (type != 8 || _res->_resLevelData0x2988PtrTable[num] != 0) {
+ if (_declaredLvlObjectsListCount < kMaxLvlObjects) {
+ assert(_declaredLvlObjectsNextPtr);
+ LvlObject *ptr = _declaredLvlObjectsNextPtr;
+ _declaredLvlObjectsNextPtr = _declaredLvlObjectsNextPtr->nextPtr;
+ assert(ptr);
+ ++_declaredLvlObjectsListCount;
+ ptr->spriteNum = num;
+ ptr->type = type;
+ if (type == 8) {
+ _res->incLvlSpriteDataRefCounter(ptr);
+ lvlObjectTypeCallback(ptr);
+ }
+ ptr->currentSprite = 0;
+ ptr->sssObject = 0;
+ ptr->nextPtr = 0;
+ ptr->bitmapBits = 0;
+ return ptr;
+ }
+ }
+ return 0;
+}
+
+void Game::clearDeclaredLvlObjectsList() {
+ memset(_declaredLvlObjectsList, 0, sizeof(_declaredLvlObjectsList));
+ for (int i = 0; i < kMaxLvlObjects - 1; ++i) {
+ _declaredLvlObjectsList[i].nextPtr = &_declaredLvlObjectsList[i + 1];
+ }
+ _declaredLvlObjectsList[kMaxLvlObjects - 1].nextPtr = 0;
+ _declaredLvlObjectsNextPtr = &_declaredLvlObjectsList[0];
+ _declaredLvlObjectsListCount = 0;
+}
+
+void Game::initLvlObjects() {
+ for (int i = 0; i < _res->_lvlHdr.screensCount; ++i) {
+ _screenLvlObjectsList[i] = 0;
+ }
+ LvlObject *prevLvlObj = 0;
+ for (int i = 0; i < _res->_lvlHdr.staticLvlObjectsCount; ++i) {
+ LvlObject *ptr = &_res->_resLvlScreenObjectDataTable[i];
+ int index = ptr->screenNum;
+ ptr->nextPtr = _screenLvlObjectsList[index];
+ _screenLvlObjectsList[index] = ptr;
+ switch (ptr->type) {
+ case 0:
+ assert(_animBackgroundDataCount < kMaxBackgroundAnims);
+ ptr->dataPtr = &_animBackgroundDataTable[_animBackgroundDataCount++];
+ memset(ptr->dataPtr, 0, sizeof(AnimBackgroundData));
+ break;
+ case 1:
+ if (ptr->dataPtr) {
+ warning("Trying to free _resLvlScreenBackgroundDataTable.backgroundSoundTable (i=%d index=%d)", i, index);
+ }
+ ptr->xPos = 0;
+ ptr->yPos = 0;
+ break;
+ case 2:
+ if (prevLvlObj == &_res->_dummyObject) {
+ prevLvlObj = 0;
+ ptr->childPtr = ptr->nextPtr;
+ } else {
+ prevLvlObj = ptr->childPtr;
+ }
+ break;
+ }
+ }
+ for (int i = _res->_lvlHdr.staticLvlObjectsCount; i < _res->_lvlHdr.staticLvlObjectsCount + _res->_lvlHdr.otherLvlObjectsCount; ++i) {
+ LvlObject *ptr = &_res->_resLvlScreenObjectDataTable[i];
+ lvlObjectTypeInit(ptr);
+ }
+}
+
+void Game::setLvlObjectSprite(LvlObject *ptr, uint8_t type, uint8_t num) {
+ if (ptr->type == 8) {
+ _res->decLvlSpriteDataRefCounter(ptr);
+ ptr->spriteNum = num;
+ ptr->type = type;
+ _res->incLvlSpriteDataRefCounter(ptr);
+ }
+}
+
+LvlObject *Game::findLvlObject(uint8_t type, uint8_t spriteNum, int screenNum) {
+ LvlObject *ptr = _screenLvlObjectsList[screenNum];
+ while (ptr) {
+ if (ptr->type == type && ptr->spriteNum == spriteNum) {
+ break;
+ }
+ ptr = ptr->nextPtr;
+ }
+ return ptr;
+}
+
+LvlObject *Game::findLvlObject2(uint8_t type, uint8_t dataNum, int screenNum) {
+ LvlObject *ptr = _screenLvlObjectsList[screenNum];
+ while (ptr) {
+ if (ptr->type == type && ptr->dataNum == dataNum) {
+ break;
+ }
+ ptr = ptr->nextPtr;
+ }
+ return ptr;
+}
+
+LvlObject *Game::findLvlObjectType2(int spriteNum, int screenNum) {
+ LvlObject *ptr = _screenLvlObjectsList[screenNum];
+ while (ptr) {
+ if (ptr->type == 2 && ptr->spriteNum == spriteNum && !ptr->dataPtr) {
+ break;
+ }
+ ptr = ptr->nextPtr;
+ }
+ return ptr;
+}
+
+LvlObject *Game::findLvlObjectBoundingBox(BoundingBox *box) {
+ LvlObject *ptr = _lvlObjectsList0;
+ while (ptr) {
+ if ((ptr->flags0 & 0x1F) != 0xB || (ptr->flags0 & 0xE0) != 0x40) {
+ BoundingBox b;
+ b.x1 = ptr->xPos;
+ b.x2 = ptr->xPos + ptr->width - 1;
+ b.y1 = ptr->yPos;
+ b.y2 = ptr->yPos + ptr->height - 1;
+ const uint8_t *coords = _res->getLvlSpriteCoordPtr(ptr->levelData0x2988, ptr->currentSprite);
+ if (updateBoundingBoxClippingOffset(box, &b, coords, (ptr->flags1 >> 4) & 3)) {
+ return ptr;
+ }
+ }
+ ptr = ptr->nextPtr;
+ }
+ return 0;
+}
+
+void Game::setLavaAndyAnimation(int yPos) {
+ const uint8_t flags = (_andyObject->flags0) & 0x1F;
+ if ((_cheats & kCheatWalkOnLava) == 0 && !_hideAndyObjectFlag) {
+ if ((_mstFlags & 0x80000000) == 0) {
+ uint8_t mask = 0;
+ const int y = _andyObject->yPos;
+ if (_andyObject->posTable[5].y + y >= yPos || _andyObject->posTable[4].y + y >= yPos) {
+ mask = 0xA3;
+ }
+ if (flags != 2 && _andyObject->posTable[7].y + y >= yPos) {
+ mask = 0xA3;
+ }
+ if (mask != 0 && mask > _actionDirectionKeyMaskIndex) {
+ _actionDirectionKeyMaskIndex = mask;
+ _actionDirectionKeyMaskCounter = 0;
+ }
+ } else if (flags == 0xB) {
+ _mstFlags &= 0x7FFFFFFF;
+ }
+ }
+}
+
+void Game::updateGatesLar(LvlObject *o, uint8_t *p, int num) {
+ p += num * 4;
+ uint32_t mask = 1 << num;
+ uint8_t _cl = p[0] & 15;
+ if (_cl >= 3) {
+ if ((o->flags0 & 0x1F) == 0) {
+ if (p[3] == 0) {
+ if (_cl == 3) {
+ p[0] = (p[0] & ~0xB) | 4;
+ p[3] = p[1];
+ o->directionKeyMask = 1; // up (open)
+ o->actionKeyMask = 0;
+ } else {
+ p[0] = (p[0] & ~0xC) | 3;
+ p[3] = p[2];
+ o->directionKeyMask = 4; // down (close)
+ o->actionKeyMask = 0;
+ }
+ } else {
+ --p[3];
+ o->directionKeyMask = 0;
+ o->actionKeyMask = 0;
+ }
+ }
+ } else {
+ if ((p[1] | p[2]) != 0) {
+ uint8_t _dl = p[0] >> 4;
+ if (_cl != _dl) {
+ uint8_t _al = (p[0] & 0xF0) | _dl;
+ p[0] = _al;
+ if (_al & 0xF0) {
+ p[3] = p[1];
+ } else {
+ p[3] = p[2];
+ }
+ }
+ if (p[3] == 0) {
+ if (p[0] & 0xF) {
+ o->directionKeyMask = 1; // up (open)
+ _mstAndyVarMask &= ~mask;
+ } else {
+ o->directionKeyMask = 4; // down (close)
+ _mstAndyVarMask |= mask;
+ }
+ _mstLevelGatesMask |= mask;
+ } else {
+ --p[3];
+ o->actionKeyMask = 0;
+ o->directionKeyMask = 0;
+ }
+ } else {
+ uint8_t _dl = p[0] >> 4;
+ if (_cl != _dl) {
+ if (p[3] != 0) {
+ --p[3];
+ } else {
+ uint8_t _al = (p[0] & 0xF0) | _dl;
+ p[0] = _al;
+ if (_al & 0xF0) {
+ o->directionKeyMask = 1; // up (open)
+ _mstAndyVarMask &= ~mask;
+ } else {
+ o->directionKeyMask = 4; // down (close)
+ _mstAndyVarMask |= mask;
+ }
+ _mstLevelGatesMask |= mask;
+ if (o->screenNum != _currentScreen && o->screenNum != _currentLeftScreen && o->screenNum != _currentRightScreen) {
+ o->actionKeyMask = 1;
+ } else {
+ o->actionKeyMask = 0;
+ }
+ }
+ }
+ }
+ }
+ int y1 = o->yPos + o->posTable[1].y;
+ int h1 = o->posTable[1].y - o->posTable[2].y - 7;
+ int x1 = o->xPos + o->posTable[1].x;
+ if (x1 < 0) {
+ x1 = 0;
+ }
+ if (y1 < 0) {
+ y1 = 0;
+ }
+ uint32_t offset = screenMaskOffset(_res->_screensBasePos[o->screenNum].u + x1, _res->_screensBasePos[o->screenNum].v + y1);
+ if (h1 < 0) {
+ h1 = -h1;
+ for (int i = 0; i < h1 / 8; ++i) {
+ memset(_screenMaskBuffer + offset, 0, 4);
+ offset += 512;
+ }
+ } else {
+ for (int i = 0; i < h1 / 8; ++i) {
+ memset(_screenMaskBuffer + offset, 2, 4);
+ offset += 512;
+ }
+ }
+ if (o->screenNum == _currentScreen || (o->screenNum == _currentRightScreen && _res->_resLevelData0x2B88SizeTable[_currentRightScreen] != 0) || (o->screenNum == _currentLeftScreen && _res->_resLevelData0x2B88SizeTable[_currentLeftScreen] != 0)) {
+ if (o->levelData0x2988) {
+ updateAndyObject(o);
+ }
+ }
+ int y2 = o->yPos + o->posTable[1].y;
+ int h2 = o->posTable[2].y - o->posTable[1].y + 7;
+ int x2 = o->xPos + o->posTable[1].x;
+ if (x2 < 0) {
+ x2 = 0;
+ }
+ if (y2 < 0) {
+ y2 = 0;
+ }
+ offset = screenMaskOffset(_res->_screensBasePos[o->screenNum].u + x2, _res->_screensBasePos[o->screenNum].v + y2);
+ if (h2 < 0) {
+ h2 = -h2;
+ for (int i = 0; i < h2 / 8; ++i) {
+ memset(_screenMaskBuffer + offset, 0, 4);
+ offset += 512;
+ }
+ } else {
+ for (int i = 0; i < h2 / 8; ++i) {
+ memset(_screenMaskBuffer + offset, 2, 4);
+ offset += 512;
+ }
+ }
+ // gate closing on Andy
+ if ((_cheats & kCheatGateNoCrush) == 0 && o->screenNum == _res->_currentScreenResourceNum && o->directionKeyMask == 4) {
+ if ((o->flags0 & 0x1F) == 1 && (o->flags0 & 0xE0) == 0x40) {
+ if (!_hideAndyObjectFlag && (_mstFlags & 0x80000000) == 0) {
+ if (clipLvlObjectsBoundingBox(_andyObject, o, 132)) {
+ setAndySpecialAnimation(0xA1);
+ }
+ }
+ }
+ }
+}
+
+void Game::updateSwitchesLar(int count, uint8_t *switchesData, BoundingBox *switchesBoundingBox, uint8_t *gatesData) {
+ for (int i = 0; i < count; ++i) {
+ switchesData[i * 4 + 1] &= ~0x40;
+ }
+ for (int i = 0; i < count; ++i) {
+ if (_andyObject->screenNum == switchesData[i * 4]) {
+ if ((switchesData[i * 4 + 1] & 0x10) == 0x10) { // can be actioned by a spectre
+ updateSwitchesLar_checkSpectre(i, &switchesData[i * 4], &switchesBoundingBox[i], gatesData);
+ }
+ AndyLvlObjectData *data = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ updateSwitchesLar_checkAndy(i, &switchesData[i * 4], &data->boundingBox, &switchesBoundingBox[i], gatesData);
+ }
+ }
+ for (int i = 0; i < count; ++i) {
+ const uint8_t _al = switchesData[i * 4 + 1];
+ const uint8_t _dl = _al & 0x40;
+ if (_dl == 0 && (_al & 0x80) == 0) {
+ switchesData[i * 4 + 1] |= 0x80;
+ } else if (_dl != 0 && (_al & 4) != 0) {
+ switchesData[i * 4 + 1] &= ~1;
+ }
+ }
+}
+
+void Game::updateSwitchesLar_checkSpectre(int num, uint8_t *p, BoundingBox *r, uint8_t *gatesData) {
+ bool found = false;
+ for (LvlObject *o = _lvlObjectsList1; o && !found; o = o->nextPtr) {
+ if (o->screenNum != p[0]) {
+ continue;
+ }
+ if (!((o->spriteNum >= 11 && o->spriteNum <= 13) || o->spriteNum == 16)) {
+ // not a spectre
+ continue;
+ }
+ BoundingBox b;
+ b.x1 = o->xPos;
+ b.y1 = o->yPos;
+ b.x2 = b.x1 + o->width - 1;
+ b.y2 = b.y1 + o->height - 1;
+ uint8_t *vf;
+ if ((p[1] & 0x40) == 0 && clipBoundingBox(r, &b)) {
+ found = true;
+ if ((p[2] & 0x80) == 0 && !updateSwitchesLar_toggle(true, p[2], p[0], num, (p[1] >> 5) & 1, r)) {
+ continue;
+ }
+ p[1] |= 0x40;
+ if ((p[1] & 0x8) != 0) {
+ continue;
+ }
+ vf = &gatesData[p[3] * 4];
+ uint8_t _al = (p[1] >> 1) & 1;
+ uint8_t _bl = (vf[0] >> 4);
+ if (_bl != _al) {
+ _bl = (_al << 4) | (vf[0] & 15);
+ vf[0] = _bl;
+ const uint8_t _cl = (p[1] >> 5) & 1;
+ if (_cl == 1 && _al == _cl) {
+ vf[3] = 4;
+ }
+ }
+ } else {
+ if ((p[1] & 0xC) == 0 && (p[1] & 0x80) != 0) {
+ vf = &gatesData[p[3] * 4];
+ uint8_t _al = ((~p[1]) >> 1) & 1;
+ uint8_t _bl = (vf[0] >> 4);
+ if (_bl != _al) {
+ _bl = (_al << 4) | (vf[0] & 15);
+ vf[0] = _bl;
+ const uint8_t _cl = (p[1] >> 5) & 1;
+ if (_cl == 1 && _al == _cl) {
+ vf[3] = 4;
+ }
+ }
+ }
+ }
+ }
+}
+
+int Game::updateSwitchesLar_checkAndy(int num, uint8_t *p, BoundingBox *b1, BoundingBox *b2, uint8_t *gatesData) {
+ int ret = 0;
+ //const uint8_t flags = _andyObject->flags0 & 0x1F;
+ if ((p[1] & 0x40) == 0 && (ret = clipBoundingBox(b1, b2)) != 0) {
+ if ((p[1] & 1) == 0) { // switch already actioned
+ return ret;
+ }
+ const int flag = (p[1] >> 5) & 1;
+ uint8_t _al = clipAndyLvlObjectLar(b1, b2, flag);
+ _al = updateSwitchesLar_toggle(_al, p[2], p[0], num, flag, b2);
+ _al = ((_al & 1) << 6) | (p[1] & ~0x40);
+ p[1] = _al;
+ if ((_al & 0x40) == 0) {
+ return ret;
+ }
+ ret = 1;
+ if ((_al & 8) != 0) {
+ const int mask = (_al & 2) != 0 ? p[3] : -p[3];
+ updateGateMaskLar(mask);
+ return ret;
+ }
+ const uint8_t _cl = (_al >> 5) & 1;
+ _al = (_al >> 1) & 1;
+ p = &gatesData[p[3] * 4];
+ uint8_t _bl = p[0] >> 4;
+ if (_bl != _al) {
+ _bl = (_al << 4) | (p[0] & 0xF);
+ p[0] = _bl;
+ if (_cl == 1 && _al == _cl) {
+ p[3] = 4;
+ }
+ }
+ return ret;
+ }
+ uint8_t _al = p[1];
+ if ((_al & 0xC) == 0 && (_al & 0x80) != 0) {
+ const uint8_t _cl = (_al >> 5) & 1;
+ _al = ((~_al) >> 1) & 1;
+ p = &gatesData[p[3] * 4];
+ uint8_t _bl = p[0] >> 4;
+ if (_bl != _al) {
+ _bl = (_al << 4) | (p[0] & 0xF);
+ p[0] = _bl;
+ if (_cl == 1 && _al == _cl) {
+ p[3] = 4;
+ }
+ }
+ }
+ return ret;
+}
+
+int Game::updateSwitchesLar_toggle(bool flag, uint8_t dataNum, int screenNum, int switchNum, int anim, const BoundingBox *box) {
+ uint8_t _al = (_andyObject->flags0 >> 5) & 7;
+ uint8_t _cl = (_andyObject->flags0 & 0x1F);
+ int ret = 0; // _bl
+ if ((dataNum & 0x80) == 0) {
+ const int dy = box->y2 - box->y1;
+ const int dx = box->x2 - box->x1;
+ if (dx >= dy) {
+ ret = 1;
+ } else {
+ const uint8_t _dl = ((_andyObject->flags1) >> 4) & 3;
+ if (anim != _dl) {
+ return 0;
+ }
+ if (_cl == 3 || _cl == 5 || _cl == 2 || _al == 2 || _cl == 0 || _al == 7) {
+ ret = 1;
+ } else {
+ setAndySpecialAnimation(3);
+ }
+ }
+ } else {
+ ret = 1;
+ }
+ if (ret) {
+ if (!flag) {
+ ret = (_andyObject->anim == 224) ? 1 : 0;
+ }
+ if (ret) {
+ LvlObject *o = findLvlObject2(0, dataNum, screenNum);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ }
+ }
+ return ret;
+}
+
+void Game::dumpSwitchesLar(int switchesCount, const uint8_t *switchesData, const BoundingBox *switchesBoundingBox, int gatesCount, const uint8_t *gatesData) {
+ fprintf(stdout, "_mstAndyVarMask 0x%x _mstLevelGatesMask 0x%x\n", _mstAndyVarMask, _mstLevelGatesMask);
+ for (int i = 0; i < gatesCount; ++i) {
+ const uint8_t *p = gatesData + i * 4;
+ fprintf(stdout, "gate %2d: state 0x%02x\n", i, p[0]);
+ }
+ for (int i = 0; i < switchesCount; ++i) {
+ const uint8_t *p = switchesData + i * 4;
+ const BoundingBox *b = &switchesBoundingBox[i];
+ fprintf(stdout, "switch %2d: screen %2d (%3d,%3d,%3d,%3d) flags 0x%02x sprite %2d gate %2d\n", i, p[0], b->x1, b->y1, b->x2, b->y2, p[1], (int8_t)p[2], p[3]);
+ }
+}
+
+void Game::updateScreenMaskLar(uint8_t *p, uint8_t flag) {
+ if (p[1] != flag) {
+ p[1] = flag;
+ const uint8_t screenNum = p[4];
+ uint32_t maskOffset = screenMaskOffset(_res->_screensBasePos[screenNum].u + p[2], _res->_screensBasePos[screenNum].v + p[3]);
+ uint8_t *dst = _screenMaskBuffer + maskOffset;
+ assert(p[0] < 6);
+ const int16_t *src = _lar_screenMaskOffsets[p[0]];
+ const int count = *src++;
+ if (!flag) {
+ for (int i = 0; i < count; ++i) {
+ const int16_t offset = *src++;
+ dst += offset;
+ *dst &= ~8;
+ }
+ } else {
+ for (int i = 0; i < count; ++i) {
+ const int16_t offset = *src++;
+ dst += offset;
+ *dst |= 8;
+ }
+ }
+ }
+}
+
+void Game::updateGateMaskLar(int num) {
+ int offset, type;
+ if (num < 0) {
+ offset = -num * 6;
+ updateScreenMaskLar(_lar1_maskData + offset, 0);
+ type = 5;
+ } else {
+ offset = num * 6;
+ updateScreenMaskLar(_lar1_maskData + offset, 1);
+ type = 2;
+ }
+ LvlObject *o = findLvlObject2(0, _lar1_maskData[offset + 5], _lar1_maskData[offset + 4]);
+ if (o) {
+ o->objectUpdateType = type;
+ }
+}
+
+int Game::clipAndyLvlObjectLar(BoundingBox *a /* unused */, BoundingBox *b, bool flag) {
+ int ret = 0;
+ const uint8_t flags = _andyObject->flags0 & 0x1F;
+ if (flags != 3 && flags != 5 && !flag) {
+ BoundingBox b1;
+ b1.x1 = _andyObject->xPos + _andyObject->posTable[5].x - 3;
+ b1.x2 = b1.x1 + 6;
+ b1.y1 = _andyObject->yPos + _andyObject->posTable[5].y - 3;
+ b1.y2 = b1.y1 + 6;
+ ret = clipBoundingBox(&b1, b);
+ if (!ret) {
+ BoundingBox b2;
+ b2.x1 = _andyObject->xPos + _andyObject->posTable[4].x - 3;
+ b2.x2 = b2.x1 + 6;
+ b2.y1 = _andyObject->yPos + _andyObject->posTable[4].y - 3;
+ b2.y2 = b2.y1 + 6;
+ ret = clipBoundingBox(&b2, b);
+ }
+ }
+ return ret;
+}
+
+void Game::resetWormHoleSprites() {
+ memset(_wormHoleSpritesTable, 0, sizeof(_wormHoleSpritesTable));
+ _wormHoleSpritesCount = 0;
+}
+
+void Game::updateWormHoleSprites() {
+ const uint8_t screenNum = _res->_currentScreenResourceNum;
+ WormHoleSprite *spr = 0;
+ for (int i = 0; i < _wormHoleSpritesCount; ++i) {
+ if (_wormHoleSpritesTable[i].screenNum == screenNum) {
+ spr = &_wormHoleSpritesTable[i];
+ break;
+ }
+ }
+ if (!spr) {
+ return;
+ }
+ LvlObject tmp;
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.screenNum = screenNum;
+ tmp.flags2 = 0x1001;
+ tmp.type = 8;
+ tmp.spriteNum = 20;
+ _res->incLvlSpriteDataRefCounter(&tmp);
+ uint32_t yOffset = 0;
+ for (int i = 0; i < 6; ++i) {
+ const uint32_t *flags = &spr->flags[i];
+ if (*flags != 0) {
+ int xOffset = 0;
+ for (int j = 0; j < 11; ++j) {
+ uint8_t _al = (*flags >> (j * 2)) & 3;
+ if (_al != 0 && _spritesNextPtr) {
+ const int xPos = spr->xPos + xOffset + 12;
+ const int yPos = spr->yPos + yOffset + 16;
+ if (rect_contains(spr->rect1_x1, spr->rect1_y1, spr->rect1_x2, spr->rect1_y2, xPos, yPos)) {
+ _al += 3;
+ } else if (rect_contains(spr->rect2_x1, spr->rect2_y1, spr->rect2_x2, spr->rect2_y2, xPos, yPos)) {
+ _al += 6;
+ }
+ tmp.anim = _al;
+ setupLvlObjectBitmap(&tmp);
+ setLvlObjectPosRelativeToPoint(&tmp, 7, xPos, yPos);
+ if (j & 1) {
+ tmp.yPos -= 16;
+ }
+ if (tmp.bitmapBits) {
+ Sprite *spr = _spritesNextPtr;
+ spr->xPos = tmp.xPos;
+ spr->yPos = tmp.yPos;
+ spr->w = tmp.width;
+ spr->h = tmp.height;
+ spr->bitmapBits = tmp.bitmapBits;
+ spr->num = tmp.flags2 & 0x3FFF;
+ addToSpriteList(spr);
+ }
+ }
+ xOffset += 24;
+ }
+ }
+ yOffset += 32;
+ }
+ _res->decLvlSpriteDataRefCounter(&tmp);
+}
+
+void Game::loadSetupCfg(bool resume) {
+ _resumeGame = resume;
+ if (!_res->readSetupCfg(&_setupConfig)) {
+ memset(&_setupConfig, 0, sizeof(_setupConfig));
+ _res->setDefaultsSetupCfg(&_setupConfig, 0);
+ }
+}
+
+void Game::saveSetupCfg() {
+ const int num = _setupConfig.currentPlayer;
+ if (_currentLevelCheckpoint > _setupConfig.players[num].progress[_currentLevel]) {
+ _setupConfig.players[num].progress[_currentLevel] = _currentLevelCheckpoint;
+ }
+ _setupConfig.players[num].levelNum = _currentLevel;
+ _setupConfig.players[num].checkpointNum = _currentLevelCheckpoint;
+ _setupConfig.players[num].cutscenesMask = _paf->_playedMask;
+ _setupConfig.players[num].difficulty = _difficulty;
+ _setupConfig.players[num].volume = _snd_masterVolume;
+ if (_currentLevel > _setupConfig.players[num].lastLevelNum) {
+ _setupConfig.players[num].lastLevelNum = _currentLevel;
+ }
+ _res->writeSetupCfg(&_setupConfig);
+}
+
+void Game::captureScreenshot() {
+ static int screenshot = 1;
+
+ char name[64];
+ snprintf(name, sizeof(name), "screenshot-%03d.bmp", screenshot);
+ FILE *fp = _fs.openSaveFile(name, true);
+ if (fp) {
+ saveBMP(fp, _video->_frontLayer, _video->_palette, Video::W, Video::H);
+ _fs.closeFile(fp);
+ }
+ if (_cheats != 0) {
+ snprintf(name, sizeof(name), "screenshot-%03d-background.bmp", screenshot);
+ fp = _fs.openSaveFile(name, true);
+ if (fp) {
+ saveBMP(fp, _video->_backgroundLayer, _video->_palette, Video::W, Video::H);
+ _fs.closeFile(fp);
+ }
+ snprintf(name, sizeof(name), "screenshot-%03d-shadow.bmp", screenshot);
+ fp = _fs.openSaveFile(name, true);
+ if (fp) {
+ saveBMP(fp, _video->_shadowLayer, _video->_palette, Video::W, Video::H);
+ _fs.closeFile(fp);
+ }
+ snprintf(name, sizeof(name), "screenshot-%03d-palette.bmp", screenshot);
+ fp = _fs.openSaveFile(name, true);
+ if (fp) {
+ static const int kPaletteRectSize = 8;
+ uint8_t paletteBuffer[8 * 256 * 8];
+ for (int x = 0; x < 256; ++x) {
+ const int xOffset = x * kPaletteRectSize;
+ for (int y = 0; y < kPaletteRectSize; ++y) {
+ memset(paletteBuffer + xOffset + y * 256 * kPaletteRectSize, x, kPaletteRectSize);
+ }
+ }
+ saveBMP(fp, paletteBuffer, _video->_palette, 256 * kPaletteRectSize, kPaletteRectSize);
+ _fs.closeFile(fp);
+ }
+ snprintf(name, sizeof(name), "screenshot-%03d-postable.txt", screenshot);
+ fp = _fs.openSaveFile(name, true);
+ if (fp) {
+ for (int y = 0; y < 24; ++y) {
+ for (int x = 0; x < 32; ++x) {
+ fprintf(fp, "%02x ", _screenPosTable[4][y * 32 + x]);
+ }
+ fputc('\n', fp);
+ }
+ _fs.closeFile(fp);
+ }
+ snprintf(name, sizeof(name), "screenshot-%03d-mask.txt", screenshot);
+ fp = _fs.openSaveFile(name, true);
+ if (fp) {
+ for (int y = 0; y < 24; ++y) {
+ for (int x = 0; x < 32; ++x) {
+ fprintf(fp, "%02x ", _screenTempMaskBuffer[y * 32 + x]);
+ }
+ fputc('\n', fp);
+ }
+ _fs.closeFile(fp);
+ }
+ }
+ ++screenshot;
+}
diff --git a/Src/Global/game.h b/Src/Global/game.h
new file mode 100644
index 0000000..3b45a60
--- /dev/null
+++ b/Src/Global/game.h
@@ -0,0 +1,573 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef GAME_H__
+#define GAME_H__
+
+#include "intern.h"
+#include "defs.h"
+#include "fileio.h"
+#include "fs.h"
+#include "mixer.h"
+#include "random.h"
+#include "resource.h"
+
+struct Game;
+struct Level;
+struct PafPlayer;
+struct Video;
+
+struct CheckpointData {
+ int16_t xPos;
+ int16_t yPos;
+ uint16_t flags2;
+ uint16_t anim;
+ uint8_t screenNum;
+ uint8_t spriteNum;
+};
+
+enum {
+ kObjectDataTypeAndy,
+ kObjectDataTypeAnimBackgroundData,
+ kObjectDataTypeShoot,
+ kObjectDataTypeLvlBackgroundSound,
+ kObjectDataTypeMonster1,
+ kObjectDataTypeMonster2
+};
+
+enum {
+ kCheatSpectreFireballNoHit = 1 << 0,
+ kCheatOneHitPlasmaCannon = 1 << 1,
+ kCheatOneHitSpecialPowers = 1 << 2,
+ kCheatWalkOnLava = 1 << 3,
+ kCheatGateNoCrush = 1 << 4
+};
+
+struct Game {
+ enum {
+ kMaxTasks = 128,
+ kMaxVars = 40,
+ kMaxLocals = 8,
+ kMaxAndyShoots = 8,
+ kMaxShootLvlObjectData = 32,
+ kMaxSssObjects = 32,
+ kMaxMonsterObjects1 = 32,
+ kMaxMonsterObjects2 = 64,
+ kMaxBackgroundAnims = 64,
+ kMaxSprites = 128,
+ kMaxLvlObjects = 160,
+ kMaxBoundingBoxes = 64,
+
+ kDefaultSoundPanning = 64,
+ kDefaultSoundVolume = 128
+ };
+
+ static const uint8_t _specialPowersDxDyTable[];
+ static const uint8_t _pointDstIndexTable[];
+ static const uint8_t _pointRandomizeShiftTable[];
+ static const uint8_t _pointSrcIndex2Table[];
+ static const uint8_t _pointSrcIndex1Table[];
+ static const uint8_t _actionDirectionKeyMaskTable[];
+ static const uint8_t _pwr1_screenTransformData[];
+ static const uint8_t _pwr2_screenTransformData[];
+ static const uint8_t _pwr1_screenTransformLut[];
+ static const uint8_t _lava_screenTransformLut[];
+ static const uint8_t _pwr2_screenTransformLut[];
+ static const uint8_t _pwr1_spritesData[];
+ static const uint8_t _isld_spritesData[];
+ static const uint8_t _lava_spritesData[];
+ static const uint8_t _lar1_spritesData[];
+ static const int16_t *_lar_screenMaskOffsets[];
+ static uint8_t _lar1_maskData[];
+
+ FileSystem _fs;
+ Level *_level;
+ Mixer _mix;
+ PafPlayer *_paf;
+ Random _rnd;
+ Resource *_res;
+ Video *_video;
+ uint32_t _cheats;
+ int _frameMs;
+ int _difficulty;
+
+ SetupConfig _setupConfig;
+ bool _playDemo;
+ bool _resumeGame;
+
+ LvlObject *_screenLvlObjectsList[kMaxScreens]; // LvlObject linked list for each screen
+ LvlObject *_andyObject;
+ LvlObject *_plasmaExplosionObject;
+ LvlObject *_plasmaCannonObject;
+ LvlObject *_specialAnimLvlObject;
+ LvlObject *_currentSoundLvlObject;
+ int _currentLevel;
+ int _currentLevelCheckpoint;
+ bool _endLevel;
+ Sprite _spritesTable[kMaxSprites];
+ Sprite *_spritesNextPtr; // pointer to the next free entry
+ Sprite *_typeSpritesList[kMaxSpriteTypes];
+ uint8_t _directionKeyMask;
+ uint8_t _actionKeyMask;
+ uint8_t _currentRightScreen;
+ uint8_t _currentLeftScreen;
+ uint8_t _currentScreen;
+ int8_t _levelRestartCounter;
+ bool _fadePalette;
+ bool _hideAndyObjectFlag;
+ ShootLvlObjectData _shootLvlObjectDataTable[kMaxShootLvlObjectData];
+ ShootLvlObjectData *_shootLvlObjectDataNextPtr; // pointer to the next free entry
+ LvlObject *_lvlObjectsList0;
+ LvlObject *_lvlObjectsList1;
+ LvlObject *_lvlObjectsList2;
+ LvlObject *_lvlObjectsList3;
+ uint8_t _screenPosTable[5][24 * 32];
+ uint8_t _screenTempMaskBuffer[24 * 32];
+ uint8_t _screenMaskBuffer[(16 * 6) * 24 * 32]; // level screens mask : 16 horizontal screens x 6 vertical screens
+ int _mstAndyCurrentScreenNum;
+ uint8_t _shakeScreenDuration;
+ const uint8_t *_shakeScreenTable;
+ uint8_t _plasmaCannonDirection;
+ uint8_t _plasmaCannonPrevDirection;
+ uint8_t _plasmaCannonPointsSetupCounter;
+ uint8_t _plasmaCannonLastIndex1;
+ bool _plasmaCannonExplodeFlag;
+ uint8_t _plasmaCannonPointsMask;
+ uint8_t _plasmaCannonFirstIndex;
+ uint8_t _plasmaCannonLastIndex2;
+ uint8_t _plasmaCannonFlags;
+ uint8_t _actionDirectionKeyMaskCounter;
+ bool _fallingAndyFlag;
+ uint8_t _fallingAndyCounter;
+ uint8_t _actionDirectionKeyMaskIndex;
+ uint8_t _andyActionKeyMaskAnd, _andyActionKeyMaskOr;
+ uint8_t _andyDirectionKeyMaskAnd, _andyDirectionKeyMaskOr;
+ int32_t _plasmaCannonPosX[129];
+ int32_t _plasmaCannonPosY[129];
+ int32_t _plasmaCannonXPointsTable1[129];
+ int32_t _plasmaCannonYPointsTable1[129];
+ int32_t _plasmaCannonXPointsTable2[127];
+ int32_t _plasmaCannonYPointsTable2[127];
+ ScreenMask _shadowScreenMasksTable[8];
+
+ uint16_t _mstCurrentAnim;
+ uint16_t _specialAnimMask;
+ int16_t _mstOriginPosX;
+ int16_t _mstOriginPosY;
+ bool _specialAnimFlag;
+ AndyShootData _andyShootsTable[kMaxAndyShoots];
+ int _andyShootsCount;
+ uint8_t _mstOp68_type, _mstOp68_arg9, _mstOp67_type;
+ uint8_t _mstOp67_flags1;
+ uint16_t _mstOp67_unk;
+ int _mstOp67_x1, _mstOp67_x2, _mstOp67_y1, _mstOp67_y2;
+ int8_t _mstOp67_screenNum;
+ uint16_t _mstOp68_flags1;
+ int _mstOp68_x1, _mstOp68_x2, _mstOp68_y1, _mstOp68_y2;
+ int8_t _mstOp68_screenNum;
+ uint32_t _mstLevelGatesMask;
+ int _runTaskOpcodesCount;
+ int32_t _mstVars[kMaxVars];
+ uint32_t _mstFlags;
+ int _clipBoxOffsetX, _clipBoxOffsetY;
+ Task *_currentTask;
+ int _mstOp54Counter;
+ int _mstOp56Counter;
+ uint8_t _mstOp54Table[32];
+ bool _mstDisabled;
+ LvlObject _declaredLvlObjectsList[kMaxLvlObjects];
+ LvlObject *_declaredLvlObjectsNextPtr; // pointer to the next free entry
+ int _declaredLvlObjectsListCount;
+ AndyLvlObjectData _andyObjectScreenData;
+ AnimBackgroundData _animBackgroundDataTable[kMaxBackgroundAnims];
+ int _animBackgroundDataCount;
+ uint8_t _andyActionKeysFlags;
+ int _executeMstLogicCounter;
+ int _executeMstLogicPrevCounter;
+ Task _tasksTable[kMaxTasks];
+ Task *_tasksList;
+ Task *_monsterObjects1TasksList;
+ Task *_monsterObjects2TasksList;
+ int _mstAndyLevelPrevPosX;
+ int _mstAndyLevelPrevPosY;
+ int _mstAndyLevelPosX;
+ int _mstAndyLevelPosY;
+ int _mstAndyScreenPosX;
+ int _mstAndyScreenPosY;
+ int _mstAndyRectNum;
+ int _m43Num1;
+ int _m43Num2;
+ int _m43Num3;
+ int _xMstPos1, _yMstPos1;
+ int _xMstPos2, _yMstPos2; // xMstDist1, yMstDist1
+ int _xMstPos3, _yMstPos3;
+ int _mstHelper1Count;
+ int _mstActionNum;
+ uint32_t _mstAndyVarMask;
+ int _mstChasingMonstersCount;
+ int _mstPosXmin, _mstPosXmax;
+ int _mstPosYmin, _mstPosYmax;
+ int _mstTemp_x1, _mstTemp_x2, _mstTemp_y1, _mstTemp_y2;
+ MonsterObject1 _monsterObjects1Table[kMaxMonsterObjects1];
+ MonsterObject2 _monsterObjects2Table[kMaxMonsterObjects2];
+ int _mstTickDelay;
+ uint8_t _mstCurrentActionKeyMask;
+ int _mstCurrentPosX, _mstCurrentPosY;
+ int _mstBoundingBoxesCount;
+ MstBoundingBox _mstBoundingBoxesTable[kMaxBoundingBoxes];
+ Task *_mstCurrentTask;
+ MstCollision _mstCollisionTable[2][32]; // 0:facingRight, 1:facingLeft
+ int _wormHoleSpritesCount;
+ WormHoleSprite _wormHoleSpritesTable[6];
+
+ Game(const char *dataPath, const char *savePath, uint32_t cheats);
+ ~Game();
+
+ // 32*24 pitch=512
+ static uint32_t screenMaskOffset(int x, int y) {
+ return ((y << 6) & ~511) + (x >> 3);
+ // return ((y & ~7) << 6) + (x >> 3)
+ }
+
+ // 32*24 pitch=32
+ static uint32_t screenGridOffset(int x, int y) {
+ return ((y & ~7) << 2) + (x >> 3);
+ }
+
+ // benchmark.cpp
+ uint32_t benchmarkLoop(const uint8_t *p, int count);
+ uint32_t benchmarkCpu();
+
+ // game.cpp
+ void mainLoop(int level, int checkpoint, bool levelChanged);
+ void mixAudio(int16_t *buf, int len);
+ void resetShootLvlObjectDataTable();
+ void clearShootLvlObjectData(LvlObject *ptr);
+ void setShakeScreen(int type, int counter);
+ void fadeScreenPalette();
+ void shakeScreen();
+ void transformShadowLayer(int delta);
+ void loadTransformLayerData(const uint8_t *data);
+ void unloadTransformLayerData();
+ void decodeShadowScreenMask(LvlBackgroundData *lvl);
+ SssObject *playSound(int num, LvlObject *ptr, int a, int b);
+ void removeSound(LvlObject *ptr);
+ void setupBackgroundBitmap();
+ void addToSpriteList(Sprite *spr);
+ void addToSpriteList(LvlObject *ptr);
+ int16_t calcScreenMaskDy(int16_t xPos, int16_t yPos, int num);
+ void setupScreenPosTable(uint8_t num);
+ void setupScreenMask(uint8_t num);
+ void resetScreenMask();
+ void setScreenMaskRectHelper(int x1, int y1, int x2, int y2, int screenNum);
+ void setScreenMaskRect(int x1, int y1, int x2, int y2, int pos);
+ void updateScreenMaskBuffer(int x, int y, int type);
+ void setupLvlObjectBitmap(LvlObject *ptr);
+ void randomizeInterpolatePoints(int32_t *pts, int count);
+ int fixPlasmaCannonPointsScreenMask(int num);
+ void setupPlasmaCannonPointsHelper();
+ void destroyLvlObjectPlasmaExplosion(LvlObject *o);
+ void shuffleArray(uint8_t *p, int count);
+ void destroyLvlObject(LvlObject *o);
+ void setupPlasmaCannonPoints(LvlObject *ptr);
+ int testPlasmaCannonPointsDirection(int x1, int y1, int x2, int y2);
+ void preloadLevelScreenData(uint8_t num, uint8_t prev);
+ void setLvlObjectPosRelativeToObject(LvlObject *ptr1, int num1, LvlObject *ptr2, int num2);
+ void setLvlObjectPosRelativeToPoint(LvlObject *ptr, int num, int x, int y);
+ void clearLvlObjectsList0();
+ void clearLvlObjectsList1();
+ void clearLvlObjectsList2();
+ void clearLvlObjectsList3();
+ LvlObject *addLvlObjectToList0(int num);
+ LvlObject *addLvlObjectToList1(int type, int num);
+ LvlObject *addLvlObjectToList2(int num);
+ LvlObject *addLvlObjectToList3(int num);
+ void removeLvlObject(LvlObject *ptr);
+ void removeLvlObject2(LvlObject *o);
+ void setAndySprite(int num);
+ void setupAndyLvlObject();
+ void setupScreenLvlObjects(int num);
+ void resetDisplay();
+ void setupScreen(uint8_t num);
+ void resetScreen();
+ void restartLevel();
+ void playAndyFallingCutscene(int type);
+ int8_t updateLvlObjectScreen(LvlObject *ptr);
+ void setAndyAnimationForArea(BoundingBox *box, int dx);
+ void setAndyLvlObjectPlasmaCannonKeyMask();
+ int setAndySpecialAnimation(uint8_t mask);
+ int clipBoundingBox(BoundingBox *coords, BoundingBox *box);
+ int updateBoundingBoxClippingOffset(BoundingBox *_ecx, BoundingBox *_ebp, const uint8_t *coords, int direction);
+ int clipLvlObjectsBoundingBoxHelper(LvlObject *o1, BoundingBox *box1, LvlObject *o2, BoundingBox *box2);
+ int clipLvlObjectsBoundingBox(LvlObject *o, LvlObject *ptr, int count);
+ int clipLvlObjectsSmall(LvlObject *o, LvlObject *ptr, int count);
+ int restoreAndyCollidesLava();
+ int updateAndyLvlObject();
+ void drawPlasmaCannon();
+ void drawScreen();
+ void updateLvlObjectList(LvlObject **list);
+ void updateLvlObjectLists();
+ LvlObject *updateAnimatedLvlObjectType0(LvlObject *ptr);
+ LvlObject *updateAnimatedLvlObjectType1(LvlObject *ptr);
+ LvlObject *updateAnimatedLvlObjectType2(LvlObject *ptr);
+ LvlObject *updateAnimatedLvlObjectTypeDefault(LvlObject *ptr);
+ LvlObject *updateAnimatedLvlObject(LvlObject *o);
+ void updateAnimatedLvlObjectsLeftRightCurrentScreens();
+ void updatePlasmaCannonExplosionLvlObject(LvlObject *ptr);
+ void resetPlasmaCannonState();
+ void updateAndyMonsterObjects();
+ void updateInput();
+ void levelMainLoop();
+ Level *createLevel();
+ void callLevel_postScreenUpdate(int num);
+ void callLevel_preScreenUpdate(int num);
+ void callLevel_initialize();
+ void callLevel_tick();
+ void callLevel_terminate();
+ void displayLoadingScreen();
+ int displayHintScreen(int num, int pause);
+ void prependLvlObjectToList(LvlObject **list, LvlObject *ptr);
+ void removeLvlObjectFromList(LvlObject **list, LvlObject *ptr);
+ void *getLvlObjectDataPtr(LvlObject *o, int type) const;
+ void lvlObjectType0Init(LvlObject *ptr);
+ void lvlObjectType1Init(LvlObject *ptr);
+ void lvlObjectTypeInit(LvlObject *ptr);
+ void lvlObjectType0CallbackHelper1();
+ int calcScreenMaskDx(int x, int y, int num);
+ void lvlObjectType0CallbackBreathBubbles(LvlObject *ptr);
+ void setupSpecialPowers(LvlObject *ptr);
+ int lvlObjectType0Callback(LvlObject *ptr);
+ int lvlObjectType1Callback(LvlObject *ptr);
+ int lvlObjectType7Callback(LvlObject *o);
+ int lvlObjectType8Callback(LvlObject *o);
+ int lvlObjectList3Callback(LvlObject *o);
+ void lvlObjectSpecialPowersCallbackHelper1(LvlObject *o);
+ uint8_t lvlObjectCallbackCollideScreen(LvlObject *o);
+ int lvlObjectSpecialPowersCallback(LvlObject *o);
+ void lvlObjectTypeCallback(LvlObject *o);
+ LvlObject *addLvlObject(int type, int x, int y, int screen, int num, int o_anim, int o_flags1, int o_flags2, int actionKeyMask, int directionKeyMask);
+ int setLvlObjectPosInScreenGrid(LvlObject *o, int pos);
+ LvlObject *declareLvlObject(uint8_t type, uint8_t num);
+ void clearDeclaredLvlObjectsList();
+ void initLvlObjects();
+ void setLvlObjectSprite(LvlObject *ptr, uint8_t _dl, uint8_t num);
+ LvlObject *findLvlObject(uint8_t type, uint8_t spriteNum, int screenNum);
+ LvlObject *findLvlObject2(uint8_t type, uint8_t dataNum, int screenNum);
+ LvlObject *findLvlObjectType2(int spriteNum, int screenNum);
+ LvlObject *findLvlObjectBoundingBox(BoundingBox *box);
+ void setLavaAndyAnimation(int yPos);
+ void updateGatesLar(LvlObject *o, uint8_t *p, int num);
+ void updateSwitchesLar(int count, uint8_t *data, BoundingBox *r, uint8_t *gatesData);
+ void updateSwitchesLar_checkSpectre(int num, uint8_t *p1, BoundingBox *r, uint8_t *gatesData);
+ int updateSwitchesLar_checkAndy(int num, uint8_t *p1, BoundingBox *b, BoundingBox *r, uint8_t *gatesData);
+ int updateSwitchesLar_toggle(bool flag, uint8_t dataNum, int screenNum, int switchNum, int anim, const BoundingBox *switchBoundingBox);
+ void dumpSwitchesLar(int switchesCount, const uint8_t *switchesData, const BoundingBox *switchesBoundingBox, int gatesCount, const uint8_t *gatesData);
+ void updateScreenMaskLar(uint8_t *p, uint8_t flag);
+ void updateGateMaskLar(int num);
+ int clipAndyLvlObjectLar(BoundingBox *a, BoundingBox *b, bool flag);
+ void resetWormHoleSprites();
+ void updateWormHoleSprites();
+ void loadSetupCfg(bool resume);
+ void saveSetupCfg();
+ void captureScreenshot();
+
+ // level1_rock.cpp
+ int objectUpdate_rock_case0(LvlObject *o);
+ void objectUpdate_rockShadow(LvlObject *ptr, uint8_t *p);
+ bool plasmaCannonHit(LvlObject *ptr);
+ int objectUpdate_rock_case1(LvlObject *o);
+ int objectUpdate_rock_case2(LvlObject *o);
+ int objectUpdate_rock_case3(LvlObject *o);
+ int objectUpdate_rock_case4(LvlObject *o);
+
+ // monsters.cpp
+ void mstMonster1ResetData(MonsterObject1 *m);
+ void mstMonster1ResetWalkPath(MonsterObject1 *m);
+ bool mstUpdateInRange(MstMonsterAction *m48);
+ bool addChasingMonster(MstMonsterAction *m48, uint8_t flag);
+ void mstMonster1ClearChasingMonster(MonsterObject1 *m);
+ void mstTaskSetMonster1BehaviorState(Task *t, MonsterObject1 *m, int num);
+ int mstTaskStopMonsterObject1(Task *t);
+ void mstMonster1SetScreenPosition(MonsterObject1 *m);
+ bool mstMonster1SetWalkingBounds(MonsterObject1 *m);
+ bool mstMonster1UpdateWalkPath(MonsterObject1 *m);
+ void mstMonster2InitFirefly(MonsterObject2 *m);
+ void mstMonster2ResetData(MonsterObject2 *m);
+ uint32_t mstMonster1GetNextWalkCode(MonsterObject1 *m);
+ int mstTaskSetNextWalkCode(Task *t);
+
+ void mstBoundingBoxClear(MonsterObject1 *m, int dir);
+ int mstBoundingBoxCollides1(int num, int x1, int y1, int x2, int y2) const;
+ int mstBoundingBoxUpdate(int num, int monster1Index, int x1, int y1, int x2, int y2);
+ int mstBoundingBoxCollides2(int monster1Index, int x1, int y1, int x2, int y2) const;
+
+ void mstTaskSetMonster2ScreenPosition(Task *t);
+ int getMstDistance(int y, const AndyShootData *p) const;
+ void mstTaskUpdateScreenPosition(Task *t);
+ void shuffleMstMonsterActionIndex(MstMonsterActionIndex *p);
+
+ void initMstCode();
+ void resetMstCode();
+ void startMstCode();
+ void executeMstCode();
+ void mstWalkPathUpdateIndex(MstWalkPath *walkPath, int index);
+ int mstWalkPathUpdateWalkNode(MstWalkPath *walkPath, MstWalkNode *walkNode, int num, int index);
+ void executeMstCodeHelper1();
+ void mstUpdateMonster1ObjectsPosition();
+ void mstLvlObjectSetActionDirection(LvlObject *o, const uint8_t *ptr, uint8_t mask1, uint8_t mask2);
+ void mstMonster1UpdateGoalPosition(MonsterObject1 *m);
+ void mstMonster1MoveTowardsGoal1(MonsterObject1 *m);
+ bool mstMonster1TestGoalDirection(MonsterObject1 *m);
+ void mstMonster1MoveTowardsGoal2(MonsterObject1 *m);
+ int mstTaskStopMonster1(Task *t, MonsterObject1 *m);
+ int mstTaskUpdatePositionActionDirection(Task *t, MonsterObject1 *m);
+ int mstMonster1FindWalkPathRect(MonsterObject1 *m, MstWalkPath *walkPath, int x, int y);
+ bool mstTestActionDirection(MonsterObject1 *m, int num);
+ bool lvlObjectCollidesAndy1(LvlObject *o, int type) const;
+ bool lvlObjectCollidesAndy2(LvlObject *o, int type) const;
+ bool lvlObjectCollidesAndy4(LvlObject *o, int type) const;
+ bool lvlObjectCollidesAndy3(LvlObject *o, int type) const;
+ bool mstCollidesByFlags(MonsterObject1 *m, uint32_t flags);
+ bool mstMonster1Collide(MonsterObject1 *m, const uint8_t *p);
+ int mstUpdateTaskMonsterObject1(Task *t);
+ int mstUpdateTaskMonsterObject2(Task *t);
+ void mstUpdateRefPos();
+ void mstUpdateMonstersRect();
+ void mstRemoveMonsterObject2(Task *t, Task **tasksList);
+ void mstTaskAttack(Task *t, uint32_t codeData, uint8_t flags);
+ void mstRemoveMonsterObject1(Task *t, Task **tasksList);
+ void mstOp26_removeMstTaskScreen(Task **tasksList, int screenNum);
+ void mstOp27_removeMstTaskScreenType(Task **tasksList, int screenNum, int type);
+ int mstOp49_setMovingBounds(int a, int b, int c, int d, int screen, Task *t, int num);
+ void mstOp52();
+ bool mstHasMonsterInRange(const MstMonsterAction *m, uint8_t flag);
+ void mstOp53(MstMonsterAction *m);
+ void mstOp54();
+ int mstOp56_specialAction(Task *t, int code, int num);
+ void mstOp57_addWormHoleSprite(int x, int y, int screenNum);
+ void mstOp58_addLvlObject(Task *t, int num);
+ void mstOp59_addShootSpecialPowers(int x, int y, int screenNum, int state, uint16_t flags);
+ void mstOp59_addShootFireball(int x, int y, int screenNum, int type, int state, uint16_t flags);
+ void mstTaskResetMonster1WalkPath(Task *t);
+ bool mstSetCurrentPos(MonsterObject1 *m, int x, int y);
+ void mstMonster1SetGoalHorizontal(MonsterObject1 *m);
+ void mstResetCollisionTable();
+ void mstTaskRestart(Task *t);
+ bool mstMonster1CheckLevelBounds(MonsterObject1 *m, int x, int y, uint8_t dir);
+ int mstTaskResetMonster1Direction(Task *t);
+ int mstTaskInitMonster1Type1(Task *t);
+ int mstTaskInitMonster1Type2(Task *t, int flag);
+ void mstOp67_addMonster(Task *t, int y1, int y2, int x1, int x2, int screen, int type, int o_flags1, int o_flags2, int arg1C, int arg20, int arg24);
+ void mstOp68_addMonsterGroup(Task *t, const uint8_t *p, int a, int b, int c, int d);
+ int mstTaskSetActionDirection(Task *t, int num, int value);
+
+ // Task list
+ Task *findFreeTask();
+ Task *createTask(const uint8_t *codeData);
+ void updateTask(Task *t, int num, const uint8_t *codeData);
+ void resetTask(Task *t, const uint8_t *codeData);
+ void removeTask(Task **tasksList, Task *t);
+ void appendTask(Task **tasksList, Task *t);
+
+ // Task storage
+ int getTaskVar(Task *t, int index, int type) const;
+ void setTaskVar(Task *t, int index, int type, int value);
+ int getTaskAndyVar(int index, Task *t) const;
+ int getTaskOtherVar(int index, Task *t) const;
+ int getTaskFlag(Task *t, int index, int type) const;
+
+ // Task.run functions
+ int mstTask_main(Task *t);
+ int mstTask_wait1(Task *t);
+ int mstTask_wait2(Task *t);
+ int mstTask_wait3(Task *t);
+ int mstTask_idle(Task *t);
+ int mstTask_mstOp231(Task *t);
+ int mstTask_mstOp232(Task *t);
+ int mstTask_mstOp233(Task *t);
+ int mstTask_mstOp234(Task *t);
+ int mstTask_monsterWait1(Task *t);
+ int mstTask_monsterWait2(Task *t);
+ int mstTask_monsterWait3(Task *t);
+ int mstTask_monsterWait4(Task *t);
+ int mstTask_monsterWait5(Task *t);
+ int mstTask_monsterWait6(Task *t);
+ int mstTask_monsterWait7(Task *t);
+ int mstTask_monsterWait8(Task *t);
+ int mstTask_monsterWait9(Task *t);
+ int mstTask_monsterWait10(Task *t);
+ int mstTask_monsterWait11(Task *t);
+
+ // sound.cpp
+ bool _sssDisabled;
+ int16_t _snd_buffer[4096];
+ int _snd_bufferOffset, _snd_bufferSize;
+ bool _snd_muted;
+ int _snd_masterPanning;
+ int _snd_masterVolume;
+ SssObject _sssObjectsTable[kMaxSssObjects];
+ bool _sssObjectsChanged;
+ int _sssObjectsCount;
+ SssObject *_sssObjectsList1; // playing
+ SssObject *_sssObjectsList2; // paused/idle
+ SssObject *_lowPrioritySssObject;
+ bool _sssUpdatedObjectsTable[kMaxSssObjects];
+ int _playingSssObjectsMax;
+ int _playingSssObjectsCount;
+
+ void muteSound();
+ void unmuteSound();
+ void resetSound();
+ int getSoundPosition(const SssObject *so);
+ void setSoundPanning(SssObject *so, int panning);
+ SssObject *findLowPrioritySoundObject() const;
+ void removeSoundObjectFromList(SssObject *so);
+ void updateSoundObject(SssObject *so);
+ void sssOp4_removeSounds(uint32_t flags);
+ void sssOp12_removeSounds2(int num, uint8_t source, uint8_t sampleIndex);
+ void sssOp16_resumeSound(SssObject *so);
+ void sssOp17_pauseSound(SssObject *so);
+ const uint8_t *executeSssCode(SssObject *so, const uint8_t *code, bool tempSssObject = false);
+ SssObject *addSoundObject(SssPcm *pcm, int priority, uint32_t flags_a, uint32_t flags_b);
+ void prependSoundObjectToList(SssObject *so);
+ void updateSssGroup2(uint32_t flags);
+ SssObject *createSoundObject(int bankIndex, int sampleIndex, uint32_t flags);
+ SssObject *startSoundObject(int bankIndex, int sampleIndex, uint32_t flags);
+ SssObject *playSoundObject(SssInfo *s, int source, int b);
+ void clearSoundObjects();
+ void setLowPrioritySoundObject(SssObject *so);
+ int getSoundObjectPanning(SssObject *so) const;
+ void setSoundObjectPanning(SssObject *so);
+ void expireSoundObjects(uint32_t flags);
+ void mixSoundObjects17640(bool flag);
+ void queueSoundObjectsPcmStride();
+
+ // andy.cpp
+ AndyMoveData _andyMoveData;
+ int32_t _andyPosX;
+ int32_t _andyPosY;
+ uint8_t _andyMoveMask;
+ bool _andyUpdatePositionFlag;
+ const Point16_t *_andyLevelData0x288PosTablePtr;
+ uint8_t _andyMoveState[32];
+ int _andyMaskBufferPos1;
+ int _andyMaskBufferPos2;
+ int _andyMaskBufferPos4;
+ int _andyMaskBufferPos5;
+ int _andyMaskBufferPos3;
+ int _andyMaskBufferPos0;
+ int _andyMaskBufferPos7;
+ int _andyMaskBufferPos6;
+
+ int moveAndyObjectOp1(int op);
+ int moveAndyObjectOp2(int op);
+ int moveAndyObjectOp3(int op);
+ int moveAndyObjectOp4(int op);
+ void setupAndyObjectMoveData(LvlObject *ptr);
+ void setupAndyObjectMoveState();
+ void updateAndyObject(LvlObject *ptr);
+};
+
+#endif // GAME_H__
diff --git a/SRC/hode b/Src/Global/hode
similarity index 100%
rename from SRC/hode
rename to Src/Global/hode
diff --git a/Src/Global/intern.h b/Src/Global/intern.h
new file mode 100644
index 0000000..b7a72cf
--- /dev/null
+++ b/Src/Global/intern.h
@@ -0,0 +1,119 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef INTERN_H__
+#define INTERN_H__
+
+#include
+#include
+#include
+#include
+#include
+
+#if defined(_WIN32) || defined(PSP)
+#define le16toh(x) x
+#define le32toh(x) x
+#define htole16(x) x
+#define htole32(x) x
+static const bool kByteSwapData = false; // no byteswap needed on little endian
+#elif defined(WII) // big endian
+#include
+#define le16toh(x) __bswap16(x)
+#define le32toh(x) __bswap32(x)
+#define htole16(x) __bswap16(x)
+#define htole32(x) __bswap32(x)
+static const bool kByteSwapData = true;
+#else
+#include
+static const bool kByteSwapData = (__BYTE_ORDER == __BIG_ENDIAN);
+#endif
+
+#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))
+#define PACKED __attribute__((packed))
+
+inline uint16_t READ_LE_UINT16(const void *ptr) {
+ if (1 && (((uintptr_t)ptr) & 1) != 0) {
+ uint16_t value;
+ memcpy(&value, ptr, sizeof(uint16_t));
+ return le16toh(value);
+ } else {
+ return le16toh(*(const uint16_t *)ptr);
+ }
+}
+
+inline uint32_t READ_LE_UINT32(const void *ptr) {
+ if (1 && (((uintptr_t)ptr) & 3) != 0) {
+ uint32_t value;
+ memcpy(&value, ptr, sizeof(uint32_t));
+ return le32toh(value);
+ } else {
+ return le32toh(*(const uint32_t *)ptr);
+ }
+}
+
+inline void WRITE_LE_UINT16(void *ptr, uint16_t v) {
+ if (1 && (((uintptr_t)ptr) & 1) != 0) {
+ const uint16_t value = htole16(v);
+ memcpy(ptr, &value, sizeof(uint16_t));
+ } else {
+ *((uint16_t *)ptr) = htole16(v);
+ }
+}
+
+inline void WRITE_LE_UINT32(void *ptr, uint32_t v) {
+ if (1 && (((uintptr_t)ptr) & 3) != 0) {
+ const uint32_t value = htole32(v);
+ memcpy(ptr, &value, sizeof(uint32_t));
+ } else {
+ *((uint32_t *)ptr) = htole32(v);
+ }
+}
+
+#undef MIN
+template
+inline T MIN(T v1, T v2) {
+ return (v1 < v2) ? v1 : v2;
+}
+
+#undef MAX
+template
+inline T MAX(T v1, T v2) {
+ return (v1 > v2) ? v1 : v2;
+}
+
+template
+inline T ABS(T t) {
+ return (t < 0) ? -t : t;
+}
+
+template
+inline T CLIP(T t, T tmin, T tmax) {
+ return (t < tmin ? tmin : (t > tmax ? tmax : t));
+}
+
+template
+inline void SWAP(T &a, T &b) {
+ T tmp = a; a = b; b = tmp;
+}
+
+inline uint32_t merge_bits(uint32_t dst, uint32_t src, uint32_t mask) {
+// return (dst & ~mask) | (src & mask);
+ return ((src ^ dst) & mask) ^ dst;
+}
+
+inline bool compare_bits(uint32_t a, uint32_t b, uint32_t mask) {
+// return (a & mask) == (b & mask);
+ return ((a ^ b) & mask) == 0;
+}
+
+inline bool rect_contains(int left, int top, int right, int bottom, int x, int y) {
+ return x >= left && x <= right && y >= top && y <= bottom;
+}
+
+inline bool rect_intersects(int left1, int top1, int right1, int bottom1, int left2, int top2, int right2, int bottom2) {
+ return right2 >= left1 && left2 <= right1 && bottom2 >= top1 && top2 <= bottom1;
+}
+
+#endif // INTERN_H__
diff --git a/SRC/level.h b/Src/Global/level.h
similarity index 100%
rename from SRC/level.h
rename to Src/Global/level.h
diff --git a/Src/Global/level1_rock.cpp b/Src/Global/level1_rock.cpp
new file mode 100644
index 0000000..7782c3d
--- /dev/null
+++ b/Src/Global/level1_rock.cpp
@@ -0,0 +1,919 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+// rock_hod - "canyon of death"
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "util.h"
+#include "video.h"
+
+static const CheckpointData _rock_checkpointData[8] = {
+ { 96, 34, 0x300c, 22, 0, 0 },
+ { 10, 126, 0x300c, 48, 2, 0 },
+ { 6, 128, 0x300c, 48, 4, 0 },
+ { 18, 125, 0x300c, 48, 5, 0 },
+ { 21, 124, 0x300c, 48, 7, 0 },
+ { 105, 52, 0x3004, 232, 9, 2 },
+ { 18, 121, 0x3004, 232, 13, 2 },
+ { 43, 96, 0x3004, 39, 17, 2 }
+};
+
+static const uint8_t _rock_screenStartData[56] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x30, 0x00, 0x00, 0x01, 0x40, 0x02, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_rock: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_rock_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _rock_screenStartData; }
+ virtual void initialize();
+ virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_rock_screen0();
+ void postScreenUpdate_rock_screen4();
+ void postScreenUpdate_rock_screen8();
+ void postScreenUpdate_rock_screen9();
+ void postScreenUpdate_rock_screen10();
+ void postScreenUpdate_rock_screen11();
+ void postScreenUpdate_rock_screen13();
+ void postScreenUpdate_rock_screen15();
+ void postScreenUpdate_rock_screen16();
+ void postScreenUpdate_rock_screen18();
+ void postScreenUpdate_rock_screen19();
+
+ void preScreenUpdate_rock_screen0();
+ void preScreenUpdate_rock_screen1();
+ void preScreenUpdate_rock_screen2();
+ void preScreenUpdate_rock_screen3();
+ void preScreenUpdate_rock_screen4();
+ void preScreenUpdate_rock_screen5();
+ void preScreenUpdate_rock_screen7();
+ void preScreenUpdate_rock_screen9();
+ void preScreenUpdate_rock_screen10();
+ void preScreenUpdate_rock_screen13();
+ void preScreenUpdate_rock_screen14();
+ void preScreenUpdate_rock_screen15();
+ void preScreenUpdate_rock_screen16();
+ void preScreenUpdate_rock_screen17();
+ void preScreenUpdate_rock_screen18();
+ void preScreenUpdate_rock_screen19();
+
+ void setupScreenCheckpoint_rock_screen2();
+ void setupScreenCheckpoint_rock_screen3();
+ void setupScreenCheckpoint_rock_screen18();
+};
+
+Level *Level_rock_create() {
+ return new Level_rock;
+}
+
+void Level_rock::postScreenUpdate_rock_screen0() {
+ switch (_res->_screensState[0].s0) {
+ case 0:
+ if ((_andyObject->flags0 & 0x1F) == 0 && ((_andyObject->flags0 >> 5) & 7) == 6) {
+ _res->_screensState[0].s0 = 2;
+ }
+ break;
+ case 2:
+ ++_screenCounterTable[0];
+ if (_screenCounterTable[0] > 25) {
+ _res->_screensState[0].s0 = 1;
+ }
+ if (_screenCounterTable[0] == 2) {
+ _g->setShakeScreen(3, 12);
+ }
+ break;
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen4() {
+ switch (_res->_screensState[4].s0) {
+ case 0:
+ if (_g->_plasmaCannonDirection != 0 && _res->_currentScreenResourceNum == 4) {
+ if (_g->testPlasmaCannonPointsDirection(162, 24, 224, 68) == 0) {
+ if (_g->testPlasmaCannonPointsDirection(202, 0, 255, 48) == 0) {
+ if (_g->testPlasmaCannonPointsDirection(173, 8, 201, 23) == 0) {
+ _g->_plasmaCannonLastIndex1 = 0;
+ return;
+ }
+ }
+ }
+ ++_screenCounterTable[4];
+ if (_screenCounterTable[4] >= 20) {
+ _res->_screensState[4].s0 = 2;
+ }
+ assert(_g->_plasmaExplosionObject);
+ _g->_plasmaExplosionObject->screenNum = _res->_currentScreenResourceNum;
+ _g->_plasmaCannonExplodeFlag = true;
+ if (_g->_shakeScreenDuration == 0) {
+ _g->setShakeScreen(3, 2);
+ } else {
+ ++_g->_shakeScreenDuration;
+ }
+ }
+ break;
+ case 2:
+ ++_screenCounterTable[4];
+ if (_screenCounterTable[4] == 33) {
+ _res->_resLvlScreenBackgroundDataTable[4].currentMaskId = 1;
+ _g->setupScreenMask(4);
+ } else if (_screenCounterTable[4] > 46) {
+ _res->_screensState[4].s0 = 1;
+ }
+ if (_screenCounterTable[4] == 31) {
+ _g->setShakeScreen(2, 12);
+ }
+ break;
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen8() {
+ if (_res->_currentScreenResourceNum == 8) {
+ if ((_andyObject->flags0 & 0x1F) == 3) {
+ _andyObject->flags2 = 0x3008;
+ } else if (_andyObject->yPos + _andyObject->height / 2 < 89) {
+ _andyObject->flags2 = 0x3004;
+ } else {
+ _andyObject->flags2 = 0x300C;
+ }
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen9() {
+ int xPos;
+ if (_res->_currentScreenResourceNum == 9) {
+ switch (_res->_screensState[9].s0) {
+ case 0:
+ xPos = 68;
+ if ((_andyObject->flags0 & 0xE0) != 0) {
+ xPos -= 14;
+ }
+ if (_andyObject->xPos > xPos && _andyObject->yPos < 86) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(1);
+ _res->_resLvlScreenBackgroundDataTable[9].currentBackgroundId = 1;
+ _video->_paletteChanged = true;
+ }
+ if (_checkpoint == 4) {
+ _checkpoint = 5;
+ }
+ _res->_screensState[9].s0 = 1;
+ _g->setAndySprite(2);
+ _andyObject->xPos = 105;
+ _andyObject->yPos = 52;
+ _andyObject->anim = 232;
+ _andyObject->frame = 0;
+ _g->setupLvlObjectBitmap(_andyObject);
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ break;
+ case 1:
+ _g->_plasmaCannonFlags |= 2;
+ break;
+ }
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen10() {
+ if (_res->_currentScreenResourceNum == 10) {
+ BoundingBox box = { 64, 0, 267, 191 };
+ _g->setAndyAnimationForArea(&box, 12);
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen11() {
+ if (_res->_currentScreenResourceNum == 11) {
+ BoundingBox box = { -12, 0, 162, 191 };
+ _g->setAndyAnimationForArea(&box, 12);
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ _g->_plasmaCannonFlags &= ~1;
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen15() {
+ if (_res->_currentScreenResourceNum == 15) {
+ _g->_plasmaCannonFlags &= ~1;
+ postScreenUpdate_rock_screen16();
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen16() {
+ switch (_res->_screensState[16].s0) {
+ case 0:
+ if (_screenCounterTable[16] < 2) {
+ if ((_andyObject->flags0 & 0x1F) == 2) {
+ break;
+ }
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (data->unk4 != 2 || _andyObject->xPos <= 155 || _andyObject->yPos >= 87) {
+ break;
+ }
+ ++_screenCounterTable[16];
+ _g->setShakeScreen(3, 4);
+ } else {
+ _res->_screensState[16].s0 = 2;
+ _g->_plasmaCannonFlags |= 1;
+ _g->setShakeScreen(1, 18);
+ }
+ break;
+ case 2:
+ ++_screenCounterTable[16];
+ if (_screenCounterTable[16] == 5) {
+ _res->_resLvlScreenBackgroundDataTable[16].currentMaskId = 1;
+ _g->setupScreenMask(16);
+ } else if (_screenCounterTable[16] == 23) {
+ _andyObject->flags1 &= ~0x30;
+ _andyObject->xPos = 131;
+ _andyObject->yPos = 177;
+ _andyObject->anim = 4;
+ _andyObject->frame = 0;
+ _g->_plasmaCannonFlags &= ~1;
+ } else if (_screenCounterTable[16] == 37) {
+ _res->_screensState[16].s0 = 3;
+ _res->_resLvlScreenBackgroundDataTable[16].currentBackgroundId = 1;
+ }
+ break;
+ case 3:
+ ++_screenCounterTable[16];
+ if (_screenCounterTable[16] == 55) {
+ _res->_screensState[16].s0 = 1;
+ }
+ break;
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen18() {
+ LvlObject *o;
+ switch (_res->_screensState[18].s0) {
+ case 0:
+ if (_andyObject->yPos + _andyObject->height < 162) {
+ if ((_andyObject->flags0 & 0x1F) == 0 && _screenCounterTable[18] == 0) {
+ _screenCounterTable[18] = 1;
+ } else {
+ ++_screenCounterTable[18];
+ if (_screenCounterTable[18] == 24) {
+ _res->_screensState[18].s0 = 2;
+ _res->_resLvlScreenBackgroundDataTable[18].currentMaskId = 2;
+ _g->setupScreenMask(18);
+ }
+ }
+ }
+ break;
+ case 2:
+ o = _g->findLvlObject(2, 0, 18);
+ ++_screenCounterTable[18];
+ if (_res->_version >= Resource::V1_2) {
+ o->actionKeyMask = 1;
+ } else if (_screenCounterTable[18] == 29) {
+ o->actionKeyMask = 1;
+ break;
+ }
+ if (_screenCounterTable[18] == 43) {
+ _g->setShakeScreen(2, 5);
+ _res->_resLvlScreenBackgroundDataTable[18].currentMaskId = 1;
+ _g->setupScreenMask(18);
+ } else if (_screenCounterTable[18] < (_res->_version >= Resource::V1_2 ? 51 : 57)) {
+ if ((o->flags0 & 0x1F) != 11 || (_andyObject->flags0 & 0x1F) == 11) {
+ break;
+ }
+ BoundingBox box = { 24, 98, 108, 165 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&box, &data->boundingBox)) {
+ _andyObject->anim = 155;
+ _andyObject->xPos = 59;
+ _andyObject->yPos = 150;
+ _andyObject->frame = 0;
+ _andyObject->flags2 = 0x300C;
+ _g->setupLvlObjectBitmap(_andyObject);
+ }
+ } else {
+ _res->_screensState[18].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[18].currentBackgroundId = 1;
+ }
+ break;
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen19() {
+ int fl;
+ switch (_res->_screensState[19].s0) {
+ case 0: {
+ BoundingBox box = { 155, 69, 210, 88 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (!_g->clipBoundingBox(&box, &data->boundingBox)) {
+ return;
+ }
+ _res->_screensState[19].s0 = 3;
+ }
+ break;
+ case 1:
+ if (_andyObject->xPos <= 139) {
+ _andyObject->actionKeyMask = 0x80;
+ _andyObject->directionKeyMask = 0;
+ }
+ fl = _andyObject->flags0 & 0x1F;
+ if (fl == 0 || fl == 2) {
+ fl = (_andyObject->flags0 >> 5) & 7;
+ if (fl == 7 || fl == 2) {
+ _res->_screensState[19].s0 = 4;
+ }
+ }
+ break;
+ case 2:
+ if (_andyObject->yPos < 1) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(2);
+ _paf->unload(2);
+ if (_res->_isDemo && !_paf->_skipCutscenes) {
+ _paf->play(21);
+ }
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ }
+ break;
+ case 3:
+ ++_screenCounterTable[19];
+ if (_screenCounterTable[19] == 1) {
+ _res->_resLvlScreenBackgroundDataTable[19].currentMaskId = 1;
+ _g->setupScreenMask(19);
+ _g->setAndySpecialAnimation(0x12);
+ } else if (_screenCounterTable[19] > 12) {
+ _res->_screensState[19].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[19].currentBackgroundId = 1;
+ }
+ break;
+ case 4:
+ ++_screenCounterTable[19];
+ if (_screenCounterTable[19] == 25) {
+ _g->setShakeScreen(2, 5);
+ _res->_resLvlScreenBackgroundDataTable[19].currentMaskId = 2;
+ _g->setupScreenMask(19);
+ } else if (_screenCounterTable[19] == 33) {
+ _res->_screensState[19].s0 = 2;
+ _res->_resLvlScreenBackgroundDataTable[19].currentBackgroundId = 2;
+ }
+ break;
+ }
+}
+
+void Level_rock::postScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ postScreenUpdate_rock_screen0();
+ break;
+ case 4:
+ postScreenUpdate_rock_screen4();
+ break;
+ case 8:
+ postScreenUpdate_rock_screen8();
+ break;
+ case 9:
+ postScreenUpdate_rock_screen9();
+ break;
+ case 10:
+ postScreenUpdate_rock_screen10();
+ break;
+ case 11:
+ postScreenUpdate_rock_screen11();
+ break;
+ case 13:
+ postScreenUpdate_rock_screen13();
+ break;
+ case 15:
+ postScreenUpdate_rock_screen15();
+ break;
+ case 16:
+ postScreenUpdate_rock_screen16();
+ break;
+ case 18:
+ postScreenUpdate_rock_screen18();
+ break;
+ case 19:
+ postScreenUpdate_rock_screen19();
+ break;
+ }
+}
+
+int Game::objectUpdate_rock_case0(LvlObject *o) {
+ return 1;
+}
+
+static const uint8_t _level1OpHelper1KeyMaskTable[112] = {
+ 8, 0, 8, 0, 8, 0, 8, 0, 8, 4, 9, 0, 9, 0, 9, 4,
+ 9, 0, 9, 0, 8, 4, 9, 0, 9, 4, 8, 0, 8, 4, 8, 4,
+ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4,
+ 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4,
+ 3, 0, 2, 4, 3, 0, 2, 4, 3, 4, 2, 0, 2, 4, 2, 4,
+ 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 4, 8, 4, 4, 0,
+ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 0, 8, 0, 4, 0
+};
+
+void Game::objectUpdate_rockShadow(LvlObject *ptr, uint8_t *p) {
+ const bool sameScreen = (_andyObject->screenNum == ptr->screenNum);
+ int i = (_andyObject->width / 2 + _andyObject->xPos) / 8;
+ if (i < 0 || ptr->screenNum != _res->_currentScreenResourceNum) {
+ i = 0;
+ } else if (i > 31) {
+ i = 31;
+ }
+ LvlObject *o = ptr->childPtr;
+ assert(o);
+ ptr->directionKeyMask = p[32 + i];
+ if (ptr->directionKeyMask != 0x80) {
+ p[67] = 0;
+ ptr->actionKeyMask = p[i];
+ } else {
+ if (p[67] != 0) {
+ --p[67];
+ } else {
+ p[67] = (_rnd.getNextNumber() >> 4) + 4;
+ p[66] = _rnd.getNextNumber() & 7;
+ }
+ const int index = p[i] * 8 + p[66];
+ ptr->directionKeyMask = _level1OpHelper1KeyMaskTable[index * 2];
+ ptr->actionKeyMask = _level1OpHelper1KeyMaskTable[index * 2 + 1];
+ }
+ if ((ptr->actionKeyMask & 4) != 0 && sameScreen && (ptr->flags0 & 0x300) == 0x300) {
+ if (clipLvlObjectsBoundingBox(_andyObject, ptr, 20)) {
+ _mstFlags |= 0x80000000;
+ setAndySpecialAnimation(0x80);
+ }
+ }
+ if ((ptr->directionKeyMask & 4) != 0) {
+ if (o->anim != 1 || o->frame != p[64]) {
+ ptr->directionKeyMask &= ~4;
+ }
+ }
+ if (_plasmaCannonDirection && (ptr->flags0 & 0x1F) != 0xB) {
+ if (plasmaCannonHit(ptr->childPtr)) {
+ ptr->actionKeyMask |= 0x20;
+ ++ptr->hitCount;
+ if (ptr->hitCount > p[65] || (_cheats & kCheatOneHitPlasmaCannon) != 0) {
+ ptr->hitCount = 0;
+ ptr->actionKeyMask |= 7;
+ }
+ _plasmaCannonExplodeFlag = true;
+ _plasmaCannonObject = ptr->childPtr;
+ }
+ }
+ o->directionKeyMask = ptr->directionKeyMask;
+ o->actionKeyMask = ptr->actionKeyMask;
+ updateAndyObject(ptr);
+ updateAndyObject(o);
+}
+
+bool Game::plasmaCannonHit(LvlObject *ptr) {
+ assert(ptr);
+ if (ptr->bitmapBits) {
+ int dx = 0;
+ if (ptr->screenNum == _currentLeftScreen) {
+ dx = -Video::W;
+ } else if (ptr->screenNum == _currentRightScreen) {
+ dx = Video::W;
+ } else if (ptr->screenNum != _currentScreen) {
+ return false;
+ }
+ if (testPlasmaCannonPointsDirection(ptr->xPos + dx, ptr->yPos, ptr->xPos + dx + ptr->width, ptr->yPos + ptr->height)) {
+ return true;
+ }
+ assert(_plasmaExplosionObject);
+ _plasmaExplosionObject->screenNum = ptr->screenNum;
+ }
+ return false;
+}
+
+// shadow screen2
+int Game::objectUpdate_rock_case1(LvlObject *o) {
+ static uint8_t data[68] = {
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x0C, 0x07, 0x00, 0x00
+ };
+ if (_level->_screenCounterTable[2] == 0) {
+ objectUpdate_rockShadow(o, data);
+ if ((o->flags0 & 0x3FF) == 0x4B) {
+ _level->_screenCounterTable[2] = 1;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+// shadow screen3
+int Game::objectUpdate_rock_case2(LvlObject *o) {
+ static uint8_t data[68] = {
+ 0x00, 0x00, 0x00, 0x00, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x04, 0x04, 0x04, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x06, 0x0B, 0x00, 0x00
+ };
+ if (_level->_screenCounterTable[3] == 0) {
+ objectUpdate_rockShadow(o, data);
+ if ((o->flags0 & 0x3FF) == 0x4B) {
+ _level->_screenCounterTable[3] = 1;
+ }
+ } else {
+ o->bitmapBits = 0;
+ o = o->childPtr;
+ assert(o);
+ o->anim = 0;
+ o->frame = 0;
+ o->xPos = 204;
+ o->yPos = 0;
+ setupLvlObjectBitmap(o);
+ }
+ return 1;
+}
+
+int Game::objectUpdate_rock_case3(LvlObject *o) {
+ updateAndyObject(o);
+ return 1;
+}
+
+int Game::objectUpdate_rock_case4(LvlObject *o) {
+ updateAndyObject(o);
+ updateAndyObject(o->childPtr);
+ return 1;
+}
+
+void Level_rock::preScreenUpdate_rock_screen0() {
+ switch (_res->_screensState[0].s0) {
+ case 0:
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 0;
+ break;
+ default:
+ _res->_screensState[0].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 1;
+ break;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen1() {
+ if (_checkpoint != 0) {
+ _res->_screensState[0].s0 = 1;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen2() {
+ if (_res->_currentScreenResourceNum == 2 && _checkpoint == 0) {
+ _checkpoint = 1;
+ } else if (_checkpoint > 1) {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 2);
+ if (ptr) {
+ ptr->anim = 0;
+ ptr->frame = 0;
+ }
+ _g->setupLvlObjectBitmap(ptr);
+ ptr = _g->findLvlObject(2, 1, 2);
+ if (ptr) {
+ ptr->anim = 0;
+ ptr->frame = 0;
+ }
+ _g->setupLvlObjectBitmap(ptr);
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen3() {
+ if (_checkpoint > 1) {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 3);
+ if (ptr) {
+ ptr->anim = 0;
+ ptr->frame = 0;
+ }
+ _g->setupLvlObjectBitmap(ptr);
+ ptr = _g->findLvlObject(2, 1, 3);
+ if (ptr) {
+ ptr->anim = 0;
+ ptr->frame = 0;
+ }
+ _g->setupLvlObjectBitmap(ptr);
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen4() {
+ int num;
+ switch (_res->_screensState[4].s0) {
+ case 0:
+ num = 0;
+ break;
+ case 1:
+ num = 1;
+ break;
+ default:
+ _res->_screensState[4].s0 = 1;
+ num = 1;
+ break;
+ }
+ _res->_resLvlScreenBackgroundDataTable[4].currentBackgroundId = num;
+ // bugfix: glitch when re-entering screen 4 (state 1) from screen 3.
+ // drawScreen() calls applyShadowColors() from 0 to shadowsCount. Setting
+ // currentShadowId to 1 will result in screen 3 shadow mask (index #0) to
+ // be applied where it shouldn't. Another fix would be iterating from
+ // currentShadowId to shadowsCount, similar to decodeShadowScreenMask.
+ // _res->_resLvlScreenBackgroundDataTable[4].currentShadowId = num;
+ _res->_resLvlScreenBackgroundDataTable[4].currentMaskId = num;
+ if (_res->_currentScreenResourceNum == 4 && _checkpoint == 1) {
+ _checkpoint = 2;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen5() {
+ if (_res->_currentScreenResourceNum == 5 && _checkpoint == 2) {
+ _checkpoint = 3;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen7() {
+ if (_res->_currentScreenResourceNum == 7 && _checkpoint == 3) {
+ _checkpoint = 4;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen9() {
+ switch (_res->_screensState[9].s0) {
+ case 0:
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(1);
+ }
+ _res->_resLvlScreenBackgroundDataTable[9].currentBackgroundId = 0;
+ break;
+ default:
+ _res->_screensState[9].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[9].currentBackgroundId = 1;
+ break;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen10() {
+ if (_res->_currentScreenResourceNum == 10) {
+ if (_checkpoint == 4) {
+ _checkpoint = 5;
+ }
+ if (!_paf->_skipCutscenes) {
+ _paf->unload(22);
+ _paf->preload(23);
+ }
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ if (_checkpoint == 5) {
+ _checkpoint = 6;
+ }
+ if (!_paf->_skipCutscenes) {
+ _paf->unload(1);
+ }
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen14() {
+ if (_res->_currentScreenResourceNum == 14) {
+ _res->_screensState[16].s0 = 0;
+ _screenCounterTable[16] = 0;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen15() {
+ if (_res->_currentScreenResourceNum == 15) {
+ if (_res->_screensState[16].s0 != 0) {
+ _g->_fallingAndyCounter = 2;
+ _g->_fallingAndyFlag = true;
+ }
+ _g->playAndyFallingCutscene(1);
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen16() {
+ uint8_t _al;
+ switch (_res->_screensState[16].s0) {
+ case 0:
+ _al = 0;
+ break;
+ default:
+ _res->_screensState[16].s0 = 1;
+ _g->_plasmaCannonFlags &= ~1;
+ _al = 1;
+ break;
+ }
+ _res->_resLvlScreenBackgroundDataTable[16].currentBackgroundId = _al;
+ _res->_resLvlScreenBackgroundDataTable[16].currentMaskId = _al;
+ if (_res->_currentScreenResourceNum == 16) {
+ _g->playAndyFallingCutscene(1);
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen17() {
+ if (_res->_currentScreenResourceNum == 17) {
+ if (_checkpoint == 6) {
+ _checkpoint = 7;
+ }
+ _g->playAndyFallingCutscene(1);
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen18() {
+ uint8_t _al;
+ switch (_res->_screensState[18].s0) {
+ case 0:
+ _al = 0;
+ break;
+ default:
+ _res->_screensState[18].s0 = 1;
+ _al = 1;
+ break;
+ }
+ _res->_resLvlScreenBackgroundDataTable[18].currentBackgroundId = _al;
+ _res->_resLvlScreenBackgroundDataTable[18].currentMaskId = _al;
+ if (_res->_currentScreenResourceNum == 18) {
+ _g->playAndyFallingCutscene(1);
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen19() {
+ uint8_t _al;
+ switch (_res->_screensState[19].s0) {
+ case 0:
+ _al = 0;
+ break;
+ case 1:
+ _al = 1;
+ break;
+ default:
+ _res->_screensState[19].s0 = 2;
+ _al = 2;
+ break;
+ }
+ _res->_resLvlScreenBackgroundDataTable[19].currentBackgroundId = _al;
+ _res->_resLvlScreenBackgroundDataTable[19].currentMaskId = _al;
+ if (_res->_currentScreenResourceNum == 19) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(2);
+ }
+ }
+}
+
+void Level_rock::preScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ preScreenUpdate_rock_screen0();
+ break;
+ case 1:
+ preScreenUpdate_rock_screen1();
+ break;
+ case 2:
+ preScreenUpdate_rock_screen2();
+ break;
+ case 3:
+ preScreenUpdate_rock_screen3();
+ break;
+ case 4:
+ preScreenUpdate_rock_screen4();
+ break;
+ case 5:
+ preScreenUpdate_rock_screen5();
+ break;
+ case 7:
+ preScreenUpdate_rock_screen7();
+ break;
+ case 9:
+ preScreenUpdate_rock_screen9();
+ break;
+ case 10:
+ preScreenUpdate_rock_screen10();
+ break;
+ case 13:
+ preScreenUpdate_rock_screen13();
+ break;
+ case 14:
+ preScreenUpdate_rock_screen14();
+ break;
+ case 15:
+ preScreenUpdate_rock_screen15();
+ break;
+ case 16:
+ preScreenUpdate_rock_screen16();
+ break;
+ case 17:
+ preScreenUpdate_rock_screen17();
+ break;
+ case 18:
+ preScreenUpdate_rock_screen18();
+ break;
+ case 19:
+ preScreenUpdate_rock_screen19();
+ break;
+ }
+}
+
+void Level_rock::initialize() {
+ if (!_paf->_skipCutscenes) {
+ if (_andyObject->spriteNum == 0) {
+ _paf->preload(22);
+ } else {
+ _paf->preload(23);
+ }
+ }
+}
+
+void Level_rock::tick() {
+ if (_res->_currentScreenResourceNum > 9) {
+ _g->_plasmaCannonFlags |= 2;
+ }
+}
+
+void Level_rock::terminate() {
+ if (!_paf->_skipCutscenes) {
+ if (_andyObject->spriteNum == 0) {
+ _paf->unload(22);
+ } else {
+ _paf->unload(23);
+ }
+ }
+}
+
+void Level_rock::setupScreenCheckpoint_rock_screen2() {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 2);
+ if (ptr) {
+ ptr->xPos = 146;
+ ptr->yPos = 0;
+ ptr->anim = 1;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ }
+ ptr = _g->findLvlObject(2, 1, 2);
+ if (ptr) {
+ ptr->xPos = 88;
+ ptr->yPos = 0;
+ ptr->anim = 1;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ }
+}
+
+void Level_rock::setupScreenCheckpoint_rock_screen3() {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 3);
+ if (ptr) {
+ ptr->xPos = 198;
+ ptr->yPos = 0;
+ ptr->anim = 1;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ }
+ ptr = _g->findLvlObject(2, 1, 3);
+ if (ptr) {
+ ptr->xPos = 116;
+ ptr->yPos = 0;
+ ptr->anim = 1;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ }
+}
+
+void Level_rock::setupScreenCheckpoint_rock_screen18() {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 18);
+ if (ptr) {
+ ptr->xPos = 16;
+ ptr->yPos = 78;
+ ptr->anim = 0;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ }
+}
+
+void Level_rock::setupScreenCheckpoint(int num) {
+ switch (num) {
+ case 2:
+ setupScreenCheckpoint_rock_screen2();
+ break;
+ case 3:
+ setupScreenCheckpoint_rock_screen3();
+ break;
+ case 18:
+ setupScreenCheckpoint_rock_screen18();
+ break;
+ }
+}
diff --git a/Src/Global/level2_fort.cpp b/Src/Global/level2_fort.cpp
new file mode 100644
index 0000000..cf3310e
--- /dev/null
+++ b/Src/Global/level2_fort.cpp
@@ -0,0 +1,347 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+// fort_hod - "swamp lands"
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "util.h"
+#include "video.h"
+
+static const CheckpointData _fort_checkpointData[5] = {
+ { 105, 46, 0x300c, 232, 0, 2 },
+ { 1, 103, 0x300c, 232, 2, 2 },
+ { 43, 96, 0x300c, 232, 6, 2 },
+ { 32, 97, 0x300c, 232, 9, 2 },
+ { 105, 52, 0x300c, 232, 17, 2 }
+};
+
+static const uint8_t _fort_screenStartData[56] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_fort: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_fort_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _fort_screenStartData; }
+ //virtual void initialize();
+ //virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_fort_screen1();
+ void postScreenUpdate_fort_screen6();
+ void postScreenUpdate_fort_screen7();
+ void postScreenUpdate_fort_screen8();
+ void postScreenUpdate_fort_screen16();
+ void postScreenUpdate_fort_screen17();
+ void postScreenUpdate_fort_screen21();
+
+ void preScreenUpdate_fort_screen1();
+ void preScreenUpdate_fort_screen2();
+ void preScreenUpdate_fort_screen6();
+ void preScreenUpdate_fort_screen9();
+ void preScreenUpdate_fort_screen14();
+ void preScreenUpdate_fort_screen16();
+ void preScreenUpdate_fort_screen17();
+ void preScreenUpdate_fort_screen21();
+
+ void setupScreenCheckpoint_fort_screen1();
+};
+
+Level *Level_fort_create() {
+ return new Level_fort;
+}
+
+void Level_fort::postScreenUpdate_fort_screen1() {
+ if (_res->_screensState[1].s0 == 2) {
+ LvlObject *o = _g->findLvlObject(2, 0, 1);
+ if (o) {
+ o->actionKeyMask = 1;
+ }
+ ++_screenCounterTable[1];
+ if (_screenCounterTable[1] == 25) {
+ _res->_resLvlScreenBackgroundDataTable[1].currentMaskId = 1;
+ _g->setupScreenMask(1);
+ } else if (_screenCounterTable[1] == 59) {
+ _res->_screensState[1].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[1].currentBackgroundId = 1;
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate_fort_screen6() {
+ if (_res->_currentScreenResourceNum == 6) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ BoundingBox b = { 0, 0, 43, 191 };
+ if (!_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _andyObject->actionKeyMask &= ~3;
+ _andyObject->actionKeyMask |= 8;
+ if (_andyObject->directionKeyMask & 4) {
+ _andyObject->directionKeyMask &= ~10;
+ ++_screenCounterTable[6];
+ if (_screenCounterTable[6] >= 188) {
+ _andyObject->directionKeyMask &= ~4;
+ _screenCounterTable[7] = 0;
+ }
+ }
+ if (_screenCounterTable[7] < 25) {
+ ++_screenCounterTable[7];
+ _andyObject->directionKeyMask &= ~4;
+ } else if ((_andyObject->directionKeyMask & 4) == 0) {
+ _screenCounterTable[6] = 0;
+ }
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate_fort_screen7() {
+ if (_res->_currentScreenResourceNum == 7) {
+ _andyObject->actionKeyMask &= ~3;
+ _andyObject->actionKeyMask |= 8;
+ if (_andyObject->directionKeyMask & 4) {
+ _andyObject->directionKeyMask &= ~0xA;
+ ++_screenCounterTable[6];
+ if (_screenCounterTable[6] >= 188) {
+ _andyObject->directionKeyMask &= ~4;
+ _screenCounterTable[7] = 0;
+ }
+ }
+ if (_screenCounterTable[7] < 25) {
+ ++_screenCounterTable[7];
+ _andyObject->directionKeyMask &= ~4;
+ } else if ((_andyObject->directionKeyMask & 4) == 0) {
+ _screenCounterTable[6] = 0;
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate_fort_screen8() {
+ if (_res->_currentScreenResourceNum == 8) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ BoundingBox b = { 138, 0, 255, 191 };
+ if (!_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _andyObject->actionKeyMask &= ~3;
+ _andyObject->actionKeyMask |= 8;
+ if (_andyObject->directionKeyMask & 4) {
+ _andyObject->directionKeyMask &= ~0xA;
+ ++_screenCounterTable[6];
+ if (_screenCounterTable[6] >= 188) {
+ _andyObject->directionKeyMask &= ~4;
+ _screenCounterTable[7] = 0;
+ }
+ }
+ if (_screenCounterTable[7] < 25) {
+ ++_screenCounterTable[7];
+ _andyObject->directionKeyMask &= ~4;
+ } else if ((_andyObject->directionKeyMask & 4) == 0) {
+ _screenCounterTable[6] = 0;
+ }
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate_fort_screen16() {
+ if (_res->_currentScreenResourceNum == 16) {
+ if (_res->_screensState[16].s0 != 1) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ BoundingBox b = { 150, 0, 187, 60 };
+ if (!_g->clipBoundingBox(&b, &data->boundingBox)) {
+ return;
+ }
+ if ((_andyObject->flags0 & 0x1F) == 0 && (_andyObject->flags0 & 0xE0) == 0xE0) {
+ _res->_screensState[16].s0 = 1;
+ _screenCounterTable[16] = 0;
+ } else {
+ _g->setAndySpecialAnimation(3);
+ }
+ } else {
+ ++_screenCounterTable[16];
+ if (_screenCounterTable[16] == 5) {
+ if (_checkpoint == 3) {
+ _checkpoint = 4;
+ }
+ if (!_paf->_skipCutscenes) {
+ _paf->play(3);
+ _paf->unload(3);
+ }
+ _video->clearPalette();
+ _g->restartLevel();
+ }
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate_fort_screen17() {
+ if (_res->_screensState[17].s0 == 1) {
+ ++_screenCounterTable[17];
+ if (_screenCounterTable[17] == 68) {
+ _res->_screensState[17].s0 = 0;
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate_fort_screen21() {
+ if (_res->_currentScreenResourceNum == 21) {
+ switch (_res->_screensState[21].s0) {
+ case 1:
+ ++_screenCounterTable[21];
+ if (_screenCounterTable[21] == 22) {
+ _res->_screensState[21].s0 = 2;
+ }
+ break;
+ case 2:
+ if (!_paf->_skipCutscenes) {
+ _paf->play(4);
+ _paf->unload(4);
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ break;
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate(int num) {
+ switch (num) {
+ case 1:
+ postScreenUpdate_fort_screen1();
+ break;
+ case 6:
+ postScreenUpdate_fort_screen6();
+ break;
+ case 7:
+ postScreenUpdate_fort_screen7();
+ break;
+ case 8:
+ postScreenUpdate_fort_screen8();
+ break;
+ case 16:
+ postScreenUpdate_fort_screen16();
+ break;
+ case 17:
+ postScreenUpdate_fort_screen17();
+ break;
+ case 21:
+ postScreenUpdate_fort_screen21();
+ break;
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen1() {
+ if (_res->_currentScreenResourceNum == 1 && _checkpoint >= 1) {
+ _res->_screensState[1].s0 = 1;
+ }
+ const int num = _res->_screensState[1].s0 == 0 ? 0 : 1;
+ _res->_resLvlScreenBackgroundDataTable[1].currentBackgroundId = num;
+ _res->_resLvlScreenBackgroundDataTable[1].currentMaskId = num;
+}
+
+void Level_fort::preScreenUpdate_fort_screen2() {
+ if (_res->_currentScreenResourceNum == 2 && _checkpoint == 0) {
+ _checkpoint = 1;
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen6() {
+ if (_res->_currentScreenResourceNum == 6 && _checkpoint == 1) {
+ _checkpoint = 2;
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen9() {
+ if (_res->_currentScreenResourceNum == 9 && _checkpoint == 2) {
+ _checkpoint = 3;
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen14() {
+ if (_res->_currentScreenResourceNum == 14) {
+ _res->_resLvlScreenBackgroundDataTable[14].currentBackgroundId = _res->_screensState[14].s0 != 0 ? 1 : 0;
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen16() {
+ if (_res->_currentScreenResourceNum == 16) {
+ _res->_screensState[16].s0 = 0;
+ _andyObject->xPos += 9;
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(3);
+ }
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen17() {
+ if (_res->_currentScreenResourceNum == 17) {
+ _res->_screensState[17].s0 = _screenCounterTable[17] == 0 ? 1 : 0;
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen21() {
+ if (_res->_currentScreenResourceNum == 21) {
+ _res->_screensState[21].s0 = 0;
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(4);
+ }
+ }
+}
+
+void Level_fort::tick() {
+ _g->_plasmaCannonFlags |= 2;
+}
+
+void Level_fort::preScreenUpdate(int num) {
+ switch (num) {
+ case 1:
+ preScreenUpdate_fort_screen1();
+ break;
+ case 2:
+ preScreenUpdate_fort_screen2();
+ break;
+ case 6:
+ preScreenUpdate_fort_screen6();
+ break;
+ case 9:
+ preScreenUpdate_fort_screen9();
+ break;
+ case 14:
+ preScreenUpdate_fort_screen14();
+ break;
+ case 16:
+ preScreenUpdate_fort_screen16();
+ break;
+ case 17:
+ preScreenUpdate_fort_screen17();
+ break;
+ case 21:
+ preScreenUpdate_fort_screen21();
+ break;
+ }
+}
+
+void Level_fort::setupScreenCheckpoint_fort_screen1() {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 1);
+ if (ptr) {
+ ptr->xPos = 129;
+ ptr->yPos = 97;
+ ptr->anim = 0;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ }
+}
+
+void Level_fort::setupScreenCheckpoint(int num) {
+ switch (num) {
+ case 1:
+ setupScreenCheckpoint_fort_screen1();
+ break;
+ }
+}
diff --git a/Src/Global/level3_pwr1.cpp b/Src/Global/level3_pwr1.cpp
new file mode 100644
index 0000000..52d718a
--- /dev/null
+++ b/Src/Global/level3_pwr1.cpp
@@ -0,0 +1,532 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+// pwr1_hod - "magic lake"
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "util.h"
+#include "video.h"
+
+static const CheckpointData _pwr1_checkpointData[10] = {
+ { 190, 48, 0x300c, 196, 1, 2 },
+ { 158, 89, 0x300c, 232, 6, 2 },
+ { 71, 128, 0x300c, 196, 9, 2 },
+ { 7, 145, 0x300c, 196, 15, 2 },
+ { 50, 62, 0x300c, 196, 18, 2 },
+ { 9, 25, 0x300c, 232, 21, 2 },
+ { 19, 73, 0x300c, 232, 27, 2 },
+ { 225, 73, 0x300c, 232, 26, 2 },
+ { 100, 121, 0x300c, 232, 29, 2 },
+ { 80, 121, 0x700c, 232, 31, 2 }
+};
+
+static const uint8_t _pwr1_screenStartData[80] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_pwr1: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_pwr1_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _pwr1_screenStartData; }
+ virtual void initialize();
+ virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ //virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_pwr1_helper(BoundingBox *b, int dx, int dy);
+ void postScreenUpdate_pwr1_screen6();
+ void postScreenUpdate_pwr1_screen10();
+ void postScreenUpdate_pwr1_screen12();
+ void postScreenUpdate_pwr1_screen14();
+ void postScreenUpdate_pwr1_screen16();
+ void postScreenUpdate_pwr1_screen18();
+ void postScreenUpdate_pwr1_screen23();
+ void postScreenUpdate_pwr1_screen27();
+ void postScreenUpdate_pwr1_screen35();
+
+ void preScreenUpdate_pwr1_screen4();
+ void preScreenUpdate_pwr1_screen6();
+ void preScreenUpdate_pwr1_screen9();
+ void preScreenUpdate_pwr1_screen15();
+ void preScreenUpdate_pwr1_screen21();
+ void preScreenUpdate_pwr1_screen23();
+ void preScreenUpdate_pwr1_screen24();
+ void preScreenUpdate_pwr1_screen26();
+ void preScreenUpdate_pwr1_screen27();
+ void preScreenUpdate_pwr1_screen29();
+ void preScreenUpdate_pwr1_screen31();
+ void preScreenUpdate_pwr1_screen35();
+};
+
+Level *Level_pwr1_create() {
+ return new Level_pwr1;
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_helper(BoundingBox *b, int dx, int dy) {
+ LvlObject *o = _g->_lvlObjectsList3;
+ while (o) {
+ if (o->spriteNum == 4 && o->anim <= 9) {
+ BoundingBox b2;
+ b2.x1 = o->posTable[3].x + o->xPos - 1;
+ b2.x2 = o->posTable[3].x + o->xPos + 1;
+ b2.y1 = o->posTable[3].y + o->yPos - 1;
+ b2.y2 = o->posTable[3].y + o->yPos + 1;
+ if (_g->clipBoundingBox(b, &b2)) {
+ o->xPos += dx;
+ o->yPos += dy;
+ }
+ }
+ o = o->nextPtr;
+ }
+ BoundingBox b2;
+ b2.x1 = _andyObject->posTable[3].x + _andyObject->xPos - 1;
+ b2.x2 = _andyObject->posTable[3].x + _andyObject->xPos + 1;
+ b2.y1 = _andyObject->posTable[3].y + _andyObject->yPos - 1;
+ b2.y2 = _andyObject->posTable[3].y + _andyObject->yPos + 1;
+ if (_g->clipBoundingBox(b, &b2)) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ data->dxPos += dx;
+ data->dyPos += dy;
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen6() {
+ LvlBackgroundData *dat = &_res->_resLvlScreenBackgroundDataTable[6];
+ switch (_res->_screensState[6].s0) {
+ case 1: {
+ BoundingBox b = { 185, 78, 225, 136 };
+ LvlObject *o = _g->findLvlObjectBoundingBox(&b);
+ if (o) {
+ ShootLvlObjectData *oosd = (ShootLvlObjectData *)_g->getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ if (oosd->type == 6) {
+ _res->_screensState[6].s0 = 4;
+ dat->currentMaskId = 2;
+ }
+ }
+ }
+ break;
+ case 2:
+ break;
+ case 3:
+ ++_screenCounterTable[6];
+ if (_screenCounterTable[6] >= 41) {
+ _res->_screensState[6].s0 = 1;
+ dat->currentMaskId = 1;
+ dat->currentBackgroundId = 1;
+ if (_checkpoint == 0) {
+ _checkpoint = 1;
+ }
+ }
+ break;
+ case 4:
+ ++_screenCounterTable[6];
+ if (_screenCounterTable[6] >= 54) {
+ _res->_screensState[6].s0 = 2;
+ dat->currentBackgroundId = 1;
+ }
+ break;
+ default: {
+ BoundingBox b = { 93, 17, 255, 156 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _res->_screensState[6].s0 = 3;
+ _g->setShakeScreen(3, 31);
+ }
+ }
+ break;
+ }
+ if (_res->_screensState[6].s3 != dat->currentMaskId) {
+ _g->setupScreenMask(6);
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen10() {
+ if (_res->_currentScreenResourceNum == 10) {
+ BoundingBox b1 = { 50, 0, 100, 122 };
+ postScreenUpdate_pwr1_helper(&b1, 0, 2);
+ BoundingBox b2 = { 58, 50, 84, 132 };
+ postScreenUpdate_pwr1_helper(&b2, 0, 2);
+ BoundingBox b3 = { 42, 80, 72, 142 };
+ postScreenUpdate_pwr1_helper(&b3, 4, 0);
+ BoundingBox b4 = { 73, 100, 94, 142 };
+ postScreenUpdate_pwr1_helper(&b4, -2, 0);
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen12() {
+ if (_res->_currentScreenResourceNum == 12) {
+ BoundingBox b1 = { 64, 124, 255, 166 };
+ postScreenUpdate_pwr1_helper(&b1, -1, 0);
+ BoundingBox b2 = { 56, 134, 160, 160 };
+ postScreenUpdate_pwr1_helper(&b2, -2, 0);
+ BoundingBox b3 = { 55, 124, 82, 146 };
+ postScreenUpdate_pwr1_helper(&b3, 0, 2);
+ BoundingBox b4 = { 55, 147, 82, 166 };
+ postScreenUpdate_pwr1_helper(&b4, 0, -2);
+ BoundingBox b5 = { 64, 46, 255, 90 };
+ postScreenUpdate_pwr1_helper(&b5, -2, 0);
+ BoundingBox b6 = { 56, 56, 255, 80 };
+ postScreenUpdate_pwr1_helper(&b6, -3, 0);
+ BoundingBox b7 = { 55, 46, 82, 68 };
+ postScreenUpdate_pwr1_helper(&b7, 0, 2);
+ BoundingBox b8 = { 55, 69, 82, 90 };
+ postScreenUpdate_pwr1_helper(&b8, 0, -2);
+ BoundingBox b9 = { 154, 52, 198, 191 };
+ postScreenUpdate_pwr1_helper(&b9, 0, -2);
+ BoundingBox bA = { 164, 44, 188, 191 };
+ postScreenUpdate_pwr1_helper(&bA, 0, -2);
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen14() {
+ if (_res->_currentScreenResourceNum == 14) {
+ BoundingBox b1 = { 0, 136, 104, 191 };
+ postScreenUpdate_pwr1_helper(&b1, 2, 0);
+ BoundingBox b2 = { 0, 152, 112, 178 };
+ postScreenUpdate_pwr1_helper(&b2, 2, 0);
+ BoundingBox b3 = { 76, 148, 116, 164 };
+ postScreenUpdate_pwr1_helper(&b3, 0, 2);
+ BoundingBox b4 = { 76, 164, 116, 186 };
+ postScreenUpdate_pwr1_helper(&b4, 0, -2);
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen16() {
+ if (_res->_currentScreenResourceNum == 16) {
+ BoundingBox b1 = { 100, 130, 160, 176 };
+ postScreenUpdate_pwr1_helper(&b1, 2, 0);
+ BoundingBox b2 = { 88, 140, 180, 167 };
+ postScreenUpdate_pwr1_helper(&b2, 2, 0);
+ BoundingBox b3 = { 88, 82, 185, 120 };
+ postScreenUpdate_pwr1_helper(&b3, 2, 0);
+ BoundingBox b4 = { 120, 92, 190, 114 };
+ postScreenUpdate_pwr1_helper(&b4, 2, 0);
+ BoundingBox b5 = { 104, 40, 147, 120 };
+ postScreenUpdate_pwr1_helper(&b5, 0, -1);
+ BoundingBox b6 = { 115, 24, 138, 90 };
+ postScreenUpdate_pwr1_helper(&b6, 0, -1);
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen18() {
+ if (_res->_currentScreenResourceNum == 18) {
+ if (_checkpoint == 3) {
+ BoundingBox b = { 0, 0, 123, 125 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ ++_checkpoint;
+ }
+ }
+ BoundingBox b1 = { 156, 80, 204, 144 };
+ postScreenUpdate_pwr1_helper(&b1, 0, -2);
+ BoundingBox b2 = { 166, 88, 194, 152 };
+ postScreenUpdate_pwr1_helper(&b2, 0, -2);
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen23() {
+ switch (_res->_screensState[23].s0) {
+ case 2:
+ ++_screenCounterTable[23];
+ if (_screenCounterTable[23] == 26) {
+ _res->_screensState[23].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[23].currentMaskId = 1;
+ _res->_resLvlScreenBackgroundDataTable[23].currentBackgroundId = 1;
+ _g->setupScreenMask(23);
+ }
+ break;
+ case 0:
+ if (_res->_currentScreenResourceNum == 23) {
+ BoundingBox b = { 26, 94, 63, 127 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ uint8_t flags = _andyObject->flags0;
+ if ((flags & 0x1F) == 0 && (flags & 0xE0) == 0xE0) {
+ _res->_screensState[23].s0 = 2;
+ } else {
+ _g->setAndySpecialAnimation(3);
+ }
+ }
+ }
+ break;
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen27() {
+ switch (_res->_screensState[27].s0) {
+ case 2:
+ ++_screenCounterTable[27];
+ if (_screenCounterTable[27] == 37) {
+ _res->_screensState[27].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[27].currentMaskId = 1;
+ _res->_resLvlScreenBackgroundDataTable[27].currentBackgroundId = 1;
+ _g->setupScreenMask(27);
+ }
+ break;
+ case 0:
+ if (_res->_currentScreenResourceNum == 27 && (_andyObject->flags0 & 0x1F) == 6) {
+ BoundingBox b1;
+ b1.x1 = _andyObject->xPos + _andyObject->posTable[7].x - 3;
+ b1.x2 = _andyObject->xPos + _andyObject->posTable[7].x + 4;
+ b1.y1 = _andyObject->yPos + _andyObject->posTable[7].y - 2;
+ b1.y2 = _andyObject->yPos + _andyObject->posTable[7].y + 2;
+ BoundingBox b2 = { 25, 163, 31, 188 };
+ if (_g->clipBoundingBox(&b2, &b1)) {
+ ++_screenCounterTable[27];
+ if (_screenCounterTable[27] == 9) {
+ _res->_screensState[27].s0 = 2;
+ }
+ }
+ }
+ break;
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen35() {
+ if (_res->_currentScreenResourceNum == 35) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ BoundingBox b = { 0, 0, 193, 88 };
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _andyObject->actionKeyMask &= ~3;
+ _andyObject->directionKeyMask &= ~4;
+ _andyObject->actionKeyMask |= 8;
+ }
+ if (_res->_screensState[35].s0 != 0) {
+ ++_screenCounterTable[35];
+ if (_screenCounterTable[35] == 46) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(5);
+ _paf->unload(5);
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ }
+ }
+ }
+}
+
+void Level_pwr1::postScreenUpdate(int num) {
+ switch (num) {
+ case 6:
+ postScreenUpdate_pwr1_screen6();
+ break;
+ case 10:
+ postScreenUpdate_pwr1_screen10();
+ break;
+ case 12:
+ postScreenUpdate_pwr1_screen12();
+ break;
+ case 14:
+ postScreenUpdate_pwr1_screen14();
+ break;
+ case 16:
+ postScreenUpdate_pwr1_screen16();
+ break;
+ case 18:
+ postScreenUpdate_pwr1_screen18();
+ break;
+ case 23:
+ postScreenUpdate_pwr1_screen23();
+ break;
+ case 27:
+ postScreenUpdate_pwr1_screen27();
+ break;
+ case 35:
+ postScreenUpdate_pwr1_screen35();
+ break;
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen4() {
+ if (_res->_currentScreenResourceNum == 4) {
+ const uint8_t num = (_res->_screensState[4].s0 == 0) ? 0 : 1;
+ _res->_resLvlScreenBackgroundDataTable[4].currentBackgroundId = num;
+ _res->_resLvlScreenBackgroundDataTable[4].currentMaskId = num;
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen6() {
+ if (_res->_currentScreenResourceNum == 6 || _res->_currentScreenResourceNum == 5) {
+ if (_res->_screensState[6].s0 == 0) {
+ if (_checkpoint != 1) {
+ _screenCounterTable[6] = 0;
+ _res->_resLvlScreenBackgroundDataTable[6].currentBackgroundId = 0;
+ _res->_resLvlScreenBackgroundDataTable[6].currentMaskId = 0;
+ _res->_screensState[6].s0 = 0;
+ } else {
+ _screenCounterTable[6] = 41;
+ _res->_resLvlScreenBackgroundDataTable[6].currentBackgroundId = 1;
+ _res->_resLvlScreenBackgroundDataTable[6].currentMaskId = 1;
+ _res->_screensState[6].s0 = 1;
+ }
+ } else {
+ _screenCounterTable[6] = 54;
+ _res->_resLvlScreenBackgroundDataTable[6].currentBackgroundId = 2;
+ _res->_resLvlScreenBackgroundDataTable[6].currentMaskId = 2;
+ _res->_screensState[6].s0 = 2;
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen9() {
+ if (_res->_currentScreenResourceNum == 9) {
+ if (_checkpoint == 1) {
+ _checkpoint = 2;
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen15() {
+ if (_res->_currentScreenResourceNum == 15) {
+ if (_checkpoint == 2) {
+ _checkpoint = 3;
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen21() {
+ if (_res->_currentScreenResourceNum == 21) {
+ if (_checkpoint == 4) {
+ _checkpoint = 5;
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen23() {
+ if (_res->_currentScreenResourceNum == 23 || _res->_currentScreenResourceNum == 26) {
+ const uint8_t num = _res->_screensState[23].s0 != 0 ? 1 : 0;
+ _res->_resLvlScreenBackgroundDataTable[23].currentBackgroundId = num;
+ _res->_resLvlScreenBackgroundDataTable[23].currentMaskId = num;
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen24() {
+ if (_res->_currentScreenResourceNum == 24) {
+ if (_res->_screensState[27].s0 != 0) {
+ if (_checkpoint == 6) {
+ _checkpoint = 7;
+ }
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen26() {
+ if (_checkpoint >= 7) {
+ _res->_screensState[23].s0 = 1;
+ }
+ preScreenUpdate_pwr1_screen23();
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen27() {
+ if (_res->_currentScreenResourceNum == 27) {
+ uint8_t num = 0;
+ if (_res->_screensState[27].s0 != 0 || _checkpoint >= 7) {
+ num = 1;
+ _screenCounterTable[27] = 37;
+ } else if (_checkpoint <= 5) {
+ _screenCounterTable[27] = 0;
+ if (_checkpoint == 5) {
+ _checkpoint = 6;
+ }
+ }
+ _res->_screensState[27].s0 = num;
+ _res->_resLvlScreenBackgroundDataTable[27].currentBackgroundId = num;
+ _res->_resLvlScreenBackgroundDataTable[27].currentMaskId = num;
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen29() {
+ if (_res->_currentScreenResourceNum == 29) {
+ if (_checkpoint == 7) {
+ _checkpoint = 8;
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen31() {
+ if (_res->_currentScreenResourceNum == 31) {
+ if (_checkpoint == 8) {
+ _checkpoint = 9;
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen35() {
+ if (_res->_currentScreenResourceNum == 35) {
+ _screenCounterTable[35] = 0;
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(5);
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate(int num) {
+ switch (num) {
+ case 4:
+ preScreenUpdate_pwr1_screen4();
+ break;
+ case 6:
+ preScreenUpdate_pwr1_screen6();
+ break;
+ case 9:
+ preScreenUpdate_pwr1_screen9();
+ break;
+ case 15:
+ preScreenUpdate_pwr1_screen15();
+ break;
+ case 21:
+ preScreenUpdate_pwr1_screen21();
+ break;
+ case 23:
+ preScreenUpdate_pwr1_screen23();
+ break;
+ case 24:
+ preScreenUpdate_pwr1_screen24();
+ break;
+ case 26:
+ preScreenUpdate_pwr1_screen26();
+ break;
+ case 27:
+ preScreenUpdate_pwr1_screen27();
+ break;
+ case 29:
+ preScreenUpdate_pwr1_screen29();
+ break;
+ case 31:
+ preScreenUpdate_pwr1_screen31();
+ break;
+ case 35:
+ preScreenUpdate_pwr1_screen35();
+ break;
+ }
+}
+
+void Level_pwr1::initialize() {
+ _g->loadTransformLayerData(Game::_pwr1_screenTransformData);
+ _g->resetWormHoleSprites();
+}
+
+void Level_pwr1::terminate() {
+ _g->unloadTransformLayerData();
+}
+
+const uint8_t Game::_pwr1_screenTransformLut[] = {
+ 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0,
+ 1, 2, 1, 0, 1, 3, 1, 0, 1, 0, 1, 4, 1, 0, 1, 0, 1, 0,
+ 1, 5, 1, 0, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0,
+ 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+void Level_pwr1::tick() {
+ _video->_displayShadowLayer = Game::_pwr1_screenTransformLut[_res->_currentScreenResourceNum * 2] != 0;
+ _g->updateWormHoleSprites();
+}
diff --git a/SRC/level4_isld.cpp b/Src/Global/level4_isld.cpp
similarity index 100%
rename from SRC/level4_isld.cpp
rename to Src/Global/level4_isld.cpp
diff --git a/Src/Global/level5_lava.cpp b/Src/Global/level5_lava.cpp
new file mode 100644
index 0000000..20d46cf
--- /dev/null
+++ b/Src/Global/level5_lava.cpp
@@ -0,0 +1,619 @@
+
+// lava_hod - "river of fire"
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "util.h"
+#include "video.h"
+
+static const CheckpointData _lava_checkpointData[6] = {
+ { 114, 54, 0x300c, 2, 0, 2 },
+ { 12, 121, 0x300c, 232, 3, 2 },
+ { -5, 96, 0x300c, 39, 5, 2 },
+ { 131, 112, 0x300c, 39, 7, 2 },
+ { 8, 105, 0x300c, 232, 11, 2 },
+ { 19, 112, 0x700c, 39, 13, 2 }
+};
+
+static const uint8_t _lava_screenStartData[40] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_lava: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_lava_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _lava_screenStartData; }
+ virtual void initialize();
+ virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_lava_screen0();
+ void postScreenUpdate_lava_screen1();
+ void postScreenUpdate_lava_screen2_updateMask(int x, int y, int h, int unk, int screenNum, const uint8_t *p);
+ void postScreenUpdate_lava_screen2_addLvlObjects(const uint8_t *p);
+ void postScreenUpdate_lava_screen2();
+ void postScreenUpdate_lava_screen3_updateMask(int x, int y, int w, int h, int screenNum, uint8_t mask);
+ void postScreenUpdate_lava_screen3_updatePlatform(LvlObject *o, BoundingBox *b);
+ void postScreenUpdate_lava_screen3();
+ void postScreenUpdate_lava_screen4();
+ void postScreenUpdate_lava_screen5();
+ void postScreenUpdate_lava_screen6();
+ void postScreenUpdate_lava_screen7();
+ void postScreenUpdate_lava_screen8();
+ void postScreenUpdate_lava_screen10();
+ void postScreenUpdate_lava_screen11();
+ void postScreenUpdate_lava_screen12();
+ void postScreenUpdate_lava_screen13();
+ void postScreenUpdate_lava_screen14();
+ void postScreenUpdate_lava_screen15();
+
+ void preScreenUpdate_lava_screen0();
+ void preScreenUpdate_lava_screen3();
+ void preScreenUpdate_lava_screen6();
+ void preScreenUpdate_lava_screen10();
+ void preScreenUpdate_lava_screen13();
+ void preScreenUpdate_lava_screen15();
+
+ void setupScreenCheckpoint_lava_screen3();
+
+ uint8_t _screen1Counter;
+ uint8_t _screen2Counter;
+};
+
+Level *Level_lava_create() {
+ return new Level_lava;
+}
+
+static LvlObject *findLvlObject_lava(LvlObject *o) {
+ LvlObject *cur = o->nextPtr;
+ while (cur) {
+ if (o->type == cur->type && o->spriteNum == cur->spriteNum && o->screenNum == cur->screenNum) {
+ return cur;
+ }
+ cur = cur->nextPtr;
+ }
+ return 0;
+}
+
+void Level_lava::postScreenUpdate_lava_screen0() {
+ switch (_res->_screensState[0].s0) {
+ case 2:
+ ++_screenCounterTable[0];
+ if (_screenCounterTable[0] >= 11) {
+ _res->_screensState[0].s0 = 1;
+ }
+ break;
+ case 0:
+ if (_andyObject->anim == 2 && _andyObject->frame > 2) {
+ _res->_screensState[0].s0 = 2;
+ }
+ break;
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen1() {
+ BoundingBox b;
+ b.x1 = _andyObject->xPos + _andyObject->posTable[4].x;
+ b.y1 = _andyObject->yPos + _andyObject->posTable[4].y;
+ b.x2 = _andyObject->xPos + _andyObject->posTable[5].x;
+ b.y2 = _andyObject->yPos + _andyObject->posTable[5].y;
+ BoundingBox b2 = { 48, 156, 79, 167 };
+ if (_res->_currentScreenResourceNum == 1 && (_andyObject->flags0 & 0x1F) != 2 && _g->clipBoundingBox(&b, &b2)) {
+ LvlObject *o = _g->findLvlObject2(0, 0, 1);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ _screen1Counter = 51;
+ } else {
+ if (_screen1Counter != 0) {
+ --_screen1Counter;
+ }
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen2_updateMask(int x, int y, int h, int flag, int screenNum, const uint8_t *p) {
+ if (x < 0) {
+ x = 0;
+ }
+ if (y < 0) {
+ y = 0;
+ }
+ uint32_t maskOffset = Game::screenMaskOffset(_res->_screensBasePos[screenNum].u + x, _res->_screensBasePos[screenNum].v + y);
+ uint8_t *dst = _g->_screenMaskBuffer + maskOffset;
+ if (flag < 0) {
+ h >>= 3;
+ const int count = -flag;
+ for (int i = 0; i < h; ++i) {
+ memset(dst + (int8_t)p[0], 0, count); p += 2;
+ dst -= 512;
+ }
+ } else {
+ h >>= 3;
+ const int count = flag;
+ for (int i = 0; i < h; ++i) {
+ memset(dst + (int8_t)p[0], p[1], count); p += 2;
+ dst -= 512;
+ }
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen2_addLvlObjects(const uint8_t *p) {
+ do {
+ LvlObject *ptr = 0;
+ if (_g->_declaredLvlObjectsListCount < Game::kMaxLvlObjects) {
+ ptr = _g->_declaredLvlObjectsNextPtr;
+ _g->_declaredLvlObjectsNextPtr = _g->_declaredLvlObjectsNextPtr->nextPtr;
+ ++_g->_declaredLvlObjectsListCount;
+ ptr->spriteNum = 22;
+ ptr->type = 8;
+ _res->incLvlSpriteDataRefCounter(ptr);
+ _g->lvlObjectTypeCallback(ptr);
+ ptr->currentSprite = 0;
+ ptr->sssObject = 0;
+ ptr->nextPtr = 0;
+ }
+ if (ptr) {
+ ptr->nextPtr = _g->_lvlObjectsList3;
+ _g->_lvlObjectsList3 = ptr;
+ ptr->callbackFuncPtr = &Game::lvlObjectList3Callback;
+ ptr->screenNum = _res->_currentScreenResourceNum;
+ const uint16_t flags2 = READ_LE_UINT16(p + 4);
+ ptr->flags2 = flags2;
+ ptr->flags1 = (ptr->flags1 & ~0x30) | ((flags2 >> 10) & 0x30);
+ ptr->anim = READ_LE_UINT16(p + 2);
+ ptr->frame = 0;
+ _g->setupLvlObjectBitmap(ptr);
+ _g->setLvlObjectPosRelativeToPoint(ptr, 7, p[0], p[1]);
+ }
+ p += 6;
+ } while (READ_LE_UINT16(p + 4) != 0xFFFF);
+}
+
+void Level_lava::postScreenUpdate_lava_screen2() {
+ BoundingBox b;
+ b.x1 = _andyObject->xPos + _andyObject->posTable[4].x;
+ b.y1 = _andyObject->yPos + _andyObject->posTable[4].y;
+ b.x2 = _andyObject->xPos + _andyObject->posTable[5].x;
+ b.y2 = _andyObject->yPos + _andyObject->posTable[5].y;
+ BoundingBox b2 = { 40, 156, 72, 165 };
+ if (_res->_currentScreenResourceNum == 2 && (_andyObject->flags0 & 0x1F) != 2 && _g->clipBoundingBox(&b, &b2)) {
+ LvlObject *o = _g->findLvlObject2(0, 0, 2);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ _screen2Counter = 13;
+ } else {
+ if (_screen2Counter != 0) {
+ --_screen2Counter;
+ }
+ }
+ LvlObject *o = _g->findLvlObject(2, 0, 2);
+ assert(o);
+ if (_screen2Counter == 0) {
+ o->directionKeyMask = 4;
+ } else {
+ o->directionKeyMask = 1;
+ }
+ static const uint8_t maskData1[3 * 8] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
+ 0x00, 0x02, 0x00, 0x02, 0x01, 0x02, 0x01, 0x02,
+ 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02
+ };
+ postScreenUpdate_lava_screen2_updateMask(o->xPos + o->posTable[7].x, o->yPos + o->height - 1, o->height - 1, -3, o->screenNum, maskData1);
+ if (_res->_currentScreenResourceNum <= 2 && (o->flags0 & 0x1F) == 2) {
+ _g->setShakeScreen(2, 2);
+ }
+ if (o->levelData0x2988) {
+ _g->updateAndyObject(o);
+ }
+ postScreenUpdate_lava_screen2_updateMask(o->xPos + o->posTable[7].x, o->yPos + o->height - 1, o->height - 1, 3, o->screenNum, maskData1);
+ if (_res->_currentScreenResourceNum == 2 && (o->flags0 & 0x1F) == 1) {
+ if ((o->flags0 & 0xE0) == 0x20) {
+ static const uint8_t data[] = {
+ 0x75, 0xAA, 0x06, 0, 0x0F, 0x90,
+ 0x65, 0x9E, 0x09, 0, 0x0F, 0x10,
+ 0x80, 0x9E, 0x08, 0, 0x09, 0x10,
+ 0xFF, 0xFF, 0xFF, 0, 0xFF, 0xFF
+ };
+ postScreenUpdate_lava_screen2_addLvlObjects(data);
+ }
+ }
+ o = _g->findLvlObject(2, 1, 2);
+ assert(o);
+ if (_screen1Counter == 0) {
+ o->directionKeyMask = 1;
+ } else {
+ o->directionKeyMask = 4;
+ }
+ static const uint8_t maskData2[5 * 8] = {
+ 0x00, 0x00, 0x00, 0x01, 0xFF, 0x02, 0xFF, 0x02,
+ 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFE, 0x02,
+ 0xFE, 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFE, 0x02,
+ 0xFE, 0x02, 0xFD, 0x02, 0xFD, 0x02, 0xFD, 0x02,
+ 0xFD, 0x02, 0xFD, 0x02, 0xFD, 0x02, 0x00, 0x00
+ };
+ postScreenUpdate_lava_screen2_updateMask(o->xPos + o->posTable[7].x, o->yPos + o->height - 1, o->height - 1, -5, o->screenNum, maskData2);
+ if (_res->_currentScreenResourceNum <= 2 && (o->flags0 & 0x1F) == 2) {
+ _g->setShakeScreen(2, 2);
+ }
+ if (o->levelData0x2988) {
+ _g->updateAndyObject(o);
+ }
+ postScreenUpdate_lava_screen2_updateMask(o->xPos + o->posTable[7].x, o->yPos + o->height - 1, o->height - 1, 5, o->screenNum, maskData2);
+ if ((o->flags0 & 0x1F) == 1) {
+ if (_res->_currentScreenResourceNum == 2) {
+ if ((o->flags0 & 0xE0) == 0x20) {
+ static const uint8_t data[] = {
+ 0xC8, 0xAC, 0x06, 0, 0x0F, 0x90,
+ 0xB9, 0xA5, 0x08, 0, 0x0F, 0x10,
+ 0xDC, 0xA5, 0x09, 0, 0x09, 0x50,
+ 0xAB, 0xA0, 0x09, 0, 0x0F, 0x10,
+ 0xFF, 0xFF, 0xFF, 0, 0xFF, 0xFF
+ };
+ postScreenUpdate_lava_screen2_addLvlObjects(data);
+ }
+ }
+ if (_res->_currentScreenResourceNum <= 2) {
+ _g->setShakeScreen(3, 2);
+ }
+ }
+ if (_res->_currentScreenResourceNum == 2 && (o->flags0 & 0x1F) != 0 && _g->clipLvlObjectsBoundingBox(_andyObject, o, 0x44)) {
+ const int x = o->xPos + o->width / 2;
+ if (_andyObject->xPos > x) {
+ _andyObject->xPos += 10;
+ _g->setAndySpecialAnimation(0x10);
+ } else {
+ _andyObject->xPos -= 10;
+ _g->setAndySpecialAnimation(0x11);
+ }
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen3_updateMask(int x, int y, int w, int h, int screenNum, uint8_t mask) {
+ if (x < 0) {
+ x = 0;
+ }
+ if (y < 0) {
+ y = 0;
+ }
+ if (x + w >= 256) {
+ w = 256 - x;
+ }
+ if (y + h >= 192) {
+ h = 192 - y;
+ }
+ uint32_t maskOffset = Game::screenMaskOffset(_res->_screensBasePos[screenNum].u + x, _res->_screensBasePos[screenNum].v + y);
+ uint32_t gridOffset = Game::screenGridOffset(x, y);
+ w >>= 3;
+ h >>= 3;
+ for (int y = 0; y < h; ++y) {
+ memset(_g->_screenMaskBuffer + maskOffset, mask, w);
+ maskOffset += w;
+ memset(_g->_screenPosTable[4] + gridOffset, 1, w);
+ gridOffset += w;
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen3_updatePlatform(LvlObject *o, BoundingBox *b2) {
+ BoundingBox b;
+ b.x1 = o->xPos + o->posTable[1].x;
+ b.y1 = o->yPos + o->posTable[1].y - 2;
+ b.x2 = o->xPos + o->posTable[2].x;
+ b.y2 = o->yPos + o->posTable[1].y + 4;
+ postScreenUpdate_lava_screen3_updateMask(b.x1, b.y2, 32, 8, o->screenNum, 0);
+ if ((o->flags0 & 0x1F) != 0xB) {
+ if ((o->flags0 & 0x1F) != 2 && _g->clipBoundingBox(&b, b2)) {
+ o->directionKeyMask = 4;
+ }
+ _g->updateAndyObject(o);
+ const int x = o->xPos + o->posTable[1].x + 6;
+ const int y = o->yPos + o->posTable[1].y + 4;
+ postScreenUpdate_lava_screen3_updateMask(x, y, 32, 8, o->screenNum, 1);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen3() {
+ if (_res->_currentScreenResourceNum == 3) {
+ BoundingBox b;
+ b.x1 = _andyObject->xPos + _andyObject->posTable[4].x;
+ b.y1 = _andyObject->yPos + _andyObject->posTable[4].y;
+ b.x2 = _andyObject->xPos + _andyObject->posTable[5].x;
+ b.y2 = _andyObject->yPos + _andyObject->posTable[5].y;
+ LvlObject *o = _g->findLvlObject(2, 0, 3);
+ postScreenUpdate_lava_screen3_updatePlatform(o, &b);
+ o = findLvlObject_lava(o);
+ postScreenUpdate_lava_screen3_updatePlatform(o, &b);
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen4() {
+ if (_res->_currentScreenResourceNum == 4) {
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen5() {
+ if (_res->_currentScreenResourceNum == 5) {
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen6() {
+ if (_res->_currentScreenResourceNum == 6) {
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen7() {
+ if (_res->_currentScreenResourceNum == 7) {
+ if (_checkpoint == 2) {
+ BoundingBox b = { 104, 0, 239, 50 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _checkpoint = 3;
+ }
+ }
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen8() {
+ if (_res->_currentScreenResourceNum == 8) {
+ if (_andyObject->xPos + _andyObject->posTable[5].x < 72 || _andyObject->xPos + _andyObject->posTable[4].x < 72) {
+ const uint8_t flags = _andyObject->flags0 & 0x1F;
+ if (flags != 3 && flags != 7 && flags != 4) {
+ _g->setLavaAndyAnimation(175);
+ }
+ }
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen10() {
+ if (_res->_currentScreenResourceNum == 10) {
+ if (_screenCounterTable[10] < 37) {
+ if (_andyObject->yPos + _andyObject->posTable[3].y < 142) {
+ _andyObject->actionKeyMask = 0x40;
+ _andyObject->directionKeyMask = 0;
+ if (_checkpoint == 3) {
+ _checkpoint = 4;
+ _res->_screensState[10].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[10].currentMaskId = 1;
+ _g->setupScreenMask(10);
+ }
+ ++_screenCounterTable[10];
+ if (_screenCounterTable[10] == 13) {
+ _g->_levelRestartCounter = 12;
+ } else {
+ ++_screenCounterTable[10];
+ if (_screenCounterTable[10] == 37) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(7);
+ _paf->unload(7);
+ _video->clearPalette();
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen11() {
+ if (_res->_currentScreenResourceNum == 11) {
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen12() {
+ if (_res->_currentScreenResourceNum == 12) {
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen14() {
+ if (_res->_currentScreenResourceNum == 14) {
+ const int x = _andyObject->xPos;
+ const Point16_t *pos = _andyObject->posTable;
+ if (x + pos[5].x < 114 || x + pos[4].x < 114 || x + pos[3].x < 114 || x + pos[0].x < 114) {
+ _g->setLavaAndyAnimation(175);
+ }
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen15() {
+ if (_res->_screensState[15].s0 != 0) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(8);
+ _paf->unload(8);
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ }
+}
+
+void Level_lava::postScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ postScreenUpdate_lava_screen0();
+ break;
+ case 1:
+ postScreenUpdate_lava_screen1();
+ break;
+ case 2:
+ postScreenUpdate_lava_screen2();
+ break;
+ case 3:
+ postScreenUpdate_lava_screen3();
+ break;
+ case 4:
+ postScreenUpdate_lava_screen4();
+ break;
+ case 5:
+ postScreenUpdate_lava_screen5();
+ break;
+ case 6:
+ postScreenUpdate_lava_screen6();
+ break;
+ case 7:
+ postScreenUpdate_lava_screen7();
+ break;
+ case 8:
+ postScreenUpdate_lava_screen8();
+ break;
+ case 10:
+ postScreenUpdate_lava_screen10();
+ break;
+ case 11:
+ postScreenUpdate_lava_screen11();
+ break;
+ case 12:
+ postScreenUpdate_lava_screen12();
+ break;
+ case 13:
+ postScreenUpdate_lava_screen13();
+ break;
+ case 14:
+ postScreenUpdate_lava_screen14();
+ break;
+ case 15:
+ postScreenUpdate_lava_screen15();
+ break;
+ }
+}
+
+void Level_lava::preScreenUpdate_lava_screen0() {
+ if (_res->_screensState[0].s0 != 0) {
+ _res->_screensState[0].s0 = 1;
+ }
+}
+
+void Level_lava::preScreenUpdate_lava_screen3() {
+ if (_res->_currentScreenResourceNum == 3) {
+ if (_checkpoint == 0) {
+ _checkpoint = 1;
+ }
+ }
+}
+
+void Level_lava::preScreenUpdate_lava_screen6() {
+ if (_res->_currentScreenResourceNum == 6) {
+ if (_checkpoint == 1) {
+ _checkpoint = 2;
+ }
+ }
+}
+
+void Level_lava::preScreenUpdate_lava_screen10() {
+ const int num = (_res->_screensState[10].s0 == 0) ? 0 : 1;
+ if (_res->_screensState[10].s0 != 0 && _res->_screensState[10].s0 != 1) {
+ _res->_screensState[10].s0 = 1;
+ }
+ _res->_resLvlScreenBackgroundDataTable[10].currentMaskId = num;
+ if (_res->_currentScreenResourceNum == 10) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(7);
+ }
+ }
+}
+
+void Level_lava::preScreenUpdate_lava_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ if (_checkpoint == 4) {
+ _checkpoint = 5;
+ }
+ }
+}
+
+void Level_lava::preScreenUpdate_lava_screen15() {
+ if (_res->_screensState[15].s0 == 0) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(8);
+ }
+ }
+}
+
+void Level_lava::preScreenUpdate(int num) {
+ switch (num) {
+ case 1:
+ preScreenUpdate_lava_screen0();
+ break;
+ case 3:
+ preScreenUpdate_lava_screen3();
+ break;
+ case 6:
+ preScreenUpdate_lava_screen6();
+ break;
+ case 10:
+ preScreenUpdate_lava_screen10();
+ break;
+ case 13:
+ preScreenUpdate_lava_screen13();
+ break;
+ case 15:
+ preScreenUpdate_lava_screen15();
+ break;
+ }
+}
+
+void Level_lava::initialize() {
+ _g->loadTransformLayerData(Game::_pwr2_screenTransformData);
+ _g->resetWormHoleSprites();
+ _screen1Counter = 0;
+ _screen2Counter = 0;
+}
+
+void Level_lava::terminate() {
+ _g->unloadTransformLayerData();
+}
+
+const uint8_t Game::_lava_screenTransformLut[] = {
+ 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+void Level_lava::tick() {
+ _video->_displayShadowLayer = Game::_lava_screenTransformLut[_res->_currentScreenResourceNum * 2] != 0;
+ assert(Game::_lava_screenTransformLut[_res->_currentScreenResourceNum * 2 + 1] == 0);
+ _g->restoreAndyCollidesLava();
+ _g->updateWormHoleSprites();
+}
+
+void Level_lava::setupScreenCheckpoint_lava_screen3() {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 3);
+ assert(ptr);
+ ptr->flags0 &= ~0x3FF;
+ ptr->xPos = 138;
+ ptr->yPos = 157;
+ ptr->anim = 0;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr = findLvlObject_lava(ptr);
+ assert(ptr);
+ ptr->flags0 &= ~0x3FF;
+ ptr->anim = 0;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->xPos = 66;
+ ptr->yPos = 157;
+}
+
+void Level_lava::setupScreenCheckpoint(int num) {
+ switch (num) {
+ case 3:
+ setupScreenCheckpoint_lava_screen3();
+ break;
+ }
+}
diff --git a/SRC/level6_pwr2.cpp b/Src/Global/level6_pwr2.cpp
similarity index 100%
rename from SRC/level6_pwr2.cpp
rename to Src/Global/level6_pwr2.cpp
diff --git a/Src/Global/level7_lar1.cpp b/Src/Global/level7_lar1.cpp
new file mode 100644
index 0000000..a74869b
--- /dev/null
+++ b/Src/Global/level7_lar1.cpp
@@ -0,0 +1,914 @@
+
+// lar1_hod - "into the lair"
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "util.h"
+#include "video.h"
+
+static const CheckpointData _lar1_checkpointData[9] = {
+ { -8, 145, 0x300c, 207, 0, 2 },
+ { 60, 49, 0x300c, 232, 2, 2 },
+ { 6, 41, 0x300c, 232, 6, 2 },
+ { 32, 37, 0x300c, 232, 8, 2 },
+ { 172, 105, 0x300c, 232, 9, 2 },
+ { 40, 145, 0x300c, 232, 14, 2 },
+ { 213, 25, 0x700c, 232, 16, 2 },
+ { 123, 57, 0x300c, 232, 20, 2 },
+ { 224, 43, 0x700c, 232, 5, 2 } // not reachable, _datHdr.levelCheckpointsCount[6] == 8
+};
+
+static const uint8_t _lar1_screenStartData[56] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_lar1: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_lar1_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _lar1_screenStartData; }
+ virtual void initialize();
+ virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_lar1_screen0();
+ void postScreenUpdate_lar1_screen3();
+ void postScreenUpdate_lar1_screen4();
+ void postScreenUpdate_lar1_screen5();
+ void postScreenUpdate_lar1_screen8();
+ void postScreenUpdate_lar1_screen9();
+ void postScreenUpdate_lar1_screen12();
+ void postScreenUpdate_lar1_screen13();
+ void postScreenUpdate_lar1_screen14();
+ void postScreenUpdate_lar1_screen15();
+ void postScreenUpdate_lar1_screen16();
+ void postScreenUpdate_lar1_screen18();
+ void postScreenUpdate_lar1_screen19();
+ void postScreenUpdate_lar1_screen20();
+ void postScreenUpdate_lar1_screen22();
+ void postScreenUpdate_lar1_screen24();
+
+ void preScreenUpdate_lar1_screen0();
+ void preScreenUpdate_lar1_screen2();
+ void preScreenUpdate_lar1_screen6();
+ void preScreenUpdate_lar1_screen8();
+ void preScreenUpdate_lar1_screen10();
+ void preScreenUpdate_lar1_screen11();
+ void preScreenUpdate_lar1_screen12();
+ void preScreenUpdate_lar1_screen13();
+ void preScreenUpdate_lar1_screen14();
+ void preScreenUpdate_lar1_screen15();
+ void preScreenUpdate_lar1_screen16();
+ void preScreenUpdate_lar1_screen17();
+ void preScreenUpdate_lar1_screen18();
+ void preScreenUpdate_lar1_screen19();
+ void preScreenUpdate_lar1_screen20();
+ void preScreenUpdate_lar1_screen23();
+ void preScreenUpdate_lar1_screen24();
+
+ void setupScreenCheckpoint_lar1_screen24_initGates();
+ void setupScreenCheckpoint_lar1_screen24_initAndy(int num);
+ void setupScreenCheckpoint_lar1_screen24();
+};
+
+Level *Level_lar1_create() {
+ return new Level_lar1;
+}
+
+static uint8_t _lar1_gatesData[13 * 4] = {
+ 0x02, 0x00, 0x00, 0x00, // screen 4
+ 0x12, 0x00, 0x00, 0x00, // screen 5
+ 0x32, 0x09, 0x02, 0x00, // screen 5
+ 0x32, 0x0E, 0x02, 0x00, // screen 5
+ 0x02, 0x00, 0x00, 0x00, // screen 8
+ 0x02, 0x00, 0x00, 0x00, // screen 8
+ 0x02, 0x00, 0x00, 0x00, // screen 9
+ 0x02, 0x00, 0x00, 0x00, // screen 13
+ 0x02, 0x00, 0x00, 0x00, // screen 14
+ 0x02, 0x00, 0x00, 0x00, // screen 15
+ 0x02, 0x00, 0x00, 0x00, // screen 16
+ 0x02, 0x00, 0x00, 0x00, // screen 18
+ 0x02, 0x00, 0x00, 0x00 // screen 18
+};
+
+static BoundingBox _lar1_switchesBbox[24] = {
+ { 203, 162, 213, 166 },
+ { 68, 86, 78, 90 },
+ { 195, 58, 205, 62 },
+ { 111, 171, 125, 175 },
+ { 111, 171, 125, 175 },
+ { 202, 171, 212, 175 },
+ { 158, 29, 170, 57 },
+ { 158, 29, 170, 57 },
+ { 158, 29, 170, 57 },
+ { 158, 29, 170, 57 },
+ { 230, 139, 242, 167 },
+ { 230, 139, 242, 167 },
+ { 230, 139, 242, 167 },
+ { 86, 37, 98, 65 },
+ { 86, 37, 98, 65 },
+ { 86, 37, 98, 65 },
+ { 238, 18, 250, 46 },
+ { 14, 138, 26, 166 },
+ { 14, 138, 26, 166 },
+ { 51, 157, 61, 161 },
+ { 51, 157, 61, 161 },
+ { 51, 157, 61, 161 },
+ { 202, 157, 212, 161 },
+ { 32, 145, 44, 173 }
+};
+
+static uint8_t _lar1_switchesData[24 * 4] = { // screenNum,state,spriteNum,gateNum
+ 0x04, 0x07, 0x01, 0x00, 0x05, 0x01, 0x01, 0x01, 0x08, 0x0F, 0x02, 0x00, 0x08, 0x07, 0x03, 0x04,
+ 0x08, 0x07, 0xFF, 0x05, 0x08, 0x07, 0x04, 0x04, 0x09, 0x27, 0xFF, 0x06, 0x09, 0x29, 0x03, 0x02,
+ 0x09, 0x29, 0xFF, 0x03, 0x09, 0x29, 0xFF, 0x04, 0x0A, 0x0B, 0x04, 0x02, 0x0A, 0x0B, 0xFF, 0x03,
+ 0x0A, 0x0B, 0xFF, 0x04, 0x0D, 0x07, 0xFF, 0x07, 0x0D, 0x0B, 0x00, 0x00, 0x0D, 0x0B, 0xFF, 0x01,
+ 0x0E, 0x07, 0x02, 0x08, 0x0F, 0x27, 0x01, 0x09, 0x0F, 0x2F, 0xFF, 0x0B, 0x12, 0x07, 0x01, 0x0B,
+ 0x12, 0x0B, 0xFF, 0x0D, 0x12, 0x0B, 0xFF, 0x0C, 0x12, 0x15, 0x02, 0x0B, 0x15, 0x2F, 0x00, 0x0E
+};
+
+static const uint8_t _lar1_setupScreen24Data[8 * 3] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x02, 0x08, 0x02, 0x0B,
+ 0x08, 0x10, 0x0C, 0x09, 0x11, 0x0E, 0x0D, 0x17
+};
+
+static void setLvlObjectUpdateType3_lar1(Game *g, int screenNum) {
+ const uint8_t *p = Game::_lar1_maskData;
+ for (int i = 0; i < 15; ++i) {
+ if (p[4] == screenNum && p[1] == 1) {
+ LvlObject *o = g->findLvlObject2(0, p[5], p[4]);
+ if (o) {
+ o->objectUpdateType = 3;
+ }
+ }
+ p += 6;
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen0() {
+ switch (_res->_screensState[0].s0) {
+ case 0:
+ _res->_screensState[0].s0 = 3;
+ break;
+ case 1:
+ if (_res->_currentScreenResourceNum == 0) {
+ BoundingBox b = { 0, 0, 63, 78 };
+ LvlObject *o = _g->findLvlObjectBoundingBox(&b);
+ if (o) {
+ if (((ShootLvlObjectData *)_g->getLvlObjectDataPtr(o, kObjectDataTypeShoot))->type == 6) {
+ _res->_screensState[0].s0 = 4;
+ }
+ }
+ }
+ break;
+ case 2:
+ if (_res->_currentScreenResourceNum == 0) {
+ BoundingBox b = { 0, 0, 14, 74 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ if (!_g->_fadePalette) {
+ _checkpoint = 1;
+ _g->_levelRestartCounter = 6;
+ } else {
+ _andyObject->directionKeyMask = 0;
+ }
+ }
+ }
+ break;
+ case 3:
+ ++_screenCounterTable[0];
+ if (_screenCounterTable[0] < 7) {
+ _andyObject->actionKeyMask = 1;
+ _andyObject->directionKeyMask = 2;
+ } else if (_screenCounterTable[0] == 7) {
+ _res->_resLvlScreenBackgroundDataTable[0].currentMaskId = 1;
+ _g->setupScreenMask(0);
+ } else if (_screenCounterTable[0] >= 45) {
+ _res->_screensState[0].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 1;
+ } else if (_screenCounterTable[0] == 11) {
+ _g->setShakeScreen(3, 2);
+ } else if (_screenCounterTable[0] == 13) {
+ _g->setShakeScreen(2, 4);
+ } else if (_screenCounterTable[0] == 19) {
+ _g->setShakeScreen(2, 4);
+ } else if (_screenCounterTable[0] == 25) {
+ _g->setShakeScreen(2, 6);
+ }
+ break;
+ case 4:
+ ++_screenCounterTable[0];
+ if (_screenCounterTable[0] >= 64) {
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 2;
+ _res->_resLvlScreenBackgroundDataTable[0].currentMaskId = 2;
+ _g->setupScreenMask(0);
+ _res->_screensState[0].s0 = 2;
+ }
+ break;
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen3() {
+ if (_res->_currentScreenResourceNum == 3) {
+ BoundingBox b = { 46, 0, 210, 106 };
+ _g->setAndyAnimationForArea(&b, 16);
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen4() {
+ LvlObject *o = _g->findLvlObject(2, 0, 4);
+ _g->updateGatesLar(o, _lar1_gatesData, 0);
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen5() {
+ LvlObject *o1 = _g->findLvlObject(2, 0, 5);
+ _g->updateGatesLar(o1, _lar1_gatesData, 1);
+ LvlObject *o2 = _g->findLvlObject(2, 1, 5);
+ _g->updateGatesLar(o2, _lar1_gatesData, 2);
+ LvlObject *o3 = _g->findLvlObject(2, 2, 5);
+ _g->updateGatesLar(o3, _lar1_gatesData, 3);
+ if (_res->_currentScreenResourceNum == 5) {
+ if (_checkpoint >= 1 && _checkpoint <= 3) {
+ BoundingBox b = { 194, 0, 255, 88 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox) && (_lar1_gatesData[6 * 4] & 0xF0) == 0x10) {
+ _checkpoint = 2;
+ _screenCounterTable[26] = (_lar1_gatesData[7 * 4] & 0xF0) != 0 ? 3 : 1;
+ }
+ }
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen8() {
+ LvlObject *o1 = _g->findLvlObject(2, 0, 8);
+ _g->updateGatesLar(o1, _lar1_gatesData, 4);
+ LvlObject *o2 = _g->findLvlObject(2, 1, 8);
+ _g->updateGatesLar(o2, _lar1_gatesData, 5);
+ if (_res->_currentScreenResourceNum == 8) {
+ if (_checkpoint >= 1 && _checkpoint <= 3) {
+ BoundingBox b = { 104, 0, 255, 80 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _checkpoint = 3;
+ _screenCounterTable[26] = (_lar1_gatesData[6 * 4] & 0xF0) != 0 ? 5 : 4;
+ if ((_lar1_gatesData[7 * 4] & 0xF0) == 0x10) {
+ _screenCounterTable[26] += 2;
+ }
+ }
+ }
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen9() {
+ LvlObject *o = _g->findLvlObject(2, 0, 9);
+ _g->updateGatesLar(o, _lar1_gatesData, 6);
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen12() {
+ if (_res->_currentScreenResourceNum == 12) {
+ const int counter = _screenCounterTable[12];
+ if (counter < 8 && (_andyObject->flags0 & 0x1F) == 3) {
+ static const uint8_t data[8 * 4] = {
+ 0x05, 0x00, 0x00, 0x04, 0x06, 0x00, 0x00, 0x00, 0x07, 0xFB, 0x04, 0x05, 0x08, 0xFA, 0x04, 0x05,
+ 0x09, 0xF9, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x04, 0xF8, 0xF7, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04
+ };
+ const uint8_t num = (_andyObject->flags0 >> 5) & 7;
+ const int offset = counter * 4;
+ if (data[offset + 2] == num || data[offset + 3] == num) {
+ BoundingBox b[] = {
+ { 205, 0, 227, 97 },
+ { 200, 16, 207, 55 },
+ { 128, 16, 159, 71 },
+ { 56, 32, 87, 87 },
+ { 179, 112, 207, 167 },
+ { 179, 64, 207, 103 },
+ { 32, 112, 87, 167 },
+ { 32, 112, 87, 167 }
+ };
+ AndyLvlObjectData *andyData = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b[counter], &andyData->boundingBox)) {
+ ++_screenCounterTable[12];
+ _g->updateGateMaskLar((int8_t)data[offset]);
+ _g->updateGateMaskLar((int8_t)data[offset + 1]);
+ }
+ }
+ }
+ _g->restoreAndyCollidesLava();
+ _g->setLavaAndyAnimation(182);
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen13() {
+ LvlObject *o = _g->findLvlObject(2, 0, 13);
+ _g->updateGatesLar(o, _lar1_gatesData, 7);
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen14() {
+ if (_res->_currentScreenResourceNum == 14) {
+ switch (_res->_screensState[14].s0) {
+ case 0:
+ if (_g->_currentLevelCheckpoint == 4) {
+ if (_checkpoint == 5) {
+ _g->_currentLevelCheckpoint = _checkpoint;
+ if (!_paf->_skipCutscenes) {
+ _paf->play(11);
+ _paf->unload(11);
+ }
+ _video->clearPalette();
+ _g->restartLevel();
+ }
+ } else {
+ BoundingBox b = { 33, 60, 76, 89 };
+ LvlObject *o = _g->findLvlObjectBoundingBox(&b);
+ if (o) {
+ if (((ShootLvlObjectData *)_g->getLvlObjectDataPtr(o, kObjectDataTypeShoot))->type == 6) {
+ _res->_screensState[14].s0 = 3;
+ }
+ }
+ }
+ break;
+ case 1: {
+ BoundingBox b = { 172, 23, 222, 53 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ if ((_andyObject->flags0 & 0x1F) == 0 && (_andyObject->flags0 & 0xE0) == 0xE0) {
+ _res->_screensState[14].s0 = 4;
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(12);
+ }
+ } else {
+ _g->setAndySpecialAnimation(3);
+ }
+ }
+ }
+ break;
+ case 3:
+ ++_screenCounterTable[14];
+ if (_screenCounterTable[14] == 1) {
+ _res->_resLvlScreenBackgroundDataTable[14].currentMaskId = 1;
+ _g->setupScreenMask(14);
+ } else if (_screenCounterTable[14] >= 20) {
+ _res->_screensState[14].s0 = 1;
+ } else if (_screenCounterTable[14] == 7 || _screenCounterTable[14] == 9 || _screenCounterTable[14] == 11 || _screenCounterTable[14] == 13 || _screenCounterTable[14] == 15) {
+ _g->setShakeScreen(3, 2);
+ }
+ break;
+ case 4:
+ ++_screenCounterTable[14];
+ if (_screenCounterTable[14] >= 37) {
+ _res->_screensState[14].s0 = 2;
+ _res->_resLvlScreenBackgroundDataTable[14].currentBackgroundId = 1;
+ _res->_resLvlScreenBackgroundDataTable[14].currentMaskId = 2;
+ if (!_paf->_skipCutscenes) {
+ _paf->play(12);
+ _paf->unload(12);
+ }
+ _video->clearPalette();
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ break;
+ }
+ }
+ LvlObject *o = _g->findLvlObject(2, 0, 14);
+ _g->updateGatesLar(o, _lar1_gatesData, 8);
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen15() {
+ LvlObject *o = _g->findLvlObject(2, 0, 15);
+ _g->updateGatesLar(o, _lar1_gatesData, 9);
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen16() {
+ LvlObject *o = _g->findLvlObject(2, 0, 16);
+ _g->updateGatesLar(o, _lar1_gatesData, 10);
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen18() {
+ LvlObject *o1 = _g->findLvlObject(2, 0, 18);
+ _g->updateGatesLar(o1, _lar1_gatesData, 11);
+ LvlObject *o2 = _g->findLvlObject(2, 1, 18);
+ _g->updateGatesLar(o2, _lar1_gatesData, 12);
+ if ((_lar1_switchesData[0x59] & 0x40) == 0 && (_lar1_switchesData[0x59] & 0x80) != 0) {
+ if ((_lar1_switchesData[0x4D] & 1) == 0) {
+ _lar1_switchesData[0x4D] |= 1;
+ }
+ }
+ if ((_lar1_switchesData[0x4D] & 0x40) == 0 && (_lar1_switchesData[0x4D] & 0x80) != 0) {
+ if ((_lar1_switchesData[0x59] & 1) == 0) {
+ _lar1_switchesData[0x59] |= 1;
+ }
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen19() {
+ if (_screenCounterTable[19] == 0) {
+ if (_res->_currentScreenResourceNum == 19) {
+ BoundingBox b = { 160, 0, 209, 71 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _g->_plasmaCannonFlags |= 2;
+ if (!_paf->_skipCutscenes) {
+ _paf->play(13);
+ _paf->unload(13);
+ }
+ _video->clearPalette();
+ ++_screenCounterTable[19]; // bugfix: conditioned with _pafSkipCutscenes
+ _g->setupScreen(_andyObject->screenNum);
+ Game::_lar1_maskData[12 * 6 + 1] = 0;
+ Game::_lar1_maskData[13 * 6 + 1] = 0;
+ _andyObject->xPos = 204;
+ _andyObject->yPos = 25;
+ _andyObject->anim = 232;
+ _andyObject->frame = 0;
+ _andyObject->flags1 = (_andyObject->flags1 & ~0x20) | 0x10;
+ _andyObject->directionKeyMask = 0;
+ _andyObject->actionKeyMask = 0;
+ _g->setupLvlObjectBitmap(_andyObject);
+ _g->removeLvlObject(_andyObject);
+ _g->clearLvlObjectsList0();
+ }
+ }
+ } else if (_res->_screensState[19].s0 == 2) {
+ ++_screenCounterTable[19];
+ if (_screenCounterTable[19] == 2) {
+ _res->_resLvlScreenBackgroundDataTable[19].currentMaskId = 1;
+ _g->setupScreenMask(19);
+ } else if (_screenCounterTable[19] >= 14) {
+ _res->_screensState[19].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[19].currentBackgroundId = 1;
+ }
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen20() {
+ if (_res->_currentScreenResourceNum == 20) {
+ postScreenUpdate_lar1_screen19();
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen22() {
+ if (_res->_currentScreenResourceNum == 22) {
+ BoundingBox b = { 36, 0, 208, 82 };
+ _g->setAndyAnimationForArea(&b, 16);
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen24() {
+ if (_res->_currentScreenResourceNum == 24) {
+ if ((_andyObject->flags0 & 0x1F) == 5) {
+ _g->_plasmaCannonFlags |= 1;
+ }
+ BoundingBox b = { 50, 168, 113, 191 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(14);
+ _paf->unload(14);
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ }
+ }
+}
+
+void Level_lar1::postScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ postScreenUpdate_lar1_screen0();
+ break;
+ case 3:
+ postScreenUpdate_lar1_screen3();
+ break;
+ case 4:
+ postScreenUpdate_lar1_screen4();
+ break;
+ case 5:
+ postScreenUpdate_lar1_screen5();
+ break;
+ case 8:
+ postScreenUpdate_lar1_screen8();
+ break;
+ case 9:
+ postScreenUpdate_lar1_screen9();
+ break;
+ case 12:
+ postScreenUpdate_lar1_screen12();
+ break;
+ case 13:
+ postScreenUpdate_lar1_screen13();
+ break;
+ case 14:
+ postScreenUpdate_lar1_screen14();
+ break;
+ case 15:
+ postScreenUpdate_lar1_screen15();
+ break;
+ case 16:
+ postScreenUpdate_lar1_screen16();
+ break;
+ case 18:
+ postScreenUpdate_lar1_screen18();
+ break;
+ case 19:
+ postScreenUpdate_lar1_screen19();
+ break;
+ case 20:
+ postScreenUpdate_lar1_screen20();
+ break;
+ case 22:
+ postScreenUpdate_lar1_screen22();
+ break;
+ case 24:
+ postScreenUpdate_lar1_screen24();
+ break;
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen0() {
+ if (_res->_currentScreenResourceNum == 0) {
+ switch (_res->_screensState[0].s0) {
+ case 0:
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 0;
+ _res->_resLvlScreenBackgroundDataTable[0].currentMaskId = 0;
+ _screenCounterTable[0] = 0;
+ break;
+ case 3:
+ _res->_screensState[0].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 1;
+ _res->_resLvlScreenBackgroundDataTable[0].currentMaskId = 1;
+ _screenCounterTable[0] = 45;
+ break;
+ case 4:
+ _screenCounterTable[0] = 64;
+ _res->_screensState[0].s0 = 2;
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 2;
+ _res->_resLvlScreenBackgroundDataTable[0].currentMaskId = 2;
+ break;
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen2() {
+ if (_res->_currentScreenResourceNum == 2) {
+ if (_checkpoint == 0) {
+ _checkpoint = 1;
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen6() {
+ if (_res->_currentScreenResourceNum == 6) {
+ if (_checkpoint >= 1 && _checkpoint <= 3) {
+ if ((_lar1_gatesData[6 * 4] & 0xF0) == 0) {
+ _checkpoint = 2;
+ _screenCounterTable[26] = ((_lar1_gatesData[7 * 4] & 0xF0) != 0) ? 2 : 0;
+ }
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen8() {
+ if (_res->_currentScreenResourceNum == 8) {
+ setLvlObjectUpdateType3_lar1(_g, 8);
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen10() {
+ if (_res->_currentScreenResourceNum == 10) {
+ setLvlObjectUpdateType3_lar1(_g, 10);
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen11() {
+ if (_res->_currentScreenResourceNum == 11) {
+ if (_checkpoint >= 2 && _checkpoint <= 3) {
+ if ((_lar1_gatesData[7 * 4] & 0xF) == 1 && (_lar1_gatesData[6 * 4] & 0xF) == 1) {
+ _checkpoint = 4;
+ }
+ }
+ _g->unloadTransformLayerData();
+ _video->_displayShadowLayer = false;
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen12() {
+ if (_res->_currentScreenResourceNum == 12) {
+ setLvlObjectUpdateType3_lar1(_g, 12);
+ _g->loadTransformLayerData(Game::_pwr2_screenTransformData);
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ _g->unloadTransformLayerData();
+ _video->_displayShadowLayer = false;
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen14() {
+ switch (_res->_screensState[14].s0) {
+ case 0:
+ _res->_resLvlScreenBackgroundDataTable[14].currentBackgroundId = 0;
+ _res->_resLvlScreenBackgroundDataTable[14].currentMaskId = 0;
+ _screenCounterTable[14] = 0;
+ break;
+ case 3:
+ _res->_screensState[14].s0 = 1;
+ _screenCounterTable[14] = 20;
+ break;
+ case 4:
+ _res->_screensState[14].s0 = 2;
+ _screenCounterTable[14] = 37;
+ break;
+ }
+ if (_res->_currentScreenResourceNum == 14) {
+ if (_res->_screensState[14].s0 == 0 && _g->_currentLevelCheckpoint == 4) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(11);
+ }
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen15() {
+ if (_res->_currentScreenResourceNum == 15) {
+ setLvlObjectUpdateType3_lar1(_g, 15);
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen16() {
+ if (_res->_currentScreenResourceNum == 16) {
+ if (_checkpoint == 5) {
+ _checkpoint = 6;
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen17() {
+ if (_res->_currentScreenResourceNum == 17) {
+ setLvlObjectUpdateType3_lar1(_g, 17);
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen18() {
+ if (_checkpoint >= 7) {
+ _lar1_gatesData[12 * 4] = (_lar1_gatesData[12 * 4] & 0xF) | 0x10;
+ }
+ if (_res->_currentScreenResourceNum == 18) {
+ setLvlObjectUpdateType3_lar1(_g, 18);
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen19() {
+ if (_res->_currentScreenResourceNum == 19) {
+ if (_checkpoint == 6) {
+ _res->_screensState[19].s0 = 0;
+ }
+ _res->_resLvlScreenBackgroundDataTable[19].currentBackgroundId = 0;
+ _res->_resLvlScreenBackgroundDataTable[19].currentMaskId = 0;
+ if (_res->_screensState[19].s0 != 0) {
+ _res->_screensState[19].s0 = 1;
+ _screenCounterTable[19] = 14;
+ if (_g->_difficulty != 0) {
+ _res->_resLvlScreenBackgroundDataTable[19].currentBackgroundId = 1;
+ _res->_resLvlScreenBackgroundDataTable[19].currentMaskId = 1;
+ }
+ } else {
+ _screenCounterTable[19] = (_g->_plasmaCannonFlags >> 1) & 1;
+ }
+ if (_screenCounterTable[19] == 0) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(13);
+ }
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen20() {
+ if (_res->_currentScreenResourceNum == 20) {
+ if (_checkpoint == 6) {
+ if ((_andyObject->flags0 & 0x1F) != 0xB) {
+ _checkpoint = 7;
+ }
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen23() {
+ if (_res->_currentScreenResourceNum == 23) {
+ setLvlObjectUpdateType3_lar1(_g, 23);
+ if (_g->_plasmaCannonFlags & 2) {
+ _lar1_gatesData[10 * 4] = (_lar1_gatesData[10 * 4] & 0xF) | 0x10;
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen24() {
+ if (_res->_currentScreenResourceNum == 24) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(14);
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ preScreenUpdate_lar1_screen0();
+ break;
+ case 2:
+ preScreenUpdate_lar1_screen2();
+ break;
+ case 6:
+ preScreenUpdate_lar1_screen6();
+ break;
+ case 8:
+ preScreenUpdate_lar1_screen8();
+ break;
+ case 10:
+ preScreenUpdate_lar1_screen10();
+ break;
+ case 11:
+ preScreenUpdate_lar1_screen11();
+ break;
+ case 12:
+ preScreenUpdate_lar1_screen12();
+ break;
+ case 13:
+ preScreenUpdate_lar1_screen13();
+ break;
+ case 14:
+ preScreenUpdate_lar1_screen14();
+ break;
+ case 15:
+ preScreenUpdate_lar1_screen15();
+ break;
+ case 16:
+ preScreenUpdate_lar1_screen16();
+ break;
+ case 17:
+ preScreenUpdate_lar1_screen17();
+ break;
+ case 18:
+ preScreenUpdate_lar1_screen18();
+ break;
+ case 19:
+ preScreenUpdate_lar1_screen19();
+ break;
+ case 20:
+ preScreenUpdate_lar1_screen20();
+ break;
+ case 23:
+ preScreenUpdate_lar1_screen23();
+ break;
+ case 24:
+ preScreenUpdate_lar1_screen24();
+ break;
+ }
+}
+
+void Level_lar1::initialize() {
+ _g->resetWormHoleSprites();
+ _screenCounterTable[26] = 0;
+}
+
+void Level_lar1::terminate() {
+ _g->unloadTransformLayerData();
+}
+
+void Level_lar1::tick() {
+ _g->updateSwitchesLar(24, _lar1_switchesData, _lar1_switchesBbox, _lar1_gatesData);
+ _g->updateWormHoleSprites();
+ if (_screenCounterTable[19] != 0) {
+ _g->_plasmaCannonFlags |= 2;
+ }
+ if (_res->_currentScreenResourceNum == 12) {
+ _video->_displayShadowLayer = true;
+ }
+}
+
+void Level_lar1::setupScreenCheckpoint_lar1_screen24_initGates() {
+ const int num = _checkpoint;
+ for (int i = _lar1_setupScreen24Data[num * 3 + 2]; i < 24; ++i) {
+ const int offset = i * 4;
+ _lar1_switchesData[offset + 1] &= ~0x40;
+ _lar1_switchesData[offset + 1] |= 1; // actioned
+ }
+ for (int i = _lar1_setupScreen24Data[num * 3 + 2]; i != 0; --i) {
+ const int offset = (i - 1) * 4;
+ _lar1_switchesData[offset + 1] &= ~1;
+ _lar1_switchesData[offset + 1] |= 0x40;
+ }
+ static const uint8_t data[] = { 0, 1, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ for (int i = _lar1_setupScreen24Data[num * 3 + 1]; i < 13; ++i) {
+ const int j = i;
+ const uint8_t _al = (data[j] << 4) | 2;
+ _lar1_gatesData[j * 4] = _al;
+ const uint32_t mask = 1 << j;
+ if (_al & 0xF0) {
+ _g->_mstAndyVarMask &= ~mask;
+ } else {
+ _g->_mstAndyVarMask |= mask;
+ }
+ _g->_mstLevelGatesMask |= mask;
+ }
+ for (int i = _lar1_setupScreen24Data[num * 3 + 1]; i != 0; --i) {
+ const int j = i - 1;
+ const uint8_t _al = (((data[j] == 0) ? 1 : 0) << 4) | 2;
+ _lar1_gatesData[j * 4] = _al;
+ const uint32_t mask = 1 << j;
+ if (_al & 0xF0) {
+ _g->_mstAndyVarMask &= ~mask;
+ } else {
+ _g->_mstAndyVarMask |= mask;
+ }
+ _g->_mstLevelGatesMask |= mask;
+ }
+ _lar1_gatesData[2 * 4] = (_lar1_gatesData[2 * 4] & 0xF) | 0x30;
+ _lar1_gatesData[3 * 4] = (_lar1_gatesData[3 * 4] & 0xF) | 0x30;
+}
+
+void Level_lar1::setupScreenCheckpoint_lar1_screen24_initAndy(int num) {
+ static const uint8_t data[8 * 6] = {
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // checkpoint, gate states
+ 0x08, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x01, 0x00, 0x01, 0x01, 0x01,
+ 0x08, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x03, 0x01, 0x00, 0x00, 0x01, 0x01,
+ 0x03, 0x01, 0x01, 0x00, 0x01, 0x01
+ };
+ const uint8_t *p = &data[(num & 7) * 6];
+ num = *p++;
+ const CheckpointData *dat = &_lar1_checkpointData[num];
+ _andyObject->xPos = dat->xPos;
+ _andyObject->yPos = dat->yPos;
+ _andyObject->flags2 = dat->flags2;
+ _andyObject->anim = dat->anim;
+ _andyObject->flags1 &= ~0x30;
+ _andyObject->flags1 |= (dat->flags2 >> 10) & 0x30;
+ _andyObject->screenNum = dat->screenNum;
+ _andyObject->frame = 0;
+ _g->setupLvlObjectBitmap(_andyObject);
+ static const uint8_t gatesNum[5] = { 7, 6, 0, 4, 5 };
+ for (int i = 0; i < 5; ++i) {
+ uint8_t _al = *p++;
+ num = gatesNum[i];
+ _al <<= 4;
+ _al |= _lar1_gatesData[num * 4] & 15;
+ _lar1_gatesData[num * 4] = _al;
+ const uint32_t mask = 1 << num;
+ if (_al & 0xF0) {
+ _g->_mstAndyVarMask &= ~mask;
+ } else {
+ _g->_mstAndyVarMask |= mask;
+ }
+ _g->_mstLevelGatesMask |= mask;
+ }
+}
+
+void Level_lar1::setupScreenCheckpoint_lar1_screen24() {
+ setupScreenCheckpoint_lar1_screen24_initGates();
+ const int num = _checkpoint;
+ for (int b = _lar1_setupScreen24Data[num * 3]; b < 15; ++b) {
+ const int offset = b * 6;
+ Game::_lar1_maskData[offset + 1] = 1;
+ _g->updateScreenMaskLar(Game::_lar1_maskData + offset, 0);
+ LvlObject *o = _g->findLvlObject2(0, Game::_lar1_maskData[offset + 5], Game::_lar1_maskData[offset + 4]);
+ if (o) {
+ o->objectUpdateType = 6;
+ }
+ }
+ for (int b = _lar1_setupScreen24Data[num * 3]; b != 0; --b) {
+ const int offset = (b - 1) * 6;
+ Game::_lar1_maskData[offset + 1] = 0;
+ _g->updateScreenMaskLar(Game::_lar1_maskData + offset, 1);
+ LvlObject *o = _g->findLvlObject2(0, Game::_lar1_maskData[offset + 5], Game::_lar1_maskData[offset + 4]);
+ if (o) {
+ o->objectUpdateType = 2;
+ }
+ }
+ if (num == 2 || num == 3) {
+ if (_screenCounterTable[26] == 0 && num == 3) {
+ _screenCounterTable[26] = 5; // bugfix: original is 4, but gate 6 (screen 8) will be closed
+ }
+ setupScreenCheckpoint_lar1_screen24_initAndy(_screenCounterTable[26]);
+ }
+ if (g_debugMask & kDebug_SWITCHES) {
+ _g->dumpSwitchesLar(24, _lar1_switchesData, _lar1_switchesBbox, 13, _lar1_gatesData);
+ }
+}
+
+void Level_lar1::setupScreenCheckpoint(int num) {
+ switch (num) {
+ case 24:
+ setupScreenCheckpoint_lar1_screen24();
+ break;
+ }
+}
diff --git a/Src/Global/level8_lar2.cpp b/Src/Global/level8_lar2.cpp
new file mode 100644
index 0000000..cdada0c
--- /dev/null
+++ b/Src/Global/level8_lar2.cpp
@@ -0,0 +1,575 @@
+
+// lar2_hod - "heart of darkness"
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "util.h"
+#include "video.h"
+
+static const CheckpointData _lar2_checkpointData[12] = {
+ { 111, 123, 0x300c, 48, 0, 0 },
+ { 11, 123, 0x300c, 48, 2, 0 },
+ { 17, 51, 0x300c, 48, 4, 0 },
+ { 69, 91, 0x300c, 48, 6, 0 },
+ { 154, 91, 0x700c, 48, 6, 0 },
+ { 154, 91, 0x700c, 48, 6, 0 },
+ { 137, 35, 0x300c, 48, 11, 0 },
+ { 154, 91, 0x700c, 48, 6, 0 },
+ { 70, 139, 0x700c, 48, 5, 0 },
+ { 106, 139, 0x700c, 48, 4, 0 },
+ { 75, 107, 0x300c, 48, 15, 0 },
+ { 75, 107, 0x300c, 48, 15, 0 }
+};
+
+static const uint8_t _lar2_screenStartData[64] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_lar2: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_lar2_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _lar2_screenStartData; }
+ //virtual void initialize();
+ //virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ virtual void setupScreenCheckpoint(int screenNum);
+
+ bool postScreenUpdate_lar2_screen2_updateGateSwitches(BoundingBox *b);
+
+ void postScreenUpdate_lar2_screen2();
+ void postScreenUpdate_lar2_screen3();
+ void postScreenUpdate_lar2_screen4();
+ void postScreenUpdate_lar2_screen5();
+ void postScreenUpdate_lar2_screen6();
+ void postScreenUpdate_lar2_screen7();
+ void postScreenUpdate_lar2_screen8();
+ void postScreenUpdate_lar2_screen10();
+ void postScreenUpdate_lar2_screen11();
+ void postScreenUpdate_lar2_screen12();
+ void postScreenUpdate_lar2_screen13();
+ void postScreenUpdate_lar2_screen19();
+
+ void preScreenUpdate_lar2_screen2();
+ void preScreenUpdate_lar2_screen4();
+ void preScreenUpdate_lar2_screen5();
+ void preScreenUpdate_lar2_screen6();
+ void preScreenUpdate_lar2_screen7();
+ void preScreenUpdate_lar2_screen8();
+ void preScreenUpdate_lar2_screen9();
+ void preScreenUpdate_lar2_screen15();
+ void preScreenUpdate_lar2_screen19();
+
+ void setupScreenCheckpoint_lar2_screen19();
+};
+
+Level *Level_lar2_create() {
+ return new Level_lar2;
+}
+
+static uint8_t _lar2_gatesData[10 * 4] = {
+ 0x02, 0x00, 0x00, 0x00, // screen 2
+ 0x02, 0x00, 0x00, 0x00, // screen 3
+ 0x02, 0x00, 0x00, 0x00, // screen 4
+ 0x12, 0x00, 0x00, 0x00, // screen 5
+ 0x02, 0x00, 0x00, 0x00, // screen 10
+ 0x02, 0x00, 0x00, 0x00, // screen 11
+ 0x02, 0x00, 0x00, 0x00, // screen 11
+ 0x02, 0x00, 0x00, 0x00, // screen 8
+ 0x12, 0x00, 0x00, 0x00, // screen 12
+ 0x02, 0x00, 0x00, 0x00 // screen 12
+};
+
+static BoundingBox _lar2_switchesBbox[13] = {
+ { 210, 155, 220, 159 },
+ { 224, 146, 234, 150 },
+ { 193, 84, 203, 88 },
+ { 209, 83, 219, 87 },
+ { 65, 179, 75, 183 },
+ { 65, 179, 75, 183 },
+ { 145, 179, 155, 183 },
+ { 145, 179, 155, 183 },
+ { 224, 179, 234, 183 },
+ { 16, 84, 26, 88 },
+ { 16, 84, 26, 88 },
+ { 16, 164, 26, 168 },
+ { 16, 164, 26, 168 }
+};
+
+static uint8_t _lar2_switchesData[13 * 4] = { // screenNum,state,spriteNum,gateNum
+ 0x03, 0x07, 0x00, 0x01, 0x0A, 0x07, 0x00, 0x04, 0x09, 0x03, 0x00, 0x07, 0x08, 0x03, 0x00, 0x07,
+ 0x0B, 0x17, 0x00, 0x06, 0x0B, 0x15, 0xFF, 0x05, 0x0B, 0x15, 0x01, 0x06, 0x0B, 0x17, 0xFF, 0x05,
+ 0x0B, 0x17, 0x02, 0x06, 0x0D, 0x05, 0x00, 0x09, 0x0D, 0x07, 0xFF, 0x08, 0x0D, 0x05, 0x01, 0x08,
+ 0x0D, 0x07, 0xFF, 0x09
+};
+
+static const uint8_t _lar2_setupScreen19Data[13 * 3] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x05, 0x02, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x09, 0x0C, 0x00, 0x0A, 0x0C,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+bool Level_lar2::postScreenUpdate_lar2_screen2_updateGateSwitches(BoundingBox *b) {
+ bool ret = false;
+ BoundingBox b1 = { 121, 158, 131, 162 };
+ if (_g->clipBoundingBox(&b1, b)) {
+ ret = true;
+ _lar2_gatesData[0] &= 0xF;
+ LvlObject *o = _g->findLvlObject2(0, 0, 2);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ }
+ BoundingBox b2 = { 170, 158, 180, 162 };
+ if (_g->clipBoundingBox(&b2, b)) {
+ ret = true;
+ _lar2_gatesData[0] &= 0xF;
+ LvlObject *o = _g->findLvlObject2(0, 1, 2);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ }
+ BoundingBox b3 = { 215, 158, 225, 162 };
+ if (_g->clipBoundingBox(&b3, b)) {
+ ret = true;
+ _lar2_gatesData[0] &= 0xF;
+ LvlObject *o = _g->findLvlObject2(0, 2, 2);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ }
+ return ret;
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen2() {
+ LvlObject *o = _g->findLvlObject(2, 0, 2);
+ _g->updateGatesLar(o, _lar2_gatesData, 0);
+ if (_res->_currentScreenResourceNum == 2) {
+ bool ret = false;
+ for (LvlObject *o = _g->_lvlObjectsList1; o; o = o->nextPtr) {
+ if (o->spriteNum == 16 && o->screenNum == _res->_currentScreenResourceNum) {
+ BoundingBox b;
+ b.x1 = o->xPos;
+ b.y1 = o->yPos;
+ b.x2 = o->xPos + o->width - 1;
+ b.y2 = o->yPos + o->height - 1;
+ if (postScreenUpdate_lar2_screen2_updateGateSwitches(&b)) {
+ ret = true;
+ }
+ }
+ }
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (postScreenUpdate_lar2_screen2_updateGateSwitches(&data->boundingBox) == 0) {
+ BoundingBox b = { 107, 77, 117, 81 };
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ LvlObject *o = _g->findLvlObject2(0, 3, 2);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ if (!ret) {
+ _lar2_gatesData[0] = (_lar2_gatesData[0] & 0xF) | 0x10;
+ }
+ }
+ }
+ }
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen3() {
+ LvlObject *o = _g->findLvlObject(2, 0, 3);
+ _g->updateGatesLar(o, _lar2_gatesData, 1);
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen4() {
+ if (_g->_currentLevelCheckpoint == 8 && _checkpoint == 9) {
+ _lar2_gatesData[4 * 2] = (_lar2_gatesData[4 * 2] & 0xF) | 0x10;
+ if (!_paf->_skipCutscenes) {
+ _paf->play(18);
+ _paf->unload(18);
+ _video->clearPalette();
+ }
+ _g->_currentLevelCheckpoint = _checkpoint; // bugfix: conditioned with _pafSkipCutscenes
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ LvlObject *o = _g->findLvlObject(2, 0, 4);
+ _g->updateGatesLar(o, _lar2_gatesData, 2);
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen5() {
+ if (_g->_currentLevelCheckpoint == 7 && _checkpoint == 8) {
+ _lar2_gatesData[4 * 3] &= 0xF;
+ }
+ LvlObject *o = _g->findLvlObject(2, 0, 5);
+ _g->updateGatesLar(o, _lar2_gatesData, 3);
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen6() {
+ if (_res->_currentScreenResourceNum == 6) {
+ if (_checkpoint == 7) {
+ if (_g->_currentLevelCheckpoint == 6) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(17);
+ _paf->unload(17);
+ _video->clearPalette();
+ }
+ _g->_currentLevelCheckpoint = _checkpoint; // bugfix: conditioned with _pafSkipCutscenes
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ } else if (_checkpoint == 3) {
+ if (_res->_screensState[6].s0 != 0) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(15);
+ _paf->unload(15);
+ _video->clearPalette();
+ }
+ _res->_screensState[6].s0 = 0; // bugfix: conditioned with _pafSkipCutscenes
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ }
+ }
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen7() {
+ if (_res->_currentScreenResourceNum == 7) {
+ if (_checkpoint == 5 && _res->_screensState[7].s0 > 1) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(16);
+ _paf->unload(16);
+ _video->clearPalette();
+ }
+ _res->_screensState[7].s0 = 1; // bugfix: conditioned with _pafSkipCutscenes
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ }
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen8() {
+ LvlObject *o = _g->findLvlObject(2, 0, 8);
+ _g->updateGatesLar(o, _lar2_gatesData, 7);
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen10() {
+ LvlObject *o = _g->findLvlObject(2, 0, 10);
+ _g->updateGatesLar(o, _lar2_gatesData, 4);
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen11() {
+ LvlObject *o = _g->findLvlObject(2, 0, 11);
+ _g->updateGatesLar(o, _lar2_gatesData, 5);
+ o = _g->findLvlObject(2, 1, 11);
+ _g->updateGatesLar(o, _lar2_gatesData, 6);
+ int offset = 0x18;
+ if ((_lar2_switchesData[0x11] & 1) == 0 && (_lar2_switchesData[0x11] & 0x40) != 0 && (_lar2_switchesData[0x19] & 1) == 0) {
+ _lar2_switchesData[0x19] = (_lar2_switchesData[0x19] | 1) & ~0x40;
+ offset = 0x1C;
+ _lar2_switchesData[0x1D] = (_lar2_switchesData[0x1D] | 1) & ~0x40;
+ }
+ if ((_lar2_switchesData[offset + 1] & 1) == 0 && (_lar2_switchesData[offset + 1] & 0x40) != 0) {
+ if ((_lar2_switchesData[0x21] & 1) != 0) {
+ goto next;
+ }
+ _lar2_switchesData[0x21] = (_lar2_switchesData[0x21] | 1) & ~0x40;
+ }
+ if ((_lar2_switchesData[0x21] & 1) == 0 && (_lar2_switchesData[0x21] & 0x40) != 0 && (_lar2_switchesData[offset + 1] & 1) == 0) {
+ _lar2_switchesData[offset + 1] = (_lar2_switchesData[offset + 1] | 1) & ~0x40;
+ _lar2_switchesData[0x1D] = (_lar2_switchesData[0x1D] | 1) & ~0x40;
+ offset = 0x1C;
+ }
+next:
+ if ((_lar2_switchesData[offset + 1] & 1) == 0 && (_lar2_switchesData[offset + 1] & 0x40) != 0 && (_lar2_switchesData[0x11] & 1) == 0) {
+ _lar2_switchesData[0x11] = (_lar2_switchesData[0x11] | 1) & ~0x40;
+ _lar2_switchesData[0x15] = (_lar2_switchesData[0x15] | 1) & ~0X40;
+ }
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen12() {
+ LvlObject *o = _g->findLvlObject(2, 0, 12);
+ _g->updateGatesLar(o, _lar2_gatesData, 8);
+ o = _g->findLvlObject(2, 1, 12);
+ _g->updateGatesLar(o, _lar2_gatesData, 9);
+ if (_res->_currentScreenResourceNum == 12) {
+ BoundingBox b1 = { 65, 84, 75, 88 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b1, &data->boundingBox)) {
+ _lar2_gatesData[4 * 8] &= 0xF;
+ o = _g->findLvlObject2(0, 0, 12);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ } else {
+ BoundingBox b2 = { 65, 163, 75, 167 };
+ if (_g->clipBoundingBox(&b2, &data->boundingBox)) {
+ _lar2_gatesData[4 * 9] &= 0xF;
+ o = _g->findLvlObject2(0, 1, 12);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ }
+ }
+ }
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ int offset = 0x2C;
+ if ((_lar2_switchesData[0x25] & 1) == 0 && (_lar2_switchesData[0x25] & 0x40) != 0 && (_lar2_switchesData[0x2D] & 1) == 0) {
+ offset = 0x30;
+ _lar2_switchesData[0x2D] = (_lar2_switchesData[0x2D] | 1) & ~0x40;
+ _lar2_switchesData[0x31] = (_lar2_switchesData[0x31] | 1) & ~0x40;
+ }
+ if ((_lar2_switchesData[offset + 1] & 1) == 0 && (_lar2_switchesData[offset + 1] & 0x40) != 0 && (_lar2_switchesData[0x25] & 1) == 0) {
+ _lar2_switchesData[0x25] = (_lar2_switchesData[0x25] | 1) & ~0x40;
+ _lar2_switchesData[0x29] = (_lar2_switchesData[0x29] | 1) & ~0x40;
+ }
+ }
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen19() {
+ if (_res->_currentScreenResourceNum == 19) {
+ if (_g->_currentLevelCheckpoint == 10 && _checkpoint == 11) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(19);
+ _paf->unload(19);
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ }
+ }
+}
+
+void Level_lar2::postScreenUpdate(int num) {
+ switch (num) {
+ case 2:
+ postScreenUpdate_lar2_screen2();
+ break;
+ case 3:
+ postScreenUpdate_lar2_screen3();
+ break;
+ case 4:
+ postScreenUpdate_lar2_screen4();
+ break;
+ case 5:
+ postScreenUpdate_lar2_screen5();
+ break;
+ case 6:
+ postScreenUpdate_lar2_screen6();
+ break;
+ case 7:
+ postScreenUpdate_lar2_screen7();
+ break;
+ case 8:
+ postScreenUpdate_lar2_screen8();
+ break;
+ case 10:
+ postScreenUpdate_lar2_screen10();
+ break;
+ case 11:
+ postScreenUpdate_lar2_screen11();
+ break;
+ case 12:
+ postScreenUpdate_lar2_screen12();
+ break;
+ case 13:
+ postScreenUpdate_lar2_screen13();
+ break;
+ case 19:
+ postScreenUpdate_lar2_screen19();
+ break;
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen2() {
+ LvlObject *o = _g->findLvlObject(2, 0, 2);
+ _g->updateGatesLar(o, _lar2_gatesData, 0);
+ if (_res->_currentScreenResourceNum == 2) {
+ if (_checkpoint == 0) {
+ _checkpoint = 1;
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen4() {
+ if (_res->_currentScreenResourceNum == 4) {
+ if (_checkpoint == 1) {
+ _checkpoint = 2;
+ }
+ if (_checkpoint >= 2) {
+ _lar2_gatesData[4 * 1] &= 0xF;
+ if (_checkpoint == 8) {
+ _lar2_gatesData[4 * 2] &= 0xF;
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(18);
+ }
+ }
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen5() {
+ if (_res->_currentScreenResourceNum == 5) {
+ if (_checkpoint == 7) {
+ _lar2_gatesData[4 * 3] = (_lar2_gatesData[4 * 3] & 0xF) | 0x10;
+ } else if (_checkpoint >= 3) {
+ _lar2_gatesData[4 * 3] &= 0xF;
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen6() {
+ if (_res->_currentScreenResourceNum == 6) {
+ if (_checkpoint == 2) {
+ _checkpoint = 3;
+ }
+ if (_checkpoint == 3) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(15);
+ }
+ _lar2_gatesData[4 * 3] &= 0xF; // bugfix: conditioned with _pafSkipCutscenes
+ } else if (_checkpoint == 6) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(17);
+ }
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen7() {
+ if (_res->_currentScreenResourceNum == 7) {
+ if (_checkpoint >= 4 && _checkpoint < 7) {
+ _res->_screensState[7].s0 = 1;
+ } else {
+ _res->_screensState[7].s0 = 0;
+ }
+ if (_checkpoint == 5) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(16);
+ }
+ }
+ if (_res->_screensState[7].s0 != 0) {
+ _res->_resLvlScreenBackgroundDataTable[7].currentBackgroundId = 1;
+ _res->_resLvlScreenBackgroundDataTable[7].currentMaskId = 1;
+ } else {
+ _res->_resLvlScreenBackgroundDataTable[7].currentBackgroundId = 0;
+ _res->_resLvlScreenBackgroundDataTable[7].currentMaskId = 0;
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen8() {
+ if (_res->_currentScreenResourceNum == 8 && _lar2_gatesData[4 * 7 + 2] > 1) {
+ _lar2_gatesData[4 * 7 + 2] = 1;
+ }
+ LvlObject *o = _g->findLvlObject(2, 0, 8);
+ _g->updateGatesLar(o, _lar2_gatesData, 7);
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen9() {
+ if (_res->_currentScreenResourceNum == 9) {
+ _lar2_gatesData[4 * 7 + 2] = 36; // gate closing countdown
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen15() {
+ if (_res->_currentScreenResourceNum == 15) {
+ if (_checkpoint == 9) {
+ _checkpoint = 10;
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen19() {
+ if (_res->_currentScreenResourceNum == 19) {
+ if (_checkpoint == 10) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(19);
+ }
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate(int num) {
+ switch (num) {
+ case 2:
+ preScreenUpdate_lar2_screen2();
+ break;
+ case 4:
+ preScreenUpdate_lar2_screen4();
+ break;
+ case 5:
+ preScreenUpdate_lar2_screen5();
+ break;
+ case 6:
+ preScreenUpdate_lar2_screen6();
+ break;
+ case 7:
+ preScreenUpdate_lar2_screen7();
+ break;
+ case 8:
+ case 11:
+ preScreenUpdate_lar2_screen8();
+ break;
+ case 9:
+ preScreenUpdate_lar2_screen9();
+ break;
+ case 15:
+ preScreenUpdate_lar2_screen15();
+ break;
+ case 19:
+ preScreenUpdate_lar2_screen19();
+ break;
+ }
+}
+
+void Level_lar2::tick() {
+ _g->updateSwitchesLar(13, _lar2_switchesData, _lar2_switchesBbox, _lar2_gatesData);
+}
+
+void Level_lar2::setupScreenCheckpoint_lar2_screen19() {
+ const int switchIndex = _lar2_setupScreen19Data[_checkpoint * 3 + 1];
+ for (int i = switchIndex; i < 13; ++i) {
+ const int offset = i * 4 + 1;
+ _lar2_switchesData[offset] = (_lar2_switchesData[offset] & ~0x40) | 1;
+ }
+ for (int i = switchIndex; i != 0; --i) {
+ const int offset = (i - 1) * 4 + 1;
+ _lar2_switchesData[offset] = (_lar2_switchesData[offset] & ~1) | 0x40;
+ }
+ static const uint8_t data[10] = { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 };
+ const int gateIndex = _lar2_setupScreen19Data[_checkpoint * 3];
+ for (int i = gateIndex; i < 10; ++i) {
+ const int num = i;
+ _lar2_gatesData[num * 4] = (data[num] << 4) | 2;
+ const uint32_t mask = 1 << num;
+ if (_lar2_gatesData[num * 4] & 0xF0) { // bugfix: original uses _lar1_gatesData
+ _g->_mstAndyVarMask &= ~mask;
+ } else {
+ _g->_mstAndyVarMask |= mask;
+ }
+ _g->_mstLevelGatesMask |= mask;
+ }
+ for (int i = gateIndex; i != 0; --i) {
+ const int num = i - 1;
+ _lar2_gatesData[num * 4] = (((data[num] == 0) ? 1 : 0) << 4) | 2;
+ const uint32_t mask = 1 << num;
+ if (_lar2_gatesData[num * 4] & 0xF0) { // bugfix: original uses _lar1_gatesData
+ _g->_mstAndyVarMask &= ~mask;
+ } else {
+ _g->_mstAndyVarMask |= mask;
+ }
+ _g->_mstLevelGatesMask |= mask;
+ }
+ if (g_debugMask & kDebug_SWITCHES) {
+ _g->dumpSwitchesLar(13, _lar2_switchesData, _lar2_switchesBbox, 10, _lar2_gatesData);
+ }
+}
+
+void Level_lar2::setupScreenCheckpoint(int num) {
+ switch (num) {
+ case 19:
+ setupScreenCheckpoint_lar2_screen19();
+ break;
+ }
+}
diff --git a/Src/Global/level9_dark.cpp b/Src/Global/level9_dark.cpp
new file mode 100644
index 0000000..46f5df4
--- /dev/null
+++ b/Src/Global/level9_dark.cpp
@@ -0,0 +1,79 @@
+
+// dark_hod
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "video.h"
+
+static const CheckpointData _dark_checkpointData[1] = {
+ { 117, 115, 0x300c, 48, 0, 0 }
+};
+
+static const uint8_t _dark_screenStartData[2] = {
+ 0x00, 0x00
+};
+
+struct Level_dark: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_dark_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _dark_screenStartData; }
+ //virtual void initialize();
+ //virtual void terminate();
+ //virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ //virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_dark_screen0();
+
+ void preScreenUpdate_dark_screen0();
+};
+
+Level *Level_dark_create() {
+ return new Level_dark;
+}
+
+void Level_dark::postScreenUpdate_dark_screen0() {
+ if (_res->_screensState[0].s0 != 0) {
+ ++_screenCounterTable[0];
+ if (_screenCounterTable[0] == 29) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(20);
+ _paf->unload(20);
+ _g->displayHintScreen(_res->_datHdr.yesNoQuitImage - 1, 25);
+ _paf->preload(21);
+ _paf->play(21);
+ _paf->unload(21);
+ }
+ _video->clearPalette();
+ _video->clearBackBuffer();
+ _g->_endLevel = true;
+ }
+ }
+}
+
+void Level_dark::postScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ postScreenUpdate_dark_screen0();
+ break;
+ }
+}
+
+void Level_dark::preScreenUpdate_dark_screen0() {
+ if (_res->_currentScreenResourceNum == 0) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(20);
+ }
+ _screenCounterTable[0] = 0;
+ _res->_screensState[0].s0 = 0;
+ }
+}
+
+void Level_dark::preScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ preScreenUpdate_dark_screen0();
+ break;
+ }
+}
diff --git a/SRC/lzw.cpp b/Src/Global/lzw.cpp
similarity index 64%
rename from SRC/lzw.cpp
rename to Src/Global/lzw.cpp
index fe6b62d..676c103 100644
--- a/SRC/lzw.cpp
+++ b/Src/Global/lzw.cpp
@@ -1,3 +1,7 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
#include "intern.h"
@@ -6,55 +10,52 @@ enum {
kClearCode = 1 << (kCodeWidth - 1),
kEndCode = kClearCode + 1,
kNewCodes = kEndCode + 1,
+ kStackSize = 8192,
kMaxBits = 12
};
struct LzwDecoder {
- uint16_t _prefix[4096];
- uint8_t _stack[8192];
+ uint16_t _prefix[1 << kMaxBits];
+ uint8_t _stack[kStackSize];
const uint8_t *_buf;
- uint8_t _currentBits;
- uint8_t _codeSize;
+ uint32_t _currentBits;
uint8_t _bitsLeft;
- uint32_t nextCode();
+ uint32_t nextCode(int codeSize);
int decode(uint8_t *dst);
};
static struct LzwDecoder _lzw;
-uint32_t LzwDecoder::nextCode() {
- if (_bitsLeft == 0) {
- _currentBits = *_buf++;
- _bitsLeft = 8;
- }
- uint32_t code = _currentBits >> (8 - _bitsLeft);
- while (_bitsLeft < _codeSize) {
- _currentBits = *_buf++;
- code |= _currentBits << _bitsLeft;
+uint32_t LzwDecoder::nextCode(int codeSize) { // 9 to 12bits
+ _currentBits |= (*_buf++) << _bitsLeft;
+ _bitsLeft += 8;
+ if (_bitsLeft < codeSize) {
+ _currentBits |= (*_buf++) << _bitsLeft;
_bitsLeft += 8;
}
- code &= (1 << _codeSize) - 1;
- _bitsLeft -= _codeSize;
+ const uint32_t code = _currentBits & ((1 << codeSize) - 1);
+ _currentBits >>= codeSize;
+ _bitsLeft -= codeSize;
return code;
}
int LzwDecoder::decode(uint8_t *dst) {
uint8_t *p = dst;
- uint32_t topSlot = 1 << kCodeWidth;
- uint8_t *stackPtr = &_stack[8191];
- uint32_t currentSlot = kNewCodes;
+ uint8_t *stackPtr = &_stack[kStackSize - 1];
uint32_t previousCode = 0;
uint32_t lastCode = 0;
uint32_t currentCode;
- _codeSize = kCodeWidth;
- while ((currentCode = nextCode()) != kEndCode) {
+ uint32_t currentSlot = kNewCodes;
+ uint32_t topSlot = 1 << kCodeWidth;
+ int codeSize = kCodeWidth;
+ while ((currentCode = nextCode(codeSize)) != kEndCode) {
if (currentCode == kClearCode) {
currentSlot = kNewCodes;
- _codeSize = kCodeWidth;
topSlot = 1 << kCodeWidth;
- while ((currentCode = nextCode()) == kClearCode) {
+ codeSize = kCodeWidth;
+ while ((currentCode = nextCode(codeSize)) == kClearCode) {
}
if (currentCode == kEndCode) {
break;
@@ -69,8 +70,8 @@ int LzwDecoder::decode(uint8_t *dst) {
uint32_t code = currentCode;
if (currentCode >= slot) {
code = lastCode;
- currentStackPtr = &_stack[8190];
- _stack[8190] = (uint8_t)previousCode;
+ currentStackPtr = &_stack[kStackSize - 2];
+ *currentStackPtr = (uint8_t)previousCode;
}
while (code >= kNewCodes) {
--currentStackPtr;
@@ -88,9 +89,9 @@ int LzwDecoder::decode(uint8_t *dst) {
++slot;
currentSlot = slot;
}
- if (slot >= topSlot && _codeSize < kMaxBits) {
+ if (slot >= topSlot && codeSize < kMaxBits) {
topSlot <<= 1;
- ++_codeSize;
+ ++codeSize;
}
while (currentStackPtr < stackPtr) {
*p++ = *currentStackPtr++;
diff --git a/Src/Global/lzw.h b/Src/Global/lzw.h
new file mode 100644
index 0000000..c677484
--- /dev/null
+++ b/Src/Global/lzw.h
@@ -0,0 +1,12 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef LZW_H__
+#define LZW_H__
+
+int decodeLZW(const uint8_t *src, uint8_t *dst);
+
+#endif // LZW_H__
+
diff --git a/Src/Global/main.cpp b/Src/Global/main.cpp
new file mode 100644
index 0000000..5023eac
--- /dev/null
+++ b/Src/Global/main.cpp
@@ -0,0 +1,260 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#if !defined(PSP) && !defined(WII)
+#include
+#endif
+#include
+#include
+
+#include "3p/inih/ini.h"
+
+#include "game.h"
+#include "menu.h"
+#include "mixer.h"
+#include "paf.h"
+#include "util.h"
+#include "resource.h"
+#include "system.h"
+#include "video.h"
+
+static const char *_title = "Heart of Darkness";
+
+static const char *_configIni = "hode.ini";
+
+static const char *_usage =
+ "hode - Heart of Darkness Interpreter\n"
+ "Usage: %s [OPTIONS]...\n"
+ " --datapath=PATH Path to data files (default '.')\n"
+ " --savepath=PATH Path to save files (default '.')\n"
+ " --level=NUM Start at level NUM\n"
+ " --checkpoint=NUM Start at checkpoint NUM\n"
+;
+
+static bool _fullscreen = false;
+static bool _widescreen = false;
+
+static const bool _runBenchmark = false;
+static bool _runMenu = true;
+static bool _displayLoadingScreen = true;
+
+static void lockAudio(int flag) {
+ if (flag) {
+ g_system->lockAudio();
+ } else {
+ g_system->unlockAudio();
+ }
+}
+
+static void mixAudio(void *userdata, int16_t *buf, int len) {
+ ((Game *)userdata)->mixAudio(buf, len);
+}
+
+static void setupAudio(Game *g) {
+ g->_mix._lock = lockAudio;
+ AudioCallback cb;
+ cb.proc = mixAudio;
+ cb.userdata = g;
+ g_system->startAudio(cb);
+}
+
+static const char *_defaultDataPath = ".";
+
+static const char *_defaultSavePath = ".";
+
+static const char *_levelNames[] = {
+ "rock",
+ "fort",
+ "pwr1",
+ "isld",
+ "lava",
+ "pwr2",
+ "lar1",
+ "lar2",
+ "dark",
+ 0
+};
+
+static bool configBool(const char *value) {
+ return strcasecmp(value, "true") == 0 || (strlen(value) == 2 && (value[0] == 't' || value[0] == '1'));
+}
+
+static int handleConfigIni(void *userdata, const char *section, const char *name, const char *value) {
+ Game *g = (Game *)userdata;
+ // fprintf(stdout, "config.ini: section '%s' name '%s' value '%s'\n", section, name, value);
+ if (strcmp(section, "engine") == 0) {
+ if (strcmp(name, "disable_paf") == 0) {
+ if (!g->_paf->_skipCutscenes) { // .paf file not found
+ g->_paf->_skipCutscenes = configBool(value);
+ }
+ } else if (strcmp(name, "disable_mst") == 0) {
+ g->_mstDisabled = configBool(value);
+ } else if (strcmp(name, "disable_sss") == 0) {
+ g->_sssDisabled = configBool(value);
+ } else if (strcmp(name, "disable_menu") == 0) {
+ _runMenu = !configBool(value);
+ } else if (strcmp(name, "max_active_sounds") == 0) {
+ g->_playingSssObjectsMax = atoi(value);
+ } else if (strcmp(name, "difficulty") == 0) {
+ g->_difficulty = atoi(value);
+ } else if (strcmp(name, "frame_duration") == 0) {
+ g->_frameMs = g->_paf->_frameMs = atoi(value);
+ } else if (strcmp(name, "loading_screen") == 0) {
+ _displayLoadingScreen = configBool(value);
+ }
+ } else if (strcmp(section, "display") == 0) {
+ if (strcmp(name, "scale_factor") == 0) {
+ const int scale = atoi(value);
+ g_system->setScaler(0, scale);
+ } else if (strcmp(name, "scale_algorithm") == 0) {
+ g_system->setScaler(value, 0);
+ } else if (strcmp(name, "gamma") == 0) {
+ g_system->setGamma(atof(value));
+ } else if (strcmp(name, "fullscreen") == 0) {
+ _fullscreen = configBool(value);
+ } else if (strcmp(name, "widescreen") == 0) {
+ _widescreen = configBool(value);
+ }
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ char *dataPath = 0;
+ char *savePath = 0;
+ int level = 0;
+ int checkpoint = 0;
+ bool resume = true; // resume game from 'setup.cfg'
+
+ g_debugMask = 0; //kDebug_GAME | kDebug_RESOURCE | kDebug_SOUND | kDebug_MONSTER;
+ int cheats = 0;
+
+#ifdef WII
+ System_earlyInit();
+ static const char *pathsWII[] = {
+ "sd:/hode",
+ "usb:/hode",
+ 0
+ };
+ for (int i = 0; pathsWII[i]; ++i) {
+ struct stat st;
+ if (stat(pathsWII[i], &st) == 0 && S_ISDIR(st.st_mode)) {
+ dataPath = strdup(pathsWII[i]);
+ savePath = strdup(pathsWII[i]);
+ break;
+ }
+ }
+#endif
+ if (System_hasCommandLine()) {
+ if (argc == 2) {
+ // data path as the only command line argument
+ struct stat st;
+ if (stat(argv[1], &st) == 0 && S_ISDIR(st.st_mode)) {
+ dataPath = strdup(argv[1]);
+ }
+ }
+ while (1) {
+ static struct option options[] = {
+ { "datapath", required_argument, 0, 1 },
+ { "savepath", required_argument, 0, 2 },
+ { "level", required_argument, 0, 3 },
+ { "checkpoint", required_argument, 0, 4 },
+ { "debug", required_argument, 0, 5 },
+ { "cheats", required_argument, 0, 6 },
+ { 0, 0, 0, 0 },
+ };
+ int index;
+ const int c = getopt_long(argc, argv, "", options, &index);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 1:
+ dataPath = strdup(optarg);
+ break;
+ case 2:
+ savePath = strdup(optarg);
+ break;
+ case 3:
+ if (optarg[0] >= '0' && optarg[0] <= '9') {
+ level = atoi(optarg);
+ } else {
+ for (int i = 0; _levelNames[i]; ++i) {
+ if (strcmp(_levelNames[i], optarg) == 0) {
+ level = i;
+ break;
+ }
+ }
+ }
+ resume = false;
+ break;
+ case 4:
+ checkpoint = atoi(optarg);
+ resume = false;
+ break;
+ case 5:
+ g_debugMask |= atoi(optarg);
+ break;
+ case 6:
+ cheats |= atoi(optarg);
+ break;
+ default:
+ fprintf(stdout, "%s\n", _usage);
+ return -1;
+ }
+ }
+ }
+ Game *g = new Game(dataPath ? dataPath : _defaultDataPath, savePath ? savePath : _defaultSavePath, cheats);
+ ini_parse(_configIni, handleConfigIni, g);
+ if (_runBenchmark) {
+ g->benchmarkCpu();
+ }
+ // load setup.dat (PC) or setup.dax (PSX)
+ g->_res->loadSetupDat();
+ const bool isPsx = g->_res->_isPsx;
+ g_system->init(_title, Video::W, Video::H, _fullscreen, _widescreen, isPsx);
+ setupAudio(g);
+ if (isPsx) {
+ g->_video->initPsx();
+ _runMenu = false;
+ }
+ if (_displayLoadingScreen) {
+ g->displayLoadingScreen();
+ }
+ do {
+ g->loadSetupCfg(resume);
+ if (_runMenu && resume) {
+ Menu *m = new Menu(g, g->_paf, g->_res, g->_video);
+ const bool runGame = m->mainLoop();
+ delete m;
+ if (!runGame) {
+ break;
+ }
+ }
+ bool levelChanged = false;
+ while (!g_system->inp.quit && level < kLvl_test) {
+ if (_displayLoadingScreen) {
+ g->displayLoadingScreen();
+ }
+ g->mainLoop(level, checkpoint, levelChanged);
+ // do not save progress when starting from a specific level checkpoint
+ if (resume) {
+ g->saveSetupCfg();
+ }
+ if (g->_res->_isDemo) {
+ break;
+ }
+ level = g->_currentLevel + 1;
+ checkpoint = 0;
+ levelChanged = true;
+ }
+ } while (!g_system->inp.quit && resume && !isPsx); // do not return to menu when starting from a specific level checkpoint
+ g_system->stopAudio();
+ g_system->destroy();
+ delete g;
+ free(dataPath);
+ free(savePath);
+ return 0;
+}
diff --git a/Src/Global/mdec.cpp b/Src/Global/mdec.cpp
new file mode 100644
index 0000000..d14775a
--- /dev/null
+++ b/Src/Global/mdec.cpp
@@ -0,0 +1,227 @@
+
+#include
+#include "intern.h"
+#include "mdec.h"
+#include "mdec_coeffs.h"
+
+struct BitStream { // most significant 16 bits
+ const uint8_t *_src;
+ uint32_t _bits;
+ int _len;
+ const uint8_t *_end;
+
+ BitStream(const uint8_t *src, int size)
+ : _src(src), _bits(0), _len(0), _end(src + size) {
+ }
+
+ int bitsAvailable() const {
+ return (_end - _src) * 8 + _len;
+ }
+
+ int getBits(int count) { // 6 to 16 bits
+ if (_len < count) {
+ _bits <<= 16;
+ assert(_src < _end);
+ _bits |= READ_LE_UINT16(_src); _src += 2;
+ _len += 16;
+ }
+ assert(_len >= count);
+ const int value = (_bits >> (_len - count)) & ((1 << count) - 1);
+ _len -= count;
+ return value;
+ }
+ int getSignedBits(int len) {
+ const int shift = 32 - len;
+ const int32_t value = getBits(len);
+ return (value << shift) >> shift;
+ }
+ bool getBit() {
+ if (_len == 0) {
+ assert(_src < _end);
+ _bits = READ_LE_UINT16(_src); _src += 2;
+ _len = 16;
+ }
+ --_len;
+ return (_bits & (1 << _len)) != 0;
+ }
+};
+
+static int readDC(BitStream *bs, int version) {
+ assert(version == 2);
+ return bs->getSignedBits(10);
+}
+
+static void readAC(BitStream *bs, int *coefficients) {
+ int count = 0;
+ int node = 0;
+ while (bs->bitsAvailable() > 0) {
+ const uint16_t value = _acHuffTree[node].value;
+ switch (value) {
+ case kAcHuff_EscapeCode: {
+ const int zeroes = bs->getBits(6);
+ count += zeroes + 1;
+ assert(count < 63);
+ coefficients += zeroes;
+ *coefficients++ = bs->getSignedBits(10);
+ }
+ break;
+ case kAcHuff_EndOfBlock:
+ return;
+ case 0:
+ if (bs->getBit()) {
+ node = _acHuffTree[node].right;
+ } else {
+ node = _acHuffTree[node].left;
+ }
+ continue;
+ default: {
+ const int zeroes = value >> 8;
+ count += zeroes + 1;
+ assert(count < 63);
+ coefficients += zeroes;
+ const int nonZeroes = value & 255;
+ *coefficients++ = bs->getBit() ? -nonZeroes : nonZeroes;
+ }
+ break;
+ }
+ node = 0; // root
+ }
+}
+
+static const uint8_t _zigZagTable[8 * 8] = {
+ 0, 1, 5, 6, 14, 15, 27, 28,
+ 2, 4, 7, 13, 16, 26, 29, 42,
+ 3, 8, 12, 17, 25, 30, 41, 43,
+ 9, 11, 18, 24, 31, 40, 44, 53,
+ 10, 19, 23, 32, 39, 45, 52, 54,
+ 20, 22, 33, 38, 46, 51, 55, 60,
+ 21, 34, 37, 47, 50, 56, 59, 61,
+ 35, 36, 48, 49, 57, 58, 62, 63
+};
+
+static const uint8_t _quantizationTable[8 * 8] = {
+ 2, 16, 19, 22, 26, 27, 29, 34,
+ 16, 16, 22, 24, 27, 29, 34, 37,
+ 19, 22, 26, 27, 29, 34, 34, 38,
+ 22, 22, 26, 27, 29, 34, 37, 40,
+ 22, 26, 27, 29, 32, 35, 40, 48,
+ 26, 27, 29, 32, 35, 40, 48, 58,
+ 26, 27, 29, 34, 38, 46, 56, 69,
+ 27, 29, 35, 38, 46, 56, 69, 83
+};
+
+static void dequantizeBlock(int *coefficients, float *block, int scale) {
+ block[0] = coefficients[0] * _quantizationTable[0]; // DC
+ for (int i = 1; i < 8 * 8; i++) {
+ block[i] = coefficients[_zigZagTable[i]] * _quantizationTable[i] * scale / 8.f;
+ }
+}
+
+static const double _idct8x8[8][8] = {
+ { 0.353553390593274, 0.490392640201615, 0.461939766255643, 0.415734806151273, 0.353553390593274, 0.277785116509801, 0.191341716182545, 0.097545161008064 },
+ { 0.353553390593274, 0.415734806151273, 0.191341716182545, -0.097545161008064, -0.353553390593274, -0.490392640201615, -0.461939766255643, -0.277785116509801 },
+ { 0.353553390593274, 0.277785116509801, -0.191341716182545, -0.490392640201615, -0.353553390593274, 0.097545161008064, 0.461939766255643, 0.415734806151273 },
+ { 0.353553390593274, 0.097545161008064, -0.461939766255643, -0.277785116509801, 0.353553390593274, 0.415734806151273, -0.191341716182545, -0.490392640201615 },
+ { 0.353553390593274, -0.097545161008064, -0.461939766255643, 0.277785116509801, 0.353553390593274, -0.415734806151273, -0.191341716182545, 0.490392640201615 },
+ { 0.353553390593274, -0.277785116509801, -0.191341716182545, 0.490392640201615, -0.353553390593273, -0.097545161008064, 0.461939766255643, -0.415734806151273 },
+ { 0.353553390593274, -0.415734806151273, 0.191341716182545, 0.097545161008064, -0.353553390593274, 0.490392640201615, -0.461939766255643, 0.277785116509801 },
+ { 0.353553390593274, -0.490392640201615, 0.461939766255643, -0.415734806151273, 0.353553390593273, -0.277785116509801, 0.191341716182545, -0.097545161008064 }
+};
+
+static void idct(float *dequantData, float *result) {
+ float tmp[8 * 8];
+ // 1D IDCT rows
+ for (int y = 0; y < 8; y++) {
+ for (int x = 0; x < 8; x++) {
+ float p = 0;
+ for (int i = 0; i < 8; ++i) {
+ p += dequantData[i] * _idct8x8[x][i];
+ }
+ tmp[y + x * 8] = p;
+ }
+ dequantData += 8;
+ }
+ // 1D IDCT columns
+ for (int x = 0; x < 8; x++) {
+ const float *u = tmp + x * 8;
+ for (int y = 0; y < 8; y++) {
+ float p = 0;
+ for (int i = 0; i < 8; ++i) {
+ p += u[i] * _idct8x8[y][i];
+ }
+ result[y * 8 + x] = p;
+ }
+ }
+}
+
+static void decodeBlock(BitStream *bs, int x8, int y8, uint8_t *dst, int dstPitch, int scale, int version) {
+ int coefficients[8 * 8];
+ memset(coefficients, 0, sizeof(coefficients));
+ coefficients[0] = readDC(bs, version);
+ readAC(bs, &coefficients[1]);
+
+ float dequantData[8 * 8];
+ dequantizeBlock(coefficients, dequantData, scale);
+
+ float idctData[8 * 8];
+ idct(dequantData, idctData);
+
+ dst += (y8 * dstPitch + x8) * 8;
+ for (int y = 0; y < 8; y++) {
+ for (int x = 0; x < 8; x++) {
+ const int val = (int)round(idctData[y * 8 + x]); // (-128,127) range
+ dst[x] = (val < -128) ? 0 : ((val > 127) ? 255 : (128 + val));
+ }
+ dst += dstPitch;
+ }
+}
+
+int decodeMDEC(const uint8_t *src, int len, const uint8_t *mborder, int mblen, int w, int h, MdecOutput *out) {
+ BitStream bs(src, len);
+ bs.getBits(16);
+ const uint16_t vlc = bs.getBits(16);
+ assert(vlc == 0x3800);
+ const uint16_t qscale = bs.getBits(16);
+ const uint16_t version = bs.getBits(16);
+ // fprintf(stdout, "mdec qscale %d version %d w %d h %d\n", qscale, version, w, h);
+ assert(version == 2);
+
+ const int blockW = (w + 15) / 16;
+ const int blockH = (h + 15) / 16;
+
+ const int yPitch = out->planes[kOutputPlaneY].pitch;
+ uint8_t *yPtr = out->planes[kOutputPlaneY].ptr + out->y * yPitch + out->x;
+ const int cbPitch = out->planes[kOutputPlaneCb].pitch;
+ uint8_t *cbPtr = out->planes[kOutputPlaneCb].ptr + (out->y * cbPitch + out->x) / 2;
+ const int crPitch = out->planes[kOutputPlaneCr].pitch;
+ uint8_t *crPtr = out->planes[kOutputPlaneCr].ptr + (out->y * crPitch + out->x) / 2;
+
+ int z = 0;
+ for (int x = 0, x2 = 0; x < blockW; ++x, x2 += 2) {
+ for (int y = 0, y2 = 0; y < blockH; ++y, y2 += 2) {
+ if (z < mblen) {
+ const uint8_t xy = mborder[z];
+ if ((xy & 15) != x || (xy >> 4) != y) {
+ continue;
+ }
+ ++z;
+ }
+ decodeBlock(&bs, x, y, crPtr, crPitch, qscale, version);
+ decodeBlock(&bs, x, y, cbPtr, cbPitch, qscale, version);
+ decodeBlock(&bs, x2, y2, yPtr, yPitch, qscale, version);
+ decodeBlock(&bs, x2 + 1, y2, yPtr, yPitch, qscale, version);
+ decodeBlock(&bs, x2, y2 + 1, yPtr, yPitch, qscale, version);
+ decodeBlock(&bs, x2 + 1, y2 + 1, yPtr, yPitch, qscale, version);
+ if (mborder && z == mblen) {
+ goto end;
+ }
+ }
+ }
+end:
+ if (!mborder && bs.bitsAvailable() >= 11) {
+ const int eof = bs.getBits(11);
+ assert(eof == 0x3FE || eof == 0x3FF);
+ }
+
+ return bs._src - src;
+}
diff --git a/Src/Global/mdec.h b/Src/Global/mdec.h
new file mode 100644
index 0000000..6739055
--- /dev/null
+++ b/Src/Global/mdec.h
@@ -0,0 +1,24 @@
+
+#ifndef MDEC_H__
+#define MDEC_H__
+
+#include
+
+enum {
+ kOutputPlaneY = 0,
+ kOutputPlaneCb = 1,
+ kOutputPlaneCr = 2
+};
+
+struct MdecOutput {
+ int x, y;
+ int w, h;
+ struct {
+ uint8_t *ptr;
+ int pitch;
+ } planes[3];
+};
+
+int decodeMDEC(const uint8_t *src, int len, const uint8_t *mborder, int mblen, int w, int h, MdecOutput *out);
+
+#endif // MDEC_H__
diff --git a/Src/Global/mdec_coeffs.h b/Src/Global/mdec_coeffs.h
new file mode 100644
index 0000000..d08658e
--- /dev/null
+++ b/Src/Global/mdec_coeffs.h
@@ -0,0 +1,235 @@
+static const uint16_t kAcHuff_EscapeCode = 0xfffe;
+static const uint16_t kAcHuff_EndOfBlock = 0xffff;
+struct AcHuff {
+ int16_t left;
+ int16_t right;
+ uint16_t value;
+};
+static const AcHuff _acHuffTree[] = {
+ { 3, 1, 0 },
+ { 225, 2, 0 },
+ { -1, -1, 0x0001 },
+ { 9, 4, 0 },
+ { 6, 5, 0 },
+ { -1, -1, 0x0101 },
+ { 7, 8, 0 },
+ { -1, -1, 0x0002 },
+ { -1, -1, 0x0201 },
+ { 16, 10, 0 },
+ { 11, 13, 0 },
+ { 32, 12, 0 },
+ { -1, -1, 0x0003 },
+ { 14, 15, 0 },
+ { -1, -1, 0x0401 },
+ { -1, -1, 0x0301 },
+ { 24, 17, 0 },
+ { 18, 21, 0 },
+ { 19, 20, 0 },
+ { -1, -1, 0x0701 },
+ { -1, -1, 0x0601 },
+ { 22, 23, 0 },
+ { -1, -1, 0x0102 },
+ { -1, -1, 0x0501 },
+ { 47, 25, 0 },
+ { 26, 29, 0 },
+ { 27, 28, 0 },
+ { -1, -1, 0x0202 },
+ { -1, -1, 0x0901 },
+ { 30, 31, 0 },
+ { -1, -1, 0x0004 },
+ { -1, -1, 0x0801 },
+ { 33, 40, 0 },
+ { 34, 37, 0 },
+ { 35, 36, 0 },
+ { -1, -1, 0x0d01 },
+ { -1, -1, 0x0006 },
+ { 38, 39, 0 },
+ { -1, -1, 0x0c01 },
+ { -1, -1, 0x0b01 },
+ { 41, 44, 0 },
+ { 42, 43, 0 },
+ { -1, -1, 0x0302 },
+ { -1, -1, 0x0103 },
+ { 45, 46, 0 },
+ { -1, -1, 0x0005 },
+ { -1, -1, 0x0a01 },
+ { 48, 224, 0 },
+ { 64, 49, 0 },
+ { 50, 57, 0 },
+ { 51, 54, 0 },
+ { 52, 53, 0 },
+ { -1, -1, 0x1001 },
+ { -1, -1, 0x0502 },
+ { 55, 56, 0 },
+ { -1, -1, 0x0007 },
+ { -1, -1, 0x0203 },
+ { 58, 61, 0 },
+ { 59, 60, 0 },
+ { -1, -1, 0x0104 },
+ { -1, -1, 0x0f01 },
+ { 62, 63, 0 },
+ { -1, -1, 0x0e01 },
+ { -1, -1, 0x0402 },
+ { 96, 65, 0 },
+ { 66, 81, 0 },
+ { 67, 74, 0 },
+ { 68, 71, 0 },
+ { 69, 70, 0 },
+ { -1, -1, 0x000b },
+ { -1, -1, 0x0802 },
+ { 72, 73, 0 },
+ { -1, -1, 0x0403 },
+ { -1, -1, 0x000a },
+ { 75, 78, 0 },
+ { 76, 77, 0 },
+ { -1, -1, 0x0204 },
+ { -1, -1, 0x0702 },
+ { 79, 80, 0 },
+ { -1, -1, 0x1501 },
+ { -1, -1, 0x1401 },
+ { 82, 89, 0 },
+ { 83, 86, 0 },
+ { 84, 85, 0 },
+ { -1, -1, 0x0009 },
+ { -1, -1, 0x1301 },
+ { 87, 88, 0 },
+ { -1, -1, 0x1201 },
+ { -1, -1, 0x0105 },
+ { 90, 93, 0 },
+ { 91, 92, 0 },
+ { -1, -1, 0x0303 },
+ { -1, -1, 0x0008 },
+ { 94, 95, 0 },
+ { -1, -1, 0x0602 },
+ { -1, -1, 0x1101 },
+ { 128, 97, 0 },
+ { 98, 113, 0 },
+ { 99, 106, 0 },
+ { 100, 103, 0 },
+ { 101, 102, 0 },
+ { -1, -1, 0x0a02 },
+ { -1, -1, 0x0902 },
+ { 104, 105, 0 },
+ { -1, -1, 0x0503 },
+ { -1, -1, 0x0304 },
+ { 107, 110, 0 },
+ { 108, 109, 0 },
+ { -1, -1, 0x0205 },
+ { -1, -1, 0x0107 },
+ { 111, 112, 0 },
+ { -1, -1, 0x0106 },
+ { -1, -1, 0x000f },
+ { 114, 121, 0 },
+ { 115, 118, 0 },
+ { 116, 117, 0 },
+ { -1, -1, 0x000e },
+ { -1, -1, 0x000d },
+ { 119, 120, 0 },
+ { -1, -1, 0x000c },
+ { -1, -1, 0x1a01 },
+ { 122, 125, 0 },
+ { 123, 124, 0 },
+ { -1, -1, 0x1901 },
+ { -1, -1, 0x1801 },
+ { 126, 127, 0 },
+ { -1, -1, 0x1701 },
+ { -1, -1, 0x1601 },
+ { 160, 129, 0 },
+ { 130, 145, 0 },
+ { 131, 138, 0 },
+ { 132, 135, 0 },
+ { 133, 134, 0 },
+ { -1, -1, 0x001f },
+ { -1, -1, 0x001e },
+ { 136, 137, 0 },
+ { -1, -1, 0x001d },
+ { -1, -1, 0x001c },
+ { 139, 142, 0 },
+ { 140, 141, 0 },
+ { -1, -1, 0x001b },
+ { -1, -1, 0x001a },
+ { 143, 144, 0 },
+ { -1, -1, 0x0019 },
+ { -1, -1, 0x0018 },
+ { 146, 153, 0 },
+ { 147, 150, 0 },
+ { 148, 149, 0 },
+ { -1, -1, 0x0017 },
+ { -1, -1, 0x0016 },
+ { 151, 152, 0 },
+ { -1, -1, 0x0015 },
+ { -1, -1, 0x0014 },
+ { 154, 157, 0 },
+ { 155, 156, 0 },
+ { -1, -1, 0x0013 },
+ { -1, -1, 0x0012 },
+ { 158, 159, 0 },
+ { -1, -1, 0x0011 },
+ { -1, -1, 0x0010 },
+ { 192, 161, 0 },
+ { 162, 177, 0 },
+ { 163, 170, 0 },
+ { 164, 167, 0 },
+ { 165, 166, 0 },
+ { -1, -1, 0x0028 },
+ { -1, -1, 0x0027 },
+ { 168, 169, 0 },
+ { -1, -1, 0x0026 },
+ { -1, -1, 0x0025 },
+ { 171, 174, 0 },
+ { 172, 173, 0 },
+ { -1, -1, 0x0024 },
+ { -1, -1, 0x0023 },
+ { 175, 176, 0 },
+ { -1, -1, 0x0022 },
+ { -1, -1, 0x0021 },
+ { 178, 185, 0 },
+ { 179, 182, 0 },
+ { 180, 181, 0 },
+ { -1, -1, 0x0020 },
+ { -1, -1, 0x010e },
+ { 183, 184, 0 },
+ { -1, -1, 0x010d },
+ { -1, -1, 0x010c },
+ { 186, 189, 0 },
+ { 187, 188, 0 },
+ { -1, -1, 0x010b },
+ { -1, -1, 0x010a },
+ { 190, 191, 0 },
+ { -1, -1, 0x0109 },
+ { -1, -1, 0x0108 },
+ { -1, 193, 0 },
+ { 194, 209, 0 },
+ { 195, 202, 0 },
+ { 196, 199, 0 },
+ { 197, 198, 0 },
+ { -1, -1, 0x0112 },
+ { -1, -1, 0x0111 },
+ { 200, 201, 0 },
+ { -1, -1, 0x0110 },
+ { -1, -1, 0x010f },
+ { 203, 206, 0 },
+ { 204, 205, 0 },
+ { -1, -1, 0x0603 },
+ { -1, -1, 0x1002 },
+ { 207, 208, 0 },
+ { -1, -1, 0x0f02 },
+ { -1, -1, 0x0e02 },
+ { 210, 217, 0 },
+ { 211, 214, 0 },
+ { 212, 213, 0 },
+ { -1, -1, 0x0d02 },
+ { -1, -1, 0x0c02 },
+ { 215, 216, 0 },
+ { -1, -1, 0x0b02 },
+ { -1, -1, 0x1f01 },
+ { 218, 221, 0 },
+ { 219, 220, 0 },
+ { -1, -1, 0x1e01 },
+ { -1, -1, 0x1d01 },
+ { 222, 223, 0 },
+ { -1, -1, 0x1c01 },
+ { -1, -1, 0x1b01 },
+ { -1, -1, 0xfffe },
+ { -1, -1, 0xffff },
+};
diff --git a/Src/Global/menu.cpp b/Src/Global/menu.cpp
new file mode 100644
index 0000000..efaba16
--- /dev/null
+++ b/Src/Global/menu.cpp
@@ -0,0 +1,1800 @@
+
+#include "game.h"
+#include "menu.h"
+#include "lzw.h"
+#include "paf.h"
+#include "resource.h"
+#include "system.h"
+#include "util.h"
+#include "video.h"
+
+enum {
+ kTitleScreen_AssignPlayer = 0,
+ kTitleScreen_Play = 1,
+ kTitleScreen_Options = 2,
+ kTitleScreen_Quit = 3,
+ kTitleScreenPSX_Load = 3, // PSX does not have 'Quit'
+ kTitleScreenPSX_Save = 4
+};
+
+enum {
+ kMenu_NewGame = 0,
+ kMenu_CurrentGame = 2,
+ kMenu_Load = 4,
+ kMenu_ResumeGame = 7,
+ kMenu_Settings = 8,
+ kMenu_Quit = 15,
+ kMenu_Cutscenes = 17
+};
+
+enum {
+ kSound_0x60 = 0x60 / 8, // test sound
+ kSound_0x70 = 0x70 / 8, // move cursor
+ kSound_0x78 = 0x78 / 8, // select
+ kSound_0x80 = 0x80 / 8, // return
+ kSound_0x88 = 0x88 / 8, // reset sound setting
+ kSound_0x90 = 0x90 / 8, // increase/decrease volume
+ kSound_0x98 = 0x98 / 8,
+ kSound_0xA0 = 0xA0 / 8 // background sounds
+};
+
+enum {
+ kCursor_Select = 0,
+ kCursor_Left = 1,
+ kCursor_Right = 2,
+ kCursor_Up = 3,
+ kCursor_Down = 4
+};
+
+enum {
+ kSettingNum_Controls = 0,
+ kSettingNum_Difficulty = 1,
+ kSettingNum_Sound = 2,
+ kSettingNum_Confirm = 3
+};
+
+enum {
+ kSoundNum_Stereo = 0,
+ kSoundNum_Volume = 1,
+ kSoundNum_Confirm = 2,
+ kSoundNum_Test = 3,
+ kSoundNum_Cancel = 4,
+ kSoundNum_Reset = 5
+};
+
+static bool isEmptySetupCfg(SetupConfig *config, int num) {
+ return config->players[num].levelNum == 0 && config->players[num].checkpointNum == 0 && config->players[num].cutscenesMask == 0;
+}
+
+Menu::Menu(Game *g, PafPlayer *paf, Resource *res, Video *video)
+ : _g(g), _paf(paf), _res(res), _video(video) {
+
+ _config = &_g->_setupConfig;
+}
+
+void Menu::setVolume() {
+ const int volume = _config->players[_config->currentPlayer].volume;
+ if (volume != _g->_snd_masterVolume) {
+ _g->_snd_masterVolume = volume;
+ }
+}
+
+static uint32_t readBitmapsGroup(int count, DatBitmapsGroup *bitmapsGroup, uint32_t ptrOffset, int paletteSize) {
+ const uint32_t baseOffset = ptrOffset;
+ for (int i = 0; i < count; ++i) {
+ int size;
+ if (paletteSize == 0) { // PSX
+ size = le32toh(bitmapsGroup[i].offset);
+ } else {
+ size = bitmapsGroup[i].w * bitmapsGroup[i].h;
+ }
+ bitmapsGroup[i].offset = ptrOffset - baseOffset;
+ bitmapsGroup[i].palette = bitmapsGroup[i].offset + size;
+ ptrOffset += size + paletteSize;
+ }
+ return ptrOffset - baseOffset;
+}
+
+static uint32_t readSoundData(uint8_t *soundData, uint32_t soundDataSize) {
+ const int soundListsCount = READ_LE_UINT32(soundData + 4);
+ const uint8_t *listData = soundData + 8 + soundListsCount * 8;
+ for (int i = 0; i < soundListsCount; ++i) {
+ WRITE_LE_UINT32(soundData + 8 + i * 8, listData - soundData);
+ const int count = READ_LE_UINT32(soundData + 8 + i * 8 + 4);
+ listData += count * sizeof(uint16_t);
+ }
+ assert((uint32_t)(listData - soundData) == soundDataSize);
+ return soundDataSize;
+}
+
+void Menu::loadData() {
+ _g->_mix._lock(1);
+ _res->loadDatMenuBuffers();
+ _g->clearSoundObjects();
+ _g->_mix._lock(0);
+
+ const int version = _res->_datHdr.version;
+
+ const int paletteSize = _res->_isPsx ? 0 : 256 * 3;
+
+ const uint8_t *ptr = _res->_menuBuffer1;
+ uint32_t hdrOffset, ptrOffset = 0;
+
+ if (version == 10) {
+
+ _titleSprites = (DatSpritesGroup *)(ptr + ptrOffset);
+ _titleSprites->size = le32toh(_titleSprites->size);
+ _titleSprites->count = le16toh(_titleSprites->count);
+ ptrOffset += sizeof(DatSpritesGroup) + _titleSprites->size;
+ _titleSprites->firstFrameOffset = 0;
+
+ _playerSprites = (DatSpritesGroup *)(ptr + ptrOffset);
+ _playerSprites->size = le32toh(_playerSprites->size);
+ _playerSprites->count = le16toh(_playerSprites->count);
+ ptrOffset += sizeof(DatSpritesGroup) + _playerSprites->size;
+ _playerSprites->firstFrameOffset = 0;
+
+ _titleBitmapSize = READ_LE_UINT32(ptr + ptrOffset);
+ _titleBitmapData = ptr + ptrOffset + sizeof(DatBitmap);
+ ptrOffset += sizeof(DatBitmap) + _titleBitmapSize + paletteSize;
+
+ _playerBitmapSize = READ_LE_UINT32(ptr + ptrOffset);
+ _playerBitmapData = ptr + ptrOffset + sizeof(DatBitmap);
+ ptrOffset += sizeof(DatBitmap) + _playerBitmapSize + paletteSize;
+
+ const int size = READ_LE_UINT32(ptr + ptrOffset); ptrOffset += 4;
+ assert((size % (16 * 10)) == 0);
+ _digitsData = ptr + ptrOffset;
+ ptrOffset += size;
+
+ const int cutscenesCount = _res->_datHdr.cutscenesCount;
+ _cutscenesBitmaps = (DatBitmapsGroup *)(ptr + ptrOffset);
+ ptrOffset += cutscenesCount * sizeof(DatBitmapsGroup);
+ _cutscenesBitmapsData = ptr + ptrOffset;
+ ptrOffset += readBitmapsGroup(cutscenesCount, _cutscenesBitmaps, ptrOffset, paletteSize);
+
+ for (int i = 0; i < kCheckpointLevelsCount; ++i) {
+ DatBitmapsGroup *bitmapsGroup = (DatBitmapsGroup *)(ptr + ptrOffset);
+ _checkpointsBitmaps[i] = bitmapsGroup;
+ const int count = _res->_datHdr.levelCheckpointsCount[i];
+ ptrOffset += count * sizeof(DatBitmapsGroup);
+ _checkpointsBitmapsData[i] = ptr + ptrOffset;
+ ptrOffset += readBitmapsGroup(count, bitmapsGroup, ptrOffset, paletteSize);
+ }
+
+ _soundData = ptr + ptrOffset;
+ readSoundData(_res->_menuBuffer1 + ptrOffset, _res->_datHdr.soundDataSize);
+
+ } else if (version == 11) {
+
+ hdrOffset = 4;
+
+ ptrOffset = 4 + (2 + kOptionsCount) * sizeof(DatBitmap);
+ ptrOffset += _res->_datHdr.cutscenesCount * sizeof(DatBitmapsGroup);
+ for (int i = 0; i < kCheckpointLevelsCount; ++i) {
+ ptrOffset += _res->_datHdr.levelCheckpointsCount[i] * sizeof(DatBitmapsGroup);
+ }
+ ptrOffset += _res->_datHdr.levelsCount * sizeof(DatBitmapsGroup);
+
+ _titleBitmapSize = READ_LE_UINT32(ptr + hdrOffset);
+ hdrOffset += sizeof(DatBitmap);
+ _titleBitmapData = ptr + ptrOffset;
+ ptrOffset += _titleBitmapSize + paletteSize;
+
+ _playerBitmapSize = READ_LE_UINT32(ptr + hdrOffset);
+ hdrOffset += sizeof(DatBitmap);
+ _playerBitmapData = ptr + ptrOffset;
+ ptrOffset += _playerBitmapSize + paletteSize;
+
+ for (int i = 0; i < kOptionsCount; ++i) {
+ _optionsBitmapSize[i] = READ_LE_UINT32(ptr + hdrOffset);
+ hdrOffset += sizeof(DatBitmap);
+ if (_optionsBitmapSize[i] != 0) {
+ _optionsBitmapData[i] = ptr + ptrOffset;
+ ptrOffset += _optionsBitmapSize[i] + paletteSize;
+ } else {
+ _optionsBitmapData[i] = 0;
+ }
+ }
+
+ const int cutscenesCount = _res->_datHdr.cutscenesCount;
+ _cutscenesBitmaps = (DatBitmapsGroup *)(ptr + hdrOffset);
+ _cutscenesBitmapsData = ptr + ptrOffset;
+ hdrOffset += cutscenesCount * sizeof(DatBitmapsGroup);
+ ptrOffset += readBitmapsGroup(cutscenesCount, _cutscenesBitmaps, ptrOffset, paletteSize);
+
+ for (int i = 0; i < kCheckpointLevelsCount; ++i) {
+ DatBitmapsGroup *bitmapsGroup = (DatBitmapsGroup *)(ptr + hdrOffset);
+ _checkpointsBitmaps[i] = bitmapsGroup;
+ _checkpointsBitmapsData[i] = ptr + ptrOffset;
+ const int count = _res->_datHdr.levelCheckpointsCount[i];
+ hdrOffset += count * sizeof(DatBitmapsGroup);
+ ptrOffset += readBitmapsGroup(count, bitmapsGroup, ptrOffset, paletteSize);
+ }
+
+ const int levelsCount = _res->_datHdr.levelsCount;
+ _levelsBitmaps = (DatBitmapsGroup *)(ptr + hdrOffset);
+ _levelsBitmapsData = ptr + ptrOffset;
+ readBitmapsGroup(levelsCount, _levelsBitmaps, ptrOffset, paletteSize);
+ }
+
+ ptr = _res->_menuBuffer0;
+ ptrOffset = 0;
+
+ if (version == 11) {
+
+ _titleSprites = (DatSpritesGroup *)(ptr + ptrOffset);
+ _titleSprites->size = le32toh(_titleSprites->size);
+ ptrOffset += sizeof(DatSpritesGroup) + _titleSprites->size;
+ _titleSprites->count = le16toh(_titleSprites->count);
+ _titleSprites->firstFrameOffset = 0;
+
+ _playerSprites = (DatSpritesGroup *)(ptr + ptrOffset);
+ _playerSprites->size = le32toh(_playerSprites->size);
+ ptrOffset += sizeof(DatSpritesGroup) + _playerSprites->size;
+ _playerSprites->count = le16toh(_playerSprites->count);
+ _playerSprites->firstFrameOffset = 0;
+
+ _optionData = ptr + ptrOffset;
+ ptrOffset += _res->_datHdr.menusCount * 8;
+
+ const int size = READ_LE_UINT32(ptr + ptrOffset); ptrOffset += 4;
+ assert((size % (16 * 10)) == 0);
+ _digitsData = ptr + ptrOffset;
+ ptrOffset += size;
+
+ _soundData = ptr + ptrOffset;
+ readSoundData(_res->_menuBuffer0 + ptrOffset, _res->_datHdr.soundDataSize);
+ ptrOffset += _res->_datHdr.soundDataSize;
+ }
+
+ if (_res->_isPsx) {
+ for (int i = 0; i < 3; ++i) {
+ DatSpritesGroup *sprites = (DatSpritesGroup *)(ptr + ptrOffset);
+ ptrOffset += sizeof(DatSpritesGroup) + ((le32toh(sprites->size) + 3) & ~3);
+ }
+ ptrOffset += 0x300 * 3;
+ }
+
+ _iconsSprites = (DatSpritesGroup *)(ptr + ptrOffset);
+ const int iconsCount = _res->_datHdr.iconsCount;
+ ptrOffset += iconsCount * sizeof(DatSpritesGroup);
+ _iconsSpritesData = ptr + ptrOffset;
+ for (int i = 0; i < iconsCount; ++i) {
+ _iconsSprites[i].size = le32toh(_iconsSprites[i].size);
+ _iconsSprites[i].count = le16toh(_iconsSprites[i].count);
+ _iconsSprites[i].firstFrameOffset = ptr + ptrOffset - _iconsSpritesData;
+ ptrOffset += _iconsSprites[i].size;
+ }
+
+ _optionsButtonSpritesCount = READ_LE_UINT32(ptr + ptrOffset); ptrOffset += 4;
+ if (_optionsButtonSpritesCount != 0) {
+ _optionsButtonSpritesData = ptr + ptrOffset;
+ hdrOffset = ptrOffset;
+ ptrOffset += _optionsButtonSpritesCount * 20;
+ for (int i = 0; i < _optionsButtonSpritesCount; ++i) {
+ DatSpritesGroup *spritesGroup = (DatSpritesGroup *)(ptr + hdrOffset + 4);
+ spritesGroup->size = le32toh(spritesGroup->size);
+ spritesGroup->count = le16toh(spritesGroup->count);
+ spritesGroup->firstFrameOffset = ptr + ptrOffset - _optionsButtonSpritesData;
+ hdrOffset += 20;
+ ptrOffset += spritesGroup->size;
+ }
+ } else {
+ _optionsButtonSpritesData = 0;
+ }
+
+ if (version == 10) {
+
+ _optionData = ptr + ptrOffset;
+ ptrOffset += _res->_datHdr.menusCount * 8;
+
+ hdrOffset = ptrOffset;
+ ptrOffset += kOptionsCount * sizeof(DatBitmap);
+ for (int i = 0; i < kOptionsCount; ++i) {
+ _optionsBitmapSize[i] = READ_LE_UINT32(ptr + hdrOffset);
+ hdrOffset += sizeof(DatBitmap);
+ if (_optionsBitmapSize[i] != 0) {
+ _optionsBitmapData[i] = ptr + ptrOffset;
+ ptrOffset += _optionsBitmapSize[i] + paletteSize;
+ } else {
+ _optionsBitmapData[i] = 0;
+ }
+ }
+
+ const int levelsCount = _res->_datHdr.levelsCount;
+ hdrOffset = ptrOffset;
+ ptrOffset += levelsCount * sizeof(DatBitmapsGroup);
+ _levelsBitmaps = (DatBitmapsGroup *)(ptr + hdrOffset);
+ _levelsBitmapsData = ptr + ptrOffset;
+ readBitmapsGroup(levelsCount, _levelsBitmaps, ptrOffset, paletteSize);
+ }
+}
+
+int Menu::getSoundNum(int num, int index) const {
+ const uint8_t *data = _soundData;
+ int count = READ_LE_UINT32(data + 4);
+ if (num < count) {
+ data += 8 + num * 8;
+ count = READ_LE_UINT32(data + 4);
+ if (index < count) {
+ data = _soundData + READ_LE_UINT32(data);
+ return (int16_t)READ_LE_UINT16(data + index * 2);
+ } else {
+ warning("Invalid sound index %d count %d", index, count);
+ }
+ }
+ return -1;
+}
+
+SssObject *Menu::playSound(int num) {
+ num = getSoundNum(num);
+ debug(kDebug_MENU, "playSound %d", num);
+ if (num != -1) {
+ return _g->playSound(num, 0, 0, 5);
+ }
+ return 0;
+}
+
+void Menu::drawBitmap(const uint8_t *data, uint32_t size, bool setPalette) {
+ if (_res->_isPsx) {
+ _video->decodeBackgroundPsx(data, size, Video::W, Video::H);
+ } else {
+ decodeLZW(data, _video->_frontLayer);
+ if (setPalette) {
+ g_system->setPalette(data + size, 256, 6);
+ }
+ }
+}
+
+void Menu::drawSprite(const DatSpritesGroup *spriteGroup, const uint8_t *ptr, uint32_t num, int x, int y) {
+ ptr += spriteGroup->firstFrameOffset;
+ for (uint32_t i = 0; i < spriteGroup->count; ++i) {
+ const uint16_t size = READ_LE_UINT16(ptr + 2);
+ if (num == i) {
+ if (_res->_isPsx) {
+ _video->decodeBackgroundOverlayPsx(ptr);
+ } else {
+ if (x < 0) {
+ x = ptr[0];
+ }
+ if (y < 0) {
+ y = ptr[1];
+ }
+ _video->decodeSPR(ptr + 8, _video->_frontLayer, x, y, 0, READ_LE_UINT16(ptr + 4), READ_LE_UINT16(ptr + 6));
+ }
+ break;
+ }
+ ptr += size + 2;
+ }
+}
+
+void Menu::drawSpriteAnim(DatSpritesGroup *spriteGroup, const uint8_t *ptr, uint32_t num) {
+ if (spriteGroup[num].num == 0) {
+ spriteGroup[num].currentFrameOffset = spriteGroup[num].firstFrameOffset;
+ }
+ ptr += spriteGroup[num].currentFrameOffset;
+ if (_res->_isPsx) {
+ _video->decodeBackgroundOverlayPsx(ptr);
+ } else {
+ _video->decodeSPR(ptr + 8, _video->_frontLayer, ptr[0], ptr[1], 0, READ_LE_UINT16(ptr + 4), READ_LE_UINT16(ptr + 6));
+ }
+ ++spriteGroup[num].num;
+ if (spriteGroup[num].num < spriteGroup[num].count) {
+ const uint16_t size = READ_LE_UINT16(ptr + 2);
+ spriteGroup[num].currentFrameOffset += size + 2;
+ } else {
+ spriteGroup[num].num = 0; // restart from frame #0
+ spriteGroup[num].currentFrameOffset = spriteGroup[num].firstFrameOffset;
+ }
+}
+
+void Menu::refreshScreen() {
+ _video->updateGameDisplay(_video->_frontLayer);
+ g_system->updateScreen(false);
+}
+
+void Menu::pafCallback(int frameNum, const uint8_t *frameData) {
+ if (_currentOptionButtonSound != 0) {
+ const int num = getSoundNum(_currentOptionButtonSound, frameNum);
+ if (num != -1) {
+ _g->playSound(num, 0, 0, 5);
+ }
+ }
+ memcpy(_video->_frontLayer, frameData, Video::W * Video::H);
+ if (_currentOptionButtonSprite && frameNum == _currentOptionButtonSprite->num) {
+ drawSpriteAnim(_currentOptionButtonSprite, _optionsButtonSpritesData, 0);
+ }
+ g_system->copyRect(0, 0, Video::W, Video::H, _video->_frontLayer, Video::W);
+}
+
+static void menuPafCallback(void *userdata, int frame, const uint8_t *buffer) {
+ ((Menu *)userdata)->pafCallback(frame, buffer);
+}
+
+bool Menu::mainLoop() {
+ bool ret = false;
+ loadData();
+ while (!g_system->inp.quit) {
+ const int option = handleTitleScreen();
+ if (option == kTitleScreen_AssignPlayer) {
+ handleAssignPlayer();
+ debug(kDebug_MENU, "currentPlayer %d", _config->currentPlayer);
+ continue;
+ } else if (option == kTitleScreen_Play) {
+ ret = true;
+ } else if (option == kTitleScreen_Options) {
+ PafCallback pafCb;
+ pafCb.frameProc = menuPafCallback;
+ pafCb.endProc = 0;
+ pafCb.userdata = this;
+ _paf->setCallback(&pafCb);
+ playSound(kSound_0xA0);
+ ret = handleOptions();
+ _g->resetSound();
+ _paf->setCallback(0);
+ } else if (option == kTitleScreen_Quit) {
+ }
+ break;
+ }
+ _res->unloadDatMenuBuffers();
+ return ret;
+}
+
+void Menu::drawTitleScreen(int option) {
+ drawBitmap(_titleBitmapData, _titleBitmapSize, true);
+ drawSprite(_titleSprites, (const uint8_t *)&_titleSprites[1], option);
+ refreshScreen();
+}
+
+int Menu::handleTitleScreen() {
+ const int firstOption = kTitleScreen_AssignPlayer;
+ const int lastOption = _res->_isPsx ? kTitleScreenPSX_Save : kTitleScreen_Quit;
+ int currentOption = kTitleScreen_Play;
+ while (!g_system->inp.quit) {
+ g_system->processEvents();
+ if (g_system->inp.keyReleased(SYS_INP_UP)) {
+ if (currentOption > firstOption) {
+ playSound(kSound_0x70);
+ --currentOption;
+ }
+ }
+ if (g_system->inp.keyReleased(SYS_INP_DOWN)) {
+ if (currentOption < lastOption) {
+ playSound(kSound_0x70);
+ ++currentOption;
+ }
+ }
+ if (g_system->inp.keyReleased(SYS_INP_SHOOT) || g_system->inp.keyReleased(SYS_INP_JUMP)) {
+ playSound(kSound_0x78);
+ break;
+ }
+ drawTitleScreen(currentOption);
+ g_system->sleep(kDelayMs);
+ }
+ return currentOption;
+}
+
+void Menu::drawDigit(int x, int y, int num) {
+ assert(x >= 0 && x + 16 < Video::W && y >= 0 && y + 10 < Video::H);
+ assert(num < 16);
+ uint8_t *dst = _video->_frontLayer + y * Video::W + x;
+ const uint8_t *src = _digitsData + num * 16;
+ for (int j = 0; j < 10; ++j) {
+ for (int i = 0; i < 16; ++i) {
+ if (src[i] != 0) {
+ dst[i] = src[i];
+ }
+ }
+ dst += Video::W;
+ src += Video::W;
+ }
+}
+
+void Menu::drawBitmap(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int x, int y, int w, int h, uint8_t baseColor) {
+ if (_res->_isPsx) {
+ const int size = bitmapsGroup->palette - bitmapsGroup->offset;
+ _video->decodeBackgroundPsx(bitmapData, size, w, h, x, y);
+ return;
+ }
+ const int srcPitch = w;
+ if (x < 0) {
+ bitmapData -= x;
+ w += x;
+ x = 0;
+ }
+ if (x + w > Video::W) {
+ w = Video::W - x;
+ }
+ if (y < 0) {
+ bitmapData -= y * srcPitch;
+ h += y;
+ y = 0;
+ }
+ if (y + h > Video::H) {
+ h = Video::H - y;
+ }
+ uint8_t *dst = _video->_frontLayer + y * Video::W + x;
+ for (int j = 0; j < h; ++j) {
+ for (int i = 0; i < w; ++i) {
+ dst[i] = baseColor - bitmapsGroup->colors + bitmapData[i];
+ }
+ dst += Video::W;
+ bitmapData += srcPitch;
+ }
+}
+
+void Menu::setCurrentPlayer(int num) {
+ debug(kDebug_MENU, "setCurrentPlayer %d", num);
+ setLevelCheckpoint(num);
+ _levelNum = _config->players[num].levelNum;
+ if (_levelNum > kLvl_dark) {
+ _levelNum = kLvl_dark;
+ }
+ if (_levelNum == kLvl_dark) {
+ _levelNum = 7;
+ _checkpointNum = 11;
+ } else {
+ _checkpointNum = _config->players[num].checkpointNum;
+ if (_checkpointNum >= _res->_datHdr.levelCheckpointsCount[_levelNum]) {
+ _checkpointNum = _res->_datHdr.levelCheckpointsCount[_levelNum] - 1;
+ }
+ }
+ const DatBitmapsGroup *bitmap = &_checkpointsBitmaps[_levelNum][_checkpointNum];
+ memcpy(_paletteBuffer + 205 * 3, _checkpointsBitmapsData[_levelNum] + bitmap->palette, 50 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+}
+
+void Menu::setLevelCheckpoint(int num) {
+ for (int i = 0; i < kCheckpointLevelsCount; ++i) {
+ uint8_t checkpoint = _config->players[num].progress[i] + 1;
+ if (checkpoint >= _res->_datHdr.levelCheckpointsCount[i]) {
+ checkpoint = _res->_datHdr.levelCheckpointsCount[i];
+ }
+ _lastLevelCheckpointNum[i] = checkpoint;
+ }
+}
+
+void Menu::drawPlayerProgress(int state, int cursor) {
+ drawBitmap(_playerBitmapData, _playerBitmapSize);
+ int player = 0;
+ for (int y = 96; y < 164; y += 17) {
+ if (isEmptySetupCfg(_config, player)) {
+ drawSprite(_playerSprites, (const uint8_t *)&_playerSprites[1], 3, 82, y - 3); // empty
+ } else {
+ int levelNum = _config->players[player].levelNum;
+ int checkpointNum;
+ if (levelNum == kLvl_dark) {
+ levelNum = 7;
+ checkpointNum = 11;
+ } else {
+ checkpointNum = _config->players[player].checkpointNum;
+ }
+ drawDigit(145, y, levelNum + 1);
+ drawDigit(234, y, checkpointNum + 1);
+ }
+ ++player;
+ }
+ player = (cursor == 0 || cursor == 5) ? _config->currentPlayer : (cursor - 1);
+ uint8_t *p = _video->_frontLayer;
+ const int offset = (player * 17) + 92;
+ for (int i = 0; i < 16; ++i) { // player
+ memcpy(p + i * Video::W + 6935, p + i * Video::W + (offset + 1) * 256 + 8, 72);
+ }
+ for (int i = 0; i < 16; ++i) { // level
+ memcpy(p + i * Video::W + 11287, p + i * Video::W + (offset + 1) * 256 + 83, 76);
+ }
+ for (int i = 0; i < 16; ++i) { // checkpoint
+ memcpy(p + i * Video::W + 15639, p + i * Video::W + (offset + 1) * 256 + 172, 76);
+ }
+ if (!isEmptySetupCfg(_config, player)) {
+ DatBitmapsGroup *bitmap = &_checkpointsBitmaps[_levelNum][_checkpointNum];
+ const uint8_t *src = _checkpointsBitmapsData[_levelNum] + bitmap->offset;
+ drawBitmap(bitmap, src, 132, 0, bitmap->w, bitmap->h, 205);
+ }
+ if (cursor > 0) {
+ if (cursor <= 4) { // highlight one player
+ drawSprite(_playerSprites, (const uint8_t *)&_playerSprites[1], 8, 2, cursor * 17 + 74);
+ drawSprite(_playerSprites, (const uint8_t *)&_playerSprites[1], 6);
+ } else if (cursor == 5) { // cancel
+ drawSprite(_playerSprites, (const uint8_t *)&_playerSprites[1], 7);
+ }
+ }
+ drawSprite(_playerSprites, (const uint8_t *)&_playerSprites[1], state); // Yes/No
+ if (state > 2) {
+ drawSprite(_playerSprites, (const uint8_t *)&_playerSprites[1], 2); // MessageBox
+ }
+ refreshScreen();
+}
+
+void Menu::handleAssignPlayer() {
+ memcpy(_paletteBuffer, _playerBitmapData + _playerBitmapSize, 256 * 3);
+ int state = 1;
+ int cursor = 0;
+ setCurrentPlayer(_config->currentPlayer);
+ drawPlayerProgress(state, cursor);
+ while (!g_system->inp.quit) {
+ g_system->processEvents();
+ if (g_system->inp.keyReleased(SYS_INP_SHOOT) || g_system->inp.keyReleased(SYS_INP_JUMP)) {
+ if (state != 0 && cursor == 5) {
+ playSound(kSound_0x80);
+ } else {
+ playSound(kSound_0x78);
+ }
+ if (state == 0) {
+ // return to title screen
+ return;
+ }
+ if (cursor == 0) {
+ cursor = _config->currentPlayer + 1;
+ } else {
+ if (cursor == 5) {
+ cursor = 0;
+ } else if (state == 1) { // select
+ --cursor;
+ _config->currentPlayer = cursor;
+ setVolume();
+ cursor = 0;
+ } else if (state == 2) { // clear
+ state = 5; // 'No'
+ } else {
+ if (state == 4) { // 'Yes', clear confirmation
+ --cursor;
+ _res->setDefaultsSetupCfg(_config, cursor);
+ setVolume();
+ }
+ setCurrentPlayer(_config->currentPlayer);
+ state = 2;
+ cursor = 0;
+ }
+ }
+ }
+ if (cursor != 0 && state < 3) {
+ if (g_system->inp.keyReleased(SYS_INP_UP)) {
+ if (cursor > 1) {
+ playSound(kSound_0x70);
+ --cursor;
+ setCurrentPlayer(cursor - 1);
+ }
+ }
+ if (g_system->inp.keyReleased(SYS_INP_DOWN)) {
+ if (cursor < 5) {
+ playSound(kSound_0x70);
+ ++cursor;
+ setCurrentPlayer((cursor == 5) ? _config->currentPlayer : (cursor - 1));
+ }
+ }
+ } else {
+ if (g_system->inp.keyReleased(SYS_INP_LEFT)) {
+ if (state == 1 || state == 2 || state == 5) {
+ playSound(kSound_0x70);
+ --state;
+ }
+ }
+ if (g_system->inp.keyReleased(SYS_INP_RIGHT)) {
+ if (state == 0 || state == 1 || state == 4) {
+ playSound(kSound_0x70);
+ ++state;
+ }
+ }
+ }
+ drawPlayerProgress(state, cursor);
+ g_system->sleep(kDelayMs);
+ }
+}
+
+void Menu::updateBitmapsCircularList(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int num, int count) {
+ _bitmapCircularListIndex[1] = num;
+ if (count > 1) {
+ _bitmapCircularListIndex[2] = (num + 1) % count;
+ } else {
+ _bitmapCircularListIndex[2] = -1;
+ }
+ if (count > 2) {
+ _bitmapCircularListIndex[0] = (num + count - 1) % count;
+ } else {
+ _bitmapCircularListIndex[0] = -1;
+ }
+ if (bitmapsGroup == _cutscenesBitmaps) {
+ for (int i = 0; i < 3; ++i) {
+ const int num = _bitmapCircularListIndex[i];
+ if (num != -1 && num < _cutsceneIndexesCount) {
+ _bitmapCircularListIndex[i] = _cutsceneIndexes[num];
+ }
+ }
+ }
+ for (int i = 0; i < 3; ++i) {
+ if (_bitmapCircularListIndex[i] != -1) {
+ const DatBitmapsGroup *bitmap = &bitmapsGroup[_bitmapCircularListIndex[i]];
+ memcpy(_paletteBuffer + (105 + 50 * i) * 3, bitmapData + bitmap->palette, 50 * 3);
+ }
+ }
+ g_system->setPalette(_paletteBuffer, 256, 6);
+}
+
+void Menu::drawBitmapsCircularList(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int num, int count, bool updatePalette) {
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 256 * 3);
+ if (updatePalette) {
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ }
+ updateBitmapsCircularList(bitmapsGroup, bitmapData, num, count);
+ static const int xPos[] = { -60, 68, 196 };
+ for (int i = 0; i < 3; ++i) {
+ if (_bitmapCircularListIndex[i] != -1) {
+ const DatBitmapsGroup *bitmap = &bitmapsGroup[_bitmapCircularListIndex[i]];
+ drawBitmap(bitmap, bitmapData + bitmap->offset, xPos[i], 14, bitmap->w, bitmap->h, 105 + 50 * i);
+ }
+ }
+}
+
+void Menu::drawCheckpointScreen() {
+ drawBitmap(_optionsBitmapData[_optionNum], _optionsBitmapSize[_optionNum]);
+ drawBitmapsCircularList(_checkpointsBitmaps[_levelNum], _checkpointsBitmapsData[_levelNum], _checkpointNum, _lastLevelCheckpointNum[_levelNum], false);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 5);
+ drawSprite(&_iconsSprites[0], _iconsSpritesData, (_checkpointNum + 1) / 10, 119, 108);
+ drawSprite(&_iconsSprites[0], _iconsSpritesData, (_checkpointNum + 1) % 10, 127, 108);
+ const int num = _loadCheckpointButtonState;
+ if (num > 1 && num < 7) {
+ drawSprite(&_iconsSprites[9], _iconsSpritesData, num - 2);
+ } else {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (num != 0) ? 8 : 7);
+ }
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 6);
+ refreshScreen();
+}
+
+void Menu::drawLevelScreen() {
+ drawBitmap(_optionsBitmapData[_optionNum], _optionsBitmapSize[_optionNum]);
+ drawSprite(&_iconsSprites[1], _iconsSpritesData, _levelNum);
+ DatBitmapsGroup *bitmap = &_levelsBitmaps[_levelNum];
+ drawBitmap(bitmap, _levelsBitmapsData + bitmap->offset, 23, 10, bitmap->w, bitmap->h, 192);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 4);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (_loadLevelButtonState != 0) ? 3 : 2);
+ refreshScreen();
+}
+
+void Menu::drawCutsceneScreen() {
+ drawBitmap(_optionsBitmapData[_optionNum], _optionsBitmapSize[_optionNum]);
+ drawBitmapsCircularList(_cutscenesBitmaps, _cutscenesBitmapsData, _cutsceneNum, _cutsceneIndexesCount, false);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 10);
+ drawSprite(&_iconsSprites[0], _iconsSpritesData, (_cutsceneNum + 1) / 10, 119, 108);
+ drawSprite(&_iconsSprites[0], _iconsSpritesData, (_cutsceneNum + 1) % 10, 127, 108);
+ const int num = _loadCutsceneButtonState;
+ if (num > 1 && num < 7) {
+ drawSprite(&_iconsSprites[14], _iconsSpritesData, num - 2);
+ } else {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (num != 0) ? 13 : 12);
+ }
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 11);
+ refreshScreen();
+}
+
+void Menu::drawSettingsScreen() {
+ drawBitmap(_optionsBitmapData[_optionNum], _optionsBitmapSize[_optionNum]);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x2A);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (_settingNum == kSettingNum_Controls) ? 0x27 : 0x24);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (_settingNum == kSettingNum_Difficulty) ? 0x26 : 0x23);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (_settingNum == kSettingNum_Sound) ? 0x28 : 0x25);
+ drawSprite(&_iconsSprites[0x29], _iconsSpritesData, (_settingNum == kSettingNum_Confirm) ? 1 : 0);
+ refreshScreen();
+}
+
+void Menu::handleSettingsScreen(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == kCursor_Select) {
+ if (_settingNum == kSettingNum_Controls) {
+ playSound(kSound_0x78);
+ _condMask = 0x10;
+ } else if (_settingNum == kSettingNum_Difficulty) {
+ playSound(kSound_0x78);
+ _condMask = 0x20;
+ } else if (_settingNum == kSettingNum_Sound) {
+ playSound(kSound_0x78);
+ _condMask = 0x40;
+ } else if (_settingNum == kSettingNum_Confirm) {
+ playSound(kSound_0x78);
+ _condMask = 0x80;
+ }
+ return;
+ } else if (num == kCursor_Left) {
+ if (_settingNum != kSettingNum_Confirm && _settingNum > 1) { // 'controls' not implemented
+ playSound(kSound_0x70);
+ --_settingNum;
+ _iconsSprites[0x27].num = 0;
+ _iconsSprites[0x26].num = 0;
+ _iconsSprites[0x28].num = 0;
+ }
+ } else if (num == kCursor_Right) {
+ if (_settingNum != kSettingNum_Confirm && _settingNum < 2) {
+ playSound(kSound_0x70);
+ ++_settingNum;
+ _iconsSprites[0x27].num = 0;
+ _iconsSprites[0x26].num = 0;
+ _iconsSprites[0x28].num = 0;
+ }
+ } else if (num == kCursor_Up) {
+ if (_settingNum == kSettingNum_Confirm) {
+ playSound(kSound_0x70);
+ _settingNum = kSettingNum_Difficulty;
+ _iconsSprites[0x26].num = 0;
+ }
+ } else if (num == kCursor_Down) {
+ if (_settingNum != kSettingNum_Confirm) {
+ playSound(kSound_0x70);
+ }
+ _settingNum = kSettingNum_Confirm;
+ }
+ drawSettingsScreen();
+ _condMask = 8;
+ g_system->sleep(kDelayMs);
+}
+
+void Menu::drawControlsScreen() {
+ drawBitmap(_optionsBitmapData[_optionNum], _optionsBitmapSize[_optionNum]);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (_controlsNum == 1) ? 0x2E : 0x2D);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (_controlsNum == 0) ? 0x2C : 0x2B);
+ drawSprite(&_iconsSprites[0x2F], _iconsSpritesData, (_controlsNum == 2) ? 1 : 0);
+ refreshScreen();
+}
+
+void Menu::handleControlsScreen(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == kCursor_Select) {
+ playSound(kSound_0x78);
+ if (_controlsNum == 0) {
+ _condMask = 0x10;
+ } else if (_controlsNum == 1) {
+ _condMask = 0x8;
+ } else if (_controlsNum == 2) {
+ _condMask = 0x80;
+ }
+ return;
+ } else if (num == kCursor_Left) {
+ if (_controlsNum == 1) {
+ playSound(kSound_0x70);
+ _controlsNum = 0;
+ _iconsSprites[0x2C].num = 0;
+ }
+ } else if (num == kCursor_Right) {
+ if (_controlsNum == 0) {
+ playSound(kSound_0x70);
+ _controlsNum = 1;
+ _iconsSprites[0x2E].num = 0;
+ }
+ } else if (num == kCursor_Up) {
+ if (_controlsNum == 2) {
+ playSound(kSound_0x70);
+ _controlsNum = 1;
+ _iconsSprites[0x2E].num = 0;
+ }
+ } else if (num == kCursor_Down) {
+ if (_controlsNum != 2) {
+ playSound(kSound_0x70);
+ _controlsNum = 2;
+ }
+ }
+ drawControlsScreen();
+ _condMask = 0x20;
+ g_system->sleep(kDelayMs);
+}
+
+void Menu::drawJoystickKeyCode(int num) {
+ const uint32_t code = READ_LE_UINT32(_config->players[_config->currentPlayer].controls + 4 * num);
+ if (code != 0) {
+ static const uint8_t xPos[] = { 20, 79, 138, 197 };
+ int bit = 0;
+ for (; bit < 32; ++bit) {
+ if ((code & (1 << bit)) != 0) {
+ break;
+ }
+ }
+ const int code1 = (bit < 8) ? (41 + bit) : 40;
+ _video->drawStringCharacter(xPos[num], 111, code1, _res->_fontDefaultColor, _video->_frontLayer);
+ if ((code & ~(1 << bit)) != 0) {
+ for (; bit < 32; ++bit) {
+ if ((code & (1 << bit)) != 0) {
+ break;
+ }
+ }
+ const int code2 = (bit < 8) ? (41 + bit) : 40;
+ _video->drawStringCharacter(xPos[num] + 23, 111, code2, _res->_fontDefaultColor, _video->_frontLayer);
+ }
+ }
+}
+
+void Menu::drawJoystickControlsScreen() {
+ drawBitmap(_optionsBitmapData[_optionNum], _optionsBitmapSize[_optionNum]);
+ drawSprite(&_iconsSprites[0x11], _iconsSpritesData, (_joystickControlsNum == 1) ? 2 : 3);
+ drawSprite(&_iconsSprites[0x11], _iconsSpritesData, 0);
+ drawSprite(&_iconsSprites[0x11], _iconsSpritesData, (_joystickControlsNum == 0) ? 0 : 1);
+ drawSprite(&_iconsSprites[0x11], _iconsSpritesData, (_joystickControlsNum == 2) ? 4 : 5);
+ drawSprite(&_iconsSprites[0x11], _iconsSpritesData, (_joystickControlsNum == 3) ? 6 : 7);
+ if (_joystickControlsNum <= 3) {
+ drawSprite(&_iconsSprites[0x11], _iconsSpritesData, _joystickControlsNum * 2);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x15);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x14);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x13);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x16);
+ } else if (_joystickControlsNum == 4) {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x19);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x14);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x13);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x16);
+ } else if (_joystickControlsNum == 5) {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x15);
+ drawSprite(&_iconsSprites[0x18], _iconsSpritesData, 0);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x13);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x16);
+ } else if (_joystickControlsNum == 6) {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x15);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x14);
+ drawSprite(&_iconsSprites[0x17], _iconsSpritesData, 0);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x16);
+ } else if (_joystickControlsNum == 7) {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x15);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x14);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x13);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1A);
+ } else if (_joystickControlsNum == 8) {
+ drawSprite(&_iconsSprites[0x11], _iconsSpritesData, 4);
+ static const int joystickKeyCode = 0;
+ int mask = 0;
+ if (READ_LE_UINT32(_config->players[_config->currentPlayer].controls + 0x0) & joystickKeyCode) {
+ mask = 1;
+ }
+ if (READ_LE_UINT32(_config->players[_config->currentPlayer].controls + 0x4) & joystickKeyCode) {
+ mask |= 2;
+ }
+ if (READ_LE_UINT32(_config->players[_config->currentPlayer].controls + 0x8) & joystickKeyCode) {
+ mask |= 4;
+ }
+ if (READ_LE_UINT32(_config->players[_config->currentPlayer].controls + 0xC) & joystickKeyCode) {
+ mask |= 5;
+ }
+ const int flag = (((mask & 5) - 5) != 0) ? 0 : 1;
+ if (((mask & 1) != 0 && flag == 0) || _iconsSprites[0x19].num != 0) {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x19);
+ } else {
+ drawSprite(&_iconsSprites[0x19], _iconsSpritesData, 0);
+ }
+ if ((mask & 2) == 0 || _iconsSprites[0x18].num == 0) {
+ drawSprite(&_iconsSprites[0x18], _iconsSpritesData, 0);
+ } else {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x18);
+ }
+ if (((mask & 4) != 0 && flag == 0) || _iconsSprites[0x17].num != 0) {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x17);
+ } else {
+ drawSprite(&_iconsSprites[0x17], _iconsSpritesData, 0);
+ }
+ if (flag == 0 || _iconsSprites[0x1A].num == 0) {
+ drawSprite(&_iconsSprites[0x1A], _iconsSpritesData, 0);
+ } else {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1A);
+ }
+ }
+ drawJoystickKeyCode(0);
+ drawJoystickKeyCode(1);
+ drawJoystickKeyCode(2);
+ drawJoystickKeyCode(3);
+ refreshScreen();
+}
+
+void Menu::handleJoystickControlsScreen(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == 1) {
+ if (_joystickControlsNum == 0) {
+ playSound(kSound_0x70);
+ _joystickControlsNum = 1;
+ } else if (_joystickControlsNum == 2) {
+ playSound(kSound_0x70);
+ _joystickControlsNum = 0;
+ } else if (_joystickControlsNum == 5) {
+ playSound(kSound_0x70);
+ _joystickControlsNum = 4;
+ } else if (_joystickControlsNum == 6) {
+ playSound(kSound_0x70);
+ _joystickControlsNum = 5;
+ } else if (_joystickControlsNum == 7) {
+ playSound(kSound_0x70);
+ _joystickControlsNum = 6;
+ } else if (_joystickControlsNum == 8) {
+ _iconsSprites[0x19].num = 0;
+ _iconsSprites[0x18].num = 0;
+ _iconsSprites[0x17].num = 0;
+ _iconsSprites[0x1A].num = 0;
+ _joystickControlsNum = 2;
+ }
+ } else if (num == 2) {
+ if (_joystickControlsNum == 0) {
+ playSound(kSound_0x70);
+ _joystickControlsNum = 2;
+ } else if (_joystickControlsNum == 1) {
+ playSound(kSound_0x70);
+ _joystickControlsNum = 0;
+ } else if (_joystickControlsNum == 4) {
+ playSound(kSound_0x70);
+ _joystickControlsNum = 5;
+ } else if (_joystickControlsNum == 5) {
+ playSound(kSound_0x70);
+ _joystickControlsNum = 6;
+ } else if (_joystickControlsNum == 6) {
+ playSound(kSound_0x70);
+ _joystickControlsNum = 7;
+ } else if (_joystickControlsNum == 8) {
+ _iconsSprites[0x19].num = 0;
+ _iconsSprites[0x18].num = 0;
+ _iconsSprites[0x17].num = 0;
+ _iconsSprites[0x1A].num = 0;
+ _joystickControlsNum = 2;
+ }
+ } else if (num == 3) {
+ if (_joystickControlsNum <= 2) {
+ playSound(kSound_0x70);
+ _joystickControlsNum = 5;
+ } else if (_joystickControlsNum == 3) {
+ playSound(kSound_0x70);
+ _joystickControlsNum = 0;
+ } else if (_joystickControlsNum == 8) {
+ _iconsSprites[0x19].num = 0;
+ _iconsSprites[0x18].num = 0;
+ _iconsSprites[0x17].num = 0;
+ _iconsSprites[0x1A].num = 0;
+ _joystickControlsNum = 2;
+ }
+ } else if (num == 4) {
+ if (_joystickControlsNum != 3) {
+ playSound(kSound_0x70);
+ }
+ if (_joystickControlsNum <= 2) {
+ _joystickControlsNum = 3;
+ } else if (_joystickControlsNum > 3 && _joystickControlsNum <= 7) {
+ _joystickControlsNum = 0;
+ }
+ }
+ drawJoystickControlsScreen();
+ if (_joystickControlsNum == 8) {
+ _condMask = 8;
+ }
+ g_system->sleep(kDelayMs);
+}
+
+void Menu::drawKeyboardKeyCode(int num) {
+ for (int i = 0; i < 2; ++i) {
+ const uint8_t code = _config->players[_config->currentPlayer].controls[16 + 2 * num + i];
+ if (code != 0) {
+ static const uint8_t xPos[] = { 20, 79, 138, 197 };
+ const int chr = _video->findStringCharacterFontIndex(code);
+ if (chr != 255) {
+ _video->drawStringCharacter(xPos[num] + i * 23, 111, chr, _res->_fontDefaultColor, _video->_frontLayer);
+ }
+ }
+ }
+}
+
+void Menu::drawKeyboardControlsScreen() {
+ drawBitmap(_optionsBitmapData[_optionNum], _optionsBitmapSize[_optionNum]);
+ for (int i = 1; i <= 7; ++i) {
+ drawSprite(&_iconsSprites[0x10], _iconsSpritesData, i);
+ }
+ if (_keyboardControlsNum < 3) {
+ drawSprite(&_iconsSprites[0x10], _iconsSpritesData, _keyboardControlsNum * 2);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1D);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1C);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1B);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1E);
+ } else if (_keyboardControlsNum == 4) {
+ drawSprite(&_iconsSprites[0x21], _iconsSpritesData, 0);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1C);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1B);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1E);
+ } else if (_keyboardControlsNum == 5) {
+ drawSprite(&_iconsSprites[0x20], _iconsSpritesData, 0);
+ drawSprite(&_iconsSprites[0x1D], _iconsSpritesData, 0);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1B);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1E);
+ } else if (_keyboardControlsNum == 6) {
+ drawSprite(&_iconsSprites[0x1F], _iconsSpritesData, 0);
+ drawSprite(&_iconsSprites[0x1D], _iconsSpritesData, 0);
+ drawSprite(&_iconsSprites[0x1C], _iconsSpritesData, 0);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1E);
+ } else if (_keyboardControlsNum == 7) {
+ drawSprite(&_iconsSprites[0x22], _iconsSpritesData, 0);
+ drawSprite(&_iconsSprites[0x1D], _iconsSpritesData, 0);
+ drawSprite(&_iconsSprites[0x1C], _iconsSpritesData, 0);
+ drawSprite(&_iconsSprites[0x1B], _iconsSpritesData, 0);
+ } else if (_keyboardControlsNum == 8) {
+ drawSprite(&_iconsSprites[0x10], _iconsSpritesData, 4);
+ static const int keyboardMask = 0;
+ int mask = keyboardMask;
+ const int flag = (((keyboardMask & 5) - 5) != 0) ? 0 : 1;
+ if (((mask & 1) != 0 && flag == 0) || _iconsSprites[0x21].num != 0) {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x21);
+ } else {
+ drawSprite(&_iconsSprites[0x21], _iconsSpritesData, 0);
+ }
+ if ((mask & 2) == 0 || _iconsSprites[0x20].num == 0) {
+ drawSprite(&_iconsSprites[0x20], _iconsSpritesData, 0);
+ } else {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x20);
+ }
+ if (((mask & 4) != 0 && flag == 0) || _iconsSprites[0x1F].num != 0) {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x1F);
+ } else {
+ drawSprite(&_iconsSprites[0x1F], _iconsSpritesData, 0);
+ }
+ if (flag == 0 || _iconsSprites[0x22].num == 0) {
+ drawSprite(&_iconsSprites[0x22], _iconsSpritesData, 0);
+ } else {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x22);
+ }
+ }
+ drawKeyboardKeyCode(0);
+ drawKeyboardKeyCode(1);
+ drawKeyboardKeyCode(2);
+ drawKeyboardKeyCode(3);
+ refreshScreen();
+}
+
+void Menu::handleKeyboardControlsScreen(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == 1) {
+ if (_keyboardControlsNum == 0) {
+ playSound(kSound_0x70);
+ _keyboardControlsNum = 1;
+ } else if (_keyboardControlsNum == 2) {
+ playSound(kSound_0x70);
+ _keyboardControlsNum = 0;
+ } else if (_keyboardControlsNum == 5) {
+ playSound(kSound_0x70);
+ _keyboardControlsNum = 4;
+ } else if (_keyboardControlsNum == 6) {
+ playSound(kSound_0x70);
+ _keyboardControlsNum = 5;
+ } else if (_keyboardControlsNum == 7) {
+ playSound(kSound_0x70);
+ _keyboardControlsNum = 6;
+ } else if (_keyboardControlsNum == 8) {
+ _iconsSprites[0x21].num = 0;
+ _iconsSprites[0x20].num = 0;
+ _iconsSprites[0x1F].num = 0;
+ _iconsSprites[0x22].num = 0;
+ _keyboardControlsNum = 2;
+ }
+ } else if (num == 2) {
+ if (_keyboardControlsNum == 0) {
+ playSound(kSound_0x70);
+ _keyboardControlsNum = 2;
+ } else if (_keyboardControlsNum == 1) {
+ playSound(kSound_0x70);
+ _keyboardControlsNum = 0;
+ } else if (_keyboardControlsNum == 4) {
+ playSound(kSound_0x70);
+ _keyboardControlsNum = 5;
+ } else if (_keyboardControlsNum == 5) {
+ playSound(kSound_0x70);
+ _keyboardControlsNum = 6;
+ } else if (_keyboardControlsNum == 6) {
+ playSound(kSound_0x70);
+ _keyboardControlsNum = 7;
+ } else if (_keyboardControlsNum == 8) {
+ _iconsSprites[0x21].num = 0;
+ _iconsSprites[0x20].num = 0;
+ _iconsSprites[0x1F].num = 0;
+ _iconsSprites[0x22].num = 0;
+ _keyboardControlsNum = 2;
+ }
+ } else if (num == 3) {
+ if (_keyboardControlsNum <= 2) {
+ playSound(kSound_0x70);
+ _keyboardControlsNum = 5;
+ } else if (_keyboardControlsNum == 3) {
+ playSound(kSound_0x70);
+ _keyboardControlsNum = 0;
+ } else if (_keyboardControlsNum == 8) {
+ _iconsSprites[0x21].num = 0;
+ _iconsSprites[0x20].num = 0;
+ _iconsSprites[0x1F].num = 0;
+ _iconsSprites[0x22].num = 0;
+ _keyboardControlsNum = 2;
+ }
+ } else if (num == 4) {
+ if (_keyboardControlsNum == 3) {
+ playSound(kSound_0x70);
+ }
+ if (_keyboardControlsNum <= 2) {
+ _keyboardControlsNum = 3;
+ } else if (_keyboardControlsNum > 3 && _keyboardControlsNum <= 7) {
+ _keyboardControlsNum = 0;
+ } else if (_keyboardControlsNum == 8) {
+ _iconsSprites[0x21].num = 0;
+ _iconsSprites[0x20].num = 0;
+ _iconsSprites[0x1F].num = 0;
+ _iconsSprites[0x22].num = 0;
+ _keyboardControlsNum = 2;
+ }
+ }
+ drawKeyboardControlsScreen();
+ if (_keyboardControlsNum == 8) {
+ _condMask = 8;
+ }
+ g_system->sleep(kDelayMs);
+}
+
+void Menu::drawDifficultyScreen() {
+ drawBitmap(_optionsBitmapData[_optionNum], _optionsBitmapSize[_optionNum]);
+ for (int i = 0; i < 3; ++i) {
+ if (i != _difficultyNum) {
+ drawSprite(&_iconsSprites[0xF], _iconsSpritesData, i * 2);
+ }
+ }
+ drawSprite(&_iconsSprites[0xF], _iconsSpritesData, _difficultyNum * 2 + 1);
+ refreshScreen();
+}
+
+void Menu::handleDifficultyScreen(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == kCursor_Select) {
+ playSound(kSound_0x78);
+ _config->players[_config->currentPlayer].difficulty = _g->_difficulty = _difficultyNum;
+ _condMask = 0x80;
+ } else if (num == kCursor_Left) {
+ if (_difficultyNum > 0) {
+ playSound(kSound_0x70);
+ --_difficultyNum;
+ }
+ } else if (num == kCursor_Right) {
+ if (_difficultyNum < 2) {
+ playSound(kSound_0x70);
+ ++_difficultyNum;
+ }
+ }
+ drawDifficultyScreen();
+ g_system->sleep(kDelayMs);
+}
+
+void Menu::drawSoundScreen() {
+ drawBitmap(_optionsBitmapData[_optionNum], _optionsBitmapSize[_optionNum]);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, (_soundNum == kSoundNum_Stereo) ? 1 : 0);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, (_soundNum == kSoundNum_Volume) ? 3 : 2);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, (_soundNum == kSoundNum_Confirm) ? 5 : 4);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, (_soundNum == kSoundNum_Test) ? 7 : 6);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, (_soundNum == kSoundNum_Cancel) ? 9 : 8);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, (_soundNum == kSoundNum_Reset) ? 11 : 10);
+ // volume bar
+ const int w = (_g->_snd_masterVolume * 96) / 128;
+ for (int y = 0; y < 15; ++y) {
+ memset(_video->_frontLayer + 18807 + 256 * y, 0xE0, w);
+ }
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 21);
+ if (_soundNum == kSoundNum_Test) {
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, _soundTestSpriteNum);
+ }
+ if (_g->_snd_masterVolume != 0) {
+ if (_config->players[_config->currentPlayer].stereo) {
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 13);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 16);
+ } else {
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 14);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 15);
+ }
+ }
+ if ((_volumeState != 0) && (_soundCounter & 1) != 0) {
+ if (_volumeState == 1) { // decrease volume
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 18);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 19);
+ } else { // increase volume
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 17);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 20);
+ }
+ } else {
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 17);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 19);
+ }
+ refreshScreen();
+}
+
+void Menu::handleSoundScreen(int num) {
+ _volumeState = 0;
+ ++_soundCounter;
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == kCursor_Select) {
+ if (_soundNum == kSoundNum_Confirm) {
+ playSound(kSound_0x78);
+ _config->players[_config->currentPlayer].volume = _g->_snd_masterVolume;
+ _condMask = 0x80;
+ } else if (_soundNum == kSoundNum_Test) {
+ SssObject *so = playSound(kSound_0x60);
+ int spriteNum = _soundTestSpriteNum = 7;
+ drawSoundScreen();
+ if (so) {
+ int frames, panning = so->panning;
+ while ((frames = _g->getSoundPosition(so)) > 0) {
+ if (frames <= 20) {
+ if (_soundTestSpriteNum != 7) {
+ _soundTestSpriteNum = 7;
+ panning = 64; // center
+ }
+ } else if (frames <= 36) {
+ if (_soundTestSpriteNum != 23) {
+ _soundTestSpriteNum = 23;
+ panning = 128; // right
+ }
+ } else { // if (frames <= 49)
+ if (_soundTestSpriteNum != 22) {
+ _soundTestSpriteNum = 22;
+ panning = 0; // left
+ }
+ }
+ if (spriteNum != _soundTestSpriteNum) {
+ spriteNum = _soundTestSpriteNum;
+ _g->setSoundPanning(so, panning);
+ drawSoundScreen();
+ }
+ g_system->sleep(kDelayMs);
+ }
+ }
+ _soundTestSpriteNum = 24;
+ } else if (_soundNum == kSoundNum_Cancel) {
+ playSound(kSound_0x80);
+ _config->players[_config->currentPlayer].volume = _g->_snd_masterVolume = _soundVolume;
+ _condMask = 0x80;
+ } else if (_soundNum == kSoundNum_Reset) {
+ playSound(kSound_0x88);
+ _config->players[_config->currentPlayer].volume = _g->_snd_masterVolume = Game::kDefaultSoundVolume;
+ }
+ } else if (num == kCursor_Left) {
+ if (_soundNum == kSoundNum_Volume) {
+ if (_g->_snd_masterVolume > 0) {
+ playSound(kSound_0x90);
+ --_g->_snd_masterVolume;
+ _config->players[_config->currentPlayer].volume = _g->_snd_masterVolume;
+ _volumeState = 1;
+ _condMask = 8;
+ }
+ } else if (_soundNum == kSoundNum_Test) {
+ playSound(kSound_0x70);
+ _soundNum = kSoundNum_Cancel;
+ } else if (_soundNum == kSoundNum_Cancel) {
+ playSound(kSound_0x70);
+ _soundNum = kSoundNum_Confirm;
+ }
+ } else if (num == kCursor_Right) {
+ if (_soundNum == kSoundNum_Volume) {
+ if (_g->_snd_masterVolume < 128) {
+ playSound(kSound_0x90);
+ ++_g->_snd_masterVolume;
+ _config->players[_config->currentPlayer].volume = _g->_snd_masterVolume;
+ _volumeState = 2;
+ _condMask = 8;
+ }
+ } else if (_soundNum == 2) {
+ playSound(kSound_0x70);
+ _soundNum = kSoundNum_Cancel;
+ } else if (_soundNum == 4) {
+ if (_g->_snd_masterVolume != 0) {
+ playSound(kSound_0x70);
+ }
+ _soundNum = kSoundNum_Test;
+ }
+ } else if (num == kCursor_Up) {
+ if (_soundNum != kSoundNum_Volume) {
+ playSound(kSound_0x70);
+ }
+ if (_soundNum >= 2 && _soundNum <= 4) {
+ _soundNum = kSoundNum_Volume;
+ } else if (_soundNum == kSoundNum_Reset) {
+ _soundNum = kSoundNum_Cancel;
+ }
+ } else if (num == kCursor_Down) {
+ if (_soundNum != kSoundNum_Reset) {
+ playSound(kSound_0x70);
+ }
+ if (_soundNum == kSoundNum_Stereo) {
+ _soundNum = kSoundNum_Volume;
+ } else if (_soundNum == kSoundNum_Volume) {
+ _soundNum = kSoundNum_Cancel;
+ } else if (_soundNum >= 2 && _soundNum <= 4) {
+ _soundNum = 5;
+ }
+ } else {
+ _soundCounter = 0;
+ }
+ drawSoundScreen();
+ g_system->sleep(kDelayMs);
+}
+
+void Menu::changeToOption(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ const int button = data[6];
+ if (button != 0xFF) {
+ assert(button < _optionsButtonSpritesCount);
+ _currentOptionButtonSound = READ_LE_UINT32(_optionsButtonSpritesData + button * 20);
+ _currentOptionButtonSprite = (DatSpritesGroup *)(_optionsButtonSpritesData + button * 20 + 4);
+ _currentOptionButtonSprite->num = 0; // start from frame #0
+ } else {
+ _currentOptionButtonSound = 0;
+ _currentOptionButtonSprite = 0;
+ }
+ if (!_paf->_skipCutscenes) {
+ _paf->play(data[5]);
+ }
+ if (_optionNum == kMenu_NewGame + 1) {
+ _config->players[_config->currentPlayer].levelNum = 0;
+ _config->players[_config->currentPlayer].checkpointNum = 0;
+ } else if (_optionNum == kMenu_CurrentGame + 1) {
+ // nothing to do
+ } else if (_optionNum == kMenu_Load + 1) {
+ _loadLevelButtonState = 0;
+ memcpy(_paletteBuffer, _optionsBitmapData[5] + _optionsBitmapSize[5], 192 * 3);
+ memcpy(_paletteBuffer + 192 * 3, _levelsBitmapsData + _levelsBitmaps[_levelNum].palette, 64 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ drawLevelScreen();
+ } else if (_optionNum == kMenu_Load + 2) {
+ _loadCheckpointButtonState = 0;
+ _checkpointNum = 0;
+ setLevelCheckpoint(_config->currentPlayer);
+ memcpy(_paletteBuffer, _optionsBitmapData[6] + _optionsBitmapSize[6], 256 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ drawCheckpointScreen();
+ } else if (_optionNum == kMenu_Settings + 1) {
+ _settingNum = kSettingNum_Difficulty;
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 256 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ handleSettingsScreen(5);
+ } else if (_optionNum == kMenu_Cutscenes + 1) {
+ _loadCutsceneButtonState = 0;
+ _cutsceneNum = 0;
+ drawCutsceneScreen();
+ } else if (_optionsBitmapSize[_optionNum] != 0) {
+ drawBitmap(_optionsBitmapData[_optionNum], _optionsBitmapSize[_optionNum]);
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 256 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ refreshScreen();
+ }
+}
+
+void Menu::handleLoadLevel(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == 16) {
+ if (_loadLevelButtonState != 0) {
+ playSound(kSound_0x70);
+ }
+ _loadLevelButtonState = 0;
+ } else if (num == 17) {
+ if (_loadLevelButtonState == 0) {
+ playSound(kSound_0x70);
+ }
+ _loadLevelButtonState = 1;
+ } else if (num == 18) {
+ if (_loadLevelButtonState != 0) {
+ playSound(kSound_0x80);
+ _condMask = 0x80;
+ } else {
+ playSound(kSound_0x78);
+ _condMask = 0x10;
+ }
+ } else if (num == 19) {
+ if (_lastLevelNum > 1) {
+ playSound(kSound_0x70);
+ ++_levelNum;
+ _checkpointNum = 0;
+ if (_levelNum >= _lastLevelNum) {
+ _levelNum = 0;
+ }
+ memcpy(_paletteBuffer + 192 * 3, _levelsBitmapsData + _levelsBitmaps[_levelNum].palette, 64 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ _condMask = 0x20;
+ }
+ } else if (num == 20) {
+ if (_lastLevelNum > 1) {
+ playSound(kSound_0x70);
+ --_levelNum;
+ _checkpointNum = 0;
+ if (_levelNum < 0) {
+ _levelNum = _lastLevelNum - 1;
+ }
+ memcpy(_paletteBuffer + 192 * 3, _levelsBitmapsData + _levelsBitmaps[_levelNum].palette, 64 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ _condMask = 0x40;
+ }
+ }
+ drawLevelScreen();
+ g_system->sleep(kDelayMs);
+}
+
+void Menu::handleLoadCheckpoint(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == 11) {
+ if (_loadCheckpointButtonState != 0) {
+ playSound(kSound_0x70);
+ _loadCheckpointButtonState = 0;
+ }
+ } else if (num == 12) {
+ if (_loadCheckpointButtonState == 0) {
+ playSound(kSound_0x70);
+ _loadCheckpointButtonState = 1;
+ }
+ } else if (num == 13) {
+ if (_loadCheckpointButtonState != 0) {
+ playSound(kSound_0x80);
+ _condMask = 0x80;
+ } else {
+ playSound(kSound_0x78);
+ _loadCheckpointButtonState = 2;
+ _condMask = 0x08;
+ if (_levelNum == 7 && _checkpointNum == 11) {
+ _config->players[_config->currentPlayer].levelNum = kLvl_dark;
+ _config->players[_config->currentPlayer].checkpointNum = 0;
+ } else {
+ _config->players[_config->currentPlayer].levelNum = _levelNum;
+ _config->players[_config->currentPlayer].checkpointNum = _checkpointNum;
+ }
+ debug(kDebug_MENU, "Restart game at level %d checkpoint %d", _levelNum, _checkpointNum);
+ return;
+ }
+ } else if (num == 14) {
+ if (_lastLevelCheckpointNum[_levelNum] > 2 || _loadCheckpointButtonState == 0) {
+ playSound(kSound_0x70);
+ ++_checkpointNum;
+ if (_checkpointNum >= _lastLevelCheckpointNum[_levelNum]) {
+ _checkpointNum = 0;
+ }
+ }
+ } else if (num == 15) {
+ if (_lastLevelCheckpointNum[_levelNum] > 2 || _loadCheckpointButtonState == 1) {
+ playSound(kSound_0x70);
+ --_checkpointNum;
+ if (_checkpointNum < 0) {
+ _checkpointNum = _lastLevelCheckpointNum[_levelNum] - 1;
+ }
+ }
+ }
+ drawCheckpointScreen();
+ g_system->sleep(kDelayMs);
+}
+
+void Menu::handleLoadCutscene(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == 6) {
+ if (_loadCutsceneButtonState != 0) {
+ playSound(kSound_0x70);
+ _loadCutsceneButtonState = 0;
+ }
+ } else if (num == 7) {
+ if (_loadCutsceneButtonState == 0) {
+ playSound(kSound_0x70);
+ _loadCutsceneButtonState = 1;
+ }
+ } else if (num == 8) {
+ if (_loadCutsceneButtonState != 0) {
+ playSound(kSound_0x80);
+ _condMask = 0x80;
+ } else {
+ playSound(kSound_0x78);
+ _loadCutsceneButtonState = 2;
+ if (!_paf->_skipCutscenes) {
+ _currentOptionButtonSound = 0;
+ _currentOptionButtonSprite = 0;
+ const int num = _cutscenesBitmaps[_cutsceneIndexes[_cutsceneNum]].data;
+ _paf->play(num);
+ if (num == kPafAnimation_end) {
+ _paf->play(kPafAnimation_cinema);
+ }
+ }
+ playSound(kSound_0x98);
+ playSound(kSound_0xA0);
+ _loadCutsceneButtonState = 0;
+ }
+ } else if (num == 9) {
+ if (_cutsceneIndexesCount > 2 || _loadCutsceneButtonState == 0) {
+ playSound(kSound_0x70);
+ ++_cutsceneNum;
+ if (_cutsceneNum >= _cutsceneIndexesCount) {
+ _cutsceneNum = 0;
+ }
+ }
+ } else if (num == 10) {
+ if (_cutsceneIndexesCount > 2 || _loadCutsceneButtonState == 1) {
+ playSound(kSound_0x70);
+ --_cutsceneNum;
+ if (_cutsceneNum < 0) {
+ _cutsceneNum = _cutsceneIndexesCount - 1;
+ }
+ }
+ }
+ drawCutsceneScreen();
+ g_system->sleep(kDelayMs);
+}
+
+static bool matchInput(uint8_t type, uint8_t mask, const PlayerInput &inp, uint8_t optionMask) {
+ if (type != 0) {
+ if ((mask & 1) != 0 && inp.keyReleased(SYS_INP_RUN)) {
+ return true;
+ }
+ if ((mask & 2) != 0 && inp.keyReleased(SYS_INP_JUMP)) {
+ return true;
+ }
+ if ((mask & 4) != 0 && inp.keyReleased(SYS_INP_SHOOT)) {
+ return true;
+ }
+ if ((mask & optionMask) != 0) {
+ return true;
+ }
+ } else {
+ if ((mask & 1) != 0 && inp.keyReleased(SYS_INP_UP)) {
+ return true;
+ }
+ if ((mask & 2) != 0 && inp.keyReleased(SYS_INP_RIGHT)) {
+ return true;
+ }
+ if ((mask & 4) != 0 && inp.keyReleased(SYS_INP_DOWN)) {
+ return true;
+ }
+ if ((mask & 8) != 0 && inp.keyReleased(SYS_INP_LEFT)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Menu::handleOptions() {
+ _lastLevelNum = _config->players[_config->currentPlayer].lastLevelNum + 1;
+ if (_lastLevelNum > _res->_datHdr.levelsCount) {
+ _lastLevelNum = _res->_datHdr.levelsCount;
+ }
+ _levelNum = _config->players[_config->currentPlayer].levelNum;
+ if (_levelNum > kLvl_dark) {
+ _levelNum = kLvl_dark;
+ }
+ if (_levelNum == kLvl_dark) {
+ _levelNum = 7;
+ _checkpointNum = 11;
+ }
+ _cutsceneIndexesCount = 0;
+ const uint32_t playedCutscenesMask = _config->players[_config->currentPlayer].cutscenesMask;
+ for (int i = 0; i < _res->_datHdr.cutscenesCount; ++i) {
+ if ((playedCutscenesMask & (1 << i)) != 0) {
+ _cutsceneIndexes[_cutsceneIndexesCount] = i;
+ ++_cutsceneIndexesCount;
+ }
+ }
+ if (_cutsceneIndexesCount > _res->_datHdr.cutscenesCount) {
+ _cutsceneIndexesCount = _res->_datHdr.cutscenesCount;
+ }
+ _optionNum = kMenu_Settings;
+ changeToOption(0);
+ _condMask = 0;
+ while (!g_system->inp.quit) {
+ g_system->processEvents();
+ if (g_system->inp.keyPressed(SYS_INP_ESC)) {
+ _optionNum = -1;
+ break;
+ }
+ // get transition from inputs and menu return code (_condMask)
+ int num = -1;
+ for (int i = 0; i < _res->_datHdr.menusCount; ++i) {
+ const uint8_t *data = &_optionData[i * 8];
+ if (data[0] == _optionNum && matchInput(data[1] & 1, data[2], g_system->inp, _condMask)) {
+ num = i;
+ break;
+ }
+ }
+ _condMask = 0;
+ if (num == -1) {
+ g_system->sleep(kDelayMs);
+ continue;
+ }
+ const uint8_t *data = &_optionData[num * 8];
+ const int prevOptionNum = _optionNum;
+ _optionNum = data[3];
+ debug(kDebug_MENU, "handleOptions option %d code %d", _optionNum, data[4]);
+ switch (data[4]) {
+ case 0:
+ if (prevOptionNum != _optionNum) {
+ _iconsSprites[0x2A].num = 0;
+ _iconsSprites[0x27].num = 0;
+ _iconsSprites[0x26].num = 0;
+ _iconsSprites[0x28].num = 0;
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 256 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ _settingNum = kSettingNum_Difficulty;
+ }
+ handleSettingsScreen(num);
+ break;
+ case 1:
+ if (prevOptionNum != _optionNum) {
+ _iconsSprites[0x2C].num = 0;
+ _iconsSprites[0x2E].num = 0;
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 256 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ _controlsNum = 2;
+ }
+ handleControlsScreen(num);
+ break;
+ case 2:
+ if (prevOptionNum != _optionNum) {
+ _iconsSprites[0x19].num = 0;
+ _iconsSprites[0x18].num = 0;
+ _iconsSprites[0x17].num = 0;
+ _iconsSprites[0x1A].num = 0;
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 256 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ _joystickControlsNum = 1;
+ }
+ handleJoystickControlsScreen(num);
+ break;
+ case 3:
+ if (prevOptionNum != _optionNum) {
+ _iconsSprites[0x21].num = 0;
+ _iconsSprites[0x20].num = 0;
+ _iconsSprites[0x1F].num = 0;
+ _iconsSprites[0x22].num = 0;
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 256 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ _keyboardControlsNum = 1;
+ }
+ handleKeyboardControlsScreen(num);
+ break;
+ case 4:
+ if (prevOptionNum != _optionNum) {
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 256 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ _difficultyNum = _config->players[_config->currentPlayer].difficulty;
+ }
+ handleDifficultyScreen(num);
+ break;
+ case 5:
+ if (prevOptionNum != _optionNum) {
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 256 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ _soundVolume = _g->_snd_masterVolume;
+ _soundNum = kSoundNum_Confirm;
+ _soundCounter = 0;
+ }
+ handleSoundScreen(num);
+ break;
+ case 6:
+ playSound(kSound_0x70);
+ changeToOption(num);
+ break;
+ case 7:
+ playSound(kSound_0x78);
+ changeToOption(num);
+ break;
+ case 8:
+ changeToOption(num);
+ break;
+ case 9:
+ handleLoadCutscene(num);
+ break;
+ case 10:
+ handleLoadLevel(num);
+ break;
+ case 11:
+ break;
+ case 12:
+ setLevelCheckpoint(_config->currentPlayer);
+ handleLoadCheckpoint(num);
+ break;
+ default:
+ warning("Unhandled option %d %d", _optionNum, data[4]);
+ break;
+ }
+ if (_optionNum == kMenu_Quit + 1) {
+ break;
+ } else if (_optionNum == kMenu_NewGame + 1 || _optionNum == kMenu_CurrentGame + 1 || _optionNum == kMenu_ResumeGame) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/Src/Global/menu.h b/Src/Global/menu.h
new file mode 100644
index 0000000..ce4741e
--- /dev/null
+++ b/Src/Global/menu.h
@@ -0,0 +1,153 @@
+
+#ifndef MENU_H__
+#define MENU_H__
+
+#include "intern.h"
+
+struct Game;
+struct PafPlayer;
+struct Resource;
+struct Video;
+
+struct DatSpritesGroup {
+ uint32_t currentFrameOffset; // 0
+ uint32_t firstFrameOffset; // 4
+ uint32_t size; // 8 following this header
+ uint16_t count; // 12
+ uint16_t num; // 14
+} PACKED; // sizeof == 16
+
+struct DatBitmap {
+ uint32_t size; // 0
+ uint32_t unk4; // 4
+ // 8 lzw + 768 palette
+} PACKED; // sizeof == 8
+
+struct DatBitmapsGroup {
+ uint8_t w;
+ uint8_t h;
+ uint8_t colors;
+ uint8_t data;
+ uint32_t offset; // 4
+ uint32_t palette; // 8
+} PACKED; // sizeof == 12
+
+struct Menu {
+ enum {
+ kDelayMs = 30,
+ kCheckpointLevelsCount = 8,
+ kCutsceneIndexesCount = 22, // kPafAnimation_cinema + 1
+ kOptionsCount = 19
+ };
+
+ Game *_g;
+ PafPlayer *_paf;
+ Resource *_res;
+ Video *_video;
+
+ SetupConfig *_config;
+ int _checkpointNum;
+ int _levelNum;
+
+ DatSpritesGroup *_titleSprites;
+ DatSpritesGroup *_playerSprites;
+ const uint8_t *_titleBitmapData;
+ uint32_t _titleBitmapSize;
+ const uint8_t *_playerBitmapData;
+ uint32_t _playerBitmapSize;
+ uint32_t _optionsBitmapSize[kOptionsCount];
+ const uint8_t *_optionsBitmapData[kOptionsCount];
+ DatBitmapsGroup *_cutscenesBitmaps;
+ const uint8_t *_cutscenesBitmapsData;
+ DatBitmapsGroup *_checkpointsBitmaps[kCheckpointLevelsCount];
+ const uint8_t *_checkpointsBitmapsData[kCheckpointLevelsCount];
+ DatBitmapsGroup *_levelsBitmaps;
+ const uint8_t *_levelsBitmapsData;
+ DatSpritesGroup *_iconsSprites;
+ const uint8_t *_iconsSpritesData;
+ int _optionsButtonSpritesCount;
+ const uint8_t *_optionsButtonSpritesData;
+ DatSpritesGroup *_currentOptionButtonSprite;
+ int _currentOptionButtonSound;
+
+ const uint8_t *_digitsData;
+ const uint8_t *_optionData;
+ const uint8_t *_soundData;
+
+ uint8_t _paletteBuffer[256 * 3];
+ uint8_t _loadLevelButtonState;
+ uint8_t _optionNum;
+ int _lastLevelNum;
+ int _lastLevelCheckpointNum[kCheckpointLevelsCount];
+ uint8_t _condMask;
+ uint8_t _loadCheckpointButtonState;
+ int _bitmapCircularListIndex[3];
+ int _cutsceneIndexesCount;
+ int _cutsceneNum;
+ uint8_t _loadCutsceneButtonState;
+ int _cutsceneIndexes[kCutsceneIndexesCount];
+ int _settingNum;
+ int _controlsNum;
+ int _joystickControlsNum;
+ int _keyboardControlsNum;
+ int _difficultyNum;
+ int _soundNum;
+ uint8_t _soundVolume;
+ int _volumeState;
+ int _soundCounter;
+ int _soundTestSpriteNum;
+
+ Menu(Game *g, PafPlayer *paf, Resource *res, Video *video);
+
+ void setVolume();
+
+ void loadData();
+
+ int getSoundNum(int num, int index = 0) const;
+ SssObject *playSound(int num);
+
+ void drawBitmap(const uint8_t *data, uint32_t size, bool setPalette = false);
+
+ void drawSprite(const DatSpritesGroup *spriteGroup, const uint8_t *ptr, uint32_t num, int x = -1, int y = -1);
+ void drawSpriteAnim(DatSpritesGroup *spriteGroup, const uint8_t *ptr, uint32_t num);
+
+ void pafCallback(int frameNum, const uint8_t *frameData);
+ void refreshScreen();
+
+ bool mainLoop();
+
+ void drawTitleScreen(int option);
+ int handleTitleScreen();
+ void drawDigit(int x, int y, int num);
+ void drawBitmap(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int x, int y, int w, int h, uint8_t baseColor = 0);
+ void setCurrentPlayer(int num);
+ void setLevelCheckpoint(int num);
+ void drawPlayerProgress(int state, int cursor);
+ void handleAssignPlayer();
+ void updateBitmapsCircularList(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int num, int count);
+ void drawBitmapsCircularList(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int num, int count, bool updatePalette);
+ void drawCheckpointScreen();
+ void drawLevelScreen();
+ void drawCutsceneScreen();
+ void drawSettingsScreen();
+ void handleSettingsScreen(int num);
+ void drawControlsScreen();
+ void handleControlsScreen(int num);
+ void drawJoystickKeyCode(int num);
+ void drawJoystickControlsScreen();
+ void handleJoystickControlsScreen(int num);
+ void drawKeyboardKeyCode(int num);
+ void drawKeyboardControlsScreen();
+ void handleKeyboardControlsScreen(int num);
+ void drawDifficultyScreen();
+ void handleDifficultyScreen(int num);
+ void drawSoundScreen();
+ void handleSoundScreen(int num);
+ void changeToOption(int num);
+ void handleLoadLevel(int num);
+ void handleLoadCheckpoint(int num);
+ void handleLoadCutscene(int num);
+ bool handleOptions();
+};
+
+#endif // MENU_H__
diff --git a/Src/Global/mixer.cpp b/Src/Global/mixer.cpp
new file mode 100644
index 0000000..15e983d
--- /dev/null
+++ b/Src/Global/mixer.cpp
@@ -0,0 +1,87 @@
+
+#include "mixer.h"
+#include "util.h"
+
+static void nullMixerLock(int lock) {
+}
+
+Mixer::Mixer()
+ : _lock(nullMixerLock) {
+ memset(_mixingQueue, 0, sizeof(_mixingQueue));
+ _mixingQueueSize = 0;
+}
+
+Mixer::~Mixer() {
+}
+
+void Mixer::queue(const int16_t *ptr, const int16_t *end, int panType, int panL, int panR, bool stereo) {
+ if (_mixingQueueSize >= kPcmChannels) {
+ warning("MixingQueue overflow %d", _mixingQueueSize);
+ return;
+ }
+ MixerChannel *channel = &_mixingQueue[_mixingQueueSize];
+ channel->ptr = ptr;
+ channel->end = end;
+ channel->panL = panL;
+ channel->panR = panR;
+ channel->panType = panType;
+ channel->stereo = stereo;
+ ++_mixingQueueSize;
+}
+
+template
+static void mixS16(int16_t *dst, const int16_t *src, int len, int panL, int panR) {
+
+ static const int kPanBits = 14; // 0..16384
+
+ for (int j = 0; j < len; j += 2, dst += 2) {
+ const int16_t sampleL = *src++;
+ const int16_t sampleR = stereo ? *src++ : sampleL;
+ if (panning != 1) {
+ dst[0] = CLIP(dst[0] + ((panL * sampleL) >> kPanBits), -32768, 32767);
+ }
+ if (panning != 2) {
+ dst[1] = CLIP(dst[1] + ((panR * sampleR) >> kPanBits), -32768, 32767);
+ }
+ }
+}
+
+void Mixer::mix(int16_t *buf, int len) {
+ // stereo s16
+ assert((len & 1) == 0);
+ if (_mixingQueueSize == 0) {
+ return;
+ }
+ for (int i = 0; i < _mixingQueueSize; ++i) {
+ const MixerChannel *channel = &_mixingQueue[i];
+ const int panL = channel->panL;
+ const int panR = channel->panR;
+ if (channel->stereo) {
+ assert(channel->ptr + len <= channel->end);
+ switch (channel->panType) {
+ case 1:
+ mixS16(buf, channel->ptr, len, panL, panR);
+ break;
+ case 2:
+ mixS16(buf, channel->ptr, len, panL, panR);
+ break;
+ default:
+ mixS16(buf, channel->ptr, len, panL, panR);
+ break;
+ }
+ } else {
+ assert(channel->ptr + len / 2 <= channel->end);
+ switch (channel->panType) {
+ case 1:
+ mixS16(buf, channel->ptr, len, panL, panR);
+ break;
+ case 2:
+ mixS16(buf, channel->ptr, len, panL, panR);
+ break;
+ default:
+ mixS16(buf, channel->ptr, len, panL, panR);
+ break;
+ }
+ }
+ }
+}
diff --git a/Src/Global/mixer.h b/Src/Global/mixer.h
new file mode 100644
index 0000000..b6dc124
--- /dev/null
+++ b/Src/Global/mixer.h
@@ -0,0 +1,44 @@
+
+#ifndef MIXER_H__
+#define MIXER_H__
+
+#include "intern.h"
+
+struct MixerChannel {
+ const int16_t *ptr;
+ const int16_t *end;
+ int panL;
+ int panR;
+ uint8_t panType;
+ bool stereo;
+};
+
+struct Mixer {
+
+ static const int kPcmChannels = 32;
+
+ void (*_lock)(int);
+
+ MixerChannel _mixingQueue[kPcmChannels];
+ int _mixingQueueSize;
+
+ Mixer();
+ ~Mixer();
+
+ void queue(const int16_t *ptr, const int16_t *end, int panType, int panL, int panR, bool stereo);
+
+ void mix(int16_t *buf, int len);
+};
+
+struct MixerLock {
+ Mixer *_mix;
+ MixerLock(Mixer *mix)
+ : _mix(mix) {
+ _mix->_lock(1);
+ }
+ ~MixerLock() {
+ _mix->_lock(0);
+ }
+};
+
+#endif
diff --git a/Src/Global/monsters.cpp b/Src/Global/monsters.cpp
new file mode 100644
index 0000000..c1df0c6
--- /dev/null
+++ b/Src/Global/monsters.cpp
@@ -0,0 +1,7117 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "game.h"
+#include "level.h"
+#include "resource.h"
+#include "util.h"
+
+// (lut[x] & 1) indicates a diagonal direction
+static const uint8_t _mstLut1[] = {
+ // 0
+ 0x08,
+ 0x00, // kDirectionKeyMaskUp
+ 0x02, // kDirectionKeyMaskRight
+ 0x01, // kDirectionKeyMaskUp | kDirectionKeyMaskRight
+ // 4
+ 0x04, // kDirectionKeyMaskDown
+ 0x00, // kDirectionKeyMaskDown | kDirectionKeyMaskUp
+ 0x03, // kDirectionKeyMaskDown | kDirectionKeyMaskRight
+ 0x01, // kDirectionKeyMaskDown | kDirectionKeyMaskUp | kDirectionKeyMaskRight
+ // 8
+ 0x06, // kDirectionKeyMaskLeft
+ 0x07, // kDirectionKeyMaskLeft | kDirectionKeyMaskUp
+ 0x02, // kDirectionKeyMaskLeft | kDirectionKeyMaskRight
+ 0x01, // kDirectionKeyMaskLeft | kDirectionKeyMaskUp | kDirectionKeyMaskRight
+ // 12
+ 0x05, // kDirectionKeyMaskLeft | kDirectionKeyMaskDown
+ 0x07, // kDirectionKeyMaskLeft | kDirectionKeyMaskDown | kDirectionKeyMaskRight
+ 0x03, // kDirectionKeyMaskLeft | kDirectionKeyMaskDown | kDirectionKeyMaskRight
+ 0x01 // kDirectionKeyMaskLeft | kDirectionKeyMaskDown | kDirectionKeyMaskUp | kDirectionKeyMaskRight
+};
+
+// indexes _mstLut1
+static const uint8_t _mstLut3[8 * 5] = {
+ 0x01, 0x03, 0x09, 0x02, 0x08, 0x03, 0x02, 0x01, 0x06, 0x09, 0x02, 0x06, 0x03, 0x04, 0x01, 0x06,
+ 0x04, 0x02, 0x0C, 0x03, 0x04, 0x0C, 0x06, 0x08, 0x02, 0x0C, 0x08, 0x04, 0x09, 0x06, 0x08, 0x09,
+ 0x0C, 0x01, 0x04, 0x09, 0x01, 0x08, 0x03, 0x0C
+};
+
+// monster frames animation (minus #0, #4, #8, #12, #16, #20, eg. every 4th is skipped)
+static const uint8_t _mstLut4[] = {
+ 0x01, 0x02, 0x03, 0x05, 0x06, 0x07, 0x09, 0x0A, 0x0B, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x13, 0x15,
+ 0x16, 0x17
+};
+
+// indexes _mstLut4 clipped to [0,17], repeat the frame preceding the skipped ones
+static const uint8_t _mstLut5[] = {
+ 0x00, 0x00, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x09, 0x09, 0x0A, 0x0B,
+ 0x0C, 0x0C, 0x0D, 0x0E, 0x0F, 0x0F, 0x10, 0x11, 0x12, 0x12, 0x13, 0x14, 0x15, 0x15, 0x16, 0x17
+};
+
+static const uint8_t _fireflyPosData[] = {
+ 0, 1, 2, 3, 4, 4, 4, 0xFF, 0, 1, 2, 2, 4, 4, 3, 0xFF,
+ 0, 1, 1, 2, 3, 2, 3, 0xFF, 0, 1, 1, 2, 3, 3, 3, 0xFF,
+ 0, 0, 1, 2, 2, 2, 2, 0xFF, 4, 4, 4, 3, 2, 1, 0, 0xFF,
+ 3, 4, 4, 2, 2, 1, 0, 0xFF, 3, 2, 3, 2, 1, 1, 0, 0xFF,
+ 3, 3, 3, 2, 1, 1, 0, 0xFF, 2, 2, 2, 2, 1, 0, 0, 0xFF
+};
+
+static const uint8_t kUndefinedMonsterByteCode[] = { 0x12, 0x34 };
+
+static uint8_t mstGetFacingDirectionMask(uint8_t a) {
+ uint8_t r = 0;
+ if (a & kDirectionKeyMaskLeft) { // Andy left facing monster
+ r |= 1;
+ }
+ if (a & kDirectionKeyMaskDown) { // Andy below the monster
+ r |= 2;
+ }
+ return r;
+}
+
+void Game::mstMonster1ResetData(MonsterObject1 *m) {
+ m->m46 = 0;
+ LvlObject *o = m->o16;
+ if (o) {
+ o->dataPtr = 0;
+ }
+ for (int i = 0; i < kMaxMonsterObjects2; ++i) {
+ MonsterObject2 *mo = &_monsterObjects2Table[i];
+ if (mo->monster2Info && mo->monster1 == m) {
+ mo->monster1 = 0;
+ }
+ }
+}
+
+void Game::mstMonster2InitFirefly(MonsterObject2 *m) {
+ LvlObject *o = m->o;
+ m->x1 = _res->_mstPointOffsets[o->screenNum].xOffset;
+ m->y1 = _res->_mstPointOffsets[o->screenNum].yOffset;
+ m->x2 = m->x1 + 255;
+ m->y2 = m->y1 + 191;
+ const uint32_t num = _rnd.update();
+ m->hPosIndex = num % 40;
+ if (_fireflyPosData[m->hPosIndex] != 0xFF) {
+ m->hPosIndex &= ~7;
+ }
+ m->vPosIndex = m->hPosIndex;
+ if (num & 0x80) {
+ m->hPosIndex += 40;
+ } else {
+ m->vPosIndex += 40;
+ }
+ m->hDir = (num >> 16) & 1;
+ m->vDir = (num >> 17) & 1;
+}
+
+void Game::mstMonster2ResetData(MonsterObject2 *m) {
+ m->monster2Info = 0;
+ LvlObject *o = m->o;
+ if (o) {
+ o->dataPtr = 0;
+ }
+}
+
+void Game::mstTaskSetMonster2ScreenPosition(Task *t) {
+ MonsterObject2 *m = t->monster2;
+ LvlObject *o = m->o;
+ m->xPos = o->xPos + o->posTable[7].x;
+ m->yPos = o->yPos + o->posTable[7].y;
+ m->xMstPos = m->xPos + _res->_mstPointOffsets[o->screenNum].xOffset;
+ m->yMstPos = m->yPos + _res->_mstPointOffsets[o->screenNum].yOffset;
+}
+
+void Game::mstMonster1ResetWalkPath(MonsterObject1 *m) {
+ _rnd.resetMst(m->rnd_m35);
+ _rnd.resetMst(m->rnd_m49);
+
+ const uint8_t *ptr = m->monsterInfos;
+ const int num = (~m->flagsA5) & 1;
+
+ m->levelPosBounds_x2 = m->walkNode->coords[0][num] - (int32_t)READ_LE_UINT32(ptr + 904);
+ m->levelPosBounds_x1 = m->walkNode->coords[1][num] + (int32_t)READ_LE_UINT32(ptr + 904);
+ m->levelPosBounds_y2 = m->walkNode->coords[2][num] - (int32_t)READ_LE_UINT32(ptr + 908);
+ m->levelPosBounds_y1 = m->walkNode->coords[3][num] + (int32_t)READ_LE_UINT32(ptr + 908);
+
+ const uint32_t indexWalkCode = m->walkNode->walkCodeReset[num];
+ m->walkCode = (indexWalkCode == kNone) ? 0 : &_res->_mstWalkCodeData[indexWalkCode];
+}
+
+bool Game::mstUpdateInRange(MstMonsterAction *m) {
+ if (m->unk4 == 0) {
+ if (mstHasMonsterInRange(m, 0) && addChasingMonster(m, 0)) {
+ return true;
+ }
+ } else {
+ int direction = _rnd.update() & 1;
+ if (mstHasMonsterInRange(m, direction) && addChasingMonster(m, direction)) {
+ return true;
+ }
+ direction ^= 1;
+ if (mstHasMonsterInRange(m, direction) && addChasingMonster(m, direction)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Game::addChasingMonster(MstMonsterAction *m48, uint8_t direction) {
+ debug(kDebug_MONSTER, "addChasingMonster %d", direction);
+ m48->direction = direction;
+ if (m48->codeData != kNone) {
+ Task *t = createTask(_res->_mstCodeData + m48->codeData * 4);
+ if (!t) {
+ return false;
+ }
+ while ((this->*(t->run))(t) == 0);
+ }
+ _mstActionNum = m48 - &_res->_mstMonsterActionData[0];
+ _mstChasingMonstersCount = 0;
+ for (int i = 0; i < m48->areaCount; ++i) {
+ MstMonsterAreaAction *unk4 = m48->area[i].data;
+ const uint8_t num = unk4->monster1Index;
+ if (num != 0xFF) {
+ assert(num < kMaxMonsterObjects1);
+ unk4->direction = direction;
+ MonsterObject1 *m = &_monsterObjects1Table[num];
+ m->action = unk4;
+ m->flags48 |= 0x40;
+ m->flagsA5 = (m->flagsA5 & ~0x75) | 0xA;
+ mstMonster1ResetWalkPath(m);
+ Task *current = _monsterObjects1TasksList;
+ Task *t = m->task;
+ assert(t);
+ while (current) {
+ Task *next = current->nextPtr;
+ if (current == t) {
+ removeTask(&_monsterObjects1TasksList, t);
+ appendTask(&_monsterObjects1TasksList, t);
+ break;
+ }
+ current = next;
+ }
+ const uint32_t codeData = unk4->codeData;
+ assert(codeData != kNone);
+ resetTask(t, _res->_mstCodeData + codeData * 4);
+ ++_mstChasingMonstersCount;
+ }
+ }
+ debug(kDebug_MONSTER, "_mstChasingMonstersCount %d", _mstChasingMonstersCount);
+ return true;
+}
+
+void Game::mstMonster1ClearChasingMonster(MonsterObject1 *m) {
+ m->flags48 &= ~0x50;
+ m->action->monster1Index = 0xFF;
+ m->action = 0;
+ --_mstChasingMonstersCount;
+ if (_mstChasingMonstersCount <= 0) {
+ _mstActionNum = -1;
+ }
+}
+
+void Game::mstTaskSetMonster1BehaviorState(Task *t, MonsterObject1 *m, int num) {
+ MstBehaviorState *behaviorState = &m->m46->data[num];
+ m->behaviorState = behaviorState;
+ m->monsterInfos = _res->_mstMonsterInfos + behaviorState->indexMonsterInfo * kMonsterInfoDataSize;
+ if (behaviorState->indexUnk51 == kNone) {
+ m->flags48 &= ~4;
+ }
+ const uint32_t indexWalkPath = behaviorState->walkPath;
+ m->walkNode = _res->_mstWalkPathData[indexWalkPath].data;
+ mstTaskUpdateScreenPosition(t);
+ if (!mstMonster1UpdateWalkPath(m)) {
+ mstMonster1ResetWalkPath(m);
+ }
+ if (t->run == &Game::mstTask_monsterWait4) {
+ t->run = &Game::mstTask_main;
+ }
+ if ((m->flagsA5 & 8) == 0 && t->run == &Game::mstTask_idle) {
+ mstTaskSetNextWalkCode(t);
+ }
+}
+
+int Game::mstTaskStopMonsterObject1(Task *t) {
+ if (_mstActionNum == -1) {
+ return 0;
+ }
+ MonsterObject1 *m = t->monster1;
+
+ // bugfix: original code meant to check bit 3 directly ?
+
+ // const uint8_t r = (m->flagsA5 == 0) ? 1 : 0;
+ // if ((r & 8) != 0) {
+ // return 0;
+ // }
+
+ const MstMonsterAreaAction *m48 = m->action;
+ if (!m48) {
+ return 0;
+ }
+ const uint32_t codeData = m48->codeData2;
+ if (codeData != kNone) {
+ resetTask(t, _res->_mstCodeData + codeData * 4);
+ mstMonster1ClearChasingMonster(m);
+ return 0;
+ }
+ mstMonster1ClearChasingMonster(m);
+ if (m->flagsA5 & 0x80) {
+ m->flagsA5 &= ~8;
+ const uint32_t codeData = m->behaviorState->codeData;
+ if (codeData != kNone) {
+ resetTask(t, _res->_mstCodeData + codeData * 4);
+ return 0;
+ }
+ m->o16->actionKeyMask = 7;
+ m->o16->directionKeyMask = 0;
+ t->run = &Game::mstTask_idle;
+ return 0;
+ }
+ m->flagsA5 = (m->flagsA5 & ~9) | 6;
+ if (m->flagsA6 & 2) {
+ return 0;
+ }
+ return mstTaskInitMonster1Type2(t, 1);
+}
+
+void Game::mstMonster1SetScreenPosition(MonsterObject1 *m) {
+ LvlObject *o = m->o16;
+ o->xPos = m->xPos - o->posTable[7].x;
+ o->yPos = m->yPos - o->posTable[7].y;
+ m->xMstPos = m->xPos + _res->_mstPointOffsets[o->screenNum].xOffset;
+ m->yMstPos = m->yPos + _res->_mstPointOffsets[o->screenNum].yOffset;
+}
+
+bool Game::mstMonster1SetWalkingBounds(MonsterObject1 *m) {
+ MstBehaviorState *behaviorState = m->behaviorState;
+ const uint32_t indexWalkPath = behaviorState->walkPath;
+ MstWalkPath *walkPath = &_res->_mstWalkPathData[indexWalkPath];
+ MstWalkNode *walkNode = walkPath->data;
+
+ int x = m->xMstPos;
+ int y = m->yMstPos;
+ if (m->levelPosBounds_x1 >= 0) {
+ if (x < m->levelPosBounds_x1) {
+ x = m->levelPosBounds_x1;
+ } else if (x > m->levelPosBounds_x2) {
+ x = m->levelPosBounds_x2;
+ }
+ if (y < m->levelPosBounds_y1) {
+ y = m->levelPosBounds_y1;
+ } else if (y > m->levelPosBounds_y2) {
+ y = m->levelPosBounds_y2;
+ }
+ }
+
+ const uint32_t indexWalkBox = walkPath->data[0].walkBox;
+ const MstWalkBox *m34 = &_res->_mstWalkBoxData[indexWalkBox];
+ int xWalkBox = (m34->right - m34->left) / 2 + m34->left;
+
+ int minDistance = 0x1000000;
+ int yWalkBox = y;
+
+ uint32_t i = 0;
+ for (; i < walkPath->count; ++i) {
+ const uint32_t indexWalkBox = walkPath->data[i].walkBox;
+ const MstWalkBox *m34 = &_res->_mstWalkBoxData[indexWalkBox];
+ if (!rect_contains(m34->left, m34->top, m34->right, m34->bottom, x, y)) {
+ // find the closest box
+ const int d1 = ABS(x - m34->left);
+ if (d1 < minDistance) {
+ minDistance = d1;
+ walkNode = &walkPath->data[i];
+ xWalkBox = m34->left;
+ }
+ const int d2 = ABS(x - m34->right);
+ if (d2 < minDistance) {
+ minDistance = d2;
+ walkNode = &walkPath->data[i];
+ xWalkBox = m34->right;
+ }
+ } else {
+ // find match, point is in the box
+ xWalkBox = x;
+ yWalkBox = y;
+ walkNode = &walkPath->data[i];
+ break;
+ }
+ }
+ if (i == walkPath->count) {
+ // calculate the yPos for the walkBox
+ const uint32_t indexWalkBox = walkNode->walkBox;
+ const MstWalkBox *m34 = &_res->_mstWalkBoxData[indexWalkBox];
+ if (y <= m34->bottom) {
+ y = (m34->bottom - m34->top) / 2 + m34->top;
+ }
+ yWalkBox = y;
+ }
+
+ // find screenNum for level position
+ int screenNum = -1;
+ int xLevelPos;
+ int yLevelPos;
+ for (int i = 0; i < _res->_mstHdr.screensCount; ++i) {
+ xLevelPos = _res->_mstPointOffsets[i].xOffset;
+ yLevelPos = _res->_mstPointOffsets[i].yOffset;
+ if (rect_contains(xLevelPos, yLevelPos, xLevelPos + 255, yLevelPos + 191, xWalkBox, yWalkBox)) {
+ screenNum = i;
+ break;
+ }
+ }
+ if (screenNum == -1) {
+ screenNum = 0;
+ xLevelPos = _res->_mstPointOffsets[0].xOffset + 256 / 2;
+ yLevelPos = _res->_mstPointOffsets[0].yOffset + 192 / 2;
+ }
+ LvlObject *o = m->o16;
+ o->screenNum = screenNum;
+ m->xPos = xWalkBox - xLevelPos;
+ m->yPos = yWalkBox - yLevelPos;
+ mstMonster1SetScreenPosition(m);
+ m->walkNode = walkNode;
+ mstMonster1ResetWalkPath(m);
+ return true;
+}
+
+bool Game::mstMonster1UpdateWalkPath(MonsterObject1 *m) {
+ debug(kDebug_MONSTER, "mstMonster1UpdateWalkPath m %p", m);
+ const uint8_t screenNum = m->o16->screenNum;
+ MstBehaviorState *behaviorState = m->behaviorState;
+ const uint32_t indexWalkPath = behaviorState->walkPath;
+ MstWalkPath *walkPath = &_res->_mstWalkPathData[indexWalkPath];
+ // start from screen number
+ uint32_t indexWalkNode = walkPath->walkNodeData[screenNum];
+ if (indexWalkNode != kNone) {
+ MstWalkNode *walkNode = &walkPath->data[indexWalkNode];
+ uint32_t indexWalkBox = walkNode->walkBox;
+ const MstWalkBox *m34 = &_res->_mstWalkBoxData[indexWalkBox];
+ while (m34->left <= m->xMstPos) {
+ if (m34->right >= m->xMstPos && m34->top <= m->yMstPos && m34->bottom >= m->yMstPos) {
+ if (m->walkNode == walkNode) {
+ return false;
+ }
+ m->walkNode = walkNode;
+ mstMonster1ResetWalkPath(m);
+ return true;
+ }
+
+ indexWalkNode = walkNode->nextWalkNode;
+ if (indexWalkNode == kNone) {
+ break;
+ }
+ walkNode = &walkPath->data[indexWalkNode];
+ indexWalkBox = walkNode->walkBox;
+ m34 = &_res->_mstWalkBoxData[indexWalkBox];
+ }
+ }
+ return mstMonster1SetWalkingBounds(m);
+}
+
+uint32_t Game::mstMonster1GetNextWalkCode(MonsterObject1 *m) {
+ MstWalkCode *walkCode = m->walkCode;
+ int num = 0;
+ if (walkCode->indexDataCount != 0) {
+ num = _rnd.getMstNextNumber(m->rnd_m35);
+ num = walkCode->indexData[num];
+ }
+ const uint32_t codeData = walkCode->codeData[num];
+ return codeData;
+}
+
+int Game::mstTaskSetNextWalkCode(Task *t) {
+ MonsterObject1 *m = t->monster1;
+ assert(m);
+ const uint32_t codeData = mstMonster1GetNextWalkCode(m);
+ assert(codeData != kNone);
+ resetTask(t, _res->_mstCodeData + codeData * 4);
+ const int counter = m->executeCounter;
+ m->executeCounter = _executeMstLogicCounter;
+ return m->executeCounter - counter;
+}
+
+void Game::mstBoundingBoxClear(MonsterObject1 *m, int dir) {
+ assert(dir == 0 || dir == 1);
+ uint8_t num = m->bboxNum[dir];
+ if (num < _mstBoundingBoxesCount && _mstBoundingBoxesTable[num].monster1Index == m->monster1Index) {
+ _mstBoundingBoxesTable[num].monster1Index = 0xFF;
+ int i = num;
+ for (; i < _mstBoundingBoxesCount; ++i) {
+ if (_mstBoundingBoxesTable[i].monster1Index != 0xFF) {
+ break;
+ }
+ }
+ if (i == _mstBoundingBoxesCount) {
+ _mstBoundingBoxesCount = num;
+ }
+ }
+ m->bboxNum[dir] = 0xFF;
+}
+
+int Game::mstBoundingBoxCollides1(int num, int x1, int y1, int x2, int y2) const {
+ for (int i = 0; i < _mstBoundingBoxesCount; ++i) {
+ const MstBoundingBox *p = &_mstBoundingBoxesTable[i];
+ if (p->monster1Index == 0xFF || num == p->monster1Index) {
+ continue;
+ }
+ if (rect_intersects(x1, y1, x2, y2, p->x1, p->y1, p->x2, p->y2)) {
+ return i + 1;
+ }
+ }
+ return 0;
+}
+
+int Game::mstBoundingBoxUpdate(int num, int monster1Index, int x1, int y1, int x2, int y2) {
+ if (num == 0xFF) {
+ for (num = 0; num < _mstBoundingBoxesCount; ++num) {
+ if (_mstBoundingBoxesTable[num].monster1Index == 0xFF) {
+ break;
+ }
+ }
+ assert(num < kMaxBoundingBoxes);
+ MstBoundingBox *p = &_mstBoundingBoxesTable[num];
+ p->x1 = x1;
+ p->y1 = y1;
+ p->x2 = x2;
+ p->y2 = y2;
+ p->monster1Index = monster1Index;
+ if (num == _mstBoundingBoxesCount) {
+ ++_mstBoundingBoxesCount;
+ }
+ } else if (num < _mstBoundingBoxesCount) {
+ MstBoundingBox *p = &_mstBoundingBoxesTable[num];
+ if (p->monster1Index == monster1Index) {
+ p->x1 = x1;
+ p->y1 = y1;
+ p->x2 = x2;
+ p->y2 = y2;
+ }
+ }
+ return num;
+}
+
+int Game::mstBoundingBoxCollides2(int monster1Index, int x1, int y1, int x2, int y2) const {
+ for (int i = 0; i < _mstBoundingBoxesCount; ++i) {
+ const MstBoundingBox *p = &_mstBoundingBoxesTable[i];
+ if (p->monster1Index == 0xFF || p->monster1Index == monster1Index) {
+ continue;
+ }
+ if (p->monster1Index == 0xFE) { // Andy
+ if (_monsterObjects1Table[monster1Index].monsterInfos[944] != 15) {
+ continue;
+ }
+ }
+ if (rect_intersects(x1, y1, x2, y2, p->x1, p->y1, p->x2, p->y2)) {
+ return i + 1;
+ }
+ }
+ return 0;
+}
+
+int Game::getMstDistance(int y, const AndyShootData *p) const {
+ switch (p->directionMask) {
+ case 0:
+ if (p->boundingBox.x1 <= _mstTemp_x2 && p->boundingBox.y2 >= _mstTemp_y1 && p->boundingBox.y1 <= _mstTemp_y2) {
+ const int dx = _mstTemp_x1 - p->boundingBox.x2;
+ if (dx <= 0) {
+ return 0;
+ }
+ return (p->type == 3) ? 0 : dx / p->width;
+ }
+ break;
+ case 1:
+ {
+ const int dx = p->boundingBox.x2 - _mstTemp_x1;
+ if (dx >= 0) {
+ const int dy = p->boundingBox.y1 - dx * 2 / 3;
+ if (dy <= _mstTemp_y2) {
+ const int dx2 = p->boundingBox.x1 - _mstTemp_x2;
+ if (dx2 <= 0) {
+ return (p->boundingBox.y2 >= _mstTemp_y1) ? 0 : -1;
+ }
+ const int dy2 = p->boundingBox.y2 + dx2 * 2 / 3;
+ if (dy2 > _mstTemp_y1) {
+ return (p->type == 3) ? 0 : dx2 / p->width;
+ }
+ }
+ }
+ }
+ break;
+ case 2: {
+ const int dx = _mstTemp_x1 - p->boundingBox.x2;
+ if (dx >= 0) {
+ const int dy = p->boundingBox.y2 + dx * 2 / 3;
+ if (dy >= _mstTemp_x1) {
+ const int dx2 = p->boundingBox.x1 - _mstTemp_x2;
+ if (dx2 <= 0) {
+ return (p->boundingBox.y1 <= _mstTemp_y2) ? 0 : -1;
+ }
+ const int dy2 = p->boundingBox.y1 + dx2 * 2 / 3;
+ if (dy2 <= _mstTemp_y2) {
+ return (p->type == 3) ? 0 : dx2 / p->width;
+ }
+ }
+ }
+ }
+ break;
+ case 3: {
+ const int dx = _mstTemp_x2 - p->boundingBox.x1;
+ if (dx >= 0) {
+ const int dy = p->boundingBox.y1 - dx * 2 / 3;
+ if (dy <= _mstTemp_y2) {
+ const int dx2 = _mstTemp_x1 - p->boundingBox.x2;
+ if (dx2 <= 0) {
+ return (p->boundingBox.y2 >= _mstTemp_y1) ? 0 : -1;
+ }
+ const int dy2 = p->boundingBox.y2 - dx2 * 2 / 3;
+ if (dy2 >= _mstTemp_y1) {
+ return (p->type == 3) ? 0 : dx2 / p->width;
+ }
+ }
+ }
+ }
+ break;
+ case 4:
+ {
+ const int dx = _mstTemp_x2 - p->boundingBox.x1;
+ if (dx >= 0) {
+ const int dy = dx * 2 / 3 + p->boundingBox.y2;
+ if (dy >= _mstTemp_y1) {
+ const int dx2 = _mstTemp_x1 - p->boundingBox.x2;
+ if (dx2 <= 0) {
+ return (p->boundingBox.y1 <= _mstTemp_y2) ? 0 : -1;
+ }
+ const int dy2 = dx2 * 2 / 3 + + p->boundingBox.y1;
+ if (dy2 <= _mstTemp_y2) {
+ return (p->type == 3) ? 0 : dx2 / p->width;
+ }
+ }
+ }
+ }
+ break;
+ case 5:
+ if (p->boundingBox.x2 >= _mstTemp_x1 && p->boundingBox.y2 >= _mstTemp_y1 && p->boundingBox.y1 <= _mstTemp_y2) {
+ const int dx = p->boundingBox.x1 - _mstTemp_x2;
+ if (dx <= 0) {
+ return 0;
+ }
+ return (p->type == 3) ? 0 : dx / p->width;
+ }
+ break;
+ case 6:
+ if (p->boundingBox.y2 >= _mstTemp_y1 && p->boundingBox.x2 >= _mstTemp_x1 && p->boundingBox.x1 <= _mstTemp_x2) {
+ const int dy = p->boundingBox.y1 - _mstTemp_y2;
+ if (dy <= 0) {
+ return 0;
+ }
+ return (p->type == 3) ? 0 : dy / p->height;
+ }
+ break;
+ case 7:
+ if (p->boundingBox.y1 <= _mstTemp_y2 && p->boundingBox.x2 >= _mstTemp_x1 && p->boundingBox.x1 <= _mstTemp_x2) {
+ const int dy = _mstTemp_y1 - p->boundingBox.y2;
+ if (dy <= 0) {
+ return 0;
+ }
+ return (p->type == 3) ? 0 : dy / p->height;
+ }
+ break;
+ case 128: // 8
+ if (p->boundingBox.x1 <= _mstTemp_x2 && y >= _mstTemp_y1 && y - _mstTemp_y2 <= 9) {
+ const int dx = _mstTemp_x1 - p->boundingBox.x2;
+ if (dx <= 0) {
+ return 0;
+ }
+ return dx / p->width;
+ }
+ break;
+ case 133: // 9
+ if (p->boundingBox.x2 >= _mstTemp_x1 && y >= _mstTemp_y1 && y - _mstTemp_y2 <= 9) {
+ const int dx = _mstTemp_x2 - p->boundingBox.x1;
+ if (dx <= 0) {
+ return 0;
+ }
+ return dx / p->width;
+ }
+ break;
+ }
+ return -1;
+}
+
+void Game::mstTaskUpdateScreenPosition(Task *t) {
+ MonsterObject1 *m = t->monster1;
+ assert(m);
+ LvlObject *o = m->o20;
+ if (!o) {
+ o = m->o16;
+ }
+ assert(o);
+ m->xPos = o->xPos + o->posTable[7].x;
+ m->yPos = o->yPos + o->posTable[7].y;
+ m->xMstPos = m->xPos + _res->_mstPointOffsets[o->screenNum].xOffset;
+ m->yMstPos = m->yPos + _res->_mstPointOffsets[o->screenNum].yOffset;
+
+ const uint8_t *ptr = m->monsterInfos;
+ if (ptr[946] & 4) {
+ const uint8_t *ptr1 = ptr + (o->flags0 & 0xFF) * 28;
+ if (ptr1[0xE] != 0) {
+ _mstTemp_x1 = m->xMstPos + (int8_t)ptr1[0xC];
+ _mstTemp_y1 = m->yMstPos + (int8_t)ptr1[0xD];
+ _mstTemp_x2 = _mstTemp_x1 + ptr1[0xE] - 1;
+ _mstTemp_y2 = _mstTemp_y1 + ptr1[0xF] - 1;
+ m->bboxNum[0] = mstBoundingBoxUpdate(m->bboxNum[0], m->monster1Index, _mstTemp_x1, _mstTemp_y1, _mstTemp_x2, _mstTemp_y2);
+ } else {
+ mstBoundingBoxClear(m, 0);
+ }
+ }
+ m->xDelta = _mstAndyLevelPosX - m->xMstPos;
+ if (m->xDelta < 0) {
+ m->xDelta = -m->xDelta;
+ m->facingDirectionMask = kDirectionKeyMaskLeft;
+ } else {
+ m->facingDirectionMask = kDirectionKeyMaskRight;
+ }
+ m->yDelta = _mstAndyLevelPosY - m->yMstPos;
+ if (m->yDelta < 0) {
+ m->yDelta = -m->yDelta;
+ m->facingDirectionMask |= kDirectionKeyMaskUp;
+ } else {
+ m->facingDirectionMask |= kDirectionKeyMaskDown;
+ }
+ m->collideDistance = -1;
+ m->shootData = 0;
+ if (_andyShootsCount != 0 && !_specialAnimFlag && (o->flags1 & 6) != 6 && (m->localVars[7] > 0 || m->localVars[7] < -1) && (m->flagsA5 & 0x80) == 0) {
+ for (int i = 0; i < _andyShootsCount; ++i) {
+ AndyShootData *p = &_andyShootsTable[i];
+ if (m->xDelta > 256 || m->yDelta > 192) {
+ continue;
+ }
+ _mstTemp_x1 = o->xPos;
+ _mstTemp_y1 = o->yPos;
+ _mstTemp_x2 = o->xPos + o->width - 1;
+ _mstTemp_y2 = o->yPos + o->height - 1;
+ const uint8_t _al = p->type;
+ if (_al == 1 || _al == 2) {
+ if (p->monsterDistance >= m->xDelta + m->yDelta) {
+ if (o->screenNum != _currentScreen) {
+ const int dx = _res->_mstPointOffsets[o->screenNum].xOffset - _res->_mstPointOffsets[_currentScreen].xOffset;
+ const int dy = _res->_mstPointOffsets[o->screenNum].yOffset - _res->_mstPointOffsets[_currentScreen].yOffset;
+ _mstTemp_x1 += dx;
+ _mstTemp_x2 += dx;
+ _mstTemp_y1 += dy;
+ _mstTemp_y2 += dy;
+ }
+ if (rect_intersects(0, 0, 255, 191, _mstTemp_x1, _mstTemp_y1, _mstTemp_x2, _mstTemp_y2)) {
+ const uint8_t type = m->monsterInfos[944];
+ if ((type == 9 && clipLvlObjectsSmall(p->o, o, 132)) || (type != 9 && clipLvlObjectsSmall(p->o, o, 20))) {
+ p->m = m;
+ p->monsterDistance = m->xDelta + m->yDelta;
+ p->clipX = _clipBoxOffsetX;
+ p->clipY = _clipBoxOffsetY;
+ }
+ }
+ if (o->screenNum != _currentScreen) {
+ const int dx = _res->_mstPointOffsets[_currentScreen].xOffset - _res->_mstPointOffsets[o->screenNum].xOffset;
+ const int dy = _res->_mstPointOffsets[_currentScreen].yOffset - _res->_mstPointOffsets[o->screenNum].yOffset;
+ _mstTemp_x1 += dx;
+ _mstTemp_x2 += dx;
+ _mstTemp_y1 += dy;
+ _mstTemp_y2 += dy;
+ }
+ }
+ } else if (_al == 3 && p->monsterDistance > m->xDelta + m->yDelta) {
+ if (o->screenNum != _currentScreen) {
+ const int dx = _res->_mstPointOffsets[o->screenNum].xOffset - _res->_mstPointOffsets[_currentScreen].xOffset;
+ const int dy = _res->_mstPointOffsets[o->screenNum].yOffset - _res->_mstPointOffsets[_currentScreen].yOffset;
+ _mstTemp_x1 += dx;
+ _mstTemp_x2 += dx;
+ _mstTemp_y1 += dy;
+ _mstTemp_y2 += dy;
+ }
+ if (rect_intersects(0, 0, 255, 191, _mstTemp_x1, _mstTemp_y1, _mstTemp_x2, _mstTemp_y2)) {
+ if (testPlasmaCannonPointsDirection(_mstTemp_x1, _mstTemp_y1, _mstTemp_x2, _mstTemp_y2)) {
+ p->monsterDistance = m->xDelta + m->yDelta;
+ p->m = m;
+ p->plasmaCannonPointsCount = _plasmaCannonLastIndex1;
+ }
+ }
+ const int dx = _res->_mstPointOffsets[_currentScreen].xOffset - _res->_mstPointOffsets[o->screenNum].xOffset;
+ const int dy = _res->_mstPointOffsets[_currentScreen].yOffset - _res->_mstPointOffsets[o->screenNum].yOffset;
+ _mstTemp_x1 += dx;
+ _mstTemp_x2 += dx;
+ _mstTemp_y1 += dy;
+ _mstTemp_y2 += dy;
+ }
+ _mstTemp_x1 += _res->_mstPointOffsets[o->screenNum].xOffset;
+ _mstTemp_y1 += _res->_mstPointOffsets[o->screenNum].yOffset;
+ _mstTemp_x2 += _res->_mstPointOffsets[o->screenNum].xOffset;
+ _mstTemp_y2 += _res->_mstPointOffsets[o->screenNum].yOffset;
+ int res = getMstDistance((m->monsterInfos[946] & 2) != 0 ? p->boundingBox.y2 : m->yMstPos, p);
+ if (res < 0) {
+ continue;
+ }
+ if (m->collideDistance == -1 || m->collideDistance > res || (m->collideDistance == 0 && res == 0 && (m->shootData->type & 1) == 0 && p->type == 2)) {
+ m->shootData = p;
+ m->collideDistance = res;
+ }
+ }
+ }
+ if (m->facingDirectionMask & kDirectionKeyMaskLeft) {
+ m->goalPosBounds_x1 = READ_LE_UINT32(ptr + 920);
+ m->goalPosBounds_x2 = READ_LE_UINT32(ptr + 924);
+ } else {
+ m->goalPosBounds_x1 = READ_LE_UINT32(ptr + 912);
+ m->goalPosBounds_x2 = READ_LE_UINT32(ptr + 916);
+ }
+ // m->unk90 =
+ // m->unk8C =
+ if (_andyShootsTable[0].type == 3 && m->shootSource != 0 && _andyShootsTable[0].directionMask == m->shootDirection && m->directionKeyMask == _andyObject->directionKeyMask) {
+ m->flags48 |= 0x80;
+ } else {
+ m->shootDirection = -1;
+ m->flags48 &= ~0x80;
+ }
+}
+
+void Game::shuffleMstMonsterActionIndex(MstMonsterActionIndex *p) {
+ for (uint32_t i = 0; i < p->dataCount; ++i) {
+ p->data[i] &= 0x7F;
+ }
+ shuffleArray(p->data, p->dataCount);
+}
+
+void Game::initMstCode() {
+ memset(_mstVars, 0, sizeof(_mstVars));
+ if (_mstDisabled) {
+ return;
+ }
+ // _mstLut initialization
+ resetMstCode();
+}
+
+void Game::resetMstCode() {
+ if (_mstDisabled) {
+ return;
+ }
+ _mstFlags = 0;
+ for (int i = 0; i < kMaxMonsterObjects1; ++i) {
+ mstMonster1ResetData(&_monsterObjects1Table[i]);
+ }
+ for (int i = 0; i < kMaxMonsterObjects2; ++i) {
+ mstMonster2ResetData(&_monsterObjects2Table[i]);
+ }
+ clearLvlObjectsList1();
+ for (int i = 0; i < _res->_mstHdr.screenAreaDataCount; ++i) {
+ _res->_mstScreenAreaData[i].unk0x1D = 1;
+ }
+ _rnd.initMstTable();
+ _rnd.initTable();
+ for (int i = 0; i < _res->_mstHdr.movingBoundsDataCount; ++i) {
+ const int count = _res->_mstMovingBoundsData[i].indexDataCount;
+ if (count != 0) {
+ shuffleArray(_res->_mstMovingBoundsData[i].indexData, count);
+ }
+ }
+ for (int i = 0; i < _res->_mstHdr.walkCodeDataCount; ++i) {
+ const int count = _res->_mstWalkCodeData[i].indexDataCount;
+ if (count != 0) {
+ shuffleArray(_res->_mstWalkCodeData[i].indexData, count);
+ }
+ }
+ for (int i = 0; i < _res->_mstHdr.monsterActionIndexDataCount; ++i) {
+ shuffleMstMonsterActionIndex(&_res->_mstMonsterActionIndexData[i]);
+ }
+ _mstOp67_x1 = -256;
+ _mstOp67_x2 = -256;
+ memset(_monsterObjects1Table, 0, sizeof(_monsterObjects1Table));
+ memset(_monsterObjects2Table, 0, sizeof(_monsterObjects2Table));
+ memset(_mstVars, 0, sizeof(_mstVars));
+ memset(_tasksTable, 0, sizeof(_tasksTable));
+ _m43Num3 = _m43Num1 = _m43Num2 = _mstActionNum = -1;
+ _mstOp54Counter = 0; // bugfix: not reset in the original, causes uninitialized reads at the beginning of 'fort'
+ _executeMstLogicPrevCounter = _executeMstLogicCounter = 0;
+ // _mstUnk8 = 0;
+ _specialAnimFlag = false;
+ _mstAndyRectNum = 0xFF;
+ _mstBoundingBoxesCount = 0;
+ _mstOp67_y1 = 0;
+ _mstOp67_y2 = 0;
+ _mstOp67_screenNum = -1;
+ _mstOp68_x1 = 256;
+ _mstOp68_x2 = 256;
+ _mstOp68_y1 = 0;
+ _mstOp68_y2 = 0;
+ _mstOp68_screenNum = -1;
+ _mstLevelGatesMask = 0;
+ // _mstLevelGatesTestMask = 0xFFFFFFFF;
+ _mstAndyVarMask = 0;
+ _tasksList = 0;
+ _monsterObjects1TasksList = 0;
+ _monsterObjects2TasksList = 0;
+ // _mstTasksList3 = 0;
+ // _mstTasksList4 = 0;
+ if (_res->_mstTickCodeData != kNone) {
+ _mstVars[31] = _mstTickDelay = _res->_mstTickDelay;
+ } else {
+ _mstVars[31] = -1;
+ }
+ _mstVars[30] = 0x20;
+ for (int i = 0; i < kMaxMonsterObjects1; ++i) {
+ _monsterObjects1Table[i].monster1Index = i;
+ }
+ for (int i = 0; i < kMaxMonsterObjects2; ++i) {
+ _monsterObjects2Table[i].monster2Index = i;
+ }
+ mstUpdateRefPos();
+ _mstAndyLevelPrevPosX = _mstAndyLevelPosX;
+ _mstAndyLevelPrevPosY = _mstAndyLevelPosY;
+}
+
+void Game::startMstCode() {
+ mstUpdateRefPos();
+ _mstAndyLevelPrevPosX = _mstAndyLevelPosX;
+ _mstAndyLevelPrevPosY = _mstAndyLevelPosY;
+ int offset = 0;
+ for (int i = 0; i < _res->_mstHdr.infoMonster1Count; ++i) {
+ offset += kMonsterInfoDataSize;
+ const uint32_t unk30 = READ_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x30]); // 900
+ const uint32_t unk34 = READ_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x34]); // 896
+
+ const uint32_t unk20 = _mstAndyLevelPosX - unk30;
+ const uint32_t unk1C = _mstAndyLevelPosX + unk30;
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x20], unk20);
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x1C], unk1C);
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x24], unk20 - unk34);
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x18], unk1C + unk34);
+
+ const uint32_t unk10 = _mstAndyLevelPosY - unk30;
+ const uint32_t unk0C = _mstAndyLevelPosY + unk30;
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x10], unk10);
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x0C], unk0C);
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x14], unk10 - unk34);
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x08], unk0C + unk34);
+ }
+ if (_level->_checkpoint < _res->_mstHdr.levelCheckpointCodeDataCount) {
+ const uint32_t codeData = _res->_mstLevelCheckpointCodeData[_level->_checkpoint];
+ if (codeData != kNone) {
+ Task *t = createTask(_res->_mstCodeData + codeData * 4);
+ if (t) {
+ _runTaskOpcodesCount = 0;
+ while ((this->*(t->run))(t) == 0);
+ }
+ }
+ }
+}
+
+static bool compareOp(int op, int num1, int num2) {
+ switch (op) {
+ case 0:
+ return num1 != num2;
+ case 1:
+ return num1 == num2;
+ case 2:
+ return num1 >= num2;
+ case 3:
+ return num1 > num2;
+ case 4:
+ return num1 <= num2;
+ case 5:
+ return num1 < num2;
+ case 6:
+ return (num1 & num2) == 0;
+ case 7:
+ return (num1 | num2) == 0;
+ case 8:
+ return (num1 ^ num2) == 0;
+ default:
+ error("compareOp unhandled op %d", op);
+ break;
+ }
+ return false;
+}
+
+static void arithOp(int op, int *p, int num) {
+ switch (op) {
+ case 0:
+ *p = num;
+ break;
+ case 1:
+ *p += num;
+ break;
+ case 2:
+ *p -= num;
+ break;
+ case 3:
+ *p *= num;
+ break;
+ case 4:
+ if (num != 0) {
+ *p /= num;
+ }
+ break;
+ case 5:
+ *p <<= num;
+ break;
+ case 6:
+ *p >>= num;
+ break;
+ case 7:
+ *p &= num;
+ break;
+ case 8:
+ *p |= num;
+ break;
+ case 9:
+ *p ^= num;
+ break;
+ default:
+ error("arithOp unhandled op %d", op);
+ break;
+ }
+}
+
+void Game::executeMstCode() {
+ _andyActionKeyMaskAnd = 0xFF;
+ _andyDirectionKeyMaskAnd = 0xFF;
+ _andyActionKeyMaskOr = 0;
+ _andyDirectionKeyMaskOr = 0;
+ if (_mstDisabled) {
+ return;
+ }
+ ++_executeMstLogicCounter;
+ if (_mstLevelGatesMask != 0) {
+ _mstHelper1Count = 0;
+ executeMstCodeHelper1();
+ _mstLevelGatesMask = 0;
+ }
+ for (int i = 0; i < kMaxAndyShoots; ++i) {
+ _andyShootsTable[i].shootObjectData = 0;
+ _andyShootsTable[i].m = 0;
+ _andyShootsTable[i].monsterDistance = 0x1000000;
+ }
+ mstUpdateMonster1ObjectsPosition();
+ if (_mstVars[31] > 0) {
+ --_mstVars[31];
+ if (_mstVars[31] == 0) {
+ uint32_t codeData = _res->_mstTickCodeData;
+ assert(codeData != kNone);
+ Task *t = createTask(_res->_mstCodeData + codeData * 4);
+ if (t) {
+ t->state = 1;
+ }
+ }
+ }
+ const MstScreenArea *msac;
+ while ((msac = _res->findMstCodeForPos(_currentScreen, _mstAndyLevelPosX, _mstAndyLevelPosY)) != 0) {
+ debug(kDebug_MONSTER, "trigger for %d,%d", _mstAndyLevelPosX, _mstAndyLevelPosY);
+ _res->flagMstCodeForPos(msac->unk0x1C, 0);
+ assert(msac->codeData != kNone);
+ createTask(_res->_mstCodeData + msac->codeData * 4);
+ }
+ if (_mstAndyCurrentScreenNum != _currentScreen) {
+ _mstAndyCurrentScreenNum = _currentScreen;
+ }
+ for (Task *t = _tasksList; t; t = t->nextPtr) {
+ _runTaskOpcodesCount = 0;
+ while ((this->*(t->run))(t) == 0);
+ }
+ for (int i = 0; i < _andyShootsCount; ++i) {
+ AndyShootData *p = &_andyShootsTable[i];
+ MonsterObject1 *m = p->m;
+ if (!m) {
+ continue;
+ }
+ const int energy = m->localVars[7];
+ ShootLvlObjectData *dat = p->shootObjectData;
+ if (dat) {
+ if (energy > 0) {
+ if (_cheats & kCheatOneHitSpecialPowers) {
+ m->localVars[7] = 0;
+ } else if (p->type == 2) {
+ m->localVars[7] -= 4;
+ if (m->localVars[7] < 0) {
+ m->localVars[7] = 0;
+ }
+ } else {
+ --m->localVars[7];
+ }
+ dat->unk3 = 0x80;
+ dat->xPosShoot = p->clipX;
+ dat->yPosShoot = p->clipY;
+ dat->o = m->o16;
+ } else if (energy == -2) {
+ dat->unk3 = 0x80;
+ dat->xPosShoot = p->clipX;
+ dat->yPosShoot = p->clipY;
+ }
+ continue;
+ }
+ if (energy > 0) {
+ if (_cheats & kCheatOneHitPlasmaCannon) {
+ m->localVars[7] = 0;
+ } else {
+ m->localVars[7] = energy - 1;
+ }
+ _plasmaCannonLastIndex1 = p->plasmaCannonPointsCount;
+ } else if (energy == -2) {
+ _plasmaCannonLastIndex1 = p->plasmaCannonPointsCount;
+ } else {
+ _plasmaCannonLastIndex1 = 0;
+ }
+ }
+ for (Task *t = _monsterObjects1TasksList; t; t = t->nextPtr) {
+ _runTaskOpcodesCount = 0;
+ if (mstUpdateTaskMonsterObject1(t) == 0) {
+ while ((this->*(t->run))(t) == 0);
+ }
+ }
+ for (Task *t = _monsterObjects2TasksList; t; t = t->nextPtr) {
+ _runTaskOpcodesCount = 0;
+ if (mstUpdateTaskMonsterObject2(t) == 0) {
+ while ((this->*(t->run))(t) == 0);
+ }
+ }
+}
+
+void Game::mstWalkPathUpdateIndex(MstWalkPath *walkPath, int index) {
+ uint32_t _walkNodesTable[32];
+ int _walkNodesFirstIndex, _walkNodesLastIndex;
+ int32_t buffer[64];
+ for (uint32_t i = 0; i < walkPath->count; ++i) {
+ MstWalkNode *walkNode = &walkPath->data[i];
+ memset(buffer, 0xFF, sizeof(buffer));
+ memset(walkNode->unk60[index], 0, walkPath->count);
+ _walkNodesTable[0] = i;
+ _walkNodesLastIndex = 1;
+ buffer[i] = 0;
+ _walkNodesFirstIndex = 0;
+ while (_walkNodesFirstIndex != _walkNodesLastIndex) {
+ uint32_t vf = _walkNodesTable[_walkNodesFirstIndex];
+ ++_walkNodesFirstIndex;
+ if (_walkNodesFirstIndex >= 32) {
+ _walkNodesFirstIndex = 0;
+ }
+ const uint32_t indexWalkBox = walkPath->data[vf].walkBox;
+ const MstWalkBox *m34 = &_res->_mstWalkBoxData[indexWalkBox];
+ for (int j = 0; j < 4; ++j) {
+ const uint32_t indexWalkNode = walkPath->data[vf].neighborWalkNode[j];
+ if (indexWalkNode == kNone) {
+ continue;
+ }
+ assert(indexWalkNode < walkPath->count);
+ uint32_t mask;
+ const uint8_t flags = m34->flags[j];
+ if (flags & 0x80) {
+ mask = _mstAndyVarMask & (1 << (flags & 0x7F));
+ } else {
+ mask = flags & (1 << index);
+ }
+ if (mask != 0) {
+ continue;
+ }
+ int delta;
+ if (j == 0 || j == 1) {
+ delta = m34->right - m34->left + buffer[vf];
+ } else {
+ delta = m34->bottom - m34->top + buffer[vf];
+ }
+ if (delta >= buffer[i]) {
+ continue;
+ }
+ if (buffer[indexWalkNode] == -1) {
+ _walkNodesTable[_walkNodesLastIndex] = indexWalkNode;
+ ++_walkNodesLastIndex;
+ if (_walkNodesLastIndex >= 32) {
+ _walkNodesLastIndex = 0;
+ }
+ }
+ buffer[i] = delta;
+ uint8_t value;
+ if (vf == i) {
+ static const uint8_t directions[] = { 2, 8, 4, 1 };
+ value = directions[j];
+ } else {
+ value = walkNode->unk60[index][vf];
+ }
+ walkNode->unk60[index][i] = value;
+ }
+ }
+ }
+}
+
+int Game::mstWalkPathUpdateWalkNode(MstWalkPath *walkPath, MstWalkNode *walkNode, int num, int index) {
+ if (walkNode->coords[num][index] < 0) {
+ const uint32_t indexWalkBox = walkNode->walkBox;
+ const MstWalkBox *m34 = &_res->_mstWalkBoxData[indexWalkBox];
+ uint32_t mask;
+ const uint8_t flags = m34->flags[num];
+ if (flags & 0x80) {
+ mask = _mstAndyVarMask & (1 << (flags & 0x7F));
+ } else {
+ mask = flags & (1 << index);
+ }
+ if (mask) {
+ switch (num) { // ((int *)&m34)[num]
+ case 0:
+ walkNode->coords[num][index] = m34->right;
+ break;
+ case 1:
+ walkNode->coords[num][index] = m34->left;
+ break;
+ case 2:
+ walkNode->coords[num][index] = m34->bottom;
+ break;
+ case 3:
+ walkNode->coords[num][index] = m34->top;
+ break;
+ }
+ } else {
+ const uint32_t indexWalkNode = walkNode->neighborWalkNode[num];
+ assert(indexWalkNode != kNone && indexWalkNode < walkPath->count);
+ walkNode->coords[num][index] = mstWalkPathUpdateWalkNode(walkPath, &walkPath->data[indexWalkNode], num, index);
+ }
+ }
+ return walkNode->coords[num][index];
+}
+
+void Game::executeMstCodeHelper1() {
+ for (int i = 0; i < _res->_mstHdr.walkPathDataCount; ++i) {
+ MstWalkPath *walkPath = &_res->_mstWalkPathData[i];
+ if (walkPath->mask & _mstLevelGatesMask) {
+ ++_mstHelper1Count;
+ for (uint32_t j = 0; j < walkPath->count; ++j) {
+ for (int k = 0; k < 2; ++k) {
+ walkPath->data[j].coords[0][k] = -1;
+ walkPath->data[j].coords[1][k] = -1;
+ walkPath->data[j].coords[2][k] = -1;
+ walkPath->data[j].coords[3][k] = -1;
+ }
+ }
+ for (uint32_t j = 0; j < walkPath->count; ++j) {
+ MstWalkNode *walkNode = &walkPath->data[j];
+ for (int k = 0; k < 4; ++k) {
+ mstWalkPathUpdateWalkNode(walkPath, walkNode, k, 0);
+ mstWalkPathUpdateWalkNode(walkPath, walkNode, k, 1);
+ }
+ }
+ mstWalkPathUpdateIndex(walkPath, 0);
+ mstWalkPathUpdateIndex(walkPath, 1);
+ }
+ }
+ if (_mstHelper1Count != 0) {
+ for (int i = 0; i < kMaxMonsterObjects1; ++i) {
+ MonsterObject1 *m = &_monsterObjects1Table[i];
+ if (!m->m46) {
+ continue;
+ }
+ MstWalkNode *walkNode = m->walkNode;
+ const int num = (~m->flagsA5) & 1;
+ m->levelPosBounds_x2 = walkNode->coords[0][num] - (int32_t)READ_LE_UINT32(m->monsterInfos + 904);
+ m->levelPosBounds_x1 = walkNode->coords[1][num] + (int32_t)READ_LE_UINT32(m->monsterInfos + 904);
+ m->levelPosBounds_y2 = walkNode->coords[2][num] - (int32_t)READ_LE_UINT32(m->monsterInfos + 908);
+ m->levelPosBounds_y1 = walkNode->coords[3][num] + (int32_t)READ_LE_UINT32(m->monsterInfos + 908);
+ }
+ }
+}
+
+void Game::mstUpdateMonster1ObjectsPosition() {
+ mstUpdateRefPos();
+ mstUpdateMonstersRect();
+ for (Task *t = _monsterObjects1TasksList; t; t = t->nextPtr) {
+ mstTaskUpdateScreenPosition(t);
+ }
+}
+
+void Game::mstLvlObjectSetActionDirection(LvlObject *o, const uint8_t *ptr, uint8_t mask1, uint8_t dirMask2) {
+ o->actionKeyMask = ptr[1];
+ o->directionKeyMask = mask1 & 15;
+ MonsterObject1 *m = (MonsterObject1 *)getLvlObjectDataPtr(o, kObjectDataTypeMonster1);
+ if ((mask1 & 0x10) == 0) {
+ const int vf = mask1 & 0xE0;
+ switch (vf) {
+ case 32:
+ case 96:
+ case 160:
+ case 192:
+ if (vf == 192) {
+ o->directionKeyMask |= (m->facingDirectionMask & ~kDirectionKeyMaskVertical);
+ } else {
+ o->directionKeyMask |= m->facingDirectionMask;
+ if (m->monsterInfos[946] & 2) {
+ if (vf == 160 && (_mstLut1[o->directionKeyMask] & 1) != 0) {
+ if (m->xDelta >= m->yDelta) {
+ o->directionKeyMask &= ~kDirectionKeyMaskVertical;
+ } else {
+ o->directionKeyMask &= ~kDirectionKeyMaskHorizontal;
+ }
+ } else {
+ if (m->xDelta >= m->yDelta * 2) {
+ o->directionKeyMask &= ~kDirectionKeyMaskVertical;
+ } else if (m->yDelta >= m->xDelta * 2) {
+ o->directionKeyMask &= ~kDirectionKeyMaskHorizontal;
+ }
+ }
+ }
+ }
+ break;
+ case 128:
+ o->directionKeyMask |= dirMask2;
+ if ((m->monsterInfos[946] & 2) != 0 && (_mstLut1[o->directionKeyMask] & 1) != 0) {
+ if (m->xDelta >= m->yDelta) {
+ o->directionKeyMask &= ~kDirectionKeyMaskVertical;
+ } else {
+ o->directionKeyMask &= ~kDirectionKeyMaskHorizontal;
+ }
+ }
+ break;
+ case 224:
+ o->directionKeyMask |= m->unkF8;
+ break;
+ default:
+ o->directionKeyMask |= dirMask2;
+ break;
+ }
+ }
+ o->directionKeyMask &= ptr[2];
+ if ((mask1 & 0xE0) == 0x40) {
+ o->directionKeyMask ^= kDirectionKeyMaskHorizontal;
+ }
+}
+
+void Game::mstMonster1UpdateGoalPosition(MonsterObject1 *m) {
+ int var1C = 0;
+ int var18 = 0;
+ debug(kDebug_MONSTER, "mstMonster1UpdateGoalPosition m %p goalScreen %d levelBounds [%d,%d,%d,%d]", m, m->goalScreenNum, m->levelPosBounds_x1, m->levelPosBounds_y1, m->levelPosBounds_x2, m->levelPosBounds_y2);
+ if (m->goalScreenNum == 0xFD) {
+ int ve, vf, vg, va;
+ const MstMovingBounds *m49 = m->m49;
+ const int xPos_1 = _mstAndyLevelPosX + m49->unk14 - m->goalDistance_x2;
+ const int xPos_2 = _mstAndyLevelPosX - m49->unk14 + m->goalDistance_x2;
+ const int yPos_1 = _mstAndyLevelPosY + m49->unk15 - m->goalDistance_y2;
+ const int yPos_2 = _mstAndyLevelPosY + m49->unk15 + m->goalDistance_y2;
+ if (m->levelPosBounds_x2 > xPos_1 && m->levelPosBounds_x1 < xPos_2 && m->levelPosBounds_y2 > yPos_1 && m->levelPosBounds_y1 < yPos_2) {
+ const int xPos_3 = _mstAndyLevelPosX + m49->unk14 + m->goalDistance_x1;
+ const int xPos_4 = _mstAndyLevelPosX - m49->unk14 - m->goalDistance_x1;
+ const int yPos_3 = _mstAndyLevelPosY + m49->unk15 + m->goalDistance_y1;
+ const int yPos_4 = _mstAndyLevelPosY - m49->unk15 - m->goalDistance_y1;
+ if (m->levelPosBounds_x2 < xPos_3) {
+ if (m->levelPosBounds_x1 > xPos_4 && m->levelPosBounds_y2 < yPos_3 && m->levelPosBounds_y1 > yPos_4) {
+ goto l41B2DC;
+ }
+ }
+ var18 = 0;
+ ve = 0x40000000;
+ if (m->levelPosBounds_x2 >= _mstAndyLevelPosX + m->goalDistance_x1 + m49->unk14) {
+ if (m->xMstPos > _mstAndyLevelPosX + m->goalDistance_x2) {
+ ve = m->xMstPos - m->goalDistance_x1 - _mstAndyLevelPosX;
+ if (ve >= 0 && ve < m49->unk14) {
+ ve = 0x40000000;
+ } else {
+ var18 = 1;
+ }
+ } else if (m->xMstPos >= _mstAndyLevelPosX + m->goalDistance_x1) {
+ ve = -1;
+ var18 = 1;
+ } else {
+ ve = m->goalDistance_x2 - m->xMstPos + _mstAndyLevelPosX;
+ if (ve >= 0 && ve < m49->unk14) {
+ ve = 0x40000000;
+ } else {
+ var18 = 1;
+ }
+ }
+ }
+ vf = 0x40000000;
+ if (m->levelPosBounds_x1 <= _mstAndyLevelPosX - m->goalDistance_x1 - m49->unk14) {
+ if (m->xMstPos > _mstAndyLevelPosX - m->goalDistance_x1) {
+ vf = m->xMstPos - _mstAndyLevelPosX + m->goalDistance_x2;
+ if (vf >= 0 && vf < m49->unk14) {
+ vf = 0x40000000;
+ } else {
+ var18 |= 2;
+ }
+ } else if (m->xMstPos >= _mstAndyLevelPosX - m->goalDistance_x2) {
+ vf = -1;
+ var18 |= 2;
+ } else {
+ vf = _mstAndyLevelPosX - m->goalDistance_x1 - m->xMstPos;
+ if (vf >= 0 && vf < m49->unk14) {
+ vf = 0x40000000;
+ } else {
+ var18 |= 2;
+ }
+ }
+ }
+ vg = 0x40000000;
+ if (m->levelPosBounds_y2 >= _mstAndyLevelPosY + m->goalDistance_y1 + m49->unk15) {
+ if (m->yMstPos > _mstAndyLevelPosY + m->goalDistance_y2) {
+ vg = m->yMstPos - m->goalDistance_y1 - _mstAndyLevelPosY;
+ if (vg >= 0 && vg < m49->unk15) {
+ vg = 0x40000000;
+ } else {
+ var18 |= 4;
+ }
+ } else if (m->yMstPos >= _mstAndyLevelPosY + m->goalDistance_y1) {
+ vg = -1;
+ var18 |= 4;
+ } else {
+ vg = m->goalDistance_y2 - m->yMstPos + _mstAndyLevelPosY;
+ if (vg >= 0 && vg < m49->unk15) {
+ vg = 0x40000000;
+ } else {
+ var18 |= 4;
+ }
+ }
+ }
+ va = 0x40000000;
+ if (m->levelPosBounds_y1 <= _mstAndyLevelPosY - m->goalDistance_y1 - m49->unk15) {
+ if (m->yMstPos > _mstAndyLevelPosY - m->goalDistance_y1) {
+ va = m->yMstPos - _mstAndyLevelPosY + m->goalDistance_y2;
+ if (va >= 0 && va < m49->unk15) {
+ va = 0x40000000;
+ } else {
+ var18 |= 8;
+ }
+ } else if (m->yMstPos >= _mstAndyLevelPosY - m->goalDistance_y2) {
+ va = -1;
+ var18 |= 8;
+ } else {
+ va = _mstAndyLevelPosY - m->goalDistance_y1 - m->yMstPos;
+ if (va >= 0 && va < m49->unk15) {
+ va = 0x40000000;
+ } else {
+ var18 |= 8;
+ }
+ }
+ }
+ if (var18 == 0) {
+ var18 = 15;
+ }
+ } else {
+l41B2DC:
+ ve = ABS(m->xMstPos - _mstAndyLevelPosX + m49->unk14 - m->goalDistance_x2);
+ vf = ABS(m->xMstPos - _mstAndyLevelPosX - m49->unk14 + m->goalDistance_x2);
+ vg = ABS(m->yMstPos - _mstAndyLevelPosY + m49->unk15 - m->goalDistance_y2);
+ va = ABS(m->yMstPos - _mstAndyLevelPosY - m49->unk15 + m->goalDistance_y2);
+ var18 = 15;
+ }
+ if (var18 == m->unkE4) {
+ var1C = m->unkE5;
+ } else {
+ switch (var18 & 3) {
+ case 0:
+ var1C = (vg > va) ? 0x21 : 0x20;
+ break;
+ case 2:
+ if (vf <= va && vf <= vg) {
+ var1C = 0x12;
+ } else {
+ var1C = (vg >= va) ? 0x21 : 0x20;
+ }
+ break;
+ case 3:
+ if (ve > vf) {
+ if (vf <= va && vf <= vg) {
+ var1C = 0x12;
+ } else {
+ var1C = (vg >= va) ? 0x21 : 0x20;
+ }
+ break;
+ }
+ // fall-through
+ case 1:
+ if (ve <= va && ve <= vg) {
+ var1C = 0x02;
+ } else {
+ var1C = (vg >= va) ? 0x21 : 0x20;
+ }
+ break;
+ }
+ m->unkE4 = var18;
+ m->unkE5 = var1C;
+ }
+ } else {
+ var1C = 0;
+ }
+ switch (var1C & 0xF0) {
+ case 0x00:
+ m->goalPos_x1 = m->goalDistance_x1;
+ m->goalPos_x2 = m->goalDistance_x2;
+ break;
+ case 0x10:
+ m->goalPos_x1 = -m->goalDistance_x2;
+ m->goalPos_x2 = -m->goalDistance_x1;
+ break;
+ case 0x20:
+ m->goalPos_x1 = -m->goalDistance_x2;
+ m->goalPos_x2 = m->goalDistance_x2;
+ break;
+ }
+ switch (var1C & 0xF) {
+ case 0:
+ m->goalPos_y1 = m->goalDistance_y1;
+ m->goalPos_y2 = m->goalDistance_y2;
+ break;
+ case 1:
+ m->goalPos_y1 = -m->goalDistance_y2;
+ m->goalPos_y2 = -m->goalDistance_y1;
+ break;
+ case 2:
+ m->goalPos_y1 = -m->goalDistance_y2;
+ m->goalPos_y2 = m->goalDistance_y2;
+ break;
+ }
+ m->goalPos_x1 += _mstAndyLevelPosX;
+ m->goalPos_x2 += _mstAndyLevelPosX;
+ m->goalPos_y1 += _mstAndyLevelPosY;
+ m->goalPos_y2 += _mstAndyLevelPosY;
+ const uint8_t *ptr = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ if ((ptr[2] & kDirectionKeyMaskVertical) == 0) {
+ m->goalDistance_y1 = m->goalPos_y1 = m->goalDistance_y2 = m->goalPos_y2 = m->yMstPos;
+ }
+ if ((ptr[2] & kDirectionKeyMaskHorizontal) == 0) {
+ m->goalDistance_x1 = m->goalPos_x1 = m->goalDistance_x2 = m->goalPos_x2 = m->xMstPos;
+ }
+ debug(kDebug_MONSTER, "mstMonster1UpdateGoalPosition m %p mask 0x%x goal [%d,%d,%d,%d]", m, var1C, m->goalPos_x1, m->goalPos_y1, m->goalPos_x2, m->goalPos_y2);
+}
+
+void Game::mstMonster1MoveTowardsGoal1(MonsterObject1 *m) {
+ MstWalkNode *walkNode = m->walkNode;
+ const uint8_t *p = m->monsterInfos;
+ const uint32_t indexWalkBox = walkNode->walkBox;
+ const MstWalkBox *m34 = &_res->_mstWalkBoxData[indexWalkBox];
+ const int w = (int32_t)READ_LE_UINT32(p + 904);
+ const int h = (int32_t)READ_LE_UINT32(p + 908);
+ debug(kDebug_MONSTER, "mstMonster1MoveTowardsGoal1 m %p pos %d,%d [%d,%d,%d,%d]", m, m->xMstPos, m->yMstPos, m34->left, m34->top, m34->right, m34->bottom);
+ if (!rect_contains(m34->left - w, m34->top - h, m34->right + w, m34->bottom + h, m->xMstPos, m->yMstPos)) {
+ mstMonster1UpdateWalkPath(m);
+ m->unkC0 = -1;
+ m->unkBC = -1;
+ }
+ m->task->flags &= ~0x80;
+ if (m->xMstPos < m->goalPos_x1) {
+ int x = m->goalPos_x2;
+ _xMstPos1 = x;
+ if (x > m->levelPosBounds_x2) {
+ x = m->levelPosBounds_x2;
+ m->task->flags |= 0x80;
+ }
+ m->goalDirectionMask = kDirectionKeyMaskRight;
+ _xMstPos2 = x - m->xMstPos;
+ } else if (m->xMstPos > m->goalPos_x2) {
+ int x = m->goalPos_x1;
+ _xMstPos1 = x;
+ if (x < m->levelPosBounds_x1) {
+ x = m->levelPosBounds_x1;
+ m->task->flags |= 0x80;
+ }
+ m->goalDirectionMask = kDirectionKeyMaskLeft;
+ _xMstPos2 = m->xMstPos - x;
+ } else {
+ _xMstPos1 = m->xMstPos;
+ m->goalDirectionMask = 0;
+ _xMstPos2 = 0;
+ }
+ if (m->yMstPos < m->goalPos_y1) {
+ int y = m->goalPos_y2;
+ _yMstPos1 = y;
+ if (y > m->levelPosBounds_y2) {
+ y = m->levelPosBounds_y2;
+ m->task->flags |= 0x80;
+ }
+ _yMstPos2 = y - m->yMstPos;
+ m->goalDirectionMask |= kDirectionKeyMaskDown;
+ } else if (m->yMstPos > m->goalPos_y2) {
+ int y = m->goalPos_y1;
+ _yMstPos1 = y;
+ if (y < m->levelPosBounds_y1) {
+ y = m->levelPosBounds_y1;
+ m->task->flags |= 0x80;
+ }
+ _yMstPos2 = m->yMstPos - y;
+ m->goalDirectionMask |= kDirectionKeyMaskUp;
+ } else {
+ _yMstPos1 = m->yMstPos;
+ _yMstPos2 = 0;
+ }
+ debug(kDebug_MONSTER, "mstMonster1MoveTowardsGoal1 m %p pos %d,%d dist %d,%d direction 0x%x", m, _xMstPos1, _yMstPos1, _xMstPos2, _yMstPos2, m->goalDirectionMask);
+ if (m->goalDirectionMask == 0) {
+ return;
+ }
+ MstBehaviorState *behaviorState = m->behaviorState;
+ MstWalkPath *walkPath = &_res->_mstWalkPathData[behaviorState->walkPath];
+ uint8_t _cl = m->walkBoxNum;
+ if (m->unkBC != _xMstPos1 || m->unkC0 != _yMstPos1) {
+ bool inWalkBox = false;
+ if (_cl < walkPath->count) {
+ MstWalkNode *walkNode = &walkPath->data[_cl];
+ const MstWalkBox *m34 = &_res->_mstWalkBoxData[walkNode->walkBox];
+ if (rect_contains(m34->left, m34->top, m34->right, m34->bottom, _xMstPos1, _yMstPos1)) {
+ inWalkBox = true;
+
+ }
+ }
+ if (!inWalkBox) {
+ const int _al = mstMonster1FindWalkPathRect(m, walkPath, _xMstPos1, _yMstPos1);
+ if (_al < 0) {
+ _xMstPos1 = _xMstPos3;
+ _yMstPos1 = _yMstPos3;
+ _cl = -1 - _al;
+ m->goalPos_x1 = m->goalPos_x2 = _xMstPos1;
+ m->goalPos_y1 = m->goalPos_y2 = _yMstPos1;
+ _xMstPos2 = ABS(m->xMstPos - _xMstPos1);
+ _yMstPos2 = ABS(m->yMstPos - _yMstPos1);
+ if (m->xMstPos < m->goalPos_x1) {
+ m->goalDirectionMask = kDirectionKeyMaskRight;
+ } else if (m->xMstPos > m->goalPos_x2) {
+ m->goalDirectionMask = kDirectionKeyMaskLeft;
+ }
+ if (m->yMstPos < m->goalPos_y1) {
+ m->goalDirectionMask |= kDirectionKeyMaskDown;
+ } else if (m->yMstPos > m->goalPos_y2) {
+ m->goalDirectionMask |= kDirectionKeyMaskUp;
+ }
+ } else {
+ _cl = _al;
+ }
+ m->walkBoxNum = _cl;
+ m->unkBC = -1;
+ m->unkC0 = -1;
+ }
+ }
+ if (_cl >= walkPath->count || m->walkNode == &walkPath->data[_cl]) {
+ m->targetDirectionMask = 0xFF;
+ return;
+ }
+ const int vf = (~m->flagsA5) & 1;
+ if (m->unkBC == _xMstPos1 && m->unkC0 == _yMstPos1) {
+ if (m->targetDirectionMask == 0xFF) {
+ return;
+ }
+ m->goalDirectionMask = m->targetDirectionMask;
+ } else {
+ m->unkBC = _xMstPos1;
+ m->unkC0 = _yMstPos1;
+ const uint32_t indexWalkPath = m->behaviorState->walkPath;
+ MstWalkPath *walkPath = &_res->_mstWalkPathData[indexWalkPath];
+ const MstWalkNode *walkNode = m->walkNode;
+ const uint8_t dirMask = walkNode->unk60[vf][_cl];
+ if (dirMask != 0) {
+ const uint8_t *p = m->monsterInfos;
+ const int w = (int32_t)READ_LE_UINT32(p + 904);
+ const int h = (int32_t)READ_LE_UINT32(p + 908);
+ while (1) {
+ const int x1 = walkNode->coords[1][vf] + w; // left
+ const int x2 = walkNode->coords[0][vf] - w; // right
+ const int y1 = walkNode->coords[3][vf] + h; // top
+ const int y2 = walkNode->coords[2][vf] - h; // bottom
+ if (!rect_contains(x1, y1, x2, y2, _xMstPos1, _yMstPos1)) {
+ break;
+ }
+ int var1C;
+ switch (walkNode->unk60[vf][_cl]) {
+ case 2:
+ var1C = 0;
+ break;
+ case 4:
+ var1C = 2;
+ break;
+ case 8:
+ var1C = 1;
+ break;
+ default: // 1
+ var1C = 3;
+ break;
+ }
+ const uint32_t indexWalkNode = walkNode->neighborWalkNode[var1C];
+ assert(indexWalkNode != kNone && indexWalkNode < walkPath->count);
+ walkNode = &walkPath->data[indexWalkNode];
+ if (walkNode == &walkPath->data[_cl]) {
+ m->targetDirectionMask = 0xFF;
+ return;
+ }
+ }
+ m->goalDirectionMask = dirMask;
+ }
+ m->targetDirectionMask = m->goalDirectionMask;
+ }
+ if (m->goalDirectionMask & kDirectionKeyMaskLeft) {
+ _xMstPos2 = m->xMstPos - m->walkNode->coords[1][vf] - (int32_t)READ_LE_UINT32(m->monsterInfos + 904);
+ } else if (m->goalDirectionMask & kDirectionKeyMaskRight) {
+ _xMstPos2 = m->walkNode->coords[0][vf] - (int32_t)READ_LE_UINT32(m->monsterInfos + 904) - m->xMstPos;
+ } else {
+ _xMstPos2 = 0;
+ }
+ if (m->goalDirectionMask & kDirectionKeyMaskUp) {
+ _yMstPos2 = m->yMstPos - m->walkNode->coords[3][vf] - (int32_t)READ_LE_UINT32(m->monsterInfos + 908);
+ } else if (m->goalDirectionMask & kDirectionKeyMaskDown) {
+ _yMstPos2 = m->walkNode->coords[2][vf] - (int32_t)READ_LE_UINT32(m->monsterInfos + 908) - m->yMstPos;
+ } else {
+ _yMstPos2 = 0;
+ }
+ debug(kDebug_MONSTER, "mstMonster1MoveTowardsGoal1 m %p dist %d,%d", m, _xMstPos2, _yMstPos2);
+}
+
+bool Game::mstMonster1TestGoalDirection(MonsterObject1 *m) {
+ if (_mstLut1[m->goalDirectionMask] & 1) {
+ if (_xMstPos2 < m->m49->unk14) {
+ m->goalDirectionMask &= ~kDirectionKeyMaskHorizontal;
+ }
+ if (_yMstPos2 < m->m49->unk15) {
+ m->goalDirectionMask &= ~kDirectionKeyMaskVertical;
+ }
+ }
+ if (_mstLut1[m->goalDirectionMask] & 1) {
+ while (--m->indexUnk49Unk1 >= 0) {
+ m->m49Unk1 = &m->m49->data1[m->indexUnk49Unk1];
+ if (_xMstPos2 >= m->m49Unk1->unkE && _yMstPos2 >= m->m49Unk1->unkF) {
+ return true;
+ }
+ }
+ } else {
+ while (--m->indexUnk49Unk1 >= 0) {
+ m->m49Unk1 = &m->m49->data1[m->indexUnk49Unk1];
+ if (((m->goalDirectionMask & kDirectionKeyMaskHorizontal) == 0 || _xMstPos2 >= m->m49Unk1->unkC) && ((m->goalDirectionMask & kDirectionKeyMaskVertical) == 0 || _yMstPos2 >= m->m49Unk1->unkD)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void Game::mstMonster1MoveTowardsGoal2(MonsterObject1 *m) {
+ if (m->targetLevelPos_x != -1) {
+ if (m->xMstPos != m->targetLevelPos_x || m->yMstPos != m->targetLevelPos_y) {
+ _xMstPos2 = m->previousDxPos;
+ _yMstPos2 = m->previousDyPos;
+ return;
+ }
+ mstBoundingBoxClear(m, 1);
+ }
+ mstMonster1MoveTowardsGoal1(m);
+ if (m->goalDirectionMask == 0) {
+ return;
+ }
+ m->previousDxPos = _xMstPos2;
+ m->previousDyPos = _yMstPos2;
+ const MstMovingBoundsUnk1 *m49Unk1 = m->m49Unk1;
+ uint8_t xDist, yDist;
+ if (_mstLut1[m->goalDirectionMask] & 1) {
+ xDist = m49Unk1->unkA;
+ yDist = m49Unk1->unkB;
+ } else {
+ xDist = m49Unk1->unk8;
+ yDist = m49Unk1->unk9;
+ }
+ if (_xMstPos2 < xDist && _yMstPos2 < yDist) {
+ if ((_xMstPos2 <= 0 && _yMstPos2 <= 0) || !mstMonster1TestGoalDirection(m)) {
+ return;
+ }
+ }
+ if (_mstLut1[m->goalDirectionMask] & 1) {
+ if (_xMstPos2 < _yMstPos2) {
+ if (_xMstPos2 < m->m49Unk1->unkA) {
+ m->goalDirectionMask &= ~kDirectionKeyMaskHorizontal;
+ if (m->targetDirectionMask != 0xFF) {
+ _xMstPos2 = 0;
+ }
+ }
+ } else {
+ if (_yMstPos2 < m->m49Unk1->unkB) {
+ m->goalDirectionMask &= ~kDirectionKeyMaskVertical;
+ if (m->targetDirectionMask != 0xFF) {
+ _yMstPos2 = 0;
+ }
+ }
+ }
+ }
+ int var10 = (~m->flagsA5) & 1;
+ const uint32_t indexWalkBox = m->walkNode->walkBox;
+ const MstWalkBox *m34 = &_res->_mstWalkBoxData[indexWalkBox];
+ int bboxIndex = 0;
+ int var8 = _mstLut1[m->goalDirectionMask];
+ for (int var20 = 0; var20 < 5; ++var20) {
+ const uint8_t *p = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ if (var20 != 0) {
+ if (p[0xE] == 0 || bboxIndex == 0) {
+ break;
+ }
+ }
+ const int num = var20 + var8 * 5;
+ const int dirMask = _mstLut3[num];
+ if (_mstLut1[dirMask] == m->unkAB) {
+ continue;
+ }
+ int vc, va;
+ if (_mstLut1[dirMask] & 1) {
+ vc = (int8_t)p[0xA];
+ va = (int8_t)p[0xB];
+ } else {
+ vc = (int8_t)p[0x8];
+ va = (int8_t)p[0x9];
+ }
+ int xPos = m->xMstPos;
+ if (dirMask & kDirectionKeyMaskLeft) {
+ xPos -= vc;
+ if (xPos < m->levelPosBounds_x1) {
+ continue;
+ }
+ } else if (dirMask & kDirectionKeyMaskRight) {
+ xPos += vc;
+ if (xPos > m->levelPosBounds_x2) {
+ continue;
+ }
+ }
+ int yPos = m->yMstPos;
+ if (dirMask & kDirectionKeyMaskUp) {
+ yPos -= va;
+ if (yPos < m->levelPosBounds_y1) {
+ continue;
+ }
+ } else if (dirMask & kDirectionKeyMaskDown) {
+ yPos += va;
+ if (yPos > m->levelPosBounds_y2) {
+ continue;
+ }
+ }
+ if (var10 == 1 && (m->flagsA5 & 4) == 0 && (m->flags48 & 8) != 0) {
+ if (!mstSetCurrentPos(m, xPos, yPos)) {
+ continue;
+ }
+ }
+ const int w = (int32_t)READ_LE_UINT32(m->monsterInfos + 904);
+ const int h = (int32_t)READ_LE_UINT32(m->monsterInfos + 908);
+ if (!rect_contains(m34->left - w, m34->top - h, m34->right + w, m34->bottom + h, xPos, yPos)) {
+ const uint32_t indexWalkPath = m->behaviorState->walkPath;
+ MstWalkPath *walkPath = &_res->_mstWalkPathData[indexWalkPath];
+ const int num = mstMonster1FindWalkPathRect(m, walkPath, xPos, yPos);
+ if (num < 0) {
+ continue;
+ }
+ if (m->walkNode->unk60[var10][num] == 0) {
+ continue;
+ }
+ }
+ m->targetLevelPos_x = xPos;
+ m->targetLevelPos_y = yPos;
+ p = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ if (p[0xE] != 0) {
+ const int x1 = m->xMstPos + (int8_t)p[0xC];
+ const int y1 = m->yMstPos + (int8_t)p[0xD];
+ const int x2 = x1 + p[0xE] - 1;
+ const int y2 = y1 + p[0xF] - 1;
+ const int r = mstBoundingBoxCollides2(m->monster1Index, x1, y1, x2, y2);
+ if (r != 0) {
+ bboxIndex = r - 1;
+ const MstBoundingBox *b = &_mstBoundingBoxesTable[bboxIndex];
+ if (rect_intersects(b->x1, b->y1, b->x2, b->y2, m->goalPos_x1, m->goalPos_y1, m->goalPos_x2, m->goalPos_y2)) {
+ break;
+ }
+ continue;
+ }
+ m->bboxNum[1] = mstBoundingBoxUpdate(m->bboxNum[1], m->monster1Index, x1, y1, x2, y2);
+ } else { // (p[0xE] == 0)
+ mstBoundingBoxClear(m, 1);
+ }
+ m->goalDirectionMask = dirMask;
+ if (var20 == 0) {
+ m->unkAB = 0xFF;
+ } else {
+ const uint8_t n = _mstLut1[dirMask];
+ m->unkAB = (n < 4) ? n + 4 : n - 4;
+ }
+ return;
+ }
+ m->task->flags |= 0x80;
+ m->goalDirectionMask = 0;
+ _yMstPos2 = 0;
+ _xMstPos2 = 0;
+}
+
+int Game::mstTaskStopMonster1(Task *t, MonsterObject1 *m) {
+ if (m->monsterInfos[946] & 4) {
+ mstBoundingBoxClear(m, 1);
+ }
+ if (m->goalScreenNum != 0xFC && (m->flagsA5 & 8) != 0 && (t->flags & 0x20) != 0 && m->action) {
+ LvlObject *o = m->o16;
+ const int xPosScreen = _res->_mstPointOffsets[_currentScreen].xOffset;
+ const int yPosScreen = _res->_mstPointOffsets[_currentScreen].yOffset;
+ const int xPosObj = o->xPos + _res->_mstPointOffsets[o->screenNum].xOffset;
+ const int yPosObj = o->yPos + _res->_mstPointOffsets[o->screenNum].yOffset;
+ // this matches the original code but rect_intersects() could probably be used
+ if (xPosObj < xPosScreen || xPosObj + o->width - 1 > xPosScreen + 255 || yPosObj < yPosScreen || yPosObj + o->height - 1 > yPosScreen + 191) {
+ return mstTaskStopMonsterObject1(t);
+ }
+ }
+ mstTaskResetMonster1WalkPath(t);
+ return 0;
+}
+
+int Game::mstTaskUpdatePositionActionDirection(Task *t, MonsterObject1 *m) {
+ if ((m->monsterInfos[946] & 4) == 0 && (_mstLut1[m->goalDirectionMask] & 1) != 0) {
+ if (_xMstPos2 < m->m49->unk14) {
+ m->goalDirectionMask &= ~kDirectionKeyMaskHorizontal;
+ }
+ if (_yMstPos2 < m->m49->unk15) {
+ m->goalDirectionMask &= ~kDirectionKeyMaskVertical;
+ }
+ }
+ const uint8_t *ptr = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ if ((m->monsterInfos[946] & 4) == 0 && (m->flagsA5 & 4) == 0 && (m->flagsA5 & 2) != 0 && (m->flags48 & 8) != 0) {
+ int vf, ve;
+ if (_mstLut1[m->goalDirectionMask] & 1) {
+ vf = (int8_t)ptr[0xA];
+ ve = (int8_t)ptr[0xB];
+ } else {
+ vf = (int8_t)ptr[0x8];
+ ve = (int8_t)ptr[0x9];
+ }
+ int x = m->xMstPos;
+ int y = m->yMstPos;
+ if (m->goalDirectionMask & kDirectionKeyMaskLeft) {
+ x -= vf;
+ } else if (m->goalDirectionMask & kDirectionKeyMaskRight) {
+ x += vf;
+ }
+ if (m->goalDirectionMask & kDirectionKeyMaskUp) {
+ y -= ve;
+ } else if (m->goalDirectionMask & kDirectionKeyMaskDown) {
+ y += ve;
+ }
+ if (!mstSetCurrentPos(m, x, y)) {
+ _xMstPos2 = ABS(m->xMstPos - _mstCurrentPosX);
+ _yMstPos2 = ABS(m->yMstPos - _mstCurrentPosY);
+ }
+ }
+ const MstMovingBoundsUnk1 *m49Unk1 = m->m49Unk1;
+ uint8_t xDist, yDist;
+ if (_mstLut1[m->goalDirectionMask] & 1) {
+ xDist = m49Unk1->unkA;
+ yDist = m49Unk1->unkB;
+ } else {
+ xDist = m49Unk1->unk8;
+ yDist = m49Unk1->unk9;
+ }
+ if (m->goalDirectionMask == 0) {
+ return mstTaskStopMonster1(t, m);
+ }
+ if (_xMstPos2 < xDist && _yMstPos2 < yDist) {
+ if ((_xMstPos2 <= 0 && _yMstPos2 <= 0) || !mstMonster1TestGoalDirection(m)) {
+ return mstTaskStopMonster1(t, m);
+ }
+ }
+ if ((m->monsterInfos[946] & 4) == 0 && (_mstLut1[m->goalDirectionMask] & 1) != 0) {
+ if (_xMstPos2 < _yMstPos2) {
+ if (_xMstPos2 < m->m49Unk1->unkA) {
+ m->goalDirectionMask &= ~kDirectionKeyMaskHorizontal;
+ if (m->targetDirectionMask != 0xFF) {
+ _xMstPos2 = 0;
+ }
+ }
+ } else {
+ if (_yMstPos2 < m->m49Unk1->unkB) {
+ m->goalDirectionMask &= ~kDirectionKeyMaskVertical;
+ if (m->targetDirectionMask != 0xFF) {
+ _yMstPos2 = 0;
+ }
+ }
+ }
+ }
+ ptr = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ mstLvlObjectSetActionDirection(m->o16, ptr, ptr[3], m->goalDirectionMask);
+ return 1;
+}
+
+// ret >0: found a rect matching
+// ret <0: return the closest match (setting _xMstPos3 and _yMstPos3)
+int Game::mstMonster1FindWalkPathRect(MonsterObject1 *m, MstWalkPath *walkPath, int x, int y) {
+ _xMstPos3 = x;
+ _yMstPos3 = y;
+ const int num = (~m->flagsA5) & 1;
+ int minDistance = 0x40000000;
+ int ret = -1;
+ int currentIndex = -1;
+ int xDist = 0;
+ int yDist = 0;
+ for (uint32_t i = 0; i < walkPath->count; ++i, --currentIndex) {
+ MstWalkNode *walkNode = &walkPath->data[i];
+ if (m->walkNode->unk60[num][i] == 0 && m->walkNode != walkNode) {
+ continue;
+ }
+ const uint32_t indexWalkBox = walkNode->walkBox;
+ const MstWalkBox *m34 = &_res->_mstWalkBoxData[indexWalkBox];
+ if (rect_contains(m34->left, m34->top, m34->right, m34->bottom, x, y)) {
+ return i;
+ }
+ int dist, curX = x, curY = y;
+
+ if (x >= m34->left && x <= m34->right) {
+ const int dy1 = ABS(y - m34->bottom);
+ const int dy2 = ABS(y - m34->top);
+ curY = (dy2 >= dy1) ? m34->bottom : m34->top;
+
+ yDist = y - curY;
+ yDist *= yDist;
+
+ dist = yDist;
+ if (minDistance >= dist) {
+ minDistance = dist;
+ _xMstPos3 = curX;
+ _yMstPos3 = curY;
+ ret = currentIndex;
+ }
+ } else if (y >= m34->top && y <= m34->bottom) {
+ const int dx1 = ABS(x - m34->right);
+ const int dx2 = ABS(x - m34->left);
+ curX = (dx2 >= dx1) ? m34->right : m34->left;
+
+ xDist = x - curX;
+ xDist *= xDist;
+
+ dist = xDist;
+ if (minDistance >= dist) {
+ minDistance = dist;
+ _xMstPos3 = curX;
+ _yMstPos3 = curY;
+ ret = currentIndex;
+ }
+ } else {
+ curX = m34->left;
+ xDist = x - curX;
+ xDist *= xDist;
+ curY = m34->top;
+ yDist = y - curY;
+ yDist *= yDist;
+
+ dist = xDist + yDist;
+ if (minDistance >= dist) {
+ minDistance = dist;
+ _xMstPos3 = curX;
+ _yMstPos3 = curY;
+ ret = currentIndex;
+ }
+ curX = m34->right;
+ xDist = x - curX;
+ xDist *= xDist;
+ dist = xDist + yDist;
+ if (minDistance >= dist) {
+ minDistance = dist;
+ _xMstPos3 = curX;
+ _yMstPos3 = curY;
+ ret = currentIndex;
+ }
+ curY = m34->bottom;
+ yDist = y - curY;
+ yDist *= yDist;
+ dist = xDist + yDist;
+ if (minDistance >= dist) {
+ minDistance = dist;
+ _xMstPos3 = curX;
+ _yMstPos3 = curY;
+ ret = currentIndex;
+ }
+ curX = m34->left;
+ xDist = x - curX;
+ xDist *= xDist;
+ dist = xDist + yDist;
+ if (minDistance >= dist) {
+ minDistance = dist;
+ _xMstPos3 = curX;
+ _yMstPos3 = curY;
+ ret = currentIndex;
+ }
+ }
+ dist = xDist + yDist;
+ if (minDistance >= dist) {
+ minDistance = dist;
+ _xMstPos3 = curX;
+ _yMstPos3 = curY;
+ ret = currentIndex;
+ }
+ }
+ return ret;
+}
+
+bool Game::mstTestActionDirection(MonsterObject1 *m, int num) {
+ LvlObject *o = m->o16;
+ const uint8_t _al = _res->_mstActionDirectionData[num].unk0;
+ const uint8_t _bl = _res->_mstActionDirectionData[num].unk2;
+ const uint8_t *var4 = m->monsterInfos + _al * 28;
+ const uint8_t _dl = (o->flags1 >> 4) & 3;
+ uint8_t var8 = ((_dl & 1) != 0) ? 8 : 2;
+ if (_dl & 2) {
+ var8 |= 4;
+ } else {
+ var8 |= 1;
+ }
+ uint8_t directionKeyMask = _bl & 15;
+ if ((_bl & 0x10) == 0) {
+ const uint32_t ve = _bl & 0xE0;
+ switch (ve) {
+ case 32:
+ case 96:
+ case 160:
+ case 192: // 0
+ if (ve == 192) {
+ directionKeyMask |= m->facingDirectionMask & ~kDirectionKeyMaskVertical;
+ } else {
+ directionKeyMask |= m->facingDirectionMask;
+ if (m->monsterInfos[946] & 2) {
+ if (ve == 160 && (_mstLut1[directionKeyMask] & 1) != 0) {
+ if (m->xDelta >= m->yDelta) {
+ directionKeyMask &= ~kDirectionKeyMaskVertical;
+ } else {
+ directionKeyMask &= ~kDirectionKeyMaskHorizontal;
+ }
+ } else {
+ if (m->xDelta >= 2 * m->yDelta) {
+ directionKeyMask &= ~kDirectionKeyMaskVertical;
+ } else if (m->yDelta >= 2 * m->xDelta) {
+ directionKeyMask &= ~kDirectionKeyMaskHorizontal;
+ }
+ }
+ }
+ }
+ break;
+ case 128: // 1
+ directionKeyMask |= var8;
+ if ((m->monsterInfos[946] & 2) != 0 && (_mstLut1[directionKeyMask] & 1) != 0) {
+ if (m->xDelta >= m->yDelta) {
+ directionKeyMask &= ~kDirectionKeyMaskVertical;
+ } else {
+ directionKeyMask &= ~kDirectionKeyMaskHorizontal;
+ }
+ }
+ break;
+ default: // 2
+ directionKeyMask |= var8;
+ break;
+ }
+ }
+ directionKeyMask &= var4[2];
+ if ((_bl & 0xE0) == 0x40) {
+ directionKeyMask ^= kDirectionKeyMaskHorizontal;
+ }
+ return ((var8 & directionKeyMask) != 0) ? 0 : 1;
+}
+
+bool Game::lvlObjectCollidesAndy1(LvlObject *o, int flags) const {
+ int x1, y1, x2, y2;
+ if (flags != 1 && flags != 0x4000) {
+ x1 = o->xPos;
+ y1 = o->yPos;
+ x2 = x1 + o->width - 1;
+ y2 = y1 + o->height - 1;
+ } else {
+ x1 = o->xPos + o->posTable[0].x;
+ x2 = o->xPos + o->posTable[1].x;
+ y1 = o->yPos + o->posTable[0].y;
+ y2 = o->yPos + o->posTable[1].y;
+ if (x1 > x2) {
+ SWAP(x1, x2);
+ }
+ if (y1 > y2) {
+ SWAP(y1, y2);
+ }
+ if (flags == 0x4000 && _andyObject->screenNum != o->screenNum) {
+ const int dx = _res->_mstPointOffsets[o->screenNum].xOffset - _res->_mstPointOffsets[_andyObject->screenNum].xOffset;
+ x1 += dx;
+ x2 += dx;
+ const int dy = _res->_mstPointOffsets[o->screenNum].yOffset - _res->_mstPointOffsets[_andyObject->screenNum].yOffset;
+ y1 += dy;
+ y2 += dy;
+ }
+ }
+ const int x = _andyObject->xPos + _andyObject->width / 2;
+ const int y = _andyObject->yPos + _andyObject->height / 2;
+ return rect_contains(x1, y1, x2, y2, x, y);
+}
+
+bool Game::lvlObjectCollidesAndy2(LvlObject *o, int type) const {
+ int x1, y1, x2, y2;
+ if (type != 1 && type != 0x1000) {
+ x1 = o->xPos;
+ y1 = o->yPos;
+ x2 = x1 + o->width - 1;
+ y2 = y1 + o->height - 1;
+ } else {
+ x1 = o->xPos + o->posTable[0].x;
+ x2 = o->xPos + o->posTable[1].x;
+ y1 = o->yPos + o->posTable[0].y;
+ y2 = o->yPos + o->posTable[1].y;
+ if (x1 > x2) {
+ SWAP(x1, x2);
+ }
+ if (y1 > y2) {
+ SWAP(y1, y2);
+ }
+ if (type == 0x1000 && _andyObject->screenNum != o->screenNum) {
+ const int dx = _res->_mstPointOffsets[_andyObject->screenNum].xOffset - _res->_mstPointOffsets[o->screenNum].xOffset;
+ x1 += dx;
+ x2 += dx;
+ const int dy = _res->_mstPointOffsets[_andyObject->screenNum].yOffset - _res->_mstPointOffsets[o->screenNum].yOffset;
+ y1 += dy;
+ y2 += dy;
+ }
+ }
+ return rect_intersects(x1, y1, x2, y2, _andyObject->xPos, _andyObject->yPos, _andyObject->xPos + _andyObject->width - 1, _andyObject->yPos + _andyObject->height - 1);
+}
+
+bool Game::lvlObjectCollidesAndy3(LvlObject *o, int type) const {
+ int x1, y1, x2, y2;
+ if (type != 1) {
+ x1 = o->xPos;
+ y1 = o->yPos;
+ x2 = o->xPos + o->width - 1;
+ y2 = o->yPos + o->height - 1;
+ } else {
+ x1 = o->xPos + o->posTable[0].x;
+ y1 = o->yPos + o->posTable[0].y;
+ x2 = o->xPos + o->posTable[1].x;
+ y2 = o->yPos + o->posTable[1].y;
+ if (x1 > x2) {
+ SWAP(x1, x2);
+ }
+ if (y1 > y2) {
+ SWAP(y1, y2);
+ }
+ }
+ const int xPos = _andyObject->xPos + _andyObject->posTable[3].x;
+ const int yPos = _andyObject->yPos + _andyObject->posTable[3].y;
+ return rect_contains(x1, y1, x2, y2, xPos, yPos);
+}
+
+bool Game::lvlObjectCollidesAndy4(LvlObject *o, int type) const {
+ int x1, y1, x2, y2;
+ if (type != 1) {
+ x1 = o->xPos;
+ y1 = o->yPos;
+ x2 = o->xPos + o->width - 1;
+ y2 = o->yPos + o->height - 1;
+ } else {
+ x1 = o->xPos + o->posTable[0].x;
+ y1 = o->yPos + o->posTable[0].y;
+ x2 = o->xPos + o->posTable[1].x;
+ y2 = o->yPos + o->posTable[1].y;
+ if (x1 > x2) {
+ SWAP(x1, x2);
+ }
+ if (y1 > y2) {
+ SWAP(y1, y2);
+ }
+ }
+ static const uint8_t indexes[] = { 1, 2, 4, 5 };
+ for (int i = 0; i < 4; ++i) {
+ const int xPos = _andyObject->xPos + _andyObject->posTable[indexes[i]].x;
+ const int yPos = _andyObject->yPos + _andyObject->posTable[indexes[i]].y;
+ if (rect_contains(x1, y1, x2, y2, xPos, yPos)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Game::mstCollidesByFlags(MonsterObject1 *m, uint32_t flags) {
+ if ((flags & 1) != 0 && (m->o16->flags0 & 0x200) == 0) {
+ return false;
+ } else if ((flags & 8) != 0 && (m->flags48 & 0x20) == 0) {
+ return false;
+ } else if ((flags & 0x100) != 0 && (_mstFlags & 0x80000000) != 0) {
+ return false;
+ } else if ((flags & 0x200) != 0 && (_mstFlags & 0x40000000) != 0) {
+ return false;
+ } else if ((flags & 0x40) != 0 && (mstGetFacingDirectionMask(m->facingDirectionMask) & 1) != ((m->o16->flags1 >> 4) & 1) && (m->monsterInfos[946] & 4) == 0) {
+ return false;
+ } else if ((flags & 0x80) != 0 && (mstGetFacingDirectionMask(m->facingDirectionMask) & 1) != ((m->o16->flags1 >> 4) & 1) && (m->monsterInfos[946] & 4) != 0) {
+ return false;
+ } else if ((flags & 0x400) != 0 && (m->o16->screenNum != _andyObject->screenNum || !lvlObjectCollidesAndy1(m->o16, 0))) {
+ return false;
+ } else if ((flags & 0x800) != 0 && (m->o16->screenNum != _andyObject->screenNum || !lvlObjectCollidesAndy1(m->o16, 1))) {
+ return false;
+ } else if ((flags & 0x100000) != 0 && (m->o16->screenNum != _andyObject->screenNum || !lvlObjectCollidesAndy3(m->o16, 0))) {
+ return false;
+ } else if ((flags & 0x200000) != 0 && (m->o16->screenNum != _andyObject->screenNum || !lvlObjectCollidesAndy3(m->o16, 1))) {
+ return false;
+ } else if ((flags & 4) != 0 && (m->o16->screenNum != _andyObject->screenNum || !lvlObjectCollidesAndy2(m->o16, 0))) {
+ return false;
+ } else if ((flags & 2) != 0 && (m->o16->screenNum != _andyObject->screenNum || !lvlObjectCollidesAndy2(m->o16, 1))) {
+ return false;
+ } else if ((flags & 0x4000) != 0 && !lvlObjectCollidesAndy1(m->o16, 0x4000)) {
+ return false;
+ } else if ((flags & 0x1000) != 0 && !lvlObjectCollidesAndy2(m->o16, 0x1000)) {
+ return false;
+ } else if ((flags & 0x20) != 0 && (m->o16->flags0 & 0x100) == 0) {
+ return false;
+ } else if ((flags & 0x10000) != 0 && (m->o16->screenNum != _andyObject->screenNum || !lvlObjectCollidesAndy4(m->o16, 1))) {
+ return false;
+ } else if ((flags & 0x20000) != 0 && (m->o16->screenNum != _andyObject->screenNum || !lvlObjectCollidesAndy4(m->o16, 0))) {
+ return false;
+ } else if ((flags & 0x40000) != 0 && (m->o16->screenNum != _andyObject->screenNum || !clipLvlObjectsBoundingBox(_andyObject, m->o16, 36))) {
+ return false;
+ } else if ((flags & 0x80000) != 0 && (m->o16->screenNum != _andyObject->screenNum || !clipLvlObjectsBoundingBox(_andyObject, m->o16, 20))) {
+ return false;
+ }
+ return true;
+}
+
+bool Game::mstMonster1Collide(MonsterObject1 *m, const uint8_t *p) {
+ const uint32_t a = READ_LE_UINT32(p + 0x10);
+ debug(kDebug_MONSTER, "mstMonster1Collide mask 0x%x flagsA6 0x%x", a, m->flagsA6);
+ if (a == 0 || !mstCollidesByFlags(m, a)) {
+ return false;
+ }
+ if ((a & 0x8000) != 0 && (m->flagsA6 & 4) == 0) {
+ Task t;
+ memcpy(&t, _mstCurrentTask, sizeof(Task));
+ _mstCurrentTask->child = 0;
+ const uint32_t codeData = READ_LE_UINT32(p + 0x18);
+ assert(codeData != kNone);
+ _mstCurrentTask->codeData = _res->_mstCodeData + codeData * 4;
+ _mstCurrentTask->run = &Game::mstTask_main;
+ _mstCurrentTask->monster1->flagsA6 |= 4;
+ Task *currentTask = _mstCurrentTask;
+ mstTask_main(_mstCurrentTask);
+ _mstCurrentTask = currentTask;
+ _mstCurrentTask->monster1->flagsA6 &= ~4;
+ t.nextPtr = _mstCurrentTask->nextPtr;
+ t.prevPtr = _mstCurrentTask->prevPtr;
+ memcpy(_mstCurrentTask, &t, sizeof(Task));
+ if (_mstCurrentTask->run == &Game::mstTask_idle && (_mstCurrentTask->monster1->flagsA6 & 2) == 0) {
+ _mstCurrentTask->run = &Game::mstTask_main;
+ }
+ return false;
+ } else {
+ mstTaskAttack(_mstCurrentTask, READ_LE_UINT32(p + 0x18), 0x10);
+ return true;
+ }
+}
+
+int Game::mstUpdateTaskMonsterObject1(Task *t) {
+ debug(kDebug_MONSTER, "mstUpdateTaskMonsterObject1 t %p", t);
+ _mstCurrentTask = t;
+ MonsterObject1 *m = t->monster1;
+ MonsterObject1 *_mstCurrentMonster1 = m;
+ LvlObject *o = m->o16;
+ const int num = o->flags0 & 0xFF;
+ const uint8_t *ptr = m->monsterInfos + num * 28;
+ int8_t a = ptr[6];
+ if (a != 0) {
+ const int num = CLIP(m->lut4Index + a, 0, 17);
+ o->flags2 = (o->flags2 & ~0x1F) | _mstLut4[num];
+ } else {
+ o->flags2 = m->o_flags2;
+ }
+ if (num == 0x1F) {
+ mstRemoveMonsterObject1(_mstCurrentTask, &_monsterObjects1TasksList);
+ return 1;
+ }
+ const uint32_t vf = READ_LE_UINT32(ptr + 20);
+ if (vf != kNone) {
+ MstBehavior *m46 = _mstCurrentMonster1->m46;
+ for (uint32_t i = 0; i < m46->count; ++i) {
+ if (m46->data[i].indexMonsterInfo == vf) {
+ mstTaskSetMonster1BehaviorState(_mstCurrentTask, _mstCurrentMonster1, i);
+ return 0;
+ }
+ }
+ }
+ if ((m->flagsA5 & 0x80) != 0) {
+ return 0;
+ }
+ if (m->localVars[7] == 0 && !_specialAnimFlag) {
+ // monster is dead
+ m->flagsA5 |= 0x80;
+ if (m->monsterInfos[946] & 4) {
+ mstBoundingBoxClear(m, 1);
+ }
+ if (t->child) {
+ t->child->codeData = 0;
+ t->child = 0;
+ }
+ if ((m->flagsA5 & 8) != 0 && m->action && _mstActionNum != -1) {
+ mstTaskStopMonsterObject1(_mstCurrentTask);
+ return 0;
+ }
+ const uint32_t codeData = m->behaviorState->codeData;
+ if (codeData != kNone) {
+ resetTask(t, _res->_mstCodeData + codeData * 4);
+ return 0;
+ }
+ o->actionKeyMask = 7;
+ o->directionKeyMask = 0;
+ t->run = &Game::mstTask_idle;
+ return 0;
+ }
+ if (t->run == &Game::mstTask_monsterWait4) {
+ return 0;
+ }
+ if (ptr[0] != 0) {
+ mstMonster1Collide(_mstCurrentMonster1, ptr);
+ return 0;
+ }
+ if ((m->flagsA5 & 0x40) != 0) {
+ return 0;
+ }
+ assert(_mstCurrentMonster1 == m);
+ int dir = 0;
+ for (int i = 0; i < _andyShootsCount; ++i) {
+ AndyShootData *p = &_andyShootsTable[i];
+ if (p->m && p->m != m) {
+ continue;
+ }
+ if (m->collideDistance < 0) {
+ continue;
+ }
+ if ((m->flags48 & 4) == 0) {
+ continue;
+ }
+ if (m->behaviorState->indexUnk51 == kNone) {
+ continue;
+ }
+ if (_rnd.getNextNumber() > m->behaviorState->unk10) {
+ continue;
+ }
+ m->shootActionIndex = 8;
+ int var28 = 0;
+ AndyShootData *var14 = m->shootData;
+ if (t->run != &Game::mstTask_monsterWait5 && t->run != &Game::mstTask_monsterWait6 && t->run != &Game::mstTask_monsterWait7 && t->run != &Game::mstTask_monsterWait8 && t->run != &Game::mstTask_monsterWait9 && t->run != &Game::mstTask_monsterWait10) {
+ if (m->monsterInfos[946] & 2) {
+ _mstCurrentMonster1->goalPos_x1 = _mstCurrentMonster1->goalPos_y1 = INT32_MIN;
+ _mstCurrentMonster1->goalPos_x2 = _mstCurrentMonster1->goalPos_y2 = INT32_MAX;
+ switch (var14->directionMask) {
+ case 0:
+ var28 = 2;
+ break;
+ case 1:
+ case 133:
+ var28 = 9;
+ break;
+ case 2:
+ var28 = 12;
+ break;
+ case 3:
+ case 128:
+ var28 = 3;
+ break;
+ case 4:
+ var28 = 6;
+ break;
+ case 5:
+ var28 = 8;
+ break;
+ case 6:
+ var28 = 1;
+ break;
+ case 7:
+ var28 = 4;
+ break;
+ }
+ _mstCurrentMonster1->shootActionIndex = _mstLut1[var28];
+ }
+ } else {
+ m->shootActionIndex = _mstLut1[m->goalDirectionMask];
+ var28 = 0;
+ }
+ uint32_t var24 = 0;
+ MstBehaviorState *vg = m->behaviorState;
+ if (vg->count != 0) {
+ var24 = _rnd.update() % (vg->count + 1);
+ }
+ int var20 = -1;
+ int indexUnk51;
+ if ((m->flagsA5 & 8) != 0 && m->action && m->action->indexUnk51 != kNone && m->monsterInfos == &_res->_mstMonsterInfos[m->action->unk0 * 948]) {
+ indexUnk51 = m->action->indexUnk51;
+ } else {
+ indexUnk51 = vg->indexUnk51;
+ }
+ int vb = -1;
+ const MstShootIndex *var18 = &_res->_mstShootIndexData[indexUnk51];
+ for (; var24 < var18->count; ++var24) {
+ assert(m->shootActionIndex >= 0 && m->shootActionIndex < 9);
+ const uint32_t indexUnk50Unk1 = var18->indexUnk50Unk1[var24 * 9 + m->shootActionIndex];
+ const MstShootAction *m50Unk1 = &_res->_mstShootData[var18->indexUnk50].data[indexUnk50Unk1];
+ const int32_t hSize = m50Unk1->hSize;
+ const int32_t vSize = m50Unk1->vSize;
+ if (hSize != 0 || vSize != 0) {
+ int dirMask = 0;
+ int ve = 0;
+ if ((m50Unk1->unk4 & kDirectionKeyMaskHorizontal) != kDirectionKeyMaskHorizontal && (m->o16->flags1 & 0x10) != 0) {
+ _mstTemp_x1 = m->xMstPos - hSize;
+ } else {
+ _mstTemp_x1 = m->xMstPos + hSize;
+ }
+ if (_mstTemp_x1 < m->xMstPos) {
+ if (_mstTemp_x1 < m->goalPos_x1) {
+ dirMask = kDirectionKeyMaskLeft;
+ }
+ ve = kDirectionKeyMaskLeft;
+ } else if (_mstTemp_x1 > m->xMstPos) {
+ if (_mstTemp_x1 > m->goalPos_x2) {
+ dirMask = kDirectionKeyMaskRight;
+ }
+ ve = kDirectionKeyMaskRight;
+ } else {
+ ve = 0;
+ }
+ _mstTemp_y1 = m->yMstPos + vSize;
+ if (_mstTemp_y1 < m->yMstPos) {
+ if (_mstTemp_y1 < m->goalPos_y1 && (m->monsterInfos[946] & 2) != 0) {
+ dirMask |= kDirectionKeyMaskUp;
+ }
+ ve |= kDirectionKeyMaskUp;
+ } else if (_mstTemp_y1 > m->yMstPos) {
+ if (_mstTemp_y1 > m->goalPos_y2 && (m->monsterInfos[946] & 2) != 0) {
+ dirMask |= kDirectionKeyMaskDown;
+ }
+ ve |= kDirectionKeyMaskDown;
+ }
+ if (var28 == dirMask) {
+ continue;
+ }
+ if (mstMonster1CheckLevelBounds(m, _mstTemp_x1, _mstTemp_y1, ve)) {
+ continue;
+ }
+ }
+ _mstTemp_x1 = m->xMstPos;
+ if ((m50Unk1->unk4 & kDirectionKeyMaskHorizontal) != kDirectionKeyMaskHorizontal && (m->o16->flags1 & 0x10) != 0) {
+ _mstTemp_x1 -= m50Unk1->width;
+ _mstTemp_x1 -= m50Unk1->xPos;
+ } else {
+ _mstTemp_x1 += m50Unk1->xPos;
+ }
+ _mstTemp_y1 = m->yMstPos + m50Unk1->yPos;
+ _mstTemp_x2 = _mstTemp_x1 + m50Unk1->width - 1;
+ _mstTemp_y2 = _mstTemp_y1 + m50Unk1->height - 1;
+ if ((m->monsterInfos[946] & 4) != 0 && mstBoundingBoxCollides1(m->monster1Index, _mstTemp_x1, _mstTemp_y1, _mstTemp_x2, _mstTemp_y2) != 0) {
+ continue;
+ }
+ if (m50Unk1->width != 0 && getMstDistance((m->monsterInfos[946] & 2) != 0 ? var14->boundingBox.y2 : m->yMstPos, var14) >= 0) {
+ continue;
+ }
+ if (m->collideDistance >= m50Unk1->unk24) {
+ MstBehaviorState *behaviorState = m->behaviorState;
+ vb = var24;
+ int vf = m50Unk1->unk24;
+ if (behaviorState->unk18 != 0) {
+ vf += (_rnd.update() % (behaviorState->unk18 * 2 + 1)) - behaviorState->unk18;
+ if (vf < 0) {
+ vf = 0;
+ }
+ }
+ dir = vf;
+ break;
+ }
+ if (var20 == -1) {
+ dir = m50Unk1->unk24;
+ var20 = var24;
+ }
+ vb = var20;
+ }
+ if (vb >= 0) {
+ const uint32_t indexUnk50Unk1 = var18->indexUnk50Unk1[vb * 9 + m->shootActionIndex];
+ MstShootAction *m50Unk1 = &_res->_mstShootData[var18->indexUnk50].data[indexUnk50Unk1];
+ mstTaskAttack(_mstCurrentTask, m50Unk1->codeData, 0x40);
+ _mstCurrentMonster1->unkF8 = m50Unk1->unk8;
+ _mstCurrentMonster1->shootSource = dir;
+ _mstCurrentMonster1->shootDirection = var14->directionMask;
+ _mstCurrentMonster1->directionKeyMask = _andyObject->directionKeyMask;
+ return 0;
+ }
+ }
+ if (o->screenNum == _currentScreen && (m->flagsA5 & 0x20) == 0 && (m->flags48 & 0x10) != 0) {
+ MstBehaviorState *behaviorState = m->behaviorState;
+ if (behaviorState->attackBox != kNone) {
+ const MstAttackBox *m47 = &_res->_mstAttackBoxData[behaviorState->attackBox];
+ if (m47->count > 0) {
+ const uint8_t dir = (o->flags1 >> 4) & 3;
+ const uint8_t *p = m47->data;
+ for (uint32_t i = 0; i < m47->count; ++i) {
+ int32_t a = READ_LE_UINT32(p); // x1
+ int32_t b = READ_LE_UINT32(p + 4); // y1
+ int32_t c = READ_LE_UINT32(p + 8); // x2
+ int32_t d = READ_LE_UINT32(p + 12); // y2
+ int x1, x2, y1, y2;
+ switch (dir) {
+ case 1:
+ x1 = m->xMstPos - c;
+ x2 = m->xMstPos - a;
+ y1 = m->yMstPos + b;
+ y2 = m->yMstPos + d;
+ break;
+ case 2:
+ x1 = m->xMstPos + a;
+ x2 = m->xMstPos + c;
+ y1 = m->yMstPos - d;
+ y2 = m->yMstPos - b;
+ break;
+ case 3:
+ x1 = m->xMstPos - c;
+ x2 = m->xMstPos - a;
+ y1 = m->yMstPos - d;
+ y2 = m->yMstPos - b;
+ break;
+ default:
+ x1 = m->xMstPos + a;
+ x2 = m->xMstPos + c;
+ y1 = m->yMstPos + b;
+ y2 = m->yMstPos + d;
+ break;
+ }
+
+ if (rect_contains(x1, y1, x2, y2, _mstAndyLevelPosX, _mstAndyLevelPosY)) {
+ mstTaskAttack(_mstCurrentTask, READ_LE_UINT32(p + 16), 0x20);
+ mstMonster1Collide(_mstCurrentMonster1, ptr);
+ return 0;
+ }
+
+ p += 20;
+ }
+ }
+ }
+ }
+ if (mstMonster1Collide(_mstCurrentMonster1, ptr)) {
+ return 0;
+ }
+ uint8_t _al = _mstCurrentMonster1->flagsA6;
+ if (_al & 2) {
+ return 0;
+ }
+ uint8_t _dl = _mstCurrentMonster1->flagsA5;
+ if (_dl & 0x30) {
+ return 0;
+ }
+ dir = _dl & 3;
+ if (dir == 1) {
+ MstWalkNode *walkNode = _mstCurrentMonster1->walkNode;
+ if (walkNode->walkCodeStage2 == kNone) {
+ return 0;
+ }
+ int ve = 0;
+ if (_mstAndyLevelPosY >= m->yMstPos - walkNode->y1 && _mstAndyLevelPosY < m->yMstPos + walkNode->y2) {
+ if (walkNode->x1 != -2 || walkNode->x1 != walkNode->x2) {
+ if (m->xDelta <= walkNode->x1) {
+ if (_al & 1) {
+ ve = 1;
+ } else {
+ _al = mstGetFacingDirectionMask(_mstCurrentMonster1->facingDirectionMask) & 1;
+ _dl = (_mstCurrentMonster1->o16->flags1 >> 4) & 1;
+ if (_dl == _al || (_mstCurrentMonster1->monsterInfos[946] & 4) != 0) {
+ ve = 1;
+ } else if (m->xDelta <= walkNode->x2) {
+ ve = 2;
+ }
+ }
+ }
+ } else if (o->screenNum == _currentScreen) {
+ if (_al & 1) {
+ ve = 1;
+ } else {
+ _al = mstGetFacingDirectionMask(_mstCurrentMonster1->facingDirectionMask) & 1;
+ _dl = (_mstCurrentMonster1->o16->flags1 >> 4) & 1;
+ if (_al != _dl && (_mstCurrentMonster1->monsterInfos[946] & 4) == 0) {
+ ve = 2;
+ } else {
+ ve = 1;
+ }
+ }
+ }
+ }
+ if (ve == 0) {
+ m->flagsA6 &= ~1;
+ if ((m->flagsA5 & 4) == 0) {
+ const uint32_t indexWalkCode = m->walkNode->walkCodeReset[0];
+ m->walkCode = &_res->_mstWalkCodeData[indexWalkCode];
+ }
+ return 0;
+ } else if (ve == 1) {
+ m->flagsA6 |= 1;
+ assert(_mstCurrentMonster1 == m);
+ if (!mstSetCurrentPos(m, m->xMstPos, m->yMstPos) && (m->monsterInfos[946] & 2) == 0) {
+ if ((_mstCurrentPosX > m->xMstPos && _mstCurrentPosX > m->walkNode->coords[0][1]) || (_mstCurrentPosX < m->xMstPos && _mstCurrentPosX < m->walkNode->coords[1][1])) {
+ uint32_t indexWalkCode = m->walkNode->walkCodeStage1;
+ if (indexWalkCode != kNone) {
+ m->walkCode = &_res->_mstWalkCodeData[indexWalkCode];
+ }
+ if (m->flagsA5 & 4) {
+ m->flagsA5 &= ~4;
+ if (!mstMonster1UpdateWalkPath(m)) {
+ mstMonster1ResetWalkPath(m);
+ }
+ indexWalkCode = m->walkNode->walkCodeStage1;
+ if (indexWalkCode != kNone) {
+ m->walkCode = &_res->_mstWalkCodeData[indexWalkCode];
+ }
+ mstTaskSetNextWalkCode(_mstCurrentTask);
+ }
+ return 0;
+ }
+ }
+ if ((m->monsterInfos[946] & 2) == 0) {
+ MstWalkNode *walkPath = m->walkNode;
+ int vf = (int32_t)READ_LE_UINT32(m->monsterInfos + 904);
+ int vb = MAX(m->goalPosBounds_x1, walkPath->coords[1][1] + vf);
+ int va = MIN(m->goalPosBounds_x2, walkPath->coords[0][1] - vf);
+ const uint32_t indexUnk36 = walkPath->movingBoundsIndex2;
+ const uint32_t indexUnk49 = _res->_mstMovingBoundsIndexData[indexUnk36].indexUnk49;
+ uint8_t _bl = _res->_mstMovingBoundsData[indexUnk49].unk14;
+ if (ABS(va - vb) <= _bl) {
+ uint32_t indexWalkCode = walkPath->walkCodeStage1;
+ if (indexWalkCode != kNone) {
+ m->walkCode = &_res->_mstWalkCodeData[indexWalkCode];
+ }
+ if (m->flagsA5 & 4) {
+ m->flagsA5 &= ~4;
+ if (!mstMonster1UpdateWalkPath(m)) {
+ mstMonster1ResetWalkPath(m);
+ }
+ indexWalkCode = m->walkNode->walkCodeStage1;
+ if (indexWalkCode != kNone) {
+ m->walkCode = &_res->_mstWalkCodeData[indexWalkCode];
+ }
+ mstTaskSetNextWalkCode(_mstCurrentTask);
+ }
+ return 0;
+ }
+ }
+ mstTaskInitMonster1Type2(_mstCurrentTask, 0);
+ return 0;
+ }
+ assert(ve == 2);
+ if (m->flagsA6 & 1) {
+ return 0;
+ }
+ const uint32_t indexWalkCode = m->walkNode->walkCodeStage2;
+ MstWalkCode *m35 = &_res->_mstWalkCodeData[indexWalkCode];
+ if (m->walkCode != m35) {
+ _mstCurrentMonster1->walkCode = m35;
+ _rnd.resetMst(_mstCurrentMonster1->rnd_m35);
+ mstTaskSetNextWalkCode(_mstCurrentTask);
+ return 0;
+ }
+ if (m->flagsA5 & 4) {
+ m->flagsA5 &= ~4;
+ if (!mstMonster1UpdateWalkPath(_mstCurrentMonster1)) {
+ mstMonster1ResetWalkPath(_mstCurrentMonster1);
+ }
+ const uint32_t indexWalkCode = m->walkNode->walkCodeStage2;
+ _mstCurrentMonster1->walkCode = &_res->_mstWalkCodeData[indexWalkCode];
+ mstTaskSetNextWalkCode(_mstCurrentTask);
+ }
+ return 0;
+ } else if (dir != 2) {
+ return 0;
+ }
+ if ((m->flagsA5 & 4) != 0 || (m->flags48 & 8) == 0) {
+ return 0;
+ }
+ if ((m->flagsA5 & 8) == 0 && (m->monsterInfos[946] & 2) == 0) {
+ const uint8_t _dl = m->facingDirectionMask;
+ if (_dl & kDirectionKeyMaskRight) {
+ if ((int32_t)READ_LE_UINT32(m->monsterInfos + 916) <= m->walkNode->coords[1][1] || (int32_t)READ_LE_UINT32(m->monsterInfos + 912) >= m->walkNode->coords[0][1]) {
+ m->flagsA6 |= 1;
+ assert(m == _mstCurrentMonster1);
+ m->flagsA5 = 1;
+ mstMonster1ResetWalkPath(m);
+ const uint32_t indexWalkCode = m->walkNode->walkCodeStage1;
+ if (indexWalkCode != kNone) {
+ m->walkCode = &_res->_mstWalkCodeData[indexWalkCode];
+ }
+ return 0;
+ }
+ } else if (_dl & kDirectionKeyMaskLeft) {
+ if ((int32_t)READ_LE_UINT32(m->monsterInfos + 920) >= m->walkNode->coords[0][1] || (int32_t)READ_LE_UINT32(m->monsterInfos + 924) <= m->walkNode->coords[1][1]) {
+ m->flagsA6 |= 1;
+ assert(m == _mstCurrentMonster1);
+ m->flagsA5 = 1;
+ mstMonster1ResetWalkPath(m);
+ const uint32_t indexWalkCode = m->walkNode->walkCodeStage1;
+ if (indexWalkCode != kNone) {
+ m->walkCode = &_res->_mstWalkCodeData[indexWalkCode];
+ }
+ return 0;
+ }
+ }
+ }
+ if (!mstSetCurrentPos(m, m->xMstPos, m->yMstPos)) {
+ mstTaskInitMonster1Type2(t, 1);
+ }
+ return 0;
+}
+
+int Game::mstUpdateTaskMonsterObject2(Task *t) {
+ debug(kDebug_MONSTER, "mstUpdateTaskMonsterObject2 t %p", t);
+ mstTaskSetMonster2ScreenPosition(t);
+ MonsterObject2 *m = t->monster2;
+ if (_currentLevel == kLvl_fort && m->monster2Info->type == 27) {
+ if (_fireflyPosData[m->hPosIndex] == 0xFF) {
+ uint32_t r = _rnd.update();
+ uint8_t _dl = (r % 5) << 3;
+ m->vPosIndex = _dl;
+ if (m->hPosIndex >= 40) {
+ m->hPosIndex = _dl;
+ m->vPosIndex = _dl + 40;
+ m->hDir = (r >> 16) & 1;
+ } else {
+ m->hPosIndex = m->vPosIndex + 40;
+ m->vDir = (r >> 16) & 1;
+ }
+ }
+ int dx = _fireflyPosData[m->hPosIndex];
+ if (m->hDir == 0) {
+ dx = -dx;
+ }
+ int dy = _fireflyPosData[m->vPosIndex];
+ if (m->vDir == 0) {
+ dy = -dy;
+ }
+ ++m->vPosIndex;
+ ++m->hPosIndex;
+ m->o->xPos += dx;
+ m->o->yPos += dy;
+ m->xMstPos += dx;
+ m->yMstPos += dy;
+ if (m->xMstPos > m->x2) {
+ m->hDir = 0;
+ } else if (m->xMstPos < m->x1) {
+ m->hDir = 1;
+ }
+ if (m->yMstPos > m->y2) {
+ m->vDir = 0;
+ } else if (m->yMstPos < m->y1) {
+ m->vDir = 1;
+ }
+ }
+ uint8_t _dl = 0;
+ for (int i = 0; i < _andyShootsCount; ++i) {
+ AndyShootData *p = &_andyShootsTable[i];
+ if (p->type == 2) {
+ _dl |= 1;
+ } else if (p->type == 1) {
+ _dl |= 2;
+ }
+ }
+ LvlObject *o = m->o;
+ MstInfoMonster2 *monster2Info = m->monster2Info;
+ const uint8_t _bl = monster2Info->shootMask;
+ if ((_bl & _dl) != 0) {
+ for (int i = 0; i < _andyShootsCount; ++i) {
+ AndyShootData *p = &_andyShootsTable[i];
+ if (p->type == 2 && (_bl & 1) == 0) {
+ continue;
+ } else if (p->type == 1 && (_bl & 2) == 0) {
+ continue;
+ }
+ if (o->screenNum != _currentScreen || p->o->screenNum != _currentScreen) {
+ continue;
+ }
+ if (!clipLvlObjectsBoundingBox(p->o, o, 20)) {
+ continue;
+ }
+ ShootLvlObjectData *s = p->shootObjectData;
+ s->unk3 = 0x80;
+ s->xPosShoot = o->xPos + o->width / 2;
+ s->yPosShoot = o->yPos + o->height / 2;
+ if (p->type != 2 || (_bl & 4) != 0) {
+ continue;
+ }
+ const uint32_t codeData = monster2Info->codeData2;
+ if (codeData != kNone) {
+ resetTask(t, _res->_mstCodeData + codeData * 4);
+ } else {
+ o->actionKeyMask = 7;
+ o->directionKeyMask = 0;
+ t->run = &Game::mstTask_idle;
+ }
+ }
+ }
+ if ((m->o->flags0 & 0xFF) == 0x1F) {
+ mstRemoveMonsterObject2(t, &_monsterObjects2TasksList);
+ return 1;
+ }
+ return 0;
+}
+
+void Game::mstUpdateRefPos() {
+ if (_andyObject) {
+ _mstAndyScreenPosX = _andyObject->xPos;
+ _mstAndyScreenPosY = _andyObject->yPos;
+ _mstAndyLevelPosX = _mstAndyScreenPosX + _res->_mstPointOffsets[_currentScreen].xOffset;
+ _mstAndyLevelPosY = _mstAndyScreenPosY + _res->_mstPointOffsets[_currentScreen].yOffset;
+ if (!_specialAnimFlag) {
+ _mstAndyRectNum = mstBoundingBoxUpdate(_mstAndyRectNum, 0xFE, _mstAndyLevelPosX, _mstAndyLevelPosY, _mstAndyLevelPosX + _andyObject->width - 1, _mstAndyLevelPosY + _andyObject->height - 1) & 0xFF;
+ }
+ _mstAndyScreenPosX += _andyObject->posTable[3].x;
+ _mstAndyScreenPosY += _andyObject->posTable[3].y;
+ _mstAndyLevelPosX += _andyObject->posTable[3].x;
+ _mstAndyLevelPosY += _andyObject->posTable[3].y;
+ } else {
+ _mstAndyScreenPosX = 128;
+ _mstAndyScreenPosY = 96;
+ _mstAndyLevelPosX = _mstAndyScreenPosX + _res->_mstPointOffsets[0].xOffset;
+ _mstAndyLevelPosY = _mstAndyScreenPosY + _res->_mstPointOffsets[0].yOffset;
+ }
+ _andyShootsCount = 0;
+ _andyShootsTable[0].type = 0;
+ if (!_lvlObjectsList0) {
+ if (_plasmaCannonDirection == 0) {
+ _executeMstLogicPrevCounter = _executeMstLogicCounter;
+ return;
+ }
+ _andyShootsTable[0].width = 512;
+ _andyShootsTable[0].height = 512;
+ _andyShootsTable[0].shootObjectData = 0;
+ _andyShootsTable[0].type = 3;
+ _andyShootsTable[0].size = 4;
+ _andyShootsTable[0].xPos = _plasmaCannonPosX[_plasmaCannonFirstIndex] + _res->_mstPointOffsets[_currentScreen].xOffset;
+ _andyShootsTable[0].yPos = _plasmaCannonPosY[_plasmaCannonFirstIndex] + _res->_mstPointOffsets[_currentScreen].yOffset;
+ switch (_plasmaCannonDirection - 1) {
+ case 0:
+ _andyShootsTable[0].directionMask = 6;
+ _andyShootsCount = 1;
+ break;
+ case 2:
+ _andyShootsTable[0].directionMask = 3;
+ _andyShootsCount = 1;
+ break;
+ case 1:
+ _andyShootsTable[0].directionMask = 0;
+ _andyShootsCount = 1;
+ break;
+ case 5:
+ _andyShootsTable[0].directionMask = 4;
+ _andyShootsCount = 1;
+ break;
+ case 3:
+ _andyShootsTable[0].directionMask = 7;
+ _andyShootsCount = 1;
+ break;
+ case 11:
+ _andyShootsTable[0].directionMask = 2;
+ _andyShootsCount = 1;
+ break;
+ case 7:
+ _andyShootsTable[0].directionMask = 5;
+ _andyShootsCount = 1;
+ break;
+ case 8:
+ _andyShootsTable[0].directionMask = 1;
+ _andyShootsCount = 1;
+ break;
+ default:
+ _andyShootsCount = 1;
+ break;
+ }
+ } else {
+ AndyShootData *p = _andyShootsTable;
+ for (LvlObject *o = _lvlObjectsList0; o; o = o->nextPtr) {
+ p->o = o;
+ if (!o->dataPtr) {
+ continue;
+ }
+ ShootLvlObjectData *ptr = (ShootLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ p->shootObjectData = ptr;
+ if (ptr->unk3 == 0x80) {
+ continue;
+ }
+ if (ptr->dxPos == 0 && ptr->dyPos == 0) {
+ continue;
+ }
+ p->width = ptr->dxPos;
+ p->height = ptr->dyPos;
+ p->directionMask = ptr->state;
+ switch (ptr->type) {
+ case 0:
+ p->type = 1;
+ p->xPos = o->xPos + _res->_mstPointOffsets[o->screenNum].xOffset + o->posTable[7].x;
+ p->size = 3;
+ p->yPos = o->yPos + _res->_mstPointOffsets[o->screenNum].yOffset + o->posTable[7].y;
+ break;
+ case 5:
+ p->directionMask |= 0x80;
+ // fall-through
+ case 4:
+ p->type = 2;
+ p->xPos = o->xPos + _res->_mstPointOffsets[o->screenNum].xOffset + o->posTable[7].x;
+ p->size = 7;
+ p->yPos = o->yPos + _res->_mstPointOffsets[o->screenNum].yOffset + o->posTable[7].y;
+ break;
+ default:
+ --p;
+ --_andyShootsCount;
+ break;
+ }
+ ++p;
+ ++_andyShootsCount;
+ if (_andyShootsCount >= kMaxAndyShoots) {
+ break;
+ }
+ }
+ if (_andyShootsCount == 0) {
+ _executeMstLogicPrevCounter = _executeMstLogicCounter;
+ return;
+ }
+ }
+ for (int i = 0; i < _andyShootsCount; ++i) {
+ AndyShootData *p = &_andyShootsTable[i];
+ p->boundingBox.x2 = p->xPos + p->size;
+ p->boundingBox.x1 = p->xPos - p->size;
+ p->boundingBox.y2 = p->yPos + p->size;
+ p->boundingBox.y1 = p->yPos - p->size;
+ }
+}
+
+void Game::mstUpdateMonstersRect() {
+ const int _mstAndyLevelPosDx = _mstAndyLevelPosX - _mstAndyLevelPrevPosX;
+ const int _mstAndyLevelPosDy = _mstAndyLevelPosY - _mstAndyLevelPrevPosY;
+ _mstAndyLevelPrevPosX = _mstAndyLevelPosX;
+ _mstAndyLevelPrevPosY = _mstAndyLevelPosY;
+ if (_mstAndyLevelPosDx == 0 && _mstAndyLevelPosDy == 0) {
+ return;
+ }
+ int offset = 0;
+ for (int i = 0; i < _res->_mstHdr.infoMonster1Count; ++i) {
+ offset += kMonsterInfoDataSize;
+ const uint32_t unk30 = READ_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x30]); // 900
+ const uint32_t unk34 = READ_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x34]); // 896
+
+ const uint32_t unk20 = _mstAndyLevelPosX - unk30;
+ const uint32_t unk1C = _mstAndyLevelPosX + unk30;
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x20], unk20);
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x1C], unk1C);
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x24], unk20 - unk34);
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x18], unk1C + unk34);
+
+ const uint32_t unk10 = _mstAndyLevelPosY - unk30;
+ const uint32_t unk0C = _mstAndyLevelPosY + unk30;
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x10], unk10);
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x0C], unk0C);
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x14], unk10 - unk34);
+ WRITE_LE_UINT32(&_res->_mstMonsterInfos[offset - 0x08], unk0C + unk34);
+ }
+}
+
+void Game::mstRemoveMonsterObject2(Task *t, Task **tasksList) {
+ MonsterObject2 *m = t->monster2;
+ m->monster2Info = 0;
+ LvlObject *o = m->o;
+ if (o) {
+ o->dataPtr = 0;
+ removeLvlObject2(o);
+ }
+ removeTask(tasksList, t);
+}
+
+void Game::mstRemoveMonsterObject1(Task *t, Task **tasksList) {
+ MonsterObject1 *m = t->monster1;
+ if (_mstActionNum != -1) {
+ if ((m->flagsA5 & 8) != 0 && m->action) {
+ mstMonster1ClearChasingMonster(m);
+ }
+ }
+ if (m->monsterInfos[946] & 4) {
+ mstBoundingBoxClear(m, 0);
+ mstBoundingBoxClear(m, 1);
+ }
+ m->m46 = 0;
+ LvlObject *o = m->o16;
+ if (o) {
+ o->dataPtr = 0;
+ }
+ for (int i = 0; i < kMaxMonsterObjects2; ++i) {
+ if (_monsterObjects2Table[i].monster2Info != 0 && _monsterObjects2Table[i].monster1 == m) {
+ _monsterObjects2Table[i].monster1 = 0;
+ }
+ }
+ removeLvlObject2(o);
+ removeTask(tasksList, t);
+}
+
+void Game::mstTaskAttack(Task *t, uint32_t codeData, uint8_t flags) {
+ MonsterObject1 *m = t->monster1;
+ m->flagsA5 = (m->flagsA5 & ~0x70) | flags;
+ Task *c = t->child;
+ if (c) {
+ t->child = 0;
+ c->codeData = 0;
+ }
+ if (m->flagsA5 & 8) {
+ Task *n = findFreeTask();
+ if (n) {
+ memcpy(n, t, sizeof(Task));
+ t->child = n;
+ if (t->run != &Game::mstTask_wait2) {
+ const uint8_t *p = n->codeData - 4;
+ if ((t->flags & 0x40) != 0 || p[0] == 203 || ((flags & 0x10) != 0 && (t->run == &Game::mstTask_monsterWait1 || t->run == &Game::mstTask_monsterWait2 || t->run == &Game::mstTask_monsterWait3 || t->run == &Game::mstTask_monsterWait4))) {
+ p += 4;
+ }
+ n->codeData = p;
+ n->run = &Game::mstTask_main;
+ }
+ }
+ }
+ assert(codeData != kNone);
+ resetTask(t, _res->_mstCodeData + codeData * 4);
+}
+
+int Game::mstTaskSetActionDirection(Task *t, int num, int delay) {
+ MonsterObject1 *m = t->monster1;
+ LvlObject *o = m->o16;
+ uint8_t var4 = _res->_mstActionDirectionData[num].unk0;
+ uint8_t var8 = _res->_mstActionDirectionData[num].unk2;
+ const uint8_t *p = m->monsterInfos + var4 * 28;
+ uint8_t _al = (o->flags1 >> 4) & 3;
+ uint8_t _cl = ((_al & 1) != 0) ? 8 : 2;
+ if (_al & 2) {
+ _cl |= 4;
+ } else {
+ _cl |= 1;
+ }
+ mstLvlObjectSetActionDirection(o, p, var8, _cl);
+ const uint8_t am = _res->_mstActionDirectionData[num].unk1;
+ o->actionKeyMask |= am;
+
+ t->flags &= ~0x80;
+ int vf = (int8_t)p[4];
+ int ve = (int8_t)p[5];
+ debug(kDebug_MONSTER, "mstTaskSetActionDirection m %p action 0x%x direction 0x%x (%d,%d)", m, o->actionKeyMask, o->directionKeyMask, vf, ve);
+ int va = 0;
+ if (vf != 0 || ve != 0) {
+ int dirMask = 0;
+ uint8_t var11 = p[2];
+ if (((var11 & kDirectionKeyMaskHorizontal) == kDirectionKeyMaskHorizontal && (o->directionKeyMask & 8) != 0) || ((var11 & kDirectionKeyMaskHorizontal) != kDirectionKeyMaskHorizontal && (o->flags1 & 0x10) != 0)) {
+ vf = m->xMstPos - vf;
+ } else {
+ vf = m->xMstPos + vf;
+ }
+ if (vf < m->xMstPos) {
+ dirMask = kDirectionKeyMaskLeft;
+ } else if (vf > m->xMstPos) {
+ dirMask = kDirectionKeyMaskRight;
+ }
+ if ((var11 & kDirectionKeyMaskVertical) == kDirectionKeyMaskVertical && (o->directionKeyMask & 1) != 0) {
+ ve = m->yMstPos - ve;
+ } else {
+ ve = m->yMstPos + ve;
+ }
+ if (ve < m->yMstPos) {
+ dirMask |= kDirectionKeyMaskUp;
+ } else if (ve > m->yMstPos) {
+ dirMask |= kDirectionKeyMaskDown;
+ }
+ if (mstMonster1CheckLevelBounds(m, vf, ve, dirMask)) {
+ t->flags |= 0x80;
+ return 0;
+ }
+ va = dirMask;
+ }
+ if ((m->monsterInfos[946] & 4) != 0 && p[0xE] != 0) {
+ if (va == 0) {
+ ve = 0;
+ } else if (_mstLut1[va] & 1) {
+ ve = (int8_t)p[0xA];
+ va = (int8_t)p[0xB];
+ } else {
+ ve = (int8_t)p[0x8];
+ va = (int8_t)p[0x9];
+ }
+ if (o->directionKeyMask & kDirectionKeyMaskLeft) {
+ ve = -ve;
+ } else if ((o->directionKeyMask & kDirectionKeyMaskRight) == 0) {
+ ve = 0;
+ }
+ if (o->directionKeyMask & kDirectionKeyMaskUp) {
+ va = -va;
+ } else if ((o->directionKeyMask & kDirectionKeyMaskDown) == 0) {
+ va = 0;
+ }
+ const int x1 = m->xMstPos + (int8_t)p[0xC] + ve;
+ const int y1 = m->yMstPos + (int8_t)p[0xD] + va;
+ const int x2 = x1 + p[0xE] - 1;
+ const int y2 = y1 + p[0xF] - 1;
+ if ((var8 & 0xE0) != 0x60 && mstBoundingBoxCollides2(m->monster1Index, x1, y1, x2, y2) != 0) {
+ t->flags |= 0x80;
+ return 0;
+ }
+ m->bboxNum[0] = mstBoundingBoxUpdate(m->bboxNum[0], m->monster1Index, x1, y1, x2, y2);
+ }
+ m->o_flags0 = var4;
+ if (delay == -1) {
+ const uint32_t offset = m->monsterInfos - _res->_mstMonsterInfos;
+ assert((offset % kMonsterInfoDataSize) == 0);
+ t->arg2 = offset / kMonsterInfoDataSize;
+ t->run = &Game::mstTask_monsterWait4;
+ debug(kDebug_MONSTER, "mstTaskSetActionDirection arg2 %d", t->arg2);
+ } else {
+ t->arg1 = delay;
+ t->run = &Game::mstTask_monsterWait3;
+ debug(kDebug_MONSTER, "mstTaskSetActionDirection arg1 %d", t->arg1);
+ }
+ return 1;
+}
+
+Task *Game::findFreeTask() {
+ for (int i = 0; i < kMaxTasks; ++i) {
+ Task *t = &_tasksTable[i];
+ if (!t->codeData) {
+ memset(t, 0, sizeof(Task));
+ return t;
+ }
+ }
+ warning("findFreeTask() no free task");
+ return 0;
+}
+
+Task *Game::createTask(const uint8_t *codeData) {
+ Task *t = findFreeTask();
+ if (t) {
+ resetTask(t, codeData);
+ t->prevPtr = 0;
+ t->nextPtr = _tasksList;
+ if (_tasksList) {
+ _tasksList->prevPtr = t;
+ }
+ _tasksList = t;
+ return t;
+ }
+ return 0;
+}
+
+void Game::updateTask(Task *t, int num, const uint8_t *codeData) {
+ debug(kDebug_MONSTER, "updateTask t %p offset 0x%04x", t, codeData - _res->_mstCodeData);
+ Task *current = _tasksList;
+ bool found = false;
+ while (current) {
+ Task *nextPtr = current->nextPtr;
+ if (current->localVars[7] == num) {
+ found = true;
+ if (current != t) {
+ if (!codeData) {
+ if (current->child) {
+ current->child->codeData = 0;
+ current->child = 0;
+ }
+ Task *prev = current->prevPtr;
+ current->codeData = 0;
+ Task *next = current->nextPtr;
+ if (next) {
+ next->prevPtr = prev;
+ }
+ if (prev) {
+ prev->nextPtr = next;
+ } else {
+ _tasksList = next;
+ }
+ } else {
+ t->codeData = codeData;
+ t->run = &Game::mstTask_main;
+ }
+ }
+ }
+ current = nextPtr;
+ }
+ if (found) {
+ return;
+ }
+ if (codeData) {
+ t = findFreeTask();
+ if (t) {
+ resetTask(t, codeData);
+ t->prevPtr = 0;
+ t->nextPtr = _tasksList;
+ if (_tasksList) {
+ _tasksList->prevPtr = t;
+ }
+ _tasksList = t;
+ t->localVars[7] = num;
+ }
+ }
+}
+
+void Game::resetTask(Task *t, const uint8_t *codeData) {
+ debug(kDebug_MONSTER, "resetTask t %p offset 0x%04x monster1 %p monster2 %p", t, codeData - _res->_mstCodeData, t->monster1, t->monster2);
+ assert(codeData);
+ t->state |= 2;
+ t->codeData = codeData;
+ t->run = &Game::mstTask_main;
+ t->localVars[7] = 0;
+ MonsterObject1 *m = t->monster1;
+ if (m) {
+ const uint8_t mask = m->flagsA5;
+ if ((mask & 0x88) == 0 || (mask & 0xF0) == 0) {
+ if ((mask & 8) != 0) {
+ t->flags = (t->flags & ~0x40) | 0x20;
+ m->flags48 &= ~0x1C;
+ } else if ((mask & 2) != 0) {
+ m->flags48 |= 8;
+ const MstBehaviorState *behaviorState = m->behaviorState;
+ if (behaviorState->indexUnk51 != kNone) {
+ m->flags48 |= 4;
+ }
+ if (behaviorState->attackBox != kNone) {
+ m->flags48 |= 0x10;
+ }
+ }
+ }
+ }
+}
+
+void Game::removeTask(Task **tasksList, Task *t) {
+ Task *c = t->child;
+ if (c) {
+ c->codeData = 0;
+ t->child = 0;
+ }
+ Task *prev = t->prevPtr;
+ t->codeData = 0;
+ Task *next = t->nextPtr;
+ if (next) {
+ next->prevPtr = prev;
+ }
+ if (prev) {
+ prev->nextPtr = next;
+ } else {
+ *tasksList = next;
+ }
+}
+
+void Game::appendTask(Task **tasksList, Task *t) {
+ Task *current = *tasksList;
+ if (!current) {
+ *tasksList = t;
+ t->nextPtr = t->prevPtr = 0;
+ } else {
+ // go to last element
+ Task *next = current->nextPtr;
+ while (next) {
+ current = next;
+ next = current->nextPtr;
+ }
+ assert(!current->nextPtr);
+ current->nextPtr = t;
+ t->nextPtr = 0;
+ t->prevPtr = current;
+ }
+}
+
+int Game::getTaskVar(Task *t, int index, int type) const {
+ switch (type) {
+ case 1:
+ return index;
+ case 2:
+ assert(index < kMaxLocals);
+ return t->localVars[index];
+ case 3:
+ assert(index < kMaxVars);
+ return _mstVars[index];
+ case 4:
+ return getTaskOtherVar(index, t);
+ case 5:
+ {
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ assert(index < kMaxLocals);
+ return m->localVars[index];
+ }
+ }
+ break;
+ default:
+ warning("getTaskVar unhandled index %d type %d", index, type);
+ break;
+ }
+ return 0;
+}
+
+void Game::setTaskVar(Task *t, int index, int type, int value) {
+ switch (type) {
+ case 2:
+ assert(index < kMaxLocals);
+ t->localVars[index] = value;
+ break;
+ case 3:
+ assert(index < kMaxVars);
+ _mstVars[index] = value;
+ break;
+ case 5: {
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ assert(index < kMaxLocals);
+ m->localVars[index] = value;
+ }
+ }
+ break;
+ default:
+ warning("setTaskVar unhandled index %d type %d", index, type);
+ break;
+ }
+}
+
+int Game::getTaskAndyVar(int index, Task *t) const {
+ if (index & 0x80) {
+ const int mask = 1 << (index & 0x7F);
+ return ((mask & _mstAndyVarMask) != 0) ? 1 : 0;
+ }
+ switch (index) {
+ case 0:
+ return (_andyObject->flags1 >> 4) & 1;
+ case 1:
+ return (_andyObject->flags1 >> 5) & 1;
+ case 2: {
+ MonsterObject1 *m = t->monster1;
+ if (m) {
+ return ((m->o16->flags1 & 0x10) != 0) ? 1 : 0;
+ } else if (t->monster2) {
+ return ((t->monster2->o->flags1 & 0x10) != 0) ? 1 : 0;
+ }
+ }
+ break;
+ case 3: {
+ MonsterObject1 *m = t->monster1;
+ if (m) {
+ return ((m->o16->flags1 & 0x20) != 0) ? 1 : 0;
+ } else if (t->monster2) {
+ return ((t->monster2->o->flags1 & 0x20) != 0) ? 1 : 0;
+ }
+ }
+ break;
+ case 4: {
+ MonsterObject1 *m = t->monster1;
+ if (m) {
+ return ((m->o16->flags0 & 0x200) != 0) ? 1 : 0;
+ } else if (t->monster2) {
+ return ((t->monster2->o->flags0 & 0x200) != 0) ? 1 : 0;
+ }
+ }
+ break;
+ case 5:
+ return ((_andyObject->flags0 & 0x1F) == 7) ? 1 : 0;
+ case 6:
+ return (_andyObject->spriteNum == 0) ? 1 : 0;
+ case 7:
+ if ((_andyObject->flags0 & 0x1F) == 7) {
+ AndyLvlObjectData *andyData = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (andyData) {
+ LvlObject *o = andyData->shootLvlObject;
+ if (o && o->dataPtr) {
+ ShootLvlObjectData *data = (ShootLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ return (data->type == 4) ? 1 : 0;
+ }
+ }
+ }
+ break;
+ case 8:
+ if ((_andyObject->flags0 & 0x1F) == 7) {
+ AndyLvlObjectData *andyData = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (andyData) {
+ LvlObject *o = andyData->shootLvlObject;
+ if (o && o->dataPtr) {
+ ShootLvlObjectData *data = (ShootLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ return (data->type == 0) ? 1 : 0;
+ }
+ }
+ }
+ default:
+ warning("getTaskAndyVar unhandled index %d", index);
+ break;
+ }
+ return 0;
+}
+
+int Game::getTaskOtherVar(int index, Task *t) const {
+ switch (index) {
+ case 0:
+ return _mstAndyScreenPosX;
+ case 1:
+ return _mstAndyScreenPosY;
+ case 2:
+ return _mstAndyLevelPosX;
+ case 3:
+ return _mstAndyLevelPosY;
+ case 4:
+ return _currentScreen;
+ case 5:
+ return _res->_screensState[_currentScreen].s0;
+ case 6:
+ return _difficulty;
+ case 7:
+ if (t->monster1 && t->monster1->shootData) {
+ return t->monster1->shootData->type;
+ }
+ return _andyShootsTable[0].type;
+ case 8:
+ if (t->monster1 && t->monster1->shootData) {
+ return t->monster1->shootData->directionMask & 0x7F;
+ }
+ return _andyShootsTable[0].directionMask & 0x7F;
+ case 9:
+ if (t->monster1 && t->monster1->action) {
+ return t->monster1->action->xPos;
+ }
+ break;
+ case 10:
+ if (t->monster1 && t->monster1->action) {
+ return t->monster1->action->yPos;
+ }
+ break;
+ case 11:
+ if (t->monster1) {
+ return t->monster1->shootSource;
+ }
+ break;
+ case 12:
+ if (t->monster1) {
+ return t->monster1->xPos;
+ } else if (t->monster2) {
+ return t->monster2->xPos;
+ }
+ break;
+ case 13:
+ if (t->monster1) {
+ return t->monster1->yPos;
+ } else if (t->monster2) {
+ return t->monster2->yPos;
+ }
+ break;
+ case 14:
+ if (t->monster1) {
+ return t->monster1->o16->screenNum;
+ } else if (t->monster2) {
+ return t->monster2->o->screenNum;
+ }
+ break;
+ case 15:
+ if (t->monster1) {
+ return t->monster1->xDelta;
+ } else if (t->monster2) {
+ return ABS(_mstAndyLevelPosX - t->monster2->xMstPos);
+ }
+ break;
+ case 16:
+ if (t->monster1) {
+ return t->monster1->yDelta;
+ } else if (t->monster2) {
+ return ABS(_mstAndyLevelPosY - t->monster2->yMstPos);
+ }
+ break;
+ case 17:
+ if (t->monster1) {
+ return t->monster1->collideDistance;
+ }
+ break;
+ case 18:
+ if (t->monster1) {
+ return ABS(t->monster1->levelPosBounds_x1 - t->monster1->xMstPos);
+ }
+ break;
+ case 19:
+ if (t->monster1) {
+ return ABS(t->monster1->levelPosBounds_x2 - t->monster1->xMstPos);
+ }
+ break;
+ case 20:
+ if (t->monster1) {
+ return ABS(t->monster1->levelPosBounds_y1 - t->monster1->yMstPos);
+ }
+ break;
+ case 21:
+ if (t->monster1) {
+ return ABS(t->monster1->levelPosBounds_y2 - t->monster1->yMstPos);
+ }
+ break;
+ case 22:
+ return _mstOp54Counter;
+ case 23:
+ return _andyObject->actionKeyMask;
+ case 24:
+ return _andyObject->directionKeyMask;
+ case 25:
+ if (t->monster1) {
+ return t->monster1->xMstPos;
+ } else if (t->monster2) {
+ return t->monster2->xMstPos;
+ }
+ break;
+ case 26:
+ if (t->monster1) {
+ return t->monster1->yMstPos;
+ } else if (t->monster2) {
+ return t->monster2->yMstPos;
+ }
+ break;
+ case 27:
+ if (t->monster1) {
+ return t->monster1->o16->flags0 & 0xFF;
+ } else if (t->monster2) {
+ return t->monster2->o->flags0 & 0xFF;
+ }
+ break;
+ case 28:
+ if (t->monster1) {
+ return t->monster1->o16->anim;
+ } else if (t->monster2) {
+ return t->monster2->o->anim;
+ }
+ break;
+ case 29:
+ if (t->monster1) {
+ return t->monster1->o16->frame;
+ } else if (t->monster2) {
+ return t->monster2->o->frame;
+ }
+ break;
+ case 30:
+ return _mstOp56Counter;
+ case 31:
+ return _executeMstLogicCounter;
+ case 32:
+ return _executeMstLogicCounter - _executeMstLogicPrevCounter;
+ case 33: {
+ LvlObject *o = 0;
+ if (t->monster1) {
+ o = t->monster1->o16;
+ } else if (t->monster2) {
+ o = t->monster2->o;
+ }
+ if (o) {
+ return _res->_screensState[o->screenNum].s0;
+ }
+ }
+ break;
+ case 34:
+ return _level->_checkpoint;
+ case 35:
+ return _mstAndyCurrentScreenNum;
+ default:
+ warning("getTaskOtherVar unhandled index %d", index);
+ break;
+ }
+ return 0;
+}
+
+int Game::getTaskFlag(Task *t, int num, int type) const {
+ switch (type) {
+ case 1:
+ return num;
+ case 2:
+ return ((t->flags & (1 << num)) != 0) ? 1 : 0;
+ case 3:
+ return ((_mstFlags & (1 << num)) != 0) ? 1 : 0;
+ case 4:
+ return getTaskAndyVar(num, t);
+ case 5: {
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ return ((m->flags48 & (1 << num)) != 0) ? 1 : 0;
+ }
+ }
+ default:
+ warning("getTaskFlag unhandled type %d num %d", type, num);
+ break;
+ }
+ return 0;
+}
+
+int Game::mstTask_main(Task *t) {
+ assert(t->codeData);
+ const int taskNum = t - _tasksTable;
+ int ret = 0;
+ t->state &= ~2;
+ const uint8_t *p = t->codeData;
+ do {
+ assert(p >= _res->_mstCodeData && p < _res->_mstCodeData + _res->_mstHdr.codeSize * 4);
+ assert(((p - t->codeData) & 3) == 0);
+ const uint32_t codeOffset = p - _res->_mstCodeData;
+ debug(kDebug_MONSTER, "executeMstCode task %d %p code %d offset 0x%04x", taskNum, t, p[0], codeOffset);
+ assert(p[0] <= 242);
+ switch (p[0]) {
+ case 0: { // 0
+ LvlObject *o = 0;
+ if (t->monster1) {
+ if ((t->monster1->flagsA6 & 2) == 0) {
+ o = t->monster1->o16;
+ }
+ } else if (t->monster2) {
+ o = t->monster2->o;
+ }
+ if (o) {
+ o->actionKeyMask = 0;
+ o->directionKeyMask = 0;
+ }
+ }
+ // fall-through
+ case 1: { // 1
+ const int num = READ_LE_UINT16(p + 2);
+ const int delay = getTaskVar(t, num, p[1]);
+ t->arg1 = delay;
+ if (delay > 0) {
+ if (p[0] == 0) {
+ t->run = &Game::mstTask_wait2;
+ ret = 1;
+ } else {
+ t->run = &Game::mstTask_wait1;
+ ret = 1;
+ }
+ }
+ }
+ break;
+ case 2: { // 2 - set_var_random_range
+ const int num = READ_LE_UINT16(p + 2);
+ MstOp2Data *m = &_res->_mstOp2Data[num];
+ int a = getTaskVar(t, m->indexVar1, m->maskVars >> 4);
+ int b = getTaskVar(t, m->indexVar2, m->maskVars & 15);
+ if (a > b) {
+ SWAP(a, b);
+ }
+ a += _rnd.update() % (b - a + 1);
+ setTaskVar(t, m->unkA, m->unk9, a);
+ }
+ break;
+ case 3:
+ case 8: // 3 - set_monster_action_direction_imm
+ if (t->monster1) {
+ const int num = READ_LE_UINT16(p + 2);
+ const int arg = _res->_mstActionDirectionData[num].unk3;
+ t->codeData = p;
+ ret = mstTaskSetActionDirection(t, num, (arg == 0xFF) ? -1 : arg);
+ }
+ break;
+ case 4: // 4 - set_monster_action_direction_task_var
+ if (t->monster1) {
+ const int num = READ_LE_UINT16(p + 2);
+ const int arg = _res->_mstActionDirectionData[num].unk3;
+ t->codeData = p;
+ assert(arg < kMaxLocals);
+ ret = mstTaskSetActionDirection(t, num, t->localVars[arg]);
+ }
+ break;
+ case 13: // 8
+ if (t->monster1) {
+ const int num = READ_LE_UINT16(p + 2);
+ if (mstTestActionDirection(t->monster1, num)) {
+ const int arg = _res->_mstActionDirectionData[num].unk3;
+ t->codeData = p;
+ ret = mstTaskSetActionDirection(t, num, (arg == 0xFF) ? -1 : arg);
+ }
+ }
+ break;
+ case 23: // 13 - set_flag_global
+ _mstFlags |= (1 << p[1]);
+ break;
+ case 24: // 14 - set_flag_task
+ t->flags |= (1 << p[1]);
+ break;
+ case 25: { // 15 - set_flag_mst
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ m->flags48 |= (1 << p[1]);
+ }
+ }
+ break;
+ case 26: // 16 - clear_flag_global
+ _mstFlags &= ~(1 << p[1]);
+ break;
+ case 27: // 17 - clear_flag_task
+ t->flags &= ~(1 << p[1]);
+ break;
+ case 28: { // 18 - clear_flag_mst
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ m->flags48 &= ~(1 << p[1]);
+ }
+ }
+ break;
+ case 30: { // 20
+ t->arg1 = 3;
+ t->arg2 = p[1];
+ if (((1 << p[1]) & _mstFlags) == 0) {
+ LvlObject *o = 0;
+ if (t->monster1) {
+ if ((t->monster1->flagsA6 & 2) == 0) {
+ o = t->monster1->o16;
+ }
+ } else if (t->monster2) {
+ o = t->monster2->o;
+ }
+ if (o) {
+ o->actionKeyMask = 0;
+ o->directionKeyMask = 0;
+ }
+ t->run = &Game::mstTask_wait3;
+ ret = 1;
+ }
+ }
+ break;
+ case 32: { // 22
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ t->arg1 = 5;
+ t->arg2 = p[1];
+ if (((1 << p[1]) & m->flags48) == 0) {
+ LvlObject *o = 0;
+ if (t->monster1) {
+ if ((t->monster1->flagsA6 & 2) == 0) {
+ o = t->monster1->o16;
+ }
+ } else if (t->monster2) {
+ o = t->monster2->o;
+ }
+ if (o) {
+ o->actionKeyMask = 0;
+ o->directionKeyMask = 0;
+ }
+ t->run = &Game::mstTask_wait3;
+ ret = 1;
+ }
+ }
+ }
+ break;
+ case 33:
+ case 229: { // 23 - jmp_imm
+ const int num = READ_LE_UINT16(p + 2);
+ p = _res->_mstCodeData + num * 4 - 4;
+ }
+ break;
+ case 34:
+ // no-op
+ break;
+ case 35: { // 24 - enable_trigger
+ const int num = READ_LE_UINT16(p + 2);
+ _res->flagMstCodeForPos(num, 1);
+ }
+ break;
+ case 36: { // 25 - disable_trigger
+ const int num = READ_LE_UINT16(p + 2);
+ _res->flagMstCodeForPos(num, 0);
+ }
+ break;
+ case 39: // 26 - remove_monsters_screen
+ if (p[1] < _res->_mstHdr.screensCount) {
+ mstOp26_removeMstTaskScreen(&_monsterObjects1TasksList, p[1]);
+ mstOp26_removeMstTaskScreen(&_monsterObjects2TasksList, p[1]);
+ // mstOp26_removeMstTaskScreen(&_mstTasksList3, p[1]);
+ // mstOp26_removeMstTaskScreen(&_mstTasksList4, p[1]);
+ }
+ break;
+ case 40: // 27 - remove_monsters_screen_type
+ if (p[1] < _res->_mstHdr.screensCount) {
+ mstOp27_removeMstTaskScreenType(&_monsterObjects1TasksList, p[1], p[2]);
+ mstOp27_removeMstTaskScreenType(&_monsterObjects2TasksList, p[1], p[2]);
+ // mstOp27_removeMstTaskScreenType(&_mstTasksList3, p[1], p[2]);
+ // mstOp27_removeMstTaskScreenType(&_mstTasksList4, p[1], p[2]);
+ }
+ break;
+ case 41: { // 28 - increment_task_var
+ assert(p[1] < kMaxLocals);
+ ++t->localVars[p[1]];
+ }
+ break;
+ case 42: { // 29 - increment_global_var
+ const int num = p[1];
+ assert(num < kMaxVars);
+ ++_mstVars[num];
+ }
+ break;
+ case 43: { // 30 - increment_monster_var
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ const int num = p[1];
+ assert(num < kMaxLocals);
+ ++m->localVars[num];
+ }
+ }
+ break;
+ case 44: { // 31 - decrement_task_var
+ const int num = p[1];
+ assert(num < kMaxLocals);
+ --t->localVars[num];
+ }
+ break;
+ case 45: { // 32 - decrement_global_var
+ const int num = p[1];
+ assert(num < kMaxVars);
+ --_mstVars[num];
+ }
+ break;
+ case 47:
+ case 48:
+ case 49:
+ case 50:
+ case 51:
+ case 52:
+ case 53:
+ case 54:
+ case 55:
+ case 56: { // 34 - arith_task_var_task_var
+ assert(p[1] < kMaxLocals);
+ assert(p[2] < kMaxLocals);
+ arithOp(p[0] - 47, &t->localVars[p[1]], t->localVars[p[2]]);
+ }
+ break;
+ case 57:
+ case 58:
+ case 59:
+ case 60:
+ case 61:
+ case 62:
+ case 63:
+ case 64:
+ case 65:
+ case 66: { // 35 - arith_global_var_task_var
+ assert(p[1] < kMaxVars);
+ assert(p[2] < kMaxLocals);
+ arithOp(p[0] - 57, &_mstVars[p[1]], t->localVars[p[2]]);
+ if (p[1] == 31 && _mstVars[31] > 0) {
+ _mstTickDelay = _mstVars[31];
+ }
+ }
+ break;
+ case 67:
+ case 68:
+ case 69:
+ case 70:
+ case 71:
+ case 72:
+ case 73:
+ case 74:
+ case 75:
+ case 76: { // 36
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ assert(p[1] < kMaxLocals);
+ assert(p[2] < kMaxLocals);
+ arithOp(p[0] - 67, &m->localVars[p[1]], t->localVars[p[2]]);
+ }
+ }
+ break;
+ case 77:
+ case 78:
+ case 79:
+ case 80:
+ case 81:
+ case 82:
+ case 83:
+ case 84:
+ case 85:
+ case 86: { // 37
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ assert(p[1] < kMaxLocals);
+ assert(p[2] < kMaxLocals);
+ arithOp(p[0] - 77, &t->localVars[p[1]], m->localVars[p[2]]);
+ }
+ }
+ break;
+ case 87:
+ case 88:
+ case 89:
+ case 90:
+ case 91:
+ case 92:
+ case 93:
+ case 94:
+ case 95:
+ case 96: { // 38
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ assert(p[1] < kMaxVars);
+ assert(p[2] < kMaxLocals);
+ arithOp(p[0] - 87, &_mstVars[p[1]], m->localVars[p[2]]);
+ if (p[1] == 31 && _mstVars[31] > 0) {
+ _mstTickDelay = _mstVars[31];
+ }
+ }
+ }
+ break;
+ case 97:
+ case 98:
+ case 99:
+ case 100:
+ case 101:
+ case 102:
+ case 103:
+ case 104:
+ case 105:
+ case 106: { // 39
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ assert(p[1] < kMaxLocals);
+ assert(p[2] < kMaxLocals);
+ arithOp(p[0] - 97, &m->localVars[p[1]], m->localVars[p[2]]);
+ }
+ }
+ break;
+ case 107:
+ case 108:
+ case 109:
+ case 110:
+ case 111:
+ case 112:
+ case 113:
+ case 114:
+ case 115:
+ case 116: { // 40
+ assert(p[1] < kMaxLocals);
+ assert(p[2] < kMaxVars);
+ arithOp(p[0] - 107, &t->localVars[p[1]], _mstVars[p[2]]);
+ }
+ break;
+ case 117:
+ case 118:
+ case 119:
+ case 120:
+ case 121:
+ case 122:
+ case 123:
+ case 124:
+ case 125:
+ case 126: { // 41
+ assert(p[1] < kMaxVars);
+ assert(p[2] < kMaxVars);
+ arithOp(p[0] - 117, &_mstVars[p[1]], _mstVars[p[2]]);
+ if (p[1] == 31 && _mstVars[31] > 0) {
+ _mstTickDelay = _mstVars[31];
+ }
+ }
+ break;
+ case 127:
+ case 128:
+ case 129:
+ case 130:
+ case 131:
+ case 132:
+ case 133:
+ case 134:
+ case 135:
+ case 136: { // 42
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ assert(p[1] < kMaxLocals);
+ assert(p[2] < kMaxVars);
+ arithOp(p[0] - 127, &m->localVars[p[1]], _mstVars[p[2]]);
+ }
+ }
+ break;
+ case 137:
+ case 138:
+ case 139:
+ case 140:
+ case 141:
+ case 142:
+ case 143:
+ case 144:
+ case 145:
+ case 146: { // 43
+ const int num = p[2];
+ assert(p[1] < kMaxLocals);
+ arithOp(p[0] - 137, &t->localVars[p[1]], getTaskOtherVar(num, t));
+ }
+ break;
+ case 147:
+ case 148:
+ case 149:
+ case 150:
+ case 151:
+ case 152:
+ case 153:
+ case 154:
+ case 155:
+ case 156: { // 44
+ const int num = p[2];
+ assert(p[1] < kMaxVars);
+ arithOp(p[0] - 147, &_mstVars[p[1]], getTaskOtherVar(num, t));
+ if (p[1] == 31 && _mstVars[31] > 0) {
+ _mstTickDelay = _mstVars[31];
+ }
+ }
+ break;
+ case 157:
+ case 158:
+ case 159:
+ case 160:
+ case 161:
+ case 162:
+ case 163:
+ case 164:
+ case 165:
+ case 166: { // 45
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ const int num = p[2];
+ assert(p[1] < kMaxLocals);
+ arithOp(p[0] - 157, &m->localVars[p[1]], getTaskOtherVar(num, t));
+ }
+ }
+ break;
+ case 167:
+ case 168:
+ case 169:
+ case 170:
+ case 171:
+ case 172:
+ case 173:
+ case 174:
+ case 175:
+ case 176: { // 46
+ const int16_t num = READ_LE_UINT16(p + 2);
+ assert(p[1] < kMaxLocals);
+ arithOp(p[0] - 167, &t->localVars[p[1]], num);
+ }
+ break;
+ case 177:
+ case 178:
+ case 179:
+ case 180:
+ case 181:
+ case 182:
+ case 183:
+ case 184:
+ case 185:
+ case 186: { // 47
+ const int16_t num = READ_LE_UINT16(p + 2);
+ assert(p[1] < kMaxVars);
+ arithOp(p[0] - 177, &_mstVars[p[1]], num);
+ if (p[1] == 31 && _mstVars[31] > 0) {
+ _mstTickDelay = _mstVars[31];
+ }
+ }
+ break;
+ case 187:
+ case 188:
+ case 189:
+ case 190:
+ case 191:
+ case 192:
+ case 193:
+ case 194:
+ case 195:
+ case 196: { // 48 - arith_monster_var_imm
+ MonsterObject1 *m = 0;
+ if (t->monster2) {
+ m = t->monster2->monster1;
+ } else {
+ m = t->monster1;
+ }
+ if (m) {
+ const int16_t num = READ_LE_UINT16(p + 2);
+ assert(p[1] < kMaxLocals);
+ arithOp(p[0] - 187, &m->localVars[p[1]], num);
+ }
+ }
+ break;
+ case 197: // 49
+ if (t->monster1) {
+ const int num = READ_LE_UINT16(p + 2);
+ const MstOp197Data *op197Data = &_res->_mstOp197Data[num];
+ const uint32_t mask = op197Data->maskVars;
+ int a = getTaskVar(t, op197Data->unk0, (mask >> 16) & 15); // var1C
+ int b = getTaskVar(t, op197Data->unk2, (mask >> 12) & 15); // x2
+ int c = getTaskVar(t, op197Data->unk4, (mask >> 8) & 15); // var14
+ int d = getTaskVar(t, op197Data->unk6, (mask >> 4) & 15);
+ int e = getTaskVar(t, op197Data->unkE, mask & 15);
+ const int screenNum = CLIP(e, -4, _res->_mstHdr.screensCount - 1);
+ ret = mstOp49_setMovingBounds(a, b, c, d, screenNum, t, num);
+ }
+ break;
+ case 198: { // 50 - call_task
+ Task *child = findFreeTask();
+ if (child) {
+ t->codeData = p + 4;
+ memcpy(child, t, sizeof(Task));
+ t->child = child;
+ const uint16_t num = READ_LE_UINT16(p + 2);
+ const uint32_t codeData = _res->_mstUnk60[num];
+ assert(codeData != kNone);
+ p = _res->_mstCodeData + codeData * 4;
+ t->codeData = p;
+ t->state &= ~2;
+ p -= 4;
+ }
+ }
+ break;
+ case 199: // 51 - stop_monster
+ mstTaskStopMonsterObject1(t);
+ return 0;
+ case 200: // 52 - stop_monster_action
+ if (t->monster1 && t->monster1->action) {
+ mstOp52();
+ return 1;
+ }
+ mstOp52();
+ break;
+ case 201: { // 53 - start_monster_action
+ const int num = READ_LE_UINT16(p + 2);
+ mstOp53(&_res->_mstMonsterActionData[num]);
+ }
+ break;
+ case 202: // 54 - continue_monster_action
+ mstOp54();
+ break;
+ case 203: // 55 - monster_attack
+ // _mstCurrentMonster1 = t->monster1;
+ if (t->monster1) {
+ const int num = READ_LE_UINT16(p + 2);
+ if (mstCollidesByFlags(t->monster1, _res->_mstOp240Data[num].flags)) {
+ t->codeData = p + 4;
+ mstTaskAttack(t, _res->_mstOp240Data[num].codeData, 0x10);
+ t->state &= ~2;
+ p = t->codeData - 4;
+ }
+ }
+ break;
+ case 204: // 56 - special_action
+ ret = mstOp56_specialAction(t, p[1], READ_LE_UINT16(p + 2));
+ break;
+ case 207:
+ case 208:
+ case 209: // 79
+ break;
+ case 210: // 57 - add_worm
+ {
+ MonsterObject1 *m = t->monster1;
+ mstOp57_addWormHoleSprite(m->xPos + (int8_t)p[2], m->yPos + (int8_t)p[3], m->o16->screenNum);
+ }
+ break;
+ case 211: // 58 - add_lvl_object
+ mstOp58_addLvlObject(t, READ_LE_UINT16(p + 2));
+ break;
+ case 212: { // 59
+ LvlObject *o = 0;
+ if (t->monster2) {
+ o = t->monster2->o;
+ } else if (t->monster1) {
+ o = t->monster1->o16;
+ } else {
+ break;
+ }
+ assert(o);
+ int xPos = o->xPos + o->posTable[6].x;
+ int yPos = o->yPos + o->posTable[6].y;
+ const uint16_t flags1 = o->flags1;
+ if (flags1 & 0x10) {
+ xPos -= (int8_t)p[2];
+ } else {
+ xPos += (int8_t)p[2];
+ }
+ if (flags1 & 0x20) {
+ yPos -= (int8_t)p[3];
+ } else {
+ yPos += (int8_t)p[3];
+ }
+ int dirMask = 0;
+ if ((t->monster1 && (t->monster1->monsterInfos[944] == 10 || t->monster1->monsterInfos[944] == 25)) || (t->monster2 && (t->monster2->monster2Info->type == 10 || t->monster2->monster2Info->type == 25))) {
+ int dx = o->posTable[6].x - o->posTable[7].x;
+ int dy = o->posTable[6].y - o->posTable[7].y;
+ if (dx >= 8) {
+ dirMask = 2;
+ } else if (dx < -8) {
+ dirMask = 8;
+ }
+ if (dy >= 8) {
+ dirMask |= 4;
+ } else if (dy < -8) {
+ dirMask |= 1;
+ }
+ } else {
+ dirMask = ((flags1 & 0x10) != 0) ? 8 : 0;
+ }
+ if (p[1] == 255) {
+ int type = 0;
+ switch (dirMask) {
+ case 1:
+ type = 6;
+ break;
+ case 3:
+ type = 3;
+ break;
+ case 4:
+ type = 7;
+ break;
+ case 6:
+ type = 4;
+ break;
+ case 8:
+ type = 5;
+ break;
+ case 9:
+ type = 1;
+ break;
+ case 12:
+ type = 2;
+ break;
+ }
+ mstOp59_addShootSpecialPowers(xPos, yPos, o->screenNum, type, (o->flags2 + 1) & 0xDFFF);
+ } else {
+ int type = 0;
+ switch (dirMask) {
+ case 1:
+ type = 6;
+ break;
+ case 3:
+ type = 3;
+ break;
+ case 4:
+ type = 7;
+ break;
+ case 6:
+ type = 4;
+ break;
+ case 8:
+ type = 5;
+ break;
+ case 9:
+ type = 1;
+ break;
+ case 12:
+ type = 2;
+ break;
+ }
+ mstOp59_addShootFireball(xPos, yPos, o->screenNum, p[1], type, (o->flags2 + 1) & 0xDFFF);
+ }
+ }
+ break;
+ case 213: { // 60 - monster_set_action_direction
+ LvlObject *o = 0;
+ if (t->monster2) {
+ o = t->monster2->o;
+ } else if (t->monster1) {
+ o = t->monster1->o16;
+ }
+ if (o) {
+ o->actionKeyMask = getTaskVar(t, p[2], p[1] >> 4);
+ o->directionKeyMask = getTaskVar(t, p[3], p[1] & 15);
+ }
+ }
+ break;
+ case 214: { // 61 - reset_monster_energy
+ MonsterObject1 *m = t->monster1;
+ if (m) {
+ m->flagsA5 &= ~0xC0;
+ m->localVars[7] = m->behaviorState->energy;
+ }
+ }
+ break;
+ case 215: { // 62
+ if (_m43Num3 != -1) {
+ assert(_m43Num3 < _res->_mstHdr.monsterActionIndexDataCount);
+ shuffleMstMonsterActionIndex(&_res->_mstMonsterActionIndexData[_m43Num3]);
+ }
+ _mstOp54Counter = 0;
+ }
+ break;
+ case 217: { // 64
+ const int16_t num = READ_LE_UINT16(p + 2);
+ if (_m43Num3 != num) {
+ _m43Num3 = num;
+ assert(num >= 0 && num < _res->_mstHdr.monsterActionIndexDataCount);
+ shuffleMstMonsterActionIndex(&_res->_mstMonsterActionIndexData[num]);
+ _mstOp54Counter = 0;
+ }
+ }
+ break;
+ case 218: { // 65
+ const int16_t num = READ_LE_UINT16(p + 2);
+ if (num != _m43Num1) {
+ _m43Num1 = num;
+ _m43Num2 = num;
+ assert(num >= 0 && num < _res->_mstHdr.monsterActionIndexDataCount);
+ shuffleMstMonsterActionIndex(&_res->_mstMonsterActionIndexData[num]);
+ }
+ }
+ break;
+ case 220:
+ case 221:
+ case 222:
+ case 223:
+ case 224:
+ case 225: { // 67 - add_monster
+ const int num = READ_LE_UINT16(p + 2);
+ MstOp223Data *m = &_res->_mstOp223Data[num];
+ const int mask = m->maskVars; // var8
+ int a = getTaskVar(t, m->indexVar1, (mask >> 16) & 15); // var1C
+ int b = getTaskVar(t, m->indexVar2, (mask >> 12) & 15); // var20
+ int c = getTaskVar(t, m->indexVar3, (mask >> 8) & 15); // var14, vb
+ int d = getTaskVar(t, m->indexVar4, (mask >> 4) & 15);
+ int e = getTaskVar(t, m->indexVar5, mask & 15);
+ if (a > b) {
+ SWAP(a, b);
+ }
+ if (c > d) {
+ SWAP(c, d);
+ }
+ LvlObject *o = 0;
+ if (t->monster2) {
+ o = t->monster2->o;
+ } else if (t->monster1) {
+ o = t->monster1->o16;
+ }
+ if (e <= -2 && o) {
+ if (o->flags1 & 0x10) {
+ const int x1 = a;
+ const int x2 = b;
+ a = o->xPos - x2;
+ b = o->xPos - x1;
+ } else {
+ a += o->xPos;
+ b += o->xPos;
+ }
+
+ c += o->yPos;
+ d += o->yPos;
+ if (e < -2) {
+ a += o->posTable[6].x;
+ b += o->posTable[6].x;
+ c += o->posTable[6].y;
+ d += o->posTable[6].y;
+ } else {
+ a += o->posTable[7].x;
+ b += o->posTable[7].x;
+ c += o->posTable[7].y;
+ d += o->posTable[7].y;
+ }
+ e = o->screenNum;
+ }
+ e = CLIP(e, -1, _res->_mstHdr.screensCount - 1);
+ if (p[0] == 224) {
+ _mstOp67_type = m->unk8;
+ _mstOp67_flags1 = m->unk9;
+ _mstOp67_unk = m->unkC;
+ _mstOp67_x1 = a;
+ _mstOp67_x2 = b;
+ _mstOp67_y1 = c;
+ _mstOp67_y2 = d;
+ _mstOp67_screenNum = e;
+ break;
+ } else if (p[0] == 225) {
+ _mstOp68_type = m->unk8;
+ _mstOp68_arg9 = m->unk9;
+ _mstOp68_flags1 = m->unkC;
+ _mstOp68_x1 = a;
+ _mstOp68_x2 = b;
+ _mstOp68_y1 = c;
+ _mstOp68_y2 = d;
+ _mstOp68_screenNum = e;
+ break;
+ } else {
+ t->flags |= 0x80;
+ if (p[0] == 222 || p[0] == 220) {
+ if (e == -1) {
+ if (a >= -_mstAndyScreenPosX && a <= 255 - _mstAndyScreenPosX) {
+ break;
+ }
+ } else if (e == _currentScreen) {
+ break;
+ }
+ }
+ }
+ mstOp67_addMonster(t, a, b, c, d, e, m->unk8, m->unk9, m->unkC, m->unkB, 0, m->unkE);
+ }
+ break;
+ case 226: { // 68 - add_monster_group
+ const int num = READ_LE_UINT16(p + 2);
+ const MstOp226Data *m226Data = &_res->_mstOp226Data[num];
+ int xPos = _res->_mstPointOffsets[_currentScreen].xOffset;
+ int yPos = _res->_mstPointOffsets[_currentScreen].yOffset;
+ int countRight = 0; // var1C
+ int countLeft = 0; // var8
+ int xOffset = m226Data->unk4 * 256;
+ for (int i = 0; i < kMaxMonsterObjects1; ++i) {
+ MonsterObject1 *m = &_monsterObjects1Table[i];
+ if (!m->m46) {
+ continue;
+ }
+ if (m->monsterInfos[944] != _res->_mstMonsterInfos[m226Data->unk0 * kMonsterInfoDataSize + 944]) {
+ continue;
+ }
+ if (!rect_contains(xPos - xOffset, yPos, xPos + xOffset + 256, yPos + 192, m->xMstPos, m->yMstPos)) {
+ continue;
+ }
+ if (_mstAndyLevelPosX > m->xMstPos) {
+ ++countLeft;
+ } else {
+ ++countRight;
+ }
+ }
+ t->flags |= 0x80;
+ const int total = countRight + countLeft;
+ if (total >= m226Data->unk3) {
+ break;
+ }
+ int vc = m226Data->unk3 - total;
+
+ int countType1 = m226Data->unk1;
+ if (countLeft >= countType1) {
+ countType1 = 0;
+ } else {
+ countType1 -= countLeft;
+ }
+ int countType2 = m226Data->unk2;
+ if (countRight >= countType2) {
+ countType2 = 0;
+ } else {
+ countType2 -= countRight;
+ }
+ if (countType1 != 0) {
+ if (_mstOp67_screenNum < 0) {
+ if (_mstOp67_x2 >= -_mstAndyScreenPosX && _mstOp67_x1 <= 255 - _mstAndyScreenPosX) {
+ countType1 = 0;
+ }
+ } else if (_mstOp67_screenNum == _currentScreen) {
+ countType1 = 0;
+ }
+ }
+ if (countType2 != 0) {
+ if (_mstOp68_screenNum < 0) {
+ if (_mstOp68_x2 >= -_mstAndyScreenPosX && _mstOp68_x1 <= 255 -_mstAndyScreenPosX) {
+ countType2 = 0;
+ }
+ } else if (_mstOp68_screenNum == _currentScreen) {
+ countType2 = 0;
+ }
+ }
+ if (countType1 != 0 || countType2 != 0) {
+ mstOp68_addMonsterGroup(t, _res->_mstMonsterInfos + m226Data->unk0 * kMonsterInfoDataSize, countType1, countType2, vc, m226Data->unk6);
+ }
+ }
+ break;
+ case 227: { // 69 - compare_vars
+ const int num = READ_LE_UINT16(p + 2);
+ assert(num < _res->_mstHdr.op227DataCount);
+ const MstOp227Data *m = &_res->_mstOp227Data[num];
+ const int a = getTaskVar(t, m->indexVar1, m->maskVars & 15);
+ const int b = getTaskVar(t, m->indexVar2, m->maskVars >> 4);
+ if (compareOp(m->compare, a, b)) {
+ assert(m->codeData < _res->_mstHdr.codeSize);
+ p = _res->_mstCodeData + m->codeData * 4 - 4;
+ }
+ }
+ break;
+ case 228: { // 70 - compare_flags
+ const int num = READ_LE_UINT16(p + 2);
+ assert(num < _res->_mstHdr.op227DataCount);
+ const MstOp227Data *m = &_res->_mstOp227Data[num];
+ const int a = getTaskFlag(t, m->indexVar1, m->maskVars & 15);
+ const int b = getTaskFlag(t, m->indexVar2, m->maskVars >> 4);
+ if (compareOp(m->compare, a, b)) {
+ assert(m->codeData < _res->_mstHdr.codeSize);
+ p = _res->_mstCodeData + m->codeData * 4 - 4;
+ }
+ }
+ break;
+ case 231:
+ case 232: { // 71 - compare_flags_monster
+ const int num = READ_LE_UINT16(p + 2);
+ const MstOp234Data *m = &_res->_mstOp234Data[num];
+ const int a = getTaskFlag(t, m->indexVar1, m->maskVars & 15);
+ const int b = getTaskFlag(t, m->indexVar2, m->maskVars >> 4);
+ if (compareOp(m->compare, a, b)) {
+ if (p[0] == 231) {
+ LvlObject *o = 0;
+ if (t->monster1) {
+ if ((t->monster1->flagsA6 & 2) == 0) {
+ o = t->monster1->o16;
+ }
+ } else if (t->monster2) {
+ o = t->monster2->o;
+ }
+ if (o) {
+ o->actionKeyMask = 0;
+ o->directionKeyMask = 0;
+ }
+ t->arg2 = num;
+ t->run = &Game::mstTask_mstOp231;
+ ret = 1;
+ }
+ } else {
+ if (p[0] == 232) {
+ LvlObject *o = 0;
+ if (t->monster1) {
+ if ((t->monster1->flagsA6 & 2) == 0) {
+ o = t->monster1->o16;
+ }
+ } else if (t->monster2) {
+ o = t->monster2->o;
+ }
+ if (o) {
+ o->actionKeyMask = 0;
+ o->directionKeyMask = 0;
+ }
+ t->arg2 = num;
+ t->run = &Game::mstTask_mstOp232;
+ ret = 1;
+ }
+ }
+ }
+ break;
+ case 233:
+ case 234: { // 72 - compare_vars_monster
+ const int num = READ_LE_UINT16(p + 2);
+ const MstOp234Data *m = &_res->_mstOp234Data[num];
+ const int a = getTaskVar(t, m->indexVar1, m->maskVars & 15);
+ const int b = getTaskVar(t, m->indexVar2, m->maskVars >> 4);
+ if (compareOp(m->compare, a, b)) {
+ if (p[0] == 233) {
+ LvlObject *o = 0;
+ if (t->monster1) {
+ if ((t->monster1->flagsA6 & 2) == 0) {
+ o = t->monster1->o16;
+ }
+ } else if (t->monster2) {
+ o = t->monster2->o;
+ }
+ if (o) {
+ o->actionKeyMask = 0;
+ o->directionKeyMask = 0;
+ }
+ t->arg2 = num;
+ t->run = &Game::mstTask_mstOp233;
+ ret = 1;
+ }
+ } else {
+ if (p[0] == 234) {
+ LvlObject *o = 0;
+ if (t->monster1) {
+ if ((t->monster1->flagsA6 & 2) == 0) {
+ o = t->monster1->o16;
+ }
+ } else if (t->monster2) {
+ o = t->monster2->o;
+ }
+ if (o) {
+ o->actionKeyMask = 0;
+ o->directionKeyMask = 0;
+ }
+ t->arg2 = num;
+ t->run = &Game::mstTask_mstOp234;
+ ret = 1;
+ }
+ }
+ }
+ break;
+ case 237: // 74 - remove_monster_task
+ if (t->monster1) {
+ if (!t->monster2) {
+ mstRemoveMonsterObject1(t, &_monsterObjects1TasksList);
+ return 1;
+ }
+ mstRemoveMonsterObject2(t, &_monsterObjects2TasksList);
+ return 1;
+ } else {
+ if (t->monster2) {
+ mstRemoveMonsterObject2(t, &_monsterObjects2TasksList);
+ return 1;
+ }
+ }
+ break;
+ case 238: { // 75 - jmp
+ const int i = READ_LE_UINT16(p + 2);
+ const uint32_t codeData = _res->_mstUnk60[i];
+ assert(codeData != kNone);
+ p = _res->_mstCodeData + codeData * 4;
+ t->codeData = p;
+ p -= 4;
+ }
+ break;
+ case 239: { // 76 - create_task
+ const int i = READ_LE_UINT16(p + 2);
+ const uint32_t codeData = _res->_mstUnk60[i];
+ assert(codeData != kNone);
+ createTask(_res->_mstCodeData + codeData * 4);
+ }
+ break;
+ case 240: { // 77 - update_task
+ const int num = READ_LE_UINT16(p + 2);
+ MstOp240Data *m = &_res->_mstOp240Data[num];
+ const uint8_t *codeData = (m->codeData == kNone) ? 0 : (_res->_mstCodeData + m->codeData * 4);
+ updateTask(t, m->flags, codeData);
+ }
+ break;
+ case 242: // 78 - terminate
+ debug(kDebug_MONSTER, "child %p monster1 %p monster2 %p", t->child, t->monster1, t->monster2);
+ if (t->child) {
+ Task *child = t->child;
+ child->prevPtr = t->prevPtr;
+ child->nextPtr = t->nextPtr;
+ memcpy(t, child, sizeof(Task));
+ t->child = 0;
+ t->state &= ~2;
+ child->codeData = 0;
+ MonsterObject1 *m = t->monster1;
+ if (m) {
+ m->flagsA5 &= ~0x70;
+ if ((m->flagsA5 & 8) != 0 && !m->action) {
+ mstTaskResetMonster1Direction(t);
+ return 1;
+ }
+ }
+ return 0;
+ } else if (t->monster1) {
+ MonsterObject1 *m = t->monster1;
+ if (m->flagsA6 & 4) {
+ return 1;
+ }
+ if ((m->flagsA5 & 0x80) == 0) {
+ if ((m->flagsA5 & 8) == 0) {
+ m->flags48 |= 8;
+ if ((m->flagsA5 & 0x70) != 0) {
+ m->flagsA5 &= ~0x70;
+ switch (m->flagsA5 & 7) {
+ case 1:
+ case 2: {
+ const uint32_t codeData = mstMonster1GetNextWalkCode(m);
+ assert(codeData != kNone);
+ resetTask(t, _res->_mstCodeData + codeData * 4);
+ t->state &= ~2;
+ p = t->codeData - 4;
+ }
+ break;
+ case 5:
+ return mstTaskInitMonster1Type1(t);
+ case 6:
+ return mstTaskInitMonster1Type2(t, 1);
+ }
+ } else {
+ const uint32_t codeData = mstMonster1GetNextWalkCode(m);
+ assert(codeData != kNone);
+ resetTask(t, _res->_mstCodeData + codeData * 4);
+ t->state &= ~2;
+ const int counter = m->executeCounter;
+ m->executeCounter = _executeMstLogicCounter;
+ p = t->codeData - 4;
+ if (m->executeCounter == counter) {
+ if ((m->flagsA6 & 2) == 0) {
+ if (m->o16) {
+ m->o16->actionKeyMask = 0;
+ m->o16->directionKeyMask = 0;
+ }
+ }
+ ret = 1;
+ }
+ }
+ } else {
+ if (m->action) {
+ mstMonster1ClearChasingMonster(m);
+ }
+ m->flagsA5 = (m->flagsA5 & ~0xF) | 6;
+ return mstTaskInitMonster1Type2(t, 1);
+ }
+ } else if ((m->flagsA5 & 8) != 0) {
+ m->flagsA5 &= ~8;
+ const uint32_t codeData = m->behaviorState->codeData;
+ if (codeData != kNone) {
+ resetTask(t, _res->_mstCodeData + codeData * 4);
+ return 0;
+ } else {
+ m->o16->actionKeyMask = 7;
+ m->o16->directionKeyMask = 0;
+ t->run = &Game::mstTask_idle;
+ return 1;
+ }
+ } else {
+ t->run = &Game::mstTask_idle;
+ return 1;
+ }
+ } else if (t->monster2) {
+ mstRemoveMonsterObject2(t, &_monsterObjects2TasksList);
+ ret = 1;
+ } else {
+ if ((t->state & 1) != 0 && _mstVars[31] == 0) {
+ _mstVars[31] = _mstTickDelay;
+ }
+ removeTask(&_tasksList, t);
+ ret = 1;
+ }
+ break;
+ default:
+ warning("Unhandled opcode %d in mstTask_main", *p);
+ break;
+ }
+ p += 4;
+ if ((t->state & 2) != 0) {
+ t->state &= ~2;
+ p = t->codeData;
+ }
+ ++_runTaskOpcodesCount;
+ if (_runTaskOpcodesCount >= 128) { // prevent infinite loop
+ warning("Stopping task %p, counter %d", t, _runTaskOpcodesCount);
+ break;
+ }
+ } while (ret == 0);
+ if (t->codeData) {
+ t->codeData = p;
+ }
+ return 1;
+}
+
+void Game::mstOp26_removeMstTaskScreen(Task **tasksList, int screenNum) {
+ Task *current = *tasksList;
+ while (current) {
+ MonsterObject1 *m = current->monster1;
+ Task *next = current->nextPtr;
+ if (m && m->o16->screenNum == screenNum) {
+ if (_mstActionNum != -1 && (m->flagsA5 & 8) != 0 && m->action) {
+ mstMonster1ClearChasingMonster(m);
+ }
+ if (m->monsterInfos[946] & 4) {
+ mstBoundingBoxClear(m, 0);
+ mstBoundingBoxClear(m, 1);
+ }
+ mstMonster1ResetData(m);
+ removeLvlObject2(m->o16);
+ removeTask(tasksList, current);
+ } else {
+ MonsterObject2 *mo = current->monster2;
+ if (mo && mo->o->screenNum == screenNum) {
+ mo->monster2Info = 0;
+ mo->o->dataPtr = 0;
+ removeLvlObject2(mo->o);
+ removeTask(tasksList, current);
+ }
+ }
+ current = next;
+ }
+}
+
+void Game::mstOp27_removeMstTaskScreenType(Task **tasksList, int screenNum, int type) {
+ Task *current = *tasksList;
+ while (current) {
+ MonsterObject1 *m = current->monster1;
+ Task *next = current->nextPtr;
+ if (m && m->o16->screenNum == screenNum && (m->monsterInfos[944] & 0x7F) == type) {
+ if (_mstActionNum != -1 && (m->flagsA5 & 8) != 0 && m->action) {
+ mstMonster1ClearChasingMonster(m);
+ }
+ if (m->monsterInfos[946] & 4) {
+ mstBoundingBoxClear(m, 0);
+ mstBoundingBoxClear(m, 1);
+ }
+ mstMonster1ResetData(m);
+ removeLvlObject2(m->o16);
+ removeTask(tasksList, current);
+ } else {
+ MonsterObject2 *mo = current->monster2;
+ if (mo && mo->o->screenNum == screenNum && (mo->monster2Info->type & 0x7F) == type) {
+ mo->monster2Info = 0;
+ mo->o->dataPtr = 0;
+ removeLvlObject2(mo->o);
+ removeTask(tasksList, current);
+ }
+ }
+ current = next;
+ }
+}
+
+int Game::mstOp49_setMovingBounds(int a, int b, int c, int d, int screen, Task *t, int num) {
+ debug(kDebug_MONSTER, "mstOp49 %d %d %d %d %d %d", a, b, c, d, screen, num);
+ MonsterObject1 *m = t->monster1;
+ const MstOp197Data *op197Data = &_res->_mstOp197Data[num];
+ MstMovingBounds *m49 = &_res->_mstMovingBoundsData[op197Data->indexUnk49];
+ m->m49 = m49;
+ m->indexUnk49Unk1 = op197Data->unkF;
+ if (m->indexUnk49Unk1 < 0) {
+ if (m49->indexDataCount == 0) {
+ m->indexUnk49Unk1 = 0;
+ } else {
+ m->indexUnk49Unk1 = m49->indexData[_rnd.getMstNextNumber(m->rnd_m49)];
+ }
+ }
+ assert((uint32_t)m->indexUnk49Unk1 < m49->count1);
+ m->m49Unk1 = &m49->data1[m->indexUnk49Unk1];
+ m->goalScreenNum = screen;
+ if (a > b) {
+ m->goalDistance_x1 = b;
+ m->goalDistance_x2 = a;
+ } else {
+ m->goalDistance_x1 = a;
+ m->goalDistance_x2 = b;
+ }
+ if (c > d) {
+ m->goalDistance_y1 = d;
+ m->goalDistance_y2 = c;
+ } else {
+ m->goalDistance_y1 = c;
+ m->goalDistance_y2 = d;
+ }
+ switch (screen + 4) {
+ case 1: { // 0xFD
+ m->unkE4 = 255;
+ const uint8_t _al = m->monsterInfos[946];
+ if (_al & 4) {
+ t->run = &Game::mstTask_monsterWait10;
+ } else if (_al & 2) {
+ t->run = &Game::mstTask_monsterWait8;
+ } else {
+ t->run = &Game::mstTask_monsterWait6;
+ }
+ if (m->goalDistance_x2 <= 0) {
+ m->goalScreenNum = kNoScreen;
+ if (m->xMstPos < _mstAndyLevelPosX) {
+ m->goalDistance_x1 = -m->goalDistance_x2;
+ m->goalDistance_x2 = -m->goalDistance_x1;
+ }
+ }
+ if ((_al & 2) != 0 && m->goalDistance_y2 <= 0) {
+ m->goalScreenNum = kNoScreen;
+ if (m->yMstPos < _mstAndyLevelPosY) {
+ m->goalDistance_y1 = -m->goalDistance_y2;
+ m->goalDistance_y2 = -m->goalDistance_y1;
+ }
+ }
+
+ }
+ break;
+ case 0: { // 0xFC
+ const uint8_t _al = m->unkF8;
+ if (_al & 8) {
+ m->goalDistance_x1 = -b;
+ m->goalDistance_x2 = -a;
+ } else if (_al & 2) {
+ m->goalDistance_x1 = a;
+ m->goalDistance_x2 = b;
+ } else {
+ m->goalDistance_x1 = 0;
+ m->goalDistance_x2 = 0;
+ }
+ if (_al & 1) {
+ m->goalDistance_y1 = -d;
+ m->goalDistance_y2 = -c;
+ } else if (_al & 4) {
+ m->goalDistance_y1 = c;
+ m->goalDistance_y2 = d;
+ } else {
+ m->goalDistance_y1 = 0;
+ m->goalDistance_y2 = 0;
+ }
+ }
+ // fall-through
+ case 2: // 0xFE
+ if (m->monsterInfos[946] & 4) {
+ t->run = &Game::mstTask_monsterWait9;
+ } else if (m->monsterInfos[946] & 2) {
+ t->run = &Game::mstTask_monsterWait7;
+ } else {
+ t->run = &Game::mstTask_monsterWait5;
+ }
+ m->goalDistance_x1 += m->xMstPos;
+ m->goalDistance_x2 += m->xMstPos;
+ m->goalDistance_y1 += m->yMstPos;
+ m->goalDistance_y2 += m->yMstPos;
+ break;
+ case 3: // 0xFF
+ if (m->monsterInfos[946] & 4) {
+ t->run = &Game::mstTask_monsterWait10;
+ } else if (m->monsterInfos[946] & 2) {
+ t->run = &Game::mstTask_monsterWait8;
+ } else {
+ t->run = &Game::mstTask_monsterWait6;
+ }
+ break;
+ default:
+ if (m->monsterInfos[946] & 4) {
+ t->run = &Game::mstTask_monsterWait9;
+ } else if (m->monsterInfos[946] & 2) {
+ t->run = &Game::mstTask_monsterWait7;
+ } else {
+ t->run = &Game::mstTask_monsterWait5;
+ }
+ m->goalDistance_x1 += _res->_mstPointOffsets[screen].xOffset;
+ m->goalDistance_x2 += _res->_mstPointOffsets[screen].xOffset;
+ m->goalDistance_y1 += _res->_mstPointOffsets[screen].yOffset;
+ m->goalDistance_y2 += _res->_mstPointOffsets[screen].yOffset;
+ break;
+ }
+ m->goalPos_x1 = m->goalDistance_x1;
+ m->goalPos_x2 = m->goalDistance_x2;
+ m->goalPos_y1 = m->goalDistance_y1;
+ m->goalPos_y2 = m->goalDistance_y2;
+ m->walkBoxNum = 0xFF;
+ m->unkC0 = -1;
+ m->unkBC = -1;
+ m->targetDirectionMask = 0xFF;
+ const uint8_t *ptr = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ if ((ptr[2] & kDirectionKeyMaskVertical) == 0) {
+ m->goalDistance_y1 = m->goalPos_y1 = m->goalDistance_y2 = m->goalPos_y2 = m->yMstPos;
+ }
+ if ((ptr[2] & kDirectionKeyMaskHorizontal) == 0) {
+ m->goalDistance_x1 = m->goalPos_x1 = m->goalDistance_x2 = m->goalPos_x2 = m->xMstPos;
+ }
+ if (m->monsterInfos[946] & 4) {
+ m->unkAB = 0xFF;
+ m->targetLevelPos_x = -1;
+ m->targetLevelPos_y = -1;
+ mstBoundingBoxClear(m, 1);
+ }
+ uint8_t _dl = m->goalScreenNum;
+ if (_dl != 0xFC && (m->flagsA5 & 8) != 0 && (t->flags & 0x20) != 0 && m->action) {
+ if (t->run != &Game::mstTask_monsterWait6 && t->run != &Game::mstTask_monsterWait8 && t->run != &Game::mstTask_monsterWait10) {
+ if ((_dl == 0xFE && m->o16->screenNum != _currentScreen) || (_dl != 0xFE && _dl != _currentScreen)) {
+ if (m->monsterInfos[946] & 4) {
+ mstBoundingBoxClear(m, 1);
+ }
+ return mstTaskStopMonsterObject1(t);
+ }
+ } else {
+ int x1, x2;
+ const int x = MIN(_mstAndyScreenPosX, 255);
+ if (_mstAndyScreenPosX < 0) {
+ x1 = x;
+ x2 = x + 255;
+ } else {
+ x1 = -x;
+ x2 = 255 - x;
+ }
+ const int y = MIN(_mstAndyScreenPosY, 191);
+ int y1, y2;
+ if (_mstAndyScreenPosY < 0) {
+ y1 = y;
+ y2 = y + 191;
+ } else {
+ y1 = -y;
+ y2 = 191 - y;
+ }
+ int vd, vf;
+ if (_dl == 0xFD && m->xMstPos < _mstAndyLevelPosX) {
+ vd = -m->goalDistance_x2;
+ vf = -m->goalDistance_x1;
+ } else {
+ vd = m->goalDistance_x1;
+ vf = m->goalDistance_x2;
+ }
+ uint8_t _bl = m->monsterInfos[946] & 2;
+ int va, vc;
+ if (_bl != 0 && _dl == 0xFD && m->yMstPos < _mstAndyLevelPosY) {
+ va = -m->goalDistance_y2;
+ vc = -m->goalDistance_y1;
+ } else {
+ va = m->goalDistance_y1;
+ vc = m->goalDistance_y2;
+ }
+ if (vd < x1 || vf > x2 || (_bl != 0 && (va < y1 || vc > y2))) {
+ if ((m->monsterInfos[946] & 4) != 0) {
+ mstBoundingBoxClear(m, 1);
+ }
+ return mstTaskStopMonsterObject1(t);
+ }
+ }
+ }
+ const uint8_t *p = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ if ((m->monsterInfos[946] & 4) != 0 && p[0xE] != 0 && m->bboxNum[0] == 0xFF) {
+ const int x1 = m->xMstPos + (int8_t)p[0xC];
+ const int y1 = m->yMstPos + (int8_t)p[0xD];
+ const int x2 = x1 + p[0xE] - 1;
+ const int y2 = y1 + p[0xF] - 1;
+ if (mstBoundingBoxCollides2(m->monster1Index, x1, y1, x2, y2) != 0) {
+ m->indexUnk49Unk1 = 0;
+ m->m49Unk1 = &m->m49->data1[0];
+ m->unkAB = 0xFF;
+ m->targetLevelPos_x = -1;
+ m->targetLevelPos_y = -1;
+ mstBoundingBoxClear(m, 1);
+ if (p[0xE] != 0) {
+ t->flags |= 0x80;
+ mstTaskResetMonster1WalkPath(t);
+ return 0;
+ }
+ } else {
+ m->bboxNum[0] = mstBoundingBoxUpdate(0xFF, m->monster1Index, x1, y1, x2, y2);
+ }
+ }
+ t->flags &= ~0x80;
+ if (m->monsterInfos[946] & 2) {
+ if (t->run == &Game::mstTask_monsterWait10) {
+ mstMonster1UpdateGoalPosition(m);
+ mstMonster1MoveTowardsGoal2(m);
+ } else if (t->run == &Game::mstTask_monsterWait8) {
+ mstMonster1UpdateGoalPosition(m);
+ mstMonster1MoveTowardsGoal1(m);
+ } else if (t->run == &Game::mstTask_monsterWait9) {
+ mstMonster1MoveTowardsGoal2(m);
+ } else { // &Game::mstTask_monsterWait7
+ mstMonster1MoveTowardsGoal1(m);
+ }
+ const MstMovingBoundsUnk1 *m49Unk1 = m->m49Unk1;
+ uint8_t xDist, yDist;
+ if (_mstLut1[m->goalDirectionMask] & 1) {
+ xDist = m49Unk1->unkE;
+ yDist = m49Unk1->unkF;
+ } else {
+ xDist = m49Unk1->unkC;
+ yDist = m49Unk1->unkD;
+ }
+ if (_xMstPos2 < xDist && _yMstPos2 < yDist && !mstMonster1TestGoalDirection(m)) {
+
+ } else {
+ if (m->goalDirectionMask) {
+ return (this->*(t->run))(t);
+ }
+ }
+ } else {
+ if (t->run == &Game::mstTask_monsterWait6) {
+ if (m->goalScreenNum == 0xFD && m->xMstPos < _mstAndyLevelPosX) {
+ m->goalPos_x1 = _mstAndyLevelPosX - m->goalDistance_x2;
+ m->goalPos_x2 = _mstAndyLevelPosX - m->goalDistance_x1;
+ } else {
+ m->goalPos_x1 = _mstAndyLevelPosX + m->goalDistance_x1;
+ m->goalPos_x2 = _mstAndyLevelPosX + m->goalDistance_x2;
+ }
+ }
+ mstMonster1SetGoalHorizontal(m);
+ bool flag = false;
+ while (_xMstPos2 < m->m49Unk1->unkC) {
+ if (--m->indexUnk49Unk1 >= 0) {
+ m->m49Unk1 = &m->m49->data1[m->indexUnk49Unk1];
+ } else {
+ flag = true;
+ break;
+
+ }
+ }
+ if (!flag) {
+ if (m->goalDirectionMask) {
+ return (this->*(t->run))(t);
+ }
+ }
+ }
+ if (m->monsterInfos[946] & 4) {
+ mstBoundingBoxClear(m, 1);
+ }
+ t->flags |= 0x80;
+ mstTaskResetMonster1WalkPath(t);
+ return 0;
+}
+
+void Game::mstOp52() {
+ if (_mstActionNum == -1) {
+ return;
+ }
+ MstMonsterAction *m48 = &_res->_mstMonsterActionData[_mstActionNum];
+ for (int i = 0; i < m48->areaCount; ++i) {
+ MstMonsterArea *m48Area = &m48->area[i];
+ const uint8_t num = m48Area->data->monster1Index;
+ if (num != 0xFF) {
+ assert(num < kMaxMonsterObjects1);
+ MonsterObject1 *m = &_monsterObjects1Table[num];
+ mstMonster1ClearChasingMonster(m);
+ if ((m->flagsA5 & 0x70) == 0) {
+ assert(m->task->monster1 == m);
+ Task *t = m->task;
+ const int num = m->o16->flags0 & 0xFF;
+ if (m->monsterInfos[num * 28] != 0) {
+ if (t->run != &Game::mstTask_monsterWait1 && t->run != &Game::mstTask_monsterWait4 && t->run != &Game::mstTask_monsterWait2 && t->run != &Game::mstTask_monsterWait3 && t->run != &Game::mstTask_monsterWait5 && t->run != &Game::mstTask_monsterWait6 && t->run != &Game::mstTask_monsterWait7 && t->run != &Game::mstTask_monsterWait8 && t->run != &Game::mstTask_monsterWait9 && t->run != &Game::mstTask_monsterWait10) {
+ m->flagsA5 = (m->flagsA5 & ~0xF) | 6;
+ mstTaskInitMonster1Type2(m->task, 1);
+ } else {
+ m->o16->actionKeyMask = 0;
+ m->o16->directionKeyMask = 0;
+ t->run = &Game::mstTask_monsterWait11;
+ }
+ } else {
+ m->flagsA5 = (m->flagsA5 & ~0xF) | 6;
+ mstTaskInitMonster1Type2(m->task, 1);
+ }
+ }
+ }
+ }
+ _mstActionNum = -1;
+}
+
+bool Game::mstHasMonsterInRange(const MstMonsterAction *m48, uint8_t flag) {
+ for (int i = 0; i < 2; ++i) {
+ for (uint32_t j = 0; j < m48->count[i]; ++j) {
+ uint32_t a = (i ^ flag); // * 32;
+ uint32_t n = m48->data1[i][j];
+ if (_mstCollisionTable[a][n].count < m48->data2[i][j]) {
+ return false;
+ }
+ }
+ }
+
+ uint8_t _op54Data[kMaxMonsterObjects1];
+ memset(_op54Data, 0, sizeof(_op54Data));
+
+ int var24 = 0;
+ //int var28 = 0;
+ int vf = 0;
+ for (int i = 0; i < m48->areaCount; ++i) {
+ const MstMonsterArea *m12 = &m48->area[i];
+ assert(m12->count == 1);
+ MstMonsterAreaAction *m12u4 = m12->data;
+ if (m12->unk0 != 0) {
+ uint8_t var1C = m12u4->unk18;
+ if (var1C != 2) {
+ vf = var1C;
+ }
+l1:
+ int var4C = vf;
+
+ int var8 = m12u4->xPos;
+ int vb = var8; // xPos
+ int var4 = m12u4->yPos;
+ int vg = var4; // yPos
+
+ int va = vf ^ flag;
+ if (va == 1) {
+ vb = -vb;
+ }
+ debug(kDebug_MONSTER, "mstHasMonsterInRange (unk0!=0) count:%d %d %d [%d,%d] screen:%d", m12->count, vb, vg, _mstPosXmin, _mstPosXmax, m12u4->screenNum);
+ if (vb >= _mstPosXmin && vb <= _mstPosXmax) {
+ uint8_t var4D = _res->_mstMonsterInfos[m12u4->unk0 * kMonsterInfoDataSize + 946] & 2;
+ if (var4D == 0 || (vg >= _mstPosYmin && vg <= _mstPosYmax)) {
+ MstCollision *varC = &_mstCollisionTable[va][m12u4->unk0];
+ vb += _mstAndyLevelPosX;
+ const int xLevelPos = vb;
+ vg += _mstAndyLevelPosY;
+ const int yLevelPos = vg;
+ int minDistY = 0x1000000;
+ int minDistX = 0x1000000;
+ int var34 = -1;
+ int var10 = varC->count;
+ //MstCollision *var20 = varC;
+ for (int j = 0; j < var10; ++j) {
+ MonsterObject1 *m = varC->monster1[j];
+ if (_op54Data[m->monster1Index] == 0 && (m12u4->screenNum < 0 || m->o16->screenNum == m12u4->screenNum)) {
+ int ve = yLevelPos - m->yMstPos;
+ int va = ABS(ve);
+ int vg = xLevelPos - m->xMstPos;
+ int vc = ABS(vg);
+ if (vc > m48->unk0 || va > m48->unk2) {
+ continue;
+ }
+ if ((var8 || var4) && m->monsterInfos[944] != 10 && m->monsterInfos[944] != 16 && m->monsterInfos[944] != 9) {
+ if (vg <= 0) {
+ if (m->levelPosBounds_x1 > xLevelPos) {
+ continue;
+ }
+ } else {
+ if (m->levelPosBounds_x2 < xLevelPos) {
+ continue;
+ }
+ }
+ if (var4D != 0) { // vertical move
+ if (ve <= 0) {
+ if (m->levelPosBounds_y1 > yLevelPos) {
+ continue;
+ }
+ } else {
+ if (m->levelPosBounds_y2 < yLevelPos) {
+ continue;
+ }
+ }
+ }
+ }
+ if (vc <= minDistX && va <= minDistY) {
+ minDistY = va;
+ minDistX = vc;
+ var34 = j;
+ }
+ }
+ }
+ if (var34 != -1) {
+ const uint8_t num = varC->monster1[var34]->monster1Index;
+ m12u4->monster1Index = num;
+ _op54Data[num] = 1;
+ debug(kDebug_MONSTER, "monster %d in range", num);
+ ++var24;
+ continue;
+ }
+ }
+ }
+ if (var1C != 2 || var4C == 1) {
+ return false;
+ }
+ vf = 1;
+ var4C = vf;
+ goto l1;
+ }
+ //++var28;
+ }
+ //var28 = vf;
+ for (int i = vf; i < m48->areaCount; ++i) {
+ MstMonsterArea *m12 = &m48->area[i];
+ assert(m12->count == 1);
+ MstMonsterAreaAction *m12u4 = m12->data;
+ if (m12->unk0 == 0) {
+ uint8_t var1C = m12u4->unk18;
+ m12u4->monster1Index = 0xFF;
+ int var4C = (var1C == 2) ? 0 : var1C;
+ int vd = var4C;
+l2:
+ int var4 = m12u4->xPos;
+ int vb = var4;
+ int var8 = m12u4->yPos;
+ int vg = var8;
+ int va = vd ^ flag;
+ if (va == 1) {
+ vb = -vb;
+ }
+ debug(kDebug_MONSTER, "mstHasMonsterInRange (unk0==0) count:%d %d %d [%d,%d] screen:%d", m12->count, vb, vg, _mstPosXmin, _mstPosXmax, m12u4->screenNum);
+ if (vb >= _mstPosXmin && vb <= _mstPosXmax) {
+ uint8_t var4D = _res->_mstMonsterInfos[m12u4->unk0 * kMonsterInfoDataSize + 946] & 2;
+ if (var4D == 0 || (vg >= _mstPosYmin && vg <= _mstPosYmax)) {
+ MstCollision *varC = &_mstCollisionTable[va][m12u4->unk0];
+ vb += _mstAndyLevelPosX;
+ const int xLevelPos = vb;
+ vg += _mstAndyLevelPosY;
+ const int yLevelPos = vg;
+ int minDistY = 0x1000000;
+ int minDistX = 0x1000000;
+ int var34 = -1;
+ int var10 = varC->count;
+ for (int j = 0; j < var10; ++j) {
+ MonsterObject1 *m = varC->monster1[j];
+ if (_op54Data[m->monster1Index] == 0 && (m12u4->screenNum < 0 || m->o16->screenNum == m12u4->screenNum)) {
+ int ve = yLevelPos - m->yMstPos;
+ int va = ABS(ve);
+ int vg = xLevelPos - m->xMstPos;
+ int vc = ABS(vg);
+ if (vc > m48->unk0 || va > m48->unk2) {
+ continue;
+ }
+ if ((var8 || var4) && m->monsterInfos[944] != 10 && m->monsterInfos[944] != 16 && m->monsterInfos[944] != 9) {
+ if (vg <= 0) {
+ if (m->levelPosBounds_x1 > xLevelPos) {
+ continue;
+ }
+ } else {
+ if (m->levelPosBounds_x2 < xLevelPos) {
+ continue;
+ }
+ }
+ if (var4D != 0) { // vertical move
+ if (ve <= 0) {
+ if (m->levelPosBounds_y1 > yLevelPos) {
+ continue;
+ }
+ } else {
+ if (m->levelPosBounds_y2 < yLevelPos) {
+ continue;
+ }
+ }
+ }
+ }
+ if (vc <= minDistX && va <= minDistY) {
+ minDistY = va;
+ minDistX = vc;
+ var34 = j;
+ }
+ }
+ }
+ if (var34 != -1) {
+ const uint8_t num = varC->monster1[var34]->monster1Index;
+ m12u4->monster1Index = num;
+ _op54Data[num] = 1;
+ debug(kDebug_MONSTER, "monster %d in range", num);
+ ++var24;
+ continue;
+ }
+ }
+ }
+ if (var1C == 2 && var4C != 1) {
+ vd = 1;
+ var4C = 1;
+ goto l2;
+ }
+ }
+ //++var28;
+ }
+ return var24 != 0;
+}
+
+void Game::mstOp53(MstMonsterAction *m) {
+ if (_mstActionNum != -1) {
+ return;
+ }
+ const int x = MIN(_mstAndyScreenPosX, 255);
+ if (_mstAndyScreenPosX < 0) {
+ _mstPosXmin = x;
+ _mstPosXmax = 255 + x;
+ } else {
+ _mstPosXmin = -x;
+ _mstPosXmax = 255 - x;
+ }
+ const int y = MIN(_mstAndyScreenPosY, 191);
+ if (_mstAndyScreenPosY < 0) {
+ _mstPosYmin = y;
+ _mstPosYmax = 191 + y;
+ } else {
+ _mstPosYmin = -y;
+ _mstPosYmax = 191 - y;
+ }
+ mstResetCollisionTable();
+ mstUpdateInRange(m);
+}
+
+void Game::mstOp54() {
+ debug(kDebug_MONSTER, "mstOp54 %d %d %d", _mstActionNum, _m43Num2, _m43Num3);
+ if (_mstActionNum != -1) {
+ return;
+ }
+ MstMonsterActionIndex *m43 = 0;
+ if (_mstFlags & 0x20000000) {
+ if (_m43Num2 == -1) {
+ return;
+ }
+ m43 = &_res->_mstMonsterActionIndexData[_m43Num2];
+ } else {
+ if (_m43Num3 == -1) {
+ return;
+ }
+ m43 = &_res->_mstMonsterActionIndexData[_m43Num3];
+ _m43Num2 = _m43Num1;
+ }
+ const int x = MIN(_mstAndyScreenPosX, 255);
+ if (_mstAndyScreenPosX < 0) {
+ _mstPosXmin = x;
+ _mstPosXmax = 255 + x;
+ } else {
+ _mstPosXmin = -x;
+ _mstPosXmax = 255 - x;
+ }
+ const int y = MIN(_mstAndyScreenPosY, 191);
+ if (_mstAndyScreenPosY < 0) {
+ _mstPosYmin = y;
+ _mstPosYmax = 191 + y;
+ } else {
+ _mstPosYmin = -y;
+ _mstPosYmax = 191 - y;
+ }
+ mstResetCollisionTable();
+ if (m43->dataCount == 0) {
+ const uint32_t indexUnk48 = m43->indexUnk48[0];
+ MstMonsterAction *m48 = &_res->_mstMonsterActionData[indexUnk48];
+ mstUpdateInRange(m48);
+ if (_mstActionNum == -1) {
+ ++_mstOp54Counter;
+ }
+ if (_mstOp54Counter <= 16) {
+ return;
+ }
+ _mstOp54Counter = 0;
+ shuffleMstMonsterActionIndex(m43);
+ } else {
+ memset(_mstOp54Table, 0, sizeof(_mstOp54Table));
+ bool var4 = false;
+ uint32_t i = 0;
+ for (; i < m43->dataCount; ++i) {
+ uint8_t num = m43->data[i];
+ if ((num & 0x80) == 0) {
+ var4 = true;
+ if (_mstOp54Table[num] == 0) {
+ _mstOp54Table[num] = 1;
+ const uint32_t indexUnk48 = m43->indexUnk48[num];
+ MstMonsterAction *m48 = &_res->_mstMonsterActionData[indexUnk48];
+ if (mstUpdateInRange(m48)) {
+ break;
+ }
+ }
+ }
+ }
+ if (_mstActionNum != -1) {
+ assert(i < m43->dataCount);
+ m43->data[i] |= 0x80;
+ } else {
+ if (var4) {
+ ++_mstOp54Counter;
+ if (_mstOp54Counter <= 16) {
+ return;
+ }
+ }
+ _mstOp54Counter = 0;
+ if (m43->dataCount != 0) {
+ shuffleMstMonsterActionIndex(m43);
+ }
+ }
+ }
+}
+
+static uint8_t getLvlObjectFlag(uint8_t type, const LvlObject *o, const LvlObject *andyObject) {
+ switch (type) {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ case 2:
+ return (o->flags1 >> 4) & 1;
+ case 3:
+ return ~(o->flags1 >> 4) & 1;
+ case 4:
+ return (andyObject->flags1 >> 4) & 1;
+ case 5:
+ return ~(andyObject->flags1 >> 4) & 1;
+ default:
+ warning("getLvlObjectFlag unhandled type %d", type);
+ break;
+ }
+ return 0;
+}
+
+int Game::mstOp56_specialAction(Task *t, int code, int num) {
+ assert(num < _res->_mstHdr.op204DataCount);
+ const MstOp204Data *op204Data = &_res->_mstOp204Data[num];
+ debug(kDebug_MONSTER, "mstOp56_specialAction code %d", code);
+ switch (code) {
+ case 0:
+ if (!_specialAnimFlag && setAndySpecialAnimation(0x71) != 0) {
+ _plasmaCannonFlags |= 1;
+ if (_andyObject->spriteNum == 0) {
+ _mstCurrentAnim = op204Data->arg0 & 0xFFFF;
+ } else {
+ _mstCurrentAnim = op204Data->arg0 >> 16;
+ }
+ LvlObject *o = 0;
+ if (t->monster2) {
+ o = t->monster2->o;
+ } else if (t->monster1) {
+ o = t->monster1->o16;
+ }
+ if (op204Data->arg3 != 6 && o) {
+ LvlObject *tmpObject = t->monster1->o16;
+ const uint8_t flags = getLvlObjectFlag(op204Data->arg3 & 255, tmpObject, _andyObject);
+ _specialAnimMask = ((flags & 3) << 4) | (_specialAnimMask & ~0x30);
+ // _specialAnimScreenNum = tmpObject->screenNum;
+ _specialAnimLvlObject = tmpObject;
+ _mstOriginPosX = op204Data->arg1 & 0xFFFF;
+ _mstOriginPosY = op204Data->arg2 & 0xFFFF;
+ } else {
+ _specialAnimMask = merge_bits(_specialAnimMask, _andyObject->flags1, 0x30); // _specialAnimMask ^= (_specialAnimMask ^ _andyObject->flags1) & 0x30;
+ // _specialAnimScreenNum = _andyObject->screenNum;
+ _specialAnimLvlObject = _andyObject;
+ _mstOriginPosX = _andyObject->posTable[3].x - _andyObject->posTable[6].x;
+ _mstOriginPosY = _andyObject->posTable[3].y - _andyObject->posTable[6].y;
+ }
+ _specialAnimFlag = true;
+ }
+ if (_mstAndyRectNum != 0xFF) {
+ _mstBoundingBoxesTable[_mstAndyRectNum].monster1Index = 0xFF;
+ }
+ break;
+ case 1:
+ if (!_specialAnimFlag) {
+ break;
+ }
+ if (setAndySpecialAnimation(0x61) != 0) {
+ _plasmaCannonFlags &= ~1;
+ if (_andyObject->spriteNum == 0) {
+ _mstCurrentAnim = op204Data->arg0 & 0xFFFF;
+ } else {
+ _mstCurrentAnim = op204Data->arg0 >> 16;
+ }
+ LvlObject *o = 0;
+ if (t->monster2) {
+ o = t->monster2->o;
+ } else if (t->monster1) {
+ o = t->monster1->o16;
+ }
+ if (op204Data->arg3 != 6 && o) {
+ LvlObject *tmpObject = t->monster1->o16;
+ const uint8_t flags = getLvlObjectFlag(op204Data->arg3 & 255, tmpObject, _andyObject);
+ _specialAnimMask = ((flags & 3) << 4) | (_specialAnimMask & ~0x30);
+ // _specialAnimScreenNum = tmpObject->screenNum;
+ _specialAnimLvlObject = tmpObject;
+ _mstOriginPosX = op204Data->arg1 & 0xFFFF;
+ _mstOriginPosY = op204Data->arg2 & 0xFFFF;
+ } else {
+ _specialAnimMask = merge_bits(_specialAnimMask, _andyObject->flags1, 0x30); // _specialAnimMask ^= (_specialAnimMask ^ _andyObject->flags1) & 0x30;
+ // _specialAnimScreenNum = _andyObject->screenNum;
+ _specialAnimLvlObject = _andyObject;
+ _mstOriginPosX = _andyObject->posTable[3].x - _andyObject->posTable[6].x;
+ _mstOriginPosY = _andyObject->posTable[3].y - _andyObject->posTable[6].y;
+ }
+ _specialAnimFlag = false;
+ }
+ _mstAndyRectNum = mstBoundingBoxUpdate(_mstAndyRectNum, 0xFE, _mstAndyLevelPosX, _mstAndyLevelPosY, _mstAndyLevelPosX + _andyObject->width - 1, _mstAndyLevelPosY + _andyObject->height - 1) & 0xFF;
+ break;
+ case 2: {
+ LvlObject *o = t->monster1->o16;
+ const uint8_t flags = getLvlObjectFlag(op204Data->arg0 & 255, o, _andyObject);
+ setAndySpecialAnimation(flags | 0x10);
+ }
+ break;
+ case 3:
+ setAndySpecialAnimation(0x12);
+ break;
+ case 4:
+ setAndySpecialAnimation(0x80);
+ break;
+ case 5: // game over, restart level
+ setAndySpecialAnimation(0xA4);
+ break;
+ case 6:
+ setAndySpecialAnimation(0xA3);
+ break;
+ case 7:
+ setAndySpecialAnimation(0x05);
+ break;
+ case 8:
+ setAndySpecialAnimation(0xA1);
+ break;
+ case 9:
+ setAndySpecialAnimation(0xA2);
+ break;
+ case 10:
+ if (op204Data->arg0 == 1) {
+ setShakeScreen(2, op204Data->arg1 & 255);
+ } else if (op204Data->arg0 == 2) {
+ setShakeScreen(1, op204Data->arg1 & 255);
+ } else {
+ setShakeScreen(3, op204Data->arg1 & 255);
+ }
+ break;
+ case 11: {
+ MonsterObject2 *m = t->monster2;
+ const int type = op204Data->arg3;
+ m->x1 = getTaskVar(t, op204Data->arg0, (type >> 0xC) & 15);
+ m->y1 = getTaskVar(t, op204Data->arg1, (type >> 0x8) & 15);
+ m->x2 = getTaskVar(t, op204Data->arg2, (type >> 0x4) & 15);
+ m->y2 = getTaskVar(t, type >> 16 , type & 15);
+ }
+ break;
+ case 12: {
+ const int type1 = ((op204Data->arg3 >> 4) & 15);
+ const int hint = getTaskVar(t, op204Data->arg0, type1);
+ const int type2 = (op204Data->arg3 & 15);
+ const int pause = getTaskVar(t, op204Data->arg1, type2);
+ displayHintScreen(hint, pause);
+ }
+ break;
+ case 13:
+ case 14:
+ case 22:
+ case 23:
+ case 24:
+ case 25: {
+ const int mask = op204Data->arg3;
+ int xPos = getTaskVar(t, op204Data->arg0, (mask >> 8) & 15);
+ int yPos = getTaskVar(t, op204Data->arg1, (mask >> 4) & 15);
+ int screenNum = getTaskVar(t, op204Data->arg2, mask & 15);
+ LvlObject *o = 0;
+ if (t->monster2) {
+ o = t->monster2->o;
+ } else if (t->monster1) {
+ o = t->monster1->o16;
+ }
+ if (screenNum < 0) {
+ if (screenNum == -2) {
+ if (!o) {
+ break;
+ }
+ screenNum = o->screenNum;
+ if (t->monster2) {
+ xPos += t->monster2->xMstPos;
+ yPos += t->monster2->yMstPos;
+ } else if (t->monster1) {
+ xPos += t->monster1->xMstPos;
+ yPos += t->monster1->yMstPos;
+ } else {
+ break;
+ }
+ } else if (screenNum == -1) {
+ xPos += _mstAndyLevelPosX;
+ yPos += _mstAndyLevelPosY;
+ screenNum = _currentScreen;
+ } else {
+ if (!o) {
+ break;
+ }
+ xPos += o->posTable[6].x - o->posTable[7].x;
+ yPos += o->posTable[6].y - o->posTable[7].y;
+ if (t->monster2) {
+ xPos += t->monster2->xMstPos;
+ yPos += t->monster2->yMstPos;
+ } else {
+ assert(t->monster1);
+ xPos += t->monster1->xMstPos;
+ yPos += t->monster1->yMstPos;
+ }
+ }
+ } else {
+ if (screenNum >= _res->_mstHdr.screensCount) {
+ screenNum = _res->_mstHdr.screensCount - 1;
+ }
+ xPos += _res->_mstPointOffsets[screenNum].xOffset;
+ yPos += _res->_mstPointOffsets[screenNum].yOffset;
+ }
+ if (code == 13) {
+ assert(o);
+ xPos -= _res->_mstPointOffsets[screenNum].xOffset;
+ xPos -= o->posTable[7].x;
+ yPos -= _res->_mstPointOffsets[screenNum].yOffset;
+ yPos -= o->posTable[7].y;
+ o->screenNum = screenNum;
+ o->xPos = xPos;
+ o->yPos = yPos;
+ setLvlObjectPosInScreenGrid(o, 7);
+ if (t->monster2) {
+ mstTaskSetMonster2ScreenPosition(t);
+ } else {
+ assert(t->monster1);
+ mstTaskUpdateScreenPosition(t);
+ }
+ } else if (code == 14) {
+ const int pos = mask >> 16;
+ assert(pos < 8);
+ xPos -= _res->_mstPointOffsets[screenNum].xOffset;
+ xPos -= _andyObject->posTable[pos].x;
+ yPos -= _res->_mstPointOffsets[screenNum].yOffset;
+ yPos -= _andyObject->posTable[pos].y;
+ _andyObject->screenNum = screenNum;
+ _andyObject->xPos = xPos;
+ _andyObject->yPos = yPos;
+ updateLvlObjectScreen(_andyObject);
+ mstUpdateRefPos();
+ mstUpdateMonstersRect();
+ } else if (code == 22) {
+ updateScreenMaskBuffer(xPos, yPos, 1);
+ } else if (code == 24) {
+ updateScreenMaskBuffer(xPos, yPos, 2);
+ } else if (code == 25) {
+ updateScreenMaskBuffer(xPos, yPos, 3);
+ } else {
+ assert(code == 23);
+ updateScreenMaskBuffer(xPos, yPos, 0);
+ }
+ }
+ break;
+ case 15: {
+ _andyObject->anim = op204Data->arg0;
+ _andyObject->frame = op204Data->arg1;
+ LvlObject *o = 0;
+ if (t->monster2) {
+ o = t->monster2->o;
+ } else if (t->monster1) {
+ o = t->monster1->o16;
+ } else {
+ o = _andyObject;
+ }
+ const uint8_t flags = getLvlObjectFlag(op204Data->arg2 & 255, o, _andyObject);
+ _andyObject->flags1 = ((flags & 3) << 4) | (_andyObject->flags1 & ~0x30);
+ const int x3 = _andyObject->posTable[3].x;
+ const int y3 = _andyObject->posTable[3].y;
+ setupLvlObjectBitmap(_andyObject);
+ _andyObject->xPos += (x3 - _andyObject->posTable[3].x);
+ _andyObject->yPos += (y3 - _andyObject->posTable[3].y);
+ updateLvlObjectScreen(o);
+ mstUpdateRefPos();
+ mstUpdateMonstersRect();
+ }
+ break;
+ case 16:
+ case 17: {
+ LvlObject *o = _andyObject;
+ if (code == 16) {
+ if (t->monster2) {
+ o = t->monster2->o;
+ } else if (t->monster1) {
+ o = t->monster1->o16;
+ }
+ }
+ const int pos = op204Data->arg2;
+ assert(pos < 8);
+ const int xPos = o->xPos + o->posTable[pos].x;
+ const int yPos = o->yPos + o->posTable[pos].y;
+ const int type1 = (op204Data->arg3 >> 4) & 15;
+ const int index1 = op204Data->arg0;
+ setTaskVar(t, index1, type1, xPos);
+ const int type2 = op204Data->arg3 & 15;
+ const int index2 = op204Data->arg1;
+ setTaskVar(t, index2, type2, yPos);
+ }
+ break;
+ case 18: {
+ _mstCurrentActionKeyMask = op204Data->arg0 & 255;
+ }
+ break;
+ case 19: {
+ _andyActionKeyMaskAnd = op204Data->arg0 & 255;
+ _andyActionKeyMaskOr = op204Data->arg1 & 255;
+ _andyDirectionKeyMaskAnd = op204Data->arg2 & 255;
+ _andyDirectionKeyMaskOr = op204Data->arg3 & 255;
+ }
+ break;
+ case 20: {
+ _mstCurrentActionKeyMask = 0;
+ t->monster1->flagsA6 |= 2;
+ t->run = &Game::mstTask_idle;
+ t->monster1->o16->actionKeyMask = _mstCurrentActionKeyMask;
+ t->monster1->o16->directionKeyMask = _andyObject->directionKeyMask;
+ return 1;
+ }
+ break;
+ case 21: {
+ t->monster1->flagsA6 &= ~2;
+ t->monster1->o16->actionKeyMask = 0;
+ t->monster1->o16->directionKeyMask = 0;
+ }
+ break;
+ case 26: {
+ int screenNum = op204Data->arg2;
+ if (screenNum < -1 && !t->monster1) {
+ break;
+ }
+ if (screenNum == -1) {
+ screenNum = _currentScreen;
+ } else if (screenNum < -1) {
+ screenNum = t->monster1->o16->screenNum;
+ }
+ if (screenNum >= _res->_mstHdr.screensCount) {
+ screenNum = _res->_mstHdr.screensCount - 1;
+ }
+ const int x = _res->_mstPointOffsets[screenNum].xOffset;
+ const int y = _res->_mstPointOffsets[screenNum].yOffset;
+ const int xOffset = op204Data->arg3 * 256;
+ int count = 0;
+ for (int i = 0; i < kMaxMonsterObjects1; ++i) {
+ const MonsterObject1 *m = &_monsterObjects1Table[i];
+ if (!m->m46) {
+ continue;
+ }
+ if (!rect_contains(x - xOffset, y, x + xOffset + 256, y + 192, m->xMstPos, m->yMstPos)) {
+ continue;
+ }
+ const int num = op204Data->arg1;
+ switch (op204Data->arg0) {
+ case 0:
+ if (m->m46 == &_res->_mstBehaviorData[num]) {
+ ++count;
+ }
+ break;
+ case 1:
+ if (m->monsterInfos == &_res->_mstMonsterInfos[num * kMonsterInfoDataSize]) {
+ ++count;
+ }
+ break;
+ case 2:
+ if (m->monsterInfos[944] == num) {
+ ++count;
+ }
+ break;
+ }
+ }
+ _mstOp56Counter = count;
+ }
+ break;
+ case 27: {
+ const int type = op204Data->arg3;
+ int a = getTaskVar(t, op204Data->arg0, (type >> 0xC) & 15);
+ int b = getTaskVar(t, op204Data->arg1, (type >> 0x8) & 15);
+ int c = getTaskVar(t, op204Data->arg2, (type >> 0x4) & 15);
+ int d = getTaskVar(t, type >> 16, type & 15);
+ setScreenMaskRect(a - 16, b, a + 16, c, d);
+ }
+ break;
+ case 28:
+ // no-op
+ break;
+ case 29: {
+ const uint8_t state = op204Data->arg1 & 255;
+ const uint8_t screen = op204Data->arg0 & 255;
+ _res->_screensState[screen].s0 = state;
+ }
+ break;
+ case 30:
+ ++_level->_checkpoint;
+ break;
+ default:
+ warning("Unhandled opcode %d in mstOp56_specialAction", code);
+ break;
+ }
+ return 0;
+}
+
+static void initWormHoleSprite(WormHoleSprite *s, const uint8_t *p) {
+ s->screenNum = p[0];
+ s->initData1 = p[1];
+ s->xPos = p[2];
+ s->yPos = p[3];
+ s->initData4 = READ_LE_UINT32(p + 4);
+ s->rect1_x1 = p[8];
+ s->rect1_y1 = p[9];
+ s->rect1_x2 = p[0xA];
+ s->rect1_y2 = p[0xB];
+ s->rect2_x1 = p[0xC];
+ s->rect2_y1 = p[0xD];
+ s->rect2_x2 = p[0xE];
+ s->rect2_y2 = p[0xF];
+}
+
+void Game::mstOp57_addWormHoleSprite(int x, int y, int screenNum) {
+ bool found = false;
+ int spriteNum = 0;
+ for (int i = 0; i < 6; ++i) {
+ if (_wormHoleSpritesTable[i].screenNum == screenNum) {
+ found = true;
+ break;
+ }
+ if (_wormHoleSpritesTable[i].screenNum == 0xFF) {
+ break;
+ }
+ ++spriteNum;
+ }
+ if (!found) {
+ if (spriteNum == 6) {
+ ++_wormHoleSpritesCount;
+ if (_wormHoleSpritesCount >= spriteNum) {
+ _wormHoleSpritesCount = 0;
+ spriteNum = 0;
+ } else {
+ spriteNum = _wormHoleSpritesCount - 1;
+ }
+ } else {
+ spriteNum = _wormHoleSpritesCount;
+ }
+ switch (_currentLevel) {
+ case 2:
+ initWormHoleSprite(&_wormHoleSpritesTable[spriteNum], _pwr1_spritesData + screenNum * 16);
+ break;
+ case 3:
+ initWormHoleSprite(&_wormHoleSpritesTable[spriteNum], _isld_spritesData + screenNum * 16);
+ break;
+ case 4:
+ initWormHoleSprite(&_wormHoleSpritesTable[spriteNum], _lava_spritesData + screenNum * 16);
+ break;
+ case 6:
+ initWormHoleSprite(&_wormHoleSpritesTable[spriteNum], _lar1_spritesData + screenNum * 16);
+ break;
+ default:
+ warning("mstOp57 unhandled level %d", _currentLevel);
+ return;
+ }
+ }
+ WormHoleSprite *boulderWormSprite = &_wormHoleSpritesTable[spriteNum];
+ const int dx = x - boulderWormSprite->xPos;
+ const int dy = y + 15 - boulderWormSprite->yPos;
+ spriteNum = _rnd._rndSeed & 3;
+ if (spriteNum == 0) {
+ spriteNum = 1;
+ }
+ static const uint8_t data[32] = {
+ 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x04, 0x04, 0x04, 0x06, 0x06, 0x06, 0x08, 0x08, 0x08, 0x0A,
+ 0x0A, 0x0A, 0x0C, 0x0C, 0x0C, 0x0E, 0x0E, 0x0E, 0x10, 0x10, 0x10, 0x12, 0x12, 0x12, 0x14, 0x14,
+ };
+ const int pos = data[(dx >> 3) & 31];
+ const int num = dy >> 5;
+ if ((boulderWormSprite->flags[num] & (3 << pos)) == 0) {
+ if (addLvlObjectToList3(20)) {
+ LvlObject *o = _lvlObjectsList3;
+ o->flags0 = _andyObject->flags0;
+ o->flags1 = _andyObject->flags1;
+ o->screenNum = screenNum;
+ o->flags2 = 0x1007;
+ o->anim = 0;
+ o->frame = 0;
+ setupLvlObjectBitmap(o);
+ setLvlObjectPosRelativeToPoint(o, 7, x, y);
+ }
+ boulderWormSprite->flags[num] |= (spriteNum << pos);
+ }
+}
+
+void Game::mstOp58_addLvlObject(Task *t, int num) {
+ const MstOp211Data *dat = &_res->_mstOp211Data[num];
+ const int mask = dat->maskVars;
+ int xPos = getTaskVar(t, dat->indexVar1, (mask >> 8) & 15);
+ int yPos = getTaskVar(t, dat->indexVar2, (mask >> 4) & 15);
+ const uint8_t type = getTaskVar(t, dat->indexVar3, mask & 15);
+ LvlObject *o = 0;
+ if (t->monster2) {
+ o = t->monster2->o;
+ } else if (t->monster1) {
+ o = t->monster1->o16;
+ }
+ uint8_t screen = type;
+ if (type == 0xFB) { // -5
+ if (!o) {
+ return;
+ }
+ xPos += o->xPos + o->posTable[6].x;
+ yPos += o->yPos + o->posTable[6].y;
+ screen = o->screenNum;
+ } else if (type == 0xFE) { // -2
+ if (!o) {
+ return;
+ }
+ xPos += o->xPos + o->posTable[7].x;
+ yPos += o->yPos + o->posTable[7].y;
+ screen = o->screenNum;
+ } else if (type == 0xFF) { // -1
+ xPos += _mstAndyScreenPosX;
+ yPos += _mstAndyScreenPosY;
+ screen = _currentScreen;
+ }
+ const uint16_t flags = (dat->unk6 == -1 && o) ? o->flags2 : 0x3001;
+ o = addLvlObject(2, xPos, yPos, screen, dat->unk8, dat->unk4, dat->unkB, flags, dat->unk9, dat->unkA);
+ if (o) {
+ o->dataPtr = 0;
+ }
+}
+
+void Game::mstOp59_addShootSpecialPowers(int x, int y, int screenNum, int state, uint16_t flags) {
+ LvlObject *o = addLvlObjectToList0(3);
+ if (o) {
+ o->dataPtr = _shootLvlObjectDataNextPtr;
+ if (_shootLvlObjectDataNextPtr) {
+ _shootLvlObjectDataNextPtr = _shootLvlObjectDataNextPtr->nextPtr;
+ memset(o->dataPtr, 0, sizeof(ShootLvlObjectData));
+ }
+ ShootLvlObjectData *s = (ShootLvlObjectData *)o->dataPtr;
+ assert(s);
+ o->callbackFuncPtr = &Game::lvlObjectSpecialPowersCallback;
+ s->state = state;
+ s->type = 0;
+ s->counter = 17;
+ s->dxPos = (int8_t)_specialPowersDxDyTable[state * 2];
+ s->dyPos = (int8_t)_specialPowersDxDyTable[state * 2 + 1];
+ static const uint8_t data[16] = {
+ 0x0D, 0x00, 0x0C, 0x01, 0x0C, 0x03, 0x0C, 0x00, 0x0C, 0x02, 0x0D, 0x01, 0x0B, 0x00, 0x0B, 0x02,
+ };
+ assert(state < 8);
+ o->anim = data[state * 2];
+ o->flags1 = ((data[state * 2 + 1] & 3) << 4) | (o->flags1 & ~0x0030);
+ o->frame = 0;
+ o->flags2 = flags;
+ o->screenNum = screenNum;
+ setupLvlObjectBitmap(o);
+ setLvlObjectPosRelativeToPoint(o, 6, x, y);
+ }
+}
+
+void Game::mstOp59_addShootFireball(int x, int y, int screenNum, int type, int state, uint16_t flags) {
+ LvlObject *o = addLvlObjectToList2(7);
+ if (o) {
+ o->dataPtr = _shootLvlObjectDataNextPtr;
+ if (_shootLvlObjectDataNextPtr) {
+ _shootLvlObjectDataNextPtr = _shootLvlObjectDataNextPtr->nextPtr;
+ memset(o->dataPtr, 0, sizeof(ShootLvlObjectData));
+ }
+ ShootLvlObjectData *s = (ShootLvlObjectData *)o->dataPtr;
+ assert(s);
+ s->state = state;
+ static const uint8_t fireballDxDy1[16] = {
+ 0x0A, 0x00, 0xF7, 0xFA, 0xF7, 0x06, 0x09, 0xFA, 0x09, 0x06, 0xF6, 0x00, 0x00, 0xF6, 0x00, 0x0A
+ };
+ static const uint8_t fireballDxDy2[16] = {
+ 0x0D, 0x00, 0xF5, 0xF9, 0xF5, 0x07, 0x0B, 0xF9, 0x0B, 0x07, 0xF3, 0x00, 0x00, 0xF3, 0x00, 0x0D
+ };
+ static const uint8_t data1[16] = {
+ 0x02, 0x00, 0x01, 0x01, 0x01, 0x03, 0x01, 0x00, 0x01, 0x02, 0x02, 0x01, 0x00, 0x00, 0x00, 0x02
+ };
+ static const uint8_t data2[16] = {
+ 0x0D, 0x00, 0x0D, 0x01, 0x0D, 0x03, 0x0D, 0x00, 0x0D, 0x02, 0x0D, 0x01, 0x0D, 0x00, 0x0D, 0x02
+ };
+ assert(state < 8);
+ const uint8_t *anim;
+ if (type >= 7) {
+ s->dxPos = (int8_t)fireballDxDy2[state * 2];
+ s->dyPos = (int8_t)fireballDxDy2[state * 2 + 1];
+ s->counter = 33;
+ anim = &data2[state * 2];
+ } else {
+ s->dxPos = (int8_t)fireballDxDy1[state * 2];
+ s->dyPos = (int8_t)fireballDxDy1[state * 2 + 1];
+ s->counter = 39;
+ anim = &data1[state * 2];
+ }
+ s->type = type;
+ o->anim = anim[0];
+ o->screenNum = screenNum;
+ o->flags1 = ((anim[1] & 3) << 4) | (o->flags1 & ~0x0030);
+ o->flags2 = flags;
+ o->frame = 0;
+ setupLvlObjectBitmap(o);
+ setLvlObjectPosRelativeToPoint(o, 6, x - s->dxPos, y - s->dyPos);
+ }
+}
+
+void Game::mstTaskResetMonster1WalkPath(Task *t) {
+ MonsterObject1 *m = t->monster1;
+ t->run = &Game::mstTask_main;
+ m->o16->actionKeyMask = 0;
+ m->o16->directionKeyMask = 0;
+ if ((m->flagsA5 & 4) != 0 && (m->flagsA5 & 0x28) == 0) {
+ switch (m->flagsA5 & 7) {
+ case 5:
+ m->flagsA5 = (m->flagsA5 & ~6) | 1;
+ if (!mstMonster1UpdateWalkPath(m)) {
+ mstMonster1ResetWalkPath(m);
+ }
+ mstTaskSetNextWalkCode(t);
+ break;
+ case 6:
+ m->flagsA5 &= ~7;
+ if (!mstSetCurrentPos(m, m->xMstPos, m->yMstPos)) {
+ m->flagsA5 |= 1;
+ if (!mstMonster1UpdateWalkPath(m)) {
+ mstMonster1ResetWalkPath(m);
+ }
+ const uint32_t indexWalkCode = m->walkNode->walkCodeStage1;
+ if (indexWalkCode != kNone) {
+ m->walkCode = &_res->_mstWalkCodeData[indexWalkCode];
+ }
+ } else {
+ m->flagsA5 |= 2;
+ if (!mstMonster1UpdateWalkPath(m)) {
+ mstMonster1ResetWalkPath(m);
+ }
+ }
+ mstTaskSetNextWalkCode(t);
+ break;
+ }
+ } else {
+ m->flagsA5 &= ~4;
+ mstMonster1UpdateWalkPath(m);
+ }
+}
+
+bool Game::mstSetCurrentPos(MonsterObject1 *m, int x, int y) {
+ _mstCurrentPosX = x;
+ _mstCurrentPosY = y;
+ const uint8_t *ptr = m->monsterInfos;
+ const int32_t a = READ_LE_UINT32(ptr + 900);
+ int x1 = _mstAndyLevelPosX - a;
+ int x2 = _mstAndyLevelPosX + a;
+ if (ptr[946] & 2) { // horizontal and vertical
+ int y1 = _mstAndyLevelPosY - a;
+ int y2 = _mstAndyLevelPosY + a;
+ if (x > x1 && x < x2 && y > y1 && y < y2) {
+ if (ABS(x - _mstAndyLevelPosX) > ABS(y - _mstAndyLevelPosY)) {
+ if (x >= _mstAndyLevelPosX) {
+ _mstCurrentPosX = x2;
+ } else {
+ _mstCurrentPosX = x1;
+ }
+ } else {
+ if (y >= _mstAndyLevelPosY) {
+ _mstCurrentPosY = y2;
+ } else {
+ _mstCurrentPosY = y1;
+ }
+ }
+ return false;
+ }
+ const int32_t b = READ_LE_UINT32(ptr + 896);
+ x1 -= b;
+ x2 += b;
+ y1 -= b;
+ y2 += b;
+ if (x < x1) {
+ _mstCurrentPosX = x1;
+ } else if (x > x2) {
+ _mstCurrentPosX = x2;
+ }
+ if (y < y1) {
+ _mstCurrentPosY = y1;
+ } else if (y > y2) {
+ _mstCurrentPosY = y2;
+ }
+ return (_mstCurrentPosX == x && _mstCurrentPosY == y);
+ } // horizontal only
+ if (x > x1 && x < x2) {
+ if (x >= _mstAndyLevelPosX) {
+ _mstCurrentPosX = x2;
+ } else {
+ _mstCurrentPosX = x1;
+ }
+ return false;
+ }
+ const int32_t b = READ_LE_UINT32(ptr + 896);
+ x1 -= b;
+ x2 += b;
+ if (x < x1) {
+ _mstCurrentPosX = x1;
+ return false;
+ } else if (x > x2) {
+ _mstCurrentPosX = x2;
+ return false;
+ }
+ return true;
+}
+
+void Game::mstMonster1SetGoalHorizontal(MonsterObject1 *m) {
+ Task *t = m->task;
+ t->flags &= ~0x80;
+ int x = m->xMstPos;
+ if (x < m->goalPos_x1) {
+ _xMstPos1 = x = m->goalPos_x2;
+ if ((m->flagsA5 & 2) != 0 && (m->flags48 & 8) != 0 && x > m->goalPosBounds_x2) {
+ t->flags |= 0x80;
+ x = m->goalPosBounds_x2;
+ }
+ if (x > m->levelPosBounds_x2) {
+ t->flags |= 0x80;
+ x = m->levelPosBounds_x2;
+ }
+ _xMstPos2 = x - m->xMstPos;
+ m->goalDirectionMask = kDirectionKeyMaskRight;
+ } else if (x > m->goalPos_x2) {
+ _xMstPos1 = x = m->goalPos_x1;
+ if ((m->flagsA5 & 2) != 0 && (m->flags48 & 8) != 0 && x < m->goalPosBounds_x1) {
+ t->flags |= 0x80;
+ x = m->goalPosBounds_x1;
+ }
+ if (x < m->levelPosBounds_x1) {
+ t->flags |= 0x80;
+ x = m->levelPosBounds_x1;
+ }
+ _xMstPos2 = m->xMstPos - x;
+ m->goalDirectionMask = kDirectionKeyMaskLeft;
+ } else {
+ _xMstPos1 = x;
+ _xMstPos2 = 0;
+ m->goalDirectionMask = 0;
+ }
+}
+
+void Game::mstResetCollisionTable() {
+ const int count = MIN(_res->_mstHdr.infoMonster1Count, 32);
+ for (int i = 0; i < 2; ++i) {
+ for (int j = 0; j < count; ++j) {
+ _mstCollisionTable[i][j].count = 0;
+ }
+ }
+ for (int i = 0; i < kMaxMonsterObjects1; ++i) {
+ MonsterObject1 *m = &_monsterObjects1Table[i];
+ if (!m->m46) {
+ continue;
+ }
+ const uint8_t _bl = m->flagsA5;
+ if ((_bl & 2) != 0 || ((_bl & 1) != 0 && ((m->walkNode->walkCodeStage1 == kNone && !m->walkCode) || (m->walkNode->walkCodeStage1 != kNone && m->walkCode == &_res->_mstWalkCodeData[m->walkNode->walkCodeStage1])))) {
+ if ((_bl & 0xB0) != 0) {
+ continue;
+ }
+ const int num = m->o16->flags0 & 0xFF;
+ if (m->monsterInfos[num * 28] != 0) {
+ continue;
+ }
+ if (m->task->run == &Game::mstTask_monsterWait4) {
+ continue;
+ }
+ uint8_t _al = m->flagsA6;
+ if (_al & 2) {
+ continue;
+ }
+ assert(m->task->monster1 == m);
+ if ((_al & 8) == 0 || m->monsterInfos[945] != 0) {
+ const uint32_t offset = m->monsterInfos - _res->_mstMonsterInfos;
+ assert(offset % kMonsterInfoDataSize == 0);
+ const uint32_t num = offset / kMonsterInfoDataSize;
+ assert(num < 32);
+ const int dir = (m->xMstPos < _mstAndyLevelPosX) ? 1 : 0;
+ const int count = _mstCollisionTable[dir][num].count;
+ _mstCollisionTable[dir][num].monster1[count] = m;
+ ++_mstCollisionTable[dir][num].count;
+ }
+ }
+ }
+}
+
+// resume bytecode execution
+void Game::mstTaskRestart(Task *t) {
+ t->run = &Game::mstTask_main;
+ LvlObject *o = 0;
+ if (t->monster1) {
+ o = t->monster1->o16;
+ } else if (t->monster2) {
+ o = t->monster2->o;
+ }
+ if (o) {
+ o->actionKeyMask = 0;
+ o->directionKeyMask = 0;
+ }
+}
+
+bool Game::mstMonster1CheckLevelBounds(MonsterObject1 *m, int x, int y, uint8_t dir) {
+ if ((m->flagsA5 & 2) != 0 && (m->flags48 & 8) != 0 && !mstSetCurrentPos(m, x, y)) {
+ return true;
+ }
+ if ((dir & kDirectionKeyMaskLeft) != 0 && x < m->levelPosBounds_x1) {
+ return true;
+ }
+ if ((dir & kDirectionKeyMaskRight) != 0 && x > m->levelPosBounds_x2) {
+ return true;
+ }
+ if ((dir & kDirectionKeyMaskUp) != 0 && y < m->levelPosBounds_y1) {
+ return true;
+ }
+ if ((dir & kDirectionKeyMaskDown) != 0 && y > m->levelPosBounds_y2) {
+ return true;
+ }
+ return false;
+}
+
+int Game::mstTaskResetMonster1Direction(Task *t) {
+ MonsterObject1 *m = t->monster1;
+ m->flagsA5 = (m->flagsA5 & ~0xF) | 6;
+ return mstTaskInitMonster1Type2(t, 1);
+}
+
+int Game::mstTaskInitMonster1Type1(Task *t) {
+ t->flags &= ~0x80;
+ MonsterObject1 *m = t->monster1;
+ m->flagsA5 = (m->flagsA5 & ~2) | 5;
+ mstMonster1ResetWalkPath(m);
+ const uint32_t indexWalkBox = m->walkNode->walkBox;
+ const MstWalkBox *m34 = &_res->_mstWalkBoxData[indexWalkBox];
+ int y = 0;
+ int x = 0;
+ bool flag = false;
+ if (m->monsterInfos[946] & 2) {
+ m->unkC0 = -1;
+ m->unkBC = -1;
+ m->walkBoxNum = 0xFF;
+ m->targetDirectionMask = 0xFF;
+ y = m34->top;
+ if (m->yMstPos < m34->top || m->yMstPos > m34->bottom) {
+ flag = true;
+ }
+ }
+ x = m34->left;
+ if (m->monsterInfos[946] & 4) {
+ m->unkAB = 0xFF;
+ m->targetLevelPos_x = -1;
+ m->targetLevelPos_y = -1;
+ mstBoundingBoxClear(m, 1);
+ }
+ if (!flag && m->xMstPos >= x && m->xMstPos <= m34->right) {
+ mstTaskResetMonster1WalkPath(t);
+ return 0;
+ }
+ const uint32_t indexUnk36 = m->walkNode->movingBoundsIndex1;
+ MstMovingBoundsIndex *m36 = &_res->_mstMovingBoundsIndexData[indexUnk36];
+ MstMovingBounds *m49 = &_res->_mstMovingBoundsData[m36->indexUnk49];
+ m->m49 = m49;
+ m->indexUnk49Unk1 = m36->unk4;
+ if (m->indexUnk49Unk1 < 0) {
+ if (m49->indexDataCount == 0) {
+ m->indexUnk49Unk1 = 0;
+ } else {
+ m->indexUnk49Unk1 = m49->indexData[_rnd.getMstNextNumber(m->rnd_m49)];
+ }
+ }
+ assert((uint32_t)m->indexUnk49Unk1 < m49->count1);
+ m->m49Unk1 = &m49->data1[m->indexUnk49Unk1];
+ int xDelta = (m34->right - x) / 4;
+ m->goalDistance_x1 = x + xDelta;
+ m->goalDistance_x2 = m34->right - xDelta;
+ if (xDelta != 0) {
+ xDelta = _rnd.update() % xDelta;
+ }
+ m->goalDistance_x1 += xDelta;
+ m->goalDistance_x2 -= xDelta;
+ m->goalPos_x1 = m->goalDistance_x1;
+ m->goalPos_x2 = m->goalDistance_x2;
+ const uint8_t *ptr = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ if ((ptr[2] & kDirectionKeyMaskHorizontal) == 0) {
+ m->goalDistance_x1 = m->goalPos_x1 = m->goalDistance_x2 = m->goalPos_x2 = m->xMstPos;
+ }
+ int vf;
+ if (m->monsterInfos[946] & 2) {
+ int yDelta = (m34->bottom - y) / 4;
+ m->goalDistance_y1 = y + yDelta;
+ m->goalDistance_y2 = m34->bottom - yDelta;
+ if (yDelta != 0) {
+ yDelta = _rnd.update() % yDelta;
+ }
+ m->goalDistance_y1 += yDelta;
+ m->goalDistance_y2 -= yDelta;
+ m->goalPos_y1 = m->goalDistance_y1;
+ m->goalPos_y2 = m->goalDistance_y2;
+ if ((ptr[2] & kDirectionKeyMaskVertical) == 0) {
+ m->goalDistance_y1 = m->goalPos_y1 = m->goalDistance_y2 = m->goalPos_y2 = m->yMstPos;
+ }
+ if (m->monsterInfos[946] & 4) {
+ mstMonster1MoveTowardsGoal2(m);
+ } else {
+ mstMonster1MoveTowardsGoal1(m);
+ }
+ vf = 1;
+ const MstMovingBoundsUnk1 *m49Unk1 = m->m49Unk1;
+ uint8_t xDist, yDist;
+ if (_mstLut1[m->goalDirectionMask] & 1) {
+ xDist = m49Unk1->unkE;
+ yDist = m49Unk1->unkF;
+ } else {
+ xDist = m49Unk1->unkC;
+ yDist = m49Unk1->unkD;
+ }
+ if (_xMstPos2 < xDist && _yMstPos2 < yDist && !mstMonster1TestGoalDirection(m)) {
+ vf = 0;
+ }
+ } else {
+ mstMonster1SetGoalHorizontal(m);
+ vf = 1;
+ while (_xMstPos2 < m->m49Unk1->unkC) {
+ if (--m->indexUnk49Unk1 >= 0) {
+ m->m49Unk1 = &m->m49->data1[m->indexUnk49Unk1];
+ } else {
+ vf = 0;
+ break;
+ }
+ }
+ }
+ if (_xMstPos2 <= 0 && ((m->monsterInfos[946] & 2) == 0 || _yMstPos2 <= 0)) {
+ if (m->monsterInfos[946] & 4) {
+ mstBoundingBoxClear(m, 1);
+ }
+ mstTaskResetMonster1WalkPath(t);
+ return 0;
+ }
+ if (vf != 0 && ((_xMstPos2 >= m->m49->unk14 || ((m->monsterInfos[946] & 2) != 0 && _yMstPos2 >= m->m49->unk15)))) {
+ const uint8_t *p = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ if ((m->monsterInfos[946] & 4) != 0 && p[0xE] != 0 && m->bboxNum[0] == 0xFF) {
+ const int x1 = m->xMstPos + (int8_t)p[0xC];
+ const int y1 = m->yMstPos + (int8_t)p[0xD];
+ const int x2 = x1 + p[0xE] - 1;
+ const int y2 = y1 + p[0xF] - 1;
+ if (mstBoundingBoxCollides2(m->monster1Index, x1, y1, x2, y2) != 0) {
+ m->indexUnk49Unk1 = 0;
+ m->m49Unk1 = &m->m49->data1[0];
+ m->unkAB = 0xFF;
+ m->targetLevelPos_x = -1;
+ m->targetLevelPos_y = -1;
+ mstBoundingBoxClear(m, 1);
+ if (p[0xE] != 0) {
+ t->flags |= 0x80;
+ mstTaskResetMonster1WalkPath(t);
+ return 0;
+ }
+ } else {
+ m->bboxNum[0] = mstBoundingBoxUpdate(0xFF, m->monster1Index, x1, y1, x2, y2);
+ }
+ }
+ if (_xMstPos2 >= m36->unk8 || ((m->monsterInfos[946] & 2) != 0 && _yMstPos2 >= m36->unk8)) {
+ m->indexUnk49Unk1 = m->m49->count1 - 1;
+ m->m49Unk1 = &m->m49->data1[m->indexUnk49Unk1];
+ if (m->monsterInfos[946] & 4) {
+ m->unkAB = 0xFF;
+ m->targetLevelPos_x = -1;
+ m->targetLevelPos_y = -1;
+ mstBoundingBoxClear(m, 1);
+ }
+ }
+ if (m->monsterInfos[946] & 4) {
+ t->run = &Game::mstTask_monsterWait9;
+ } else if (m->monsterInfos[946] & 2) {
+ t->run = &Game::mstTask_monsterWait7;
+ } else {
+ t->run = &Game::mstTask_monsterWait5;
+ }
+ return (this->*(t->run))(t);
+ } else if (m->monsterInfos[946] & 4) {
+ mstBoundingBoxClear(m, 1);
+ }
+ t->flags |= 0x80;
+ mstTaskResetMonster1WalkPath(t);
+ return -1;
+}
+
+int Game::mstTaskInitMonster1Type2(Task *t, int flag) {
+ debug(kDebug_MONSTER, "mstTaskInitMonster1Type2 t %p flag %d", t, flag);
+ t->flags &= ~0x80;
+ MonsterObject1 *m = t->monster1;
+ m->flagsA5 = (m->flagsA5 & ~1) | 6;
+ mstMonster1ResetWalkPath(m);
+ const uint32_t indexUnk36 = m->walkNode->movingBoundsIndex2;
+ MstMovingBoundsIndex *m36 = &_res->_mstMovingBoundsIndexData[indexUnk36];
+ MstMovingBounds *m49 = &_res->_mstMovingBoundsData[m36->indexUnk49];
+ m->m49 = m49;
+ if (flag != 0) {
+ m->indexUnk49Unk1 = m49->count1 - 1;
+ } else {
+ m->indexUnk49Unk1 = m36->unk4;
+ }
+ if (m->indexUnk49Unk1 < 0) {
+ if (m49->indexDataCount == 0) {
+ m->indexUnk49Unk1 = 0;
+ } else {
+ m->indexUnk49Unk1 = m49->indexData[_rnd.getMstNextNumber(m->rnd_m49)];
+ }
+ }
+ assert((uint32_t)m->indexUnk49Unk1 < m49->count1);
+ m->m49Unk1 = &m49->data1[m->indexUnk49Unk1];
+ m->goalScreenNum = 0xFD;
+ m->unkC0 = -1;
+ m->unkBC = -1;
+ m->walkBoxNum = 0xFF;
+ m->targetDirectionMask = 0xFF;
+ if (mstSetCurrentPos(m, m->xMstPos, m->yMstPos)) {
+ mstTaskResetMonster1WalkPath(t);
+ return 0;
+ }
+ int vf;
+ const uint8_t *p = m->monsterInfos;
+ if (p[946] & 2) {
+ m->unkE4 = 255;
+ if (p[946] & 4) {
+ m->unkAB = 0xFF;
+ m->targetLevelPos_x = -1;
+ m->targetLevelPos_y = -1;
+ mstBoundingBoxClear(m, 1);
+ }
+ m->goalDistance_x1 = READ_LE_UINT32(m->monsterInfos + 900);
+ m->goalDistance_x2 = m->goalDistance_x1 + READ_LE_UINT32(m->monsterInfos + 896);
+ m->goalDistance_y1 = READ_LE_UINT32(m->monsterInfos + 900);
+ m->goalDistance_y2 = m->goalDistance_y1 + READ_LE_UINT32(m->monsterInfos + 896);
+ m->goalPos_x1 = m->goalDistance_x1;
+ m->goalPos_x2 = m->goalDistance_x2;
+ m->goalPos_y1 = m->goalDistance_y1;
+ m->goalPos_y2 = m->goalDistance_y2;
+ const uint8_t *ptr1 = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ if ((ptr1[2] & kDirectionKeyMaskVertical) == 0) {
+ m->goalDistance_y1 = m->goalPos_y1 = m->goalDistance_y2 = m->goalPos_y2 = m->yMstPos;
+ }
+ if ((ptr1[2] & kDirectionKeyMaskHorizontal) == 0) {
+ m->goalDistance_x1 = m->goalPos_x1 = m->goalDistance_x2 = m->goalPos_x2 = m->xMstPos;
+ }
+ if (p[946] & 4) {
+ mstMonster1UpdateGoalPosition(m);
+ mstMonster1MoveTowardsGoal2(m);
+ } else {
+ mstMonster1UpdateGoalPosition(m);
+ mstMonster1MoveTowardsGoal1(m);
+ }
+ vf = 1;
+ const MstMovingBoundsUnk1 *m49Unk1 = m->m49Unk1;
+ uint8_t xDist, yDist;
+ if (_mstLut1[m->goalDirectionMask] & 1) {
+ xDist = m49Unk1->unkE;
+ yDist = m49Unk1->unkF;
+ } else {
+ xDist = m49Unk1->unkC;
+ yDist = m49Unk1->unkD;
+ }
+ if (_xMstPos2 < xDist && _yMstPos2 < yDist && !mstMonster1TestGoalDirection(m)) {
+ vf = 0;
+ }
+ } else {
+ int32_t ve = READ_LE_UINT32(p + 900);
+ int32_t vc = READ_LE_UINT32(p + 896);
+ int r = vc / 4;
+ m->goalDistance_x1 = ve + r;
+ m->goalDistance_x2 = vc + ve - r;
+ if (r != 0) {
+ r = _rnd.update() % r;
+ }
+ m->goalDistance_x1 += r;
+ m->goalDistance_x2 -= r;
+ if (m->goalScreenNum == 0xFD && m->xMstPos < _mstAndyLevelPosX) {
+ m->goalPos_x1 = _mstAndyLevelPosX - m->goalDistance_x2;
+ m->goalPos_x2 = _mstAndyLevelPosX - m->goalDistance_x1;
+ } else {
+ m->goalPos_x1 = _mstAndyLevelPosX + m->goalDistance_x1;
+ m->goalPos_x2 = _mstAndyLevelPosX + m->goalDistance_x2;
+ }
+ mstMonster1SetGoalHorizontal(m);
+ vf = 1;
+ while (_xMstPos2 < m->m49Unk1->unkC) {
+ if (--m->indexUnk49Unk1 >= 0) {
+ m->m49Unk1 = &m->m49->data1[m->indexUnk49Unk1];
+ } else {
+ vf = 0;
+ break;
+ }
+ }
+ }
+ if (_xMstPos2 <= 0 && ((m->monsterInfos[946] & 2) == 0 || _yMstPos2 <= 0)) {
+ if (m->monsterInfos[946] & 4) {
+ mstBoundingBoxClear(m, 1);
+ }
+ mstTaskResetMonster1WalkPath(t);
+ return 0;
+ }
+ if (vf) {
+ if (_xMstPos2 >= m->m49->unk14 || ((m->monsterInfos[946] & 2) != 0 && _yMstPos2 >= m->m49->unk15)) {
+ const uint8_t *p = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ if ((m->monsterInfos[946] & 4) != 0 && p[0xE] != 0 && m->bboxNum[0] == 0xFF) {
+ const int x1 = m->xMstPos + (int8_t)p[0xC];
+ const int y1 = m->yMstPos + (int8_t)p[0xD];
+ const int x2 = x1 + p[0xE] - 1;
+ const int y2 = y1 + p[0xF] - 1;
+ if (mstBoundingBoxCollides2(m->monster1Index, x1, y1, x2, y2) != 0) {
+ m->indexUnk49Unk1 = 0;
+ m->m49Unk1 = &m->m49->data1[0];
+ m->unkAB = 0xFF;
+ m->targetLevelPos_x = -1;
+ m->targetLevelPos_y = -1;
+ mstBoundingBoxClear(m, 1);
+ if (p[0xE] != 0) {
+ t->flags |= 0x80;
+ mstTaskResetMonster1WalkPath(t);
+ return 0;
+ }
+ } else {
+ m->bboxNum[0] = mstBoundingBoxUpdate(0xFF, m->monster1Index, x1, y1, x2, y2);
+ }
+ }
+ if (_xMstPos2 >= m36->unk8 || ((m->monsterInfos[946] & 2) != 0 && _yMstPos2 >= m36->unk8)) {
+ m->indexUnk49Unk1 = m->m49->count1 - 1;
+ m->m49Unk1 = &m->m49->data1[m->indexUnk49Unk1];
+ if (m->monsterInfos[946] & 4) {
+ m->unkAB = 0xFF;
+ m->targetLevelPos_x = -1;
+ m->targetLevelPos_y = -1;
+ mstBoundingBoxClear(m, 1);
+ }
+ }
+ if (m->monsterInfos[946] & 4) {
+ t->run = &Game::mstTask_monsterWait10;
+ } else if (m->monsterInfos[946] & 2) {
+ t->run = &Game::mstTask_monsterWait8;
+ } else {
+ t->run = &Game::mstTask_monsterWait6;
+ }
+ return (this->*(t->run))(t);
+ }
+ }
+ if (m->monsterInfos[946] & 4) {
+ mstBoundingBoxClear(m, 1);
+ }
+ t->flags |= 0x80;
+ mstTaskResetMonster1WalkPath(t);
+ return -1;
+}
+
+void Game::mstOp67_addMonster(Task *currentTask, int x1, int x2, int y1, int y2, int screen, int type, int o_flags1, int o_flags2, int arg1C, int arg20, int arg24) {
+ debug(kDebug_MONSTER, "mstOp67_addMonster pos %d,%d,%d,%d %d %d 0x%x 0x%x %d %d %d", y1, x1, y2, x2, screen, type, o_flags1, o_flags2, arg1C, arg20, arg24);
+ if (o_flags2 == 0xFFFF) {
+ LvlObject *o = 0;
+ if (currentTask->monster1) {
+ o = currentTask->monster1->o16;
+ } else if (currentTask->monster2) {
+ o = currentTask->monster2->o;
+ }
+ o_flags2 = o ? o->flags2 : 0x3001;
+ }
+ if (y1 != y2) {
+ y1 += _rnd.update() % ABS(y2 - y1 + 1);
+ }
+ if (x1 != x2) {
+ x1 += _rnd.update() % ABS(x2 - x1 + 1);
+ }
+ int objScreen = (screen < 0) ? _currentScreen : screen;
+
+ LvlObject *o = 0;
+ MonsterObject2 *mo = 0;
+ MonsterObject1 *m = 0;
+
+ if (arg1C != -128) {
+ if (_mstVars[30] > kMaxMonsterObjects1) {
+ _mstVars[30] = kMaxMonsterObjects1;
+ }
+ int count = 0;
+ for (int i = 0; i < kMaxMonsterObjects1; ++i) {
+ if (_monsterObjects1Table[i].m46) {
+ ++count;
+ }
+ }
+ if (count >= _mstVars[30]) {
+ return;
+ }
+ if (arg1C < 0) {
+ const MstBehaviorIndex *m42 = &_res->_mstBehaviorIndexData[arg24];
+ if (m42->dataCount == 0) {
+ arg1C = m42->data[0];
+ } else {
+ arg1C = m42->data[_rnd.update() % m42->dataCount];
+ }
+ }
+ for (int i = 0; i < kMaxMonsterObjects1; ++i) {
+ if (!_monsterObjects1Table[i].m46) {
+ m = &_monsterObjects1Table[i];
+ break;
+ }
+ }
+ if (!m) {
+ warning("mstOp67 unable to find a free MonsterObject1");
+ return;
+ }
+ memset(m->localVars, 0, sizeof(m->localVars));
+ m->flags48 = 0x1C;
+ m->flagsA5 = 0;
+ m->collideDistance = -1;
+ m->walkCode = 0;
+ m->flagsA6 = 0;
+
+ assert((uint32_t)arg1C < _res->_mstBehaviorIndexData[arg24].count1);
+ const uint32_t indexBehavior = _res->_mstBehaviorIndexData[arg24].behavior[arg1C];
+ MstBehavior *m46 = &_res->_mstBehaviorData[indexBehavior];
+ m->m46 = m46;
+ assert((uint32_t)arg20 < m46->count);
+ MstBehaviorState *behaviorState = &m46->data[arg20];
+ m->behaviorState = behaviorState;
+ m->monsterInfos = _res->_mstMonsterInfos + behaviorState->indexMonsterInfo * kMonsterInfoDataSize;
+
+ m->localVars[7] = behaviorState->energy;
+
+ if (behaviorState->indexUnk51 == kNone) {
+ m->flags48 &= ~4;
+ }
+
+ const uint8_t *ptr = m->monsterInfos;
+ o = addLvlObject(ptr[945], x1, y1, objScreen, ptr[944], behaviorState->anim, o_flags1, o_flags2, 0, 0);
+ if (!o) {
+ mstMonster1ResetData(m);
+ return;
+ }
+ m->o16 = o;
+ if (_currentLevel == kLvl_lar2 && m->monsterInfos[944] == 26) { // Master of Darkness
+ m->o20 = addLvlObject(ptr[945], x1, y1, objScreen, ptr[944], behaviorState->anim + 1, o_flags1, 0x3001, 0, 0);
+ if (!m->o20) {
+ warning("mstOp67 failed to addLvlObject in kLvl_lar2");
+ mstMonster1ResetData(m);
+ return;
+ }
+ if (screen < 0) {
+ m->o20->xPos += _mstAndyScreenPosX;
+ m->o20->yPos += _mstAndyScreenPosY;
+ }
+ m->o20->dataPtr = 0;
+ setLvlObjectPosRelativeToObject(m->o16, 6, m->o20, 6);
+ }
+ m->o_flags2 = o_flags2 & 0xFFFF;
+ m->lut4Index = _mstLut5[o_flags2 & 0x1F];
+ o->dataPtr = m;
+ } else {
+ for (int i = 0; i < kMaxMonsterObjects2; ++i) {
+ if (!_monsterObjects2Table[i].monster2Info) {
+ mo = &_monsterObjects2Table[i];
+ break;
+ }
+ }
+ if (!mo) {
+ warning("mstOp67 no free monster2");
+ return;
+ }
+ assert(arg24 >= 0 && arg24 < _res->_mstHdr.infoMonster2Count);
+ mo->monster2Info = &_res->_mstInfoMonster2Data[arg24];
+ if (currentTask->monster1) {
+ mo->monster1 = currentTask->monster1;
+ } else if (currentTask->monster2) {
+ mo->monster1 = currentTask->monster2->monster1;
+ } else {
+ mo->monster1 = 0;
+ }
+
+ mo->flags24 = 0;
+
+ uint8_t _cl = mo->monster2Info->type;
+ uint16_t anim = mo->monster2Info->anim;
+
+ o = addLvlObject((_cl >> 7) & 1, x1, y1, objScreen, (_cl & 0x7F), anim, o_flags1, o_flags2, 0, 0);
+ if (!o) {
+ mo->monster2Info = 0;
+ if (mo->o) {
+ mo->o->dataPtr = 0;
+ }
+ return;
+ }
+ mo->o = o;
+ o->dataPtr = mo;
+ }
+ if (screen < 0) {
+ o->xPos += _mstAndyScreenPosX;
+ o->yPos += _mstAndyScreenPosY;
+ }
+ setLvlObjectPosInScreenGrid(o, 7);
+ if (mo) {
+ Task *t = findFreeTask();
+ if (!t) {
+ mo->monster2Info = 0;
+ if (mo->o) {
+ mo->o->dataPtr = 0;
+ }
+ removeLvlObject2(o);
+ return;
+ }
+ resetTask(t, kUndefinedMonsterByteCode);
+ t->monster2 = mo;
+ t->monster1 = 0;
+ mo->task = t;
+ t->codeData = 0;
+ appendTask(&_monsterObjects2TasksList, t);
+ t->codeData = kUndefinedMonsterByteCode;
+ mstTaskSetMonster2ScreenPosition(t);
+ const uint32_t codeData = mo->monster2Info->codeData;
+ assert(codeData != kNone);
+ resetTask(t, _res->_mstCodeData + codeData * 4);
+ if (_currentLevel == kLvl_fort && mo->monster2Info->type == 27) {
+ mstMonster2InitFirefly(mo);
+ }
+ } else {
+ Task *t = findFreeTask();
+ if (!t) {
+ mstMonster1ResetData(m);
+ removeLvlObject2(o);
+ return;
+ }
+ resetTask(t, kUndefinedMonsterByteCode);
+ t->monster1 = m;
+ t->monster2 = 0;
+ m->task = t;
+ t->codeData = 0;
+ appendTask(&_monsterObjects1TasksList, t);
+ t->codeData = kUndefinedMonsterByteCode;
+ _rnd.resetMst(m->rnd_m35);
+ _rnd.resetMst(m->rnd_m49);
+
+ m->levelPosBounds_x1 = -1;
+ MstBehaviorState *behaviorState = m->behaviorState;
+ m->walkNode = _res->_mstWalkPathData[behaviorState->walkPath].data;
+
+ if (m->monsterInfos[946] & 4) {
+ m->bboxNum[0] = 0xFF;
+ m->bboxNum[1] = 0xFF;
+ }
+ mstTaskUpdateScreenPosition(t);
+ switch (type) {
+ case 1:
+ mstTaskInitMonster1Type1(t);
+ assert(t->run != &Game::mstTask_main || (t->codeData && t->codeData != kUndefinedMonsterByteCode));
+ break;
+ case 2:
+ if (m) {
+ m->flagsA6 |= 1;
+ }
+ mstTaskInitMonster1Type2(t, 0);
+ assert(t->run != &Game::mstTask_main || (t->codeData && t->codeData != kUndefinedMonsterByteCode));
+ break;
+ default:
+ m->flagsA5 = 1;
+ if (!mstMonster1UpdateWalkPath(m)) {
+ mstMonster1ResetWalkPath(m);
+ }
+ mstTaskSetNextWalkCode(t);
+ break;
+ }
+ }
+ currentTask->flags &= ~0x80;
+}
+
+void Game::mstOp68_addMonsterGroup(Task *t, const uint8_t *p, int a, int b, int c, int d) {
+ const MstBehaviorIndex *m42 = &_res->_mstBehaviorIndexData[d];
+ struct {
+ int m42Index;
+ int m46Index;
+ } data[16];
+ int count = 0;
+ for (uint32_t i = 0; i < m42->count1; ++i) {
+ MstBehavior *m46 = &_res->_mstBehaviorData[m42->behavior[i]];
+ for (uint32_t j = 0; j < m46->count; ++j) {
+ MstBehaviorState *behaviorState = &m46->data[j];
+ uint32_t indexMonsterInfo = p - _res->_mstMonsterInfos;
+ assert((indexMonsterInfo % kMonsterInfoDataSize) == 0);
+ indexMonsterInfo /= kMonsterInfoDataSize;
+ if (behaviorState->indexMonsterInfo == indexMonsterInfo) {
+ assert(count < 16);
+ data[count].m42Index = i;
+ data[count].m46Index = j;
+ ++count;
+ }
+ }
+ }
+ if (count == 0) {
+ return;
+ }
+ int j = 0;
+ for (int i = 0; i < a; ++i) {
+ mstOp67_addMonster(t, _mstOp67_x1, _mstOp67_x2, _mstOp67_y1, _mstOp67_y2, _mstOp67_screenNum, _mstOp67_type, _mstOp67_flags1, _mstOp68_flags1, data[j].m42Index, data[j].m46Index, d);
+ if (--c == 0) {
+ return;
+ }
+ if (++j >= count) {
+ j = 0;
+ }
+ }
+ for (int i = 0; i < b; ++i) {
+ mstOp67_addMonster(t, _mstOp68_x1, _mstOp68_x2, _mstOp68_y1, _mstOp68_y2, _mstOp68_screenNum, _mstOp68_type, _mstOp68_arg9, _mstOp68_flags1, data[j].m42Index, data[j].m46Index, d);
+ if (--c == 0) {
+ return;
+ }
+ if (++j >= count) {
+ j = 0;
+ }
+ }
+}
+
+int Game::mstTask_wait1(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_wait1 t %p count %d", t, t->arg1);
+ --t->arg1;
+ if (t->arg1 == 0) {
+ t->run = &Game::mstTask_main;
+ return 0;
+ }
+ return 1;
+}
+
+int Game::mstTask_wait2(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_wait2 t %p count %d", t, t->arg1);
+ --t->arg1;
+ if (t->arg1 == 0) {
+ mstTaskRestart(t);
+ return 0;
+ }
+ return 1;
+}
+
+int Game::mstTask_wait3(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_wait3 t %p type %d flag %d", t, t->arg1, t->arg2);
+ if (getTaskFlag(t, t->arg2, t->arg1) == 0) {
+ return 1;
+ }
+ mstTaskRestart(t);
+ return 0;
+}
+
+int Game::mstTask_idle(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_idle t %p", t);
+ return 1;
+}
+
+int Game::mstTask_mstOp231(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_mstOp231 t %p", t);
+ const MstOp234Data *m = &_res->_mstOp234Data[t->arg2];
+ const int a = getTaskFlag(t, m->indexVar1, m->maskVars & 15);
+ const int b = getTaskFlag(t, m->indexVar2, m->maskVars >> 4);
+ if (!compareOp(m->compare, a, b)) {
+ mstTaskRestart(t);
+ return 0;
+ }
+ return 1;
+}
+
+int Game::mstTask_mstOp232(Task *t) {
+ warning("mstTask_mstOp232 unimplemented");
+ t->run = &Game::mstTask_main;
+ return 0;
+}
+
+int Game::mstTask_mstOp233(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_mstOp233 t %p", t);
+ const MstOp234Data *m = &_res->_mstOp234Data[t->arg2];
+ const int a = getTaskVar(t, m->indexVar1, m->maskVars & 15);
+ const int b = getTaskVar(t, m->indexVar2, m->maskVars >> 4);
+ if (!compareOp(m->compare, a, b)) {
+ mstTaskRestart(t);
+ return 0;
+ }
+ return 1;
+}
+
+int Game::mstTask_mstOp234(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_mstOp234 t %p", t);
+ const MstOp234Data *m = &_res->_mstOp234Data[t->arg2];
+ const int a = getTaskVar(t, m->indexVar1, m->maskVars & 15);
+ const int b = getTaskVar(t, m->indexVar2, m->maskVars >> 4);
+ if (compareOp(m->compare, a, b)) {
+ mstTaskRestart(t);
+ return 0;
+ }
+ return 1;
+}
+
+int Game::mstTask_monsterWait1(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_monsterWait1 t %p", t);
+ if (t->arg1 == 0) {
+ mstMonster1UpdateWalkPath(t->monster1);
+ mstTaskRestart(t);
+ return 0;
+ }
+ --t->arg1;
+ return 1;
+}
+
+int Game::mstTask_monsterWait2(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_monsterWait2 t %p", t);
+ MonsterObject1 *m = t->monster1;
+ const uint16_t flags0 = m->o16->flags0;
+ if ((flags0 & 0x100) != 0 && (flags0 & 0xFF) == m->o_flags0) {
+ mstMonster1UpdateWalkPath(t->monster1);
+ mstTaskRestart(t);
+ return 0;
+ }
+ return 1;
+}
+
+int Game::mstTask_monsterWait3(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_monsterWait3 t %p", t);
+ MonsterObject1 *m = t->monster1;
+ const uint16_t flags0 = m->o16->flags0;
+ if ((flags0 & 0xFF) == m->o_flags0) {
+ if (t->arg1 > 0) {
+ t->run = &Game::mstTask_monsterWait1;
+ } else {
+ t->run = &Game::mstTask_monsterWait2;
+ }
+ return (this->*(t->run))(t);
+ }
+ return 1;
+}
+
+int Game::mstTask_monsterWait4(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_monsterWait4 t %p", t);
+ MonsterObject1 *m = t->monster1;
+ const uint32_t offset = m->monsterInfos - _res->_mstMonsterInfos;
+ assert(offset % kMonsterInfoDataSize == 0);
+ const uint32_t num = offset / kMonsterInfoDataSize;
+ if (t->arg2 != num) {
+ mstMonster1UpdateWalkPath(m);
+ mstTaskRestart(t);
+ return 0;
+ }
+ return 1;
+}
+
+int Game::mstTask_monsterWait5(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_monsterWait5 t %p", t);
+ // horizontal move
+ MonsterObject1 *m = t->monster1;
+ mstMonster1SetGoalHorizontal(m);
+ if (_xMstPos2 < m->m49Unk1->unk8) {
+ if (_xMstPos2 > 0) {
+ while (--m->indexUnk49Unk1 >= 0) {
+ m->m49Unk1 = &m->m49->data1[m->indexUnk49Unk1];
+ if (_xMstPos2 >= m->m49Unk1->unkC) {
+ goto set_am;
+ }
+ }
+ }
+ return mstTaskStopMonster1(t, m);
+ }
+set_am:
+ const uint8_t *ptr = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ mstLvlObjectSetActionDirection(m->o16, ptr, ptr[3], m->goalDirectionMask);
+ return 1;
+}
+
+int Game::mstTask_monsterWait6(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_monsterWait6 t %p", t);
+ MonsterObject1 *m = t->monster1;
+ // horizontal move with goal
+ if (m->goalScreenNum == 0xFD && m->xMstPos < _mstAndyLevelPosX) {
+ m->goalPos_x1 = _mstAndyLevelPosX - m->goalDistance_x2;
+ m->goalPos_x2 = _mstAndyLevelPosX - m->goalDistance_x1;
+ } else {
+ m->goalPos_x1 = _mstAndyLevelPosX + m->goalDistance_x1;
+ m->goalPos_x2 = _mstAndyLevelPosX + m->goalDistance_x2;
+ }
+ mstMonster1SetGoalHorizontal(m);
+ if (_xMstPos2 < m->m49Unk1->unk8) {
+ if (_xMstPos2 > 0) {
+ while (--m->indexUnk49Unk1 >= 0) {
+ m->m49Unk1 = &m->m49->data1[m->indexUnk49Unk1];
+ if (_xMstPos2 >= m->m49Unk1->unkC) {
+ goto set_am;
+ }
+ }
+ }
+ return mstTaskStopMonster1(t, m);
+ }
+set_am:
+ const uint8_t *ptr = _res->_mstMonsterInfos + m->m49Unk1->offsetMonsterInfo;
+ mstLvlObjectSetActionDirection(m->o16, ptr, ptr[3], m->goalDirectionMask);
+ return 1;
+}
+
+int Game::mstTask_monsterWait7(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_monsterWait7 t %p", t);
+ MonsterObject1 *m = t->monster1;
+ mstMonster1MoveTowardsGoal1(m);
+ return mstTaskUpdatePositionActionDirection(t, m);
+}
+
+int Game::mstTask_monsterWait8(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_monsterWait8 t %p", t);
+ MonsterObject1 *m = t->monster1;
+ mstMonster1UpdateGoalPosition(m);
+ mstMonster1MoveTowardsGoal1(m);
+ return mstTaskUpdatePositionActionDirection(t, m);
+}
+
+int Game::mstTask_monsterWait9(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_monsterWait9 t %p", t);
+ MonsterObject1 *m = t->monster1;
+ mstMonster1MoveTowardsGoal2(m);
+ return mstTaskUpdatePositionActionDirection(t, m);
+}
+
+int Game::mstTask_monsterWait10(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_monsterWait10 t %p", t);
+ MonsterObject1 *m = t->monster1;
+ mstMonster1UpdateGoalPosition(m);
+ mstMonster1MoveTowardsGoal2(m);
+ return mstTaskUpdatePositionActionDirection(t, m);
+}
+
+int Game::mstTask_monsterWait11(Task *t) {
+ debug(kDebug_MONSTER, "mstTask_monsterWait11 t %p", t);
+ MonsterObject1 *m = t->monster1;
+ const int num = m->o16->flags0 & 0xFF;
+ if (m->monsterInfos[num * 28] == 0) {
+ mstTaskResetMonster1Direction(t);
+ }
+ return 1;
+}
diff --git a/Src/Global/paf.cpp b/Src/Global/paf.cpp
new file mode 100644
index 0000000..dcabc41
--- /dev/null
+++ b/Src/Global/paf.cpp
@@ -0,0 +1,549 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "fs.h"
+#include "paf.h"
+#include "system.h"
+#include "util.h"
+
+static const char *_filenames[] = {
+ "hod.paf",
+ "hod_demo.paf",
+ "hod_demo2.paf",
+ 0
+};
+
+static bool openPaf(FileSystem *fs, File *f) {
+ for (int i = 0; _filenames[i]; ++i) {
+ FILE *fp = fs->openAssetFile(_filenames[i]);
+ if (fp) {
+ f->setFp(fp);
+ return true;
+ }
+ }
+ return false;
+}
+
+static void closePaf(FileSystem *fs, File *f) {
+ if (f->_fp) {
+ fs->closeFile(f->_fp);
+ f->_fp = 0;
+ }
+}
+
+PafPlayer::PafPlayer(FileSystem *fs)
+ : _fs(fs) {
+ _skipCutscenes = !openPaf(_fs, &_file);
+ _videoNum = -1;
+ memset(&_pafHdr, 0, sizeof(_pafHdr));
+ memset(_pageBuffers, 0, sizeof(_pageBuffers));
+ _demuxAudioFrameBlocks = 0;
+ _demuxVideoFrameBlocks = 0;
+ _audioQueue = _audioQueueTail = 0;
+ _playedMask = 0;
+ memset(&_pafCb, 0, sizeof(_pafCb));
+ _volume = 128;
+ _frameMs = kFrameDuration;
+}
+
+PafPlayer::~PafPlayer() {
+ unload();
+ closePaf(_fs, &_file);
+}
+
+void PafPlayer::setVolume(int volume) {
+ _volume = volume;
+}
+
+void PafPlayer::preload(int num) {
+ debug(kDebug_PAF, "preload %d", num);
+ assert(num >= 0 && num < kMaxVideosCount);
+ if (_videoNum != num) {
+ unload(_videoNum);
+ _videoNum = num;
+ }
+ _file.seek(num * 4, SEEK_SET);
+ _videoOffset = _file.readUint32();
+ _file.seek(_videoOffset, SEEK_SET);
+ memset(&_pafHdr, 0, sizeof(_pafHdr));
+ if (!readPafHeader()) {
+ unload();
+ return;
+ }
+ uint8_t *buffer = (uint8_t *)calloc(kPageBufferSize * 4 + 256 * 4, 1);
+ if (!buffer) {
+ warning("preloadPaf() Unable to allocate page buffers");
+ unload();
+ return;
+ }
+ for (int i = 0; i < 4; ++i) {
+ _pageBuffers[i] = buffer + i * kPageBufferSize;
+ }
+ _demuxVideoFrameBlocks = (uint8_t *)calloc(_pafHdr.maxVideoFrameBlocksCount, _pafHdr.readBufferSize);
+ if (_pafHdr.maxAudioFrameBlocksCount != 0) {
+ _demuxAudioFrameBlocks = (uint8_t *)calloc(_pafHdr.maxAudioFrameBlocksCount, _pafHdr.readBufferSize);
+ _flushAudioSize = (_pafHdr.maxAudioFrameBlocksCount - 1) * _pafHdr.readBufferSize;
+ } else {
+ _demuxAudioFrameBlocks = 0;
+ _flushAudioSize = 0;
+ }
+ _audioBufferOffsetRd = 0;
+ _audioBufferOffsetWr = 0;
+ _audioQueue = _audioQueueTail = 0;
+}
+
+void PafPlayer::play(int num) {
+ debug(kDebug_PAF, "play %d", num);
+ if (_videoNum != num) {
+ preload(num);
+ }
+ if (_videoNum == num) {
+ _playedMask |= 1 << num;
+ mainLoop();
+ }
+}
+
+void PafPlayer::unload(int num) {
+ if (_videoNum < 0) {
+ return;
+ }
+ free(_pageBuffers[0]);
+ memset(_pageBuffers, 0, sizeof(_pageBuffers));
+ free(_demuxVideoFrameBlocks);
+ _demuxVideoFrameBlocks = 0;
+ free(_demuxAudioFrameBlocks);
+ _demuxAudioFrameBlocks = 0;
+ free(_pafHdr.frameBlocksCountTable);
+ free(_pafHdr.framesOffsetTable);
+ free(_pafHdr.frameBlocksOffsetTable);
+ memset(&_pafHdr, 0, sizeof(_pafHdr));
+ _videoNum = -1;
+ while (_audioQueue) {
+ PafAudioQueue *next = _audioQueue->next;
+ free(_audioQueue->buffer);
+ free(_audioQueue);
+ _audioQueue = next;
+ }
+ _audioQueueTail = 0;
+}
+
+bool PafPlayer::readPafHeader() {
+ static const char *kSignature = "Packed Animation File V1.0\n(c) 1992-96 Amazing Studio\n";
+ _file.read(_bufferBlock, kBufferBlockSize);
+ if (memcmp(_bufferBlock, kSignature, strlen(kSignature)) != 0) {
+ warning("readPafHeader() Unexpected signature");
+ return false;
+ }
+ _pafHdr.frameDuration = READ_LE_UINT32(_bufferBlock + 0x88);
+ _pafHdr.startOffset = READ_LE_UINT32(_bufferBlock + 0xA4);
+ _pafHdr.preloadFrameBlocksCount = READ_LE_UINT32(_bufferBlock + 0x9C);
+ _pafHdr.readBufferSize = READ_LE_UINT32(_bufferBlock + 0x98);
+ assert(_pafHdr.readBufferSize == kBufferBlockSize);
+ _pafHdr.framesCount = READ_LE_UINT32(_bufferBlock + 0x84);
+ if (_pafHdr.framesCount <= 0) {
+ warning("readPafHeader() Invalid number of frames %d", _pafHdr.framesCount);
+ return false;
+ }
+ _pafHdr.maxVideoFrameBlocksCount = READ_LE_UINT32(_bufferBlock + 0xA8);
+ _pafHdr.maxAudioFrameBlocksCount = READ_LE_UINT32(_bufferBlock + 0xAC);
+ _pafHdr.frameBlocksCount = READ_LE_UINT32(_bufferBlock + 0xA0);
+ if (_pafHdr.frameBlocksCount <= 0) {
+ warning("readPafHeader() Invalid number of blocks %d", _pafHdr.frameBlocksCount);
+ return false;
+ }
+ _pafHdr.frameBlocksCountTable = readPafHeaderTable(_pafHdr.framesCount);
+ _pafHdr.framesOffsetTable = readPafHeaderTable(_pafHdr.framesCount);
+ _pafHdr.frameBlocksOffsetTable = readPafHeaderTable(_pafHdr.frameBlocksCount);
+ return _pafHdr.frameBlocksCountTable != 0 && _pafHdr.framesOffsetTable != 0 && _pafHdr.frameBlocksOffsetTable != 0;
+}
+
+uint32_t *PafPlayer::readPafHeaderTable(int count) {
+ uint32_t *dst = (uint32_t *)malloc(count * sizeof(uint32_t));
+ if (!dst) {
+ warning("readPafHeaderTable() Unable to allocate %d bytes", count * sizeof(uint32_t));
+ return 0;
+ }
+ for (int i = 0; i < count; ++i) {
+ dst[i] = _file.readUint32();
+ }
+ const int align = (count * 4) & 0x7FF;
+ if (align != 0) {
+ _file.seek(0x800 - align, SEEK_CUR);
+ }
+ return dst;
+}
+
+void PafPlayer::decodeVideoFrame(const uint8_t *src) {
+ const uint8_t *base = src;
+ const int code = *src++;
+ if (code & 0x20) {
+ for (int i = 0; i < 4; ++i) {
+ memset(_pageBuffers[i], 0, kPageBufferSize);
+ }
+ memset(_paletteBuffer, 0, sizeof(_paletteBuffer));
+ _paletteChanged = true;
+ _currentPageBuffer = 0;
+ }
+ if (code & 0x40) {
+ int index = src[0];
+ int count = (src[1] + 1) * 3;
+ assert(index * 3 + count <= 768);
+ src += 2;
+ memcpy(_paletteBuffer + index * 3, src, count);
+ _paletteChanged = true;
+ src += count;
+ }
+ switch (code & 0xF) {
+ case 0:
+ decodeVideoFrameOp0(base, src, code);
+ break;
+ case 1:
+ decodeVideoFrameOp1(src);
+ break;
+ case 2:
+ decodeVideoFrameOp2(src);
+ break;
+ case 4:
+ decodeVideoFrameOp4(src);
+ break;
+ }
+}
+
+static void pafCopy4x4h(uint8_t *dst, const uint8_t *src) {
+ for (int i = 0; i < 4; ++i) {
+ memcpy(dst, src, 4);
+ src += 4;
+ dst += 256;
+ }
+}
+
+static void pafCopy4x4v(uint8_t *dst, const uint8_t *src) {
+ for (int i = 0; i < 4; ++i) {
+ memcpy(dst, src, 4);
+ src += 256;
+ dst += 256;
+ }
+}
+
+static void pafCopySrcMask(uint8_t mask, uint8_t *dst, const uint8_t *src) {
+ for (int i = 0; i < 4; ++i) {
+ if (mask & (1 << (3 - i))) {
+ dst[i] = src[i];
+ }
+ }
+}
+
+static void pafCopyColorMask(uint8_t mask, uint8_t *dst, uint8_t color) {
+ for (int i = 0; i < 4; ++i) {
+ if (mask & (1 << (3 - i))) {
+ dst[i] = color;
+ }
+ }
+}
+
+static const char *updateSequences[] = {
+ "",
+ "\x02",
+ "\x05\x07",
+ "\x05",
+ "\x06",
+ "\x05\x07\x05\x07",
+ "\x05\x07\x05",
+ "\x05\x07\x06",
+ "\x05\x05",
+ "\x03",
+ "\x06\x06",
+ "\x02\x04",
+ "\x02\x04\x05\x07",
+ "\x02\x04\x05",
+ "\x02\x04\x06",
+ "\x02\x04\x05\x07\x05\x07"
+};
+
+uint8_t *PafPlayer::getVideoPageOffset(uint16_t val) {
+ const int x = val & 0x7F; val >>= 7;
+ const int y = val & 0x7F; val >>= 7;
+ return _pageBuffers[val] + (y * kVideoWidth + x) * 2;
+}
+
+void PafPlayer::decodeVideoFrameOp0(const uint8_t *base, const uint8_t *src, uint8_t code) {
+ int count = *src++;
+ if (count != 0) {
+ if ((code & 0x10) != 0) {
+ int align = src - base;
+ align &= 3;
+ if (align != 0) {
+ src += 4 - align;
+ }
+ }
+ for (int i = 0; i < count; ++i) {
+ uint8_t *dst = getVideoPageOffset((src[0] << 8) | src[1]);
+ uint32_t offset = (src[1] & 0x7F) * 2;
+ uint32_t end = READ_LE_UINT16(src + 2); src += 4;
+ end += offset;
+ do {
+ ++offset;
+ pafCopy4x4h(dst, src);
+ src += 16;
+ if ((offset & 0x3F) == 0) {
+ dst += kVideoWidth * 3;
+ }
+ dst += 4;
+ } while (offset < end);
+ }
+ }
+
+ uint8_t *dst = _pageBuffers[_currentPageBuffer];
+ for (int y = 0; y < kVideoHeight; y += 4, dst += kVideoWidth * 3) {
+ for (int x = 0; x < kVideoWidth; x += 4, dst += 4) {
+ const uint8_t *src2 = getVideoPageOffset((src[0] << 8) | src[1]); src += 2;
+ pafCopy4x4v(dst, src2);
+ }
+ }
+
+ const uint32_t opcodesSize = READ_LE_UINT16(src); src += 4;
+ const uint8_t *opcodesData = src;
+ src += opcodesSize;
+
+ uint8_t mask = 0;
+ uint8_t color = 0;
+ const uint8_t *src2 = 0;
+
+ dst = _pageBuffers[_currentPageBuffer];
+ for (int y = 0; y < kVideoHeight; y += 4, dst += kVideoWidth * 3) {
+ for (int x = 0; x < kVideoWidth; x += 8) {
+ uint8_t updateIndex = *opcodesData++;
+ for (int i = 0; i < 2; ++i, dst += 4) {
+ const char *opcodes = updateSequences[updateIndex >> 4];
+ updateIndex <<= 4;
+ while (*opcodes) {
+ uint32_t offset = kVideoWidth * 2;
+ const int code = *opcodes++;
+ switch (code) {
+ case 2:
+ offset = 0;
+ case 3:
+ color = *src++;
+ case 4:
+ mask = *src++;
+ pafCopyColorMask(mask >> 4, dst + offset, color);
+ offset += kVideoWidth;
+ pafCopyColorMask(mask & 15, dst + offset, color);
+ break;
+ case 5:
+ offset = 0;
+ case 6:
+ src2 = getVideoPageOffset((src[0] << 8) | src[1]); src += 2;
+ case 7:
+ mask = *src++;
+ pafCopySrcMask(mask >> 4, dst + offset, src2 + offset);
+ offset += kVideoWidth;
+ pafCopySrcMask(mask & 15, dst + offset, src2 + offset);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+void PafPlayer::decodeVideoFrameOp1(const uint8_t *src) {
+ memcpy(_pageBuffers[_currentPageBuffer], src + 2, kVideoWidth * kVideoHeight);
+}
+
+void PafPlayer::decodeVideoFrameOp2(const uint8_t *src) {
+ const int page = *src++;
+ if (page != _currentPageBuffer) {
+ memcpy(_pageBuffers[_currentPageBuffer], _pageBuffers[page], kVideoWidth * kVideoHeight);
+ }
+}
+
+void PafPlayer::decodeVideoFrameOp4(const uint8_t *src) {
+ uint8_t *dst = _pageBuffers[_currentPageBuffer];
+ src += 2;
+ int size = kVideoWidth * kVideoHeight;
+ while (size != 0) {
+ int8_t code = *src++;
+ int count;
+ if (code < 0) {
+ count = 1 - code;
+ const uint8_t color = *src++;
+ memset(dst, color, count);
+ } else {
+ count = code + 1;
+ memcpy(dst, src, count);
+ src += count;
+ }
+ dst += count;
+ size -= count;
+ }
+}
+
+static void decodeAudioFrame2205(const uint8_t *src, int len, int16_t *dst, int volume) {
+ static const int offset = 256 * sizeof(int16_t);
+ for (int i = 0; i < len * 2; ++i) { // stereo
+ dst[i] = (((int16_t)READ_LE_UINT16(src + src[offset + i] * sizeof(int16_t))) * volume) >> 7;
+ }
+}
+
+void PafPlayer::decodeAudioFrame(const uint8_t *src, uint32_t offset, uint32_t size) {
+ assert(size == _pafHdr.readBufferSize);
+
+ // copy should be sequential
+ if (offset != _audioBufferOffsetWr) {
+ warning("Unexpected offset 0x%x wr 0x%x rd 0x%x num %d", offset, _audioBufferOffsetWr, _audioBufferOffsetRd, _videoNum);
+ assert(offset == 0);
+ // this happens in paf #3 of Italian release, there is a flush at 0x16800 instead of 0x1f000
+ _audioBufferOffsetWr = 0;
+ _audioBufferOffsetRd = 0;
+ }
+
+ _audioBufferOffsetWr = offset + size;
+
+ const int count = (_audioBufferOffsetWr - _audioBufferOffsetRd) / kAudioStrideSize;
+ if (count != 0) {
+ PafAudioQueue *sq = (PafAudioQueue *)malloc(sizeof(PafAudioQueue));
+ if (sq) {
+ sq->offset = 0;
+ sq->size = count * kAudioSamples * 2;
+ sq->buffer = (int16_t *)malloc(sq->size * sizeof(int16_t));
+ if (sq->buffer) {
+ for (int i = 0; i < count; ++i) {
+ decodeAudioFrame2205(src + _audioBufferOffsetRd + i * kAudioStrideSize, kAudioSamples, sq->buffer + i * kAudioSamples * 2, _volume);
+ }
+ }
+ sq->next = 0;
+
+ g_system->lockAudio();
+ if (_audioQueueTail) {
+ _audioQueueTail->next = sq;
+ } else {
+ assert(!_audioQueue);
+ _audioQueue = sq;
+ }
+ _audioQueueTail = sq;
+ g_system->unlockAudio();
+ }
+
+ _audioBufferOffsetRd += count * kAudioStrideSize;
+ }
+ if (_audioBufferOffsetWr == _flushAudioSize) {
+ _audioBufferOffsetWr = 0;
+ _audioBufferOffsetRd = 0;
+ }
+}
+
+void PafPlayer::mix(int16_t *buf, int samples) {
+ while (_audioQueue && samples > 0) {
+ assert(_audioQueue->size != 0);
+ *buf++ = _audioQueue->buffer[_audioQueue->offset++];
+ *buf++ = _audioQueue->buffer[_audioQueue->offset++];
+ samples -= 2;
+ if (_audioQueue->offset >= _audioQueue->size) {
+ assert(_audioQueue->offset == _audioQueue->size);
+ PafAudioQueue *next = _audioQueue->next;
+ free(_audioQueue->buffer);
+ free(_audioQueue);
+ _audioQueue = next;
+ }
+ }
+ if (!_audioQueue) {
+ _audioQueueTail = 0;
+ }
+ if (samples > 0) {
+ warning("PafPlayer::mix() soundQueue underrun %d", samples);
+ }
+}
+
+static void mixAudio(void *userdata, int16_t *buf, int len) {
+ ((PafPlayer *)userdata)->mix(buf, len);
+}
+
+void PafPlayer::mainLoop() {
+ _file.seek(_videoOffset + _pafHdr.startOffset, SEEK_SET);
+ for (int i = 0; i < 4; ++i) {
+ memset(_pageBuffers[i], 0, kPageBufferSize);
+ }
+ memset(_paletteBuffer, 0, sizeof(_paletteBuffer));
+ _paletteChanged = true;
+ _currentPageBuffer = 0;
+ int currentFrameBlock = 0;
+
+ AudioCallback prevAudioCb;
+ if (_demuxAudioFrameBlocks) {
+ AudioCallback audioCb;
+ audioCb.proc = mixAudio;
+ audioCb.userdata = this;
+ prevAudioCb = g_system->setAudioCallback(audioCb);
+ }
+
+ // keep original frame rate for audio
+ const uint32_t frameMs = (_demuxAudioFrameBlocks != 0) ? _pafHdr.frameDuration : (_pafHdr.frameDuration * _frameMs / kFrameDuration);
+ uint32_t frameTime = g_system->getTimeStamp() + frameMs;
+
+ uint32_t blocksCountForFrame = _pafHdr.preloadFrameBlocksCount;
+ for (int i = 0; i < (int)_pafHdr.framesCount; ++i) {
+ // read buffering blocks
+ blocksCountForFrame += _pafHdr.frameBlocksCountTable[i];
+ while (blocksCountForFrame != 0) {
+ _file.read(_bufferBlock, _pafHdr.readBufferSize);
+ const uint32_t dstOffset = _pafHdr.frameBlocksOffsetTable[currentFrameBlock] & ~(1 << 31);
+ if (_pafHdr.frameBlocksOffsetTable[currentFrameBlock] & (1 << 31)) {
+ assert(dstOffset + _pafHdr.readBufferSize <= _pafHdr.maxAudioFrameBlocksCount * _pafHdr.readBufferSize);
+ memcpy(_demuxAudioFrameBlocks + dstOffset, _bufferBlock, _pafHdr.readBufferSize);
+ decodeAudioFrame(_demuxAudioFrameBlocks, dstOffset, _pafHdr.readBufferSize);
+ } else {
+ assert(dstOffset + _pafHdr.readBufferSize <= _pafHdr.maxVideoFrameBlocksCount * _pafHdr.readBufferSize);
+ memcpy(_demuxVideoFrameBlocks + dstOffset, _bufferBlock, _pafHdr.readBufferSize);
+ }
+ ++currentFrameBlock;
+ --blocksCountForFrame;
+ }
+ // decode video data
+ decodeVideoFrame(_demuxVideoFrameBlocks + _pafHdr.framesOffsetTable[i]);
+
+ if (_pafCb.frameProc) {
+ _pafCb.frameProc(_pafCb.userdata, i, _pageBuffers[_currentPageBuffer]);
+ } else {
+ g_system->copyRect(0, 0, kVideoWidth, kVideoHeight, _pageBuffers[_currentPageBuffer], kVideoWidth);
+ }
+ if (_paletteChanged) {
+ _paletteChanged = false;
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ }
+ g_system->updateScreen(false);
+ g_system->processEvents();
+ if (g_system->inp.quit || g_system->inp.keyPressed(SYS_INP_ESC)) {
+ break;
+ }
+
+ const int delay = MAX(10, frameTime - g_system->getTimeStamp());
+ g_system->sleep(delay);
+ frameTime = g_system->getTimeStamp() + frameMs;
+
+ // set next decoding video page
+ ++_currentPageBuffer;
+ _currentPageBuffer &= 3;
+ }
+
+ if (_pafCb.endProc) {
+ _pafCb.endProc(_pafCb.userdata);
+ }
+
+ // restore audio callback
+ if (_demuxAudioFrameBlocks) {
+ g_system->setAudioCallback(prevAudioCb);
+ }
+
+ unload();
+}
+
+void PafPlayer::setCallback(const PafCallback *pafCb) {
+ if (pafCb) {
+ _pafCb = *pafCb;
+ } else {
+ memset(&_pafCb, 0, sizeof(_pafCb));
+ }
+}
diff --git a/Src/Global/paf.h b/Src/Global/paf.h
new file mode 100644
index 0000000..5582cd0
--- /dev/null
+++ b/Src/Global/paf.h
@@ -0,0 +1,131 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef PAF_PLAYER_H__
+#define PAF_PLAYER_H__
+
+#include "intern.h"
+#include "defs.h"
+#include "fileio.h"
+
+struct PafHeader {
+ uint32_t preloadFrameBlocksCount;
+ uint32_t *frameBlocksCountTable;
+ uint32_t *framesOffsetTable;
+ uint32_t *frameBlocksOffsetTable;
+ int32_t framesCount;
+ int32_t frameBlocksCount;
+ uint32_t startOffset;
+ uint32_t readBufferSize;
+ int32_t maxVideoFrameBlocksCount;
+ int32_t maxAudioFrameBlocksCount;
+ int32_t frameDuration;
+};
+
+// names taken from the PSX filenames
+enum {
+ kPafAnimation_intro = 0,
+ kPafAnimation_cine14l = 1,
+ kPafAnimation_rapt = 2,
+ kPafAnimation_glisse = 3,
+ kPafAnimation_meeting = 4,
+ kPafAnimation_island = 5,
+ kPafAnimation_islefall = 6,
+ kPafAnimation_vicious = 7,
+ kPafAnimation_together = 8,
+ kPafAnimation_power = 9,
+ kPafAnimation_back = 10,
+ kPafAnimation_dogfree1 = 11,
+ kPafAnimation_dogfree2 = 12,
+ kPafAnimation_meteor = 13,
+ kPafAnimation_cookie = 14,
+ kPafAnimation_plot = 15,
+ kPafAnimation_puzzle = 16,
+ kPafAnimation_lstpiece = 17,
+ kPafAnimation_dogfall = 18,
+ kPafAnimation_lastfall = 19,
+ kPafAnimation_end = 20,
+ kPafAnimation_cinema = 21,
+ kPafAnimation_CanyonAndyFallingCannon = 22, // kPafAnimation_ghct
+ kPafAnimation_CanyonAndyFalling = 23, // kPafAnimation_chute
+ kPafAnimation_IslandAndyFalling = 24 // kPafAnimation_chute_i
+};
+
+struct FileSystem;
+
+struct PafAudioQueue {
+ int16_t *buffer; // stereo samples
+ int offset, size;
+ PafAudioQueue *next;
+};
+
+struct PafCallback {
+ void (*frameProc)(void *userdata, int num, const uint8_t *frame);
+ void (*endProc)(void *userdata);
+ void *userdata;
+};
+
+struct PafPlayer {
+
+ enum {
+ kMaxVideosCount = 50,
+ kBufferBlockSize = 2048,
+ kVideoWidth = 256,
+ kVideoHeight = 192,
+ kPageBufferSize = 256 * 256,
+ kAudioSamples = 2205,
+ kAudioStrideSize = 4922 // 256 * sizeof(int16_t) + 2205 * 2
+ };
+
+ bool _skipCutscenes;
+ FileSystem *_fs;
+ File _file;
+ int _videoNum;
+ uint32_t _videoOffset;
+ PafHeader _pafHdr;
+ int _currentPageBuffer;
+ uint8_t *_pageBuffers[4];
+ uint8_t _paletteBuffer[256 * 3];
+ bool _paletteChanged;
+ uint8_t _bufferBlock[kBufferBlockSize];
+ uint8_t *_demuxVideoFrameBlocks;
+ uint8_t *_demuxAudioFrameBlocks;
+ uint32_t _audioBufferOffsetRd;
+ uint32_t _audioBufferOffsetWr;
+ PafAudioQueue *_audioQueue, *_audioQueueTail;
+ uint32_t _flushAudioSize;
+ uint32_t _playedMask;
+ PafCallback _pafCb;
+ int _volume;
+ int _frameMs;
+
+ PafPlayer(FileSystem *fs);
+ ~PafPlayer();
+
+ void setVolume(int volume);
+
+ void preload(int num);
+ void play(int num);
+ void unload(int num = -1);
+
+ bool readPafHeader();
+ uint32_t *readPafHeaderTable(int count);
+
+ void decodeVideoFrame(const uint8_t *src);
+ uint8_t *getVideoPageOffset(uint16_t val);
+ void decodeVideoFrameOp0(const uint8_t *base, const uint8_t *src, uint8_t code);
+ void decodeVideoFrameOp1(const uint8_t *src);
+ void decodeVideoFrameOp2(const uint8_t *src);
+ void decodeVideoFrameOp4(const uint8_t *src);
+
+ void decodeAudioFrame(const uint8_t *src, uint32_t offset, uint32_t size);
+
+ void mix(int16_t *buf, int samples);
+ void mainLoop();
+
+ void setCallback(const PafCallback *pafCb);
+};
+
+#endif // PAF_PLAYER_H__
diff --git a/Src/Global/random.cpp b/Src/Global/random.cpp
new file mode 100644
index 0000000..fe2c021
--- /dev/null
+++ b/Src/Global/random.cpp
@@ -0,0 +1,79 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "random.h"
+
+void Random::setSeed() {
+ _rndSeed = 0x101A1010;
+}
+
+void Random::initMstTable() {
+ for (int i = 0; i < 8; ++i) {
+ for (int j = 0; j < 32; ++j) {
+ _mstRandomTable[i][j] = j;
+ }
+ for (int j = 0; j < 64; ++j) {
+ const int index1 = update() & 31;
+ const int index2 = update() & 31;
+ SWAP(_mstRandomTable[i][index1], _mstRandomTable[i][index2]);
+ }
+ }
+}
+
+void Random::initTable(int rounds) {
+ for (int i = 0; i < 100; ++i) {
+ _rndRandomTable[i] = i + 1;
+ }
+ for (int j = 0; j < rounds; ++j) {
+ for (int i = 0; i < 256; ++i) {
+ const int a = update() % 100;
+ const int b = update() % 100;
+ SWAP(_rndRandomTable[a], _rndRandomTable[b]);
+ }
+ }
+ _rndRandomTableIndex = 0;
+}
+
+uint32_t Random::update() {
+ const bool overflow = (_rndSeed & (1 << 31)) != 0;
+ _rndSeed *= 2;
+ if (!overflow) {
+ _rndSeed ^= 0xDEADBEEF;
+ }
+ return _rndSeed;
+}
+
+uint8_t Random::getNextNumber() {
+ const uint8_t num = _rndRandomTable[_rndRandomTableIndex];
+ ++_rndRandomTableIndex;
+ if (_rndRandomTableIndex == 100) {
+ _rndRandomTableIndex = 0;
+ }
+ return num;
+}
+
+void Random::resetMst(uint8_t *p) {
+ p[0] = update() & 7; // row
+ p[1] = update() & 31; // start
+ p[2] = 32; // count
+}
+
+uint8_t Random::getMstNextNumber(uint8_t *p) {
+ const uint8_t num = _mstRandomTable[p[0]][p[1]];
+ ++p[1];
+ if (p[1] >= 32) {
+ p[1] = 0;
+ }
+ --p[2];
+ if (p[2] == 0) { // move to next row
+ ++p[0];
+ if (p[0] >= 8) {
+ p[0] = 0;
+ }
+ p[1] = update() & 31;
+ p[2] = 32;
+ }
+ return num;
+}
diff --git a/Src/Global/random.h b/Src/Global/random.h
new file mode 100644
index 0000000..2eecdcc
--- /dev/null
+++ b/Src/Global/random.h
@@ -0,0 +1,26 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef RANDOM_H__
+#define RANDOM_H__
+
+#include "intern.h"
+
+struct Random {
+ uint32_t _rndSeed;
+ uint8_t _rndRandomTable[100];
+ int _rndRandomTableIndex;
+ uint8_t _mstRandomTable[8][32];
+
+ void setSeed();
+ void initMstTable();
+ void initTable(int rounds = 1);
+ uint32_t update();
+ uint8_t getNextNumber();
+ void resetMst(uint8_t *p);
+ uint8_t getMstNextNumber(uint8_t *p);
+};
+
+#endif // RANDOM_H__
diff --git a/Src/Global/resource.cpp b/Src/Global/resource.cpp
new file mode 100644
index 0000000..2f885ff
--- /dev/null
+++ b/Src/Global/resource.cpp
@@ -0,0 +1,2097 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "fileio.h"
+#include "fs.h"
+#include "game.h"
+#include "lzw.h"
+#include "resource.h"
+#include "util.h"
+
+// load and uncompress .sss pcm on level start
+static const bool kPreloadSssPcm = true;
+
+static const bool kPreloadLvlBackgroundData = true;
+
+static const bool kCheckSssBytecode = false;
+
+// menu settings and player progress
+static const char *_setupCfg = "setup.cfg";
+
+static const char *_setupDat = "SETUP.DAT";
+static const char *_setupDax = "SETUP.DAX";
+
+static const char *_hodDem = "HOD.DEM";
+
+static const char *_prefixes[] = {
+ "rock",
+ "fort",
+ "pwr1",
+ "isld",
+ "lava",
+ "pwr2",
+ "lar1",
+ "lar2",
+ "dark",
+ "test"
+};
+
+static bool openDat(FileSystem *fs, const char *name, File *f) {
+ FILE *fp = fs->openAssetFile(name);
+ if (fp) {
+ f->setFp(fp);
+ f->seek(0, SEEK_SET);
+ return true;
+ }
+ return false;
+}
+
+static void closeDat(FileSystem *fs, File *f) {
+ if (f->_fp) {
+ fs->closeFile(f->_fp);
+ f->setFp(0);
+ }
+}
+
+static int skipBytesAlign(File *f, int len) {
+ const int size = (len + 3) & ~3;
+ f->seek(size, SEEK_CUR);
+ return size;
+}
+
+static int readBytesAlign(File *f, uint8_t *buf, int len) {
+ f->read(buf, len);
+ if ((len & 3) != 0) {
+ f->seek(4 - (len & 3), SEEK_CUR);
+ }
+ return (len + 3) & ~3;
+}
+
+Resource::Resource(FileSystem *fs)
+ : _fs(fs), _isPsx(false), _isDemo(false), _version(V1_1) {
+
+ memset(_screensGrid, 0, sizeof(_screensGrid));
+ memset(_screensBasePos, 0, sizeof(_screensBasePos));
+ memset(_screensState, 0, sizeof(_screensState));
+
+ // masks
+ _resLevelData0x470CTable = 0;
+ _resLevelData0x470CTablePtrHdr = 0;
+ _resLevelData0x470CTablePtrData = 0;
+
+ _lvlSssOffset = 0;
+
+ // sprites
+ memset(_resLevelData0x2988SizeTable, 0, sizeof(_resLevelData0x2988SizeTable));
+ memset(_resLevelData0x2988Table, 0, sizeof(_resLevelData0x2988Table));
+ memset(_resLevelData0x2988PtrTable, 0, sizeof(_resLevelData0x2988PtrTable));
+ memset(_resLvlSpriteDataPtrTable, 0, sizeof(_resLvlSpriteDataPtrTable));
+
+ // backgrounds
+ memset(_resLvlScreenBackgroundDataTable, 0, sizeof(_resLvlScreenBackgroundDataTable));
+ memset(_resLvlScreenBackgroundDataPtrTable, 0, sizeof(_resLvlScreenBackgroundDataPtrTable));
+ memset(_resLevelData0x2B88SizeTable, 0, sizeof(_resLevelData0x2B88SizeTable));
+
+ memset(_resLvlScreenObjectDataTable, 0, sizeof(_resLvlScreenObjectDataTable));
+ memset(&_dummyObject, 0, sizeof(_dummyObject));
+
+ if (sectorAlignedGameData()) {
+ _datFile = new SectorFile;
+ _lvlFile = new SectorFile;
+ _mstFile = new SectorFile;
+ _sssFile = new SectorFile;
+ // from v1.2, game data files are 'sector aligned'
+ _version = V1_2;
+ } else {
+ _datFile = new File;
+ _lvlFile = new File;
+ _mstFile = new File;
+ _sssFile = new File;
+ // detect if this is version 1.0 by reading the size of the first screen background using the v1.1 offset
+ char filename[32];
+ snprintf(filename, sizeof(filename), "%s_HOD.LVL", _prefixes[0]);
+ if (openDat(_fs, filename, _lvlFile)) {
+ _lvlFile->seek(0x2B88, SEEK_SET);
+ _lvlFile->skipUint32();
+ const int size = _lvlFile->readUint32();
+ if (size == 0) {
+ _version = V1_0;
+ }
+ closeDat(_fs, _lvlFile);
+ }
+ }
+ // detect if this is a demo version by trying to open the second level data files
+ char filename[32];
+ snprintf(filename, sizeof(filename), "%s_HOD.LVL", _prefixes[1]);
+ if (openDat(_fs, filename, _lvlFile)) {
+ closeDat(_fs, _lvlFile);
+ } else {
+ _isDemo = true;
+ }
+ debug(kDebug_RESOURCE, "psx %d demo %d version %d", _isPsx, _isDemo, _version);
+ memset(&_datHdr, 0, sizeof(_datHdr));
+ memset(&_lvlHdr, 0, sizeof(_lvlHdr));
+ memset(&_mstHdr, 0, sizeof(_mstHdr));
+ memset(&_sssHdr, 0, sizeof(_sssHdr));
+
+ _lvlSpritesOffset = 0x288 + 96 * (_version == V1_0 ? 96 : 104);
+ _lvlBackgroundsOffset = _lvlSpritesOffset + 32 * 16;
+ _lvlMasksOffset = _lvlBackgroundsOffset + kMaxScreens * (16 + 160);
+
+ _loadingImageBuffer = 0;
+ _fontBuffer = 0;
+ _fontDefaultColor = 0;
+ _menuBuffer0 = 0;
+ _menuBuffer1 = 0;
+
+ memset(&_dem, 0, sizeof(_dem));
+ _demOffset = 0;
+}
+
+Resource::~Resource() {
+ delete _datFile;
+ delete _lvlFile;
+ delete _mstFile;
+ delete _sssFile;
+}
+
+bool Resource::sectorAlignedGameData() {
+ FILE *fp = _fs->openAssetFile(_setupDat);
+ if (!fp) {
+ fp = _fs->openAssetFile(_setupDax);
+ if (!fp) {
+ error("Unable to open '%s' or '%s'", _setupDat, _setupDax);
+ return false;
+ }
+ }
+ bool ret = false;
+ uint8_t buf[2048];
+ if (fread(buf, 1, sizeof(buf), fp) == sizeof(buf)) {
+ ret = fioUpdateCRC(0, buf, sizeof(buf)) == 0;
+ }
+ _fs->closeFile(fp);
+ return ret;
+}
+
+void Resource::loadSetupDat() {
+ if (!openDat(_fs, _setupDat, _datFile)) {
+ _isPsx = openDat(_fs, _setupDax, _datFile);
+ }
+
+ _datHdr.version = _datFile->readUint32();
+ if (_datHdr.version != 10 && _datHdr.version != 11) {
+ warning("Unhandled .dat version %d", _datHdr.version);
+ return;
+ }
+
+ _datHdr.bufferSize0 = _datFile->readUint32();
+ _datHdr.bufferSize1 = _datFile->readUint32();
+ _datHdr.sssOffset = _datFile->readUint32();
+ _datHdr.iconsCount = _datFile->readUint32();
+ _datHdr.menusCount = _datFile->readUint32();
+ _datHdr.cutscenesCount = _datFile->readUint32();
+ _datHdr.levelsCount = _datFile->readUint32();
+ for (int i = 0; i < kLvl_dark; ++i) { // last level has a single checkpoint
+ _datHdr.levelCheckpointsCount[i] = _datFile->readUint32();
+ }
+ _datHdr.yesNoQuitImage = _datFile->readUint32();
+ _datHdr.soundDataSize = _datFile->readUint32();
+ _datHdr.loadingImageSize = _datFile->readUint32();
+ const int hintsCount = (_datHdr.version == 11) ? 46 : 20;
+ for (int i = 0; i < hintsCount; ++i) {
+ _datHdr.hintsImageOffsetTable[i] = _datFile->readUint32();
+ }
+ for (int i = 0; i < hintsCount; ++i) {
+ _datHdr.hintsImageSizeTable[i] = _datFile->readUint32();
+ }
+ _datFile->seek(2048, SEEK_SET); // align to next sector
+
+ _loadingImageBuffer = (uint8_t *)malloc(_datHdr.loadingImageSize);
+ if (_loadingImageBuffer) {
+ _datFile->read(_loadingImageBuffer, _datHdr.loadingImageSize);
+
+ uint32_t offset = 0;
+
+ if (!_isPsx) {
+ // loading image
+ uint32_t size = READ_LE_UINT32(_loadingImageBuffer + offset); offset += 8;
+ offset += size + 768;
+
+ // loading animation
+ size = READ_LE_UINT32(_loadingImageBuffer + offset + 8); offset += 16;
+ offset += size;
+ }
+
+ // font
+ static const int kFontSize = 16 * 16 * 64;
+ _fontBuffer = (uint8_t *)malloc(kFontSize);
+ if (_fontBuffer) {
+ /* size = READ_LE_UINT32(_loadingImageBuffer + offset); */ offset += 4;
+ if (_datHdr.version == 11) {
+ const uint32_t uncompressedSize = decodeLZW(_loadingImageBuffer + offset, _fontBuffer);
+ assert(uncompressedSize == kFontSize);
+ } else {
+ memcpy(_fontBuffer, _loadingImageBuffer + offset, kFontSize);
+ }
+ for (int i = 0; i < kFontSize; ++i) {
+ _fontDefaultColor = _fontBuffer[i];
+ if (_fontDefaultColor != 0) {
+ break;
+ }
+ }
+ }
+ }
+ assert(_datHdr.yesNoQuitImage == hintsCount - 3);
+ _menuBuffersOffset = _datHdr.hintsImageOffsetTable[_datHdr.yesNoQuitImage + 2];
+}
+
+bool Resource::loadDatHintImage(int num, uint8_t *dst, uint8_t *pal) {
+ const int size = _datHdr.hintsImageSizeTable[num];
+ if (size == 0) {
+ return false;
+ }
+ const int offset = _datHdr.hintsImageOffsetTable[num];
+ assert(size <= 256 * 192);
+ _datFile->seek(offset, SEEK_SET);
+ _datFile->read(dst, size);
+ if (!_isPsx) {
+ if (_datHdr.version == 11) {
+ _datFile->seek(offset + fioAlignSizeTo2048(size), SEEK_SET); // align to next sector
+ }
+ _datFile->read(pal, 768);
+ }
+ return true;
+}
+
+bool Resource::loadDatLoadingImage(uint8_t *dst, uint8_t *pal) {
+ assert(!_isPsx);
+ if (_loadingImageBuffer) {
+ const uint32_t bufferSize = READ_LE_UINT32(_loadingImageBuffer);
+ const int size = decodeLZW(_loadingImageBuffer + 8, dst);
+ assert(size == 256 * 192);
+ // palette follows compressed bitmap
+ memcpy(pal, _loadingImageBuffer + 8 + bufferSize, 256 * 3);
+ return true;
+ }
+ return false;
+}
+
+void Resource::loadDatMenuBuffers() {
+ assert((_datHdr.sssOffset & 0x7FF) == 0);
+ _datFile->seek(_datHdr.sssOffset, SEEK_SET);
+ loadSssData(_datFile, _datHdr.sssOffset);
+
+ const uint32_t baseOffset = _menuBuffersOffset;
+ _datFile->seek(baseOffset, SEEK_SET);
+ _menuBuffer1 = (uint8_t *)malloc(_datHdr.bufferSize1);
+ if (_menuBuffer1) {
+ _datFile->read(_menuBuffer1, _datHdr.bufferSize1);
+ }
+ if (_datHdr.version == 11) {
+ _datFile->seek(baseOffset + fioAlignSizeTo2048(_datHdr.bufferSize1), SEEK_SET); // align to next sector
+ }
+ _menuBuffer0 = (uint8_t *)malloc(_datHdr.bufferSize0);
+ if (_menuBuffer0) {
+ _datFile->read(_menuBuffer0, _datHdr.bufferSize0);
+ }
+}
+
+void Resource::unloadDatMenuBuffers() {
+ free(_menuBuffer1);
+ _menuBuffer1 = 0;
+ free(_menuBuffer0);
+ _menuBuffer0 = 0;
+}
+
+void Resource::loadLevelData(int levelNum) {
+
+ char filename[32];
+ const char *levelName = _prefixes[levelNum];
+
+ closeDat(_fs, _lvlFile);
+ snprintf(filename, sizeof(filename), "%s_HOD.LVL", levelName);
+ if (openDat(_fs, filename, _lvlFile)) {
+ loadLvlData(_lvlFile);
+ } else {
+ error("Unable to open '%s'", filename);
+ }
+
+ closeDat(_fs, _mstFile);
+ snprintf(filename, sizeof(filename), "%s_HOD.MST", levelName);
+ if (openDat(_fs, filename, _mstFile)) {
+ loadMstData(_mstFile);
+ } else {
+ warning("Unable to open '%s'", filename);
+ memset(&_mstHdr, 0, sizeof(_mstHdr));
+ }
+
+ closeDat(_fs, _sssFile);
+ snprintf(filename, sizeof(filename), "%s_HOD.SSS", levelName);
+ if (openDat(_fs, filename, _sssFile)) {
+ loadSssData(_sssFile);
+ } else if (_isPsx) {
+ assert((_lvlSssOffset & 0x7FF) == 0);
+ _lvlFile->seek(_lvlSssOffset, SEEK_SET);
+ loadSssData(_lvlFile, _lvlSssOffset);
+ } else {
+ warning("Unable to open '%s'", filename);
+ memset(&_sssHdr, 0, sizeof(_sssHdr));
+ }
+}
+
+void Resource::loadLvlScreenObjectData(LvlObject *dat, const uint8_t *src) {
+ const uint8_t *start = src;
+ dat->xPos = READ_LE_UINT32(src); src += 4;
+ dat->yPos = READ_LE_UINT32(src); src += 4;
+ dat->screenNum = *src++;
+ dat->screenState = *src++;
+ dat->dataNum = *src++;
+ dat->frame = *src++;
+ dat->anim = READ_LE_UINT16(src); src += 2;
+ dat->type = *src++;
+ dat->spriteNum = *src++;
+ dat->flags0 = READ_LE_UINT16(src); src += 2;
+ dat->flags1 = READ_LE_UINT16(src); src += 2;
+ dat->flags2 = READ_LE_UINT16(src); src += 2;
+ dat->objectUpdateType = *src++;
+ dat->hitCount = *src++;
+ const uint32_t objRef = READ_LE_UINT32(src); src += 4;
+ if (objRef) {
+ dat->childPtr = &_dummyObject;
+ debug(kDebug_RESOURCE, "loadLvlObj dat %p linkObjRef 0x%x", dat, objRef);
+ }
+ dat->width = READ_LE_UINT16(src); src += 2;
+ dat->height = READ_LE_UINT16(src); src += 2;
+ dat->directionKeyMask = *src++;
+ dat->actionKeyMask = *src++;
+ dat->currentSprite = READ_LE_UINT16(src); src += 2;
+ dat->currentSound = READ_LE_UINT16(src); src += 2;
+ src += 2; // 0x26
+ dat->bitmapBits = 0; src += 4;
+ dat->callbackFuncPtr = 0; src += 4;
+ dat->dataPtr = 0; src += 4;
+ dat->sssObject = 0; src += 4;
+ dat->levelData0x2988 = 0; src += 4;
+ for (int i = 0; i < 8; ++i) {
+ dat->posTable[i].x = READ_LE_UINT16(src); src += 2;
+ dat->posTable[i].y = READ_LE_UINT16(src); src += 2;
+ }
+ dat->nextPtr = 0; src += 4;
+ assert((src - start) == 96);
+}
+
+static uint32_t resFixPointersLevelData0x2988(uint8_t *src, uint8_t *ptr, LvlObjectData *dat, bool isPsx) {
+ uint8_t *base = src;
+
+ dat->unk0 = *src++;
+ dat->spriteNum = *src++;
+ dat->framesCount = READ_LE_UINT16(src); src += 2;
+ dat->hotspotsCount = READ_LE_UINT16(src); src += 2;
+ dat->movesCount = READ_LE_UINT16(src); src += 2;
+ dat->coordsCount = READ_LE_UINT16(src); src += 2;
+ dat->refCount = *src++;
+ dat->frame = *src++;
+ dat->anim = READ_LE_UINT16(src); src += 2;
+ src += 2; // 0xE
+ src += 4; // 0x10
+ uint32_t movesDataOffset = READ_LE_UINT32(src); src += 4; // 0x14
+ src += 4; // 0x18
+ uint32_t framesDataOffset = READ_LE_UINT32(src); src += 4; // 0x1C
+ src += 4; // 0x20
+ uint32_t coordsDataOffset = READ_LE_UINT32(src); src += 4; // 0x24
+ uint32_t hotspotsDataOffset = READ_LE_UINT32(src); src += 4; // 0x28
+
+ if (dat->refCount != 0) {
+ return 0;
+ }
+
+ assert(src == base + kLvlAnimHdrOffset);
+ dat->animsInfoData = base;
+ dat->refCount = 0xFF;
+ dat->framesData = (framesDataOffset == 0) ? 0 : base + framesDataOffset;
+ dat->hotspotsData = (hotspotsDataOffset == 0) ? 0 : base + hotspotsDataOffset;
+ dat->movesData = (movesDataOffset == 0) ? 0 : base + movesDataOffset;
+ dat->coordsData = (coordsDataOffset == 0) ? 0 : base + coordsDataOffset;
+ if (kByteSwapData) {
+ for (int i = 0; i < dat->hotspotsCount; ++i) {
+ LvlAnimHeader *ah = ((LvlAnimHeader *)(base + kLvlAnimHdrOffset)) + i;
+ ah->unk0 = le16toh(ah->unk0);
+ ah->seqOffset = le32toh(ah->seqOffset);
+ if (ah->seqOffset == 0) {
+ continue;
+ }
+ for (int j = 0; j < ah->seqCount; ++j) {
+ LvlAnimSeqHeader *ash = ((LvlAnimSeqHeader *)(base + ah->seqOffset)) + j;
+ ash->firstFrame = le16toh(ash->firstFrame);
+ ash->unk2 = le16toh(ash->unk2);
+ ash->sound = le16toh(ash->sound);
+ ash->flags0 = le16toh(ash->flags0);
+ ash->flags1 = le16toh(ash->flags1);
+ ash->unkE = le16toh(ash->unkE);
+ ash->offset = le32toh(ash->offset);
+ if (ash->offset == 0) {
+ continue;
+ }
+ for (int k = 0; k < ash->count; ++k) {
+ LvlAnimSeqFrameHeader *asfh = ((LvlAnimSeqFrameHeader *)(base + ash->offset)) + k;
+ asfh->move = le16toh(asfh->move);
+ asfh->anim = le16toh(asfh->anim);
+ }
+ }
+ }
+ for (int i = 0; i < dat->movesCount; ++i) {
+ LvlSprMoveData *smd = ((LvlSprMoveData *)dat->movesData) + i;
+ smd->op3 = le16toh(smd->op3);
+ smd->op4 = le16toh(smd->op4);
+ }
+ }
+
+ dat->framesOffsetsTable = ptr;
+ if (dat->coordsData) {
+ dat->coordsOffsetsTable = ptr + dat->framesCount * 4;
+ } else {
+ dat->coordsOffsetsTable = 0;
+ }
+
+ if (dat->unk0 == 1) { // fixed size offset table
+ assert(isPsx);
+ dat->framesOffsetsTable = (uint8_t *)malloc(dat->framesCount * sizeof(uint32_t));
+ uint32_t framesOffset = 6 * dat->framesCount;
+ if (READ_LE_UINT16(dat->framesData + framesOffset) == 0) {
+ framesOffset += 2;
+ }
+ for (int i = 0; i < dat->framesCount; ++i) {
+ const int size = READ_LE_UINT16(dat->framesData + i * 6);
+ WRITE_LE_UINT32(dat->framesOffsetsTable + i * sizeof(uint32_t), framesOffset);
+ framesOffset += size;
+ }
+ dat->coordsOffsetsTable = ptr;
+ return 0;
+ }
+
+ uint32_t framesOffset = 0;
+ for (int i = 0; i < dat->framesCount; ++i) {
+ const int size = READ_LE_UINT16(dat->framesData + framesOffset);
+ WRITE_LE_UINT32(dat->framesOffsetsTable + i * sizeof(uint32_t), framesOffset);
+ framesOffset += size;
+ }
+ uint32_t coordsOffset = 0;
+ for (int i = 0; i < dat->coordsCount; ++i) {
+ const int count = dat->coordsData[coordsOffset];
+ WRITE_LE_UINT32(dat->coordsOffsetsTable + i * sizeof(uint32_t), coordsOffset);
+ coordsOffset += count * 4 + 1;
+ }
+
+ return (dat->framesCount + dat->coordsCount) * sizeof(uint32_t);
+}
+
+void Resource::loadLvlSpriteData(int num, const uint8_t *buf) {
+ assert((unsigned int)num < kMaxSpriteTypes);
+
+ static const uint32_t baseOffset = _lvlSpritesOffset;
+
+ uint8_t header[3 * sizeof(uint32_t)];
+ if (!buf) {
+ _lvlFile->seekAlign(baseOffset + num * 16);
+ _lvlFile->read(header, sizeof(header));
+ buf = header;
+ }
+ const uint32_t offset = READ_LE_UINT32(&buf[0]);
+ const uint32_t size = READ_LE_UINT32(&buf[4]);
+ if (size == 0) {
+ return;
+ }
+ const uint32_t readSize = READ_LE_UINT32(&buf[8]);
+ assert(readSize <= size);
+ uint8_t *ptr = (uint8_t *)malloc(size);
+ _lvlFile->seek(_isPsx ? _lvlSssOffset + offset : offset, SEEK_SET);
+ _lvlFile->read(ptr, readSize);
+
+ LvlObjectData *dat = &_resLevelData0x2988Table[num];
+ const uint32_t readOffsetsSize = resFixPointersLevelData0x2988(ptr, ptr + readSize, dat, _isPsx);
+ const uint32_t allocatedOffsetsSize = size - readSize;
+ assert(allocatedOffsetsSize == readOffsetsSize);
+
+ _resLevelData0x2988PtrTable[dat->spriteNum] = dat;
+ _resLvlSpriteDataPtrTable[num] = ptr;
+ _resLevelData0x2988SizeTable[num] = size;
+}
+
+const uint8_t *Resource::getLvlScreenMaskDataPtr(int num) const {
+ assert((unsigned int)num < kMaxScreens * 4);
+ const uint32_t offset = READ_LE_UINT32(_resLevelData0x470CTablePtrHdr + num * 8);
+ return (offset != 0) ? _resLevelData0x470CTable + offset : 0;
+}
+
+const uint8_t *Resource::getLvlScreenPosDataPtr(int num) const {
+ assert((unsigned int)num < kMaxScreens * 4);
+ const uint32_t offset = READ_LE_UINT32(_resLevelData0x470CTablePtrHdr + num * 8 + 4);
+ return (offset != 0) ? _resLevelData0x470CTable + offset : 0;
+}
+
+void Resource::loadLvlScreenMaskData() {
+ _lvlFile->seekAlign(_lvlMasksOffset);
+ const uint32_t offset = _lvlFile->readUint32();
+ const uint32_t size = _lvlFile->readUint32();
+ _resLevelData0x470CTable = (uint8_t *)malloc(size);
+ _lvlFile->seek(offset, SEEK_SET);
+ _lvlFile->read(_resLevelData0x470CTable, size);
+ _resLevelData0x470CTablePtrHdr = _resLevelData0x470CTable;
+ _resLevelData0x470CTablePtrData = _resLevelData0x470CTable + (kMaxScreens * 4) * (2 * sizeof(uint32_t));
+ // .sss is embedded in .lvl on PSX
+ _lvlSssOffset = offset + fioAlignSizeTo2048(size);
+}
+
+static const uint32_t _lvlTag = 0x484F4400; // 'HOD\x00'
+
+void Resource::loadLvlData(File *fp) {
+
+ assert(fp == _lvlFile);
+
+ unloadLvlData();
+
+ const uint32_t tag = _lvlFile->readUint32();
+ if (tag != _lvlTag) {
+ error("Unhandled .lvl tag 0x%x", tag);
+ closeDat(_fs, _lvlFile);
+ return;
+ }
+
+ _lvlHdr.screensCount = _lvlFile->readByte();
+ _lvlHdr.staticLvlObjectsCount = _lvlFile->readByte();
+ _lvlHdr.otherLvlObjectsCount = _lvlFile->readByte();
+ _lvlHdr.spritesCount = _lvlFile->readByte();
+ debug(kDebug_RESOURCE, "Resource::loadLvlData() %d %d %d %d", _lvlHdr.screensCount, _lvlHdr.staticLvlObjectsCount, _lvlHdr.otherLvlObjectsCount, _lvlHdr.spritesCount);
+
+ _lvlFile->seekAlign(0x8);
+ for (int i = 0; i < _lvlHdr.screensCount; ++i) {
+ _lvlFile->read(_screensGrid[i], 4);
+ }
+ _lvlFile->seekAlign(0xA8);
+ for (int i = 0; i < _lvlHdr.screensCount; ++i) {
+ LvlScreenVector *dat = &_screensBasePos[i];
+ dat->u = _lvlFile->readUint32();
+ dat->v = _lvlFile->readUint32();
+ }
+ _lvlFile->seekAlign(0x1E8);
+ for (int i = 0; i < _lvlHdr.screensCount; ++i) {
+ LvlScreenState *dat = &_screensState[i];
+ dat->s0 = _lvlFile->readByte();
+ dat->s1 = _lvlFile->readByte();
+ dat->s2 = _lvlFile->readByte();
+ dat->s3 = _lvlFile->readByte();
+ }
+ _lvlFile->seekAlign(0x288);
+ static const int kSizeOfLvlObject = 96;
+ const int lvlObjectsCount = (_lvlSpritesOffset - 0x288) / kSizeOfLvlObject;
+ debug(kDebug_RESOURCE, "Resource::loadLvlData() lvlObjectsCount %d", lvlObjectsCount);
+ for (int i = 0; i < lvlObjectsCount; ++i) {
+ LvlObject *dat = &_resLvlScreenObjectDataTable[i];
+ uint8_t buf[kSizeOfLvlObject];
+ _lvlFile->read(buf, kSizeOfLvlObject);
+ loadLvlScreenObjectData(dat, buf);
+ }
+
+ loadLvlScreenMaskData();
+
+ memset(_resLevelData0x2988SizeTable, 0, sizeof(_resLevelData0x2988SizeTable));
+ memset(_resLevelData0x2988PtrTable, 0, sizeof(_resLevelData0x2988PtrTable));
+
+ _lvlFile->seekAlign(_lvlSpritesOffset);
+ uint8_t spr[kMaxSpriteTypes * 16];
+ assert(_lvlHdr.spritesCount <= kMaxSpriteTypes);
+ _lvlFile->read(spr, _lvlHdr.spritesCount * 16);
+ for (int i = 0; i < _lvlHdr.spritesCount; ++i) {
+ loadLvlSpriteData(i, spr + i * 16);
+ }
+
+ memset(_resLevelData0x2B88SizeTable, 0, sizeof(_resLevelData0x2B88SizeTable));
+
+ if (kPreloadLvlBackgroundData) {
+ _lvlFile->seekAlign(_lvlBackgroundsOffset);
+ uint8_t buf[kMaxScreens * 16];
+ assert(_lvlHdr.screensCount <= kMaxScreens);
+ _lvlFile->read(buf, _lvlHdr.screensCount * 16);
+ for (unsigned int i = 0; i < _lvlHdr.screensCount; ++i) {
+ loadLvlScreenBackgroundData(i, buf + i * 16);
+ }
+ }
+}
+
+void Resource::unloadLvlData() {
+ free(_resLevelData0x470CTable);
+ _resLevelData0x470CTable = 0;
+ for (unsigned int i = 0; i < kMaxScreens; ++i) {
+ unloadLvlScreenBackgroundData(i);
+ }
+ for (unsigned int i = 0; i < kMaxSpriteTypes; ++i) {
+ LvlObjectData *dat = &_resLevelData0x2988Table[i];
+ if (dat->unk0 == 1) {
+ free(dat->framesOffsetsTable);
+ dat->framesOffsetsTable = 0;
+ }
+ free(_resLvlSpriteDataPtrTable[i]);
+ _resLvlSpriteDataPtrTable[i] = 0;
+ }
+}
+
+static uint32_t resFixPointersLevelData0x2B88(const uint8_t *src, uint8_t *ptr, uint8_t *offsetsPtr, LvlBackgroundData *dat, bool isPsx) {
+ const uint8_t *start = src;
+
+ dat->backgroundCount = *src++;
+ dat->currentBackgroundId = *src++;
+ dat->maskCount = *src++;
+ dat->currentMaskId = *src++;
+ dat->shadowCount = *src++;
+ dat->currentShadowId = *src++;
+ dat->soundCount = *src++;
+ dat->currentSoundId = *src++;
+ dat->dataUnk3Count = *src++;
+ dat->unk9 = *src++;
+ dat->dataUnk45Count = *src++;
+ dat->unkB = *src++;
+ dat->backgroundPaletteId = READ_LE_UINT16(src); src += 2;
+ dat->backgroundBitmapId = READ_LE_UINT16(src); src += 2;
+ for (int i = 0; i < 4; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ dat->backgroundPaletteTable[i] = (offs != 0) ? ptr + offs : 0;
+ }
+ for (int i = 0; i < 4; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ dat->backgroundBitmapTable[i] = (offs != 0) ? ptr + offs : 0;
+ }
+ for (int i = 0; i < 4; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ dat->dataUnk0Table[i] = (offs != 0) ? ptr + offs : 0;
+ }
+ for (int i = 0; i < 4; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ dat->backgroundMaskTable[i] = (offs != 0) ? ptr + offs : 0;
+ }
+ for (int i = 0; i < 4; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ dat->backgroundSoundTable[i] = (offs != 0) ? ptr + offs : 0;
+ }
+ for (int i = 0; i < 8; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ dat->backgroundAnimationTable[i] = (offs != 0) ? ptr + offs : 0;
+ }
+ uint32_t offsetsSize = 0;
+ for (int i = 0; i < 8; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ if (offs != 0) {
+ dat->backgroundLvlObjectDataTable[i] = (LvlObjectData *)malloc(sizeof(LvlObjectData));
+ offsetsSize += resFixPointersLevelData0x2988(ptr + offs, offsetsPtr + offsetsSize, dat->backgroundLvlObjectDataTable[i], isPsx);
+ } else {
+ dat->backgroundLvlObjectDataTable[i] = 0;
+ }
+ }
+ assert((src - start) == 160);
+ return offsetsSize;
+}
+
+void Resource::loadLvlScreenBackgroundData(int num, const uint8_t *buf) {
+ assert((unsigned int)num < kMaxScreens);
+
+ static const uint32_t baseOffset = _lvlBackgroundsOffset;
+
+ uint8_t header[3 * sizeof(uint32_t)];
+ if (!buf) {
+ _lvlFile->seekAlign(baseOffset + num * 16);
+ _lvlFile->read(header, sizeof(header));
+ buf = header;
+ }
+ const uint32_t offset = READ_LE_UINT32(&buf[0]);
+ const uint32_t size = READ_LE_UINT32(&buf[4]);
+ if (size == 0) {
+ return;
+ }
+ const uint32_t readSize = READ_LE_UINT32(&buf[8]);
+ assert(readSize <= size);
+ uint8_t *ptr = (uint8_t *)malloc(size);
+ _lvlFile->seek(_isPsx ? _lvlSssOffset + offset : offset, SEEK_SET);
+ _lvlFile->read(ptr, readSize);
+
+ uint8_t hdr[160];
+ _lvlFile->seekAlign(baseOffset + kMaxScreens * 16 + num * 160);
+ _lvlFile->read(hdr, 160);
+ LvlBackgroundData *dat = &_resLvlScreenBackgroundDataTable[num];
+ const uint32_t readOffsetsSize = resFixPointersLevelData0x2B88(hdr, ptr, ptr + readSize, dat, _isPsx);
+ const uint32_t allocatedOffsetsSize = size - readSize;
+ assert(allocatedOffsetsSize == readOffsetsSize);
+
+ _resLvlScreenBackgroundDataPtrTable[num] = ptr;
+ _resLevelData0x2B88SizeTable[num] = size;
+}
+
+void Resource::unloadLvlScreenBackgroundData(int num) {
+ if (_resLevelData0x2B88SizeTable[num] != 0) {
+ free(_resLvlScreenBackgroundDataPtrTable[num]);
+ _resLvlScreenBackgroundDataPtrTable[num] = 0;
+ _resLevelData0x2B88SizeTable[num] = 0;
+
+ LvlBackgroundData *dat = &_resLvlScreenBackgroundDataTable[num];
+ for (int i = 0; i < 4; ++i) {
+ free(dat->backgroundLvlObjectDataTable[i]);
+ }
+ memset(dat, 0, sizeof(LvlBackgroundData));
+ }
+}
+
+bool Resource::isLvlSpriteDataLoaded(int num) const {
+ return _resLevelData0x2988SizeTable[num] != 0;
+}
+
+bool Resource::isLvlBackgroundDataLoaded(int num) const {
+ return _resLevelData0x2B88SizeTable[num] != 0;
+}
+
+void Resource::incLvlSpriteDataRefCounter(LvlObject *ptr) {
+ LvlObjectData *dat = _resLevelData0x2988PtrTable[ptr->spriteNum];
+ assert(dat);
+ ++dat->refCount;
+ ptr->levelData0x2988 = dat;
+}
+
+void Resource::decLvlSpriteDataRefCounter(LvlObject *ptr) {
+ LvlObjectData *dat = _resLevelData0x2988PtrTable[ptr->spriteNum];
+ if (dat) {
+ --dat->refCount;
+ }
+}
+
+const uint8_t *Resource::getLvlSpriteFramePtr(LvlObjectData *dat, int frame, uint16_t *w, uint16_t *h) const {
+ assert(frame < dat->framesCount);
+ const uint8_t *p = dat->framesData;
+ if (dat->unk0 == 1) {
+ p += frame * 6;
+ const uint16_t size = READ_LE_UINT16(p);
+ *w = READ_LE_UINT16(p + 2);
+ *h = READ_LE_UINT16(p + 4);
+ if (size > 8) {
+ return dat->framesData + READ_LE_UINT32(dat->framesOffsetsTable + frame * sizeof(uint32_t));
+ }
+ } else {
+ p += READ_LE_UINT32(dat->framesOffsetsTable + frame * sizeof(uint32_t));
+ const uint16_t size = READ_LE_UINT16(p);
+ *w = READ_LE_UINT16(p + 2);
+ *h = READ_LE_UINT16(p + 4);
+ if (size > 8) {
+ return p + 6;
+ }
+ }
+ return 0;
+}
+
+const uint8_t *Resource::getLvlSpriteCoordPtr(LvlObjectData *dat, int num) const {
+ assert(num < dat->coordsCount);
+ return dat->coordsData + READ_LE_UINT32(dat->coordsOffsetsTable + num * sizeof(uint32_t));
+}
+
+int Resource::findScreenGridIndex(int screenNum) const {
+ for (int i = 0; i < 4; ++i) {
+ if (_screensGrid[_currentScreenResourceNum][i] == screenNum) {
+ return i;
+ }
+ }
+ if (_currentScreenResourceNum == screenNum) {
+ return 4;
+ }
+ return -1;
+}
+
+void Resource::loadSssData(File *fp, const uint32_t baseOffset) {
+
+ assert(fp == _sssFile || fp == _datFile || fp == _lvlFile);
+
+ if (_sssHdr.bufferSize != 0) {
+ unloadSssData();
+ _sssHdr.bufferSize = 0;
+ _sssHdr.infosDataCount = 0;
+ }
+ _sssHdr.version = fp->readUint32();
+ if (_sssHdr.version != 6 && _sssHdr.version != 10 && _sssHdr.version != 12) {
+ warning("Unhandled .sss version %d", _sssHdr.version);
+ closeDat(_fs, _sssFile);
+ return;
+ }
+
+ _sssHdr.bufferSize = fp->readUint32();
+ _sssHdr.preloadPcmCount = fp->readUint32();
+ _sssHdr.preloadInfoCount = fp->readUint32();
+ debug(kDebug_RESOURCE, "_sssHdr.bufferSize %d _sssHdr.preloadPcmCount %d _sssHdr.preloadInfoCount %d", _sssHdr.bufferSize, _sssHdr.preloadPcmCount, _sssHdr.preloadInfoCount);
+ _sssHdr.infosDataCount = fp->readUint32();
+ _sssHdr.filtersDataCount = fp->readUint32();
+ _sssHdr.banksDataCount = fp->readUint32();
+ debug(kDebug_RESOURCE, "_sssHdr.infosDataCount %d _sssHdr.filtersDataCount %d _sssHdr.banksDataCount %d", _sssHdr.infosDataCount, _sssHdr.filtersDataCount, _sssHdr.banksDataCount);
+ _sssHdr.samplesDataCount = fp->readUint32();
+ _sssHdr.codeSize = fp->readUint32();
+ debug(kDebug_RESOURCE, "_sssHdr.samplesDataCount %d _sssHdr.codeSize %d", _sssHdr.samplesDataCount, _sssHdr.codeSize);
+ if (_sssHdr.version == 10 || _sssHdr.version == 12) {
+ _sssHdr.preloadData1Count = fp->readUint32() & 255; // pcm
+ _sssHdr.preloadData2Count = fp->readUint32() & 255; // sprites
+ _sssHdr.preloadData3Count = fp->readUint32() & 255; // mst
+ }
+ _sssHdr.pcmCount = fp->readUint32();
+
+ const int bufferSize = _sssHdr.bufferSize + _sssHdr.filtersDataCount * 52 + _sssHdr.banksDataCount * 56;
+ debug(kDebug_RESOURCE, "bufferSize %d", bufferSize);
+
+ const bool preloadPcm = (fp == _datFile) || (kPreloadSssPcm && !_isPsx);
+
+ // fp->flush();
+ fp->seek(baseOffset + 2048, SEEK_SET); // align to the next sector
+
+ // _sssBuffer1
+ int bytesRead = 0;
+
+ _sssInfosData.allocate(_sssHdr.infosDataCount);
+ for (int i = 0; i < _sssHdr.infosDataCount; ++i) {
+ _sssInfosData[i].sssBankIndex = fp->readUint16(); // index _sssBanksData
+ _sssInfosData[i].sampleIndex = fp->readByte();
+ _sssInfosData[i].targetVolume = fp->readByte();
+ _sssInfosData[i].targetPriority = fp->readByte();
+ _sssInfosData[i].targetPanning = fp->readByte();
+ _sssInfosData[i].concurrencyMask = fp->readByte();
+ fp->skipByte(); // padding to 8 bytes
+ bytesRead += 8;
+ }
+ _sssDefaultsData.allocate(_sssHdr.filtersDataCount);
+ for (int i = 0; i < _sssHdr.filtersDataCount; ++i) {
+ _sssDefaultsData[i].defaultVolume = fp->readByte();
+ _sssDefaultsData[i].defaultPriority = fp->readByte();
+ _sssDefaultsData[i].defaultPanning = fp->readByte();
+ fp->skipByte(); // padding to 4 bytes
+ bytesRead += 4;
+ }
+ _sssBanksData.allocate(_sssHdr.banksDataCount);
+ for (int i = 0; i < _sssHdr.banksDataCount; ++i) {
+ _sssBanksData[i].flags = fp->readByte();
+ _sssBanksData[i].count = fp->readByte();
+ assert(_sssBanksData[i].count <= 4); // matches sizeof(_sssDataUnk6.unk0)
+ _sssBanksData[i].sssFilter = fp->readUint16();
+ _sssBanksData[i].firstSampleIndex = fp->readUint32();
+ debug(kDebug_RESOURCE, "SssBank #%d count %d codeOffset 0x%x", i, _sssBanksData[i].count, _sssBanksData[i].firstSampleIndex);
+ bytesRead += 8;
+ }
+ _sssSamplesData.allocate(_sssHdr.samplesDataCount);
+ for (int i = 0; i < _sssHdr.samplesDataCount; ++i) {
+ _sssSamplesData[i].pcm = fp->readUint16();
+ _sssSamplesData[i].framesCount = fp->readUint16();
+ _sssSamplesData[i].initVolume = fp->readByte();
+ _sssSamplesData[i].unk5 = fp->readByte();
+ _sssSamplesData[i].initPriority = fp->readByte();
+ _sssSamplesData[i].initPanning = fp->readByte();
+ _sssSamplesData[i].codeOffset1 = fp->readUint32();
+ _sssSamplesData[i].codeOffset2 = fp->readUint32();
+ _sssSamplesData[i].codeOffset3 = fp->readUint32();
+ _sssSamplesData[i].codeOffset4 = fp->readUint32();
+ debug(kDebug_RESOURCE, "SssSample #%d pcm %d frames %d", i, _sssSamplesData[i].pcm, _sssSamplesData[i].framesCount);
+ bytesRead += 24;
+ }
+ _sssCodeData = (uint8_t *)malloc(_sssHdr.codeSize);
+ fp->read(_sssCodeData, _sssHdr.codeSize);
+ bytesRead += _sssHdr.codeSize;
+ if (_sssHdr.version == 10 || _sssHdr.version == 12) {
+
+ // _sssPreloadData1
+ fp->seek(_sssHdr.preloadData1Count * 4, SEEK_CUR);
+ bytesRead += _sssHdr.preloadData1Count * 4;
+ // _sssPreloadData2
+ fp->seek(_sssHdr.preloadData2Count * 4, SEEK_CUR);
+ bytesRead += _sssHdr.preloadData2Count * 4;
+ // _sssPreloadData3
+ fp->seek(_sssHdr.preloadData3Count * 4, SEEK_CUR);
+ bytesRead += _sssHdr.preloadData3Count * 4;
+
+ _sssPreload1Table.allocate(_sssHdr.preloadData1Count);
+ const int ptrSize = (_sssHdr.version == 12) ? 2 : 1;
+ for (int i = 0; i < _sssHdr.preloadData1Count; ++i) {
+ const int count = (ptrSize == 2) ? fp->readUint16() : fp->readByte();
+ debug(kDebug_RESOURCE, "sssPreloadData1 #%d count %d", i, count);
+ _sssPreload1Table[i].count = count;
+ _sssPreload1Table[i].ptrSize = ptrSize;
+ const int tableSize = ptrSize * count;
+ _sssPreload1Table[i].ptr = (uint8_t *)malloc(tableSize);
+ fp->read(_sssPreload1Table[i].ptr, tableSize);
+ bytesRead += tableSize + ptrSize;
+ }
+ for (int i = 0; i < _sssHdr.preloadData2Count; ++i) {
+ const int count = fp->readByte();
+ fp->seek(count, SEEK_CUR);
+ debug(kDebug_RESOURCE, "sssPreloadData2 #%d count %d", i, count);
+ bytesRead += count + 1;
+ }
+ for (int i = 0; i < _sssHdr.preloadData3Count; ++i) {
+ const int count = fp->readByte();
+ fp->seek(count, SEEK_CUR);
+ debug(kDebug_RESOURCE, "sssPreloadData3 #%d count %d", i, count);
+ bytesRead += count + 1;
+ }
+ {
+ const int count = fp->readByte();
+ fp->seek(count, SEEK_CUR);
+ bytesRead += count + 1;
+ }
+ // _sssPreloadInfosData = data;
+ }
+ _sssPreloadInfosData.allocate(_sssHdr.preloadInfoCount);
+ for (int i = 0; i < _sssHdr.preloadInfoCount; ++i) {
+ _sssPreloadInfosData[i].count = fp->readUint32();
+ fp->readUint32();
+ debug(kDebug_RESOURCE, "_sssPreloadInfosData #%d/%d count %d offset 0x%x", i, _sssHdr.preloadInfoCount, _sssPreloadInfosData[i].count);
+ bytesRead += 8;
+ }
+ if (_sssHdr.version == 10 || _sssHdr.version == 12) {
+ static const int kSizeOfPreloadInfoData_V10 = 32;
+ for (int i = 0; i < _sssHdr.preloadInfoCount; ++i) {
+ const int count = _sssPreloadInfosData[i].count;
+ _sssPreloadInfosData[i].data = (SssPreloadInfoData *)calloc(count, sizeof(SssPreloadInfoData));
+ for (int j = 0; j < count; ++j) {
+ SssPreloadInfoData *preloadInfoData = &_sssPreloadInfosData[i].data[j];
+ preloadInfoData->pcmBlockOffset = fp->readUint16();
+ preloadInfoData->pcmBlockSize = fp->readUint16();
+ fp->seek(12, SEEK_CUR);
+ preloadInfoData->screenNum = fp->readByte();
+ const int preload3Index = fp->readByte(); // mst
+ assert(preload3Index < _sssHdr.preloadData3Count);
+ preloadInfoData->preload3Index = preload3Index;
+ const int preload1Index = fp->readByte(); // pcm
+ assert(preload1Index < _sssHdr.preloadData1Count);
+ preloadInfoData->preload1Index = preload1Index;
+ const int preload2Index = fp->readByte(); // lvl
+ assert(preload2Index < _sssHdr.preloadData2Count);
+ preloadInfoData->preload2Index = preload2Index;
+ fp->seek(8, SEEK_CUR);
+ preloadInfoData->unk1C = fp->readUint32();
+ bytesRead += kSizeOfPreloadInfoData_V10;
+ }
+ for (int j = 0; j < count; ++j) {
+ const int len = _sssPreloadInfosData[i].data[j].unk1C * 4;
+ bytesRead += skipBytesAlign(fp, len);
+ }
+ }
+ } else if (_sssHdr.version == 6) {
+ static const int kSizeOfPreloadInfoData_V6 = 68;
+ assert((unsigned int)_sssHdr.preloadInfoCount < kMaxScreens);
+ uint8_t buffer[kMaxScreens * kSizeOfPreloadInfoData_V6];
+
+ for (int i = 0; i < _sssHdr.preloadInfoCount; ++i) {
+ const int count = _sssPreloadInfosData[i].count;
+ _sssPreloadInfosData[i].data = (SssPreloadInfoData *)calloc(count, sizeof(SssPreloadInfoData));
+
+ fp->read(buffer, kSizeOfPreloadInfoData_V6 * count);
+ bytesRead += kSizeOfPreloadInfoData_V6 * count;
+
+ for (int j = 0; j < count; ++j) {
+ SssPreloadInfoData *preloadInfoData = &_sssPreloadInfosData[i].data[j];
+ preloadInfoData->screenNum = buffer[j * kSizeOfPreloadInfoData_V6];
+
+ // 0x2C is pcm preload list
+ preloadInfoData->preload1Data_V6.count = READ_LE_UINT32(buffer + j * kSizeOfPreloadInfoData_V6 + 0x2C);
+ preloadInfoData->preload1Data_V6.ptrSize = 2;
+ const int preload1DataLen = ((preloadInfoData->preload1Data_V6.count * 2) + 3) & ~3;
+ preloadInfoData->preload1Data_V6.ptr = (uint8_t *)malloc(preload1DataLen);
+ bytesRead += fp->read(preloadInfoData->preload1Data_V6.ptr, preload1DataLen);
+
+ static const int8_t offsets[7] = { 0x30, 0x34, 0x04, 0x08, 0x0C, 0x10, 0x14 };
+ static const int8_t lengths[7] = { 1, 1, 2, 2, 1, 1, 1 };
+ for (int k = 0; k < 7; ++k) {
+ const int len = READ_LE_UINT32(buffer + j * kSizeOfPreloadInfoData_V6 + offsets[k]) * lengths[k];
+ bytesRead += skipBytesAlign(fp, len);
+ }
+ }
+ }
+ }
+
+ _sssPcmTable.allocate(_sssHdr.pcmCount);
+ uint32_t sssPcmOffset = baseOffset;
+ for (int i = 0; i < _sssHdr.pcmCount; ++i) {
+ _sssPcmTable[i].ptr = 0; fp->skipUint32();
+ _sssPcmTable[i].offset = fp->readUint32();
+ _sssPcmTable[i].totalSize = fp->readUint32();
+ _sssPcmTable[i].strideSize = fp->readUint32();
+ _sssPcmTable[i].strideCount = fp->readUint16();
+ _sssPcmTable[i].flags = fp->readUint16();
+ debug(kDebug_RESOURCE, "sssPcmTable #%d/%d offset 0x%x size %d", i, _sssHdr.pcmCount, _sssPcmTable[i].offset, _sssPcmTable[i].totalSize);
+ if (_sssPcmTable[i].totalSize != 0) {
+ assert((_sssPcmTable[i].totalSize % _sssPcmTable[i].strideSize) == 0);
+ assert(_sssPcmTable[i].totalSize == _sssPcmTable[i].strideSize * _sssPcmTable[i].strideCount);
+ if (sssPcmOffset != 0) {
+ assert(fp != _datFile || _sssPcmTable[i].offset == 0x2800); // .dat
+ _sssPcmTable[i].offset += sssPcmOffset;
+ sssPcmOffset += _sssPcmTable[i].totalSize;
+ }
+ if (_isPsx) {
+ assert(_sssPcmTable[i].strideSize == 512);
+ _sssPcmTable[i].pcmSize = (_sssPcmTable[i].totalSize / 16) * 56 * sizeof(int16_t);
+ } else {
+ assert(_sssPcmTable[i].strideSize == 2276 || _sssPcmTable[i].strideSize == 4040);
+ _sssPcmTable[i].pcmSize = (_sssPcmTable[i].totalSize - 256 * sizeof(int16_t) * _sssPcmTable[i].strideCount) * sizeof(int16_t);
+ }
+ } else {
+ _sssPcmTable[i].pcmSize = 0;
+ }
+ bytesRead += 20;
+ }
+
+ // allocate structure but skip read as table is cleared and initialized in clearSoundObjects()
+ static const int kSizeOfSssFilter = 52;
+ fp->seek(_sssHdr.filtersDataCount * kSizeOfSssFilter, SEEK_CUR);
+ _sssFilters.allocate(_sssHdr.filtersDataCount);
+ bytesRead += _sssHdr.filtersDataCount * kSizeOfSssFilter;
+
+ _sssDataUnk6.allocate(_sssHdr.banksDataCount);
+ for (int i = 0; i < _sssHdr.banksDataCount; ++i) {
+ _sssDataUnk6[i].unk0[0] = fp->readUint32();
+ _sssDataUnk6[i].unk0[1] = fp->readUint32();
+ _sssDataUnk6[i].unk0[2] = fp->readUint32();
+ _sssDataUnk6[i].unk0[3] = fp->readUint32();
+ _sssDataUnk6[i].mask = fp->readUint32();
+ bytesRead += 20;
+ debug(kDebug_RESOURCE, "sssDataUnk6 #%d/%d unk10 0x%x", i, _sssHdr.banksDataCount);
+ }
+
+ const int lutSize = _sssHdr.banksDataCount * sizeof(uint32_t);
+ // allocate structures but skip read as tables are initialized in clearSoundObjects()
+ fp->seek(lutSize * 3 * 3, SEEK_CUR);
+ bytesRead += lutSize * 3 * 3;
+ for (int i = 0; i < 3; ++i) {
+ _sssGroup1[i] = (uint32_t *)malloc(lutSize);
+ _sssGroup2[i] = (uint32_t *)malloc(lutSize);
+ _sssGroup3[i] = (uint32_t *)malloc(lutSize);
+ }
+ // _sssPreloadedPcmTotalSize = 0;
+
+ if (kCheckSssBytecode) {
+ checkSssCode(_sssCodeData, _sssHdr.codeSize);
+ }
+ for (int i = 0; i < _sssHdr.banksDataCount; ++i) {
+ if (_sssBanksData[i].count != 0) {
+ // const int num = _sssBanksData[i].firstSampleIndex;
+ // _sssBanksData[i].codeOffset = &_sssSamplesData[num];
+ } else {
+ _sssBanksData[i].firstSampleIndex = kNone;
+ }
+ }
+ // fixup _sssSamplesData[i].codeOffset
+ debug(kDebug_RESOURCE, "bufferSize %d bytesRead %d", bufferSize, bytesRead);
+ if (bufferSize != bytesRead) {
+ error("Unexpected number of bytes read %d (%d)", bytesRead, bufferSize);
+ }
+ if (preloadPcm) {
+ fp->seek(_sssPcmTable[0].offset, SEEK_SET);
+ for (int i = 0; i < _sssHdr.pcmCount; ++i) {
+ if (_sssPcmTable[i].pcmSize != 0) {
+ loadSssPcm(fp, &_sssPcmTable[i]);
+ }
+ }
+ }
+ for (int i = 0; i < _sssHdr.banksDataCount; ++i) {
+ uint32_t mask = 1;
+ _sssDataUnk6[i].mask = 0;
+ const SssSample *codeOffset = &_sssSamplesData[_sssBanksData[i].firstSampleIndex];
+ uint32_t *ptr = _sssDataUnk6[i].unk0;
+ for (int j = 0; j < _sssBanksData[i].count; ++j) {
+ for (int k = 0; k < codeOffset->unk5; ++k) {
+ if (mask == 0) {
+ break;
+ }
+ *ptr |= mask;
+ mask <<= 1;
+ }
+ ++codeOffset;
+ _sssDataUnk6[i].mask |= *ptr;
+ ++ptr;
+ }
+ }
+ resetSssFilters();
+ // clearSoundObjects();
+ clearSssGroup3();
+}
+
+void Resource::unloadSssData() {
+ for (unsigned int i = 0; i < _sssPreload1Table.count; ++i) {
+ free(_sssPreload1Table[i].ptr);
+ _sssPreload1Table[i].ptr = 0;
+ }
+ for (unsigned int i = 0; i < _sssPreloadInfosData.count; ++i) {
+ if (_sssHdr.version == 6) {
+ for (unsigned int j = 0; j < _sssPreloadInfosData[i].count; ++j) {
+ SssPreloadInfoData *preloadInfoData = &_sssPreloadInfosData[i].data[j];
+ free(preloadInfoData->preload1Data_V6.ptr);
+ preloadInfoData->preload1Data_V6.ptr = 0;
+ }
+ }
+ free(_sssPreloadInfosData[i].data);
+ _sssPreloadInfosData[i].data = 0;
+ }
+ for (unsigned int i = 0; i < _sssPcmTable.count; ++i) {
+ free(_sssPcmTable[i].ptr);
+ _sssPcmTable[i].ptr = 0;
+ }
+ for (int i = 0; i < 3; ++i) {
+ free(_sssGroup1[i]);
+ _sssGroup1[i] = 0;
+ free(_sssGroup2[i]);
+ _sssGroup2[i] = 0;
+ free(_sssGroup3[i]);
+ _sssGroup3[i] = 0;
+ }
+ free(_sssCodeData);
+ _sssCodeData = 0;
+}
+
+void Resource::checkSssCode(const uint8_t *buf, int size) const {
+ static const uint8_t opcodesLength[] = {
+ 4, 0, 4, 0, 4, 12, 8, 0, 16, 4, 4, 4, 4, 8, 12, 0, 4, 8, 8, 4, 4, 4, 8, 4, 8, 4, 8, 16, 8, 4
+ };
+ int offset = 0;
+ while (offset < size) {
+ const int len = opcodesLength[buf[offset]];
+ if (len == 0) {
+ warning("Invalid .sss opcode %d", buf[offset]);
+ break;
+ }
+ offset += len;
+ }
+ assert(offset == size);
+}
+
+static int8_t sext8(uint8_t x, int bits) {
+ const int shift = 8 - bits;
+ return ((int8_t)(x << shift)) >> shift;
+}
+
+static int _pcmL1, _pcmL0;
+
+static void decodeSssSpuAdpcmUnit(const uint8_t *src, int16_t *dst) { // src: 16bytes, dst: 112bytes
+ static const int16_t K0_1024[] = { 0, 960, 1840, 1568, 1952 };
+ static const int16_t K1_1024[] = { 0, 0, -832, -880, -960 };
+ const uint8_t param = *src++;
+ const int shift = 12 - (param & 15);
+ assert(shift >= 0);
+ const int filter = param >> 4;
+ assert(filter < 5);
+ const int flag = *src++;
+ assert(flag < 7);
+ for (int i = 0; i < 14; ++i) {
+ const uint8_t b = *src++;
+ const int t1 = sext8(b & 15, 4);
+ const int s1 = (t1 << shift) + ((_pcmL0 * K0_1024[filter] + _pcmL1 * K1_1024[filter] + 512) >> 10);
+ _pcmL1 = _pcmL0;
+ _pcmL0 = s1;
+ dst[0] = (_pcmL1 + _pcmL0) >> 1;
+ dst[1] = CLIP(_pcmL0, -32768, 32767);
+ dst += 2;
+ const int t2 = sext8(b >> 4, 4);
+ const int s2 = (t2 << shift) + ((_pcmL0 * K0_1024[filter] + _pcmL1 * K1_1024[filter] + 512) >> 10);
+ _pcmL1 = _pcmL0;
+ _pcmL0 = s2;
+ dst[0] = (_pcmL1 + _pcmL0) >> 1;
+ dst[1] = CLIP(_pcmL0, -32768, 32767);
+ dst += 2;
+ }
+}
+
+void Resource::loadSssPcm(File *fp, SssPcm *pcm) {
+ assert(!pcm->ptr);
+ const uint32_t decompressedSize = pcm->pcmSize;
+ debug(kDebug_SOUND, "Loading PCM %p decompressedSize %d", pcm, decompressedSize);
+ int16_t *p = (int16_t *)malloc(decompressedSize);
+ if (!p) {
+ warning("Failed to allocate %d bytes for PCM", decompressedSize);
+ return;
+ }
+ pcm->ptr = p;
+ if (_isPsx) {
+ assert(pcm->strideSize == 512);
+ uint8_t strideBuffer[512];
+ for (int i = 0; i < pcm->strideCount; ++i) {
+ fp->read(strideBuffer, sizeof(strideBuffer));
+ _pcmL1 = _pcmL0 = 0;
+ for (unsigned int j = 0; j < 512; j += 16) {
+ decodeSssSpuAdpcmUnit(strideBuffer + j, p);
+ p += 56;
+ }
+ }
+ } else {
+ if (fp != _datFile) {
+ fp->seek(pcm->offset, SEEK_SET);
+ }
+ const uint32_t strideSize = pcm->strideSize;
+ assert(strideSize == 2276 || strideSize == 4040);
+ uint8_t strideBuffer[4040]; // maximum stride size
+ for (int i = 0; i < pcm->strideCount; ++i) {
+ fp->read(strideBuffer, strideSize);
+ for (unsigned int j = 256 * sizeof(int16_t); j < strideSize; ++j) {
+ *p++ = READ_LE_UINT16(strideBuffer + strideBuffer[j] * sizeof(int16_t));
+ }
+ }
+ }
+ assert((p - pcm->ptr) * sizeof(int16_t) == decompressedSize);
+}
+
+void Resource::clearSssGroup3() {
+ if (_sssHdr.banksDataCount != 0) {
+ for (int i = 0; i < 3; ++i) {
+ memset(_sssGroup3[i], 0, _sssHdr.banksDataCount * sizeof(uint32_t));
+ }
+ }
+}
+
+void Resource::resetSssFilters() {
+ for (int i = 0; i < _sssHdr.filtersDataCount; ++i) {
+ memset(&_sssFilters[i], 0, sizeof(SssFilter));
+ const int volume = _sssDefaultsData[i].defaultVolume;
+ _sssFilters[i].volumeCurrent = volume << 16;
+ _sssFilters[i].volume = volume;
+ const int panning = _sssDefaultsData[i].defaultPanning;
+ _sssFilters[i].panningCurrent = panning << 16;
+ _sssFilters[i].panning = panning;
+ const int priority = _sssDefaultsData[i].defaultPriority;
+ _sssFilters[i].priorityCurrent = priority;
+ _sssFilters[i].priority = priority;
+ }
+}
+
+void Resource::preloadSssPcmList(const SssPreloadInfoData *preloadInfoData) {
+ File *fp = _sssFile;
+ if (_isPsx) {
+ const uint16_t offset = preloadInfoData->pcmBlockOffset;
+ if (offset == 0xFFFF) {
+ return;
+ }
+ _lvlFile->seek(offset * 2048, SEEK_SET);
+ fp = _lvlFile;
+ } else if (kPreloadSssPcm) {
+ return;
+ }
+ const SssPreloadList *preloadList = (_sssHdr.version == 6) ? &preloadInfoData->preload1Data_V6 : &_sssPreload1Table[preloadInfoData->preload1Index];
+ for (int i = 0; i < preloadList->count; ++i) {
+ const int num = (preloadList->ptrSize == 2) ? READ_LE_UINT16(preloadList->ptr + i * 2) : preloadList->ptr[i];
+ if (_sssPcmTable[num].pcmSize != 0) {
+ if (!_sssPcmTable[num].ptr) {
+ loadSssPcm(fp, &_sssPcmTable[num]);
+ } else if (_isPsx) {
+ fp->seek(_sssPcmTable[num].strideCount * 512, SEEK_CUR);
+ }
+ }
+ }
+}
+
+void Resource::loadMstData(File *fp) {
+ assert(fp == _mstFile);
+
+ if (_mstHdr.dataSize != 0) {
+ unloadMstData();
+ _mstHdr.dataSize = 0;
+ }
+
+ _mstHdr.version = fp->readUint32();
+ if (_mstHdr.version != 160) {
+ warning("Unhandled .mst version %d", _mstHdr.version);
+ closeDat(_fs, _mstFile);
+ return;
+ }
+
+ _mstHdr.dataSize = fp->readUint32();
+ _mstHdr.walkBoxDataCount = fp->readUint32();
+ _mstHdr.walkCodeDataCount = fp->readUint32();
+ _mstHdr.movingBoundsIndexDataCount = fp->readUint32();
+ _mstHdr.levelCheckpointCodeDataCount = fp->readUint32();
+ _mstHdr.screenAreaDataCount = fp->readUint32();
+ _mstHdr.screenAreaIndexDataCount = fp->readUint32();
+ _mstHdr.behaviorIndexDataCount = fp->readUint32();
+ _mstHdr.monsterActionIndexDataCount = fp->readUint32();
+ _mstHdr.walkPathDataCount = fp->readUint32();
+ _mstHdr.infoMonster2Count = fp->readUint32();
+ _mstHdr.behaviorDataCount = fp->readUint32();
+ _mstHdr.attackBoxDataCount = fp->readUint32();
+ _mstHdr.monsterActionDataCount = fp->readUint32();
+ _mstHdr.infoMonster1Count = fp->readUint32();
+ _mstHdr.movingBoundsDataCount = fp->readUint32();
+ _mstHdr.shootDataCount = fp->readUint32();
+ _mstHdr.shootIndexDataCount = fp->readUint32();
+ _mstHdr.actionDirectionDataCount = fp->readUint32();
+ _mstHdr.op223DataCount = fp->readUint32();
+ _mstHdr.op226DataCount = fp->readUint32();
+ _mstHdr.op227DataCount = fp->readUint32();
+ _mstHdr.op234DataCount = fp->readUint32();
+ _mstHdr.op2DataCount = fp->readUint32();
+ _mstHdr.op197DataCount = fp->readUint32();
+ _mstHdr.op211DataCount = fp->readUint32();
+ _mstHdr.op240DataCount = fp->readUint32();
+ _mstHdr.unk0x70 = fp->readUint32();
+ _mstHdr.unk0x74 = fp->readUint32();
+ _mstHdr.op204DataCount = fp->readUint32();
+ _mstHdr.codeSize = fp->readUint32();
+ _mstHdr.screensCount = fp->readUint32();
+ debug(kDebug_RESOURCE, "_mstHdr.version %d _mstHdr.codeSize %d", _mstHdr.version, _mstHdr.codeSize);
+
+ fp->seek(2048, SEEK_SET); // align to the next sector
+
+ int bytesRead = 0;
+
+ _mstPointOffsets.allocate(_mstHdr.screensCount);
+ for (int i = 0; i < _mstHdr.screensCount; ++i) {
+ _mstPointOffsets[i].xOffset = fp->readUint32();
+ _mstPointOffsets[i].yOffset = fp->readUint32();
+ bytesRead += 8;
+ }
+
+ _mstWalkBoxData.allocate(_mstHdr.walkBoxDataCount);
+ for (int i = 0; i < _mstHdr.walkBoxDataCount; ++i) {
+ _mstWalkBoxData[i].right = fp->readUint32();
+ _mstWalkBoxData[i].left = fp->readUint32();
+ _mstWalkBoxData[i].bottom = fp->readUint32();
+ _mstWalkBoxData[i].top = fp->readUint32();
+ _mstWalkBoxData[i].flags[0] = fp->readByte();
+ _mstWalkBoxData[i].flags[1] = fp->readByte();
+ _mstWalkBoxData[i].flags[2] = fp->readByte();
+ _mstWalkBoxData[i].flags[3] = fp->readByte();
+ bytesRead += 20;
+ }
+
+ _mstWalkCodeData.allocate(_mstHdr.walkCodeDataCount);
+ for (int i = 0; i < _mstHdr.walkCodeDataCount; ++i) {
+ fp->skipUint32();
+ _mstWalkCodeData[i].codeDataCount = fp->readUint32();
+ _mstWalkCodeData[i].codeData = (uint32_t *)malloc(_mstWalkCodeData[i].codeDataCount * sizeof(uint32_t));
+ fp->skipUint32();
+ _mstWalkCodeData[i].indexDataCount = fp->readUint32();
+ if (_mstWalkCodeData[i].indexDataCount != 0) {
+ _mstWalkCodeData[i].indexData = (uint8_t *)malloc(_mstWalkCodeData[i].indexDataCount);
+ } else {
+ _mstWalkCodeData[i].indexData = 0;
+ }
+ bytesRead += 16;
+ }
+ for (int i = 0; i < _mstHdr.walkCodeDataCount; ++i) {
+ for (uint32_t j = 0; j < _mstWalkCodeData[i].codeDataCount; ++j) {
+ _mstWalkCodeData[i].codeData[j] = fp->readUint32();
+ bytesRead += 4;
+ }
+ if (_mstWalkCodeData[i].indexDataCount != 0) {
+ bytesRead += readBytesAlign(fp, _mstWalkCodeData[i].indexData, _mstWalkCodeData[i].indexDataCount);
+ }
+ }
+
+ _mstMovingBoundsIndexData.allocate(_mstHdr.movingBoundsIndexDataCount);
+ for (int i = 0; i < _mstHdr.movingBoundsIndexDataCount; ++i) {
+ _mstMovingBoundsIndexData[i].indexUnk49 = fp->readUint32();
+ _mstMovingBoundsIndexData[i].unk4 = fp->readUint32();
+ _mstMovingBoundsIndexData[i].unk8 = fp->readUint32();
+ bytesRead += 12;
+ }
+
+ _mstTickDelay = fp->readUint32();
+ _mstTickCodeData = fp->readUint32();
+ bytesRead += 8;
+
+ _mstLevelCheckpointCodeData.allocate(_mstHdr.levelCheckpointCodeDataCount);
+ for (int i = 0; i < _mstHdr.levelCheckpointCodeDataCount; ++i) {
+ _mstLevelCheckpointCodeData[i] = fp->readUint32();
+ bytesRead += 4;
+ }
+
+ _mstScreenAreaData.allocate(_mstHdr.screenAreaDataCount);
+ for (int i = 0; i < _mstHdr.screenAreaDataCount; ++i) {
+ MstScreenArea *msac = &_mstScreenAreaData[i];
+ msac->x1 = fp->readUint32();
+ msac->x2 = fp->readUint32();
+ msac->y1 = fp->readUint32();
+ msac->y2 = fp->readUint32();
+ msac->nextByPos = fp->readUint32();
+ msac->prev = fp->readUint32();
+ msac->nextByValue = fp->readUint32();
+ msac->unk0x1C = fp->readByte();
+ msac->unk0x1D = fp->readByte();
+ msac->unk0x1E = fp->readUint16();
+ msac->codeData = fp->readUint32();
+ bytesRead += 36;
+ }
+
+ _mstScreenAreaByValueIndexData.allocate(_mstHdr.screenAreaIndexDataCount);
+ for (int i = 0; i < _mstHdr.screenAreaIndexDataCount; ++i) {
+ _mstScreenAreaByValueIndexData[i] = fp->readUint32();
+ bytesRead += 4;
+ }
+
+ _mstScreenAreaByPosIndexData.allocate(_mstHdr.screensCount);
+ for (int i = 0; i < _mstHdr.screensCount; ++i) {
+ _mstScreenAreaByPosIndexData[i] = fp->readUint32();
+ bytesRead += 4;
+ }
+
+ _mstUnk41.allocate(_mstHdr.screensCount);
+ for (int i = 0; i < _mstHdr.screensCount; ++i) {
+ _mstUnk41[i] = fp->readUint32();
+ bytesRead += 4;
+ }
+
+ _mstBehaviorIndexData.allocate(_mstHdr.behaviorIndexDataCount);
+ for (int i = 0; i < _mstHdr.behaviorIndexDataCount; ++i) {
+ fp->skipUint32();
+ _mstBehaviorIndexData[i].count1 = fp->readUint32();
+ _mstBehaviorIndexData[i].behavior = (uint32_t *)malloc(_mstBehaviorIndexData[i].count1 * sizeof(uint32_t));
+ fp->skipUint32();
+ _mstBehaviorIndexData[i].dataCount = fp->readUint32();
+ if (_mstBehaviorIndexData[i].dataCount != 0) {
+ _mstBehaviorIndexData[i].data = (uint8_t *)malloc(_mstBehaviorIndexData[i].dataCount);
+ } else {
+ _mstBehaviorIndexData[i].data = 0;
+ }
+ bytesRead += 16;
+ }
+ for (int i = 0; i < _mstHdr.behaviorIndexDataCount; ++i) {
+ for (uint32_t j = 0; j < _mstBehaviorIndexData[i].count1; ++j) {
+ _mstBehaviorIndexData[i].behavior[j] = fp->readUint32();
+ bytesRead += 4;
+ }
+ if (_mstBehaviorIndexData[i].dataCount != 0) {
+ bytesRead += readBytesAlign(fp, _mstBehaviorIndexData[i].data, _mstBehaviorIndexData[i].dataCount);
+ }
+ }
+
+ _mstMonsterActionIndexData.allocate(_mstHdr.monsterActionIndexDataCount);
+ for (int i = 0; i < _mstHdr.monsterActionIndexDataCount; ++i) {
+ fp->skipUint32();
+ _mstMonsterActionIndexData[i].count1 = fp->readUint32();
+ _mstMonsterActionIndexData[i].indexUnk48 = (uint32_t *)malloc(_mstMonsterActionIndexData[i].count1 * sizeof(uint32_t));
+ fp->skipUint32();
+ _mstMonsterActionIndexData[i].dataCount = fp->readUint32();
+ if (_mstMonsterActionIndexData[i].dataCount != 0) {
+ _mstMonsterActionIndexData[i].data = (uint8_t *)malloc(_mstMonsterActionIndexData[i].dataCount);
+ } else {
+ _mstMonsterActionIndexData[i].data = 0;
+ }
+ bytesRead += 16;
+ }
+ for (int i = 0; i < _mstHdr.monsterActionIndexDataCount; ++i) {
+ for (uint32_t j = 0; j < _mstMonsterActionIndexData[i].count1; ++j) {
+ _mstMonsterActionIndexData[i].indexUnk48[j] = fp->readUint32();
+ bytesRead += 4;
+ }
+ if (_mstMonsterActionIndexData[i].dataCount != 0) {
+ bytesRead += readBytesAlign(fp, _mstMonsterActionIndexData[i].data, _mstMonsterActionIndexData[i].dataCount);
+ }
+ }
+
+ _mstWalkPathData.allocate(_mstHdr.walkPathDataCount);
+ for (int i = 0; i < _mstHdr.walkPathDataCount; ++i) {
+ fp->skipUint32();
+ fp->skipUint32();
+ _mstWalkPathData[i].mask = fp->readUint32();
+ _mstWalkPathData[i].count = fp->readUint32();
+ bytesRead += 16;
+ }
+ for (int i = 0; i < _mstHdr.walkPathDataCount; ++i) {
+ const int count = _mstWalkPathData[i].count;
+ _mstWalkPathData[i].data = (MstWalkNode *)malloc(sizeof(MstWalkNode) * count);
+ for (int j = 0; j < count; ++j) {
+ uint8_t data[104];
+ fp->read(data, sizeof(data));
+ bytesRead += 104;
+ _mstWalkPathData[i].data[j].x1 = READ_LE_UINT32(data);
+ _mstWalkPathData[i].data[j].x2 = READ_LE_UINT32(data + 4);
+ _mstWalkPathData[i].data[j].y1 = READ_LE_UINT32(data + 8);
+ _mstWalkPathData[i].data[j].y2 = READ_LE_UINT32(data + 12);
+ _mstWalkPathData[i].data[j].walkBox = READ_LE_UINT32(data + 16); // sizeof == 20
+ _mstWalkPathData[i].data[j].walkCodeStage1 = READ_LE_UINT32(data + 20); // sizeof == 16
+ _mstWalkPathData[i].data[j].walkCodeStage2 = READ_LE_UINT32(data + 24); // sizeof == 16
+ _mstWalkPathData[i].data[j].movingBoundsIndex1 = READ_LE_UINT32(data + 28); // sizeof == 12
+ _mstWalkPathData[i].data[j].movingBoundsIndex2 = READ_LE_UINT32(data + 32); // sizeof == 12
+ _mstWalkPathData[i].data[j].walkCodeReset[0] = READ_LE_UINT32(data + 36); // sizeof == 16
+ _mstWalkPathData[i].data[j].walkCodeReset[1] = READ_LE_UINT32(data + 40); // sizeof == 16
+ for (int k = 0; k < 4; ++k) {
+ _mstWalkPathData[i].data[j].coords[k][0] = READ_LE_UINT32(data + 0x2C + k * 8);
+ _mstWalkPathData[i].data[j].coords[k][1] = READ_LE_UINT32(data + 0x30 + k * 8);
+ }
+ _mstWalkPathData[i].data[j].neighborWalkNode[0] = READ_LE_UINT32(data + 76); // sizeof == 104
+ _mstWalkPathData[i].data[j].neighborWalkNode[1] = READ_LE_UINT32(data + 80); // sizeof == 104
+ _mstWalkPathData[i].data[j].neighborWalkNode[2] = READ_LE_UINT32(data + 84); // sizeof == 104
+ _mstWalkPathData[i].data[j].neighborWalkNode[3] = READ_LE_UINT32(data + 88); // sizeof == 104
+ _mstWalkPathData[i].data[j].nextWalkNode = READ_LE_UINT32(data + 92); // sizeof == 104
+ if (count != 0) {
+ _mstWalkPathData[i].data[j].unk60[0] = (uint8_t *)malloc(count);
+ _mstWalkPathData[i].data[j].unk60[1] = (uint8_t *)malloc(count);
+ } else {
+ _mstWalkPathData[i].data[j].unk60[0] = 0;
+ _mstWalkPathData[i].data[j].unk60[1] = 0;
+ }
+ }
+ _mstWalkPathData[i].walkNodeData = (uint32_t *)malloc(_mstHdr.screensCount * sizeof(uint32_t));
+ for (int j = 0; j < _mstHdr.screensCount; ++j) {
+ _mstWalkPathData[i].walkNodeData[j] = fp->readUint32();
+ bytesRead += 4;
+ }
+ for (int j = 0; j < count; ++j) {
+ for (int k = 0; k < 2; ++k) {
+ bytesRead += readBytesAlign(fp, _mstWalkPathData[i].data[j].unk60[k], count);
+ }
+ }
+ }
+
+ _mstInfoMonster2Data.allocate(_mstHdr.infoMonster2Count);
+ for (int i = 0; i < _mstHdr.infoMonster2Count; ++i) {
+ _mstInfoMonster2Data[i].type = fp->readByte();
+ _mstInfoMonster2Data[i].shootMask = fp->readByte();
+ _mstInfoMonster2Data[i].anim = fp->readUint16();
+ _mstInfoMonster2Data[i].codeData = fp->readUint32();
+ _mstInfoMonster2Data[i].codeData2 = fp->readUint32();
+ bytesRead += 12;
+ }
+
+ _mstBehaviorData.allocate(_mstHdr.behaviorDataCount);
+ for (int i = 0; i < _mstHdr.behaviorDataCount; ++i) {
+ fp->skipUint32();
+ _mstBehaviorData[i].count = fp->readUint32();
+ bytesRead += 8;
+ }
+ for (int i = 0; i < _mstHdr.behaviorDataCount; ++i) {
+ _mstBehaviorData[i].data = (MstBehaviorState *)malloc(_mstBehaviorData[i].count * sizeof(MstBehaviorState));
+ for (uint32_t j = 0; j < _mstBehaviorData[i].count; ++j) {
+ uint8_t data[44];
+ fp->read(data, sizeof(data));
+ bytesRead += 44;
+ _mstBehaviorData[i].data[j].indexMonsterInfo = READ_LE_UINT32(data);
+ _mstBehaviorData[i].data[j].anim = READ_LE_UINT16(data + 0x04);
+ _mstBehaviorData[i].data[j].unk6 = READ_LE_UINT16(data + 0x06);
+ _mstBehaviorData[i].data[j].unk8 = READ_LE_UINT32(data + 0x08);
+ _mstBehaviorData[i].data[j].energy = READ_LE_UINT32(data + 0x0C);
+ _mstBehaviorData[i].data[j].unk10 = READ_LE_UINT32(data + 0x10);
+ _mstBehaviorData[i].data[j].count = READ_LE_UINT32(data + 0x14);
+ _mstBehaviorData[i].data[j].unk18 = READ_LE_UINT32(data + 0x18);
+ _mstBehaviorData[i].data[j].indexUnk51 = READ_LE_UINT32(data + 0x1C);
+ _mstBehaviorData[i].data[j].walkPath = READ_LE_UINT32(data + 0x20);
+ _mstBehaviorData[i].data[j].attackBox = READ_LE_UINT32(data + 0x24);
+ _mstBehaviorData[i].data[j].codeData = READ_LE_UINT32(data + 0x28);
+ }
+ }
+
+ _mstAttackBoxData.allocate(_mstHdr.attackBoxDataCount);
+ for (int i = 0; i < _mstHdr.attackBoxDataCount; ++i) {
+ fp->skipUint32();
+ _mstAttackBoxData[i].count = fp->readUint32();
+ bytesRead += 8;
+ }
+ for (int i = 0; i < _mstHdr.attackBoxDataCount; ++i) {
+ _mstAttackBoxData[i].data = (uint8_t *)malloc(_mstAttackBoxData[i].count * 20);
+ fp->read(_mstAttackBoxData[i].data, _mstAttackBoxData[i].count * 20);
+ bytesRead += _mstAttackBoxData[i].count * 20;
+ }
+
+ _mstMonsterActionData.allocate(_mstHdr.monsterActionDataCount);
+ for (int i = 0; i < _mstHdr.monsterActionDataCount; ++i) {
+ MstMonsterAction *m = &_mstMonsterActionData[i];
+ m->unk0 = fp->readUint16();
+ m->unk2 = fp->readUint16();
+ m->unk4 = fp->readByte();
+ m->direction = fp->readByte();
+ m->unk6 = fp->readByte();
+ m->unk7 = fp->readByte();
+ m->codeData = fp->readUint32();
+ m->area = 0; fp->skipUint32();
+ m->areaCount = fp->readUint32();
+ fp->seek(16, SEEK_CUR);
+ m->count[0] = fp->readUint32();
+ m->count[1] = fp->readUint32();
+ bytesRead += 44;
+ }
+ for (int i = 0; i < _mstHdr.monsterActionDataCount; ++i) {
+ MstMonsterAction *m = &_mstMonsterActionData[i];
+ for (int j = 0; j < 2; ++j) {
+ const int count = m->count[j];
+ if (count != 0) {
+ m->data1[j] = (uint32_t *)malloc(count * sizeof(uint32_t));
+ for (int k = 0; k < count; ++k) {
+ m->data1[j][k] = fp->readUint32();
+ }
+ bytesRead += count * 4;
+ m->data2[j] = (uint32_t *)malloc(count * sizeof(uint32_t));
+ for (int k = 0; k < count; ++k) {
+ m->data2[j][k] = fp->readUint32();
+ }
+ bytesRead += count * 4;
+ } else {
+ m->data1[j] = 0;
+ m->data2[j] = 0;
+ }
+ }
+ MstMonsterArea *m12 = (MstMonsterArea *)malloc(m->areaCount * sizeof(MstMonsterArea));
+ for (int j = 0; j < m->areaCount; ++j) {
+ m12[j].unk0 = fp->readByte();
+ fp->skipByte();
+ fp->skipByte();
+ fp->skipByte();
+ m12[j].data = 0; fp->skipUint32();
+ m12[j].count = fp->readUint32();
+ bytesRead += 12;
+ }
+ for (int j = 0; j < m->areaCount; ++j) {
+ m12[j].data = (MstMonsterAreaAction *)malloc(m12[j].count * sizeof(MstMonsterAreaAction));
+ for (uint32_t k = 0; k < m12[j].count; ++k) {
+ uint8_t data[28];
+ fp->read(data, sizeof(data));
+ m12[j].data[k].unk0 = READ_LE_UINT32(data);
+ m12[j].data[k].indexUnk51 = READ_LE_UINT32(data + 0x4);
+ m12[j].data[k].xPos = READ_LE_UINT32(data + 0x8);
+ m12[j].data[k].yPos = READ_LE_UINT32(data + 0xC);
+ m12[j].data[k].codeData = READ_LE_UINT32(data + 0x10);
+ m12[j].data[k].codeData2 = READ_LE_UINT32(data + 0x14);
+ m12[j].data[k].unk18 = data[0x18];
+ m12[j].data[k].direction = data[0x19];
+ m12[j].data[k].screenNum = data[0x1A];
+ m12[j].data[k].monster1Index = data[0x1B];
+ bytesRead += 28;
+ }
+ }
+ m->area = m12;
+ }
+
+ const int mapDataSize = _mstHdr.infoMonster1Count * kMonsterInfoDataSize;
+ _mstMonsterInfos = (uint8_t *)malloc(mapDataSize);
+ fp->read(_mstMonsterInfos, mapDataSize);
+ bytesRead += mapDataSize;
+
+ _mstMovingBoundsData.allocate(_mstHdr.movingBoundsDataCount);
+ for (int i = 0; i < _mstHdr.movingBoundsDataCount; ++i) {
+ _mstMovingBoundsData[i].indexMonsterInfo = fp->readUint32();
+ fp->skipUint32();
+ _mstMovingBoundsData[i].count1 = fp->readUint32();
+ fp->skipUint32();
+ _mstMovingBoundsData[i].indexDataCount = fp->readUint32();
+ _mstMovingBoundsData[i].unk14 = fp->readByte();
+ _mstMovingBoundsData[i].unk15 = fp->readByte();
+ _mstMovingBoundsData[i].unk16 = fp->readByte();
+ _mstMovingBoundsData[i].unk17 = fp->readByte();
+ bytesRead += 24;
+ }
+ for (int i = 0; i < _mstHdr.movingBoundsDataCount; ++i) {
+ _mstMovingBoundsData[i].data1 = (MstMovingBoundsUnk1 *)malloc(_mstMovingBoundsData[i].count1 * sizeof(MstMovingBoundsUnk1));
+ const int start = _mstMovingBoundsData[i].indexMonsterInfo;
+ assert(start < _mstHdr.infoMonster1Count);
+ for (uint32_t j = 0; j < _mstMovingBoundsData[i].count1; ++j) {
+ fp->skipUint32();
+ _mstMovingBoundsData[i].data1[j].unk4 = fp->readUint32();
+ _mstMovingBoundsData[i].data1[j].unk8 = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unk9 = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unkA = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unkB = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unkC = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unkD = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unkE = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unkF = fp->readByte();
+ const uint32_t num = _mstMovingBoundsData[i].data1[j].unk4;
+ assert(num < 32);
+ _mstMovingBoundsData[i].data1[j].offsetMonsterInfo = start * kMonsterInfoDataSize + num * 28;
+ bytesRead += 16;
+ }
+ if (_mstMovingBoundsData[i].indexDataCount != 0) {
+ _mstMovingBoundsData[i].indexData = (uint8_t *)malloc(_mstMovingBoundsData[i].indexDataCount);
+ fp->read(_mstMovingBoundsData[i].indexData, _mstMovingBoundsData[i].indexDataCount);
+ bytesRead += _mstMovingBoundsData[i].indexDataCount;
+ } else {
+ _mstMovingBoundsData[i].indexData = 0;
+ }
+ }
+
+ _mstShootData.allocate(_mstHdr.shootDataCount);
+ for (int i = 0; i < _mstHdr.shootDataCount; ++i) {
+ _mstShootData[i].data = 0; fp->skipUint32();
+ _mstShootData[i].count = fp->readUint32();
+ bytesRead += 8;
+ }
+ for (int i = 0; i < _mstHdr.shootDataCount; ++i) {
+ _mstShootData[i].data = (MstShootAction *)malloc(_mstShootData[i].count * sizeof(MstShootAction));
+ for (uint32_t j = 0; j < _mstShootData[i].count; ++j) {
+ _mstShootData[i].data[j].codeData = fp->readUint32();
+ _mstShootData[i].data[j].unk4 = fp->readUint32();
+ _mstShootData[i].data[j].unk8 = fp->readUint32();
+ _mstShootData[i].data[j].xPos = fp->readUint32();
+ _mstShootData[i].data[j].yPos = fp->readUint32();
+ _mstShootData[i].data[j].width = fp->readUint32();
+ _mstShootData[i].data[j].height = fp->readUint32();
+ _mstShootData[i].data[j].hSize = fp->readUint32();
+ _mstShootData[i].data[j].vSize = fp->readUint32();
+ _mstShootData[i].data[j].unk24 = fp->readUint32();
+ bytesRead += 40;
+ }
+ }
+
+ _mstShootIndexData.allocate(_mstHdr.shootIndexDataCount);
+ for (int i = 0; i < _mstHdr.shootIndexDataCount; ++i) {
+ _mstShootIndexData[i].indexUnk50 = fp->readUint32();
+ assert(_mstShootIndexData[i].indexUnk50 < (uint32_t)_mstHdr.shootDataCount);
+ _mstShootIndexData[i].indexUnk50Unk1 = 0; fp->skipUint32();
+ _mstShootIndexData[i].count = fp->readUint32();
+ bytesRead += 12;
+ }
+ for (int i = 0; i < _mstHdr.shootIndexDataCount; ++i) {
+ _mstShootIndexData[i].indexUnk50Unk1 = (uint32_t *)malloc(_mstShootIndexData[i].count * 9 * sizeof(uint32_t));
+ for (uint32_t j = 0; j < _mstShootIndexData[i].count * 9; ++j) {
+ _mstShootIndexData[i].indexUnk50Unk1[j] = fp->readUint32();
+ assert(_mstShootIndexData[i].indexUnk50Unk1[j] < _mstShootData[_mstShootIndexData[i].indexUnk50].count);
+ bytesRead += 4;
+ }
+ }
+ _mstActionDirectionData.allocate(_mstHdr.actionDirectionDataCount);
+ for (int i = 0; i < _mstHdr.actionDirectionDataCount; ++i) {
+ _mstActionDirectionData[i].unk0 = fp->readByte();
+ _mstActionDirectionData[i].unk1 = fp->readByte();
+ _mstActionDirectionData[i].unk2 = fp->readByte();
+ _mstActionDirectionData[i].unk3 = fp->readByte();
+ bytesRead += 4;
+ }
+
+ _mstOp223Data.allocate(_mstHdr.op223DataCount);
+ for (int i = 0; i < _mstHdr.op223DataCount; ++i) {
+ _mstOp223Data[i].indexVar1 = fp->readUint16();
+ _mstOp223Data[i].indexVar2 = fp->readUint16();
+ _mstOp223Data[i].indexVar3 = fp->readUint16();
+ _mstOp223Data[i].indexVar4 = fp->readUint16();
+ _mstOp223Data[i].unk8 = fp->readByte();
+ _mstOp223Data[i].unk9 = fp->readByte();
+ _mstOp223Data[i].indexVar5 = fp->readByte();
+ _mstOp223Data[i].unkB = fp->readByte();
+ _mstOp223Data[i].unkC = fp->readUint16();
+ _mstOp223Data[i].unkE = fp->readUint16();
+ _mstOp223Data[i].maskVars = fp->readUint32();
+ bytesRead += 20;
+ }
+
+ _mstOp226Data.allocate(_mstHdr.op226DataCount);
+ for (int i = 0; i < _mstHdr.op226DataCount; ++i) {
+ _mstOp226Data[i].unk0 = fp->readByte();
+ _mstOp226Data[i].unk1 = fp->readByte();
+ _mstOp226Data[i].unk2 = fp->readByte();
+ _mstOp226Data[i].unk3 = fp->readByte();
+ _mstOp226Data[i].unk4 = fp->readByte();
+ _mstOp226Data[i].unk5 = fp->readByte();
+ _mstOp226Data[i].unk6 = fp->readByte();
+ _mstOp226Data[i].unk7 = fp->readByte();
+ bytesRead += 8;
+ }
+
+ _mstOp227Data.allocate(_mstHdr.op227DataCount);
+ for (int i = 0; i < _mstHdr.op227DataCount; ++i) {
+ _mstOp227Data[i].indexVar1 = fp->readUint16();
+ _mstOp227Data[i].indexVar2 = fp->readUint16();
+ _mstOp227Data[i].compare = fp->readByte();
+ _mstOp227Data[i].maskVars = fp->readByte();
+ _mstOp227Data[i].codeData = fp->readUint16();
+ bytesRead += 8;
+ }
+
+ _mstOp234Data.allocate(_mstHdr.op234DataCount);
+ for (int i = 0; i < _mstHdr.op234DataCount; ++i) {
+ _mstOp234Data[i].indexVar1 = fp->readUint16();
+ _mstOp234Data[i].indexVar2 = fp->readUint16();
+ _mstOp234Data[i].compare = fp->readByte();
+ _mstOp234Data[i].maskVars = fp->readByte();
+ _mstOp234Data[i].codeData = fp->readUint16();
+ bytesRead += 8;
+ }
+
+ _mstOp2Data.allocate(_mstHdr.op2DataCount);
+ for (int i = 0; i < _mstHdr.op2DataCount; ++i) {
+ _mstOp2Data[i].indexVar1 = fp->readUint32();
+ _mstOp2Data[i].indexVar2 = fp->readUint32();
+ _mstOp2Data[i].maskVars = fp->readByte();
+ _mstOp2Data[i].unk9 = fp->readByte();
+ _mstOp2Data[i].unkA = fp->readByte();
+ _mstOp2Data[i].unkB = fp->readByte();
+ bytesRead += 12;
+ }
+
+ _mstOp197Data.allocate(_mstHdr.op197DataCount);
+ for (int i = 0; i < _mstHdr.op197DataCount; ++i) {
+ _mstOp197Data[i].unk0 = fp->readUint16();
+ _mstOp197Data[i].unk2 = fp->readUint16();
+ _mstOp197Data[i].unk4 = fp->readUint16();
+ _mstOp197Data[i].unk6 = fp->readUint16();
+ _mstOp197Data[i].maskVars = fp->readUint32();
+ _mstOp197Data[i].indexUnk49 = fp->readUint16();
+ _mstOp197Data[i].unkE = fp->readByte();
+ _mstOp197Data[i].unkF = fp->readByte();
+ bytesRead += 16;
+ }
+
+ _mstOp211Data.allocate(_mstHdr.op211DataCount);
+ for (int i = 0; i < _mstHdr.op211DataCount; ++i) {
+ _mstOp211Data[i].indexVar1 = fp->readUint16();
+ _mstOp211Data[i].indexVar2 = fp->readUint16();
+ _mstOp211Data[i].unk4 = fp->readUint16();
+ _mstOp211Data[i].unk6 = fp->readUint16();
+ _mstOp211Data[i].unk8 = fp->readByte();
+ _mstOp211Data[i].unk9 = fp->readByte();
+ _mstOp211Data[i].unkA = fp->readByte();
+ _mstOp211Data[i].unkB = fp->readByte();
+ _mstOp211Data[i].indexVar3 = fp->readByte();
+ _mstOp211Data[i].unkD = fp->readByte();
+ _mstOp211Data[i].maskVars = fp->readUint16();
+ bytesRead += 16;
+ }
+
+ _mstOp240Data.allocate(_mstHdr.op240DataCount);
+ for (int i = 0; i < _mstHdr.op240DataCount; ++i) {
+ _mstOp240Data[i].flags = fp->readUint32();
+ _mstOp240Data[i].codeData = fp->readUint32();
+ bytesRead += 8;
+ }
+
+ _mstUnk60.allocate(_mstHdr.unk0x70);
+ for (int i = 0; i < _mstHdr.unk0x70; ++i) {
+ _mstUnk60[i] = fp->readUint32();
+ bytesRead += 4;
+ }
+
+ fp->seek(_mstHdr.unk0x74 * 4, SEEK_CUR); // _mstUnk61
+ bytesRead += _mstHdr.unk0x74 * 4;
+
+ _mstOp204Data.allocate(_mstHdr.op204DataCount);
+ for (int i = 0; i < _mstHdr.op204DataCount; ++i) {
+ _mstOp204Data[i].arg0 = fp->readUint32();
+ _mstOp204Data[i].arg1 = fp->readUint32();
+ _mstOp204Data[i].arg2 = fp->readUint32();
+ _mstOp204Data[i].arg3 = fp->readUint32();
+ bytesRead += 16;
+ }
+
+ _mstCodeData = (uint8_t *)malloc(_mstHdr.codeSize * 4);
+ fp->read(_mstCodeData, _mstHdr.codeSize * 4);
+ bytesRead += _mstHdr.codeSize * 4;
+
+ if (bytesRead != _mstHdr.dataSize) {
+ warning("Unexpected .mst bytesRead %d dataSize %d", bytesRead, _mstHdr.dataSize);
+ }
+}
+
+void Resource::unloadMstData() {
+ for (int i = 0; i < _mstHdr.walkCodeDataCount; ++i) {
+ free(_mstWalkCodeData[i].codeData);
+ _mstWalkCodeData[i].codeData = 0;
+ free(_mstWalkCodeData[i].indexData);
+ _mstWalkCodeData[i].indexData = 0;
+ }
+ for (int i = 0; i < _mstHdr.behaviorIndexDataCount; ++i) {
+ free(_mstBehaviorIndexData[i].behavior);
+ _mstBehaviorIndexData[i].behavior = 0;
+ free(_mstBehaviorIndexData[i].data);
+ _mstBehaviorIndexData[i].data = 0;
+ }
+ for (int i = 0; i < _mstHdr.monsterActionIndexDataCount; ++i) {
+ free(_mstMonsterActionIndexData[i].indexUnk48);
+ _mstMonsterActionIndexData[i].indexUnk48 = 0;
+ free(_mstMonsterActionIndexData[i].data);
+ _mstMonsterActionIndexData[i].data = 0;
+ }
+ for (int i = 0; i < _mstHdr.walkPathDataCount; ++i) {
+ free(_mstWalkPathData[i].data);
+ _mstWalkPathData[i].data = 0;
+ free(_mstWalkPathData[i].walkNodeData);
+ _mstWalkPathData[i].walkNodeData = 0;
+ }
+ for (int i = 0; i < _mstHdr.behaviorDataCount; ++i) {
+ free(_mstBehaviorData[i].data);
+ _mstBehaviorData[i].data = 0;
+ }
+ for (int i = 0; i < _mstHdr.attackBoxDataCount; ++i) {
+ free(_mstAttackBoxData[i].data);
+ _mstAttackBoxData[i].data = 0;
+ }
+ for (int i = 0; i < _mstHdr.monsterActionDataCount; ++i) {
+ MstMonsterAction *m = &_mstMonsterActionData[i];
+ for (int j = 0; j < 2; ++j) {
+ free(m->data1[j]);
+ m->data1[j] = 0;
+ free(m->data2[j]);
+ m->data2[j] = 0;
+ }
+ free(m->area);
+ m->area = 0;
+ }
+
+ free(_mstMonsterInfos);
+ _mstMonsterInfos = 0;
+
+ for (int i = 0; i < _mstHdr.movingBoundsDataCount; ++i) {
+ free(_mstMovingBoundsData[i].data1);
+ _mstMovingBoundsData[i].data1 = 0;
+ free(_mstMovingBoundsData[i].indexData);
+ _mstMovingBoundsData[i].indexData = 0;
+ }
+ for (int i = 0; i < _mstHdr.shootDataCount; ++i) {
+ free(_mstShootData[i].data);
+ _mstShootData[i].data = 0;
+ }
+ for (int i = 0; i < _mstHdr.shootIndexDataCount; ++i) {
+ free(_mstShootIndexData[i].indexUnk50Unk1);
+ _mstShootIndexData[i].indexUnk50Unk1 = 0;
+ }
+
+ free(_mstCodeData);
+ _mstCodeData = 0;
+}
+
+const MstScreenArea *Resource::findMstCodeForPos(int num, int xPos, int yPos) const {
+ uint32_t i = _mstScreenAreaByPosIndexData[num];
+ while (i != kNone) {
+ const MstScreenArea *msac = &_mstScreenAreaData[i];
+ if (msac->x1 <= xPos && msac->x2 >= xPos && msac->unk0x1D != 0 && msac->y1 <= yPos && msac->y2 >= yPos) {
+ return msac;
+ }
+ i = msac->nextByPos;
+ }
+ return 0;
+}
+
+void Resource::flagMstCodeForPos(int num, uint8_t value) {
+ uint32_t i = _mstScreenAreaByValueIndexData[num];
+ while (i != kNone) {
+ MstScreenArea *msac = &_mstScreenAreaData[i];
+ msac->unk0x1D = value;
+ i = msac->nextByValue;
+ }
+}
+
+enum {
+ kModeSave,
+ kModeLoad
+};
+
+static uint8_t _checksum;
+
+static void persistUint8(FILE *fp, const uint8_t &val) {
+ fputc(val, fp);
+ _checksum ^= val;
+}
+
+static void persistUint8(FILE *fp, uint8_t &val) {
+ val = fgetc(fp);
+ _checksum ^= val;
+}
+
+static void persistUint32(FILE *fp, const uint32_t &val) {
+ for (int i = 0; i < 4; ++i) {
+ const uint8_t b = (val >> (i * 8)) & 0xFF;
+ fputc(b, fp);
+ _checksum ^= b;
+ }
+}
+
+static void persistUint32(FILE *fp, uint32_t &val) {
+ val = 0;
+ for (int i = 0; i < 4; ++i) {
+ const uint8_t b = fgetc(fp);
+ val |= b << (i * 8);
+ _checksum ^= b;
+ }
+}
+
+template
+static void persistSetupCfg(FILE *fp, T *config) {
+ _checksum = 0;
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 10; ++j) {
+ persistUint8(fp, config->players[i].progress[j]);
+ }
+ persistUint8(fp, config->players[i].levelNum);
+ persistUint8(fp, config->players[i].checkpointNum);
+ persistUint32(fp, config->players[i].cutscenesMask);
+ persistUint8(fp, config->players[i].difficulty);
+ for (int j = 0; j < 32; ++j) {
+ persistUint8(fp, config->players[i].controls[j]);
+ }
+ persistUint8(fp, config->players[i].stereo);
+ persistUint8(fp, config->players[i].volume);
+ persistUint8(fp, config->players[i].lastLevelNum);
+ }
+ persistUint8(fp, config->unkD0);
+ persistUint8(fp, config->currentPlayer);
+ const uint8_t checksum = _checksum;
+ persistUint8(fp, config->unkD2);
+ if (M == kModeSave) {
+ persistUint8(fp, checksum);
+ } else {
+ persistUint8(fp, config->checksum);
+ if (M == kModeLoad && checksum != config->checksum) {
+ warning("Invalid checksum 0x%x (0x%x) for 'setup.cfg'", config->checksum, checksum);
+ }
+ }
+}
+
+static const uint32_t _demTag = 0x31434552; // 'REC1'
+
+bool Resource::loadHodDem() {
+ bool ret = false;
+ File f;
+ if (openDat(_fs, _hodDem, &f)) {
+ const uint32_t tag = f.readUint32();
+ if (tag == _demTag) {
+ f.skipUint32();
+ _dem.randSeed = f.readUint32();
+ _dem.keyMaskLen = f.readUint32();
+ _dem.level = f.readByte();
+ _dem.checkpoint = f.readByte();
+ _dem.difficulty = f.readByte();
+ _dem.randRounds = f.readByte();
+ f.skipUint32();
+ f.skipUint32();
+ _dem.actionKeyMask = (uint8_t *)malloc(_dem.keyMaskLen);
+ f.read(_dem.actionKeyMask, _dem.keyMaskLen);
+ _dem.directionKeyMask = (uint8_t *)malloc(_dem.keyMaskLen);
+ f.read(_dem.directionKeyMask, _dem.keyMaskLen);
+ ret = true;
+ }
+ closeDat(_fs, &f);
+ }
+ return ret;
+}
+
+void Resource::unloadHodDem() {
+ free(_dem.actionKeyMask);
+ free(_dem.directionKeyMask);
+ memset(&_dem, 0, sizeof(_dem));
+}
+
+bool Resource::writeSetupCfg(const SetupConfig *config) {
+ FILE *fp = _fs->openSaveFile(_setupCfg, true);
+ if (fp) {
+ persistSetupCfg(fp, config);
+ _fs->closeFile(fp);
+ return true;
+ }
+ warning("Failed to save '%s'", _setupCfg);
+ return false;
+}
+
+bool Resource::readSetupCfg(SetupConfig *config) {
+ FILE *fp = _fs->openSaveFile(_setupCfg, false);
+ if (fp) {
+ persistSetupCfg(fp, config);
+ _fs->closeFile(fp);
+ return true;
+ }
+ return false;
+}
+
+void Resource::setDefaultsSetupCfg(SetupConfig *config, int num) {
+ assert(num >= 0 && num < 4);
+ memset(config->players[num].progress, 0, 10);
+ config->players[num].levelNum = 0;
+ config->players[num].checkpointNum = 0;
+ config->players[num].cutscenesMask = 0;
+ memset(config->players[num].controls, 0, 32);
+ config->players[num].controls[0x0] = 0x11;
+ config->players[num].controls[0x4] = 0x22;
+ config->players[num].controls[0x8] = 0x84;
+ config->players[num].controls[0xC] = 0x48;
+ config->players[num].difficulty = 1;
+ config->players[num].stereo = 1;
+ config->players[num].volume = 128;
+ config->players[num].lastLevelNum = 0;
+}
diff --git a/Src/Global/resource.h b/Src/Global/resource.h
new file mode 100644
index 0000000..3bc0064
--- /dev/null
+++ b/Src/Global/resource.h
@@ -0,0 +1,699 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef RESOURCE_H__
+#define RESOURCE_H__
+
+#include "defs.h"
+#include "intern.h"
+
+struct DatHdr {
+ uint32_t version; // 0x0
+ uint32_t bufferSize0; // 0x4
+ uint32_t bufferSize1; // 0x8
+ uint32_t sssOffset; // 0xC
+ int32_t iconsCount; // 0x10
+ int32_t menusCount; // 0x14
+ int32_t cutscenesCount; // 0x18
+ int32_t levelsCount; // 0x1C
+ int32_t levelCheckpointsCount[8]; // 0x20..0x3C
+ int32_t yesNoQuitImage; // 0x40
+ uint32_t soundDataSize; // 0x44
+ uint32_t loadingImageSize; // 0x48
+ uint32_t hintsImageOffsetTable[46];
+ uint32_t hintsImageSizeTable[46];
+};
+
+struct LvlHdr {
+ uint8_t screensCount;
+ uint8_t staticLvlObjectsCount;
+ uint8_t otherLvlObjectsCount;
+ uint8_t spritesCount;
+};
+
+struct LvlScreenVector {
+ int32_t u;
+ int32_t v;
+};
+
+struct LvlScreenState {
+ uint8_t s0; // current state
+ uint8_t s1; // states count
+ uint8_t s2; // lvlObjects initialized
+ uint8_t s3; // current mask
+};
+
+struct LvlBackgroundData {
+ uint8_t backgroundCount;
+ uint8_t currentBackgroundId;
+ uint8_t maskCount;
+ uint8_t currentMaskId;
+ uint8_t shadowCount;
+ uint8_t currentShadowId;
+ uint8_t soundCount;
+ uint8_t currentSoundId;
+ uint8_t dataUnk3Count;
+ uint8_t unk9;
+ uint8_t dataUnk45Count;
+ uint8_t unkB;
+ uint16_t backgroundPaletteId;
+ uint16_t backgroundBitmapId;
+ uint8_t *backgroundPaletteTable[4];
+ uint8_t *backgroundBitmapTable[4];
+ uint8_t *dataUnk0Table[4]; // unused
+ uint8_t *backgroundMaskTable[4];
+ uint8_t *backgroundSoundTable[4];
+ uint8_t *backgroundAnimationTable[8];
+ LvlObjectData *backgroundLvlObjectDataTable[8];
+};
+
+struct MstHdr {
+ int version;
+ int dataSize;
+ int walkBoxDataCount; // 8
+ int walkCodeDataCount; // C
+ int movingBoundsIndexDataCount; // 10
+ int levelCheckpointCodeDataCount; // 14
+ int screenAreaDataCount; // 18
+ int screenAreaIndexDataCount; // 1C
+ int behaviorIndexDataCount; // 20
+ int monsterActionIndexDataCount; // 24
+ int walkPathDataCount; // 28
+ int infoMonster2Count; // 2C
+ int behaviorDataCount; // 30
+ int attackBoxDataCount; // 34
+ int monsterActionDataCount; // 38
+ int infoMonster1Count; // 3C
+ int movingBoundsDataCount; // 40
+ int shootDataCount; // 44
+ int shootIndexDataCount; // 48
+ int actionDirectionDataCount; // 4C
+ int op223DataCount; // 50
+ int op226DataCount; // 54
+ int op227DataCount; // 58
+ int op234DataCount; // 5C
+ int op2DataCount; // 60
+ int op197DataCount; // 64
+ int op211DataCount; // 68
+ int op240DataCount; // 6C
+ int unk0x70; // mstUnk60DataCount
+ int unk0x74; // mstUnk61DataCount
+ int op204DataCount; // 78
+ int codeSize; // sizeof(uint32_t)
+ int screensCount; // 80
+};
+
+struct MstPointOffset {
+ int32_t xOffset;
+ int32_t yOffset;
+}; // sizeof == 8
+
+struct MstScreenArea {
+ int32_t x1; // 0
+ int32_t x2; // 4
+ int32_t y1; // 8
+ int32_t y2; // 0xC
+ uint32_t nextByPos; // 0x10 _mstScreenAreaByPosIndexData
+ uint32_t prev; // 0x14 unused
+ uint32_t nextByValue; // 0x18 _mstScreenAreaByValueIndexData
+ uint8_t unk0x1C; // 0x1C _mstScreenAreaByValueIndexData
+ uint8_t unk0x1D; // 0x1D value
+ uint16_t unk0x1E; // 0x1E unused
+ uint32_t codeData; // 0x20, offset _mstCodeData
+}; // sizeof == 36
+
+struct MstWalkBox { // u34
+ int32_t right; // 0
+ int32_t left; // 4
+ int32_t bottom; // 8
+ int32_t top; // 0xC
+ uint8_t flags[4]; // 0x10
+}; // sizeof == 20
+
+struct MstWalkCode { // u35
+ uint32_t *codeData; // 0, offset _mstCodeData
+ uint32_t codeDataCount; // 4
+ uint8_t *indexData; // 8
+ uint32_t indexDataCount; // C
+}; // sizeof == 16
+
+struct MstMovingBoundsIndex { // u36
+ uint32_t indexUnk49; // indexes _mstMovingBoundsData
+ uint32_t unk4; // indexes _mstMovingBoundsData.data1
+ int32_t unk8;
+}; // sizeof == 12
+
+struct MstBehaviorIndex { // u42
+ uint32_t *behavior; // 0 indexes _mstBehaviorData
+ uint32_t count1; // 4
+ uint8_t *data; // 8 lut - indexes .behavior
+ uint32_t dataCount; // C
+}; // sizeof == 16
+
+struct MstMonsterActionIndex { // u43
+ uint32_t *indexUnk48; // indexes _mstMonsterActionData
+ uint32_t count1; // 4
+ uint8_t *data; // 8 lut - indexes .indexUnk48
+ uint32_t dataCount; // C
+}; // sizeof == 16
+
+struct MstWalkNode { // u44u1
+ int32_t x1; // 0
+ int32_t x2; // 4
+ int32_t y1; // 8
+ int32_t y2; // 0xC
+ uint32_t walkBox; // 0x10
+ uint32_t walkCodeStage1; // 0x14
+ uint32_t walkCodeStage2; // 0x18
+ uint32_t movingBoundsIndex1; // 0x1C
+ uint32_t movingBoundsIndex2; // 0x20
+ uint32_t walkCodeReset[2]; // 0x24
+ int32_t coords[4][2]; // 0x2C, 0x34, 0x3C, 0x44
+ uint32_t neighborWalkNode[4]; // per direction 0x4C
+ uint32_t nextWalkNode; // next 0x5C
+ uint8_t *unk60[2]; // 0x60
+}; // sizeof == 104
+
+struct MstWalkPath { // u44
+ MstWalkNode *data;
+ uint32_t *walkNodeData; // indexed by screen number
+ uint32_t mask; // 0x8
+ uint32_t count; // 0xC
+}; // sizeof == 16
+
+struct MstInfoMonster2 { // u45
+ uint8_t type; // 0x0
+ uint8_t shootMask; // 0x1
+ uint16_t anim; // 0x2
+ uint32_t codeData; // 4 init
+ uint32_t codeData2; // 8 shoot
+}; // sizeof == 12
+
+struct MstBehaviorState { // u46u1
+ uint32_t indexMonsterInfo; // 0, indexes _mstMonsterInfos
+ uint16_t anim; // 4
+ uint16_t unk6; // 6
+ uint32_t unk8; // 0x8
+ uint32_t energy; // 0xC
+ uint32_t unk10; // 0x10
+ uint32_t count; // 0x14
+ uint32_t unk18; // 0x18
+ uint32_t indexUnk51; // 0x1C indexes _mstShootIndexData
+ uint32_t walkPath; // 0x20 indexes _mstWalkPathData
+ uint32_t attackBox; // 0x24 indexes _mstAttackBoxData
+ uint32_t codeData; // 0x28 indexes _mstCodeData
+}; // sizeof == 44
+
+struct MstBehavior { // u46
+ MstBehaviorState *data;
+ uint32_t count;
+}; // sizeof == 8
+
+struct MstAttackBox { // u47
+ uint8_t *data; // sizeof == 20 (uint32_t * 5)
+ uint32_t count;
+}; // sizeof == 8
+
+struct MstMonsterAreaAction {
+ uint32_t unk0; // 0x0, indexes _mstMonsterInfos
+ uint32_t indexUnk51; // 0x4
+ int32_t xPos; // 0x8
+ int32_t yPos; // 0xC
+ uint32_t codeData; // 0x10
+ uint32_t codeData2; // 0x14 stop
+ uint8_t unk18; // 0x18
+ uint8_t direction; // 0x19
+ int8_t screenNum; // 0x1A
+ uint8_t monster1Index; // 0x1B
+}; // sizeof == 28
+
+struct MstMonsterArea {
+ uint8_t unk0;
+ MstMonsterAreaAction *data; // 0x4 sizeof == 28
+ uint32_t count;
+}; // sizeof == 12
+
+struct MstMonsterAction { // u48
+ uint16_t unk0;
+ uint16_t unk2;
+ uint8_t unk4;
+ uint8_t direction;
+ uint8_t unk6;
+ uint8_t unk7;
+ uint32_t codeData; // 0x8 indexes _mstCodeData
+ MstMonsterArea *area; // 0xC
+ int areaCount; // 0x10
+ uint32_t *data1[2]; // 0x14, 0x18
+ uint32_t *data2[2]; // 0x1C, 0x20
+ uint32_t count[2]; // 0x24, 0x28
+}; // sizeof == 44
+
+struct MstMovingBoundsUnk1 { // u49u1
+ uint32_t offsetMonsterInfo; // 0, offset _mstMonsterInfos[32]
+ uint32_t unk4;
+ uint8_t unk8;
+ uint8_t unk9;
+ uint8_t unkA;
+ uint8_t unkB;
+ uint8_t unkC;
+ uint8_t unkD;
+ uint8_t unkE;
+ uint8_t unkF;
+}; // sizeof == 16
+
+struct MstMovingBounds { // u49
+ uint32_t indexMonsterInfo; // 0, indexes _mstMonsterInfos
+ MstMovingBoundsUnk1 *data1; // 0x4, sizeof == 16
+ uint32_t count1; // 0x8
+ uint8_t *indexData; // 0xC
+ uint32_t indexDataCount; // 0x10
+ uint8_t unk14; // 0x14
+ uint8_t unk15; // 0x15
+ uint8_t unk16; // 0x16
+ uint8_t unk17; // 0x17
+}; // sizeof == 24
+
+struct MstShootAction { // u50u1
+ uint32_t codeData;
+ uint32_t unk4;
+ uint32_t unk8;
+ uint32_t xPos; // C
+ uint32_t yPos; // 10
+ uint32_t width; // 14
+ uint32_t height; // 18
+ uint32_t hSize; // 1C
+ uint32_t vSize; // 20
+ int32_t unk24;
+}; // sizeof == 40
+
+struct MstShoot { // u50
+ MstShootAction *data;
+ uint32_t count;
+}; // sizeof == 8
+
+struct MstShootIndex { // u51
+ uint32_t indexUnk50;
+ uint32_t *indexUnk50Unk1; // sizeof == 40
+ uint32_t count;
+}; // sizeof == 12
+
+struct MstActionDirectionMask { // u52
+ uint8_t unk0;
+ uint8_t unk1;
+ uint8_t unk2;
+ uint8_t unk3;
+}; // sizeof == 4
+
+struct MstOp240Data {
+ uint32_t flags;
+ uint32_t codeData;
+}; // sizeof == 8
+
+struct MstOp223Data {
+ int16_t indexVar1;
+ int16_t indexVar2; // 2
+ int16_t indexVar3; // 4
+ int16_t indexVar4; // 6
+ uint8_t unk8; // 8
+ uint8_t unk9;
+ int8_t indexVar5; // A
+ int8_t unkB; // B
+ uint16_t unkC; // C
+ uint16_t unkE; // E
+ uint32_t maskVars; // 0x10
+}; // sizeof == 20
+
+struct MstOp227Data {
+ int16_t indexVar1; // 0
+ int16_t indexVar2; // 2
+ uint8_t compare; // 4
+ uint8_t maskVars; // 5
+ uint16_t codeData; // 6
+}; // sizeof == 8
+
+struct MstOp234Data {
+ int16_t indexVar1; // 0
+ int16_t indexVar2; // 2
+ uint8_t compare; // 4
+ uint8_t maskVars; // 5
+ uint32_t codeData;
+}; // sizeof == 8
+
+struct MstOp2Data {
+ int32_t indexVar1; // 0
+ int32_t indexVar2; // 4
+ uint8_t maskVars; // 8
+ uint8_t unk9;
+ uint8_t unkA;
+ uint8_t unkB;
+}; // sizeof == 12
+
+struct MstOp226Data {
+ uint8_t unk0;
+ uint8_t unk1;
+ uint8_t unk2;
+ uint8_t unk3;
+ uint8_t unk4;
+ uint8_t unk5;
+ uint8_t unk6;
+ uint8_t unk7;
+}; // sizeof == 8
+
+struct MstOp204Data {
+ uint32_t arg0; // 0
+ uint32_t arg1; // 4
+ uint32_t arg2; // 8
+ uint32_t arg3; // C
+}; // sizeof == 16
+
+struct MstOp197Data {
+ int16_t unk0;
+ int16_t unk2;
+ int16_t unk4;
+ int16_t unk6;
+ uint32_t maskVars;
+ uint16_t indexUnk49;
+ int8_t unkE;
+ int8_t unkF;
+}; // sizeof == 16
+
+struct MstOp211Data {
+ int16_t indexVar1; // 0
+ int16_t indexVar2; // 2
+ uint16_t unk4; // 4
+ int16_t unk6; // 6
+ uint8_t unk8; // 8
+ uint8_t unk9; // 9
+ uint8_t unkA; // A
+ uint8_t unkB; // B
+ int8_t indexVar3; // C
+ uint8_t unkD; // D
+ uint16_t maskVars; // E
+}; // sizeof == 16
+
+struct SssHdr {
+ int version;
+ int bufferSize;
+ int preloadPcmCount;
+ int preloadInfoCount;
+ int infosDataCount;
+ int filtersDataCount;
+ int banksDataCount;
+ int samplesDataCount;
+ int codeSize;
+ int preloadData1Count; // 24
+ int preloadData2Count; // 28
+ int preloadData3Count; // 2C
+ int pcmCount; // 30
+};
+
+struct SssInfo {
+ uint16_t sssBankIndex; // 0 indexes _sssBanksData
+ int8_t sampleIndex; // 2
+ uint8_t targetVolume; // 3
+ int8_t targetPriority; // 4
+ int8_t targetPanning; // 5
+ uint8_t concurrencyMask; // 6
+};
+
+struct SssDefaults {
+ uint8_t defaultVolume;
+ int8_t defaultPriority;
+ int8_t defaultPanning;
+};
+
+struct SssBank {
+ uint8_t flags; // 0 flags0
+ int8_t count; // 1 codeOffsetCount
+ uint16_t sssFilter; // 2 indexes _sssFilters
+ uint32_t firstSampleIndex; // 4 indexes _sssSamplesData
+};
+
+struct SssSample {
+ uint16_t pcm; // indexes _sssPcmTable
+ uint16_t framesCount;
+ uint8_t initVolume; // 0x4
+ uint8_t unk5; // unused
+ int8_t initPriority; // 0x6
+ int8_t initPanning; // 0x7
+ uint32_t codeOffset1; // 0x8 indexes _sssCodeData
+ uint32_t codeOffset2; // 0xC indexes _sssCodeData
+ uint32_t codeOffset3; // 0x10 indexes _sssCodeData
+ uint32_t codeOffset4; // 0x14 indexes _sssCodeData
+}; // sizeof == 24
+
+struct SssPreloadList {
+ int count;
+ int ptrSize;
+ uint8_t *ptr; // uint16_t for v6 or v12, uint8_t for v10
+};
+
+struct SssPreloadInfoData {
+ uint16_t pcmBlockOffset;
+ uint16_t pcmBlockSize;
+ uint8_t screenNum;
+ uint8_t preload3Index;
+ uint8_t preload1Index;
+ uint8_t preload2Index;
+ uint32_t unk1C;
+ SssPreloadList preload1Data_V6;
+}; // sizeof == 32 (v10,v12) 68 (v6)
+
+struct SssPreloadInfo {
+ uint32_t count;
+ SssPreloadInfoData *data;
+};
+
+struct SssFilter {
+ int32_t volume;
+ int32_t volumeCurrent; // fp16
+ int32_t volumeDelta;
+ int32_t volumeSteps;
+ int32_t panning;
+ int32_t panningCurrent; // fp16
+ int32_t panningDelta;
+ int32_t panningSteps;
+ int32_t priority;
+ int32_t priorityCurrent;
+ bool changed; // 0x30
+}; // sizeof == 52
+
+struct SssPcm {
+ int16_t *ptr; // 0 PCM data
+ uint32_t offset; // 4 offset in .sss
+ uint32_t totalSize; // 8 size in .sss (256 int16_t words + followed by indexes)
+ uint32_t strideSize; // 12
+ uint16_t strideCount; // 16
+ uint16_t flags; // 18 1:stereo
+ uint32_t pcmSize; // decompressed PCM size in bytes
+};
+
+struct SssUnk6 {
+ uint32_t unk0[4]; // 0
+ uint32_t mask; // 10
+};
+
+struct Dem {
+ uint32_t randSeed;
+ uint32_t keyMaskLen;
+ uint8_t level;
+ uint8_t checkpoint;
+ uint8_t difficulty;
+ uint8_t randRounds;
+ uint8_t *actionKeyMask;
+ uint8_t *directionKeyMask;
+};
+
+template
+struct ResStruct {
+ T *ptr;
+ unsigned int count;
+
+ ResStruct()
+ : ptr(0), count(0) {
+ }
+ ~ResStruct() {
+ deallocate();
+ }
+
+ void deallocate() {
+ free(ptr);
+ ptr = 0;
+ count = 0;
+ }
+ void allocate(unsigned int size) {
+ free(ptr);
+ count = size;
+ ptr = (T *)malloc(size * sizeof(T));
+ }
+
+ const T& operator[](int i) const {
+ assert((unsigned int)i < count);
+ return ptr[i];
+ }
+ T& operator[](int i) {
+ assert((unsigned int)i < count);
+ return ptr[i];
+ }
+};
+
+struct FileSystem;
+
+struct Resource {
+ enum {
+ V1_0,
+ V1_1,
+ V1_2 // 1.2 or newer
+ };
+
+ FileSystem *_fs;
+
+ DatHdr _datHdr;
+ File *_datFile;
+ LvlHdr _lvlHdr;
+ File *_lvlFile;
+ MstHdr _mstHdr;
+ File *_mstFile;
+ SssHdr _sssHdr;
+ File *_sssFile;
+
+ bool _isPsx;
+ bool _isDemo;
+ int _version;
+
+ uint8_t *_loadingImageBuffer;
+ uint8_t *_fontBuffer;
+ uint8_t _fontDefaultColor;
+ uint8_t *_menuBuffer0;
+ uint8_t *_menuBuffer1;
+ uint32_t _menuBuffersOffset;
+
+ Dem _dem;
+ uint32_t _demOffset;
+
+ uint8_t _currentScreenResourceNum;
+
+ uint8_t _screensGrid[kMaxScreens][4];
+ LvlScreenVector _screensBasePos[kMaxScreens];
+ LvlScreenState _screensState[kMaxScreens];
+ uint8_t *_resLevelData0x470CTable; // masks
+ uint8_t *_resLevelData0x470CTablePtrHdr;
+ uint8_t *_resLevelData0x470CTablePtrData;
+ uint32_t _lvlSpritesOffset;
+ uint32_t _lvlBackgroundsOffset;
+ uint32_t _lvlMasksOffset;
+ uint32_t _lvlSssOffset; // .sss offset (PSX)
+ uint32_t _resLevelData0x2988SizeTable[kMaxSpriteTypes]; // sprites
+ LvlObjectData _resLevelData0x2988Table[kMaxSpriteTypes];
+ LvlObjectData *_resLevelData0x2988PtrTable[kMaxSpriteTypes];
+ uint8_t *_resLvlSpriteDataPtrTable[kMaxSpriteTypes];
+ uint32_t _resLevelData0x2B88SizeTable[kMaxScreens]; // backgrounds
+ LvlBackgroundData _resLvlScreenBackgroundDataTable[kMaxScreens];
+ uint8_t *_resLvlScreenBackgroundDataPtrTable[kMaxScreens];
+
+ LvlObject _resLvlScreenObjectDataTable[104];
+ LvlObject _dummyObject; // (LvlObject *)0xFFFFFFFF
+
+ ResStruct _sssInfosData;
+ ResStruct _sssDefaultsData;
+ ResStruct _sssBanksData;
+ ResStruct _sssSamplesData;
+ ResStruct _sssPreload1Table; // pcm
+ ResStruct _sssPreloadInfosData; // indexed by screen number
+ ResStruct _sssFilters;
+ ResStruct _sssPcmTable;
+ ResStruct _sssDataUnk6;
+ uint32_t *_sssGroup1[3];
+ uint32_t *_sssGroup2[3];
+ uint32_t *_sssGroup3[3];
+ uint8_t *_sssCodeData;
+
+ ResStruct _mstPointOffsets;
+ ResStruct _mstWalkBoxData;
+ ResStruct _mstWalkCodeData;
+ ResStruct _mstMovingBoundsIndexData;
+ uint32_t _mstTickDelay;
+ uint32_t _mstTickCodeData;
+ ResStruct _mstLevelCheckpointCodeData;
+ ResStruct _mstScreenAreaData;
+ ResStruct _mstScreenAreaByValueIndexData;
+ ResStruct _mstScreenAreaByPosIndexData;
+ ResStruct _mstUnk41;
+ ResStruct _mstBehaviorIndexData;
+ ResStruct _mstMonsterActionIndexData;
+ ResStruct _mstWalkPathData;
+ ResStruct _mstInfoMonster2Data;
+ ResStruct _mstBehaviorData;
+ ResStruct _mstAttackBoxData;
+ ResStruct _mstMonsterActionData;
+ uint8_t *_mstMonsterInfos; // sizeof == 948
+ ResStruct _mstMovingBoundsData;
+ ResStruct _mstShootData;
+ ResStruct _mstShootIndexData;
+ ResStruct _mstActionDirectionData;
+ ResStruct _mstOp223Data;
+ ResStruct _mstOp227Data;
+ ResStruct _mstOp234Data;
+ ResStruct _mstOp2Data;
+ ResStruct _mstOp197Data;
+ ResStruct _mstOp211Data;
+ ResStruct _mstOp240Data;
+ ResStruct _mstUnk60; // indexes _mstCodeData
+ ResStruct _mstOp204Data;
+ uint8_t *_mstCodeData;
+ ResStruct _mstOp226Data;
+
+ Resource(FileSystem *fs);
+ ~Resource();
+
+ bool sectorAlignedGameData();
+
+ void loadSetupDat();
+ bool loadDatHintImage(int num, uint8_t *dst, uint8_t *pal);
+ bool loadDatLoadingImage(uint8_t *dst, uint8_t *pal);
+ void loadDatMenuBuffers();
+ void unloadDatMenuBuffers();
+
+ void loadLevelData(int levelNum);
+
+ void loadLvlScreenObjectData(LvlObject *dat, const uint8_t *src);
+ void loadLvlData(File *fp);
+ void unloadLvlData();
+ void loadLvlSpriteData(int num, const uint8_t *buf = 0);
+ const uint8_t *getLvlScreenMaskDataPtr(int num) const;
+ const uint8_t *getLvlScreenPosDataPtr(int num) const;
+ void loadLvlScreenMaskData();
+ void loadLvlScreenBackgroundData(int num, const uint8_t *buf = 0);
+ void unloadLvlScreenBackgroundData(int num);
+ bool isLvlSpriteDataLoaded(int num) const;
+ bool isLvlBackgroundDataLoaded(int num) const;
+ void incLvlSpriteDataRefCounter(LvlObject *ptr);
+ void decLvlSpriteDataRefCounter(LvlObject *ptr);
+ const uint8_t *getLvlSpriteFramePtr(LvlObjectData *dat, int frame, uint16_t *w, uint16_t *h) const;
+ const uint8_t *getLvlSpriteCoordPtr(LvlObjectData *dat, int num) const;
+ int findScreenGridIndex(int screenNum) const;
+
+ void loadSssData(File *fp, const uint32_t baseOffset = 0);
+ void unloadSssData();
+ void checkSssCode(const uint8_t *buf, int size) const;
+ void loadSssPcm(File *fp, SssPcm *pcm);
+ void clearSssGroup3();
+ void resetSssFilters();
+ void preloadSssPcmList(const SssPreloadInfoData *preloadInfoData);
+
+ void loadMstData(File *fp);
+ void unloadMstData();
+ const MstScreenArea *findMstCodeForPos(int num, int xPos, int yPos) const;
+ void flagMstCodeForPos(int num, uint8_t value);
+
+ bool loadHodDem();
+ void unloadHodDem();
+
+ bool writeSetupCfg(const SetupConfig *config);
+ bool readSetupCfg(SetupConfig *config);
+ void setDefaultsSetupCfg(SetupConfig *config, int num);
+};
+
+#endif // RESOURCE_H__
diff --git a/Src/Global/scaler.h b/Src/Global/scaler.h
new file mode 100644
index 0000000..aa3ed69
--- /dev/null
+++ b/Src/Global/scaler.h
@@ -0,0 +1,22 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef SCALER_H__
+#define SCALER_H__
+
+#include "intern.h"
+
+typedef void (*ScaleProc32)(int factor, uint32_t *dst, int dstPitch, const uint32_t *src, int srcPitch, int w, int h);
+
+struct Scaler {
+ const char *name;
+ int factorMin, factorMax;
+ ScaleProc32 scale;
+};
+
+extern const Scaler scaler_nearest;
+extern const Scaler scaler_xbr;
+
+#endif // SCALER_H__
diff --git a/Src/Global/scaler_nearest.cpp b/Src/Global/scaler_nearest.cpp
new file mode 100644
index 0000000..e595775
--- /dev/null
+++ b/Src/Global/scaler_nearest.cpp
@@ -0,0 +1,46 @@
+
+#include "scaler.h"
+
+static void scale_nearest(int factor, uint32_t *dst, int dstPitch, const uint32_t *src, int srcPitch, int w, int h) {
+ switch (factor) {
+ case 2:
+ while (h--) {
+ uint32_t *p = dst;
+ for (int i = 0; i < w; ++i, p += 2) {
+ uint32_t c = *(src + i);
+ *(p) = c;
+ *(p + 1) = c;
+ *(p + dstPitch) = c;
+ *(p + dstPitch + 1) = c;
+ }
+ dst += dstPitch * 2;
+ src += srcPitch;
+ }
+ break;
+ case 3:
+ while (h--) {
+ uint32_t *p = dst;
+ for (int i = 0; i < w; ++i, p += 3) {
+ uint32_t c = *(src + i);
+ *(p) = c;
+ *(p + 1) = c;
+ *(p + 2) = c;
+ *(p + dstPitch) = c;
+ *(p + dstPitch + 1) = c;
+ *(p + dstPitch + 2) = c;
+ *(p + 2 * dstPitch) = c;
+ *(p + 2 * dstPitch + 1) = c;
+ *(p + 2 * dstPitch + 2) = c;
+ }
+ dst += dstPitch * 3;
+ src += srcPitch;
+ }
+ break;
+ }
+}
+
+const Scaler scaler_nearest = {
+ "nearest",
+ 2, 3,
+ scale_nearest
+};
diff --git a/Src/Global/scaler_xbr.cpp b/Src/Global/scaler_xbr.cpp
new file mode 100644
index 0000000..a90c546
--- /dev/null
+++ b/Src/Global/scaler_xbr.cpp
@@ -0,0 +1,40 @@
+
+#include "scaler.h"
+extern "C" {
+#include "3p/libxbr-standalone/filters.h"
+}
+
+static bool _xbr_init;
+static xbr_data _xbr_data;
+
+static void scale_xbr(int factor, uint32_t *dst, int dstPitch, const uint32_t *src, int srcPitch, int w, int h) {
+ if (!_xbr_init) {
+ xbr_init_data(&_xbr_data);
+ _xbr_init = true;
+ }
+ xbr_params params;
+ params.input = (uint8_t *)src;
+ params.output = (uint8_t *)dst;
+ params.inWidth = w;
+ params.inHeight = h;
+ params.inPitch = srcPitch * sizeof(uint32_t);
+ params.outPitch = dstPitch * sizeof(uint32_t);
+ params.data = &_xbr_data;
+ switch (factor) {
+ case 2:
+ xbr_filter_xbr2x(¶ms);
+ break;
+ case 3:
+ xbr_filter_xbr3x(¶ms);
+ break;
+ case 4:
+ xbr_filter_xbr4x(¶ms);
+ break;
+ }
+}
+
+const Scaler scaler_xbr = {
+ "xbr",
+ 2, 4,
+ scale_xbr
+};
diff --git a/Src/Global/screenshot.cpp b/Src/Global/screenshot.cpp
new file mode 100644
index 0000000..59e6f36
--- /dev/null
+++ b/Src/Global/screenshot.cpp
@@ -0,0 +1,61 @@
+
+#include
+#include "screenshot.h"
+
+static void fwriteUint16LE(FILE *fp, uint16_t n) {
+ fputc(n & 0xFF, fp);
+ fputc(n >> 8, fp);
+}
+
+static void fwriteUint32LE(FILE *fp, uint32_t n) {
+ fwriteUint16LE(fp, n & 0xFFFF);
+ fwriteUint16LE(fp, n >> 16);
+}
+
+static const uint16_t TAG_BM = 0x4D42;
+
+void saveBMP(FILE *fp, const uint8_t *bits, const uint8_t *pal, int w, int h) {
+ const int alignWidth = (w + 3) & ~3;
+ const int imageSize = alignWidth * h;
+
+ // Write file header
+ fwriteUint16LE(fp, TAG_BM);
+ fwriteUint32LE(fp, 14 + 40 + 4 * 256 + imageSize);
+ fwriteUint16LE(fp, 0); // reserved1
+ fwriteUint16LE(fp, 0); // reserved2
+ fwriteUint32LE(fp, 14 + 40 + 4 * 256);
+
+ // Write info header
+ fwriteUint32LE(fp, 40);
+ fwriteUint32LE(fp, w);
+ fwriteUint32LE(fp, h);
+ fwriteUint16LE(fp, 1); // planes
+ fwriteUint16LE(fp, 8); // bit_count
+ fwriteUint32LE(fp, 0); // compression
+ fwriteUint32LE(fp, imageSize); // size_image
+ fwriteUint32LE(fp, 0); // x_pels_per_meter
+ fwriteUint32LE(fp, 0); // y_pels_per_meter
+ fwriteUint32LE(fp, 0); // num_colors_used
+ fwriteUint32LE(fp, 0); // num_colors_important
+
+ // Write palette data
+ for (int i = 0; i < 256; ++i) {
+ fputc(pal[2], fp);
+ fputc(pal[1], fp);
+ fputc(pal[0], fp);
+ fputc(0, fp);
+ pal += 3;
+ }
+
+ // Write bitmap data
+ const int pitch = w;
+ bits += h * pitch;
+ for (int i = 0; i < h; ++i) {
+ bits -= pitch;
+ fwrite(bits, w, 1, fp);
+ int pad = alignWidth - w;
+ while (pad--) {
+ fputc(0, fp);
+ }
+ }
+}
diff --git a/Src/Global/screenshot.h b/Src/Global/screenshot.h
new file mode 100644
index 0000000..003f1a1
--- /dev/null
+++ b/Src/Global/screenshot.h
@@ -0,0 +1,10 @@
+
+#ifndef SCREENSHOT_H__
+#define SCREENSHOT_H__
+
+#include
+#include
+
+void saveBMP(FILE *fp, const uint8_t *bits, const uint8_t *pal, int w, int h);
+
+#endif
diff --git a/Src/Global/sound.cpp b/Src/Global/sound.cpp
new file mode 100644
index 0000000..2924201
--- /dev/null
+++ b/Src/Global/sound.cpp
@@ -0,0 +1,1204 @@
+
+#include "game.h"
+#include "resource.h"
+#include "system.h"
+#include "util.h"
+
+enum {
+ kFlagPlaying = 1 << 0,
+ kFlagPaused = 1 << 1, // no PCM
+ kFlagNoCode = 1 << 2 // no bytecode
+};
+
+// if x < 90, lut[x] ~= x / 2
+// if x > 90, lut[x] ~= 45 + (x - 90) * 2
+static const uint8_t _volumeRampTable[129] = {
+ 0x00, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06,
+ 0x06, 0x07, 0x07, 0x07, 0x08, 0x08, 0x09, 0x09, 0x09, 0x0A, 0x0A, 0x0B, 0x0B, 0x0C, 0x0C, 0x0C,
+ 0x0D, 0x0D, 0x0E, 0x0E, 0x0F, 0x0F, 0x10, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x13, 0x13, 0x14,
+ 0x14, 0x15, 0x15, 0x16, 0x16, 0x17, 0x17, 0x18, 0x18, 0x19, 0x1A, 0x1A, 0x1B, 0x1B, 0x1C, 0x1C,
+ 0x1D, 0x1D, 0x1E, 0x1F, 0x1F, 0x20, 0x20, 0x21, 0x22, 0x22, 0x23, 0x23, 0x24, 0x25, 0x25, 0x26,
+ 0x27, 0x27, 0x28, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2F, 0x30, 0x32, 0x33, 0x35, 0x37, 0x38,
+ 0x3A, 0x3C, 0x3D, 0x3F, 0x41, 0x43, 0x44, 0x46, 0x48, 0x4A, 0x4C, 0x4E, 0x50, 0x52, 0x54, 0x56,
+ 0x59, 0x5B, 0x5D, 0x5F, 0x62, 0x64, 0x66, 0x69, 0x6B, 0x6E, 0x70, 0x73, 0x76, 0x78, 0x7B, 0x7E,
+ 0x80
+};
+
+static uint32_t sssGroupValue(uint8_t source, uint8_t mask, const SssInfo *s) {
+ return (mask << 24) | (source << 20) | ((s->sampleIndex & 15) << 16) | (s->concurrencyMask << 12) | (s->sssBankIndex & 0xFFF);
+}
+
+static bool compareSssGroup(uint32_t flags_a, uint32_t flags_b) {
+ if (0) {
+ // the x86 code compares the 3 bit fields.
+ // the original code possibly used a structure with bit fields that was optimized by the compiler as a uint32_t
+ if (((flags_a >> 20) & 15) == ((flags_b >> 20) & 15)) { // source : 0,1,2 (Andy, monster1, monster2)
+ if ((flags_a & 0xFFF) == (flags_b & 0xFFF)) { // bank index
+ return (flags_a >> 24) == (flags_b >> 24); // sample index, used as a mask lut[][] |= 1 << index
+ }
+ }
+ return false;
+ }
+ // we can instead simply compare masked integers
+ return compare_bits(flags_a, flags_b, 0xFFF00FFF);
+}
+
+// returns the active samples for the table/source/bank
+static uint32_t *getSssGroupPtr(Resource *res, int num, uint32_t flags) {
+ const int source = (flags >> 20) & 15; // 0,1,2
+ assert(source < 3);
+ const int bankIndex = flags & 0xFFF;
+ assert(bankIndex < res->_sssHdr.banksDataCount);
+ switch (num) {
+ case 1:
+ return &res->_sssGroup1[source][bankIndex];
+ case 2:
+ return &res->_sssGroup2[source][bankIndex];
+ case 3:
+ return &res->_sssGroup3[source][bankIndex];
+ }
+ error("Invalid sssGroup %d", num);
+ return 0;
+}
+
+void Game::muteSound() {
+ MixerLock ml(&_mix);
+ _snd_muted = true;
+ _snd_bufferOffset = _snd_bufferSize = 0;
+ _mix._mixingQueueSize = 0;
+}
+
+void Game::unmuteSound() {
+ MixerLock ml(&_mix);
+ _snd_muted = false;
+ _snd_bufferOffset = _snd_bufferSize = 0;
+ _mix._mixingQueueSize = 0;
+}
+
+void Game::resetSound() {
+ MixerLock ml(&_mix);
+ clearSoundObjects();
+}
+
+int Game::getSoundPosition(const SssObject *so) {
+ MixerLock ml(&_mix);
+ return so->pcm ? so->pcmFramesCount : -1;
+}
+
+void Game::setSoundPanning(SssObject *so, int panning) {
+ MixerLock ml(&_mix);
+ so->panning = panning;
+ setSoundObjectPanning(so);
+}
+
+SssObject *Game::findLowPrioritySoundObject() const {
+ SssObject *so = 0;
+ if (_playingSssObjectsCount >= _playingSssObjectsMax && _sssObjectsList1) {
+ so = _sssObjectsList1;
+ for (SssObject *current = so->nextPtr; current; current = current->nextPtr) {
+ if (so->currentPriority > current->currentPriority) {
+ so = current;
+ }
+ }
+ }
+ return so;
+}
+
+void Game::removeSoundObjectFromList(SssObject *so) {
+ debug(kDebug_SOUND, "removeSoundObjectFromList so %p flags 0x%x", so, so->flags);
+ so->pcm = 0;
+ if ((so->flags & kFlagPlaying) != 0) {
+
+ // remove from linked list1
+ SssObject *next = so->nextPtr;
+ SssObject *prev = so->prevPtr;
+ if (next) {
+ next->prevPtr = prev;
+ }
+ if (prev) {
+ prev->nextPtr = next;
+ } else {
+ assert(so == _sssObjectsList1);
+ _sssObjectsList1 = next;
+ }
+ --_playingSssObjectsCount;
+
+ if (_playingSssObjectsMax > 0) {
+ if (_lowPrioritySssObject == so || (_playingSssObjectsCount < _playingSssObjectsMax && _lowPrioritySssObject)) {
+ _lowPrioritySssObject = findLowPrioritySoundObject();
+ }
+ }
+ } else {
+
+ // remove from linked list2
+ SssObject *next = so->nextPtr;
+ SssObject *prev = so->prevPtr;
+ if (next) {
+ next->prevPtr = prev;
+ }
+ if (prev) {
+ prev->nextPtr = next;
+ } else {
+ assert(so == _sssObjectsList2);
+ _sssObjectsList2 = next;
+ }
+ }
+}
+
+void Game::updateSoundObject(SssObject *so) {
+ _sssUpdatedObjectsTable[so->num] = true;
+ _sssObjectsChanged = so->filter->changed;
+ if ((so->flags & kFlagNoCode) == 0) {
+ if (so->pcm == 0) {
+ if (so->codeDataStage1) {
+ so->codeDataStage1 = executeSssCode(so, so->codeDataStage1);
+ }
+ if (so->pcm == 0) {
+ return;
+ }
+ if (so->codeDataStage4) {
+ so->codeDataStage4 = executeSssCode(so, so->codeDataStage4);
+ }
+ if (so->pcm == 0) {
+ return;
+ }
+ } else {
+ if (so->codeDataStage1) {
+ so->codeDataStage1 = executeSssCode(so, so->codeDataStage1);
+ }
+ if (so->pcm == 0) {
+ return;
+ }
+ if (so->panningPtr) {
+ const int panning = getSoundObjectPanning(so);
+ if (panning != so->panning) {
+ so->panning = panning;
+ _sssObjectsChanged = true;
+ }
+ } else {
+ if (so->codeDataStage2) {
+ so->codeDataStage2 = executeSssCode(so, so->codeDataStage2);
+ }
+ }
+ if (so->pcm == 0) {
+ return;
+ }
+ if (so->codeDataStage3) {
+ so->codeDataStage3 = executeSssCode(so, so->codeDataStage3);
+ }
+ if (so->pcm == 0) {
+ return;
+ }
+ if (so->codeDataStage4) {
+ so->codeDataStage4 = executeSssCode(so, so->codeDataStage4);
+ }
+ if (so->pcm == 0) {
+ return;
+ }
+ if (_sssObjectsChanged && (so->flags & kFlagPlaying) != 0) {
+ setSoundObjectPanning(so);
+ }
+ }
+ } else if ((so->flags & kFlagPlaying) != 0) {
+ if (so->panningPtr) {
+ const int panning = getSoundObjectPanning(so);
+ if (panning != so->panning) {
+ so->panning = panning;
+ _sssObjectsChanged = true;
+ setSoundObjectPanning(so);
+ }
+ } else if (_sssObjectsChanged) {
+ setSoundObjectPanning(so);
+ }
+ }
+ if (so->pcmFramesCount != 0) {
+ --so->pcmFramesCount;
+ if ((so->flags & kFlagPaused) == 0) {
+ ++so->currentPcmFrame;
+ }
+ } else {
+ const uint32_t flags = so->flags0;
+ removeSoundObjectFromList(so);
+ if (so->nextSoundBank != -1) {
+ LvlObject *tmp = _currentSoundLvlObject;
+ _currentSoundLvlObject = so->lvlObject;
+ createSoundObject(so->nextSoundBank, so->nextSoundSample, flags);
+ _currentSoundLvlObject = tmp;
+ return;
+ }
+ updateSssGroup2(flags);
+ }
+}
+
+void Game::sssOp12_removeSounds2(int num, uint8_t source, uint8_t sampleIndex) {
+ assert(source < 3);
+ assert(num < _res->_sssHdr.banksDataCount);
+ assert(sampleIndex < 32);
+ const uint32_t mask = (1 << sampleIndex);
+ _res->_sssGroup1[source][num] &= ~mask;
+ for (SssObject *so = _sssObjectsList1; so; so = so->nextPtr) {
+ if (so->bankIndex == num && ((so->flags1 >> 20) & 15) == source && (so->flags1 >> 24) == sampleIndex) {
+ so->codeDataStage3 = 0;
+ if (so->codeDataStage4 == 0) {
+ removeSoundObjectFromList(so);
+ }
+ so->nextSoundBank = -1;
+ so->delayCounter = -2;
+ }
+ }
+ for (SssObject *so = _sssObjectsList2; so; so = so->nextPtr) {
+ if (so->bankIndex == num && ((so->flags1 >> 20) & 15) == source && (so->flags1 >> 24) == sampleIndex) {
+ so->codeDataStage3 = 0;
+ if (so->codeDataStage4 == 0) {
+ removeSoundObjectFromList(so);
+ }
+ so->nextSoundBank = -1;
+ so->delayCounter = -2;
+ }
+ }
+ while (_sssObjectsCount > 0 && _sssObjectsTable[_sssObjectsCount - 1].pcm == 0) {
+ --_sssObjectsCount;
+ }
+}
+
+void Game::sssOp16_resumeSound(SssObject *so) {
+ debug(kDebug_SOUND, "sssOp16_resumeSound so %p flags 0x%x", so, so->flags);
+ if ((so->flags & kFlagPaused) != 0) {
+ SssObject *next = so->nextPtr;
+ SssObject *prev = so->prevPtr;
+ SssPcm *pcm = so->pcm;
+ so->pcm = 0;
+ if (next) {
+ next->prevPtr = prev;
+ }
+ if (prev) {
+ prev->nextPtr = next;
+ } else {
+ assert(so == _sssObjectsList2);
+ _sssObjectsList2 = next;
+ }
+ so->pcm = pcm;
+ so->flags &= ~kFlagPaused;
+ prependSoundObjectToList(so);
+ }
+}
+
+void Game::sssOp17_pauseSound(SssObject *so) {
+ debug(kDebug_SOUND, "sssOp17_pauseSound so %p flags 0x%x", so, so->flags);
+ if ((so->flags & kFlagPaused) == 0) {
+ SssPcm *pcm = so->pcm;
+ SssObject *prev = so->prevPtr;
+ SssObject *next = so->nextPtr;
+ so->pcm = 0;
+ if ((so->flags & kFlagPlaying) != 0) {
+ if (next) {
+ next->prevPtr = prev;
+ }
+ if (prev) {
+ prev->nextPtr = next;
+ } else {
+ assert(so == _sssObjectsList1);
+ _sssObjectsList1 = next;
+ }
+ --_playingSssObjectsCount;
+
+ if (_playingSssObjectsMax > 0) {
+ if (so == _lowPrioritySssObject || (_playingSssObjectsCount < _playingSssObjectsMax && _lowPrioritySssObject)) {
+ _lowPrioritySssObject = findLowPrioritySoundObject();
+ }
+ }
+ } else {
+ if (next) {
+ next->prevPtr = prev;
+ }
+ if (prev) {
+ prev->nextPtr = next;
+ } else {
+ assert(so == _sssObjectsList2);
+ _sssObjectsList2 = next;
+ }
+ }
+ so->pcm = pcm;
+ so->flags = (so->flags & ~kFlagPlaying) | kFlagPaused;
+ prependSoundObjectToList(so);
+ }
+}
+
+void Game::sssOp4_removeSounds(uint32_t flags) {
+ const uint32_t mask = 1 << (flags >> 24);
+ *getSssGroupPtr(_res, 1, flags) &= ~mask;
+ for (SssObject *so = _sssObjectsList1; so; so = so->nextPtr) {
+ if (compare_bits(so->flags1, flags, 0xFFFF0FFF)) {
+ so->codeDataStage3 = 0;
+ if (so->codeDataStage4 == 0) {
+ removeSoundObjectFromList(so);
+ }
+ so->nextSoundBank = -1;
+ so->delayCounter = -2;
+ }
+ }
+ for (SssObject *so = _sssObjectsList2; so; so = so->nextPtr) {
+ if (compare_bits(so->flags1, flags, 0xFFFF0FFF)) {
+ so->codeDataStage3 = 0;
+ if (so->codeDataStage4 == 0) {
+ removeSoundObjectFromList(so);
+ }
+ so->nextSoundBank = -1;
+ so->delayCounter = -2;
+ }
+ }
+ while (_sssObjectsCount > 0 && _sssObjectsTable[_sssObjectsCount - 1].pcm == 0) {
+ --_sssObjectsCount;
+ }
+}
+
+const uint8_t *Game::executeSssCode(SssObject *so, const uint8_t *code, bool tempSssObject) {
+ while (1) {
+ debug(kDebug_SOUND, "executeSssCode() code %d", *code);
+ switch (*code) {
+ case 0:
+ return 0;
+ case 2: // add_sound
+ if (so->delayCounter >= -1) {
+ LvlObject *tmp = _currentSoundLvlObject;
+ _currentSoundLvlObject = so->lvlObject;
+ createSoundObject(READ_LE_UINT16(code + 2), (int8_t)code[1], so->flags0);
+ _currentSoundLvlObject = tmp;
+ }
+ code += 4;
+ if (so->pcm == 0) {
+ return code;
+ }
+ break;
+ case 4: { // remove_sound
+ const uint8_t sampleIndex = code[1];
+ const uint16_t bankIndex = READ_LE_UINT16(code + 2);
+ uint32_t flags = (so->flags0 & 0xFFF0F000);
+ flags |= ((sampleIndex & 0xF) << 16) | (bankIndex & 0xFFF);
+ sssOp4_removeSounds(flags);
+ code += 4;
+ }
+ break;
+ case 5: { // seek_forward
+ const int32_t frame = READ_LE_UINT32(code + 4);
+ if (so->currentPcmFrame < frame) {
+ so->currentPcmFrame = frame;
+ if (so->pcm) {
+ const int16_t *ptr = so->pcm->ptr;
+ if (ptr) {
+ so->currentPcmPtr = ptr + READ_LE_UINT32(code + 8) / sizeof(int16_t);
+ }
+ }
+ }
+ code += 12;
+ }
+ break;
+ case 6: { // repeat_jge
+ --so->repeatCounter;
+ if (so->repeatCounter < 0) {
+ code += 8;
+ } else {
+ const int32_t offset = READ_LE_UINT32(code + 4);
+ code -= offset;
+ }
+ }
+ break;
+ case 8: { // seek_backward_delay
+ const int32_t frame = READ_LE_UINT32(code + 12);
+ if (so->currentPcmFrame > frame) {
+ --so->pauseCounter;
+ if (so->pauseCounter < 0) {
+ code += 16;
+ break;
+ }
+ so->currentPcmFrame = READ_LE_UINT32(code + 8);
+ if (so->pcm) {
+ const int16_t *ptr = so->pcm->ptr;
+ if (ptr) {
+ so->currentPcmPtr = ptr + READ_LE_UINT32(code + 4) / sizeof(int16_t);
+ }
+ }
+ }
+ return code;
+ }
+ break;
+ case 9: { // modulate_panning
+ so->panningModulateCurrent += so->panningModulateDelta;
+ const int panning = (so->panningModulateCurrent + 0x8000) >> 16;
+ if (panning != so->panning) {
+ so->panning = panning;
+ _sssObjectsChanged = true;
+ }
+ --so->panningModulateSteps;
+ if (so->panningModulateSteps >= 0) {
+ return code;
+ }
+ code += 4;
+ }
+ break;
+ case 10: { // modulate_volume
+ if (so->volumeModulateSteps >= 0) {
+ so->volumeModulateCurrent += so->volumeModulateDelta;
+ const int volume = (so->volumeModulateCurrent + 0x8000) >> 16;
+ if (volume != so->volume) {
+ so->volume = volume;
+ _sssObjectsChanged = true;
+ }
+ --so->volumeModulateSteps;
+ if (so->volumeModulateSteps >= 0) {
+ return code;
+ }
+ }
+ code += 4;
+ }
+ break;
+ case 11: { // set_volume
+ if (so->volume != code[1]) {
+ so->volume = code[1];
+ _sssObjectsChanged = true;
+ }
+ code += 4;
+ }
+ break;
+ case 12: { // remove_sounds2
+ uint32_t va = so->flags1 >> 24;
+ uint32_t vd = (so->flags1 >> 20) & 0xF;
+ uint16_t vc = READ_LE_UINT16(code + 2);
+ sssOp12_removeSounds2(vc, vd, va);
+ code += 4;
+ }
+ break;
+ case 13: { // init_volume_modulation
+ const int count = READ_LE_UINT32(code + 4);
+ so->volumeModulateSteps = count - 1;
+ const int16_t value = READ_LE_UINT16(code + 2);
+ if (value == -1) {
+ so->volumeModulateCurrent = so->volume << 16;
+ } else {
+ assert(value >= 0);
+ so->volumeModulateCurrent = value << 16;
+ so->volume = value;
+ _sssObjectsChanged = true;
+ }
+ so->volumeModulateDelta = ((code[1] << 16) - so->volumeModulateCurrent) / count;
+ return code + 8;
+ }
+ break;
+ case 14: { // init_panning_modulation
+ const int count = READ_LE_UINT32(code + 8);
+ so->panningModulateSteps = count - 1;
+ const int16_t value = READ_LE_UINT16(code + 2);
+ if (value == -1) {
+ so->panningModulateCurrent = so->panning << 16;
+ } else {
+ assert(value >= 0);
+ so->panningModulateCurrent = value << 16;
+ so->panning = value;
+ _sssObjectsChanged = true;
+ }
+ so->panningModulateDelta = ((code[1] << 16) - so->panningModulateCurrent) / count;
+ return code + 12;
+ }
+ break;
+ case 16: { // resume_sound
+ if (tempSssObject) {
+ // 'tempSssObject' is allocated on the stack, it must not be added to the linked list
+ warning("Invalid call to .sss opcode 16 with temporary SssObject");
+ return 0;
+ }
+ --so->pauseCounter;
+ if (so->pauseCounter >= 0) {
+ return code;
+ }
+ sssOp16_resumeSound(so);
+ code += 4;
+ if (so->pcm == 0) {
+ return code;
+ }
+ _sssObjectsChanged = true;
+ }
+ break;
+ case 17: { // pause_sound
+ if (tempSssObject) {
+ // 'tempSssObject' is allocated on the stack, it must not be added to the linked list
+ warning("Invalid call to .sss opcode 17 with temporary SssObject");
+ return 0;
+ }
+ sssOp17_pauseSound(so);
+ so->pauseCounter = READ_LE_UINT32(code + 4);
+ return code + 8;
+ }
+ break;
+ case 18: { // decrement_repeat_counter
+ if (so->repeatCounter < 0) {
+ so->repeatCounter = READ_LE_UINT32(code + 4);
+ }
+ code += 8;
+ }
+ break;
+ case 19: { // set_panning
+ if (so->panning != code[1]) {
+ so->panning = code[1];
+ _sssObjectsChanged = true;
+ }
+ code += 4;
+ }
+ break;
+ case 20: { // set_pause_counter
+ so->pauseCounter = READ_LE_UINT16(code + 2);
+ code += 4;
+ }
+ break;
+ case 21: { // decrement_delay_counter
+ --so->delayCounter;
+ if (so->delayCounter >= 0) {
+ return code;
+ }
+ code += 4;
+ }
+ break;
+ case 22: { // set_delay_counter
+ so->delayCounter = READ_LE_UINT32(code + 4);
+ return code + 8;
+ }
+ break;
+ case 23: { // decrement_volume_modulate_steps
+ --so->volumeModulateSteps;
+ if (so->volumeModulateSteps >= 0) {
+ return code;
+ }
+ code += 4;
+ }
+ break;
+ case 24: { // set_volume_modulate_steps
+ so->volumeModulateSteps = READ_LE_UINT32(code + 4);
+ return code + 8;
+ }
+ break;
+ case 25: { // decrement_panning_modulate_steps
+ --so->panningModulateSteps;
+ if (so->panningModulateSteps >= 0) {
+ return code;
+ }
+ code += 4;
+ }
+ break;
+ case 26: { // set_panning_modulate_steps
+ so->panningModulateSteps = READ_LE_UINT32(code + 4);
+ return code + 8;
+ }
+ break;
+ case 27: { // seek_backward
+ const int32_t frame = READ_LE_UINT32(code + 12);
+ if (so->currentPcmFrame > frame) {
+ so->currentPcmFrame = READ_LE_UINT32(code + 8);
+ if (so->pcm) {
+ const int16_t *ptr = so->pcm->ptr;
+ if (ptr) {
+ so->currentPcmPtr = ptr + READ_LE_UINT32(code + 4) / sizeof(int16_t);
+ }
+ }
+ }
+ return code;
+ }
+ break;
+ case 28: { // jump
+ const int32_t offset = READ_LE_UINT32(code + 4);
+ code -= offset;
+ }
+ break;
+ case 29: // end
+ so->pcmFramesCount = 0;
+ return 0;
+ default:
+ error("Invalid .sss opcode %d", *code);
+ break;
+ }
+ }
+ return code;
+}
+
+SssObject *Game::addSoundObject(SssPcm *pcm, int priority, uint32_t flags_a, uint32_t flags_b) {
+ int minIndex = -1;
+ int minPriority = -1;
+ for (int i = 0; i < kMaxSssObjects; ++i) {
+ if (!_sssObjectsTable[i].pcm) {
+ minPriority = 0;
+ minIndex = i;
+ break;
+ }
+ if (_sssObjectsTable[i].currentPriority < minPriority) {
+ minPriority = _sssObjectsTable[i].currentPriority;
+ minIndex = i;
+ }
+ }
+ if (minIndex == -1) {
+ return 0;
+ }
+ assert(minIndex != -1);
+ SssObject *so = &_sssObjectsTable[minIndex];
+ if (so->pcm && minPriority >= priority) {
+ return 0;
+ }
+ if (so->pcm) {
+ removeSoundObjectFromList(so);
+ }
+ so->flags1 = flags_a;
+ so->currentPriority = priority;
+ so->pcm = pcm;
+ so->volume = kDefaultSoundVolume;
+ so->panning = kDefaultSoundPanning;
+ so->stereo = (pcm->flags & 1) != 0;
+ so->nextSoundBank = -1;
+ so->currentPcmFrame = 0;
+ so->flags = 0;
+ so->pcmFramesCount = pcm->strideCount;
+ so->currentPcmPtr = pcm->ptr;
+ if (!so->currentPcmPtr) {
+ so->flags |= kFlagPaused;
+ }
+ so->flags0 = flags_b;
+ prependSoundObjectToList(so);
+ return so->pcm ? so : 0;
+}
+
+void Game::prependSoundObjectToList(SssObject *so) {
+ if (!so->pcm || !so->pcm->ptr) {
+ so->flags = (so->flags & ~kFlagPlaying) | kFlagPaused;
+ }
+ debug(kDebug_SOUND, "prependSoundObjectToList so %p flags 0x%x", so, so->flags);
+ if (so->flags & kFlagPaused) {
+ debug(kDebug_SOUND, "Adding so %p to list2 flags 0x%x", so, so->flags);
+ so->prevPtr = 0;
+ so->nextPtr = _sssObjectsList2;
+ if (_sssObjectsList2) {
+ _sssObjectsList2->prevPtr = so;
+ }
+ _sssObjectsList2 = so;
+ } else {
+ debug(kDebug_SOUND, "Adding so %p to list1 flags 0x%x", so, so->flags);
+ SssObject *stopSo = so;
+ if (so->pcm && so->pcm->ptr) {
+ if (_playingSssObjectsMax > 0 && _playingSssObjectsCount >= _playingSssObjectsMax) {
+ if (so->currentPriority > _lowPrioritySssObject->currentPriority) {
+
+ stopSo = _lowPrioritySssObject;
+ debug(kDebug_SOUND, "Removing so %p priority %d", stopSo, stopSo->priority);
+ SssObject *next = _lowPrioritySssObject->nextPtr;
+ SssObject *prev = _lowPrioritySssObject->prevPtr;
+
+ so->nextPtr = next;
+ so->prevPtr = prev;
+
+ if (next) {
+ next->prevPtr = so;
+ }
+ if (prev) {
+ prev->nextPtr = so;
+ } else {
+ assert(stopSo == _sssObjectsList1);
+ _sssObjectsList1 = so;
+ }
+ _lowPrioritySssObject = findLowPrioritySoundObject();
+ }
+ } else {
+ stopSo = 0;
+
+ ++_playingSssObjectsCount;
+ so->nextPtr = _sssObjectsList1;
+ so->prevPtr = 0;
+ if (_sssObjectsList1) {
+ _sssObjectsList1->prevPtr = so;
+ }
+ _sssObjectsList1 = so;
+ if (_playingSssObjectsMax > 0) {
+ if (_playingSssObjectsCount < _playingSssObjectsMax) {
+ _lowPrioritySssObject = 0;
+ } else if (!_lowPrioritySssObject) {
+ _lowPrioritySssObject = findLowPrioritySoundObject();
+ } else if (so->currentPriority < _lowPrioritySssObject->currentPriority) {
+ _lowPrioritySssObject = so;
+ }
+ }
+ }
+ so->flags |= kFlagPlaying;
+ }
+ if (stopSo) {
+ stopSo->flags &= ~kFlagPlaying;
+ stopSo->pcm = 0;
+ updateSssGroup2(stopSo->flags0);
+ }
+ }
+ if (so->num >= _sssObjectsCount) {
+ _sssObjectsCount = so->num + 1;
+ }
+}
+
+void Game::updateSssGroup2(uint32_t flags) {
+ const uint32_t mask = 1 << (flags >> 24);
+ uint32_t *sssGroupPtr = getSssGroupPtr(_res, 2, flags);
+ if ((*sssGroupPtr & mask) != 0) {
+ for (SssObject *so = _sssObjectsList1; so; so = so->nextPtr) {
+ if (compareSssGroup(so->flags0, flags)) {
+ return;
+ }
+ }
+ for (SssObject *so = _sssObjectsList2; so; so = so->nextPtr) {
+ if (compareSssGroup(so->flags0, flags)) {
+ return;
+ }
+ }
+ *sssGroupPtr &= ~mask;
+ }
+}
+
+SssObject *Game::createSoundObject(int bankIndex, int sampleIndex, uint32_t flags) {
+ debug(kDebug_SOUND, "createSoundObject bank %d sample %d c 0x%x", bankIndex, sampleIndex, flags);
+ SssObject *ret = 0;
+ SssBank *bank = &_res->_sssBanksData[bankIndex];
+ if (sampleIndex < 0) {
+ if ((bank->flags & 1) != 0) {
+ if (bank->count <= 0) {
+ return 0;
+ }
+ const int firstSampleIndex = bank->firstSampleIndex;
+ const SssSample *sample = &_res->_sssSamplesData[firstSampleIndex];
+ int framesCount = 0;
+ for (int i = 0; i < bank->count; ++i) {
+ if (sample->pcm != 0xFFFF) {
+ SssObject *so = startSoundObject(bankIndex, i, flags);
+ if (so && so->pcmFramesCount >= framesCount) {
+ framesCount = so->pcmFramesCount;
+ ret = so;
+ }
+ }
+ ++sample;
+ }
+ } else {
+ uint32_t mask = 1 << (_rnd.update() & 31);
+ SssUnk6 *s6 = &_res->_sssDataUnk6[bankIndex];
+ if ((s6->mask & mask) == 0) {
+ if (mask > s6->mask) {
+ do {
+ mask >>= 1;
+ } while ((s6->mask & mask) == 0);
+ } else {
+ do {
+ mask <<= 1;
+ } while ((s6->mask & mask) == 0);
+ }
+ }
+ int b = 0;
+ for (; b < bank->count; ++b) {
+ if ((s6->unk0[b] & mask) != 0) {
+ break;
+ }
+ }
+ if ((bank->flags & 2) != 0) {
+ s6->mask &= ~s6->unk0[b];
+ if (s6->mask == 0 && bank->count > 0) {
+ for (int i = 0; i < bank->count; ++i) {
+ s6->mask |= s6->unk0[i];
+ }
+ }
+ }
+ ret = startSoundObject(bankIndex, b, flags);
+ }
+ if (ret && (bank->flags & 4) != 0) {
+ ret->nextSoundBank = bankIndex;
+ ret->nextSoundSample = -1;
+ }
+ } else {
+ ret = startSoundObject(bankIndex, sampleIndex, flags);
+ if (ret && (bank->flags & 4) != 0) {
+ ret->nextSoundBank = bankIndex;
+ ret->nextSoundSample = sampleIndex;
+ }
+ }
+ return ret;
+}
+
+SssObject *Game::startSoundObject(int bankIndex, int sampleIndex, uint32_t flags) {
+ debug(kDebug_SOUND, "startSoundObject bank %d sample %d flags 0x%x", bankIndex, sampleIndex, flags);
+
+ SssBank *bank = &_res->_sssBanksData[bankIndex];
+ const int sampleNum = bank->firstSampleIndex + sampleIndex;
+ debug(kDebug_SOUND, "startSoundObject sample %d", sampleNum);
+ const SssSample *sample = &_res->_sssSamplesData[sampleNum];
+ SssPcm *pcm = &_res->_sssPcmTable[sample->pcm];
+
+ if (sample->framesCount != 0) {
+ SssFilter *filter = &_res->_sssFilters[bank->sssFilter];
+ const int priority = CLIP(filter->priorityCurrent + sample->initPriority, 0, 7);
+ uint32_t flags1 = flags & 0xFFF0F000;
+ flags1 |= ((sampleIndex & 0xF) << 16) | (bankIndex & 0xFFF);
+ SssObject *so = addSoundObject(pcm, priority, flags1, flags);
+ if (so) {
+ if (sample->codeOffset1 == kNone && sample->codeOffset2 == kNone && sample->codeOffset3 == kNone && sample->codeOffset4 == kNone) {
+ so->flags |= kFlagNoCode;
+ }
+ so->codeDataStage1 = (sample->codeOffset1 == kNone) ? 0 : _res->_sssCodeData + sample->codeOffset1;
+ so->codeDataStage2 = (sample->codeOffset2 == kNone) ? 0 : _res->_sssCodeData + sample->codeOffset2;
+ so->codeDataStage3 = (sample->codeOffset3 == kNone) ? 0 : _res->_sssCodeData + sample->codeOffset3;
+ so->codeDataStage4 = (sample->codeOffset4 == kNone) ? 0 : _res->_sssCodeData + sample->codeOffset4;
+ so->lvlObject = _currentSoundLvlObject;
+ so->repeatCounter = -1;
+ so->pauseCounter = -1;
+ so->delayCounter = -1;
+ so->volumeModulateSteps = -100;
+ so->panningModulateSteps = -1;
+ so->volumeModulateDelta = 0;
+ so->panningModulateDelta = 0;
+ so->flags0 = flags;
+ so->pcmFramesCount = sample->framesCount;
+ so->bankIndex = bankIndex;
+ so->priority = sample->initPriority;
+ so->filter = filter;
+ so->volume = sample->initVolume;
+ so->panning = sample->initPanning;
+ if (sample->initPanning == -1) {
+ if (_currentSoundLvlObject) {
+ _currentSoundLvlObject->sssObject = so;
+ so->panningPtr = &_snd_masterPanning;
+ so->panning = getSoundObjectPanning(so);
+ } else {
+ so->panningPtr = 0;
+ so->panning = kDefaultSoundPanning;
+ }
+ } else {
+ so->panningPtr = 0;
+ }
+ setSoundObjectPanning(so);
+ if (so->pcm) {
+ updateSoundObject(so);
+ return so;
+ }
+ }
+ return 0;
+ }
+
+ SssObject tmpObj;
+ memset(&tmpObj, 0, sizeof(tmpObj));
+ tmpObj.flags0 = flags;
+ tmpObj.flags1 = flags;
+ tmpObj.bankIndex = bankIndex;
+ tmpObj.repeatCounter = -1;
+ tmpObj.pauseCounter = -1;
+ tmpObj.lvlObject = _currentSoundLvlObject;
+ tmpObj.panningPtr = 0;
+ debug(kDebug_SOUND, "startSoundObject dpcm %d", sample->pcm);
+ tmpObj.pcm = pcm;
+ if (sample->codeOffset1 != kNone) {
+ const uint8_t *code = _res->_sssCodeData + sample->codeOffset1;
+ executeSssCode(&tmpObj, code, true);
+ }
+ updateSssGroup2(flags);
+ return 0;
+}
+
+SssObject *Game::playSoundObject(SssInfo *s, int source, int mask) {
+ debug(kDebug_SOUND, "playSoundObject num %d lut 0x%x mask 0x%x", s->sssBankIndex, source, mask);
+ if (_sssDisabled) {
+ return 0;
+ }
+ const int filterIndex = _res->_sssBanksData[s->sssBankIndex].sssFilter;
+ SssFilter *filter = &_res->_sssFilters[filterIndex];
+ bool found = false;
+ for (int i = 0; i < _sssObjectsCount; ++i) {
+ SssObject *so = &_sssObjectsTable[i];
+ if (so->pcm != 0 && so->filter == filter) {
+ found = true;
+ break;
+ }
+ }
+ int va, vc;
+ va = s->targetVolume << 16;
+ vc = filter->volumeCurrent;
+ if (vc != va) {
+ if (!found) {
+ filter->volumeCurrent = va;
+ } else {
+ filter->volumeSteps = 4;
+ filter->changed = true;
+ filter->volumeDelta = (va - vc) / 4;
+ }
+ }
+ va = s->targetPanning << 16;
+ vc = filter->panningCurrent;
+ if (vc != va) {
+ if (!found) {
+ filter->panningCurrent = va;
+ } else {
+ filter->panningSteps = 4;
+ filter->changed = true;
+ filter->panningDelta = (va - vc) / 4;
+ }
+ }
+ va = s->targetPriority;
+ vc = filter->priorityCurrent;
+ if (vc != va) {
+ filter->priorityCurrent = va;
+ for (int i = 0; i < _sssObjectsCount; ++i) {
+ SssObject *so = &_sssObjectsTable[i];
+ if (so->pcm != 0 && so->filter == filter) {
+ so->currentPriority = CLIP(va + so->priority, 0, 7);
+ if (_playingSssObjectsMax > 0) {
+ setLowPrioritySoundObject(so);
+ }
+ }
+ }
+ }
+ const uint32_t ve = sssGroupValue(source, mask, s);
+ const uint8_t _al = s->concurrencyMask;
+ if (_al & 2) {
+ const uint32_t mask = 1 << (ve >> 24);
+ uint32_t *sssGroupPtr3 = getSssGroupPtr(_res, 3, ve);
+ *sssGroupPtr3 |= mask;
+ uint32_t *sssGroupPtr2 = getSssGroupPtr(_res, 2, ve);
+ if (*sssGroupPtr2 & mask) {
+ return 0;
+ }
+ *sssGroupPtr2 |= mask;
+ } else if (_al & 1) {
+ const uint32_t mask = 1 << (ve >> 24);
+ uint32_t *sssGroupPtr1 = getSssGroupPtr(_res, 1, ve);
+ if (*sssGroupPtr1 & mask) {
+ return 0;
+ }
+ *sssGroupPtr1 |= mask;
+ } else if (_al & 4) {
+ for (SssObject *so = _sssObjectsList1; so; so = so->nextPtr) {
+ if (compareSssGroup(so->flags0, ve)) {
+ return 0;
+ }
+ }
+ for (SssObject *so = _sssObjectsList2; so; so = so->nextPtr) {
+ if (compareSssGroup(so->flags0, ve)) {
+ return 0;
+ }
+ }
+ }
+ return createSoundObject(s->sssBankIndex, s->sampleIndex, ve);
+}
+
+void Game::clearSoundObjects() {
+ memset(_sssObjectsTable, 0, sizeof(_sssObjectsTable));
+ _sssObjectsList1 = 0;
+ _sssObjectsList2 = 0;
+ _lowPrioritySssObject = 0;
+ for (int i = 0; i < kMaxSssObjects; ++i) {
+ _sssObjectsTable[i].num = i;
+ }
+ _sssObjectsCount = 0;
+ _playingSssObjectsCount = 0;
+ _snd_bufferOffset = _snd_bufferSize = 0;
+ _mix._mixingQueueSize = 0;
+ if (_res->_sssHdr.infosDataCount != 0) {
+ const int size = _res->_sssHdr.banksDataCount * sizeof(uint32_t);
+ for (int i = 0; i < 3; ++i) {
+ memset(_res->_sssGroup1[i], 0, size);
+ memset(_res->_sssGroup2[i], 0, size);
+ memset(_res->_sssGroup3[i], 0, size);
+ }
+ }
+ _res->resetSssFilters();
+}
+
+void Game::setLowPrioritySoundObject(SssObject *so) {
+ if ((so->flags & kFlagPaused) == 0) {
+ _lowPrioritySssObject = findLowPrioritySoundObject();
+ }
+}
+
+int Game::getSoundObjectPanning(SssObject *so) const {
+ const LvlObject *obj = so->lvlObject;
+ if (obj) {
+ switch (obj->type) {
+ case 8:
+ case 2:
+ case 0:
+ if (obj->screenNum == _currentLeftScreen) {
+ return -1;
+ }
+ if (obj->screenNum == _currentRightScreen) {
+ return 129;
+ }
+ if (obj->screenNum == _currentScreen || (_currentLevel == kLvl_lar2 && obj->spriteNum == 27) || (_currentLevel == kLvl_isld && obj->spriteNum == 26)) {
+ const int dist = (obj->xPos + obj->width / 2) / 2;
+ return CLIP(dist, 0, 128);
+ }
+ // fall-through
+ default:
+ return -2;
+ }
+ }
+ return so->panning;
+}
+
+void Game::setSoundObjectPanning(SssObject *so) {
+ if ((so->flags & kFlagPaused) == 0 && so->volume != 0 && _snd_masterVolume != 0) {
+ int volume = ((so->filter->volumeCurrent >> 16) * so->volume) >> 7;
+ int panning = 0;
+ if (so->panningPtr) {
+ int priority = CLIP(so->priority + so->filter->priorityCurrent, 0, 7);
+ if (so->panning == -2) {
+ volume = 0;
+ panning = kDefaultSoundPanning;
+ priority = 0;
+ } else if (so->panning < 0) {
+ panning = 0;
+ volume >>= 2;
+ priority /= 2;
+ } else if (so->panning > 128) {
+ panning = 128;
+ volume >>= 2;
+ priority /= 2;
+ }
+ if (so->currentPriority != priority) {
+ so->currentPriority = priority;
+ if (_playingSssObjectsMax > 0) {
+ _lowPrioritySssObject = findLowPrioritySoundObject();
+ }
+ }
+ } else {
+ panning = CLIP(so->panning + (so->filter->panningCurrent >> 16), 0, 128);
+ }
+ if (so->pcm == 0) {
+ return;
+ }
+ if ((uint32_t)volume >= ARRAYSIZE(_volumeRampTable)) {
+ warning("Out of bounds volume %d (filter %d volume %d)", volume, (so->filter->volumeCurrent >> 16), so->volume);
+ so->panL = 0;
+ so->panR = 0;
+ so->panType = 0;
+ return;
+ }
+ int vd = _volumeRampTable[volume]; // 0..128
+ int va = vd << 7; // 0..16384
+ switch (panning) {
+ case 0: // full left
+ so->panL = va;
+ so->panR = 0;
+ so->panType = 2;
+ break;
+ case 64: // center
+ va /= 2;
+ so->panL = va;
+ so->panR = va;
+ so->panType = 3;
+ break;
+ case 128: // full right
+ so->panL = 0;
+ so->panR = va;
+ so->panType = 1;
+ break;
+ default:
+ vd *= panning;
+ so->panR = vd;
+ so->panL = va - vd;
+ so->panType = 4;
+ break;
+ }
+ so->panR = (so->panR * _snd_masterVolume + 64) >> 7;
+ so->panL = (so->panL * _snd_masterVolume + 64) >> 7;
+ } else {
+ so->panL = 0;
+ so->panR = 0;
+ so->panType = 0;
+ }
+}
+
+void Game::expireSoundObjects(uint32_t flags) {
+ const uint32_t mask = 1 << (flags >> 24);
+ *getSssGroupPtr(_res, 1, flags) &= ~mask;
+ *getSssGroupPtr(_res, 2, flags) &= ~mask;
+ for (SssObject *so = _sssObjectsList1; so; so = so->nextPtr) {
+ if (compare_bits(so->flags0, flags, 0xFFFF0FFF)) {
+ so->codeDataStage3 = 0;
+ if (so->codeDataStage4 == 0) {
+ removeSoundObjectFromList(so);
+ }
+ so->nextSoundBank = -1;
+ so->delayCounter = -2;
+ }
+ }
+ for (SssObject *so = _sssObjectsList2; so; so = so->nextPtr) {
+ if (compare_bits(so->flags0, flags, 0xFFFF0FFF)) {
+ so->codeDataStage3 = 0;
+ if (so->codeDataStage4 == 0) {
+ removeSoundObjectFromList(so);
+ }
+ so->nextSoundBank = -1;
+ so->delayCounter = -2;
+ }
+ }
+ while (_sssObjectsCount > 0 && _sssObjectsTable[_sssObjectsCount - 1].pcm == 0) {
+ --_sssObjectsCount;
+ }
+}
+
+void Game::mixSoundObjects17640(bool flag) {
+ for (int i = 0; i < _res->_sssHdr.filtersDataCount; ++i) {
+ SssFilter *filter = &_res->_sssFilters[i];
+ filter->changed = false;
+ if (filter->volumeSteps != 0) {
+ --filter->volumeSteps;
+ filter->volumeCurrent += filter->volumeDelta;
+ filter->changed = true;
+ }
+
+ if (filter->panningSteps != 0) {
+ --filter->panningSteps;
+ filter->panningCurrent += filter->panningDelta;
+ filter->changed = true;
+ }
+ }
+ for (int i = 0; i < _sssObjectsCount; ++i) {
+ SssObject *so = &_sssObjectsTable[i];
+ if (so->pcm && !_sssUpdatedObjectsTable[i]) {
+ if (flag) {
+ const uint32_t flags = so->flags0;
+ const uint32_t mask = 1 << (flags >> 24);
+ if ((*getSssGroupPtr(_res, 2, flags) & mask) != 0 && (*getSssGroupPtr(_res, 3, flags) & mask) == 0) {
+ expireSoundObjects(flags);
+ }
+ }
+ if (so->pcm) {
+ updateSoundObject(so);
+ }
+ }
+ }
+ memset(_sssUpdatedObjectsTable, 0, sizeof(_sssUpdatedObjectsTable));
+ if (flag) {
+ _res->clearSssGroup3();
+ }
+ queueSoundObjectsPcmStride();
+}
+
+void Game::queueSoundObjectsPcmStride() {
+ _mix._mixingQueueSize = 0;
+ for (SssObject *so = _sssObjectsList1; so; so = so->nextPtr) {
+ const SssPcm *pcm = so->pcm;
+ if (pcm != 0) {
+ const int16_t *ptr = pcm->ptr;
+ if (!ptr) {
+ continue;
+ }
+ if (so->currentPcmPtr < ptr) {
+ continue;
+ }
+ const uint32_t pcmSize = pcm->pcmSize / sizeof(int16_t);
+ const int16_t *end = ptr + pcmSize;
+ if (so->currentPcmPtr >= end) {
+ continue;
+ }
+ if ((so->panL == 0 && so->panR == 0) || so->panType == 0) {
+ continue;
+ }
+ _mix.queue(so->currentPcmPtr, end, so->panType, so->panL, so->panR, so->stereo);
+ const int strideSize = pcmSize / pcm->strideCount;
+ assert(strideSize == 1764 || strideSize == 3528 || strideSize == 1792); // words
+ so->currentPcmPtr += strideSize;
+ }
+ }
+}
diff --git a/SRC/staticres.cpp b/Src/Global/staticres.cpp
similarity index 99%
rename from SRC/staticres.cpp
rename to Src/Global/staticres.cpp
index 2eaa097..a3190fb 100644
--- a/SRC/staticres.cpp
+++ b/Src/Global/staticres.cpp
@@ -1,3 +1,7 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
#include "game.h"
#include "video.h"
diff --git a/Src/Global/system.h b/Src/Global/system.h
new file mode 100644
index 0000000..6a68dcb
--- /dev/null
+++ b/Src/Global/system.h
@@ -0,0 +1,76 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef SYSTEM_H__
+#define SYSTEM_H__
+
+#include "intern.h"
+
+#define SYS_INP_UP (1 << 0)
+#define SYS_INP_RIGHT (1 << 1)
+#define SYS_INP_DOWN (1 << 2)
+#define SYS_INP_LEFT (1 << 3)
+#define SYS_INP_RUN (1 << 4) /* (1 << 0) */
+#define SYS_INP_JUMP (1 << 5) /* (1 << 1) */
+#define SYS_INP_SHOOT (1 << 6) /* (1 << 2) */
+#define SYS_INP_ESC (1 << 7)
+
+struct PlayerInput {
+ uint8_t prevMask, mask;
+ bool quit;
+ bool screenshot;
+
+ bool keyPressed(int keyMask) const {
+ return (prevMask & keyMask) == 0 && (mask & keyMask) == keyMask;
+ }
+ bool keyReleased(int keyMask) const {
+ return (prevMask & keyMask) == keyMask && (mask & keyMask) == 0;
+ }
+};
+
+struct AudioCallback {
+ void (*proc)(void *param, int16_t *stream, int len); // 22khz
+ void *userdata;
+};
+
+struct System {
+ PlayerInput inp, pad;
+
+ virtual ~System() {}
+
+ virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv) = 0;
+ virtual void destroy() = 0;
+
+ virtual void setScaler(const char *name, int multiplier) = 0;
+ virtual void setGamma(float gamma) = 0;
+
+ virtual void setPalette(const uint8_t *pal, int n, int depth) = 0;
+ virtual void clearPalette() = 0;
+ virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) = 0;
+ virtual void copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch) = 0;
+ virtual void fillRect(int x, int y, int w, int h, uint8_t color) = 0;
+ virtual void copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal) = 0;
+ virtual void shakeScreen(int dx, int dy) = 0;
+ virtual void updateScreen(bool drawWidescreen) = 0;
+
+ virtual void processEvents() = 0;
+ virtual void sleep(int duration) = 0;
+ virtual uint32_t getTimeStamp() = 0;
+
+ virtual void startAudio(AudioCallback callback) = 0;
+ virtual void stopAudio() = 0;
+ virtual void lockAudio() = 0;
+ virtual void unlockAudio() = 0;
+ virtual AudioCallback setAudioCallback(AudioCallback callback) = 0;
+};
+
+extern void System_earlyInit();
+extern void System_printLog(FILE *, const char *s);
+extern void System_fatalError(const char *s);
+extern bool System_hasCommandLine();
+
+extern System *const g_system;
+
+#endif // SYSTEM_H__
diff --git a/Src/Global/system_psp.cpp b/Src/Global/system_psp.cpp
new file mode 100644
index 0000000..d91db9f
--- /dev/null
+++ b/Src/Global/system_psp.cpp
@@ -0,0 +1,429 @@
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "system.h"
+
+struct System_PSP : System {
+
+ uint32_t _buttons;
+ int _shakeDx, _shakeDy;
+ bool _stretchScreen;
+ AudioCallback _audioCb;
+ int _audioChannel;
+ SceUID _audioMutex;
+ uint32_t _vramOffset;
+
+ System_PSP();
+ virtual ~System_PSP();
+ virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv);
+ virtual void destroy();
+ virtual void setScaler(const char *name, int multiplier);
+ virtual void setGamma(float gamma);
+ virtual void setPalette(const uint8_t *pal, int n, int depth);
+ virtual void clearPalette();
+ virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch);
+ virtual void copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch);
+ virtual void fillRect(int x, int y, int w, int h, uint8_t color);
+ virtual void copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal);
+ virtual void shakeScreen(int dx, int dy);
+ virtual void updateScreen(bool drawWidescreen);
+ virtual void processEvents();
+ virtual void sleep(int duration);
+ virtual uint32_t getTimeStamp();
+ virtual void startAudio(AudioCallback callback);
+ virtual void stopAudio();
+ virtual void lockAudio();
+ virtual void unlockAudio();
+ virtual AudioCallback setAudioCallback(AudioCallback callback);
+};
+
+static System_PSP system_psp;
+System *const g_system = &system_psp;
+
+// static const int AUDIO_FREQ = 44100;
+static const int AUDIO_SAMPLES_COUNT = 2048;
+
+static const int SCREEN_W = 480;
+static const int SCREEN_H = 272;
+static const int SCREEN_PITCH = 512;
+
+static const int GAME_W = 256;
+static const int GAME_H = 192;
+
+static const int BLUR_TEX_W = 16;
+static const int BLUR_TEX_H = 16;
+
+static uint32_t WHITE_COLOR = GU_RGBA(255, 255, 255, 255);
+
+static uint32_t __attribute__((aligned(16))) _dlist[1024];
+static uint32_t __attribute__((aligned(16))) _clut[256];
+static uint8_t __attribute__((aligned(16))) _texture[256 * 256];
+static uint32_t __attribute__((aligned(16))) _clut2[256];
+static uint8_t __attribute__((aligned(16))) _texture2[256 * 256];
+
+PSP_MODULE_INFO("Heart of Darkness", 0, 2, 9);
+PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER);
+
+struct Vertex {
+ uint16_t u, v;
+ int16_t x, y, z;
+};
+
+static void initVertex2D(Vertex *vertices, int w, int h, int targetW, int targetH) {
+ vertices[0].u = 0; vertices[1].u = w;
+ vertices[0].v = 0; vertices[1].v = h;
+ vertices[0].x = 0; vertices[1].x = targetW;
+ vertices[0].y = 0; vertices[1].y = targetH;
+ vertices[0].z = 0; vertices[1].z = 0;
+}
+
+void System_printLog(FILE *fp, const char *s) {
+ if (fp == stderr) {
+ static bool firstOpen = false;
+ if (!firstOpen) {
+ fp = fopen("stderr.txt", "w");
+ firstOpen = true;
+ } else {
+ fp = fopen("stderr.txt", "a");
+ }
+ } else if (fp == stdout) {
+ static bool firstOpen = false;
+ if (!firstOpen) {
+ fp = fopen("stdout.txt", "w");
+ firstOpen = true;
+ } else {
+ fp = fopen("stdout.txt", "a");
+ }
+ } else {
+ return;
+ }
+ fprintf(fp, "%s\n", s);
+ fclose(fp);
+}
+
+void System_fatalError(const char *s) {
+ sceKernelExitGame();
+}
+
+bool System_hasCommandLine() {
+ return false;
+}
+
+static int exitCallback(int arg1, int arg2, void *common) {
+ g_system->inp.quit = true;
+ return 0;
+}
+
+static int callbackThread(SceSize args, void *argp) {
+ const int cb = sceKernelCreateCallback("Exit Callback", exitCallback, NULL);
+ sceKernelRegisterExitCallback(cb);
+ sceKernelSleepThreadCB();
+ return 0;
+}
+
+System_PSP::System_PSP() {
+}
+
+System_PSP::~System_PSP() {
+}
+
+void System_PSP::init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv) {
+
+ memset(&inp, 0, sizeof(inp));
+ memset(&pad, 0, sizeof(pad));
+ _buttons = 0;
+ _shakeDx = _shakeDy = 0;
+ _stretchScreen = false; // keep 4:3 AR
+
+ memset(&_audioCb, 0, sizeof(_audioCb));
+ _audioChannel = -1;
+ _audioMutex = 0;
+
+ const int th = sceKernelCreateThread("update_thread", callbackThread, 0x11, 0xFA0, 0, 0);
+ if (th >= 0) {
+ sceKernelStartThread(th, 0, 0);
+ }
+
+ sceKernelDcacheWritebackAll();
+
+ sceCtrlSetSamplingCycle(0);
+ sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);
+
+ sceGuInit();
+ sceGuStart(GU_DIRECT, _dlist);
+
+ const int fbSize = SCREEN_PITCH * SCREEN_H * sizeof(uint32_t); // rgba
+ const int zbSize = SCREEN_PITCH * SCREEN_H * sizeof(uint16_t); // 16 bits
+ _vramOffset = 0;
+ sceGuDrawBuffer(GU_PSM_8888, (void *)_vramOffset, SCREEN_PITCH); _vramOffset += fbSize;
+ sceGuDispBuffer(SCREEN_W, SCREEN_H, (void *)_vramOffset, SCREEN_PITCH); _vramOffset += fbSize;
+ sceGuDepthBuffer((void *)_vramOffset, SCREEN_PITCH); _vramOffset += zbSize;
+
+ sceGuOffset(2048 - (SCREEN_W / 2), 2048 - (SCREEN_H / 2));
+ sceGuViewport(2048, 2048, SCREEN_W, SCREEN_H);
+ sceGuScissor(0, 0, SCREEN_W, SCREEN_H);
+ sceGuEnable(GU_SCISSOR_TEST);
+ sceGuEnable(GU_TEXTURE_2D);
+ sceGuClear(GU_COLOR_BUFFER_BIT | GU_DEPTH_BUFFER_BIT);
+
+ sceGuFinish();
+ sceGuSync(GU_SYNC_WHAT_DONE, GU_SYNC_FINISH);
+
+ sceDisplayWaitVblankStart();
+ sceGuDisplay(GU_TRUE);
+}
+
+void System_PSP::destroy() {
+ sceGuTerm();
+ sceKernelExitGame();
+}
+
+void System_PSP::setScaler(const char *name, int multiplier) {
+}
+
+void System_PSP::setGamma(float gamma) {
+}
+
+void System_PSP::setPalette(const uint8_t *pal, int n, int depth) {
+ const int shift = 8 - depth;
+ for (int i = 0; i < n; ++i) {
+ int r = pal[i * 3];
+ int g = pal[i * 3 + 1];
+ int b = pal[i * 3 + 2];
+ if (shift != 0) {
+ r = (r << shift) | (r >> depth);
+ g = (g << shift) | (g >> depth);
+ b = (b << shift) | (b >> depth);
+ }
+ _clut[i] = GU_RGBA(r, g, b, 255);
+ }
+ sceKernelDcacheWritebackRange(_clut, sizeof(_clut));
+}
+
+void System_PSP::clearPalette() {
+ for (int i = 0; i < 256; ++i) {
+ _clut[i] = GU_RGBA(0, 0, 0, 255);
+ }
+ sceKernelDcacheWritebackRange(_clut, sizeof(_clut));
+}
+
+void System_PSP::copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) {
+ uint8_t *p = _texture + y * GAME_W + x;
+ if (w == GAME_W && w == pitch) {
+ memcpy(p, buf, w * h);
+ } else {
+ for (int i = 0; i < h; ++i) {
+ memcpy(p, buf, w);
+ p += GAME_W;
+ buf += pitch;
+ }
+ }
+ sceKernelDcacheWritebackRange(_texture, sizeof(_texture));
+}
+
+void System_PSP::copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch) {
+}
+
+void System_PSP::fillRect(int x, int y, int w, int h, uint8_t color) {
+ uint8_t *p = _texture + y * GAME_W + x;
+ if (w == GAME_W) {
+ memset(p, color, w * h);
+ } else {
+ for (int i = 0; i < h; ++i) {
+ memset(p, color, w);
+ p += GAME_W;
+ }
+ }
+ sceKernelDcacheWritebackRange(_texture, sizeof(_texture));
+}
+
+void System_PSP::copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal) {
+ for (int i = 0; i < 256; ++i) {
+ _clut2[i] = GU_RGBA(pal[i * 3], pal[i * 3 + 1], pal[i * 3 + 2], 255);
+ }
+ sceKernelDcacheWritebackRange(_clut2, sizeof(_clut2));
+ memcpy(_texture2, buf, w * h);
+ sceKernelDcacheWritebackRange(_texture2, sizeof(_texture2));
+
+ sceGuStart(GU_DIRECT, _dlist);
+
+ sceGuDrawBufferList(GU_PSM_8888, (void *)_vramOffset, BLUR_TEX_W);
+ sceGuOffset(2048 - (BLUR_TEX_W / 2), 2048 - (BLUR_TEX_H / 2));
+ sceGuViewport(2048, 2048, BLUR_TEX_W, BLUR_TEX_H);
+ sceGuClearColor(WHITE_COLOR);
+ sceGuClear(GU_COLOR_BUFFER_BIT);
+
+ sceGuClutMode(GU_PSM_8888, 0, 0xFF, 0);
+ sceGuClutLoad(256 / 8, _clut2);
+ sceGuTexMode(GU_PSM_T8, 0, 0, 0);
+ sceGuTexImage(0, 256, 256, 256, _texture2);
+ sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB);
+ sceGuTexFilter(GU_LINEAR, GU_LINEAR);
+
+ Vertex *vertices = (Vertex *)sceGuGetMemory(2 * sizeof(struct Vertex));
+ initVertex2D(vertices, GAME_W, GAME_H, BLUR_TEX_W, BLUR_TEX_H);
+ sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, 0, vertices);
+
+ sceGuFinish();
+}
+
+void System_PSP::shakeScreen(int dx, int dy) {
+ _shakeDx = dx;
+ _shakeDy = dy;
+}
+
+void System_PSP::updateScreen(bool drawWidescreen) {
+
+ sceGuStart(GU_DIRECT, _dlist);
+
+ sceGuClearColor(0);
+ sceGuClear(GU_COLOR_BUFFER_BIT);
+
+ if (!_stretchScreen && drawWidescreen) {
+
+ sceGuTexMode(GU_PSM_8888, 0, 0, 0);
+ sceGuTexImage(0, BLUR_TEX_W, BLUR_TEX_H, BLUR_TEX_W, (uint8_t *)sceGeEdramGetAddr() + _vramOffset);
+ sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB);
+ sceGuTexFilter(GU_LINEAR, GU_LINEAR);
+
+ Vertex *vertices = (Vertex *)sceGuGetMemory(2 * sizeof(Vertex));
+ initVertex2D(vertices, BLUR_TEX_W, BLUR_TEX_H, SCREEN_W, SCREEN_H);
+ sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, 0, vertices);
+ }
+
+ sceGuClutMode(GU_PSM_8888, 0, 0xFF, 0);
+ sceGuClutLoad(256 / 8, _clut);
+ sceGuTexMode(GU_PSM_T8, 0, 0, 0);
+ sceGuTexImage(0, 256, 256, 256, _texture);
+ sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB);
+ sceGuTexFilter(GU_LINEAR, GU_LINEAR);
+
+ Vertex *vertices = (Vertex *)sceGuGetMemory(2 * sizeof(Vertex));
+ initVertex2D(vertices, GAME_W, GAME_H, SCREEN_W, SCREEN_H);
+ vertices[0].x += _shakeDx;
+ vertices[1].x += _shakeDx;
+ vertices[0].y += _shakeDy;
+ vertices[1].y += _shakeDy;
+ if (!_stretchScreen) {
+ const int w = (SCREEN_H * GAME_W / GAME_H);
+ const int dx = (SCREEN_W - w) / 2;
+ vertices[0].x += dx;
+ vertices[1].x -= dx;
+ }
+ sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, 0, vertices);
+
+ sceGuFinish();
+ sceGuSync(GU_SYNC_WHAT_DONE, GU_SYNC_FINISH);
+
+ sceDisplayWaitVblankStart();
+ sceGuSwapBuffers();
+}
+
+void System_PSP::processEvents() {
+ inp.prevMask = inp.mask;
+ inp.mask = 0;
+
+ static const struct {
+ int psp;
+ int sys;
+ } mapping[] = {
+ { PSP_CTRL_UP, SYS_INP_UP },
+ { PSP_CTRL_RIGHT, SYS_INP_RIGHT },
+ { PSP_CTRL_DOWN, SYS_INP_DOWN },
+ { PSP_CTRL_LEFT, SYS_INP_LEFT },
+ { PSP_CTRL_CROSS, SYS_INP_JUMP },
+ { PSP_CTRL_SQUARE, SYS_INP_RUN },
+ { PSP_CTRL_CIRCLE, SYS_INP_SHOOT },
+ { PSP_CTRL_TRIANGLE, SYS_INP_SHOOT | SYS_INP_RUN },
+ { PSP_CTRL_START, SYS_INP_ESC },
+ { 0, 0 }
+ };
+ SceCtrlData data;
+ sceCtrlPeekBufferPositive(&data, 1);
+ for (int i = 0; mapping[i].psp != 0; ++i) {
+ if (data.Buttons & mapping[i].psp) {
+ inp.mask |= mapping[i].sys;
+ }
+ }
+ static const int lxMargin = 64;
+ if (data.Lx < 127 - lxMargin) {
+ inp.mask |= SYS_INP_LEFT;
+ } else if (data.Lx > 127 + lxMargin) {
+ inp.mask |= SYS_INP_RIGHT;
+ }
+ static const int lyMargin = 64;
+ if (data.Ly < 127 - lyMargin) {
+ inp.mask |= SYS_INP_UP;
+ } else if (data.Ly > 127 + lyMargin) {
+ inp.mask |= SYS_INP_DOWN;
+ }
+
+ const uint32_t mask = data.Buttons ^ _buttons;
+ if ((data.Buttons & PSP_CTRL_LTRIGGER) & mask) {
+ _stretchScreen = !_stretchScreen;
+ }
+ if ((data.Buttons & PSP_CTRL_RTRIGGER) & mask) {
+ }
+ _buttons = data.Buttons;
+}
+
+void System_PSP::sleep(int duration) {
+ sceKernelDelayThread(duration * 1000);
+}
+
+uint32_t System_PSP::getTimeStamp() {
+ struct timeval tv;
+ sceKernelLibcGettimeofday(&tv, 0);
+ return tv.tv_sec * 1000 + tv.tv_usec / 1000;
+}
+
+static void audioCallback(void *buf, unsigned int samples, void *userdata) { // 44100hz S16 stereo
+ int16_t buf22khz[samples];
+ memset(buf22khz, 0, sizeof(buf22khz));
+ system_psp.lockAudio();
+ (system_psp._audioCb.proc)(system_psp._audioCb.userdata, buf22khz, samples);
+ system_psp.unlockAudio();
+ uint32_t *buf44khz = (uint32_t *)buf;
+ static int16_t prev;
+ for (unsigned int i = 0; i < samples; ++i) {
+ const int16_t current = buf22khz[i];
+ buf44khz[i] = (current << 16) | (((prev + current) >> 1) & 0xFFFF);
+ prev = current;
+ }
+}
+
+void System_PSP::startAudio(AudioCallback callback) {
+ // sceAudioSetFrequency(AUDIO_FREQ);
+ pspAudioInit();
+ _audioCb = callback;
+ _audioMutex = sceKernelCreateSema("audio_lock", 0, 1, 1, 0);
+ _audioChannel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, AUDIO_SAMPLES_COUNT, PSP_AUDIO_FORMAT_STEREO);
+ pspAudioSetChannelCallback(_audioChannel, audioCallback, 0);
+}
+
+void System_PSP::stopAudio() {
+ sceAudioChRelease(_audioChannel);
+ sceKernelDeleteSema(_audioMutex);
+ pspAudioEnd();
+}
+
+void System_PSP::lockAudio() {
+ sceKernelWaitSema(_audioMutex, 1, 0);
+}
+
+void System_PSP::unlockAudio() {
+ sceKernelSignalSema(_audioMutex, 1);
+}
+
+AudioCallback System_PSP::setAudioCallback(AudioCallback callback) {
+ AudioCallback cb = _audioCb;
+ lockAudio();
+ _audioCb = callback;
+ unlockAudio();
+ return cb;
+}
diff --git a/Src/Global/system_sdl2.cpp b/Src/Global/system_sdl2.cpp
new file mode 100644
index 0000000..5a10e9a
--- /dev/null
+++ b/Src/Global/system_sdl2.cpp
@@ -0,0 +1,829 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include
+#include
+#include
+#include "scaler.h"
+#include "system.h"
+#include "util.h"
+
+static const char *kIconBmp = "icon.bmp";
+
+static int _scalerMultiplier = 3;
+static const Scaler *_scaler = &scaler_xbr;
+
+static const struct {
+ const char *name;
+ const Scaler *scaler;
+} _scalers[] = {
+ { "nearest", &scaler_nearest },
+ { "xbr", &scaler_xbr },
+ { 0, 0 }
+};
+
+struct KeyMapping {
+ int keyCode;
+ int mask;
+};
+
+struct System_SDL2 : System {
+ enum {
+ kJoystickCommitValue = 3200,
+ kKeyMappingsSize = 20,
+ kAudioHz = 22050
+ };
+
+ uint8_t *_offscreenLut;
+ uint32_t *_offscreenRgb;
+ SDL_Window *_window;
+ SDL_Renderer *_renderer;
+ SDL_Texture *_texture;
+ SDL_Texture *_backgroundTexture; // YUV (PSX)
+ int _texW, _texH;
+ SDL_PixelFormat *_fmt;
+ uint32_t _pal[256];
+ int _screenW, _screenH;
+ int _shakeDx, _shakeDy;
+ SDL_Texture *_widescreenTexture;
+ KeyMapping _keyMappings[kKeyMappingsSize];
+ int _keyMappingsCount;
+ AudioCallback _audioCb;
+ uint8_t _gammaLut[256];
+ SDL_GameController *_controller;
+ SDL_Joystick *_joystick;
+
+ System_SDL2();
+ virtual ~System_SDL2() {}
+ virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv);
+ virtual void destroy();
+ virtual void setScaler(const char *name, int multiplier);
+ virtual void setGamma(float gamma);
+ virtual void setPalette(const uint8_t *pal, int n, int depth);
+ virtual void clearPalette();
+ virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch);
+ virtual void copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch);
+ virtual void fillRect(int x, int y, int w, int h, uint8_t color);
+ virtual void copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal);
+ virtual void shakeScreen(int dx, int dy);
+ virtual void updateScreen(bool drawWidescreen);
+ virtual void processEvents();
+ virtual void sleep(int duration);
+ virtual uint32_t getTimeStamp();
+
+ virtual void startAudio(AudioCallback callback);
+ virtual void stopAudio();
+ virtual void lockAudio();
+ virtual void unlockAudio();
+ virtual AudioCallback setAudioCallback(AudioCallback callback);
+
+ void addKeyMapping(int key, uint8_t mask);
+ void setupDefaultKeyMappings();
+ void updateKeys(PlayerInput *inp);
+ void prepareScaledGfx(const char *caption, bool fullscreen, bool widescreen, bool yuv);
+};
+
+static System_SDL2 system_sdl2;
+System *const g_system = &system_sdl2;
+
+void System_printLog(FILE *fp, const char *s) {
+ if (fp == stderr) {
+ fprintf(stderr, "WARNING: %s\n", s);
+ } else {
+ fprintf(fp, "%s\n", s);
+ }
+}
+
+void System_fatalError(const char *s) {
+ fprintf(stderr, "ERROR: %s\n", s);
+ SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Heart of Darkness", s, system_sdl2._window);
+ exit(-1);
+}
+
+bool System_hasCommandLine() {
+ return true;
+}
+
+System_SDL2::System_SDL2() :
+ _offscreenLut(0), _offscreenRgb(0),
+ _window(0), _renderer(0), _texture(0), _backgroundTexture(0), _fmt(0), _widescreenTexture(0),
+ _controller(0), _joystick(0) {
+ for (int i = 0; i < 256; ++i) {
+ _gammaLut[i] = i;
+ }
+}
+
+void System_SDL2::init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv) {
+ SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
+ SDL_ShowCursor(SDL_DISABLE);
+ setupDefaultKeyMappings();
+ memset(&inp, 0, sizeof(inp));
+ memset(&pad, 0, sizeof(pad));
+ _screenW = w;
+ _screenH = h;
+ _shakeDx = _shakeDy = 0;
+ memset(_pal, 0, sizeof(_pal));
+ const int offscreenSize = w * h;
+ _offscreenLut = (uint8_t *)malloc(offscreenSize);
+ if (!_offscreenLut) {
+ error("System_SDL2::init() Unable to allocate offscreen buffer");
+ }
+ _offscreenRgb = (uint32_t *)malloc(offscreenSize * sizeof(uint32_t));
+ if (!_offscreenRgb) {
+ error("System_SDL2::init() Unable to allocate RGB offscreen buffer");
+ }
+ memset(_offscreenLut, 0, offscreenSize);
+ prepareScaledGfx(title, fullscreen, widescreen, yuv);
+
+ SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt");
+ _joystick = 0;
+ _controller = 0;
+ const int count = SDL_NumJoysticks();
+ if (count > 0) {
+ for (int i = 0; i < count; ++i) {
+ if (SDL_IsGameController(i)) {
+ _controller = SDL_GameControllerOpen(i);
+ if (_controller) {
+ fprintf(stdout, "Using controller '%s'\n", SDL_GameControllerName(_controller));
+ break;
+ }
+ }
+ _joystick = SDL_JoystickOpen(i);
+ if (_joystick) {
+ fprintf(stdout, "Using joystick '%s'\n", SDL_JoystickName(_joystick));
+ break;
+ }
+ }
+ }
+}
+
+void System_SDL2::destroy() {
+ free(_offscreenLut);
+ _offscreenLut = 0;
+ free(_offscreenRgb);
+ _offscreenRgb = 0;
+
+ if (_fmt) {
+ SDL_FreeFormat(_fmt);
+ _fmt = 0;
+ }
+ if (_texture) {
+ SDL_DestroyTexture(_texture);
+ _texture = 0;
+ }
+ if (_widescreenTexture) {
+ SDL_DestroyTexture(_widescreenTexture);
+ _widescreenTexture = 0;
+ }
+ if (_backgroundTexture) {
+ SDL_DestroyTexture(_backgroundTexture);
+ _backgroundTexture = 0;
+ }
+ if (_renderer) {
+ SDL_DestroyRenderer(_renderer);
+ _renderer = 0;
+ }
+ if (_window) {
+ SDL_DestroyWindow(_window);
+ _window = 0;
+ }
+
+ if (_controller) {
+ SDL_GameControllerClose(_controller);
+ _controller = 0;
+ }
+ if (_joystick) {
+ SDL_JoystickClose(_joystick);
+ _joystick = 0;
+ }
+ SDL_Quit();
+}
+
+template
+static void blur(int radius, const uint32_t *src, int srcPitch, int w, int h, const SDL_PixelFormat *fmt, uint32_t *dst, int dstPitch) {
+
+ const int count = 2 * radius + 1;
+
+ const uint32_t rmask = fmt->Rmask;
+ const uint32_t rshift = fmt->Rshift;
+ const uint32_t gmask = fmt->Gmask;
+ const uint32_t gshift = fmt->Gshift;
+ const uint32_t bmask = fmt->Bmask;
+ const uint32_t bshift = fmt->Bshift;
+
+ for (int j = 0; j < (vertical ? w : h); ++j) {
+
+ uint32_t r = 0;
+ uint32_t g = 0;
+ uint32_t b = 0;
+
+ uint32_t color;
+
+ for (int i = -radius; i <= radius; ++i) {
+ if (vertical) {
+ color = src[MAX(i, 0) * srcPitch];
+ } else {
+ color = src[MAX(i, 0)];
+ }
+ r += (color & rmask) >> rshift;
+ g += (color & gmask) >> gshift;
+ b += (color & bmask) >> bshift;
+ }
+ color = ((r / count) << rshift) | ((g / count) << gshift) | ((b / count) << bshift);
+ dst[0] = color;
+
+ for (int i = 1; i < (vertical ? h : w); ++i) {
+ if (vertical) {
+ color = src[MIN(i + radius, h - 1) * srcPitch];
+ } else {
+ color = src[MIN(i + radius, w - 1)];
+ }
+ r += (color & rmask) >> rshift;
+ g += (color & gmask) >> gshift;
+ b += (color & bmask) >> bshift;
+
+ if (vertical) {
+ color = src[MAX(i - radius - 1, 0) * srcPitch];
+ } else {
+ color = src[MAX(i - radius - 1, 0)];
+ }
+ r -= (color & rmask) >> rshift;
+ g -= (color & gmask) >> gshift;
+ b -= (color & bmask) >> bshift;
+
+ color = ((r / count) << rshift) | ((g / count) << gshift) | ((b / count) << bshift);
+ if (vertical) {
+ dst[i * srcPitch] = color;
+ } else {
+ dst[i] = color;
+ }
+ }
+
+ src += vertical ? 1 : srcPitch;
+ dst += vertical ? 1 : dstPitch;
+ }
+}
+
+void System_SDL2::copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal) {
+ if (!_widescreenTexture) {
+ return;
+ }
+
+ assert(w == _screenW && h == _screenH);
+ void *ptr = 0;
+ int pitch = 0;
+ if (SDL_LockTexture(_widescreenTexture, 0, &ptr, &pitch) == 0) {
+ assert((pitch & 3) == 0);
+
+ uint32_t *src = (uint32_t *)malloc(w * h * sizeof(uint32_t));
+ uint32_t *tmp = (uint32_t *)malloc(w * h * sizeof(uint32_t));
+ uint32_t *dst = (uint32_t *)ptr;
+
+ if (src && tmp) {
+ for (int i = 0; i < w * h; ++i) {
+ const uint8_t color = buf[i];
+ src[i] = SDL_MapRGB(_fmt, _gammaLut[pal[color * 3]], _gammaLut[pal[color * 3 + 1]], _gammaLut[pal[color * 3 + 2]]);
+ }
+ static const int radius = 8;
+ // horizontal pass
+ blur(radius, src, w, w, h, _fmt, tmp, w);
+ // vertical pass
+ blur(radius, tmp, w, w, h, _fmt, dst, pitch / sizeof(uint32_t));
+ }
+
+ free(src);
+ free(tmp);
+
+ SDL_UnlockTexture(_widescreenTexture);
+ }
+}
+
+void System_SDL2::setScaler(const char *name, int multiplier) {
+ if (multiplier != 0) {
+ _scalerMultiplier = multiplier;
+ }
+ if (name) {
+ for (int i = 0; _scalers[i].name; ++i) {
+ if (strcmp(name, _scalers[i].name) == 0) {
+ _scaler = _scalers[i].scaler;
+ break;
+ }
+ }
+ }
+}
+
+void System_SDL2::setGamma(float gamma) {
+ for (int i = 0; i < 256; ++i) {
+ _gammaLut[i] = (uint8_t)round(pow(i / 255., 1. / gamma) * 255);
+ }
+}
+
+void System_SDL2::setPalette(const uint8_t *pal, int n, int depth) {
+ assert(n <= 256);
+ assert(depth <= 8);
+ const int shift = 8 - depth;
+ for (int i = 0; i < n; ++i) {
+ int r = pal[i * 3 + 0];
+ int g = pal[i * 3 + 1];
+ int b = pal[i * 3 + 2];
+ if (shift != 0) {
+ r = (r << shift) | (r >> depth);
+ g = (g << shift) | (g >> depth);
+ b = (b << shift) | (b >> depth);
+ }
+ r = _gammaLut[r];
+ g = _gammaLut[g];
+ b = _gammaLut[b];
+ _pal[i] = SDL_MapRGB(_fmt, r, g, b);
+ }
+ if (_backgroundTexture) {
+ _pal[0] = 0;
+ }
+}
+
+void System_SDL2::clearPalette() {
+ memset(_pal, 0, sizeof(_pal));
+}
+
+void System_SDL2::copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) {
+ assert(x >= 0 && x + w <= _screenW && y >= 0 && y + h <= _screenH);
+ if (w == pitch && w == _screenW) {
+ memcpy(_offscreenLut + y * _screenW + x, buf, w * h);
+ } else {
+ for (int i = 0; i < h; ++i) {
+ memcpy(_offscreenLut + y * _screenW + x, buf, w);
+ buf += pitch;
+ ++y;
+ }
+ }
+}
+
+void System_SDL2::copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch) {
+ if (_backgroundTexture) {
+ SDL_UpdateYUVTexture(_backgroundTexture, 0, y, ypitch, u, upitch, v, vpitch);
+ }
+}
+
+void System_SDL2::fillRect(int x, int y, int w, int h, uint8_t color) {
+ assert(x >= 0 && x + w <= _screenW && y >= 0 && y + h <= _screenH);
+ if (w == _screenW) {
+ memset(_offscreenLut + y * _screenW + x, color, w * h);
+ } else {
+ for (int i = 0; i < h; ++i) {
+ memset(_offscreenLut + y * _screenW + x, color, w);
+ ++y;
+ }
+ }
+}
+
+void System_SDL2::shakeScreen(int dx, int dy) {
+ _shakeDx = dx;
+ _shakeDy = dy;
+}
+
+static void clearScreen(uint32_t *dst, int dstPitch, int x, int y, int w, int h) {
+ uint32_t *p = dst + (y * dstPitch + x) * _scalerMultiplier;
+ for (int j = 0; j < h * _scalerMultiplier; ++j) {
+ memset(p, 0, w * sizeof(uint32_t) * _scalerMultiplier);
+ p += dstPitch;
+ }
+}
+
+void System_SDL2::updateScreen(bool drawWidescreen) {
+ void *texturePtr = 0;
+ int texturePitch = 0;
+ if (SDL_LockTexture(_texture, 0, &texturePtr, &texturePitch) != 0) {
+ return;
+ }
+ int w = _screenW;
+ int h = _screenH;
+ const uint8_t *src = _offscreenLut;
+ uint32_t *dst = (uint32_t *)texturePtr;
+ assert((texturePitch & 3) == 0);
+ const int dstPitch = texturePitch / sizeof(uint32_t);
+ const int srcPitch = _screenW;
+ if (!_widescreenTexture) {
+ if (_shakeDy > 0) {
+ clearScreen(dst, dstPitch, 0, 0, w, _shakeDy);
+ h -= _shakeDy;
+ dst += _shakeDy * dstPitch;
+ } else if (_shakeDy < 0) {
+ h += _shakeDy;
+ clearScreen(dst, dstPitch, 0, h, w, -_shakeDy);
+ src -= _shakeDy * srcPitch;
+ }
+ if (_shakeDx > 0) {
+ clearScreen(dst, dstPitch, 0, 0, _shakeDx, h);
+ w -= _shakeDx;
+ dst += _shakeDx;
+ } else if (_shakeDx < 0) {
+ w += _shakeDx;
+ clearScreen(dst, dstPitch, w, 0, -_shakeDx, h);
+ src -= _shakeDx;
+ }
+ }
+ uint32_t *p = (_scalerMultiplier == 1) ? dst : _offscreenRgb;
+ for (int i = 0; i < w * h; ++i) {
+ p[i] = _pal[src[i]];
+ }
+ if (_scalerMultiplier != 1) {
+ _scaler->scale(_scalerMultiplier, dst, dstPitch, _offscreenRgb, srcPitch, w, h);
+ }
+ SDL_UnlockTexture(_texture);
+
+ SDL_RenderClear(_renderer);
+
+ if (_widescreenTexture) {
+ if (drawWidescreen) {
+ SDL_RenderCopy(_renderer, _widescreenTexture, 0, 0);
+ }
+ SDL_Rect r;
+ r.x = _shakeDx * _scalerMultiplier;
+ r.y = _shakeDy * _scalerMultiplier;
+ SDL_RenderGetLogicalSize(_renderer, &r.w, &r.h);
+ r.x += (r.w - _texW) / 2;
+ r.w = _texW;
+ r.y += (r.h - _texH) / 2;
+ r.h = _texH;
+ if (_backgroundTexture) {
+ SDL_RenderCopy(_renderer, _backgroundTexture, 0, &r);
+ }
+ SDL_RenderCopy(_renderer, _texture, 0, &r);
+ } else {
+ if (_backgroundTexture) {
+ SDL_RenderCopy(_renderer, _backgroundTexture, 0, 0);
+ }
+ SDL_RenderCopy(_renderer, _texture, 0, 0);
+ }
+ SDL_RenderPresent(_renderer);
+ _shakeDx = _shakeDy = 0;
+}
+
+void System_SDL2::processEvents() {
+ SDL_Event ev;
+ pad.prevMask = pad.mask;
+ while (SDL_PollEvent(&ev)) {
+ switch (ev.type) {
+ case SDL_KEYUP:
+ if (ev.key.keysym.sym == SDLK_s) {
+ inp.screenshot = true;
+ }
+ break;
+ case SDL_JOYDEVICEADDED:
+ if (!_joystick) {
+ _joystick = SDL_JoystickOpen(ev.jdevice.which);
+ if (_joystick) {
+ fprintf(stdout, "Using joystick '%s'\n", SDL_JoystickName(_joystick));
+ }
+ }
+ break;
+ case SDL_JOYDEVICEREMOVED:
+ if (_joystick == SDL_JoystickFromInstanceID(ev.jdevice.which)) {
+ fprintf(stdout, "Removed joystick '%s'\n", SDL_JoystickName(_joystick));
+ SDL_JoystickClose(_joystick);
+ _joystick = 0;
+ }
+ break;
+ case SDL_JOYHATMOTION:
+ if (_joystick) {
+ pad.mask &= ~(SYS_INP_UP | SYS_INP_DOWN | SYS_INP_LEFT | SYS_INP_RIGHT);
+ if (ev.jhat.value & SDL_HAT_UP) {
+ pad.mask |= SYS_INP_UP;
+ }
+ if (ev.jhat.value & SDL_HAT_DOWN) {
+ pad.mask |= SYS_INP_DOWN;
+ }
+ if (ev.jhat.value & SDL_HAT_LEFT) {
+ pad.mask |= SYS_INP_LEFT;
+ }
+ if (ev.jhat.value & SDL_HAT_RIGHT) {
+ pad.mask |= SYS_INP_RIGHT;
+ }
+ }
+ break;
+ case SDL_JOYAXISMOTION:
+ if (_joystick) {
+ switch (ev.jaxis.axis) {
+ case 0:
+ pad.mask &= ~(SYS_INP_RIGHT | SYS_INP_LEFT);
+ if (ev.jaxis.value > kJoystickCommitValue) {
+ pad.mask |= SYS_INP_RIGHT;
+ } else if (ev.jaxis.value < -kJoystickCommitValue) {
+ pad.mask |= SYS_INP_LEFT;
+ }
+ break;
+ case 1:
+ pad.mask &= ~(SYS_INP_UP | SYS_INP_DOWN);
+ if (ev.jaxis.value > kJoystickCommitValue) {
+ pad.mask |= SYS_INP_DOWN;
+ } else if (ev.jaxis.value < -kJoystickCommitValue) {
+ pad.mask |= SYS_INP_UP;
+ }
+ break;
+ }
+ }
+ break;
+ case SDL_JOYBUTTONDOWN:
+ case SDL_JOYBUTTONUP:
+ if (_joystick) {
+ const bool pressed = (ev.jbutton.state == SDL_PRESSED);
+ switch (ev.jbutton.button) {
+ case 0:
+ if (pressed) {
+ pad.mask |= SYS_INP_RUN;
+ } else {
+ pad.mask &= ~SYS_INP_RUN;
+ }
+ break;
+ case 1:
+ if (pressed) {
+ pad.mask |= SYS_INP_JUMP;
+ } else {
+ pad.mask &= ~SYS_INP_JUMP;
+ }
+ break;
+ case 2:
+ if (pressed) {
+ pad.mask |= SYS_INP_SHOOT;
+ } else {
+ pad.mask &= ~SYS_INP_SHOOT;
+ }
+ break;
+ case 3:
+ if (pressed) {
+ pad.mask |= SYS_INP_SHOOT | SYS_INP_RUN;
+ } else {
+ pad.mask &= ~(SYS_INP_SHOOT | SYS_INP_RUN);
+ }
+ break;
+ }
+ }
+ break;
+ case SDL_CONTROLLERDEVICEADDED:
+ if (!_controller) {
+ _controller = SDL_GameControllerOpen(ev.cdevice.which);
+ if (_controller) {
+ fprintf(stdout, "Using controller '%s'\n", SDL_GameControllerName(_controller));
+ }
+ }
+ break;
+ case SDL_CONTROLLERDEVICEREMOVED:
+ if (_controller == SDL_GameControllerFromInstanceID(ev.cdevice.which)) {
+ fprintf(stdout, "Removed controller '%s'\n", SDL_GameControllerName(_controller));
+ SDL_GameControllerClose(_controller);
+ _controller = 0;
+ }
+ break;
+ case SDL_CONTROLLERAXISMOTION:
+ if (_controller) {
+ switch (ev.caxis.axis) {
+ case SDL_CONTROLLER_AXIS_LEFTX:
+ case SDL_CONTROLLER_AXIS_RIGHTX:
+ if (ev.caxis.value < -kJoystickCommitValue) {
+ pad.mask |= SYS_INP_LEFT;
+ } else {
+ pad.mask &= ~SYS_INP_LEFT;
+ }
+ if (ev.caxis.value > kJoystickCommitValue) {
+ pad.mask |= SYS_INP_RIGHT;
+ } else {
+ pad.mask &= ~SYS_INP_RIGHT;
+ }
+ break;
+ case SDL_CONTROLLER_AXIS_LEFTY:
+ case SDL_CONTROLLER_AXIS_RIGHTY:
+ if (ev.caxis.value < -kJoystickCommitValue) {
+ pad.mask |= SYS_INP_UP;
+ } else {
+ pad.mask &= ~SYS_INP_UP;
+ }
+ if (ev.caxis.value > kJoystickCommitValue) {
+ pad.mask |= SYS_INP_DOWN;
+ } else {
+ pad.mask &= ~SYS_INP_DOWN;
+ }
+ break;
+ }
+ }
+ break;
+ case SDL_CONTROLLERBUTTONDOWN:
+ case SDL_CONTROLLERBUTTONUP:
+ if (_controller) {
+ const bool pressed = (ev.cbutton.state == SDL_PRESSED);
+ switch (ev.cbutton.button) {
+ case SDL_CONTROLLER_BUTTON_A:
+ if (pressed) {
+ pad.mask |= SYS_INP_RUN;
+ } else {
+ pad.mask &= ~SYS_INP_RUN;
+ }
+ break;
+ case SDL_CONTROLLER_BUTTON_B:
+ if (pressed) {
+ pad.mask |= SYS_INP_JUMP;
+ } else {
+ pad.mask &= ~SYS_INP_JUMP;
+ }
+ break;
+ case SDL_CONTROLLER_BUTTON_X:
+ if (pressed) {
+ pad.mask |= SYS_INP_SHOOT;
+ } else {
+ pad.mask &= ~SYS_INP_SHOOT;
+ }
+ break;
+ case SDL_CONTROLLER_BUTTON_Y:
+ if (pressed) {
+ pad.mask |= SYS_INP_SHOOT | SYS_INP_RUN;
+ } else {
+ pad.mask &= ~(SYS_INP_SHOOT | SYS_INP_RUN);
+ }
+ break;
+ case SDL_CONTROLLER_BUTTON_BACK:
+ case SDL_CONTROLLER_BUTTON_START:
+ inp.quit = pressed;
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_UP:
+ if (pressed) {
+ pad.mask |= SYS_INP_UP;
+ } else {
+ pad.mask &= ~SYS_INP_UP;
+ }
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
+ if (pressed) {
+ pad.mask |= SYS_INP_DOWN;
+ } else {
+ pad.mask &= ~SYS_INP_DOWN;
+ }
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
+ if (pressed) {
+ pad.mask |= SYS_INP_LEFT;
+ } else {
+ pad.mask &= ~SYS_INP_LEFT;
+ }
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
+ if (pressed) {
+ pad.mask |= SYS_INP_RIGHT;
+ } else {
+ pad.mask &= ~SYS_INP_RIGHT;
+ }
+ break;
+ }
+ }
+ break;
+ case SDL_QUIT:
+ inp.quit = true;
+ break;
+ }
+ }
+ updateKeys(&inp);
+}
+
+void System_SDL2::sleep(int duration) {
+ SDL_Delay(duration);
+}
+
+uint32_t System_SDL2::getTimeStamp() {
+ return SDL_GetTicks();
+}
+
+static void mixAudioS16(void *param, uint8_t *buf, int len) {
+ memset(buf, 0, len);
+ system_sdl2._audioCb.proc(system_sdl2._audioCb.userdata, (int16_t *)buf, len / 2);
+}
+
+void System_SDL2::startAudio(AudioCallback callback) {
+ SDL_AudioSpec desired;
+ memset(&desired, 0, sizeof(desired));
+ desired.freq = kAudioHz;
+ desired.format = AUDIO_S16SYS;
+ desired.channels = 2;
+ desired.samples = 4096;
+ desired.callback = mixAudioS16;
+ desired.userdata = this;
+ if (SDL_OpenAudio(&desired, 0) == 0) {
+ _audioCb = callback;
+ SDL_PauseAudio(0);
+ } else {
+ error("System_SDL2::startAudio() Unable to open sound device");
+ }
+}
+
+void System_SDL2::stopAudio() {
+ SDL_CloseAudio();
+}
+
+void System_SDL2::lockAudio() {
+ SDL_LockAudio();
+}
+
+void System_SDL2::unlockAudio() {
+ SDL_UnlockAudio();
+}
+
+AudioCallback System_SDL2::setAudioCallback(AudioCallback callback) {
+ SDL_LockAudio();
+ AudioCallback cb = _audioCb;
+ _audioCb = callback;
+ SDL_UnlockAudio();
+ return cb;
+}
+
+void System_SDL2::addKeyMapping(int key, uint8_t mask) {
+ if (_keyMappingsCount < kKeyMappingsSize) {
+ for (int i = 0; i < _keyMappingsCount; ++i) {
+ if (_keyMappings[i].keyCode == key) {
+ _keyMappings[i].mask = mask;
+ return;
+ }
+ }
+ if (_keyMappingsCount < kKeyMappingsSize) {
+ _keyMappings[_keyMappingsCount].keyCode = key;
+ _keyMappings[_keyMappingsCount].mask = mask;
+ ++_keyMappingsCount;
+ }
+ }
+}
+
+void System_SDL2::setupDefaultKeyMappings() {
+ _keyMappingsCount = 0;
+ memset(_keyMappings, 0, sizeof(_keyMappings));
+
+ /* original key mappings of the PC version */
+
+ addKeyMapping(SDL_SCANCODE_LEFT, SYS_INP_LEFT);
+ addKeyMapping(SDL_SCANCODE_UP, SYS_INP_UP);
+ addKeyMapping(SDL_SCANCODE_RIGHT, SYS_INP_RIGHT);
+ addKeyMapping(SDL_SCANCODE_DOWN, SYS_INP_DOWN);
+// addKeyMapping(SDL_SCANCODE_PAGEUP, SYS_INP_UP | SYS_INP_RIGHT);
+// addKeyMapping(SDL_SCANCODE_HOME, SYS_INP_UP | SYS_INP_LEFT);
+// addKeyMapping(SDL_SCANCODE_END, SYS_INP_DOWN | SYS_INP_LEFT);
+// addKeyMapping(SDL_SCANCODE_PAGEDOWN, SYS_INP_DOWN | SYS_INP_RIGHT);
+
+ addKeyMapping(SDL_SCANCODE_RETURN, SYS_INP_JUMP);
+ addKeyMapping(SDL_SCANCODE_LCTRL, SYS_INP_RUN);
+ addKeyMapping(SDL_SCANCODE_F, SYS_INP_RUN);
+ addKeyMapping(SDL_SCANCODE_LALT, SYS_INP_JUMP);
+ addKeyMapping(SDL_SCANCODE_G, SYS_INP_JUMP);
+ addKeyMapping(SDL_SCANCODE_LSHIFT, SYS_INP_SHOOT);
+ addKeyMapping(SDL_SCANCODE_H, SYS_INP_SHOOT);
+ addKeyMapping(SDL_SCANCODE_D, SYS_INP_SHOOT | SYS_INP_RUN);
+ addKeyMapping(SDL_SCANCODE_SPACE, SYS_INP_SHOOT | SYS_INP_RUN);
+ addKeyMapping(SDL_SCANCODE_ESCAPE, SYS_INP_ESC);
+}
+
+void System_SDL2::updateKeys(PlayerInput *inp) {
+ inp->prevMask = inp->mask;
+ inp->mask = 0;
+ const uint8_t *keyState = SDL_GetKeyboardState(NULL);
+ for (int i = 0; i < _keyMappingsCount; ++i) {
+ const KeyMapping *keyMap = &_keyMappings[i];
+ if (keyState[keyMap->keyCode]) {
+ inp->mask |= keyMap->mask;
+ }
+ }
+ inp->mask |= pad.mask;
+}
+
+void System_SDL2::prepareScaledGfx(const char *caption, bool fullscreen, bool widescreen, bool yuv) {
+ int flags = 0;
+ if (fullscreen) {
+ flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
+ } else {
+ flags |= SDL_WINDOW_RESIZABLE;
+ }
+ _texW = _screenW * _scalerMultiplier;
+ _texH = _screenH * _scalerMultiplier;
+ const int windowW = widescreen ? _texH * 16 / 9 : _texW;
+ const int windowH = _texH;
+ _window = SDL_CreateWindow(caption, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, windowW, windowH, flags);
+ SDL_Surface *icon = SDL_LoadBMP(kIconBmp);
+ if (icon) {
+ SDL_SetWindowIcon(_window, icon);
+ SDL_FreeSurface(icon);
+ }
+ _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
+ SDL_RenderSetLogicalSize(_renderer, windowW, windowH);
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, (_scaler == &scaler_nearest) ? "0" : "1");
+
+ const int pixelFormat = yuv ? SDL_PIXELFORMAT_RGBA8888 : SDL_PIXELFORMAT_RGB888;
+ _texture = SDL_CreateTexture(_renderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, _texW, _texH);
+ if (widescreen) {
+ _widescreenTexture = SDL_CreateTexture(_renderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, _screenW, _screenH);
+ } else {
+ _widescreenTexture = 0;
+ }
+ if (yuv) {
+ _backgroundTexture = SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, _screenW, _screenH);
+ // the game texture is drawn on top
+ SDL_SetTextureBlendMode(_texture, SDL_BLENDMODE_BLEND);
+ } else {
+ _backgroundTexture = 0;
+ }
+ _fmt = SDL_AllocFormat(pixelFormat);
+}
diff --git a/Src/Global/system_wii.cpp b/Src/Global/system_wii.cpp
new file mode 100644
index 0000000..e7d416f
--- /dev/null
+++ b/Src/Global/system_wii.cpp
@@ -0,0 +1,505 @@
+
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "system.h"
+
+struct System_Wii : System {
+
+ int _shakeDx, _shakeDy;
+ uint64_t _startTime;
+ AudioCallback _audioCb;
+ mutex_t _audioMutex;
+ lwpq_t _audioQueue;
+ lwp_t _audioThread;
+ bool _audioOut;
+ GXRModeObj *_rmodeObj;
+ GXTlutObj _tlutObj;
+ GXTexObj _texObj;
+ int _projTop, _projLeft;
+ int _gamma;
+
+ System_Wii();
+ virtual ~System_Wii();
+ virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv);
+ virtual void destroy();
+ virtual void setScaler(const char *name, int multiplier);
+ virtual void setGamma(float gamma);
+ virtual void setPalette(const uint8_t *pal, int n, int depth);
+ virtual void clearPalette();
+ virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch);
+ virtual void copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch);
+ virtual void fillRect(int x, int y, int w, int h, uint8_t color);
+ virtual void copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal);
+ virtual void shakeScreen(int dx, int dy);
+ virtual void updateScreen(bool drawWidescreen);
+ virtual void processEvents();
+ virtual void sleep(int duration);
+ virtual uint32_t getTimeStamp();
+ virtual void startAudio(AudioCallback callback);
+ virtual void stopAudio();
+ virtual void lockAudio();
+ virtual void unlockAudio();
+ virtual AudioCallback setAudioCallback(AudioCallback callback);
+
+ void setupVideo();
+
+ void initGX();
+ void finiGX();
+ void drawTextureGX(int x, int y);
+};
+
+static System_Wii system_wii;
+System *const g_system = &system_wii;
+
+static uint32_t __attribute__((aligned(16))) _fifo[256 * 1024];
+static uint32_t *_xfb[2];
+static int _current_fb;
+
+static const int GAME_W = 256;
+static const int GAME_H = 192;
+
+static uint16_t __attribute__((aligned(32))) _clut[256];
+static uint8_t __attribute__((aligned(32))) _texture[GAME_W * GAME_H];
+
+static const int DMA_BUFFER_SAMPLES = 512;
+static const int DMA_BUFFER_SIZE = DMA_BUFFER_SAMPLES * 2 * sizeof(int16_t); // stereo s16
+
+static const int AUDIO_THREAD_PRIORITY = 80;
+
+static const int AUDIO_BUFFER_SAMPLES = DMA_BUFFER_SAMPLES * 22050 / 32000;
+static const int AUDIO_BUFFER_SIZE = AUDIO_BUFFER_SAMPLES * 2 * sizeof(int16_t); // stereo s16
+
+static uint8_t __attribute__((aligned(32))) _audioThreadStack[16384];
+static int16_t __attribute__((aligned(32))) _resample[DMA_BUFFER_SAMPLES * 2];
+static uint8_t __attribute__((aligned(32))) _dma[2][DMA_BUFFER_SIZE];
+static int _current_dma;
+
+void System_earlyInit() {
+ fatInitDefault();
+}
+
+void System_fatalError(const char *s) {
+ GXRModeObj *rmodeObj = system_wii._rmodeObj;
+ if (!rmodeObj) {
+ VIDEO_Init();
+ system_wii.setupVideo();
+ rmodeObj = system_wii._rmodeObj;
+ }
+ console_init(_xfb[_current_fb], 20, 20, rmodeObj->fbWidth, rmodeObj->xfbHeight, rmodeObj->fbWidth * VI_DISPLAY_PIX_SZ);
+ fputs(s, stdout);
+ usleep(10 * 1000 * 1000);
+}
+
+void System_printLog(FILE *fp, const char *s) {
+}
+
+bool System_hasCommandLine() {
+ return false;
+}
+
+System_Wii::System_Wii() {
+ _rmodeObj = 0;
+}
+
+System_Wii::~System_Wii() {
+}
+
+void System_Wii::init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv) {
+ memset(&inp, 0, sizeof(inp));
+ memset(&pad, 0, sizeof(pad));
+
+ _shakeDx = _shakeDy = 0;
+ _startTime = gettime();
+
+ memset(&_audioCb, 0, sizeof(_audioCb));
+ _audioMutex = 0;
+ _audioThread = LWP_THREAD_NULL;
+ _audioOut = false;
+
+ AUDIO_Init(0);
+
+ _gamma = GX_GM_1_0;
+
+ if (!_rmodeObj) {
+ VIDEO_Init();
+ setupVideo();
+ }
+
+ PAD_Init();
+ WUPC_Init();
+ WPAD_Init();
+ WiiDRC_Init();
+
+ initGX();
+}
+
+void System_Wii::destroy() {
+ WUPC_Shutdown();
+ WPAD_Shutdown();
+
+ finiGX();
+
+ VIDEO_SetBlack(TRUE);
+ VIDEO_Flush();
+ free(MEM_K1_TO_K0(_xfb[0]));
+ _xfb[0] = 0;
+ free(MEM_K1_TO_K0(_xfb[1]));
+ _xfb[1] = 0;
+
+ fatUnmount("sd:/");
+ fatUnmount("usb:/");
+}
+
+void System_Wii::setScaler(const char *name, int multiplier) {
+}
+
+void System_Wii::setGamma(float gamma) {
+ if (gamma < 1.7f) {
+ _gamma = GX_GM_1_0;
+ } else if (gamma < 2.2f) {
+ _gamma = GX_GM_1_7;
+ } else {
+ _gamma = GX_GM_2_2;
+ }
+}
+
+void System_Wii::setPalette(const uint8_t *pal, int n, int depth) {
+ const int shift = 8 - depth;
+ for (int i = 0; i < n; ++i) {
+ int r = pal[i * 3];
+ int g = pal[i * 3 + 1];
+ int b = pal[i * 3 + 2];
+ if (shift != 0) {
+ r = (r << shift) | (r >> depth);
+ g = (g << shift) | (g >> depth);
+ b = (b << shift) | (b >> depth);
+ }
+ _clut[i] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
+ }
+ DCFlushRange(_clut, sizeof(_clut));
+ GX_LoadTlut(&_tlutObj, GX_TLUT0);
+}
+
+void System_Wii::clearPalette() {
+ memset(_clut, 0, sizeof(_clut));
+ DCFlushRange(_clut, sizeof(_clut));
+ GX_LoadTlut(&_tlutObj, GX_TLUT0);
+}
+
+void System_Wii::copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) {
+ uint64_t *dst = (uint64_t *)_texture + y * GAME_W + x;
+ uint64_t *src = (uint64_t *)buf;
+ assert((pitch & 7) == 0);
+ pitch >>= 3;
+ w >>= 3;
+ for (int y = 0; y < h; y += 4) {
+ for (int x = 0; x < w; ++x) {
+ for (int i = 0; i < 4; ++i) {
+ *dst++ = src[pitch * i + x];
+ }
+ }
+ src += pitch * 4;
+ }
+ DCFlushRange(_texture, sizeof(_texture));
+ GX_LoadTexObj(&_texObj, GX_TEXMAP0);
+}
+
+void System_Wii::copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch) {
+}
+
+void System_Wii::fillRect(int x, int y, int w, int h, uint8_t color) {
+ uint8_t *p = _texture + y * GAME_W + x;
+ if (w == GAME_W) {
+ memset(p, color, w * h);
+ } else {
+ for (int y = 0; y < h; ++y) {
+ memset(p, color, w);
+ p += GAME_W;
+ }
+ }
+ DCFlushRange(_texture, sizeof(_texture));
+ GX_LoadTexObj(&_texObj, GX_TEXMAP0);
+}
+
+void System_Wii::copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal) {
+}
+
+void System_Wii::shakeScreen(int dx, int dy) {
+ _shakeDx = dx;
+ _shakeDy = dy;
+}
+
+void System_Wii::updateScreen(bool drawWidescreen) {
+
+ GX_InvalidateTexAll();
+ drawTextureGX(_shakeDx, _shakeDy);
+ GX_DrawDone();
+
+ _current_fb ^= 1;
+ GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
+ GX_SetColorUpdate(GX_TRUE);
+ GX_CopyDisp(_xfb[_current_fb], GX_TRUE);
+ GX_Flush();
+
+ VIDEO_SetNextFramebuffer(_xfb[_current_fb]);
+ VIDEO_Flush();
+ VIDEO_WaitVSync();
+}
+
+void System_Wii::processEvents() {
+ inp.prevMask = inp.mask;
+ inp.mask = 0;
+
+ if (SYS_ResetButtonDown()) {
+ inp.quit = true;
+ }
+
+ static const struct {
+ int pad;
+ int wpad;
+ int wdrc;
+ int sys;
+ } mapping[] = {
+ { PAD_BUTTON_UP, (WPAD_CLASSIC_BUTTON_UP | WPAD_BUTTON_RIGHT), WIIDRC_BUTTON_UP, SYS_INP_UP },
+ { PAD_BUTTON_RIGHT, (WPAD_CLASSIC_BUTTON_RIGHT | WPAD_BUTTON_DOWN), WIIDRC_BUTTON_RIGHT, SYS_INP_RIGHT },
+ { PAD_BUTTON_DOWN, (WPAD_CLASSIC_BUTTON_DOWN | WPAD_BUTTON_LEFT), WIIDRC_BUTTON_DOWN, SYS_INP_DOWN },
+ { PAD_BUTTON_LEFT, (WPAD_CLASSIC_BUTTON_LEFT | WPAD_BUTTON_UP), WIIDRC_BUTTON_LEFT, SYS_INP_LEFT },
+ { PAD_BUTTON_A, (WPAD_CLASSIC_BUTTON_A | WPAD_BUTTON_A), WIIDRC_BUTTON_A, SYS_INP_JUMP },
+ { PAD_BUTTON_B, (WPAD_CLASSIC_BUTTON_B | WPAD_BUTTON_B), WIIDRC_BUTTON_B, SYS_INP_RUN },
+ { PAD_BUTTON_X, (WPAD_CLASSIC_BUTTON_X | WPAD_BUTTON_1), WIIDRC_BUTTON_X, SYS_INP_SHOOT },
+ { PAD_BUTTON_Y, (WPAD_CLASSIC_BUTTON_Y | WPAD_BUTTON_2), WIIDRC_BUTTON_Y, SYS_INP_SHOOT | SYS_INP_RUN },
+ { PAD_BUTTON_START, (WPAD_CLASSIC_BUTTON_HOME | WPAD_BUTTON_HOME), WIIDRC_BUTTON_HOME, SYS_INP_ESC },
+ { 0, 0, 0 }
+ };
+ PAD_ScanPads();
+ const uint32_t padMask = PAD_ButtonsDown(0) | PAD_ButtonsHeld(0);
+ WUPC_UpdateButtonStats();
+ uint32_t wpadMask = WUPC_ButtonsDown(0) | WUPC_ButtonsHeld(0);
+ WPAD_ScanPads();
+ wpadMask |= WPAD_ButtonsDown(0) | WPAD_ButtonsHeld(0);
+ WiiDRC_ScanPads();
+ const uint32_t wdrcMask = WiiDRC_ButtonsDown() | WiiDRC_ButtonsHeld();
+ for (int i = 0; mapping[i].pad != 0; ++i) {
+ if ((mapping[i].pad & padMask) != 0 || (mapping[i].wpad & wpadMask) != 0 || (mapping[i].wdrc & wdrcMask) != 0) {
+ inp.mask |= mapping[i].sys;
+ }
+ }
+ const int x = PAD_StickX(0);
+ if (x > 64) {
+ inp.mask |= SYS_INP_RIGHT;
+ } else if (x < -64) {
+ inp.mask |= SYS_INP_LEFT;
+ }
+ const int y = PAD_StickY(0);
+ if (y > 64) {
+ inp.mask |= SYS_INP_UP;
+ } else if (y < -64) {
+ inp.mask |= SYS_INP_DOWN;
+ }
+}
+
+void System_Wii::sleep(int duration) {
+ usleep(duration * 1000);
+}
+
+uint32_t System_Wii::getTimeStamp() {
+ const uint64_t ticks = diff_ticks(_startTime, gettime());
+ return ticks_to_millisecs(ticks);
+}
+
+static void *audioThread(void *arg) {
+ while (system_wii._audioOut) {
+
+ memset(_dma[_current_dma], 0, DMA_BUFFER_SIZE);
+
+ int16_t buf22khz[AUDIO_BUFFER_SAMPLES * 2]; // stereo
+ memset(buf22khz, 0, sizeof(buf22khz));
+ system_wii.lockAudio();
+ (system_wii._audioCb.proc)(system_wii._audioCb.userdata, buf22khz, AUDIO_BUFFER_SAMPLES * 2);
+ system_wii.unlockAudio();
+
+ // point resampling
+ static const int fracBits = 16;
+ static const uint32_t step = (22050 << fracBits) / 32000;
+ uint32_t len = 0;
+ for (uint32_t pos = 0; len < DMA_BUFFER_SAMPLES * 2; pos += step) {
+ const int16_t *p = buf22khz + MIN((pos >> fracBits), AUDIO_BUFFER_SAMPLES - 1) * 2;
+ _resample[len++] = p[0];
+ _resample[len++] = p[1];
+ }
+ assert(len * sizeof(int16_t) == DMA_BUFFER_SIZE);
+
+ memcpy(_dma[_current_dma], _resample, DMA_BUFFER_SIZE);
+ DCFlushRange(_dma[_current_dma], DMA_BUFFER_SIZE);
+
+ LWP_ThreadSleep(system_wii._audioQueue);
+ }
+ return 0;
+}
+
+static void dmaCallback() {
+ _current_dma ^= 1;
+ AUDIO_InitDMA((uint32_t)_dma[_current_dma], DMA_BUFFER_SIZE);
+ LWP_ThreadSignal(system_wii._audioQueue);
+}
+
+void System_Wii::startAudio(AudioCallback callback) {
+ _audioCb = callback;
+ LWP_MutexInit(&_audioMutex, FALSE);
+ LWP_InitQueue(&_audioQueue);
+ _audioOut = true;
+ AUDIO_SetDSPSampleRate(AI_SAMPLERATE_32KHZ);
+ LWP_CreateThread(&_audioThread, audioThread, 0, _audioThreadStack, sizeof(_audioThreadStack), AUDIO_THREAD_PRIORITY);
+ AUDIO_RegisterDMACallback(dmaCallback);
+ dmaCallback();
+ AUDIO_InitDMA((uint32_t)_dma[_current_dma], DMA_BUFFER_SIZE);
+ AUDIO_StartDMA();
+}
+
+void System_Wii::stopAudio() {
+ AUDIO_StopDMA();
+ AUDIO_RegisterDMACallback(0);
+ _audioOut = false;
+ LWP_ThreadSignal(_audioQueue);
+ LWP_JoinThread(_audioThread, 0);
+ LWP_MutexDestroy(_audioMutex);
+ LWP_CloseQueue(_audioQueue);
+}
+
+void System_Wii::lockAudio() {
+ LWP_MutexLock(_audioMutex);
+}
+
+void System_Wii::unlockAudio() {
+ LWP_MutexUnlock(_audioMutex);
+}
+
+AudioCallback System_Wii::setAudioCallback(AudioCallback callback) {
+ lockAudio();
+ AudioCallback cb = _audioCb;
+ _audioCb = callback;
+ unlockAudio();
+ return cb;
+}
+
+void System_Wii::setupVideo() {
+ _rmodeObj = VIDEO_GetPreferredMode(0);
+ _rmodeObj->viWidth = 640;
+ _rmodeObj->viXOrigin = (VI_MAX_WIDTH_NTSC - _rmodeObj->viWidth) / 2;
+ VIDEO_Configure(_rmodeObj);
+
+ _xfb[0] = (uint32_t *)SYS_AllocateFramebuffer(_rmodeObj);
+ _xfb[1] = (uint32_t *)SYS_AllocateFramebuffer(_rmodeObj);
+ DCInvalidateRange(_xfb[0], VIDEO_GetFrameBufferSize(_rmodeObj));
+ DCInvalidateRange(_xfb[1], VIDEO_GetFrameBufferSize(_rmodeObj));
+ _xfb[0] = (uint32_t *)MEM_K0_TO_K1(_xfb[0]);
+ _xfb[1] = (uint32_t *)MEM_K0_TO_K1(_xfb[1]);
+
+ VIDEO_ClearFrameBuffer(_rmodeObj, _xfb[0], COLOR_BLACK);
+ VIDEO_ClearFrameBuffer(_rmodeObj, _xfb[1], COLOR_BLACK);
+ VIDEO_SetNextFramebuffer(_xfb[0]);
+ _current_fb = 0;
+
+ VIDEO_SetBlack(FALSE);
+ VIDEO_Flush();
+ VIDEO_WaitVSync();
+ if (_rmodeObj->viTVMode & VI_NON_INTERLACE) {
+ VIDEO_WaitVSync();
+ }
+}
+
+void System_Wii::initGX() {
+ memset(_fifo, 0, sizeof(_fifo));
+ GX_Init(_fifo, sizeof(_fifo));
+
+ GXColor background = { 0, 0, 0, 0xFF };
+ GX_SetCopyClear(background, GX_MAX_Z24);
+
+ GX_SetViewport(0, 0, _rmodeObj->fbWidth, _rmodeObj->efbHeight, 0, 1);
+ const int xfbHeight = GX_SetDispCopyYScale(GX_GetYScaleFactor(_rmodeObj->efbHeight, _rmodeObj->xfbHeight));
+ GX_SetScissor(0, 0, _rmodeObj->fbWidth, _rmodeObj->efbHeight);
+
+ GX_SetDispCopySrc(0, 0, _rmodeObj->fbWidth, _rmodeObj->efbHeight);
+ GX_SetDispCopyDst(_rmodeObj->fbWidth, xfbHeight);
+
+ GX_SetCopyFilter(_rmodeObj->aa, _rmodeObj->sample_pattern, GX_TRUE, _rmodeObj->vfilter);
+ GX_SetFieldMode(_rmodeObj->field_rendering, ((_rmodeObj->viHeight == 2 * _rmodeObj->xfbHeight) ? GX_ENABLE : GX_DISABLE));
+ GX_SetPixelFmt(_rmodeObj->aa ? GX_PF_RGB565_Z16 : GX_PF_RGB8_Z24, GX_ZC_LINEAR);
+ GX_SetDispCopyGamma(_gamma);
+ GX_SetCullMode(GX_CULL_NONE);
+
+ Mtx m;
+ const int h = GAME_H;
+ const int top = 0;
+ const int w = GAME_W * 11 / 10;
+ const int left = 0;
+ guOrtho(m, top, h - top, left, w - left, 0, 1);
+ GX_LoadProjectionMtx(m, GX_ORTHOGRAPHIC);
+ _projTop = (h - GAME_H) / 2;
+ _projLeft = (w - GAME_W) / 2;
+
+ GX_InvVtxCache();
+ GX_InvalidateTexAll();
+
+ GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_S16, 0);
+ GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
+ GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
+
+ GX_ClearVtxDesc();
+ GX_SetVtxDesc(GX_VA_PTNMTXIDX, GX_PNMTX0);
+ GX_SetVtxDesc(GX_VA_TEX0MTXIDX, GX_TEXMTX0);
+ GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
+ GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
+ GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
+
+ GX_InitTlutObj(&_tlutObj, _clut, GX_TL_RGB565, 256);
+ GX_InitTexObjCI(&_texObj, _texture, GAME_W, GAME_H, GX_TF_CI8, GX_CLAMP, GX_CLAMP, GX_FALSE, GX_TLUT0);
+ GX_InitTexObjFilterMode(&_texObj, GX_LIN_MIP_NEAR, GX_LINEAR); // GX_NEAR, GX_NEAR);
+}
+
+void System_Wii::finiGX() {
+ GX_AbortFrame();
+ GX_Flush();
+}
+
+void System_Wii::drawTextureGX(int x, int y) {
+ Mtx m;
+ guMtxIdentity(m);
+ guMtxTrans(m, x, y, 0);
+ GX_LoadPosMtxImm(m, GX_PNMTX0);
+
+ const int x1 = _projLeft;
+ const int y1 = _projTop;
+ const int x2 = x1 + GAME_W;
+ const int y2 = y1 + GAME_H;
+
+ GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
+
+ GX_Position3s16(x1, y1, 0);
+ GX_Color1u32(0xFFFFFFFF);
+ GX_TexCoord2f32(0, 0);
+
+ GX_Position3s16(x2, y1, 0);
+ GX_Color1u32(0xFFFFFFFF);
+ GX_TexCoord2f32(1, 0);
+
+ GX_Position3s16(x2, y2, 0);
+ GX_Color1u32(0xFFFFFFFF);
+ GX_TexCoord2f32(1, 1);
+
+ GX_Position3s16(x1, y2, 0);
+ GX_Color1u32(0xFFFFFFFF);
+ GX_TexCoord2f32(0, 1);
+
+ GX_End();
+}
diff --git a/Src/Global/util.cpp b/Src/Global/util.cpp
new file mode 100644
index 0000000..8a3be0d
--- /dev/null
+++ b/Src/Global/util.cpp
@@ -0,0 +1,53 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifdef __ANDROID__
+#define LOG_TAG "HodJni"
+#include
+#endif
+#include
+#include
+extern void System_printLog(FILE *, const char *s);
+extern void System_fatalError(const char *s);
+
+int g_debugMask;
+
+void debug(int mask, const char *msg, ...) {
+ char buf[1024];
+ if (mask & g_debugMask) {
+ va_list va;
+ va_start(va, msg);
+ vsprintf(buf, msg, va);
+ va_end(va);
+#ifdef __ANDROID__
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "%s", buf);
+#endif
+ System_printLog(stdout, buf);
+ }
+}
+
+void error(const char *msg, ...) {
+ char buf[1024];
+ va_list va;
+ va_start(va, msg);
+ vsprintf(buf, msg, va);
+ va_end(va);
+#ifdef __ANDROID__
+ __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "%s", buf);
+#endif
+ System_fatalError(buf);
+}
+
+void warning(const char *msg, ...) {
+ char buf[1024];
+ va_list va;
+ va_start(va, msg);
+ vsprintf(buf, msg, va);
+ va_end(va);
+#ifdef __ANDROID__
+ __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "%s", buf);
+#endif
+ System_printLog(stderr, buf);
+}
diff --git a/Src/Global/util.h b/Src/Global/util.h
new file mode 100644
index 0000000..6edbbea
--- /dev/null
+++ b/Src/Global/util.h
@@ -0,0 +1,32 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef UTIL_H__
+#define UTIL_H__
+
+#include "intern.h"
+
+enum {
+ kDebug_GAME = 1 << 0,
+ kDebug_RESOURCE = 1 << 1,
+ kDebug_ANDY = 1 << 2,
+ kDebug_SOUND = 1 << 3,
+ kDebug_PAF = 1 << 4,
+ kDebug_MONSTER = 1 << 5,
+ kDebug_SWITCHES = 1 << 6, // 'lar1' and 'lar2' levels
+ kDebug_MENU = 1 << 7
+};
+
+extern int g_debugMask;
+
+void debug(int mask, const char *msg, ...);
+void error(const char *msg, ...);
+void warning(const char *msg, ...);
+
+#ifdef NDEBUG
+#define debug(x, ...)
+#endif
+
+#endif // UTIL_H__
diff --git a/Src/Global/video.cpp b/Src/Global/video.cpp
new file mode 100644
index 0000000..d48e6b0
--- /dev/null
+++ b/Src/Global/video.cpp
@@ -0,0 +1,515 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "video.h"
+#include "mdec.h"
+#include "system.h"
+
+static const bool kUseShadowColorLut = false;
+
+Video::Video() {
+ _displayShadowLayer = false;
+ _drawLine.x1 = 0;
+ _drawLine.y1 = 0;
+ _drawLine.x2 = W - 1;
+ _drawLine.y2 = H - 1;
+ _shadowLayer = (uint8_t *)malloc(W * H + 1); // projectionData offset can be equal to W * H
+ _frontLayer = (uint8_t *)malloc(W * H);
+ _backgroundLayer = (uint8_t *)malloc(W * H);
+ if (kUseShadowColorLut) {
+ _shadowColorLookupTable = (uint8_t *)malloc(256 * 256); // shadowLayer, frontLayer
+ } else {
+ _shadowColorLookupTable = 0;
+ }
+ _shadowScreenMaskBuffer = (uint8_t *)malloc(256 * 192 * 2 + 256 * 4);
+ for (int i = 144; i < 256; ++i) {
+ _shadowColorLut[i] = i;
+ }
+ _transformShadowBuffer = 0;
+ _transformShadowLayerDelta = 0;
+ memset(&_mdec, 0, sizeof(_mdec));
+}
+
+Video::~Video() {
+ free(_shadowLayer);
+ free(_frontLayer);
+ free(_backgroundLayer);
+ free(_shadowColorLookupTable);
+ free(_shadowScreenMaskBuffer);
+ free(_mdec.planes[kOutputPlaneY].ptr);
+ free(_mdec.planes[kOutputPlaneCb].ptr);
+ free(_mdec.planes[kOutputPlaneCr].ptr);
+}
+
+void Video::initPsx() {
+ static const int w = (W + 15) & ~15;
+ static const int h = (H + 15) & ~15;
+ static const int w2 = w / 2;
+ static const int h2 = h / 2;
+ _mdec.planes[kOutputPlaneY].ptr = (uint8_t *)malloc(w * h);
+ _mdec.planes[kOutputPlaneY].pitch = w;
+ _mdec.planes[kOutputPlaneCb].ptr = (uint8_t *)malloc(w2 * h2);
+ _mdec.planes[kOutputPlaneCb].pitch = w2;
+ _mdec.planes[kOutputPlaneCr].ptr = (uint8_t *)malloc(w2 * h2);
+ _mdec.planes[kOutputPlaneCr].pitch = w2;
+}
+
+static int colorBrightness(int r, int g, int b) {
+ return (r + g * 2) * 19 + b * 7;
+}
+
+void Video::updateGamePalette(const uint16_t *pal) {
+ for (int i = 0; i < 256 * 3; ++i) {
+ _palette[i] = pal[i] >> 8;
+ }
+ g_system->setPalette(_palette, 256, 8);
+}
+
+void Video::updateGameDisplay(uint8_t *buf) {
+ g_system->copyRect(0, 0, W, H, buf, 256);
+ if (_mdec.planes[kOutputPlaneY].ptr) {
+ updateYuvDisplay();
+ }
+}
+
+void Video::updateYuvDisplay() {
+ g_system->copyYuv(Video::W, Video::H, _mdec.planes[0].ptr, _mdec.planes[0].pitch, _mdec.planes[1].ptr, _mdec.planes[1].pitch, _mdec.planes[2].ptr, _mdec.planes[2].pitch);
+}
+
+void Video::copyYuvBackBuffer() {
+}
+
+void Video::updateScreen() {
+ g_system->updateScreen(true);
+}
+
+void Video::clearBackBuffer() {
+ g_system->fillRect(0, 0, W, H, CLEAR_COLOR);
+}
+
+void Video::clearPalette() {
+ memset(_palette, 0, sizeof(_palette));
+ g_system->clearPalette();
+}
+
+void Video::decodeSPR(const uint8_t *src, uint8_t *dst, int x, int y, uint8_t flags, uint16_t spr_w, uint16_t spr_h) {
+ if (y >= H) {
+ return;
+ } else if (y < 0) {
+ flags |= kSprClipTop;
+ }
+ const int y2 = y + spr_h - 1;
+ if (y2 < 0) {
+ return;
+ } else if (y2 >= H) {
+ flags |= kSprClipBottom;
+ }
+
+ if (x >= W) {
+ return;
+ } else if (x < 0) {
+ flags |= kSprClipLeft;
+ }
+ const int x2 = x + spr_w - 1;
+ if (x2 < 0) {
+ return;
+ } else if (x2 >= W) {
+ flags |= kSprClipRight;
+ }
+
+ if (flags & kSprHorizFlip) {
+ x = x2;
+ }
+ if (flags & kSprVertFlip) {
+ y = y2;
+ }
+ const int xOrig = x;
+ while (1) {
+ uint8_t *p = dst + y * W + x;
+ int code = *src++;
+ int count = code & 0x3F;
+ int clippedCount = count;
+ if (y < 0 || y >= H) {
+ clippedCount = 0;
+ }
+ switch (code >> 6) {
+ case 0:
+ if ((flags & (kSprHorizFlip | kSprClipLeft | kSprClipRight)) == 0) {
+ memcpy(p, src, clippedCount);
+ x += count;
+ } else if (flags & kSprHorizFlip) {
+ for (int i = 0; i < clippedCount; ++i) {
+ if (x - i >= 0 && x - i < W) {
+ p[-i] = src[i];
+ }
+ }
+ x -= count;
+ } else {
+ for (int i = 0; i < clippedCount; ++i) {
+ if (x + i >= 0 && x + i < W) {
+ p[i] = src[i];
+ }
+ }
+ x += count;
+ }
+ src += count;
+ break;
+ case 1:
+ code = *src++;
+ if ((flags & (kSprHorizFlip | kSprClipLeft | kSprClipRight)) == 0) {
+ memset(p, code, clippedCount);
+ x += count;
+ } else if (flags & kSprHorizFlip) {
+ for (int i = 0; i < clippedCount; ++i) {
+ if (x - i >= 0 && x - i < W) {
+ p[-i] = code;
+ }
+ }
+ x -= count;
+ } else {
+ for (int i = 0; i < clippedCount; ++i) {
+ if (x + i >= 0 && x + i < W) {
+ p[i] = code;
+ }
+ }
+ x += count;
+ }
+ break;
+ case 2:
+ if (count == 0) {
+ count = *src++;
+ }
+ if (flags & kSprHorizFlip) {
+ x -= count;
+ } else {
+ x += count;
+ }
+ break;
+ case 3:
+ if (count == 0) {
+ count = *src++;
+ if (count == 0) {
+ return;
+ }
+ }
+ if (flags & kSprVertFlip) {
+ y -= count;
+ } else {
+ y += count;
+ }
+ if (flags & kSprHorizFlip) {
+ x = xOrig - *src++;
+ } else {
+ x = xOrig + *src++;
+ }
+ break;
+ }
+ }
+}
+
+void Video::decodeRLE(const uint8_t *src, uint8_t *dst, int size) {
+ int count;
+
+ while (size > 0) {
+ int8_t code = *src++;
+ if (code < 0) {
+ count = 1 - code;
+ const uint8_t color = *src++;
+ memset(dst, color, count);
+ } else {
+ count = code + 1;
+ memcpy(dst, src, count);
+ src += count;
+ }
+ dst += count;
+ size -= count;
+ }
+ assert(size == 0);
+}
+
+// https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
+int Video::computeLineOutCode(int x, int y) {
+ int mask = 0;
+ if (y > _drawLine.y2) mask |= 1 << 24;
+ if (x > _drawLine.x2) mask |= 1 << 16;
+ if (y < _drawLine.y1) mask |= 1 << 8;
+ if (x < _drawLine.x1) mask |= 1;
+ return mask;
+}
+
+bool Video::clipLineCoords(int &x1, int &y1, int &x2, int &y2) {
+ int mask1 = computeLineOutCode(x2, y2);
+ while (1) {
+ const int mask2 = computeLineOutCode(x1, y1);
+ if (mask2 == 0 && mask1 == 0) {
+ break;
+ }
+ if ((mask1 & mask2) != 0) {
+ return true;
+ }
+ if (mask2 & 1) { // (x < _drawLine.x1)
+ y1 += (y2 - y1) * (_drawLine.x1 - x1) / (x2 - x1);
+ x1 = _drawLine.x1;
+ continue;
+ }
+ if (mask2 & 0x100) { // (y < _drawLine.y1)
+ x1 += (x2 - x1) * (_drawLine.y1 - y1) / (y2 - y1);
+ y1 = _drawLine.y1;
+ continue;
+ }
+ if (mask2 & 0x10000) { // (x > _drawLine.x2)
+ y1 += (y2 - y1) * (_drawLine.x2 - x1) / (x2 - x1);
+ x1 = _drawLine.x2;
+ continue;
+ }
+ if (mask2 & 0x1000000) { // (y > _drawLine.y2)
+ x1 += (x2 - x1) * (_drawLine.y2 - y1) / (y2 - y1);
+ y1 = _drawLine.y2;
+ continue;
+ }
+ SWAP(x1, x2);
+ SWAP(y1, y2);
+ assert(mask2 == 0);
+ mask1 = 0;
+ }
+ return false;
+}
+
+void Video::drawLine(int x1, int y1, int x2, int y2, uint8_t color) {
+ if (clipLineCoords(x1, y1, x2, y2)) {
+ return;
+ }
+ assert(x1 >= _drawLine.x1 && x1 <= _drawLine.x2);
+ assert(y1 >= _drawLine.y1 && y1 <= _drawLine.y2);
+ assert(x2 >= _drawLine.x1 && x2 <= _drawLine.x2);
+ assert(y2 >= _drawLine.y1 && y2 <= _drawLine.y2);
+ int dstPitch = W;
+ int dx = x2 - x1;
+ if (dx == 0) {
+ int dy = y2 - y1;
+ if (dy < 0) {
+ y1 += dy;
+ dy = -dy;
+ }
+ uint8_t *dst = _frontLayer + y1 * W + x1;
+ for (int i = 0; i <= dy; ++i) {
+ *dst = color;
+ dst += dstPitch;
+ }
+ return;
+ }
+ if (dx < 0) {
+ x1 += dx;
+ dx = -dx;
+ SWAP(y1, y2);
+ }
+ uint8_t *dst = _frontLayer + y1 * W + x1;
+ int dy = y2 - y1;
+ if (dy == 0) {
+ memset(dst, color, dx);
+ return;
+ }
+ if (dy < 0) {
+ dy = -dy;
+ dstPitch = -dstPitch;
+ }
+ int step = 0;
+ if (dx > dy) {
+ SWAP(dx, dy);
+ dx *= 2;
+ const int stepInc = dy * 2;
+ step -= stepInc;
+ for (int i = 0; i <= dy; ++i) {
+ *dst = color;
+ step += dx;
+ if (step >= 0) {
+ step -= stepInc;
+ dst += dstPitch;
+ }
+ ++dst;
+ }
+ } else {
+ dx *= 2;
+ const int stepInc = dy * 2;
+ step -= stepInc;
+ for (int i = 0; i <= dy; ++i) {
+ *dst = color;
+ step += dx;
+ if (step >= 0) {
+ step -= stepInc;
+ ++dst;
+ }
+ dst += dstPitch;
+ }
+ }
+}
+
+static uint8_t lookupColor(uint8_t a, uint8_t b, const uint8_t *lut) {
+ return (a >= 144 && b < 144) ? lut[b] : b;
+}
+
+void Video::applyShadowColors(int x, int y, int src_w, int src_h, int dst_pitch, int src_pitch, uint8_t *dst1, uint8_t *dst2, uint8_t *src1, uint8_t *src2) {
+ assert(dst1 == _shadowLayer);
+ assert(dst2 == _frontLayer);
+ // src1 == projectionData
+ // src2 == shadowPalette
+
+ dst2 += y * dst_pitch + x;
+ for (int j = 0; j < src_h; ++j) {
+ for (int i = 0; i < src_w; ++i) {
+ int offset = READ_LE_UINT16(src1); src1 += 2;
+ assert(offset <= W * H);
+ if (kUseShadowColorLut) {
+ // build lookup offset
+ // msb : _shadowLayer[ _projectionData[ (x, y) ] ]
+ // lsb : _frontLayer[ (x, y) ]
+ offset = (dst1[offset] << 8) | dst2[i];
+
+ // lookup color matrix
+ // if msb < 144 : _frontLayer.color
+ // if msb >= 144 : if _frontLayer.color < 144 ? shadowPalette[ _frontLayer.color ] : _frontLayer.color
+ dst2[i] = _shadowColorLookupTable[offset];
+ } else {
+ dst2[i] = lookupColor(_shadowLayer[offset], dst2[i], _shadowColorLut);
+ }
+ }
+ dst2 += dst_pitch;
+ }
+}
+
+void Video::buildShadowColorLookupTable(const uint8_t *src, uint8_t *dst) {
+ if (kUseShadowColorLut) {
+ assert(dst == _shadowColorLookupTable);
+ // 256x256
+ // 0..143 : 0..255
+ // 144..255 : src[0..143] 144..255
+ for (int i = 0; i < 144; ++i) {
+ for (int j = 0; j < 256; ++j) {
+ *dst++ = j;
+ }
+ }
+ for (int i = 0; i < 112; ++i) {
+ memcpy(dst, src, 144);
+ dst += 144;
+ for (int j = 0; j < 112; ++j) {
+ *dst++ = 144 + j;
+ }
+ }
+ }
+ memcpy(_shadowColorLut, src, 144); // indexes 144-256 are not remapped
+ if (0) {
+ // lookup[a * 256 + b]
+ //
+ // if (a < 144) return b;
+ // else if (b < 144) return src[b]
+ // else return b;
+ //
+ // return (a >= 144 && b < 144) ? src[b] : b;
+ for (int a = 0; a < 256; ++a) {
+ for (int b = 0; b < 256; ++b) {
+ const int res1 = (a >= 144 && b < 144) ? src[b] : b;
+ const int res2 = dst[a * 256 + b - 65536];
+ if (res1 != res2) {
+ fprintf(stdout, "buildShadowColorLookupTable a %d b %d res1 %d res2 %d\n", a, b, res1, res2);
+ }
+ assert(res1 == res2);
+ }
+ }
+ }
+}
+
+// returns the font index
+uint8_t Video::findStringCharacterFontIndex(uint8_t chr) const {
+ // bugfix: the original code seems to ignore the last 3 entries
+ for (int i = 0; i < 39 * 2; i += 2) {
+ if (_fontCharactersTable[i] == chr) {
+ return _fontCharactersTable[i + 1];
+ }
+ }
+ return 255;
+}
+
+void Video::drawStringCharacter(int x, int y, uint8_t chr, uint8_t color, uint8_t *dst) {
+ const uint8_t *p = _font + ((chr & 15) + (chr >> 4) * 256) * 16;
+ dst += y * W + x;
+ for (int j = 0; j < 16; ++j) {
+ for (int i = 0; i < 16; ++i) {
+ if (p[i] != 0) {
+ dst[i] = color;
+ }
+ }
+ p += 16 * 16;
+ dst += W;
+ }
+}
+
+void Video::drawString(const char *s, int x, int y, uint8_t color, uint8_t *dst) {
+ for (int i = 0; s[i]; ++i) {
+ uint8_t chr = s[i];
+ if (chr >= 'a' && chr <= 'z') {
+ chr += 'A' - 'a';
+ }
+ if (chr != ' ') {
+ chr = findStringCharacterFontIndex(chr);
+ if (chr == 255) {
+ continue;
+ }
+ drawStringCharacter(x, y, chr, color, dst);
+ }
+ x += 8;
+ }
+}
+
+uint8_t Video::findWhiteColor() const {
+ uint8_t color = 0;
+ int whiteQuant = 0;
+ for (int i = 0; i < 256; ++i) {
+ const int q = colorBrightness(_palette[i * 3], _palette[i * 3 + 1], _palette[i * 3 + 2]);
+ if (q > whiteQuant) {
+ whiteQuant = q;
+ color = i;
+ }
+ }
+ return color;
+}
+
+void Video::decodeBackgroundPsx(const uint8_t *src, int size, int w, int h, int x, int y) {
+ _mdec.x = x;
+ _mdec.y = y;
+ _mdec.w = w;
+ _mdec.h = h;
+ if (size < 0) { // size not available
+ size = w * h * sizeof(uint16_t);
+ }
+ decodeMDEC(src, size, 0, 0, w, h, &_mdec);
+ copyYuvBackBuffer();
+}
+
+void Video::decodeBackgroundOverlayPsx(const uint8_t *src, int x, int y) {
+ const uint16_t size = READ_LE_UINT16(src + 2);
+ if (size > 6) {
+ const int count = READ_LE_UINT32(src + 4);
+ assert(count >= 1 && count <= 3);
+ int offset = 8;
+ for (int i = 0; i < count && offset < size; ++i) {
+ _mdec.x = x + src[offset];
+ _mdec.y = y + src[offset + 1];
+ const int len = READ_LE_UINT16(src + offset + 2);
+ _mdec.w = src[offset + 4] * 16;
+ _mdec.h = src[offset + 5] * 16;
+ const int mborderlen = src[offset + 6];
+ const int mborderalign = src[offset + 7];
+ const uint8_t *data = &src[offset + 8];
+ if (mborderalign == 0) {
+ decodeMDEC(data, len - 8, 0, 0, _mdec.w, _mdec.h, &_mdec);
+ } else {
+ // different macroblocks order
+ decodeMDEC(data + mborderalign, len - 8 - mborderalign, data, mborderlen, _mdec.w, _mdec.h, &_mdec);
+ }
+ offset += len;
+ }
+ assert(offset == size + 2);
+ }
+}
diff --git a/Src/Global/video.h b/Src/Global/video.h
new file mode 100644
index 0000000..59b6023
--- /dev/null
+++ b/Src/Global/video.h
@@ -0,0 +1,82 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef VIDEO_H__
+#define VIDEO_H__
+
+#include "intern.h"
+#include "mdec.h"
+
+enum {
+ kSprHorizFlip = 1 << 0, // left-right
+ kSprVertFlip = 1 << 1, // up-down
+ kSprClipTop = 1 << 2,
+ kSprClipBottom = 1 << 3,
+ kSprClipLeft = 1 << 4,
+ kSprClipRight = 1 << 5
+};
+
+struct Video {
+ enum {
+ CLEAR_COLOR = 0xC4,
+ W = 256,
+ H = 192
+ };
+
+ static const uint8_t _fontCharactersTable[78];
+
+ uint8_t _palette[256 * 3];
+ uint16_t _displayPaletteBuffer[256 * 3];
+ bool _paletteChanged;
+ bool _displayShadowLayer;
+ uint8_t *_shadowLayer;
+ uint8_t *_frontLayer;
+ uint8_t *_backgroundLayer;
+ uint8_t *_shadowColorLookupTable;
+ uint16_t _fadePaletteBuffer[256 * 3];
+ uint8_t *_shadowScreenMaskBuffer;
+ uint8_t *_transformShadowBuffer;
+ uint8_t _transformShadowLayerDelta;
+ uint8_t _shadowColorLut[256];
+ const uint8_t *_font;
+
+ struct {
+ int x1, y1;
+ int x2, y2;
+ } _drawLine;
+
+ MdecOutput _mdec;
+
+ Video();
+ ~Video();
+
+ void initPsx();
+
+ void updateGamePalette(const uint16_t *pal);
+ void updateGameDisplay(uint8_t *buf);
+ void updateYuvDisplay();
+ void copyYuvBackBuffer();
+ void updateScreen();
+ void clearBackBuffer();
+ void clearPalette();
+ static void decodeRLE(const uint8_t *src, uint8_t *dst, int size);
+ static void decodeSPR(const uint8_t *src, uint8_t *dst, int x, int y, uint8_t flags, uint16_t spr_w, uint16_t spr_h);
+ int computeLineOutCode(int x, int y);
+ bool clipLineCoords(int &x1, int &y1, int &x2, int &y2);
+ void drawLine(int x1, int y1, int x2, int y2, uint8_t color);
+ void applyShadowColors(int x, int y, int src_w, int src_h, int dst_pitch, int src_pitch, uint8_t *dst1, uint8_t *dst2, uint8_t *src1, uint8_t *src2);
+ void buildShadowColorLookupTable(const uint8_t *src, uint8_t *dst);
+
+ uint8_t findStringCharacterFontIndex(uint8_t chr) const;
+ void drawStringCharacter(int x, int y, uint8_t chr, uint8_t color, uint8_t *dst);
+ void drawString(const char *s, int x, int y, uint8_t color, uint8_t *dst);
+ uint8_t findWhiteColor() const;
+
+ void decodeBackgroundPsx(const uint8_t *src, int size, int w, int h, int x = 0, int y = 0);
+ void decodeBackgroundOverlayPsx(const uint8_t *src, int x = 0, int y = 0);
+};
+
+#endif // VIDEO_H__
+
diff --git a/Src/Vita_&_Switch/.gitignore b/Src/Vita_&_Switch/.gitignore
new file mode 100644
index 0000000..e551916
--- /dev/null
+++ b/Src/Vita_&_Switch/.gitignore
@@ -0,0 +1,6 @@
+*.d
+*.o
+build
+hode
+hode.ini
+setup.cfg
diff --git a/Src/Vita_&_Switch/.gitmodules b/Src/Vita_&_Switch/.gitmodules
new file mode 100644
index 0000000..07f6af1
--- /dev/null
+++ b/Src/Vita_&_Switch/.gitmodules
@@ -0,0 +1,8 @@
+[submodule "3p/inih"]
+ path = 3p/inih
+ url = https://github.com/benhoyt/inih.git
+ ignore = dirty
+[submodule "3p/libxbr-standalone"]
+ path = 3p/libxbr-standalone
+ url = https://github.com/Treeki/libxbr-standalone.git
+ ignore = dirty
diff --git a/SRC/res/bg.png b/Src/Vita_&_Switch/3p/res/bg.png
similarity index 100%
rename from SRC/res/bg.png
rename to Src/Vita_&_Switch/3p/res/bg.png
diff --git a/SRC/res/icon.jpg b/Src/Vita_&_Switch/3p/res/icon.jpg
similarity index 100%
rename from SRC/res/icon.jpg
rename to Src/Vita_&_Switch/3p/res/icon.jpg
diff --git a/SRC/res/icon0.png b/Src/Vita_&_Switch/3p/res/icon0.png
similarity index 100%
rename from SRC/res/icon0.png
rename to Src/Vita_&_Switch/3p/res/icon0.png
diff --git a/SRC/res/startup.png b/Src/Vita_&_Switch/3p/res/startup.png
similarity index 100%
rename from SRC/res/startup.png
rename to Src/Vita_&_Switch/3p/res/startup.png
diff --git a/SRC/res/template.xml b/Src/Vita_&_Switch/3p/res/template.xml
similarity index 100%
rename from SRC/res/template.xml
rename to Src/Vita_&_Switch/3p/res/template.xml
diff --git a/Src/Vita_&_Switch/CMakeLists.txt b/Src/Vita_&_Switch/CMakeLists.txt
new file mode 100644
index 0000000..30d41b4
--- /dev/null
+++ b/Src/Vita_&_Switch/CMakeLists.txt
@@ -0,0 +1,55 @@
+project(hode)
+
+cmake_minimum_required(VERSION 3.0)
+
+include(FindPkgConfig)
+
+set(CMAKE_CXX_FLAGS "-g -Wall -pedantic -MMD ${CMAKE_CXX_FLAGS}")
+if(VITA)
+ set(ENV{PKG_CONFIG_PATH} "$ENV{VITASDK}/arm-vita-eabi/lib/pkgconfig")
+endif(VITA)
+
+pkg_check_modules(SDL2 sdl2 REQUIRED)
+
+include_directories(
+ ${SDL2_INCLUDE_DIRS}
+)
+
+file(GLOB SRC *.cpp 3p/inih/ini.c 3p/libxbr-standalone/xbr.c)
+list(FILTER SRC EXCLUDE REGEX ".*android.cpp|system_psp.cpp|system_wii.cpp")
+add_executable(${CMAKE_PROJECT_NAME}
+ ${SRC}
+)
+
+target_link_libraries(${CMAKE_PROJECT_NAME}
+ ${SDL2_STATIC_LIBRARIES}
+)
+
+if(SWITCH_LIBNX)
+ set(CMAKE_C_FLAGS "-O2 -ffunction-sections -fdata-sections -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -ftls-model=local-exec")
+ set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")
+ include_directories($ENV{DEVKITPRO}/libnx/include)
+ add_definitions(-D__SWITCH__)
+ add_custom_target(${CMAKE_PROJECT_NAME}.nro
+ DEPENDS ${CMAKE_PROJECT_NAME}
+ COMMAND nacptool --create "Heart of Darkness" "cyx, usineur" "0.2.9" ${CMAKE_PROJECT_NAME}.nacp
+ COMMAND elf2nro ${CMAKE_PROJECT_NAME} ${CMAKE_PROJECT_NAME}.nro --icon=${CMAKE_SOURCE_DIR}/3p/res/icon.jpg --nacp=${CMAKE_PROJECT_NAME}.nacp
+ )
+ add_custom_target(nxlink
+ COMMAND nxlink -a $(SWITCHIP) ${CMAKE_PROJECT_NAME}.nro -s -p ${CMAKE_PROJECT_NAME}/${CMAKE_PROJECT_NAME}.nro
+ DEPENDS ${CMAKE_PROJECT_NAME}.nro
+ )
+endif(SWITCH_LIBNX)
+
+if(VITA)
+ include("${VITASDK}/share/vita.cmake" REQUIRED)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -fpermissive")
+ vita_create_self(eboot.bin ${CMAKE_PROJECT_NAME} UNSAFE)
+ vita_create_vpk(${CMAKE_PROJECT_NAME}.vpk "HODEVITA1" eboot.bin
+ NAME ${CMAKE_PROJECT_NAME}
+ FILE 3p/res/icon0.png sce_sys/icon0.png
+ FILE 3p/res/bg.png sce_sys/livearea/contents/bg.png
+ FILE 3p/res/startup.png sce_sys/livearea/contents/startup.png
+ FILE 3p/res/template.xml sce_sys/livearea/contents/template.xml
+)
+endif(VITA)
diff --git a/Src/Vita_&_Switch/Makefile b/Src/Vita_&_Switch/Makefile
new file mode 100644
index 0000000..a2eb586
--- /dev/null
+++ b/Src/Vita_&_Switch/Makefile
@@ -0,0 +1,27 @@
+
+SDL_CFLAGS = `sdl2-config --cflags`
+SDL_LIBS = `sdl2-config --libs`
+
+CPPFLAGS += -g -Wall -Wpedantic $(SDL_CFLAGS) $(DEFINES) -MMD
+
+SRCS = andy.cpp benchmark.cpp fileio.cpp fs_posix.cpp game.cpp \
+ level1_rock.cpp level2_fort.cpp level3_pwr1.cpp level4_isld.cpp \
+ level5_lava.cpp level6_pwr2.cpp level7_lar1.cpp level8_lar2.cpp level9_dark.cpp \
+ lzw.cpp main.cpp mdec.cpp menu.cpp mixer.cpp monsters.cpp paf.cpp random.cpp \
+ resource.cpp screenshot.cpp sound.cpp staticres.cpp system_sdl2.cpp \
+ util.cpp video.cpp
+
+SCALERS := scaler_nearest.cpp scaler_xbr.cpp
+
+OBJS = $(SRCS:.cpp=.o) $(SCALERS:.cpp=.o) 3p/inih/ini.o 3p/libxbr-standalone/xbr.o
+DEPS = $(SRCS:.cpp=.d) $(SCALERS:.cpp=.d) 3p/inih/ini.d 3p/libxbr-standalone/xbr.d
+
+all: hode
+
+hode: $(OBJS)
+ $(CXX) $(LDFLAGS) -o $@ $^ $(SDL_LIBS)
+
+clean:
+ rm -f $(OBJS) $(DEPS)
+
+-include $(DEPS)
diff --git a/Src/Vita_&_Switch/andy.cpp b/Src/Vita_&_Switch/andy.cpp
new file mode 100644
index 0000000..e543912
--- /dev/null
+++ b/Src/Vita_&_Switch/andy.cpp
@@ -0,0 +1,1711 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "game.h"
+#include "random.h"
+#include "util.h"
+
+// probably rename this to anim.cpp as this updates most LvlObject, not only Andy
+
+static const uint8_t andyOp1LookupTable[] = {
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 4, 3, 4, 2, 2, 1, 1,
+ 4, 3, 4, 3, 5, 6, 7, 8, 7, 8, 5, 6, 8, 7, 6, 5,
+ 6, 5, 8, 7, 9, 9, 9, 9, 10, 10, 11, 11, 12, 13, 12, 13,
+ 11, 11, 10, 10, 13, 12, 13, 12, 14, 15, 16, 17, 15, 14, 17, 16,
+ 16, 17, 14, 15, 17, 16, 15, 14, 18, 19, 18, 19, 19, 18, 19, 18,
+ 20, 20, 21, 21, 21, 21, 20, 20, 22, 23, 22, 23, 23, 22, 23, 22,
+ 24, 24, 25, 25, 25, 25, 24, 24, 26, 26, 26, 26, 27, 27, 27, 27
+};
+
+int Game::moveAndyObjectOp1(int op) {
+ op = andyOp1LookupTable[op];
+ switch (op) {
+ case 0:
+ return 1;
+ case 1:
+ return _directionKeyMask & 1;
+ case 2:
+ return (_directionKeyMask >> 2) & 1;
+ case 3:
+ return (_directionKeyMask >> 1) & 1;
+ case 4:
+ return (_directionKeyMask >> 3) & 1;
+ case 5:
+ return ((_directionKeyMask & 3) == 3) ? 1 : 0;
+ case 6:
+ return ((_directionKeyMask & 9) == 9) ? 1 : 0;
+ case 7:
+ return ((_directionKeyMask & 6) == 6) ? 1 : 0;
+ case 8:
+ return ((_directionKeyMask & 12) == 12) ? 1 : 0;
+ case 9:
+ return (_directionKeyMask == 0) ? 1 : 0;
+ case 10:
+ return (~_directionKeyMask) & 1;
+ case 11:
+ return ((~_directionKeyMask) >> 2) & 1;
+ case 12:
+ return ((~_directionKeyMask) >> 1) & 1;
+ case 13:
+ return ((~_directionKeyMask) >> 3) & 1;
+ case 14:
+ return _directionKeyMask & 3;
+ case 15:
+ return _directionKeyMask & 9;
+ case 16:
+ return _directionKeyMask & 6;
+ case 17:
+ return _directionKeyMask & 12;
+ case 18:
+ return (_directionKeyMask == 2) ? 1 : 0;
+ case 19:
+ return (_directionKeyMask == 8) ? 1 : 0;
+ case 20:
+ return (_directionKeyMask == 1) ? 1 : 0;
+ case 21:
+ return (_directionKeyMask == 4) ? 1 : 0;
+ case 22:
+ return ((_directionKeyMask & 13) != 0) ? 1 : 0;
+ case 23:
+ return ((_directionKeyMask & 7) != 0) ? 1 : 0;
+ case 24:
+ return ((_directionKeyMask & 14) != 0) ? 1 : 0;
+ case 25:
+ return ((_directionKeyMask & 11) != 0) ? 1 : 0;
+ case 26:
+ return _directionKeyMask & 5;
+ case 27:
+ return _directionKeyMask & 10;
+ default:
+ error("moveAndyObjectOp1 op %d", op);
+ break;
+ }
+ return 0;
+}
+
+int Game::moveAndyObjectOp2(int op) {
+ switch (op) {
+ case 0:
+ return 1;
+ case 1:
+ return _actionKeyMask & 1;
+ case 2:
+ return (_actionKeyMask >> 1) & 1;
+ case 3:
+ return (_actionKeyMask >> 2) & 1;
+ case 4:
+ return ((_actionKeyMask & 3) == 3) ? 1 : 0;
+ case 5:
+ return ((_actionKeyMask & 6) == 6) ? 1 : 0;
+ case 6:
+ return ((_actionKeyMask & 5) == 5) ? 1 : 0;
+ case 7:
+ return ((_actionKeyMask & 7) == 7) ? 1 : 0;
+ case 8:
+ return (_actionKeyMask >> 3) & 1;
+ case 9:
+ return (_actionKeyMask == 0) ? 1 : 0;
+ case 10:
+ return (~_actionKeyMask) & 1;
+ case 11:
+ return ((~_actionKeyMask) >> 1) & 1;
+ case 12:
+ return ((~_actionKeyMask) >> 2) & 1;
+ case 13:
+ return (_actionKeyMask >> 4) & 1;
+ case 14:
+ return (_actionKeyMask >> 5) & 1;
+ case 15:
+ return ((_actionKeyMask & 48) == 48) ? 1 : 0;
+ case 16:
+ return (_actionKeyMask >> 6) & 1;
+ case 17:
+ return (_actionKeyMask == 1) ? 1 : 0;
+ case 18:
+ return (_actionKeyMask == 2) ? 1 : 0;
+ case 19:
+ return (_actionKeyMask == 4) ? 1 : 0;
+ case 20:
+ return (_actionKeyMask >> 7) & 1;
+ case 21:
+ return (_actionKeyMask == 8) ? 1 : 0;
+ case 22:
+ return (_actionKeyMask == 16) ? 1 : 0;
+ case 23:
+ return (_actionKeyMask == 32) ? 1 : 0;
+ case 24:
+ return (_actionKeyMask == 64) ? 1 : 0;
+ case 25:
+ return (_actionKeyMask == 128) ? 1 : 0;
+ case 26:
+ return ((_actionKeyMask & 24) == 24) ? 1 : 0;
+ case 27:
+ return ((_actionKeyMask & 96) == 96) ? 1 : 0;
+ case 28:
+ return ((_actionKeyMask & 192) == 192) ? 1 : 0;
+ case 29:
+ return ((_actionKeyMask & 3) == 0) ? 1 : 0;
+ case 30:
+ return ((_actionKeyMask & 6) == 0) ? 1 : 0;
+ case 31:
+ return ((_actionKeyMask & 5) == 0) ? 1 : 0;
+ case 32:
+ return ((_actionKeyMask & 7) == 0) ? 1 : 0;
+ case 33:
+ return ((~_actionKeyMask) >> 3) & 1;
+ case 34:
+ return ((~_actionKeyMask) >> 4) & 1;
+ case 35:
+ return ((~_actionKeyMask) >> 5) & 1;
+ case 36:
+ return ((~_actionKeyMask) >> 6) & 1;
+ case 37:
+ return ((~_actionKeyMask) >> 7) & 1;
+ case 38:
+ return ((_actionKeyMask & 3) != 0) ? 1 : 0;
+ case 39:
+ return ((_actionKeyMask & 6) != 0) ? 1 : 0;
+ case 40:
+ return ((_actionKeyMask & 5) != 0) ? 1 : 0;
+ case 41:
+ return ((_actionKeyMask & 7) != 0) ? 1 : 0;
+ case 42:
+ return ((_actionKeyMask & 3) == 1) ? 1 : 0;
+ case 43:
+ return ((_actionKeyMask & 5) == 1) ? 1 : 0;
+ case 44:
+ return ((_actionKeyMask & 3) == 2) ? 1 : 0;
+ case 45:
+ return ((_actionKeyMask & 6) == 2) ? 1 : 0;
+ case 46:
+ return ((_actionKeyMask & 5) == 4) ? 1 : 0;
+ case 47:
+ return ((_actionKeyMask & 6) == 4) ? 1 : 0;
+ default:
+ error("moveAndyObjectOp2 op %d", op);
+ break;
+ }
+ return 0;
+}
+
+static const int _andyMoveTable1[] = { 0, 1, -1, 513, 511, 512, -511, -513, -512 };
+
+static const int _andyMoveTable2[] = { 0, 1, 512, 513, 1024, 1025, 1536, 1537 };
+
+static const int _andyMoveTable3[] = { 0, -1, 512, 511, 1024, 1023, 1536, 1535 };
+
+static const int _andyMoveTable4[] = { 0, 1, 512, 1, -511 };
+
+static const int _andyMoveTable5[] = { 0, -1, 512, -1, -513 };
+
+static const int _andyMoveTable6[] = { -1024, -1025, -1026, -512, -513, -514 };
+
+static const int _andyMoveTable7[] = { -1024, -1023, -1022, -512, -511, -510 };
+
+static const int _andyMoveTable8[] = { 0, 512, 1024, 1536 };
+
+static const int _andyMoveTable9[] = { -1024, 1, -512, -1 };
+
+static const int _andyMoveTable10[] = { -1024, -1, -512, 1 };
+
+static const int _andyMoveTable11[] = { -509, -512 };
+
+static const int _andyMoveTable12[] = { -515, -512 };
+
+static const int _andyMoveTable13[] = { 1, 1, 1, 512, -1, -1 };
+
+static const int _andyMoveTable14[] = { -1, -1, -1, 512, 1, 1 };
+
+static const int _andyMoveTable15[] = { 1536, 512, 1, 512 };
+
+static const int _andyMoveTable16[] = { 1536, 512, -1, 512 };
+
+static const int _andyMoveTable17[] = { 1022, -1, 512, 1 };
+
+static const int _andyMoveTable18[] = { 1026, 1, 512, -1 };
+
+static const int _andyMoveTable19[] = { -2, -1, -1, 512, 1, 1 };
+
+static const int _andyMoveTable20[] = { 2, 1, 1, 512, -1, -1 };
+
+static const int _andyMoveTable21[] = { -1, -512, -1, -1, -511 };
+
+static const int _andyMoveTable22[] = { 1, -512, 1, 1, -513 };
+
+static const int _andyMoveTable23[] = { -1024, -512 };
+
+static const int _andyMoveTable24[] = { -1024, -512 };
+
+static const int _andyMoveTable25[] = { 1, -512, 1, 1, -511 };
+
+static const int _andyMoveTable26[] = { -1, -512, -1, -1, -511 };
+
+static const int _andyMoveTable27[] = { 1, 512, 512, 1, 1, 511, -1 };
+
+static const int _andyMoveTable28[] = { -1, 512, 512, -1, -1, 513, 1 };
+
+static const int _andyMoveTable29[] = { 1536, 512 };
+
+static const int _andyMoveTable30[] = { 1536, 512 };
+
+static const int _andyMoveTable31[] = { -1, 512, 512, -1, -1, 513, 1 };
+
+static const int _andyMoveTable32[] = { 1, 512, 512, 1, 1, 511, -1 };
+
+static const int _andyMoveTable33[] = { -1, -1, -1, 512, 1, 1 };
+
+static const int _andyMoveTable34[] = { 1, 1, 1, 512, -1, -1 };
+
+static const int _andyMoveTable35[] = {
+ 0, 0, 8, 0, -8, 0, 8, 8, -8, 8, 0, 8, 8, -8, -8, -8,
+ 0, -8, 16, 0, -16, 0, 16, 16, -16, 16, 0, 16, 16, -16, -16, -16,
+ 24, 0, -24, 0, 24, 24, -24, 24, 0, 24, 24, -24, -24, -24, 0, 0,
+};
+
+static const int _andyMoveTable37[] = { -1021, 1027, 2048, -1027, 1021, 2048, -1021, -2048, -3069, -1027, -2048, -3075 };
+
+static const int _andyMoveTable39[] = { 1027, 2048, 3075 };
+
+static const int _andyMoveTable40[] = { 1021, 2048, 3069 };
+
+static const int _andyMoveTable41[] = { -1025, 2, 1023, 2050 };
+
+static const int _andyMoveTable42[] = { -1023, -2, 1025, 2046 };
+
+static const int _andyMoveTable43[] = { -512, 1536, -515, -509, 1533, 1539 };
+
+int Game::moveAndyObjectOp3(int op) {
+ switch (op) {
+ case 0:
+ return 1;
+ case 1: {
+ int offset = (_andyMoveMask & 1) != 0 ? 3 : 0;
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable37[offset + i] + _andyMaskBufferPos1] & 8) == 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 2: {
+ int i, offset = _andyMaskBufferPos1;
+ for (i = 0; i < 9; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable1[i] + offset] & 8) != 0) {
+ offset += _andyMoveTable1[i];
+ break;
+ }
+ }
+ if ((_screenMaskBuffer[offset + 2048] & 8) != 0) {
+ const int alignDy = (_andyLevelData0x288PosTablePtr[1].y + _andyMoveData.yPos) & 7;
+ _andyMoveData.yPos += _andyMoveTable35[i * 2 + 1] - alignDy;
+ const int alignDx = (_andyLevelData0x288PosTablePtr[1].x + _andyMoveData.xPos) & 7;
+ _andyMoveData.xPos += _andyMoveTable35[i * 2] - alignDx + 4;
+ if ((_andyMoveMask & 1) != 0) {
+ --_andyMoveData.xPos;
+ }
+ return 1;
+ }
+ }
+ return 0;
+ case 3:
+ for (int i = 0; i < 9; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable1[i] + _andyMaskBufferPos1] & 8) != 0) {
+ const int alignDx = (_andyLevelData0x288PosTablePtr[1].x + _andyMoveData.xPos) & 7;
+ _andyMoveData.xPos += _andyMoveTable35[i * 2] - alignDx + 4;
+ break;
+ }
+ }
+ return 1;
+ case 4: {
+ int offset = _andyMaskBufferPos1 - 512;
+ int ret = 0;
+ const int *p;
+ if (_andyMoveMask & 1) {
+ --offset;
+ p = &_andyMoveTable37[3];
+ } else {
+ ++offset;
+ p = &_andyMoveTable37[0];
+ }
+ int _al = 0;
+ for (int i = 0; i < 9; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable1[i] + offset] & 8) == 0) {
+ continue;
+ }
+ ret = 1;
+ _al = i;
+ offset += _andyMoveTable1[i];
+ break;
+ }
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[p[i] + offset] & 8) != 0) {
+ continue;
+ }
+ return 0;
+ }
+ if (ret) {
+ const int alignDy = (_andyLevelData0x288PosTablePtr[1].y + _andyMoveData.yPos) & 7;
+ _andyMoveData.yPos += _andyMoveTable35[_al * 2 + 1] - alignDy - 8;
+ const int alignDx = (_andyLevelData0x288PosTablePtr[1].x + _andyMoveData.xPos) & 7;
+ _andyMoveData.xPos += _andyMoveTable35[_al * 2 + 0] - alignDx + 4;
+ if (_andyMoveMask & 1) {
+ _andyMoveData.xPos -= 9;
+ } else {
+ _andyMoveData.xPos += 8;
+ }
+ return ret;
+ }
+ }
+ return 0;
+ case 5:
+ return (_andyMoveState[8] >> 3) & 1;
+ case 6:
+ return (_andyMoveState[20] >> 3) & 1;
+ case 7:
+ return (_andyMoveState[28] >> 3) & 1;
+ case 8:
+ return (_andyMoveState[0] >> 3) & 1;
+ case 9: {
+ int offset = (_andyMoveMask & 1) != 0 ? -1 : 1;
+ int mask = _screenMaskBuffer[_andyMaskBufferPos1];
+ if ((mask & 1) != 0 && (_screenMaskBuffer[_andyMaskBufferPos1 + offset] & 1) != 0) {
+ return (mask & 4) != 0 ? 0 : 1;
+ }
+ mask = _screenMaskBuffer[_andyMaskBufferPos1 - 512];
+ if ((mask & 1) != 0 && (_screenMaskBuffer[_andyMaskBufferPos1 - 512 + offset] & 1) != 0) {
+ return (mask & 4) != 0 ? 0 : 1;
+ }
+ }
+ return 0;
+ case 10: {
+ const int offset = (_andyMoveMask & 1) != 0 ? 1 : -1;
+ if (_screenMaskBuffer[_andyMaskBufferPos5 + 512] & 1) {
+ if (_screenMaskBuffer[_andyMaskBufferPos5 + 512 + offset] & 1) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 11: {
+ const int index = (_andyMoveMask & 1) == 0 ? 5 : 3;
+ if ((_andyMoveState[index] & 8) != 0 && (_andyMoveState[16 + index] & 8) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 12: {
+ const int index = (_andyMoveMask & 1) == 0 ? 1 : 7;
+ if ((_andyMoveState[8 + index] & 8) != 0 && (_andyMoveState[24 + index] & 8) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 13:
+ return (_andyMoveState[8] & 9) == 0 ? 1 : 0;
+ case 14: {
+ const int index = (_andyMoveMask & 1) | 2;
+ return (_andyMoveState[4 + 8 * index] & 9) == 0 ? 1 : 0;
+ }
+ case 15: {
+ const int index = (_andyMoveMask & 1) == 0 ? 7 : -1;
+ return ((_andyMoveState[24 + index] >> 3) & (_andyMoveState[8 + index] >> 3)) & 1;
+ }
+ case 16: {
+ const int index = (_andyMoveMask & 1) == 0 ? 3 : 5;
+ return ((_andyMoveState[16 + index] >> 3) & (_andyMoveState[index] >> 3)) & 1;
+ }
+ case 17: {
+ const int index = (_andyMoveMask & 1) == 0 ? 7 : 1;
+ return ((_andyMoveState[16 + index] >> 3) & (_andyMoveState[index] >> 3)) & 1;
+ }
+ case 18: {
+ const int index = (_andyMoveMask & 1) == 0 ? 1 : 7;
+ return ((_andyMoveState[24 + index] >> 3) & (_andyMoveState[8 + index] >> 3)) & 1;
+ }
+ case 19: {
+ const int index = (_andyMoveMask & 1) == 0 ? 3 : 5;
+ return ((_andyMoveState[24 + index] >> 3) & (_andyMoveState[8 + index] >> 3)) & 1;
+ }
+ case 20: {
+ const int index = (_andyMoveMask & 1) == 0 ? 5 : 3;
+ return ((_andyMoveState[16 + index] >> 3) & (_andyMoveState[index] >> 3)) & 1;
+ }
+ case 21: {
+ const int index = (_andyMoveMask & 1) == 0 ? 5 : 3;
+ return ((_andyMoveState[24 + index] >> 3) & (_andyMoveState[8 + index] >> 3)) & 1;
+ }
+ case 22:
+ return (_andyMoveState[0] & 9) == 0 ? 1 : 0;
+ case 23: {
+ const int index = (~_andyMoveMask & 1) | 2;
+ return (_andyMoveState[4 + 8 * index] & 9) == 0 ? 1 : 0;
+ }
+ case 24: {
+ const int index = (_andyMoveMask & 1) == 0 ? 5 : 2;
+ return ((~(_andyMoveState[16 + index] & _andyMoveState[index])) >> 3) & 1;
+ }
+ case 25: {
+ const int index = (_andyMoveMask & 1) == 0 ? 1 : 6;
+ return ((~(_andyMoveState[24 + index] & _andyMoveState[8 + index])) >> 3) & 1;
+ }
+ case 26: {
+ const int index = (_andyMoveMask & 1) == 0 ? 3 : 5;
+ return ((~(_andyMoveState[16 + index] & _andyMoveState[index])) >> 3) & 1;
+ }
+ case 27: {
+ const int index = (_andyMoveMask & 1) == 0 ? 5 : 3;
+ return ((~(_andyMoveState[24 + index] & _andyMoveState[8 + index])) >> 3) & 1;
+ }
+ case 28: {
+ const int index = (_andyMoveMask & 1) == 0 ? 1 : 7;
+ return ((~_andyMoveState[index]) >> 3) & 1;
+ }
+ case 29: {
+ const int index = (_andyMoveMask & 1) == 0 ? 7 : 1;
+ return ((~_andyMoveState[8 + index]) >> 3) & 1;
+ }
+ case 30: {
+ int index1, index2;
+ if (_andyMoveMask & 1) {
+ index1 = 2;
+ index2 = 3;
+ } else {
+ index1 = 6;
+ index2 = 5;
+ }
+ if (((_andyMoveState[16 + index1] & _andyMoveState[index1]) & 8) == 0 || (_andyMoveState[index2] & 1) != 0) {
+ return 0;
+ }
+ }
+ return 1;
+ case 31: {
+ int index1, index2;
+ if (_andyMoveMask & 1) {
+ index1 = 6;
+ index2 = 5;
+ } else {
+ index1 = 2;
+ index2 = 3;
+ }
+ if (((_andyMoveState[24 + index1] & _andyMoveState[8 + index1]) & 8) == 0 || (_andyMoveState[8 + index2] & 1) != 0) {
+ return 0;
+ }
+ }
+ return 1;
+ case 32: {
+ int index1, index2;
+ if (_andyMoveMask & 1) {
+ index1 = 2;
+ index2 = 3;
+ } else {
+ index1 = 6;
+ index2 = 5;
+ }
+ if (((_andyMoveState[16 + index1] & _andyMoveState[index1]) & 8) != 0 && (_andyMoveState[index2] & 1) == 0) {
+ return 0;
+ }
+ }
+ return 1;
+ case 33: {
+ int index1, index2;
+ if (_andyMoveMask & 1) {
+ index1 = 6;
+ index2 = 5;
+ } else {
+ index1 = 2;
+ index2 = 3;
+ }
+ if (((_andyMoveState[24 + index1] & _andyMoveState[8 + index1]) & 8) != 0 && (_andyMoveState[index2] & 1) == 0) {
+ return 0;
+ }
+ }
+ return 1;
+ case 34: {
+ const int index = (_andyMoveMask & 1) == 0 ? 1 : 7;
+ return ((_andyMoveState[16 + index] >> 3) & (_andyMoveState[index] >> 3)) & 1;
+ }
+ case 35:
+ if ((_screenMaskBuffer[_andyMaskBufferPos3 - 514] | _screenMaskBuffer[_andyMaskBufferPos3 - 513]) & 8) {
+ if ((_screenMaskBuffer[_andyMaskBufferPos3 + 514] | _screenMaskBuffer[_andyMaskBufferPos3 + 513]) & 8) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ if ((_screenMaskBuffer[_andyMaskBufferPos3 - 511] | _screenMaskBuffer[_andyMaskBufferPos3 - 510]) & 8) {
+ if ((_screenMaskBuffer[_andyMaskBufferPos3 + 511] | _screenMaskBuffer[_andyMaskBufferPos3 + 510]) & 8) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ return 0;
+ case 36:
+ if (_andyMoveMask & 1) {
+ int _al = 0;
+ int ret = 1;
+ int offset = _andyMaskBufferPos2;
+ if (_screenMaskBuffer[_andyMaskBufferPos2 + 1] & 8) {
+ ++offset;
+ _al = 2;
+ } else if (_screenMaskBuffer[_andyMaskBufferPos2 + 2] & 8) {
+ offset += 2;
+ _al = 0;
+ } else if (_screenMaskBuffer[_andyMaskBufferPos2 + 3] & 8) {
+ offset += 3;
+ _al = 1;
+ } else if (_screenMaskBuffer[_andyMaskBufferPos2 + 4] & 8) {
+ offset += 4;
+ _al = 9;
+ } else {
+ return 0;
+ }
+ int xPos = _andyMoveTable37[3];
+ int i = 0;
+ while (_screenMaskBuffer[xPos + offset] & 8) {
+ ++i;
+ if (i >= 2) {
+ const int alignDx = (_andyLevelData0x288PosTablePtr[2].x + _andyMoveData.xPos) & 7;
+ _andyMoveData.xPos += _andyMoveTable35[_al * 2] - alignDx + 7;
+ return ret;
+ }
+ xPos = _andyMoveTable37[3 + i];
+ }
+ } else {
+ int ret = 1;
+ int offset = _andyMaskBufferPos1;
+ int _al = 0;
+ if (_screenMaskBuffer[_andyMaskBufferPos1 - 1] & 8) {
+ --offset;
+ _al = 2;
+ } else if (_screenMaskBuffer[_andyMaskBufferPos1 - 2] & 8) {
+ offset -= 2;
+ _al = 10;
+ } else if (_screenMaskBuffer[_andyMaskBufferPos1 - 3] & 8) {
+ offset -= 3;
+ _al = 17;
+ }
+ int xPos = _andyMoveTable37[0];
+ int i = 0;
+ while (_screenMaskBuffer[xPos + offset] & 8) {
+ ++i;
+ if (i >= 2) {
+ const int alignDx = (_andyLevelData0x288PosTablePtr[1].x - _andyMoveData.xPos) & 7;
+ _andyMoveData.xPos += _andyMoveTable35[_al * 2] - alignDx + 4;
+ return ret;
+ }
+ xPos = _andyMoveTable37[i];
+ }
+ }
+ return 0;
+ case 37:
+ if ((_andyMoveMask & 1) == 0) {
+ int ret = 1;
+ int _al = 0;
+ int offset = _andyMaskBufferPos1;
+ if (_screenMaskBuffer[_andyMaskBufferPos1 + 1] & 8) {
+ ++offset;
+ _al = 2;
+ } else if (_screenMaskBuffer[_andyMaskBufferPos1 + 2] & 8) {
+ offset += 2;
+ _al = 0;
+ } else if (_screenMaskBuffer[_andyMaskBufferPos1 + 3] & 8) {
+ offset += 3;
+ _al = 1;
+ } else if (_screenMaskBuffer[_andyMaskBufferPos1 + 4] & 8) {
+ offset += 4;
+ _al = 9;
+ } else {
+ ret = 0;
+ }
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[offset + _andyMoveTable37[3 + i]] & 8) == 0) {
+ return 0;
+ }
+ }
+ if (ret) {
+ const int alignDx = (_andyLevelData0x288PosTablePtr[1].x + _andyMoveData.xPos) & 7;
+ _andyMoveData.xPos += _andyMoveTable35[_al * 2] - alignDx + 7;
+ }
+ return ret;
+ } else {
+ int ret = 1;
+ int _al = 0;
+ int offset = _andyMaskBufferPos2;
+ if (_screenMaskBuffer[_andyMaskBufferPos2 - 1] & 8) {
+ --offset;
+ _al = 2;
+ } else if (_screenMaskBuffer[_andyMaskBufferPos2 - 2] & 8) {
+ offset -= 2;
+ _al = 10;
+ } else if (_screenMaskBuffer[_andyMaskBufferPos2 - 3] & 8) {
+ offset -= 3;
+ _al = 17;
+ }
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[offset + _andyMoveTable37[i]] & 8) == 0) {
+ return 0;
+ }
+ }
+ const int alignDx = (_andyLevelData0x288PosTablePtr[2].x + _andyMoveData.xPos) & 7;
+ _andyMoveData.xPos += _andyMoveTable35[_al * 2] - alignDx + 4;
+ return ret;
+ }
+ return 0;
+ case 38:
+ if (_screenMaskBuffer[_andyMaskBufferPos3 + 1536] & 8) {
+ if ((_screenMaskBuffer[_andyMaskBufferPos3 + 2557] | _screenMaskBuffer[_andyMaskBufferPos3 + 2563]) & 8) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ if ((_screenMaskBuffer[_andyMaskBufferPos3 + 1533] | _screenMaskBuffer[_andyMaskBufferPos3 + 1534] | _screenMaskBuffer[_andyMaskBufferPos3 + 1535]) & 8) {
+ if ((_screenMaskBuffer[_andyMaskBufferPos3 + 2562] | _screenMaskBuffer[_andyMaskBufferPos3 + 2561] | _screenMaskBuffer[_andyMaskBufferPos3 + 2560]) & 8) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ if ((_screenMaskBuffer[_andyMaskBufferPos3 + 1539] | _screenMaskBuffer[_andyMaskBufferPos3 + 1538] | _screenMaskBuffer[_andyMaskBufferPos3 + 1537]) & 8) {
+ if ((_screenMaskBuffer[_andyMaskBufferPos3 + 2558] | _screenMaskBuffer[_andyMaskBufferPos3 + 2559] | _screenMaskBuffer[_andyMaskBufferPos3 + 2560]) & 8) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ return 0;
+ case 39:
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos5] & 1) == 0) {
+ continue;
+ }
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos4] & 1) == 0) {
+ continue;
+ }
+ return 1;
+ }
+ return 0;
+ case 40:
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos5] & 1) == 0) {
+ continue;
+ }
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos4] & 1) == 0) {
+ continue;
+ }
+ return 0;
+ }
+ return 1;
+ case 41: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable2 : _andyMoveTable3;
+ for (int i = 0; i < 2; ++i) {
+ int mask = _screenMaskBuffer[_andyMaskBufferPos1 + p[i]];
+ mask |= _screenMaskBuffer[_andyMaskBufferPos2 + p[i]];
+ mask |= _screenMaskBuffer[_andyMaskBufferPos4 + p[i]];
+ mask |= _screenMaskBuffer[_andyMaskBufferPos5 + p[i]];
+ mask |= _screenMaskBuffer[_andyMaskBufferPos3 + p[i]];
+ mask |= _screenMaskBuffer[_andyMaskBufferPos0 + p[i]];
+ if ((mask & 6) != 0) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 42: {
+ int ret = 1;
+ int _al = 0;
+ int offset = _andyMaskBufferPos4;
+ if ((_andyMoveData.flags1 & 0x10) != 0) {
+ if (_screenMaskBuffer[offset + 512] & 8) {
+ offset += 512;
+ _al = 9;
+ } else if (_screenMaskBuffer[offset + 511] & 8) {
+ offset += 511;
+ _al = 1;
+ } else if (_screenMaskBuffer[offset + 510] & 8) {
+ offset += 510;
+ _al = 0;
+ } else if (_screenMaskBuffer[offset + 509] & 8) {
+ offset += 509;
+ _al = 2;
+ } else if (_screenMaskBuffer[offset + 508] & 8) {
+ offset += 508;
+ _al = 10;
+ } else {
+ ret = 0;
+ }
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable39[i] + offset] & 8) == 0) {
+ return 0;
+ }
+ }
+ if (ret) {
+ const int alignDx = (_andyLevelData0x288PosTablePtr[5].x - _andyMoveData.xPos) & 7;
+ _andyMoveData.xPos += _andyMoveTable35[_al * 2] - alignDx + 7;
+ return ret;
+ }
+ } else {
+ if (_screenMaskBuffer[offset + 512] & 8) {
+ offset += 512;
+ _al = 10;
+ } else if (_screenMaskBuffer[offset + 513] & 8) {
+ offset += 513;
+ _al = 2;
+ } else if (_screenMaskBuffer[offset + 514] & 8) {
+ offset += 514;
+ _al = 0;
+ } else if (_screenMaskBuffer[offset + 515] & 8) {
+ offset += 515;
+ _al = 1;
+ } else if (_screenMaskBuffer[offset + 516] & 8) {
+ offset += 516;
+ _al = 9;
+ } else {
+ ret = 0;
+ }
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable40[i] + offset] & 8) == 0) {
+ return 0;
+ }
+ }
+ if (ret) {
+ const int alignDx = (_andyLevelData0x288PosTablePtr[5].x - _andyMoveData.xPos) & 7;
+ _andyMoveData.xPos += _andyMoveTable35[_al * 2] - alignDx;
+ return ret;
+ }
+ }
+ }
+ return 0;
+ case 43: {
+ int ret = 1;
+ int _al = 0;
+ int offset = _andyMaskBufferPos5;
+ if ((_andyMoveData.flags1 & 0x10) == 0) {
+ if (_screenMaskBuffer[offset + 512] & 8) {
+ offset += 512;
+ _al = 9;
+ } else if (_screenMaskBuffer[offset + 511] & 8) {
+ offset += 511;
+ _al = 1;
+ } else if (_screenMaskBuffer[offset + 510] & 8) {
+ offset += 510;
+ _al = 0;
+ } else if (_screenMaskBuffer[offset + 509] & 8) {
+ offset += 509;
+ _al = 2;
+ } else if (_screenMaskBuffer[offset + 508] & 8) {
+ offset += 508;
+ _al = 10;
+ } else {
+ ret = 0;
+ }
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable39[i] + offset] & 8) == 0) {
+ return 0;
+ }
+ }
+ if (ret) {
+ const int alignDx = (_andyLevelData0x288PosTablePtr[4].x + _andyMoveData.xPos) & 7;
+ _andyMoveData.xPos += _andyMoveTable35[_al * 2] - alignDx + 6;
+ return ret;
+ }
+ } else {
+ if (_screenMaskBuffer[offset + 512] & 8) {
+ offset += 512;
+ _al = 10;
+ } else if (_screenMaskBuffer[offset + 513] & 8) {
+ offset += 513;
+ _al = 2;
+ } else if (_screenMaskBuffer[offset + 514] & 8) {
+ offset += 514;
+ _al = 0;
+ } else if (_screenMaskBuffer[offset + 515] & 8) {
+ offset += 515;
+ _al = 1;
+ } else if (_screenMaskBuffer[offset + 516] & 8) {
+ offset += 516;
+ _al = 9;
+ } else {
+ ret = 0;
+ }
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable40[i] + offset] & 8) == 0) {
+ return 0;
+ }
+ }
+ if (ret) {
+ const int alignDx = (_andyLevelData0x288PosTablePtr[4].x + _andyMoveData.xPos) & 7;
+ _andyMoveData.xPos += _andyMoveTable35[_al * 2] - alignDx + 1;
+ return ret;
+ }
+ }
+ }
+ return 0;
+ case 44: {
+ const int offset = (_andyMoveMask & 1) == 0 ? _andyMaskBufferPos5 - 1 : _andyMaskBufferPos5 + 1;
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + offset] & 1) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 45: {
+ const int *p = (_andyMoveMask & 1) != 0 ? _andyMoveTable4 : _andyMoveTable5;
+ for (int i = 0; i < 5; ++i) {
+ if ((_screenMaskBuffer[_andyMaskBufferPos1 + p[i]] & 6) != 0) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 46: {
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos3] & 1) != 0) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 47: {
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos3] & 1) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 48: {
+ const int *p = (_andyMoveMask & 1) != 0 ? _andyMoveTable4 : _andyMoveTable5;
+ for (int i = 0; i < 5; ++i) {
+ if ((_screenMaskBuffer[_andyMaskBufferPos2 + p[i]] & 6) != 0) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 49: {
+ const int d = (_andyMoveMask & 1) != 0 ? _andyMaskBufferPos4 - 1 : _andyMaskBufferPos4 + 1;
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + d] & 1) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 50:
+ for (int i = 0; i < 3; ++i) {
+ if (((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos4] | _screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos5]) & 1) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 51:
+ for (int i = 0; i < 3; ++i) {
+ if (((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos5] | _screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos4]) & 1) != 0) {
+ return 0;
+ }
+ }
+ return 1;
+ case 52:
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos5] & 1) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 53:
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos5] & 1) != 0) {
+ return 0;
+ }
+ }
+ return 1;
+ case 54:
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos4] & 1) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 55:
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos4] & 1) != 0) {
+ return 0;
+ }
+ }
+ return 1;
+ case 56:
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos1] & 6) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 57:
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos4] & 2) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 58:
+ case 60:
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos2] & 6) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 59:
+ case 61:
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable8[i] + _andyMaskBufferPos5] & 2) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 62: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable4 : _andyMoveTable5;
+ for (int i = 0; i < 5; ++i) {
+ if ((_screenMaskBuffer[_andyMaskBufferPos1 + p[i]] & 6) != 0) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 63: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable4 : _andyMoveTable5;
+ for (int i = 0; i < 5; ++i) {
+ if ((_screenMaskBuffer[_andyMaskBufferPos2 + p[i]] & 6) != 0) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 64: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable39 : _andyMoveTable40;
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[_andyMaskBufferPos1 + p[i]] & 8) == 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 65: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable40 : _andyMoveTable39;
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[_andyMaskBufferPos1 + p[i]] & 8) == 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 66: {
+ const int *p = (_andyMoveMask & 1) == 0 ? &_andyMoveTable37[3] : &_andyMoveTable37[0];
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[_andyMaskBufferPos1 + p[i]] & 8) == 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 67: {
+ const int offset = (_andyMoveMask & 2) == 0 ? -512 : 512;
+ int mask = _screenMaskBuffer[_andyMaskBufferPos0] | _screenMaskBuffer[_andyMaskBufferPos1] | _screenMaskBuffer[_andyMaskBufferPos2];
+ if (mask & 6) {
+ return 1;
+ }
+ mask = _screenMaskBuffer[_andyMaskBufferPos0 + offset] | _screenMaskBuffer[_andyMaskBufferPos1 + offset] | _screenMaskBuffer[_andyMaskBufferPos2 + offset];
+ if (mask & 6) {
+ return 1;
+ }
+ }
+ return 0;
+ case 68: {
+ const int offset = (_andyMoveMask & 1) == 0 ? 1 : -1;
+ int mask = _screenMaskBuffer[_andyMaskBufferPos0] | _screenMaskBuffer[_andyMaskBufferPos1] | _screenMaskBuffer[_andyMaskBufferPos2];
+ if (mask & 6) {
+ return 1;
+ }
+ mask = _screenMaskBuffer[_andyMaskBufferPos0 + offset] | _screenMaskBuffer[_andyMaskBufferPos1 + offset] | _screenMaskBuffer[_andyMaskBufferPos2 + offset];
+ if (mask & 6) {
+ return 1;
+ }
+ }
+ return 0;
+ case 69: {
+ const int offset = (_andyMoveMask & 2) == 0 ? 512 : -512;
+ int mask = _screenMaskBuffer[_andyMaskBufferPos0] | _screenMaskBuffer[_andyMaskBufferPos1] | _screenMaskBuffer[_andyMaskBufferPos2];
+ if (mask & 6) {
+ return 1;
+ }
+ mask = _screenMaskBuffer[_andyMaskBufferPos0 + offset] | _screenMaskBuffer[_andyMaskBufferPos1 + offset] | _screenMaskBuffer[_andyMaskBufferPos2 + offset];
+ if (mask & 6) {
+ return 1;
+ }
+ }
+ return 0;
+ case 70: {
+ const int offset = (_andyMoveMask & 1) == 0 ? -1 : 1;
+ int mask = _screenMaskBuffer[_andyMaskBufferPos0] | _screenMaskBuffer[_andyMaskBufferPos1] | _screenMaskBuffer[_andyMaskBufferPos2];
+ if (mask & 6) {
+ return 1;
+ }
+ mask = _screenMaskBuffer[_andyMaskBufferPos0 + offset] | _screenMaskBuffer[_andyMaskBufferPos1 + offset] | _screenMaskBuffer[_andyMaskBufferPos2 + offset];
+ if (mask & 6) {
+ return 1;
+ }
+ }
+ return 0;
+ case 71: {
+ const int offset = (_andyMoveMask & 2) == 0 ? -512 : 512;
+ if (((_screenMaskBuffer[_andyMaskBufferPos6] | _screenMaskBuffer[_andyMaskBufferPos6 + offset]) & 6) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 72: {
+ int offset = (_andyMoveMask & 1) != 0 ? -1 : 1;
+ if (((_screenMaskBuffer[_andyMaskBufferPos6] | _screenMaskBuffer[_andyMaskBufferPos6 + offset]) & 6) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 73: {
+ int offset = (_andyMoveMask & 2) != 0 ? -512 : 512;
+ int mask = _screenMaskBuffer[_andyMaskBufferPos6] | _screenMaskBuffer[_andyMaskBufferPos6 + offset];
+ if ((mask & 6) != 0 || (mask & 1) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 74: {
+ const int offset = (_andyMoveMask & 1) != 0 ? 1 : -1;
+ if (((_screenMaskBuffer[_andyMaskBufferPos6] | _screenMaskBuffer[_andyMaskBufferPos6 + offset]) & 6) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 75: {
+ const int offset = (_andyMoveMask & 2) != 0 ? -512 : 512;
+ if ((_screenMaskBuffer[_andyMaskBufferPos7 + offset] & 15) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 76: {
+ const int *p = (_andyMoveMask & 1) != 0 ? _andyMoveTable10 : _andyMoveTable9;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 4; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 77: {
+ const int *p = (_andyMoveMask & 1) != 0 ? _andyMoveTable12 : _andyMoveTable11;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 2; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 78: {
+ const int *p = (_andyMoveMask & 1) != 0 ? _andyMoveTable14 : _andyMoveTable13;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 6; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 79: {
+ const int *p = (_andyMoveMask & 1) != 0 ? _andyMoveTable16 : _andyMoveTable15;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 4; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 80: {
+ const int *p = (_andyMoveMask & 1) != 0 ? _andyMoveTable18 : _andyMoveTable17;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 4; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 3) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 81: {
+ const int *p = (_andyMoveMask & 1) != 0 ? _andyMoveTable20 : _andyMoveTable19;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 6; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 82: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable21 : _andyMoveTable22;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 5; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 83: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable23 : _andyMoveTable24;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 2; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 84: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable25 : _andyMoveTable26;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 5; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 85: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable27 : _andyMoveTable28;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 7; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 86: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable29 : _andyMoveTable30;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 2; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 87: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable31 : _andyMoveTable32;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 7; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 88: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable33 : _andyMoveTable34;
+ int offset = _andyMaskBufferPos0;
+ for (int i = 0; i < 6; ++i) {
+ offset += p[i];
+ if ((_screenMaskBuffer[offset] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 89: {
+ const int offset = (_andyMoveMask & 1) == 0 ? 7 : 1;
+ return ((~(_andyMoveState[16 + offset] & _andyMoveState[offset])) >> 3) & 1;
+ }
+ case 90: {
+ const int offset = (_andyMoveMask & 1) == 0 ? 1 : 7;
+ return ((~(_andyMoveState[24 + offset] & _andyMoveState[8 + offset])) >> 3) & 1;
+ }
+ case 91: {
+ const int offset = (_andyMoveMask & 1) != 0 ? 5 : 3;
+ return ((~(_andyMoveState[24 + offset] & _andyMoveState[8 + offset])) >> 3) & 1;
+ }
+ case 92: {
+ const int offset = (_andyMoveMask & 1) != 0 ? 3 : 5;
+ return ((~(_andyMoveState[16 + offset] & _andyMoveState[offset])) >> 3) & 1;
+ }
+ case 93: {
+ const int *p = (_andyMoveMask & 1) != 0 ? _andyMoveTable5 : _andyMoveTable4;
+ for (int i = 0; i < 5; ++i) {
+ if (_screenMaskBuffer[p[i] + _andyMaskBufferPos0] & 6) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 94: {
+ const int *p = (_andyMoveMask & 1) != 0 ? _andyMoveTable4 : _andyMoveTable5;
+ for (int i = 0; i < 5; ++i) {
+ if (_screenMaskBuffer[p[i] + _andyMaskBufferPos0] & 6) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 95: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable41 : _andyMoveTable42;
+ for (int i = 0; i < 4; ++i) {
+ if ((_screenMaskBuffer[p[i] + _andyMaskBufferPos0] & 9) == 0) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 96:
+ for (int i = 0; i < 2; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable43[i] + _andyMaskBufferPos0] & 9) == 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 97:
+ for (int i = 0; i < 4; ++i) {
+ if ((_screenMaskBuffer[_andyMoveTable43[2 + i] + _andyMaskBufferPos0] & 9) == 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 98:
+ return _screenMaskBuffer[_andyMaskBufferPos5 + 512] & 1;
+ case 99: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable6 : _andyMoveTable7;
+ for (int i = 0; i < 6; ++i) {
+ if ((_screenMaskBuffer[p[i] + _andyMaskBufferPos0] & 6) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ case 100: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable4 : _andyMoveTable5;
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[p[i] + _andyMaskBufferPos2] & 6) != 0) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 101: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable5 : _andyMoveTable4;
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[p[i] + _andyMaskBufferPos2] & 6) != 0) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 102: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable4 : _andyMoveTable5;
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[p[i] + _andyMaskBufferPos1] & 6) != 0) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 103: {
+ const int *p = (_andyMoveMask & 1) == 0 ? _andyMoveTable5 : _andyMoveTable4;
+ for (int i = 0; i < 3; ++i) {
+ if ((_screenMaskBuffer[p[i] + _andyMaskBufferPos1] & 6) != 0) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ case 104: {
+ int offset = (_andyMoveMask & 2) != 0 ? 512 : -512;
+ if ((_screenMaskBuffer[_andyMaskBufferPos4] | _screenMaskBuffer[_andyMaskBufferPos5]) & 6) {
+ return 1;
+ }
+ if ((_screenMaskBuffer[_andyMaskBufferPos4 + offset] | _screenMaskBuffer[_andyMaskBufferPos5 + offset]) & 6) {
+ return 1;
+ }
+ }
+ return 0;
+ case 105: {
+ int offset = (_andyMoveMask & 1) != 0 ? -1 : 1;
+ if ((_screenMaskBuffer[_andyMaskBufferPos4] | _screenMaskBuffer[_andyMaskBufferPos5]) & 6) {
+ return 1;
+ }
+ if ((_screenMaskBuffer[_andyMaskBufferPos4 + offset] | _screenMaskBuffer[_andyMaskBufferPos5 + offset]) & 6) {
+ return 1;
+ }
+ }
+ return 0;
+ case 106: {
+ int offset = (_andyMoveMask & 2) != 0 ? -512 : 512;
+ if ((_screenMaskBuffer[_andyMaskBufferPos5] | _screenMaskBuffer[_andyMaskBufferPos4]) & 6) {
+ return 1;
+ }
+ if ((_screenMaskBuffer[_andyMaskBufferPos5 + offset] | _screenMaskBuffer[_andyMaskBufferPos4 + offset]) & 6) {
+ return 1;
+ }
+ }
+ return 0;
+ case 107: {
+ int offset = (_andyMoveMask & 2) != 0 ? 1 : -1;
+ if ((_screenMaskBuffer[_andyMaskBufferPos5] | _screenMaskBuffer[_andyMaskBufferPos4]) & 6) {
+ return 1;
+ }
+ if ((_screenMaskBuffer[_andyMaskBufferPos5 + offset] | _screenMaskBuffer[_andyMaskBufferPos4 + offset]) & 6) {
+ return 1;
+ }
+ }
+ return 0;
+ case 108: {
+ const int offset = (_andyMoveMask & 2) != 0 ? 512 : -512;
+ if ((_screenMaskBuffer[_andyMaskBufferPos0 + offset] & 6) != 0) {
+ const int offset2 = (_andyMoveMask & 1) != 0 ? -1 : 1;
+ if ((_screenMaskBuffer[_andyMaskBufferPos0 + offset2] & 6) != 0) {
+ return 1;
+ }
+ }
+ if ((_screenMaskBuffer[_andyMaskBufferPos0] & 6) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ case 109: {
+ const int offset = (_andyMoveMask & 2) != 0 ? -512 : 512;
+ if ((_screenMaskBuffer[_andyMaskBufferPos0 + offset] & 6) != 0) {
+ const int offset2 = (_andyMoveMask & 1) != 0 ? -1 : 1;
+ if ((_screenMaskBuffer[_andyMaskBufferPos0 + offset2] & 6) != 0) {
+ return 1;
+ }
+ }
+ if ((_screenMaskBuffer[_andyMaskBufferPos0] & 6) != 0) {
+ return 1;
+ }
+ }
+ return 0;
+ default:
+ error("moveAndyObjectOp3 op %d", op);
+ break;
+ }
+ return 0;
+}
+
+int Game::moveAndyObjectOp4(int op) {
+ switch (op) {
+ case 0:
+ return 1;
+ case 1:
+ return _rnd.getNextNumber() >= 25;
+ case 2:
+ return _rnd.getNextNumber() >= 50;
+ case 3:
+ return _rnd.getNextNumber() >= 75;
+ case 4:
+ return _rnd.getNextNumber() >= 90;
+ case 5:
+ _andyUpdatePositionFlag = true;
+ return 1;
+ default:
+ error("moveAndyObjectOp4 op %d", op);
+ break;
+ }
+ return 0;
+}
+
+void Game::setupAndyObjectMoveData(LvlObject *ptr) {
+ _andyMoveData.xPos = ptr->xPos;
+ _andyMoveData.yPos = ptr->yPos;
+ _andyPosX = _res->_screensBasePos[ptr->screenNum].u + _andyMoveData.xPos;
+ _andyPosY = _res->_screensBasePos[ptr->screenNum].v + _andyMoveData.yPos;
+ _andyMoveData.anim = ptr->anim;
+ _andyMoveData.unkA = ptr->currentSprite;
+ _andyMoveData.frame = ptr->frame;
+ _andyMoveData.flags0 = ptr->flags0;
+ _andyMoveData.flags1 = ptr->flags1;
+ _andyMoveMask = (ptr->flags1 >> 4) & 3;
+ _andyMoveData.unk16 = ptr->width;
+ _andyMoveData.unk18 = ptr->height;
+ const LvlObjectData *dat = ptr->levelData0x2988;
+ _andyMoveData.unkC = dat->hotspotsCount;
+ _andyMoveData.unk1C = dat->animsInfoData;
+ _andyMoveData.framesData = dat->framesData;
+ _andyMoveData.unk24 = dat->movesData;
+ _andyMoveData.unk28 = dat->hotspotsData;
+ _andyLevelData0x288PosTablePtr = ptr->posTable;
+}
+
+void Game::setupAndyObjectMoveState() {
+ int yPos, xPos;
+
+ yPos = _andyLevelData0x288PosTablePtr[1].y + _andyPosY;
+ xPos = _andyLevelData0x288PosTablePtr[1].x + _andyPosX;
+ _andyMaskBufferPos1 = screenMaskOffset(xPos, yPos);
+ _andyMoveState[0x0] = _screenMaskBuffer[_andyMaskBufferPos1 - 0x800];
+ _andyMoveState[0x1] = _screenMaskBuffer[_andyMaskBufferPos1 - 0x3FD];
+ _andyMoveState[0x2] = _screenMaskBuffer[_andyMaskBufferPos1 + 6];
+ _andyMoveState[0x3] = _screenMaskBuffer[_andyMaskBufferPos1 + 0x403];
+ _andyMoveState[0x4] = _screenMaskBuffer[_andyMaskBufferPos1 + 0x800];
+ _andyMoveState[0x5] = _screenMaskBuffer[_andyMaskBufferPos1 + 0x3FD];
+ _andyMoveState[0x6] = _screenMaskBuffer[_andyMaskBufferPos1 - 6];
+ _andyMoveState[0x7] = _screenMaskBuffer[_andyMaskBufferPos1 - 0x403];
+
+ yPos = _andyLevelData0x288PosTablePtr[2].y + _andyPosY;
+ xPos = _andyLevelData0x288PosTablePtr[2].x + _andyPosX;
+ _andyMaskBufferPos2 = screenMaskOffset(xPos, yPos);
+ _andyMoveState[0x8] = _screenMaskBuffer[_andyMaskBufferPos2 - 0x800];
+ _andyMoveState[0x9] = _screenMaskBuffer[_andyMaskBufferPos2 - 0x3FD];
+ _andyMoveState[0xA] = _screenMaskBuffer[_andyMaskBufferPos2 + 6];
+ _andyMoveState[0xB] = _screenMaskBuffer[_andyMaskBufferPos2 + 0x403];
+ _andyMoveState[0xC] = _screenMaskBuffer[_andyMaskBufferPos2 + 0x800];
+ _andyMoveState[0xD] = _screenMaskBuffer[_andyMaskBufferPos2 + 0x3FD];
+ _andyMoveState[0xE] = _screenMaskBuffer[_andyMaskBufferPos2 - 6];
+ _andyMoveState[0xF] = _screenMaskBuffer[_andyMaskBufferPos2 - 0x403];
+
+ yPos = _andyLevelData0x288PosTablePtr[4].y + _andyPosY;
+ xPos = _andyLevelData0x288PosTablePtr[4].x + _andyPosX;
+ _andyMaskBufferPos4 = screenMaskOffset(xPos, yPos);
+ _andyMoveState[0x10] = _screenMaskBuffer[_andyMaskBufferPos4 - 0x600];
+ _andyMoveState[0x11] = _screenMaskBuffer[_andyMaskBufferPos4 - 0x1FD];
+ _andyMoveState[0x12] = _screenMaskBuffer[_andyMaskBufferPos4 + 0x206];
+ _andyMoveState[0x13] = _screenMaskBuffer[_andyMaskBufferPos4 + 0x603];
+ _andyMoveState[0x14] = _screenMaskBuffer[_andyMaskBufferPos4 + 0xA00];
+ _andyMoveState[0x15] = _screenMaskBuffer[_andyMaskBufferPos4 + 0x5FD];
+ _andyMoveState[0x16] = _screenMaskBuffer[_andyMaskBufferPos4 + 0x1FA];
+ _andyMoveState[0x17] = _screenMaskBuffer[_andyMaskBufferPos4 - 0x203];
+
+ yPos = _andyLevelData0x288PosTablePtr[5].y + _andyPosY;
+ xPos = _andyLevelData0x288PosTablePtr[5].x + _andyPosX;
+ _andyMaskBufferPos5 = screenMaskOffset(xPos, yPos);
+ _andyMoveState[0x18] = _screenMaskBuffer[_andyMaskBufferPos5 - 0x600];
+ _andyMoveState[0x19] = _screenMaskBuffer[_andyMaskBufferPos5 - 0x1FD];
+ _andyMoveState[0x1A] = _screenMaskBuffer[_andyMaskBufferPos5 + 0x206];
+ _andyMoveState[0x1B] = _screenMaskBuffer[_andyMaskBufferPos5 + 0x603];
+ _andyMoveState[0x1C] = _screenMaskBuffer[_andyMaskBufferPos5 + 0xA00];
+ _andyMoveState[0x1D] = _screenMaskBuffer[_andyMaskBufferPos5 + 0x5FD];
+ _andyMoveState[0x1E] = _screenMaskBuffer[_andyMaskBufferPos5 + 0x1FA];
+ _andyMoveState[0x1F] = _screenMaskBuffer[_andyMaskBufferPos5 - 0x203];
+
+ yPos = _andyLevelData0x288PosTablePtr[3].y + _andyPosY;
+ xPos = _andyLevelData0x288PosTablePtr[3].x + _andyPosX;
+ _andyMaskBufferPos3 = screenMaskOffset(xPos, yPos);
+
+ yPos = _andyLevelData0x288PosTablePtr[0].y + _andyPosY;
+ xPos = _andyLevelData0x288PosTablePtr[0].x + _andyPosX;
+ _andyMaskBufferPos0 = screenMaskOffset(xPos, yPos);
+
+ yPos = _andyLevelData0x288PosTablePtr[7].y + _andyPosY;
+ xPos = _andyLevelData0x288PosTablePtr[7].x + _andyPosX;
+ _andyMaskBufferPos7 = screenMaskOffset(xPos, yPos);
+
+ yPos = _andyLevelData0x288PosTablePtr[6].y + _andyPosY;
+ xPos = _andyLevelData0x288PosTablePtr[6].x + _andyPosX;
+ _andyMaskBufferPos6 = screenMaskOffset(xPos, yPos);
+}
+
+void Game::updateAndyObject(LvlObject *ptr) {
+ _andyUpdatePositionFlag = false;
+ int xPos = 0;
+ int yPos = 0;
+ int mask = 0;
+
+ _actionKeyMask = ptr->actionKeyMask;
+ _directionKeyMask = ptr->directionKeyMask;
+ LvlObjectData *dat = ptr->levelData0x2988;
+
+ int currentAnimFrame = ptr->frame;
+ int currentAnim = ptr->anim;
+
+ LvlAnimHeader *ah = ((LvlAnimHeader *)(dat->animsInfoData + kLvlAnimHdrOffset)) + ptr->anim;
+ assert(ptr->frame < ah->seqCount);
+ LvlAnimSeqHeader *ash = ((LvlAnimSeqHeader *)(dat->animsInfoData + ah->seqOffset)) + ptr->frame;
+ LvlAnimSeqFrameHeader *asfh = (LvlAnimSeqFrameHeader *)(dat->animsInfoData + ash->offset);
+
+ int count = ash->count;
+ if (count == 0) goto sameAnim;
+
+ setupAndyObjectMoveData(ptr);
+ if (dat->frame != 0) {
+ setupAndyObjectMoveState();
+ }
+ while (count != 0) {
+ --count;
+ assert(asfh[count].move < dat->movesCount);
+ LvlSprMoveData *m = ((LvlSprMoveData *)dat->movesData) + asfh[count].move;
+ int op = m->op1 * 4 + ((ptr->flags1 >> 4) & 3);
+ mask = moveAndyObjectOp1(op);
+ if (mask == 0) {
+ continue;
+ }
+ mask &= moveAndyObjectOp2(m->op2);
+ if (mask == 0) {
+ continue;
+ }
+ mask &= moveAndyObjectOp3(m->op3);
+ if (mask == 0) {
+ continue;
+ }
+ mask &= moveAndyObjectOp4(m->op4);
+ if (mask == 0) {
+ continue;
+ }
+ break;
+ }
+ ptr->flags0 = _andyMoveData.flags0;
+ ptr->flags1 = _andyMoveData.flags1;
+ ptr->xPos = _andyMoveData.xPos;
+ ptr->yPos = _andyMoveData.yPos;
+ if (_andyUpdatePositionFlag) {
+ xPos = ptr->posTable[7].x + ptr->xPos;
+ yPos = ptr->posTable[7].y + ptr->yPos;
+ }
+ if (mask) {
+
+ assert(count < ash->count);
+ currentAnimFrame = asfh[count].frame;
+ currentAnim = asfh[count].anim;
+
+ ah = (LvlAnimHeader *)(dat->animsInfoData + kLvlAnimHdrOffset) + currentAnim;
+ ash = (LvlAnimSeqHeader *)(dat->animsInfoData + ah->seqOffset) + currentAnimFrame;
+ asfh = &asfh[count];
+
+ uint16_t w, h;
+ _res->getLvlSpriteFramePtr(dat, ash->firstFrame, &w, &h);
+
+ ptr->flags1 = ((ptr->flags1 & 0x30) ^ ((asfh->unk5 & 3) << 4)) | (ptr->flags1 & ~0x30);
+ int type = (ptr->flags1 >> 4) & 3;
+
+ switch (type) {
+ case 0:
+ ptr->xPos += asfh->unk6;
+ ptr->yPos += asfh->unk7;
+ break;
+ case 1:
+ ptr->xPos += ptr->width - asfh->unk6 - w;
+ ptr->yPos += asfh->unk7;
+ break;
+ case 2:
+ ptr->xPos += asfh->unk6;
+ ptr->yPos += ptr->height - asfh->unk7 - h;
+ break;
+ case 3:
+ ptr->xPos += ptr->width - asfh->unk6 - w;
+ ptr->yPos += ptr->height - asfh->unk7 - h;
+ break;
+ }
+
+ } else {
+sameAnim:
+
+ uint16_t frame1_w, frame1_h;
+ _res->getLvlSpriteFramePtr(dat, ash->firstFrame, &frame1_w, &frame1_h);
+
+ ++currentAnimFrame;
+ if (currentAnimFrame >= ah->seqCount) {
+ currentAnimFrame = 0;
+ }
+
+ ash = (LvlAnimSeqHeader *)(dat->animsInfoData + ah->seqOffset) + currentAnimFrame;
+
+ uint16_t frame2_w, frame2_h;
+ _res->getLvlSpriteFramePtr(dat, ash->firstFrame, &frame2_w, &frame2_h);
+
+ int dw = frame2_w - frame1_w;
+ int dh = frame2_h - frame1_h;
+
+ const int type = (ptr->flags1 >> 4) & 3;
+ switch (type) {
+ case 0:
+ ptr->xPos += ash->dx;
+ ptr->yPos += ash->dy;
+ break;
+ case 1:
+ ptr->xPos -= ash->dx + dw;
+ ptr->yPos += ash->dy;
+ break;
+ case 2:
+ ptr->xPos += ash->dx;
+ ptr->yPos -= ash->dy + dh;
+ break;
+ case 3:
+ ptr->xPos -= ash->dx + dw;
+ ptr->yPos -= ash->dy + dh;
+ break;
+ }
+ }
+
+ ptr->anim = currentAnim;
+ ptr->frame = currentAnimFrame;
+
+ ptr->currentSound = ash->sound;
+ if (ptr->currentSound != 0xFFFF && ptr->type == 8 && ptr->spriteNum < 5) {
+ playSound(ptr->currentSound, ptr, 0, 0);
+ }
+
+ ptr->flags0 = merge_bits(ptr->flags0, ash->flags0, 0x3FF);
+ ptr->flags1 = merge_bits(ptr->flags1, ash->flags1, 6);
+ ptr->flags1 = merge_bits(ptr->flags1, ash->flags1, 8);
+
+ ptr->currentSprite = ash->firstFrame;
+
+ ptr->bitmapBits = _res->getLvlSpriteFramePtr(dat, ash->firstFrame, &ptr->width, &ptr->height);
+
+ LvlSprHotspotData *hs = ((LvlSprHotspotData *)dat->hotspotsData) + ash->firstFrame;
+
+ if (_andyUpdatePositionFlag) {
+ ptr->flags1 &= ~0x30;
+ }
+ const int w = ptr->width - 1;
+ const int h = ptr->height - 1;
+ const int type = (ptr->flags1 >> 4) & 3;
+ for (int i = 0; i < 8; ++i) {
+ switch (type) {
+ case 0:
+ ptr->posTable[i].x = hs->pts[i].x;
+ ptr->posTable[i].y = hs->pts[i].y;
+ break;
+ case 1:
+ ptr->posTable[i].x = w - hs->pts[i].x;
+ ptr->posTable[i].y = hs->pts[i].y;
+ break;
+ case 2:
+ ptr->posTable[i].x = hs->pts[i].x;
+ ptr->posTable[i].y = h - hs->pts[i].y;
+ break;
+ case 3:
+ ptr->posTable[i].x = w - hs->pts[i].x;
+ ptr->posTable[i].y = h - hs->pts[i].y;
+ break;
+ }
+ }
+ if (_andyUpdatePositionFlag) {
+ ptr->xPos = xPos - ptr->posTable[7].x;
+ ptr->yPos = yPos - ptr->posTable[7].y;
+ }
+}
diff --git a/Src/Vita_&_Switch/benchmark.cpp b/Src/Vita_&_Switch/benchmark.cpp
new file mode 100644
index 0000000..5474aba
--- /dev/null
+++ b/Src/Vita_&_Switch/benchmark.cpp
@@ -0,0 +1,37 @@
+
+#include "game.h"
+#include "lzw.h"
+#include "system.h"
+#include "util.h"
+#include "video.h"
+
+uint32_t Game::benchmarkLoop(const uint8_t *p, int count) {
+ uint32_t accum = 0;
+ count >>= 2; // sizeof(uint32_t)
+ if (count > 0) {
+ count += 1023;
+ count >>= 10;
+ for (; count > 0; --count) {
+ accum += READ_LE_UINT32(p);
+ p += (1 << 10) * sizeof(uint32_t);
+ }
+ }
+ return accum;
+}
+
+uint32_t Game::benchmarkCpu() {
+ const uint32_t t0 = g_system->getTimeStamp();
+ uint8_t *p = _video->_shadowLayer;
+ int count = 32;
+ do {
+ decodeLZW(_pwr1_screenTransformData, p);
+ benchmarkLoop(p, Video::W * Video::H);
+ decodeLZW(_pwr2_screenTransformData, p);
+ _video->updateGameDisplay(p);
+ } while (--count != 0);
+ const uint32_t score = g_system->getTimeStamp() - t0;
+ // The original engine flags the CPU as 'slow'
+ // if the GetTickCount difference is >= 1100ms
+ warning("benchmark CPU %d", score);
+ return score;
+}
diff --git a/Src/Vita_&_Switch/defs.h b/Src/Vita_&_Switch/defs.h
new file mode 100644
index 0000000..5a4114a
--- /dev/null
+++ b/Src/Vita_&_Switch/defs.h
@@ -0,0 +1,469 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef DEFS_H__
+#define DEFS_H__
+
+enum {
+ kPosTopScreen = 0,
+ kPosRightScreen = 1,
+ kPosBottomScreen = 2,
+ kPosLeftScreen = 3
+};
+
+enum {
+ kNone = 0xFFFFFFFF, // (uint32_t)-1
+ kNoScreen = 0xFF, // (uint8_t)-1
+ kLvlAnimHdrOffset = 0x2C,
+ kMaxScreens = 40,
+ kMaxSpriteTypes = 32,
+ kMonsterInfoDataSize = 948 // (32 * 28 + 52)
+};
+
+enum {
+ kActionKeyMaskRun = 0x1,
+ kActionKeyMaskJump = 0x2,
+ kActionKeyMaskShoot = 0x4
+};
+
+enum {
+ kDirectionKeyMaskUp = 1, // climb
+ kDirectionKeyMaskRight = 2,
+ kDirectionKeyMaskDown = 4, // crouch
+ kDirectionKeyMaskLeft = 8,
+ kDirectionKeyMaskHorizontal = kDirectionKeyMaskLeft | kDirectionKeyMaskRight, // 0xA
+ kDirectionKeyMaskVertical = kDirectionKeyMaskDown | kDirectionKeyMaskUp // 0x5
+};
+
+enum {
+ kLvl_rock, // 0
+ kLvl_fort,
+ kLvl_pwr1,
+ kLvl_isld,
+ kLvl_lava, // 4
+ kLvl_pwr2,
+ kLvl_lar1,
+ kLvl_lar2,
+ kLvl_dark, // 8
+ kLvl_test
+};
+
+struct SetupConfig {
+ struct {
+ uint8_t progress[10];
+ uint8_t levelNum;
+ uint8_t checkpointNum;
+ uint32_t cutscenesMask;
+ uint8_t controls[32];
+ uint8_t difficulty;
+ uint8_t stereo;
+ uint8_t volume;
+ uint8_t lastLevelNum;
+ } players[4]; // sizeof == 52
+ uint8_t unkD0;
+ uint8_t currentPlayer; // 0xD1
+ uint8_t unkD2;
+ uint8_t checksum;
+}; // sizeof == 212
+
+struct Point8_t {
+ int8_t x;
+ int8_t y;
+} PACKED;
+
+struct Point16_t {
+ int16_t x;
+ int16_t y;
+};
+
+struct AnimBackgroundData {
+ const uint8_t *currentSpriteData; // 0
+ uint8_t *nextSpriteData; // 4
+ uint8_t *otherSpriteData; // 8
+ uint16_t framesCount; // 12
+ uint16_t currentFrame; // 14
+};
+
+struct LvlAnimSeqHeader {
+ uint16_t firstFrame;
+ uint16_t unk2;
+ int8_t dx; // 4
+ int8_t dy; // 5
+ uint8_t count; // 6
+ uint8_t unk7; // 7
+ uint16_t sound;
+ uint16_t flags0;
+ uint16_t flags1;
+ uint16_t unkE;
+ uint32_t offset; // 0x10, LvlAnimSeqFrameHeader
+} PACKED; // sizeof == 20
+
+struct LvlAnimSeqFrameHeader {
+ uint16_t move; // 0
+ uint16_t anim; // 2
+ uint8_t frame; // 4
+ uint8_t unk5; // 5
+ int8_t unk6;
+ int8_t unk7;
+} PACKED; // sizeof == 8
+
+struct LvlAnimHeader {
+ uint16_t unk0;
+ uint8_t seqCount;
+ uint8_t unk2;
+ uint32_t seqOffset;
+} PACKED; // sizeof == 8
+
+struct LvlSprMoveData {
+ uint8_t op1;
+ uint8_t op2;
+ uint16_t op3;
+ uint16_t op4;
+ uint16_t unk0x6; // padding to 8 bytes
+} PACKED; // sizeof == 8
+
+struct LvlSprHotspotData {
+ Point8_t pts[8];
+} PACKED; // sizeof == 16
+
+struct LvlObjectData {
+ uint8_t unk0;
+ uint8_t spriteNum;
+ uint16_t framesCount;
+ uint16_t hotspotsCount;
+ uint16_t movesCount;
+ uint16_t coordsCount;
+ uint8_t refCount; // 0xA
+ uint8_t frame; // 0xB
+ uint16_t anim; // 0xC
+ uint8_t *animsInfoData; // 0x10, LevelSprAnimInfo
+ uint8_t *movesData; // 0x14, LvlSprMoveData
+ uint8_t *framesData; // 0x18
+ uint8_t *framesOffsetsTable; // 0x1C
+ uint8_t *coordsData; // 0x20
+ uint8_t *coordsOffsetsTable; // 0x24
+ uint8_t *hotspotsData; // 0x28, LvlSprHotspotData
+};
+
+struct Game;
+struct SssObject;
+
+struct LvlObject {
+ int32_t xPos; // 0
+ int32_t yPos; // 4
+ uint8_t screenNum; // 8
+ uint8_t screenState;
+ uint8_t dataNum;
+ uint8_t frame;
+ uint16_t anim;
+ uint8_t type;
+ uint8_t spriteNum;
+ uint16_t flags0;
+ uint16_t flags1;
+ uint16_t flags2;
+ uint8_t objectUpdateType;
+ uint8_t hitCount;
+ LvlObject *childPtr; // _andyObject : plasma cannon object or specialAnimation
+ uint16_t width;
+ uint16_t height;
+ uint8_t actionKeyMask;
+ uint8_t directionKeyMask;
+ uint16_t currentSprite;
+ uint16_t currentSound; // 24
+ const uint8_t *bitmapBits; // 28
+ int (Game::*callbackFuncPtr)(LvlObject *ptr); // 2C
+ void *dataPtr; // 30
+ SssObject *sssObject; // 34
+ LvlObjectData *levelData0x2988;
+ Point16_t posTable[8];
+ LvlObject *nextPtr;
+};
+
+struct SssFilter;
+struct SssPcm;
+
+struct SssObject {
+ SssPcm *pcm; // 0x0
+ uint16_t num; // 0x4
+ uint16_t bankIndex; // 0x6
+ int8_t priority; // 0x8
+ int8_t currentPriority; // 0x9
+ uint8_t flags; // 0xA
+ bool stereo; // 0xB
+ uint32_t flags0; // 0xC
+ uint32_t flags1; // 0x10
+ int32_t panning; // 0x14 panning default:64
+ int32_t volume; // 0x18 volume (db) default:128
+ int panL; // 0x1C
+ int panR; // 0x20
+ int panType; // 0x24 : 0: silent, 1:fullRight 2:fullLeft 3:both
+ const int16_t *currentPcmPtr; // 0x28
+ int32_t pcmFramesCount; // 0x2C
+ SssObject *prevPtr; // 0x30
+ SssObject *nextPtr; // 0x34
+ const uint8_t *codeDataStage1; // 0x38
+ const uint8_t *codeDataStage2; // 0x3C
+ const uint8_t *codeDataStage3; // 0x40
+ const uint8_t *codeDataStage4; // 0x44
+ int32_t repeatCounter; // 0x48
+ int32_t pauseCounter; // 0x4C
+ int32_t delayCounter; // 0x50
+ int32_t volumeModulateSteps; // 0x54
+ int32_t panningModulateSteps; // 0x58
+ int32_t volumeModulateCurrent; // 0x5C
+ int32_t volumeModulateDelta; // 0x60
+ int32_t panningModulateCurrent; // 0x64
+ int32_t panningModulateDelta; // 0x68
+ int32_t currentPcmFrame; // 0x6C
+ int *panningPtr; // 0x70
+ LvlObject *lvlObject; // 0x74
+ int32_t nextSoundBank; // 0x78 indexes
+ int32_t nextSoundSample; // 0x7C
+ SssFilter *filter;
+};
+
+struct Sprite {
+ int16_t xPos;
+ int16_t yPos;
+ const uint8_t *bitmapBits;
+ Sprite *nextPtr;
+ uint16_t num;
+ uint16_t w, h;
+};
+
+struct BoundingBox {
+ int32_t x1; // 0
+ int32_t y1; // 4
+ int32_t x2; // 8
+ int32_t y2; // C
+};
+
+struct AndyLvlObjectData {
+ uint8_t unk0;
+ uint8_t unk1;
+ uint8_t unk2;
+ uint8_t unk3;
+ uint8_t unk4;
+ uint8_t unk5;
+ uint16_t unk6;
+ BoundingBox boundingBox; // 0x8
+ int32_t dxPos; // 0x18
+ int32_t dyPos; // 0x1C
+ LvlObject *shootLvlObject; // 0x20 'cannon plasma' or 'special powers'
+};
+
+struct ShootLvlObjectData {
+ uint8_t type; // 0x0
+ uint8_t state; // 0x1
+ uint8_t counter; // 0x2
+ uint8_t unk3; // 0x3 value
+ int32_t dxPos; // 0x4
+ int32_t dyPos; // 0x8
+ int32_t xPosShoot; // 0xC
+ int32_t yPosShoot; // 0x10
+ int32_t xPosObject; // 0x14
+ int32_t yPosObject; // 0x18
+ LvlObject *o; // 0x1C
+ ShootLvlObjectData *nextPtr; // 0x20 pointer to the next free entry
+};
+
+struct ScreenMask { // ShadowScreenMask
+ uint32_t dataSize;
+ uint8_t *projectionDataPtr;
+ uint8_t *shadowPalettePtr;
+ int16_t x;
+ int16_t y;
+ uint16_t w;
+ uint16_t h;
+}; // sizeof == 0x14
+
+struct WormHoleSprite {
+ uint8_t screenNum;
+ uint8_t initData1;
+ int8_t xPos;
+ int8_t yPos;
+ uint32_t initData4;
+ uint8_t rect1_x1;
+ uint8_t rect1_y1;
+ uint8_t rect1_x2;
+ uint8_t rect1_y2;
+ uint8_t rect2_x1;
+ uint8_t rect2_y1;
+ uint8_t rect2_x2;
+ uint8_t rect2_y2;
+ uint32_t flags[7]; // 0x10
+}; // sizeof == 0x2C
+
+struct MonsterObject1;
+
+struct AndyShootData {
+ int32_t xPos;
+ int32_t yPos; // 4
+ BoundingBox boundingBox; // 8
+ int32_t size; // 0x18
+ int32_t width;
+ int32_t height;
+ int32_t directionMask; // 0x24
+ ShootLvlObjectData *shootObjectData; // 0x28
+ LvlObject *o; // 0x2C
+ MonsterObject1 *m; // 0x30
+ int32_t clipX; // 0x34
+ int32_t clipY; // 0x38
+ int32_t monsterDistance; // 0x3C
+ uint8_t type; // 0x40
+ uint8_t plasmaCannonPointsCount; // 0x41
+}; // sizeof == 0x44
+
+struct AndyMoveData {
+ int32_t xPos;
+ int32_t yPos;
+ uint16_t anim; // 8
+ uint16_t unkA;
+ uint16_t unkC;
+ uint16_t unkE;
+ uint8_t frame; // 0x10
+ uint8_t unk11;
+ uint16_t flags0;
+ uint16_t flags1;
+ uint16_t unk16;
+ uint16_t unk18;
+ uint16_t unk1A;
+ const uint8_t *unk1C;
+ const uint8_t *framesData;
+ const uint8_t *unk24;
+ const uint8_t *unk28;
+}; // sizeof == 0x2C
+
+struct MstBoundingBox {
+ int x1; // 0
+ int y1; // 4
+ int x2; // 8
+ int y2; // 0xC
+ int monster1Index; // 0x10
+}; // sizeof == 0x20
+
+struct MstCollision {
+ MonsterObject1 *monster1[32]; // 0x00
+ uint32_t count; // 0x80
+}; // sizeof == 132
+
+struct Task;
+
+struct MstWalkCode;
+struct MstWalkPath;
+struct MstInfoMonster2;
+struct MstBehavior;
+struct MstBehaviorState;
+struct MstWalkNode;
+struct MstMonsterAreaAction;
+struct MstMovingBounds;
+struct MstMovingBoundsUnk1;
+
+struct MonsterObject1 {
+ MstBehavior *m46; // 0x0
+ MstBehaviorState *behaviorState; // 0x4
+ const uint8_t *monsterInfos; // 0x8
+ MstWalkNode *walkNode; // 0xC
+ LvlObject *o16; // 0x10
+ LvlObject *o20; // 0x14
+ MstMonsterAreaAction *action; // 0x18
+ AndyShootData *shootData; // 0x1C
+ int monster1Index; // 0x20
+ int executeCounter; // 0x24
+ int32_t localVars[8]; // 0x28
+ uint8_t flags48; // 0x48 0x4:indexUnk51!=kNone, 0x10:attackBox!=kNone
+ uint8_t facingDirectionMask; // 0x49
+ uint8_t goalDirectionMask; // 0x4A
+ uint8_t goalScreenNum; // 0x4B
+ int xPos; // 0x4C
+ int yPos; // 0x50
+ int xMstPos; // 0x54 currentLevelPos_x
+ int yMstPos; // 0x58 currentLevelPos_y
+ int xDelta; // 0x5C
+ int yDelta; // 0x60
+ int goalDistance_x1; // 0x64
+ int goalDistance_x2; // 0x68
+ int goalDistance_y1; // 0x6C
+ int goalDistance_y2; // 0x70
+ int goalPos_x1; // 0x74
+ int goalPos_y1; // 0x78
+ int goalPos_x2; // 0x7C
+ int goalPos_y2; // 0x80
+ int goalPosBounds_x2; // 0x84
+ int goalPosBounds_x1; // 0x88
+ // int goalPosBounds_y2; // 0x8C unused
+ // int goalPosBounds_y1; // 0x90 unused
+ int levelPosBounds_x2; // 0x94
+ int levelPosBounds_x1; // 0x98
+ int levelPosBounds_y2; // 0x9C
+ int levelPosBounds_y1; // 0xA0
+ uint8_t o_flags0; // 0xA4
+ uint8_t flagsA5; // 0xA5
+ uint8_t flagsA6; // 0xA6 |1:turning |2:idle |4:colliding
+ uint8_t targetDirectionMask; // 0xA7
+ uint8_t bboxNum[2]; // 0xA8, 0xA9
+ uint8_t walkBoxNum; // 0xAA
+ uint8_t unkAB; // 0xAB
+ int32_t targetLevelPos_x; // 0xAC
+ int32_t targetLevelPos_y; // 0xB0
+ int32_t previousDxPos; // 0xB4 _xMstPos2
+ int32_t previousDyPos; // 0xB8 _yMstPos2
+ int32_t unkBC; // 0xBC _xMstPos1
+ int32_t unkC0; // 0xC0 _yMstPos1
+ Task *task; // 0xC4
+ uint8_t rnd_m49[4]; // 0xC8
+ uint8_t rnd_m35[4]; // 0xCC
+ MstWalkCode *walkCode; // 0xD0
+ MstMovingBoundsUnk1 *m49Unk1; // 0xD4
+ MstMovingBounds *m49; // 0xD8
+ int indexUnk49Unk1; // 0xDC
+ uint8_t unkE4;
+ uint8_t unkE5;
+ uint8_t lut4Index; // 0xE6
+ uint8_t directionKeyMask;
+ uint16_t o_flags2; // 0xE8
+ int collideDistance; // 0xEC
+ int shootActionIndex; // 0xF0 [0..8]
+ int shootSource; // 0xF4
+ uint8_t unkF8; // 0xF8
+ int shootDirection; // 0xFC
+}; // sizeof == 256
+
+struct MonsterObject2 {
+ MstInfoMonster2 *monster2Info;
+ LvlObject *o; // 4
+ MonsterObject1 *monster1; // 8
+ int monster2Index; // 10
+ int xPos; // 14
+ int yPos; // 18
+ int xMstPos; // 1C
+ int yMstPos; // 20
+ uint8_t flags24; // 24
+ int x1; // 28
+ int x2; // 2C
+ int y1; // 30
+ int y2; // 34
+ uint8_t hPosIndex; // 38
+ uint8_t vPosIndex; // 39
+ uint8_t hDir; // 3A
+ uint8_t vDir; // 3B
+ Task *task; // 0x3C
+}; // sizeof == 64
+
+struct Task {
+ const uint8_t *codeData;
+ Task *prevPtr, *nextPtr; // 4,8
+ MonsterObject1 *monster1; // 0xC
+ MonsterObject2 *monster2; // 0x10
+ int32_t localVars[8]; // 0x14
+ uint8_t flags; // 0x34
+ uint8_t state; // 0x35
+ int16_t arg1; // 0x36 delay/counter/type
+ uint32_t arg2; // 0x38 num/index
+ int (Game::*run)(Task *t); // 0x3C
+ Task *child; // 0x40
+}; // sizeof == 0x44
+
+#endif // DEFS_H__
diff --git a/Src/Vita_&_Switch/fileio.cpp b/Src/Vita_&_Switch/fileio.cpp
new file mode 100644
index 0000000..0b2e9d9
--- /dev/null
+++ b/Src/Vita_&_Switch/fileio.cpp
@@ -0,0 +1,158 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include
+#include "fileio.h"
+#include "util.h"
+
+static const bool kCheckSectorFileCrc = false;
+
+#ifdef PSP
+static const bool kSeekAbsolutePosition = true;
+#else
+static const bool kSeekAbsolutePosition = false;
+#endif
+
+File::File()
+ : _fp(0) {
+}
+
+File::~File() {
+}
+
+void File::setFp(FILE *fp) {
+ _fp = fp;
+}
+
+void File::seekAlign(uint32_t pos) {
+ fseek(_fp, pos, SEEK_SET);
+}
+
+void File::seek(int pos, int whence) {
+ if (kSeekAbsolutePosition && whence == SEEK_CUR) {
+ pos += ftell(_fp);
+ whence = SEEK_SET;
+ }
+ fseek(_fp, pos, whence);
+}
+
+int File::read(uint8_t *ptr, int size) {
+ return fread(ptr, 1, size, _fp);
+}
+
+uint8_t File::readByte() {
+ uint8_t buf;
+ read(&buf, 1);
+ return buf;
+}
+
+uint16_t File::readUint16() {
+ uint8_t buf[2];
+ read(buf, 2);
+ return READ_LE_UINT16(buf);
+}
+
+uint32_t File::readUint32() {
+ uint8_t buf[4];
+ read(buf, 4);
+ return READ_LE_UINT32(buf);
+}
+
+SectorFile::SectorFile() {
+ memset(_buf, 0, sizeof(_buf));
+ _bufPos = 2044;
+}
+
+int fioAlignSizeTo2048(int size) {
+ return ((size + 2043) / 2044) * 2048;
+}
+
+uint32_t fioUpdateCRC(uint32_t sum, const uint8_t *buf, uint32_t size) {
+ assert((size & 3) == 0);
+ for (uint32_t offset = 0; offset < size; offset += 4) {
+ sum ^= READ_LE_UINT32(buf + offset);
+ }
+ return sum;
+}
+
+void SectorFile::refillBuffer(uint8_t *ptr) {
+ if (ptr) {
+ static const int kPayloadSize = kFioBufferSize - 4;
+ const int size = fread(ptr, 1, kPayloadSize, _fp);
+ assert(size == kPayloadSize);
+ uint8_t buf[4];
+ const int count = fread(buf, 1, 4, _fp);
+ assert(count == 4);
+ if (kCheckSectorFileCrc) {
+ const uint32_t crc = fioUpdateCRC(0, ptr, kPayloadSize);
+ assert(crc == READ_LE_UINT32(buf));
+ }
+ } else {
+ const int size = fread(_buf, 1, kFioBufferSize, _fp);
+ assert(size == kFioBufferSize);
+ if (kCheckSectorFileCrc) {
+ const uint32_t crc = fioUpdateCRC(0, _buf, kFioBufferSize);
+ assert(crc == 0);
+ }
+ _bufPos = 0;
+ }
+}
+
+void SectorFile::seekAlign(uint32_t pos) {
+ pos += (pos / 2048) * 4;
+ const long alignPos = pos & ~2047;
+ if (alignPos != (ftell(_fp) - 2048)) {
+ fseek(_fp, alignPos, SEEK_SET);
+ refillBuffer();
+ }
+ _bufPos = pos - alignPos;
+}
+
+void SectorFile::seek(int pos, int whence) {
+ if (whence == SEEK_SET) {
+ assert((pos & 2047) == 0);
+ _bufPos = 2044;
+ File::seek(pos, SEEK_SET);
+ } else {
+ assert(whence == SEEK_CUR && pos >= 0);
+ const int bufLen = 2044 - _bufPos;
+ if (pos < bufLen) {
+ _bufPos += pos;
+ } else {
+ pos -= bufLen;
+ const int count = (fioAlignSizeTo2048(pos) / 2048) - 1;
+ if (count > 0) {
+ const int alignPos = count * 2048;
+ fseek(_fp, alignPos, SEEK_CUR);
+ }
+ refillBuffer();
+ _bufPos = pos % 2044;
+ }
+ }
+}
+
+int SectorFile::read(uint8_t *ptr, int size) {
+ const int bufLen = 2044 - _bufPos;
+ if (size >= bufLen) {
+ if (bufLen) {
+ memcpy(ptr, _buf + _bufPos, bufLen);
+ ptr += bufLen;
+ size -= bufLen;
+ }
+ const int count = (fioAlignSizeTo2048(size) / 2048) - 1;
+ for (int i = 0; i < count; ++i) {
+ refillBuffer(ptr);
+ ptr += 2044;
+ size -= 2044;
+ }
+ refillBuffer();
+ }
+ if (size != 0) {
+ assert(size <= 2044 - _bufPos);
+ memcpy(ptr, _buf + _bufPos, size);
+ _bufPos += size;
+ }
+ return 0;
+}
diff --git a/Src/Vita_&_Switch/fileio.h b/Src/Vita_&_Switch/fileio.h
new file mode 100644
index 0000000..44d7fc9
--- /dev/null
+++ b/Src/Vita_&_Switch/fileio.h
@@ -0,0 +1,53 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef FILEIO_H__
+#define FILEIO_H__
+
+#include "intern.h"
+
+struct File {
+
+ FILE *_fp;
+
+ File();
+ virtual ~File();
+
+ void setFp(FILE *fp);
+
+ virtual void seekAlign(uint32_t pos);
+ virtual void seek(int pos, int whence);
+ virtual int read(uint8_t *ptr, int size);
+ uint8_t readByte();
+ uint16_t readUint16();
+ uint32_t readUint32();
+
+ void skipByte() { seek(1, SEEK_CUR); }
+ void skipUint16() { seek(2, SEEK_CUR); }
+ void skipUint32() { seek(4, SEEK_CUR); }
+};
+
+struct SectorFile : File {
+
+ enum {
+ kFioBufferSize = 2048
+ };
+
+ uint8_t _buf[kFioBufferSize];
+ int _bufPos;
+
+ SectorFile();
+
+ void refillBuffer(uint8_t *ptr = 0);
+ virtual void seekAlign(uint32_t pos);
+ virtual void seek(int pos, int whence);
+ virtual int read(uint8_t *ptr, int size);
+};
+
+int fioAlignSizeTo2048(int size);
+uint32_t fioUpdateCRC(uint32_t sum, const uint8_t *buf, uint32_t size);
+
+#endif // FILEIO_H__
+
diff --git a/Src/Vita_&_Switch/fs.h b/Src/Vita_&_Switch/fs.h
new file mode 100644
index 0000000..0bb0217
--- /dev/null
+++ b/Src/Vita_&_Switch/fs.h
@@ -0,0 +1,25 @@
+
+#ifndef FS_H__
+#define FS_H__
+
+#include
+
+struct FileSystem {
+
+ const char *_dataPath;
+ const char *_savePath;
+ int _filesCount;
+ char **_filesList;
+
+ FileSystem(const char *dataPath, const char *savePath);
+ ~FileSystem();
+
+ FILE *openAssetFile(const char *filename);
+ FILE *openSaveFile(const char *filename, bool write);
+ int closeFile(FILE *);
+
+ void addFilePath(const char *path);
+ void listFiles(const char *dir);
+};
+
+#endif // FS_H__
diff --git a/Src/Vita_&_Switch/fs_android.cpp b/Src/Vita_&_Switch/fs_android.cpp
new file mode 100644
index 0000000..1679c1a
--- /dev/null
+++ b/Src/Vita_&_Switch/fs_android.cpp
@@ -0,0 +1,121 @@
+
+#define LOG_TAG "HodJni"
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "fs.h"
+
+static AAssetManager *_assetManager;
+static const char *_dataPath;
+
+static int asset_readfn(void *cookie, char *buf, int size) {
+ AAsset *asset = (AAsset *)cookie;
+ return AAsset_read(asset, buf, size);
+}
+
+static int asset_writefn(void *cookie, const char *buf, int size) {
+ return -1;
+}
+
+static fpos_t asset_seekfn(void *cookie, fpos_t offset, int whence) {
+ AAsset *asset = (AAsset *)cookie;
+ return AAsset_seek(asset, offset, whence);
+}
+
+static int asset_closefn(void *cookie) {
+ AAsset *asset = (AAsset *)cookie;
+ AAsset_close(asset);
+ return 0;
+}
+
+void android_setAssetManager(AAssetManager *assetManager) {
+ _assetManager = assetManager;
+}
+
+FILE *android_fopen(const char *fname, const char *mode) {
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "android_fopen '%s' mode '%s'", fname, mode);
+ assert(mode[0] == 'r');
+ if (1) { // support for gamedata files installed on external storage
+ char path[MAXPATHLEN];
+ snprintf(path, sizeof(path), "%s/%s", _dataPath, fname);
+ struct stat st;
+ if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) {
+ return fopen(path, mode);
+ }
+ }
+ AAsset *asset = AAssetManager_open(_assetManager, fname, AASSET_MODE_STREAMING);
+ if (asset) {
+ FILE *fp = funopen(asset, asset_readfn, asset_writefn, asset_seekfn, asset_closefn);
+ return fp;
+ }
+ return fopen(fname, mode);
+}
+
+//
+// FileSystem
+//
+
+FileSystem::FileSystem(const char *dataPath, const char *savePath) {
+ JNIEnv *env = (JNIEnv *)SDL_AndroidGetJNIEnv();
+ jobject activity = (jobject)SDL_AndroidGetActivity();
+
+ jmethodID methodGetAssets = env->GetMethodID(env->GetObjectClass(activity), "getAssets", "()Landroid/content/res/AssetManager;");
+ assert(methodGetAssets);
+
+ jobject localAssetManager = env->CallObjectMethod(activity, methodGetAssets);
+ assert(localAssetManager);
+
+ jobject globalAssetManager = env->NewGlobalRef(localAssetManager);
+ assert(globalAssetManager);
+
+ _assetManager = AAssetManager_fromJava(env, globalAssetManager);
+ _dataPath = dataPath;
+ _savePath = savePath;
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "dataPath %s _assetManager %p", _dataPath, _assetManager);
+}
+
+FileSystem::~FileSystem() {
+}
+
+FILE *FileSystem::openAssetFile(const char *filename) {
+ char *name = (char *)alloca(strlen(filename) + 1);
+ for (int i = 0; i < strlen(filename) + 1; ++i) {
+ if (filename[i] >= 'A' && filename[i] <= 'Z') {
+ name[i] = 'a' + (filename[i] - 'A');
+ } else {
+ name[i] = filename[i];
+ }
+ }
+ FILE *fp = android_fopen(name, "rb");
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "android_fopen %s %p", name, fp);
+ return fp;
+}
+
+FILE *FileSystem::openSaveFile(const char *filename, bool write) {
+ FILE *fp = 0;
+ char *prefPath = SDL_GetPrefPath(ANDROID_PACKAGE_NAME, "hode");
+ if (prefPath) {
+ char path[MAXPATHLEN];
+ snprintf(path, sizeof(path), "%s/%s", prefPath, filename);
+ fp = fopen(path, write ? "wb" : "rb");
+ SDL_free(prefPath);
+ }
+ return fp;
+}
+
+int FileSystem::closeFile(FILE *fp) {
+ const int err = ferror(fp);
+ fclose(fp);
+ return err;
+}
diff --git a/Src/Vita_&_Switch/fs_posix.cpp b/Src/Vita_&_Switch/fs_posix.cpp
new file mode 100644
index 0000000..e255e91
--- /dev/null
+++ b/Src/Vita_&_Switch/fs_posix.cpp
@@ -0,0 +1,98 @@
+
+#include
+#include
+#include
+#include
+#include
+#include "fs.h"
+#include "util.h"
+
+static const char *_suffixes[] = {
+ "hod.dem",
+ "setup.dat",
+ "setup.dax",
+ ".paf",
+ "_hod.lvl",
+ "_hod.sss",
+ "_hod.mst",
+ 0
+};
+
+static bool matchGameData(const char *path) {
+ const int len = strlen(path);
+ for (int i = 0; _suffixes[i]; ++i) {
+ const int suffixLen = strlen(_suffixes[i]);
+ if (len >= suffixLen && strcasecmp(path + len - suffixLen, _suffixes[i]) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+FileSystem::FileSystem(const char *dataPath, const char *savePath)
+ : _dataPath(dataPath), _savePath(savePath), _filesCount(0), _filesList(0) {
+ listFiles(dataPath);
+}
+
+FileSystem::~FileSystem() {
+ for (int i = 0; i < _filesCount; ++i) {
+ free(_filesList[i]);
+ }
+ free(_filesList);
+}
+
+FILE *FileSystem::openAssetFile(const char *name) {
+ FILE *fp = 0;
+ for (int i = 0; i < _filesCount; ++i) {
+ const char *p = strrchr(_filesList[i], '/');
+ assert(p);
+ if (strcasecmp(name, p + 1) == 0) {
+ fp = fopen(_filesList[i], "rb");
+ break;
+ }
+ }
+ return fp;
+}
+
+FILE *FileSystem::openSaveFile(const char *filename, bool write) {
+ char path[MAXPATHLEN];
+ snprintf(path, sizeof(path), "%s/%s", _savePath, filename);
+ return fopen(path, write ? "wb" : "rb");
+}
+
+int FileSystem::closeFile(FILE *fp) {
+ const int err = ferror(fp);
+ fclose(fp);
+ return err;
+}
+
+void FileSystem::addFilePath(const char *path) {
+ _filesList = (char **)realloc(_filesList, (_filesCount + 1) * sizeof(char *));
+ if (_filesList) {
+ _filesList[_filesCount] = strdup(path);
+ ++_filesCount;
+ }
+}
+
+void FileSystem::listFiles(const char *dir) {
+ DIR *d = opendir(dir);
+ if (d) {
+ dirent *de;
+ while ((de = readdir(d)) != NULL) {
+ if (de->d_name[0] == '.') {
+ continue;
+ }
+ char filePath[MAXPATHLEN];
+ snprintf(filePath, sizeof(filePath), "%s/%s", dir, de->d_name);
+ struct stat st;
+ if (stat(filePath, &st) == 0) {
+ if (S_ISDIR(st.st_mode)) {
+ listFiles(filePath);
+ } else if (matchGameData(filePath)) {
+ addFilePath(filePath);
+ }
+ }
+ }
+ closedir(d);
+ }
+}
diff --git a/Src/Vita_&_Switch/game.cpp b/Src/Vita_&_Switch/game.cpp
new file mode 100644
index 0000000..b30d9cd
--- /dev/null
+++ b/Src/Vita_&_Switch/game.cpp
@@ -0,0 +1,4863 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "game.h"
+#include "fileio.h"
+#include "level.h"
+#include "lzw.h"
+#include "paf.h"
+#include "screenshot.h"
+#include "system.h"
+#include "util.h"
+#include "video.h"
+
+// starting level cutscene number
+static const uint8_t _cutscenes[] = { 0, 2, 4, 5, 6, 8, 10, 14, 19 };
+
+Game::Game(const char *dataPath, const char *savePath, uint32_t cheats)
+ : _fs(dataPath, savePath) {
+
+ _level = 0;
+ _res = new Resource(&_fs);
+ _paf = new PafPlayer(&_fs);
+ _rnd.setSeed();
+ _video = new Video();
+ _cheats = cheats;
+ _playDemo = false;
+
+ _frameMs = kFrameTimeStamp;
+ _difficulty = 1;
+ _loadingScreenEnabled = true;
+
+ memset(_screenLvlObjectsList, 0, sizeof(_screenLvlObjectsList));
+ _andyObject = 0;
+ _plasmaExplosionObject = 0;
+ _plasmaCannonObject = 0;
+ memset(_spritesTable, 0, sizeof(_spritesTable));
+ memset(_typeSpritesList, 0, sizeof(_typeSpritesList));
+
+ _directionKeyMask = 0;
+ _actionKeyMask = 0;
+
+ _currentScreen = 0;
+
+ _lvlObjectsList0 = 0;
+ _lvlObjectsList1 = 0;
+ _lvlObjectsList2 = 0;
+ _lvlObjectsList3 = 0;
+ memset(_screenMaskBuffer, 0, sizeof(_screenMaskBuffer));
+ memset(_shootLvlObjectDataTable, 0, sizeof(_shootLvlObjectDataTable));
+ _mstAndyCurrentScreenNum = -1;
+ _plasmaCannonDirection = 0;
+ _andyActionKeyMaskAnd = 0xFF;
+ _andyActionKeyMaskOr = 0;
+ _andyDirectionKeyMaskAnd = 0xFF;
+ _andyDirectionKeyMaskOr = 0;
+
+ _mstDisabled = false;
+ _specialAnimMask = 0; // original only clears ~0x30
+ _mstCurrentAnim = 0;
+ _mstOriginPosX = Video::W / 2;
+ _mstOriginPosY = Video::H / 2;
+ memset(_declaredLvlObjectsList, 0, sizeof(_declaredLvlObjectsList));
+ _declaredLvlObjectsNextPtr = 0;
+ _declaredLvlObjectsListCount = 0;
+
+ memset(_animBackgroundDataTable, 0, sizeof(_animBackgroundDataTable));
+ _animBackgroundDataCount = 0;
+
+ memset(_monsterObjects1Table, 0, sizeof(_monsterObjects1Table));
+ memset(_monsterObjects2Table, 0, sizeof(_monsterObjects2Table));
+
+ _sssDisabled = false;
+ _snd_muted = false;
+ _snd_masterPanning = kDefaultSoundPanning;
+ _snd_masterVolume = kDefaultSoundVolume;
+ _sssObjectsCount = 0;
+ _sssObjectsList1 = 0;
+ _sssObjectsList2 = 0;
+ _playingSssObjectsMax = 16; // 10 if (lowMemory || slowCPU)
+}
+
+Game::~Game() {
+ delete _paf;
+ delete _res;
+ delete _video;
+}
+
+void Game::resetShootLvlObjectDataTable() {
+ _shootLvlObjectDataNextPtr = &_shootLvlObjectDataTable[0];
+ for (int i = 0; i < kMaxShootLvlObjectData - 1; ++i) {
+ _shootLvlObjectDataTable[i].nextPtr = &_shootLvlObjectDataTable[i + 1];
+ }
+ _shootLvlObjectDataTable[kMaxShootLvlObjectData - 1].nextPtr = 0;
+}
+
+void Game::clearShootLvlObjectData(LvlObject *ptr) {
+ ShootLvlObjectData *dat = (ShootLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeShoot);
+ dat->nextPtr = _shootLvlObjectDataNextPtr;
+ _shootLvlObjectDataNextPtr = dat;
+ ptr->dataPtr = 0;
+}
+
+void Game::setShakeScreen(int type, int counter) {
+ static const uint8_t table1[] = { 1, 2, 3, 4 };
+ static const uint8_t table2[] = { 2, 3, 4, 5 };
+ static const uint8_t table3[] = { 3, 4, 5, 8 };
+ switch (type) {
+ case 1:
+ _shakeScreenTable = table1;
+ break;
+ case 2:
+ _shakeScreenTable = table2;
+ break;
+ case 3:
+ _shakeScreenTable = table3;
+ break;
+ default:
+ return;
+ }
+ _shakeScreenDuration = counter;
+}
+
+void Game::fadeScreenPalette() {
+ if (!_fadePalette) {
+ assert(_levelRestartCounter != 0);
+ for (int i = 0; i < 256 * 3; ++i) {
+ _video->_fadePaletteBuffer[i] = _video->_displayPaletteBuffer[i] / _levelRestartCounter;
+ }
+ _fadePalette = true;
+ } else {
+ if (_levelRestartCounter != 0) {
+ _snd_masterVolume -= _snd_masterVolume / _levelRestartCounter;
+ if (_snd_masterVolume < 0) {
+ _snd_masterVolume = 0;
+ }
+ }
+ --_levelRestartCounter;
+ }
+ for (int i = 0; i < 256 * 3; ++i) {
+ int color = _video->_displayPaletteBuffer[i] - _video->_fadePaletteBuffer[i];
+ if (color < 0) {
+ color = 0;
+ }
+ _video->_displayPaletteBuffer[i] = color;
+ }
+ _video->_paletteChanged = true;
+}
+
+void Game::shakeScreen() {
+ if (_video->_displayShadowLayer) {
+ const int num = (_currentLevel == kLvl_lava || _currentLevel == kLvl_lar1) ? 1 : 4;
+ transformShadowLayer(num);
+ }
+ if (_shakeScreenDuration != 0) {
+ --_shakeScreenDuration;
+ if (_shakeScreenDuration != 0) {
+ const int r1 = _rnd.update() & 3;
+ const int r2 = _rnd.update() & 3;
+ int dx = _shakeScreenTable[r2] & ~3;
+ if (r1 & 1) {
+ dx = -dx;
+ }
+ int dy = _shakeScreenTable[r1];
+ if (_shakeScreenDuration & 1) {
+ dy = -dy;
+ }
+ g_system->shakeScreen(dx, dy);
+ }
+ }
+ if (_levelRestartCounter != 0) {
+ fadeScreenPalette();
+ }
+}
+
+static BoundingBox _screenTransformRects[] = {
+ { 0, 0, 0, 0 },
+ { 0, 0, 255, 128 },
+ { 0, 10, 154, 126 },
+ { 0, 0, 107, 36 },
+ { 0, 1, 78, 29 },
+ { 14, 7, 249, 72 },
+ { 14, 0, 255, 72 },
+ { 0, 0, 255, 144 },
+ { 0, 0, 255, 144 },
+ { 0, 69, 255, 191 }
+};
+
+void Game::transformShadowLayer(int delta) {
+ const uint8_t *src = _video->_transformShadowBuffer + _video->_transformShadowLayerDelta; // vg
+ uint8_t *dst = _video->_shadowLayer; // va
+ _video->_transformShadowLayerDelta += delta; // overflow/wrap at 255
+ for (int y = 0; y < Video::H; ++y) {
+ if (0) {
+ for (int x = 0; x < Video::W - 6; ++x) {
+ const int offset = x + *src++;
+ *dst++ = _video->_frontLayer[y * Video::W + offset];
+ }
+ memset(dst, Video::CLEAR_COLOR, 6);
+ dst += 6;
+ src += 6;
+ } else {
+ for (int x = 0; x < Video::W; ++x) {
+ const int offset = MIN(255, x + *src++);
+ *dst++ = _video->_frontLayer[y * Video::W + offset];
+ }
+ }
+ }
+ uint8_t r = 0;
+ if (_currentLevel == kLvl_pwr1) {
+ r = _pwr1_screenTransformLut[_res->_currentScreenResourceNum * 2 + 1];
+ } else if (_currentLevel == kLvl_pwr2) {
+ r = _pwr2_screenTransformLut[_res->_currentScreenResourceNum * 2 + 1];
+ }
+ if (r != 0) {
+ assert(r < ARRAYSIZE(_screenTransformRects));
+ const BoundingBox *b = &_screenTransformRects[r];
+ const int offset = b->y1 * Video::W + b->x1;
+ src = _video->_frontLayer + offset;
+ dst = _video->_shadowLayer + offset;
+ for (int y = b->y1; y < b->y2; ++y) {
+ for (int x = b->x1; x < b->x2; ++x) {
+ dst[x] = src[x];
+ }
+ dst += Video::W;
+ src += Video::W;
+ }
+ }
+}
+
+void Game::loadTransformLayerData(const uint8_t *data) {
+ assert(!_video->_transformShadowBuffer);
+ _video->_transformShadowBuffer = (uint8_t *)malloc(256 * 192 + 256);
+ const int size = decodeLZW(data, _video->_transformShadowBuffer);
+ assert(size == 256 * 192);
+ memcpy(_video->_transformShadowBuffer + 256 * 192, _video->_transformShadowBuffer, 256);
+}
+
+void Game::unloadTransformLayerData() {
+ free(_video->_transformShadowBuffer);
+ _video->_transformShadowBuffer = 0;
+}
+
+void Game::decodeShadowScreenMask(LvlBackgroundData *lvl) {
+ uint8_t *dst = _video->_shadowScreenMaskBuffer;
+ for (int i = lvl->currentShadowId; i < lvl->shadowCount; ++i) {
+ const uint8_t *src = lvl->backgroundMaskTable[i];
+ if (src) {
+ const int decodedSize = decodeLZW(src + 2, dst);
+
+ _shadowScreenMasksTable[i].dataSize = READ_LE_UINT32(dst);
+
+ // header : 20 bytes
+ // projectionData : w * h * sizeof(uint16_t) - for a given (x, y) returns the casted (x, y)
+ // paletteData : 256 (only the first 144 bytes are read)
+
+ _shadowScreenMasksTable[i].projectionDataPtr = dst + 0x14 + READ_LE_UINT32(dst + 4);
+ _shadowScreenMasksTable[i].shadowPalettePtr = dst + 0x14 + READ_LE_UINT32(dst + 8);
+ const int x = _shadowScreenMasksTable[i].x = READ_LE_UINT16(dst + 0xC);
+ const int y = _shadowScreenMasksTable[i].y = READ_LE_UINT16(dst + 0xE);
+ const int w = _shadowScreenMasksTable[i].w = READ_LE_UINT16(dst + 0x10);
+ const int h = _shadowScreenMasksTable[i].h = READ_LE_UINT16(dst + 0x12);
+
+ debug(kDebug_GAME, "shadow screen mask #%d pos %d,%d dim %d,%d size %d", i, x, y, w, h, decodedSize);
+
+ const int size = w * h;
+ uint8_t *p = _shadowScreenMasksTable[i].projectionDataPtr + 2;
+ for (int j = 1; j < size; ++j) {
+ const int16_t offset = (int16_t)READ_LE_UINT16(p - 2) + (int16_t)READ_LE_UINT16(p);
+ // fprintf(stdout, "shadow #%d offset #%d 0x%x 0x%x\n", i, j, READ_LE_UINT16(p), offset);
+ WRITE_LE_UINT16(p, offset);
+ p += 2;
+ }
+
+ const int shadowPaletteSize = decodedSize - 20 - w * h * sizeof(uint16_t);
+ assert(shadowPaletteSize >= 144);
+
+ _video->buildShadowColorLookupTable(_shadowScreenMasksTable[i].shadowPalettePtr, _video->_shadowColorLookupTable);
+ dst += decodedSize;
+ }
+ }
+}
+
+// a: type/source (0, 1, 2) b: num/index (3, monster1Index, monster2.monster1Index)
+void Game::playSound(int num, LvlObject *ptr, int a, int b) {
+ MixerLock ml(&_mix);
+ if (num < _res->_sssHdr.infosDataCount) {
+ debug(kDebug_GAME, "playSound num %d/%d a=%d b=%d", num, _res->_sssHdr.infosDataCount, a, b);
+ _currentSoundLvlObject = ptr;
+ playSoundObject(&_res->_sssInfosData[num], a, b);
+ _currentSoundLvlObject = 0;
+ }
+}
+
+void Game::removeSound(LvlObject *ptr) {
+ MixerLock ml(&_mix);
+ for (int i = 0; i < _sssObjectsCount; ++i) {
+ if (_sssObjectsTable[i].lvlObject == ptr) {
+ _sssObjectsTable[i].lvlObject = 0;
+ }
+ }
+ ptr->sssObject = 0;
+}
+
+void Game::setupBackgroundBitmap() {
+ LvlBackgroundData *lvl = &_res->_resLvlScreenBackgroundDataTable[_res->_currentScreenResourceNum];
+ const int num = lvl->currentBackgroundId;
+ const uint8_t *pal = lvl->backgroundPaletteTable[num];
+ lvl->backgroundPaletteId = READ_LE_UINT16(pal); pal += 2;
+ const uint8_t *pic = lvl->backgroundBitmapTable[num];
+ lvl->backgroundBitmapId = READ_LE_UINT16(pic); pic += 2;
+ if (lvl->backgroundPaletteId != 0xFFFF) {
+ playSound(lvl->backgroundPaletteId, 0, 0, 3);
+ }
+ if (lvl->backgroundBitmapId != 0xFFFF) {
+ playSound(lvl->backgroundBitmapId, 0, 0, 3);
+ }
+ if (_res->_isPsx) {
+ const int size = Video::W * Video::H * sizeof(uint16_t);
+ _video->decodeBackgroundPsx(pic + 2, size, Video::W, Video::H);
+ } else {
+ decodeLZW(pic, _video->_backgroundLayer);
+ }
+ if (lvl->shadowCount != 0) {
+ decodeShadowScreenMask(lvl);
+ }
+ for (int i = 0; i < 256 * 3; ++i) {
+ _video->_displayPaletteBuffer[i] = pal[i] << 8;
+ }
+ _video->_paletteChanged = true;
+}
+
+void Game::addToSpriteList(Sprite *spr) {
+ _spritesNextPtr = spr->nextPtr;
+ const int index = spr->num & 0x1F;
+ spr->nextPtr = _typeSpritesList[index];
+ _typeSpritesList[index] = spr;
+}
+
+void Game::addToSpriteList(LvlObject *ptr) {
+ Sprite *spr = _spritesNextPtr;
+ if (spr) {
+ const uint8_t num = _res->_currentScreenResourceNum;
+ const uint8_t *grid = _res->_screensGrid[num];
+
+ LvlObjectData *dat = ptr->levelData0x2988;
+ LvlAnimHeader *ah = (LvlAnimHeader *)(dat->animsInfoData + kLvlAnimHdrOffset) + ptr->anim;
+ LvlAnimSeqHeader *ash = (LvlAnimSeqHeader *)(dat->animsInfoData + ah->seqOffset) + ptr->frame;
+
+ spr->num = (((ash->flags1 ^ ptr->flags1) & 0xFFF0) << 10) | (ptr->flags2 & 0x3FFF);
+
+ int index = ptr->screenNum;
+ spr->xPos = ptr->xPos;
+ spr->yPos = ptr->yPos;
+ if (index == grid[kPosTopScreen]) {
+ spr->yPos -= Video::H;
+ } else if (index == grid[kPosBottomScreen]) {
+ spr->yPos += Video::H;
+ } else if (index == grid[kPosRightScreen]) {
+ spr->xPos += Video::W;
+ } else if (index == grid[kPosLeftScreen]) {
+ spr->xPos -= Video::W;
+ } else if (index != num) {
+ return;
+ }
+ if (spr->xPos >= Video::W || spr->xPos + ptr->width < 0) {
+ return;
+ }
+ if (spr->yPos >= Video::H || spr->yPos + ptr->height < 0) {
+ return;
+ }
+ if (_currentLevel == kLvl_isld && ptr->spriteNum == 2) {
+ AndyLvlObjectData *dataPtr = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ spr->xPos += dataPtr->dxPos;
+ }
+ if (ptr->bitmapBits) {
+ spr->w = ptr->width;
+ spr->h = ptr->height;
+ spr->bitmapBits = ptr->bitmapBits;
+ addToSpriteList(spr);
+ }
+ }
+}
+
+int16_t Game::calcScreenMaskDy(int16_t xPos, int16_t yPos, int num) {
+ if (xPos < 0) {
+ xPos += Video::W;
+ num = _res->_screensGrid[num][kPosLeftScreen];
+ } else if (xPos >= Video::W) {
+ xPos -= Video::W;
+ num = _res->_screensGrid[num][kPosRightScreen];
+ }
+ if (num != kNoScreen) {
+ if (yPos < 0) {
+ yPos += Video::H;
+ num = _res->_screensGrid[num][kPosTopScreen];
+ } else if (yPos >= Video::H) {
+ yPos -= Video::H;
+ num = _res->_screensGrid[num][kPosBottomScreen];
+ }
+ }
+ uint8_t var1 = 0xFF - (yPos & 7);
+ if (num == kNoScreen) {
+ return var1;
+ }
+ int vg = screenMaskOffset(_res->_screensBasePos[num].u + xPos, _res->_screensBasePos[num].v + yPos);
+ int vf = screenGridOffset(xPos, yPos);
+ if (_screenMaskBuffer[vg - 512] & 1) {
+ vf -= 32;
+ var1 -= 8;
+ } else if (_screenMaskBuffer[vg] & 1) {
+ /* nothing to do */
+ } else if (_screenMaskBuffer[vg + 512] & 1) {
+ vf += 32;
+ var1 += 8;
+ } else if (_screenMaskBuffer[vg + 1024] & 1) {
+ vf += 64;
+ var1 += 16;
+ } else {
+ return 0;
+ }
+ int _dl = _res->findScreenGridIndex(num);
+ if (_dl < 0) {
+ return (int8_t)(var1 + 4);
+ }
+ const uint8_t *p = _res->_resLevelData0x470CTablePtrData + (xPos & 7);
+ return (int8_t)(var1 + p[_screenPosTable[_dl][vf] * 8]);
+}
+
+void Game::setupScreenPosTable(uint8_t num) {
+ const uint8_t *src = _res->_screensGrid[num];
+ for (int i = 0; i < 4; ++i) {
+ if (src[i] != kNoScreen) {
+ int index = _res->_resLvlScreenBackgroundDataTable[src[i]].currentMaskId;
+ const uint8_t *p = _res->getLvlScreenPosDataPtr(src[i] * 4 + index);
+ if (p) {
+ Video::decodeRLE(p, _screenPosTable[i], 32 * 24);
+ continue;
+ }
+ }
+ memset(_screenPosTable[i], 0, 32 * 24);
+ }
+ int index = _res->_resLvlScreenBackgroundDataTable[num].currentMaskId;
+ const uint8_t *p = _res->getLvlScreenPosDataPtr(num * 4 + index);
+ if (p) {
+ Video::decodeRLE(p, _screenPosTable[4], 32 * 24);
+ } else {
+ memset(_screenPosTable[4], 0, 32 * 24);
+ }
+}
+
+void Game::setupScreenMask(uint8_t num) {
+ if (num == kNoScreen) {
+ return;
+ }
+ int mask = _res->_resLvlScreenBackgroundDataTable[num].currentMaskId;
+ if (_res->_screensState[num].s3 != mask) {
+ debug(kDebug_GAME, "setupScreenMask num %d mask %d", num, mask);
+ _res->_screensState[num].s3 = mask;
+ const uint8_t *maskData = _res->getLvlScreenMaskDataPtr(num * 4 + mask);
+ if (maskData) {
+ Video::decodeRLE(maskData, _screenTempMaskBuffer, 32 * 24);
+ } else {
+ memset(_screenTempMaskBuffer, 0, 32 * 24);
+ }
+ uint8_t *p = _screenMaskBuffer + screenMaskOffset(_res->_screensBasePos[num].u, _res->_screensBasePos[num].v);
+ for (int i = 0; i < 24; ++i) {
+ memcpy(p, _screenTempMaskBuffer + i * 32, 32);
+ p += 512;
+ }
+ }
+ if (_res->_currentScreenResourceNum == num) {
+ setupScreenPosTable(num);
+ }
+}
+
+void Game::resetScreenMask() {
+ memset(_screenMaskBuffer, 0, sizeof(_screenMaskBuffer));
+ for (int i = 0; i < _res->_lvlHdr.screensCount; ++i) {
+ _res->_screensState[i].s3 = 0xFF;
+ setupScreenMask(i);
+ }
+}
+
+void Game::setScreenMaskRectHelper(int x1, int y1, int x2, int y2, int screenNum) {
+ if (screenNum != kNoScreen) {
+
+ int index = _res->_resLvlScreenBackgroundDataTable[screenNum].currentMaskId;
+ const uint8_t *p = _res->getLvlScreenMaskDataPtr(screenNum * 4 + index);
+ Video::decodeRLE(p, _screenTempMaskBuffer, 32 * 24);
+
+ int h = (y2 - y1 + 7) >> 3;
+ int w = (x2 - x1 + 7) >> 3;
+
+ const int x = x1 - _res->_screensBasePos[screenNum].u;
+ const int y = y1 - _res->_screensBasePos[screenNum].v;
+
+ const int u = _res->_screensBasePos[screenNum].u + Video::W;
+ if (x2 < u) {
+ ++w;
+ }
+
+ const uint8_t *src = _screenTempMaskBuffer + screenGridOffset(x, y);
+ uint8_t *dst = _screenMaskBuffer + screenMaskOffset(x1, y1);
+ for (int i = 0; i < h; ++i) {
+ memcpy(dst, src, w);
+ src += 32;
+ dst += 512;
+ }
+ }
+}
+
+void Game::setScreenMaskRect(int x1, int y1, int x2, int y2, int screenNum) {
+ const int u = _res->_screensBasePos[screenNum].u;
+ const int v = _res->_screensBasePos[screenNum].v;
+ const int topScreenNum = _res->_screensGrid[screenNum][kPosTopScreen];
+ if (x1 < u || y1 < v || y2 >= v + Video::H) {
+ if (topScreenNum != kNoScreen) {
+ const int u2 = _res->_screensBasePos[topScreenNum].u;
+ const int v2 = _res->_screensBasePos[topScreenNum].v;
+ if (x1 >= u2 && y1 >= v2 && y2 < v + Video::H) {
+ setScreenMaskRectHelper(x1, y1, x2, v2 + Video::H, topScreenNum);
+ }
+ }
+ }
+ setScreenMaskRectHelper(x1, v, x2, y2, screenNum);
+}
+
+void Game::updateScreenMaskBuffer(int x, int y, int type) {
+ uint8_t *p = _screenMaskBuffer + screenMaskOffset(x, y);
+ switch (type) {
+ case 0:
+ p[0] &= ~8;
+ break;
+ case 1:
+ p[0] |= 8;
+ break;
+ case 2:
+ p[0] |= 8;
+ p[-1] = 0;
+ p[-2] = 0;
+ p[-3] = 0;
+ break;
+ case 3:
+ p[0] |= 8;
+ p[1] = 0;
+ p[2] = 0;
+ p[3] = 0;
+ break;
+ }
+}
+
+void Game::setupLvlObjectBitmap(LvlObject *ptr) {
+ LvlObjectData *dat = ptr->levelData0x2988;
+ if (!dat) {
+ return;
+ }
+ LvlAnimHeader *ah = (LvlAnimHeader *)(dat->animsInfoData + kLvlAnimHdrOffset) + ptr->anim;
+ LvlAnimSeqHeader *ash = (LvlAnimSeqHeader *)(dat->animsInfoData + ah->seqOffset) + ptr->frame;
+
+ ptr->currentSound = ash->sound;
+ ptr->flags0 = merge_bits(ptr->flags0, ash->flags0, 0x3FF);
+ ptr->flags1 = merge_bits(ptr->flags1, ash->flags1, 6);
+ ptr->flags1 = merge_bits(ptr->flags1, ash->flags1, 8);
+ ptr->currentSprite = ash->firstFrame;
+
+ ptr->bitmapBits = _res->getLvlSpriteFramePtr(dat, ash->firstFrame, &ptr->width, &ptr->height);
+
+ const int w = ptr->width - 1;
+ const int h = ptr->height - 1;
+
+ if (ptr->type == 8 && (ptr->spriteNum == 2 || ptr->spriteNum == 0)) {
+ AndyLvlObjectData *dataPtr = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ dataPtr->boundingBox.x1 = ptr->xPos;
+ dataPtr->boundingBox.y1 = ptr->yPos;
+ dataPtr->boundingBox.x2 = ptr->xPos + w;
+ dataPtr->boundingBox.y2 = ptr->yPos + h;
+ }
+
+ const LvlSprHotspotData *hotspot = ((LvlSprHotspotData *)dat->hotspotsData) + ash->firstFrame;
+ const int type = (ptr->flags1 >> 4) & 3;
+ for (int i = 0; i < 8; ++i) {
+ switch (type) {
+ case 0:
+ ptr->posTable[i].x = hotspot->pts[i].x;
+ ptr->posTable[i].y = hotspot->pts[i].y;
+ break;
+ case 1:
+ ptr->posTable[i].x = w - hotspot->pts[i].x;
+ ptr->posTable[i].y = hotspot->pts[i].y;
+ break;
+ case 2:
+ ptr->posTable[i].x = hotspot->pts[i].x;
+ ptr->posTable[i].y = h - hotspot->pts[i].y;
+ break;
+ case 3:
+ ptr->posTable[i].x = w - hotspot->pts[i].x;
+ ptr->posTable[i].y = h - hotspot->pts[i].y;
+ break;
+ }
+ }
+}
+
+void Game::randomizeInterpolatePoints(int32_t *pts, int count) {
+ int32_t rnd = _rnd.update();
+ for (int i = 0; i < count; ++i) {
+ const int index = _pointDstIndexTable[i];
+ const int c1 = pts[_pointSrcIndex1Table[index]];
+ const int c2 = pts[_pointSrcIndex2Table[index]];
+ pts[index] = (c1 + c2 + (rnd >> _pointRandomizeShiftTable[i])) / 2;
+ rnd *= 2;
+ }
+}
+
+int Game::fixPlasmaCannonPointsScreenMask(int num) {
+ uint8_t _dl = ((~_plasmaCannonDirection) & 1) | 6;
+ int var1 = _plasmaCannonFirstIndex;
+ int vf = _res->_screensBasePos[num].u;
+ int ve = _res->_screensBasePos[num].v;
+ uint8_t _al = 0;
+ if (_andyObject->anim == 84) {
+ int yPos, xPos;
+ yPos = _plasmaCannonYPointsTable1[var1];
+ if (yPos < 0) {
+ yPos = 0;
+ }
+ yPos += ve;
+ while ((xPos = _plasmaCannonXPointsTable1[var1]) >= 0) {
+ xPos += vf;
+ _al = _screenMaskBuffer[screenMaskOffset(xPos, yPos)];
+ if ((_al & _dl) != 0) {
+ _plasmaCannonLastIndex1 = var1;
+ break;
+ }
+ var1 += 4;
+ if (var1 >= _plasmaCannonLastIndex2) {
+ break;
+ }
+ }
+ } else {
+ int xPos, yPos;
+ while ((xPos = _plasmaCannonXPointsTable1[var1]) >= 0 && (yPos = _plasmaCannonYPointsTable1[var1]) >= 0) {
+ yPos += ve;
+ xPos += vf;
+ _al = _screenMaskBuffer[screenMaskOffset(xPos, yPos)];
+ if ((_al & _dl) != 0) {
+ _plasmaCannonLastIndex1 = var1;
+ break;
+ }
+ var1 += 4;
+ if (var1 >= _plasmaCannonLastIndex2) {
+ break;
+ }
+ }
+ }
+ return _al;
+}
+
+void Game::setupPlasmaCannonPointsHelper() {
+ if (_plasmaCannonPointsSetupCounter == 0) {
+ int xR = _rnd.update();
+ for (int i = 0; i < 64; ++i) {
+ const int index = _pointDstIndexTable[i];
+ const int x1 = _plasmaCannonPosX[_pointSrcIndex1Table[index]];
+ const int x2 = _plasmaCannonPosX[_pointSrcIndex2Table[index]];
+ _plasmaCannonPosX[index] = (x1 + x2 + (xR >> _pointRandomizeShiftTable[i])) / 2;
+ xR *= 2;
+ }
+ int yR = _rnd.update();
+ for (int i = 0; i < 64; ++i) {
+ const int index = _pointDstIndexTable[i];
+ const int y1 = _plasmaCannonPosY[_pointSrcIndex1Table[index]];
+ const int y2 = _plasmaCannonPosY[_pointSrcIndex2Table[index]];
+ _plasmaCannonPosY[index] = (y1 + y2 + (yR >> _pointRandomizeShiftTable[i])) / 2;
+ yR *= 2;
+ }
+ if (_andyObject->anim == 84) {
+ for (int i = 0; i <= 496; i += 8) {
+ const int index = i / 4;
+ _plasmaCannonXPointsTable2[2 + index] = (_plasmaCannonPosX[4 + index] - _plasmaCannonXPointsTable1[4 + index]) / 2;
+ _plasmaCannonYPointsTable2[2 + index] = (_plasmaCannonPosY[4 + index] - _plasmaCannonYPointsTable1[4 + index]) / 8;
+ }
+ } else {
+ for (int i = 0; i <= 496; i += 8) {
+ const int index = i / 4;
+ _plasmaCannonXPointsTable2[2 + index] = (_plasmaCannonPosX[4 + index] - _plasmaCannonXPointsTable1[4 + index]) / 2;
+ _plasmaCannonYPointsTable2[2 + index] = (_plasmaCannonPosY[4 + index] - _plasmaCannonYPointsTable1[4 + index]) / 2;
+ }
+ }
+ for (int i = 0; i <= 504; i += 8) {
+ const int index = i / 4;
+ _plasmaCannonXPointsTable1[2 + index] = _plasmaCannonPosX[2 + index];
+ _plasmaCannonYPointsTable1[2 + index] = _plasmaCannonPosY[2 + index];
+ }
+ } else {
+ for (int i = 0; i <= 496; i += 8) {
+ const int index = i / 4;
+ _plasmaCannonXPointsTable1[4 + index] += _plasmaCannonXPointsTable2[2 + index];
+ _plasmaCannonYPointsTable1[4 + index] += _plasmaCannonYPointsTable2[2 + index];
+ }
+ }
+ ++_plasmaCannonPointsSetupCounter;
+ if (_plasmaCannonPointsSetupCounter >= 2) {
+ _plasmaCannonPointsSetupCounter = 0;
+ }
+ _plasmaCannonLastIndex2 = 128;
+ if (_plasmaCannonDirection == 0) {
+ _plasmaCannonLastIndex1 = _plasmaCannonPointsMask = 0;
+ _plasmaCannonFirstIndex = 128;
+ } else {
+ _plasmaCannonFirstIndex = 0;
+ _plasmaCannonPointsMask = fixPlasmaCannonPointsScreenMask(_res->_currentScreenResourceNum);
+ }
+}
+
+void Game::destroyLvlObjectPlasmaExplosion(LvlObject *o) {
+ AndyLvlObjectData *l = (AndyLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeAndy);
+ if (l->shootLvlObject) {
+ l->shootLvlObject = 0;
+
+ assert(_plasmaExplosionObject);
+
+ LvlObject *ptr = _plasmaExplosionObject->nextPtr;
+ removeLvlObjectFromList(&_plasmaExplosionObject, ptr);
+ destroyLvlObject(ptr);
+
+ ptr = _plasmaExplosionObject;
+ removeLvlObjectFromList(&_plasmaExplosionObject, ptr);
+ destroyLvlObject(ptr);
+ }
+}
+
+void Game::shuffleArray(uint8_t *p, int count) {
+ for (int i = 0; i < count * 2; ++i) {
+ const int index1 = _rnd.update() % count;
+ const int index2 = _rnd.update() % count;
+ SWAP(p[index1], p[index2]);
+ }
+}
+
+void Game::destroyLvlObject(LvlObject *o) {
+ assert(o);
+ if (o->type == 8) {
+ _res->decLvlSpriteDataRefCounter(o);
+ o->nextPtr = _declaredLvlObjectsNextPtr;
+ --_declaredLvlObjectsListCount;
+ _declaredLvlObjectsNextPtr = o;
+ switch (o->spriteNum) {
+ case 0:
+ case 2:
+ o->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (o->dataPtr) {
+ clearShootLvlObjectData(o);
+ }
+ break;
+ }
+ }
+ if (o->sssObject) {
+ removeSound(o);
+ }
+ o->sssObject = 0;
+ o->bitmapBits = 0;
+}
+
+void Game::setupPlasmaCannonPoints(LvlObject *ptr) {
+ _plasmaCannonDirection = 0;
+ if ((ptr->flags0 & 0x1F) == 4) {
+ if ((ptr->actionKeyMask & 4) == 0) { // not using plasma cannon
+ destroyLvlObjectPlasmaExplosion(ptr);
+ } else {
+ _plasmaCannonPosX[0] = _plasmaCannonPosX[128] = ptr->xPos + ptr->posTable[6].x;
+ _plasmaCannonPosY[0] = _plasmaCannonPosY[128] = ptr->yPos + ptr->posTable[6].y;
+ const int num = ((ptr->flags0 >> 5) & 7) - 3;
+ switch (num) {
+ case 0:
+ _plasmaCannonPosY[128] -= 176; // 192 - 16
+ _plasmaCannonDirection = 3;
+ break;
+ case 1:
+ _plasmaCannonPosY[128] += 176;
+ _plasmaCannonDirection = 6;
+ break;
+ case 3:
+ _plasmaCannonPosY[128] -= 176;
+ _plasmaCannonDirection = 1;
+ break;
+ case 4:
+ _plasmaCannonPosY[128] += 176;
+ _plasmaCannonDirection = 4;
+ break;
+ default:
+ _plasmaCannonDirection = 2;
+ break;
+ }
+ if (ptr->flags1 & 0x10) {
+ if (_plasmaCannonDirection != 1) {
+ _plasmaCannonDirection = (_plasmaCannonDirection & ~2) | 8;
+ _plasmaCannonPosX[128] -= 264; // 256 + 8
+ }
+ } else {
+ if (_plasmaCannonDirection != 1) {
+ _plasmaCannonPosX[128] += 264;
+ }
+ }
+ if (_plasmaCannonPrevDirection != _plasmaCannonDirection) {
+ _plasmaCannonXPointsTable1[0] = _plasmaCannonPosX[0];
+ _plasmaCannonXPointsTable1[128] = _plasmaCannonPosX[128];
+ randomizeInterpolatePoints(_plasmaCannonXPointsTable1, 64);
+ _plasmaCannonYPointsTable1[0] = _plasmaCannonPosY[0];
+ _plasmaCannonYPointsTable1[128] = _plasmaCannonPosY[128];
+ randomizeInterpolatePoints(_plasmaCannonYPointsTable1, 64);
+ _plasmaCannonPrevDirection = _plasmaCannonDirection;
+ }
+ }
+ }
+ if (_plasmaCannonPrevDirection != 0) {
+ setupPlasmaCannonPointsHelper();
+ if (_plasmaCannonFirstIndex >= _plasmaCannonLastIndex2) {
+ _plasmaCannonPrevDirection = 0;
+ _plasmaCannonLastIndex2 = 16;
+ }
+ }
+}
+
+int Game::testPlasmaCannonPointsDirection(int x1, int y1, int x2, int y2) {
+ int index1 = _plasmaCannonFirstIndex;
+ int vg = _plasmaCannonPosX[index1];
+ int ve = _plasmaCannonPosY[index1];
+ int index2 = _plasmaCannonLastIndex1;
+ if (index2 == 0) {
+ index2 = _plasmaCannonLastIndex2;
+ }
+ int va = _plasmaCannonXPointsTable1[index2];
+ int vf = _plasmaCannonYPointsTable1[index2];
+ if (vg > va) {
+ if (ve > vf) {
+ if (x1 > vg || x2 < va || y1 > ve || y2 < vf) {
+ return 0;
+ }
+ index1 += 4;
+ do {
+ va = _plasmaCannonXPointsTable1[index1];
+ vf = _plasmaCannonYPointsTable1[index1];
+ if (x1 <= vg && x2 >= va && y1 <= ve && y2 >= vf) {
+ goto endDir;
+ }
+ vg = va;
+ ve = vf;
+ index1 += 4;
+ } while (index1 < index2);
+ return 0;
+ } else {
+ if (x1 > vg || x2 < va || y1 > vf || y2 < ve) {
+ return 0;
+ }
+ index1 += 4;
+ do {
+ va = _plasmaCannonXPointsTable1[index1];
+ vf = _plasmaCannonYPointsTable1[index1];
+ if (x1 <= vg && x2 >= va && y1 <= vf && y2 >= ve) {
+ goto endDir;
+ }
+ vg = va;
+ ve = vf;
+ index1 += 4;
+ } while (index1 < index2);
+ return 0;
+ }
+ } else {
+ if (ve > vf) {
+ if (x1 > va || x2 < vg || y1 > ve || y2 < vf) {
+ return 0;
+ }
+ index1 += 4;
+ do {
+ va = _plasmaCannonXPointsTable1[index1];
+ vf = _plasmaCannonYPointsTable1[index1];
+ if (x1 <= va && x2 >= vg && y1 <= ve && y2 >= vf) {
+ goto endDir;
+ }
+ vg = va;
+ ve = vf;
+ index1 += 4;
+ } while (index1 < index2);
+ return 0;
+ } else {
+ if (x1 > va || x2 < vg || y1 > vf || y2 < ve) {
+ return 0;
+ }
+ index1 += 4;
+ do {
+ va = _plasmaCannonXPointsTable1[index1];
+ vf = _plasmaCannonYPointsTable1[index1];
+ if (x1 <= va && x2 >= vg && y1 <= vf && y2 >= ve) {
+ goto endDir;
+ }
+ vg = va;
+ ve = vf;
+ index1 += 4;
+ } while (index1 < index2);
+ return 0;
+ }
+ }
+endDir:
+ _plasmaCannonLastIndex1 = index1;
+ _plasmaCannonPointsMask = 0;
+ return 1;
+}
+
+void Game::preloadLevelScreenData(uint8_t num, uint8_t prev) {
+ assert(num != kNoScreen);
+ if (!_res->isLvlBackgroundDataLoaded(num)) {
+ _res->loadLvlScreenBackgroundData(num);
+ }
+ if (num < _res->_sssPreloadInfosData.count) {
+ const SssPreloadInfo *preloadInfo = &_res->_sssPreloadInfosData[num];
+ for (unsigned int i = 0; i < preloadInfo->count; ++i) {
+ const SssPreloadInfoData *preloadData = &preloadInfo->data[i];
+ if (preloadData->screenNum == prev) {
+ _res->preloadSssPcmList(preloadData);
+ break;
+ }
+ }
+ }
+}
+
+void Game::setLvlObjectPosRelativeToObject(LvlObject *ptr1, int num1, LvlObject *ptr2, int num2) {
+ ptr1->xPos = ptr2->posTable[num2].x - ptr1->posTable[num1].x + ptr2->xPos;
+ ptr1->yPos = ptr2->posTable[num2].y - ptr1->posTable[num1].y + ptr2->yPos;
+}
+
+void Game::setLvlObjectPosRelativeToPoint(LvlObject *ptr, int num, int x, int y) {
+ assert(num >= 0 && num < 8);
+ ptr->xPos = x - ptr->posTable[num].x;
+ ptr->yPos = y - ptr->posTable[num].y;
+}
+
+void Game::clearLvlObjectsList0() {
+ LvlObject *ptr = _lvlObjectsList0;
+ while (ptr) {
+ LvlObject *next = ptr->nextPtr;
+ if (ptr->type == 8) {
+ _res->decLvlSpriteDataRefCounter(ptr);
+ ptr->nextPtr = _declaredLvlObjectsNextPtr;
+ --_declaredLvlObjectsListCount;
+ _declaredLvlObjectsNextPtr = ptr;
+ switch (ptr->spriteNum) {
+ case 0:
+ case 2:
+ ptr->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (ptr->dataPtr) {
+ clearShootLvlObjectData(ptr);
+ }
+ break;
+ }
+ if (ptr->sssObject) {
+ removeSound(ptr);
+ }
+ ptr->sssObject = 0;
+ ptr->bitmapBits = 0;
+ }
+ ptr = next;
+ }
+ _lvlObjectsList0 = 0;
+}
+
+void Game::clearLvlObjectsList1() {
+ if (!_lvlObjectsList1) {
+ return;
+ }
+ for (int i = 0; i < kMaxMonsterObjects1; ++i) {
+ mstMonster1ResetData(&_monsterObjects1Table[i]);
+ }
+ for (int i = 0; i < kMaxMonsterObjects2; ++i) {
+ mstMonster2ResetData(&_monsterObjects2Table[i]);
+ }
+ LvlObject *ptr = _lvlObjectsList1;
+ while (ptr) {
+ LvlObject *next = ptr->nextPtr;
+ if (ptr->type == 8) {
+ _res->decLvlSpriteDataRefCounter(ptr);
+ ptr->nextPtr = _declaredLvlObjectsNextPtr;
+ --_declaredLvlObjectsListCount;
+ _declaredLvlObjectsNextPtr = ptr;
+ switch (ptr->spriteNum) {
+ case 0:
+ case 2:
+ ptr->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (ptr->dataPtr) {
+ clearShootLvlObjectData(ptr);
+ }
+ break;
+ }
+ if (ptr->sssObject) {
+ removeSound(ptr);
+ }
+ ptr->sssObject = 0;
+ ptr->bitmapBits = 0;
+ }
+ ptr = next;
+ }
+ _lvlObjectsList1 = 0;
+}
+
+void Game::clearLvlObjectsList2() {
+ LvlObject *ptr = _lvlObjectsList2;
+ while (ptr) {
+ LvlObject *next = ptr->nextPtr;
+ if (ptr->type == 8) {
+ _res->decLvlSpriteDataRefCounter(ptr);
+ ptr->nextPtr = _declaredLvlObjectsNextPtr;
+ --_declaredLvlObjectsListCount;
+ _declaredLvlObjectsNextPtr = ptr;
+ switch (ptr->spriteNum) {
+ case 0:
+ case 2:
+ ptr->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (ptr->dataPtr) {
+ clearShootLvlObjectData(ptr);
+ }
+ break;
+ }
+ if (ptr->sssObject) {
+ removeSound(ptr);
+ }
+ ptr->sssObject = 0;
+ ptr->bitmapBits = 0;
+ }
+ ptr = next;
+ }
+ _lvlObjectsList2 = 0;
+}
+
+void Game::clearLvlObjectsList3() {
+ LvlObject *ptr = _lvlObjectsList3;
+ while (ptr != 0) {
+ LvlObject *next = ptr->nextPtr;
+ if (ptr->type == 8) {
+ _res->decLvlSpriteDataRefCounter(ptr);
+ ptr->nextPtr = _declaredLvlObjectsNextPtr;
+ --_declaredLvlObjectsListCount;
+ _declaredLvlObjectsNextPtr = ptr;
+ switch (ptr->spriteNum) {
+ case 0:
+ case 2:
+ ptr->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (ptr->dataPtr) {
+ clearShootLvlObjectData(ptr);
+ }
+ break;
+ }
+ if (ptr->sssObject) {
+ removeSound(ptr);
+ }
+ ptr->sssObject = 0;
+ ptr->bitmapBits = 0;
+ }
+ ptr = next;
+ }
+ _lvlObjectsList3 = 0;
+}
+
+LvlObject *Game::addLvlObjectToList0(int num) {
+ if (_res->_resLevelData0x2988PtrTable[num] != 0 && _declaredLvlObjectsListCount < kMaxLvlObjects) {
+ assert(_declaredLvlObjectsNextPtr);
+ LvlObject *ptr = _declaredLvlObjectsNextPtr;
+ _declaredLvlObjectsNextPtr = _declaredLvlObjectsNextPtr->nextPtr;
+ ++_declaredLvlObjectsListCount;
+ ptr->spriteNum = num;
+ ptr->type = 8;
+ _res->incLvlSpriteDataRefCounter(ptr);
+ lvlObjectTypeCallback(ptr);
+ ptr->currentSprite = 0;
+ ptr->sssObject = 0;
+ ptr->nextPtr = 0;
+ ptr->bitmapBits = 0;
+ ptr->nextPtr = _lvlObjectsList0;
+ _lvlObjectsList0 = ptr;
+ return ptr;
+ }
+ return 0;
+}
+
+LvlObject *Game::addLvlObjectToList1(int type, int num) {
+ if ((type != 8 || _res->_resLevelData0x2988PtrTable[num] != 0) && _declaredLvlObjectsListCount < kMaxLvlObjects) {
+ assert(_declaredLvlObjectsNextPtr);
+ LvlObject *ptr = _declaredLvlObjectsNextPtr;
+ _declaredLvlObjectsNextPtr = _declaredLvlObjectsNextPtr->nextPtr;
+ ++_declaredLvlObjectsListCount;
+ ptr->spriteNum = num;
+ ptr->type = type;
+ if (type == 8) {
+ _res->incLvlSpriteDataRefCounter(ptr);
+ lvlObjectTypeCallback(ptr);
+ }
+ ptr->currentSprite = 0;
+ ptr->sssObject = 0;
+ ptr->nextPtr = 0;
+ ptr->bitmapBits = 0;
+ ptr->nextPtr = _lvlObjectsList1;
+ _lvlObjectsList1 = ptr;
+ return ptr;
+ }
+ return 0;
+}
+
+LvlObject *Game::addLvlObjectToList2(int num) {
+ if (_res->_resLevelData0x2988PtrTable[num] != 0 && _declaredLvlObjectsListCount < kMaxLvlObjects) {
+ assert(_declaredLvlObjectsNextPtr);
+ LvlObject *ptr = _declaredLvlObjectsNextPtr;
+ _declaredLvlObjectsNextPtr = _declaredLvlObjectsNextPtr->nextPtr;
+ ++_declaredLvlObjectsListCount;
+ ptr->spriteNum = num;
+ ptr->type = 8;
+ _res->incLvlSpriteDataRefCounter(ptr);
+ lvlObjectTypeCallback(ptr);
+ ptr->currentSprite = 0;
+ ptr->sssObject = 0;
+ ptr->nextPtr = 0;
+ ptr->bitmapBits = 0;
+ ptr->nextPtr = _lvlObjectsList2;
+ _lvlObjectsList2 = ptr;
+ return ptr;
+ }
+ return 0;
+}
+
+LvlObject *Game::addLvlObjectToList3(int num) {
+ if (_res->_resLevelData0x2988PtrTable[num] != 0 && _declaredLvlObjectsListCount < kMaxLvlObjects) {
+ assert(_declaredLvlObjectsNextPtr);
+ LvlObject *ptr = _declaredLvlObjectsNextPtr;
+ _declaredLvlObjectsNextPtr = _declaredLvlObjectsNextPtr->nextPtr;
+ ++_declaredLvlObjectsListCount;
+ ptr->spriteNum = num;
+ ptr->type = 8;
+ _res->incLvlSpriteDataRefCounter(ptr);
+ lvlObjectTypeCallback(ptr);
+ ptr->currentSprite = 0;
+ ptr->sssObject = 0;
+ ptr->nextPtr = 0;
+ ptr->bitmapBits = 0;
+ ptr->nextPtr = _lvlObjectsList3;
+ _lvlObjectsList3 = ptr;
+ ptr->callbackFuncPtr = &Game::lvlObjectList3Callback;
+ return ptr;
+ }
+ return 0;
+}
+
+void Game::removeLvlObject(LvlObject *ptr) {
+ AndyLvlObjectData *dataPtr = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ LvlObject *o = dataPtr->shootLvlObject;
+ if (o) {
+ dataPtr->shootLvlObject = 0;
+ removeLvlObjectFromList(&_lvlObjectsList0, o);
+ destroyLvlObject(o);
+ }
+}
+
+void Game::removeLvlObject2(LvlObject *o) {
+ if (o->type != 2) {
+ LvlObject *ptr = _lvlObjectsList1;
+ if (ptr) {
+ if (ptr == o) {
+ _lvlObjectsList1 = o->nextPtr;
+ } else {
+ LvlObject *prev = 0;
+ do {
+ prev = ptr;
+ ptr = ptr->nextPtr;
+ } while (ptr && ptr != o);
+ assert(ptr);
+ prev->nextPtr = ptr->nextPtr;
+ }
+ }
+ }
+ o->dataPtr = 0;
+ if (o->type == 8) {
+ _res->decLvlSpriteDataRefCounter(o);
+ o->nextPtr = _declaredLvlObjectsNextPtr;
+ _declaredLvlObjectsNextPtr = o;
+ --_declaredLvlObjectsListCount;
+ } else {
+ switch (o->spriteNum) {
+ case 0:
+ case 2:
+ o->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (o->dataPtr) {
+ clearShootLvlObjectData(o);
+ }
+ break;
+ }
+ }
+ if (o->sssObject) {
+ removeSound(o);
+ o->sssObject = 0;
+ }
+ o->bitmapBits = 0;
+}
+
+void Game::setAndySprite(int num) {
+ switch (num) {
+ case 0: // Andy with plasma cannon and helmet
+ removeLvlObject(_andyObject);
+ setLvlObjectSprite(_andyObject, 8, 0);
+ _andyObject->anim = 48;
+ break;
+ case 2: // Andy
+ destroyLvlObjectPlasmaExplosion(_andyObject);
+ _plasmaCannonDirection = 0;
+ _plasmaCannonLastIndex1 = 0;
+ _plasmaCannonExplodeFlag = false;
+ _plasmaCannonPointsMask = 0;
+ _plasmaCannonObject = 0;
+ setLvlObjectSprite(_andyObject, 8, 2);
+ _andyObject->anim = 232;
+ break;
+ }
+ _andyObject->frame = 0;
+}
+
+void Game::setupAndyLvlObject() {
+ LvlObject *ptr = _andyObject;
+ _fallingAndyFlag = false;
+ _andyActionKeysFlags = 0;
+ _hideAndyObjectFlag = false;
+ const CheckpointData *dat = _level->getCheckpointData(_level->_checkpoint);
+ _plasmaCannonFlags = 0;
+ _actionDirectionKeyMaskIndex = 0;
+ _mstAndyCurrentScreenNum = ptr->screenNum;
+ if (dat->spriteNum != ptr->spriteNum) {
+ setAndySprite(dat->spriteNum);
+ }
+ ptr->childPtr = 0;
+ ptr->xPos = dat->xPos;
+ ptr->yPos = dat->yPos;
+ ptr->flags2 = dat->flags2;
+ ptr->anim = dat->anim;
+ ptr->flags1 = ((ptr->flags2 >> 10) & 0x30) | (ptr->flags1 & ~0x30);
+ ptr->screenNum = dat->screenNum;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ _currentScreen = dat->screenNum;
+ _currentLeftScreen = _res->_screensGrid[_currentScreen][kPosLeftScreen];
+ _currentRightScreen = _res->_screensGrid[_currentScreen][kPosRightScreen];
+ ptr->frame = 0;
+ setupLvlObjectBitmap(ptr);
+ AndyLvlObjectData *dataPtr = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ dataPtr->unk6 = 0;
+ if (ptr->spriteNum == 2) {
+ removeLvlObject(ptr);
+ } else {
+ destroyLvlObjectPlasmaExplosion(ptr);
+ }
+}
+
+void Game::setupScreenLvlObjects(int num) {
+ _res->_screensState[num].s2 = 1;
+ for (LvlObject *ptr = _screenLvlObjectsList[num]; ptr; ptr = ptr->nextPtr) {
+ switch (ptr->type) {
+ case 0: {
+ AnimBackgroundData *p = (AnimBackgroundData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAnimBackgroundData);
+ uint8_t *data = _res->_resLvlScreenBackgroundDataTable[num].backgroundAnimationTable[ptr->dataNum];
+ if (!data) {
+ warning("No backgroundAnimationData num %d screen %d", ptr->dataNum, num);
+ break;
+ }
+ if (_res->_isPsx) {
+ p->framesCount = READ_LE_UINT32(data); data += 4;
+ ptr->currentSound = READ_LE_UINT32(data); data += 4;
+ p->nextSpriteData = READ_LE_UINT16(data + 6) + data + 6;
+ } else {
+ p->framesCount = READ_LE_UINT16(data); data += 2;
+ ptr->currentSound = READ_LE_UINT16(data); data += 2;
+ p->nextSpriteData = READ_LE_UINT16(data + 4) + data + 4;
+ }
+ p->currentSpriteData = p->otherSpriteData = data;
+ p->currentFrame = 0;
+ }
+ break;
+ case 1: {
+ uint8_t *data = _res->_resLvlScreenBackgroundDataTable[num].backgroundSoundTable[ptr->dataNum];
+ if (!data) {
+ warning("No backgroundSoundData num %d screen %d", ptr->dataNum, num);
+ break;
+ }
+ ptr->currentSound = READ_LE_UINT16(data); data += 2;
+ ptr->dataPtr = data;
+ }
+ break;
+ case 2:
+ ptr->levelData0x2988 = _res->_resLvlScreenBackgroundDataTable[num].backgroundLvlObjectDataTable[ptr->dataNum];
+ if (!ptr->levelData0x2988) {
+ warning("No backgroundLvlObjectData num %d screen %d", ptr->dataNum, num);
+ break;
+ }
+ if (_currentLevel == kLvl_rock) {
+ switch (ptr->objectUpdateType) {
+ case 0:
+ case 5:
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case0;
+ break;
+ case 1: // shadow screen2
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case1;
+ break;
+ case 2: // shadow screen3
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case2;
+ break;
+ case 3:
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case3;
+ break;
+ case 4:
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case4;
+ break;
+ default:
+ warning("setupScreenLvlObjects unimplemented for level %d, state %d", _currentLevel, ptr->objectUpdateType);
+ break;
+ }
+ } else {
+ // other levels use two callbacks
+ switch (ptr->objectUpdateType) {
+ case 0:
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case0;
+ break;
+ case 1:
+ ptr->callbackFuncPtr = &Game::objectUpdate_rock_case3;
+ break;
+ default:
+ warning("setupScreenLvlObjects unimplemented for level %d, state %d", _currentLevel, ptr->objectUpdateType);
+ break;
+ }
+ }
+ setupLvlObjectBitmap(ptr);
+ break;
+ }
+ }
+}
+
+void Game::resetDisplay() {
+// _video_blitSrcPtr = _video_blitSrcPtr2;
+ _video->_displayShadowLayer = false;
+ _shakeScreenDuration = 0;
+ _levelRestartCounter = 0;
+ _fadePalette = false;
+ memset(_video->_fadePaletteBuffer, 0, sizeof(_video->_fadePaletteBuffer));
+ _snd_masterVolume = kDefaultSoundVolume; // _plyConfigTable[_plyConfigNumber].soundVolume;
+}
+
+void Game::setupScreen(uint8_t num) {
+ uint8_t i, prev;
+
+ if (num == kNoScreen) {
+ return;
+ }
+ prev = _res->_currentScreenResourceNum;
+ _res->_currentScreenResourceNum = num;
+ setupScreenLvlObjects(num);
+ callLevel_preScreenUpdate(num);
+ if (_res->_screensState[num].s0 >= _res->_screensState[num].s1) {
+ _res->_screensState[num].s0 = _res->_screensState[num].s1 - 1;
+ }
+ callLevel_postScreenUpdate(num);
+ i = _res->_screensGrid[num][kPosTopScreen];
+ if (i != kNoScreen && prev != i) {
+ callLevel_preScreenUpdate(i);
+ setupScreenMask(i);
+ callLevel_postScreenUpdate(i);
+ }
+ i = _res->_screensGrid[num][kPosRightScreen];
+ if (i != kNoScreen && _res->_resLevelData0x2B88SizeTable[i] != 0 && prev != i) {
+ setupScreenLvlObjects(i);
+ callLevel_preScreenUpdate(i);
+ setupScreenMask(i);
+ callLevel_postScreenUpdate(i);
+ }
+ i = _res->_screensGrid[num][kPosBottomScreen];
+ if (i != kNoScreen && prev != i) {
+ callLevel_preScreenUpdate(i);
+ setupScreenMask(i);
+ callLevel_postScreenUpdate(i);
+ }
+ i = _res->_screensGrid[num][kPosLeftScreen];
+ if (i != kNoScreen && _res->_resLevelData0x2B88SizeTable[i] != 0 && prev != i) {
+ setupScreenLvlObjects(i);
+ callLevel_preScreenUpdate(i);
+ setupScreenMask(i);
+ callLevel_postScreenUpdate(i);
+ }
+ callLevel_postScreenUpdate(num);
+ setupBackgroundBitmap();
+ setupScreenMask(num);
+ resetDisplay();
+// _time_counter1 = GetTickCount();
+}
+
+void Game::resetScreen() {
+ for (int i = 0; i < _res->_lvlHdr.screensCount; ++i) {
+ _res->_screensState[i].s0 = 0;
+ _level->_screenCounterTable[i] = 0;
+ }
+ const uint8_t *dat2 = _level->getScreenRestartData();
+ const int screenNum = _level->getCheckpointData(_level->_checkpoint)->screenNum;
+ for (int i = 0; i < screenNum; ++i) {
+ _res->_screensState[i].s0 = *dat2++;
+ _level->_screenCounterTable[i] = *dat2++;
+ }
+ resetScreenMask();
+ for (int i = screenNum; i < _res->_lvlHdr.screensCount; ++i) {
+ _level->setupScreenCheckpoint(i);
+ }
+ resetWormHoleSprites();
+}
+
+void Game::restartLevel() {
+ setupAndyLvlObject();
+ clearLvlObjectsList2();
+ clearLvlObjectsList3();
+ if (!_mstDisabled) {
+ resetMstCode();
+ startMstCode();
+ } else {
+ _mstFlags = 0;
+ }
+ if (_res->_sssHdr.infosDataCount != 0) {
+ resetSound();
+ }
+ const int screenNum = _level->getCheckpointData(_level->_checkpoint)->screenNum;
+ preloadLevelScreenData(screenNum, kNoScreen);
+ _andyObject->levelData0x2988 = _res->_resLevelData0x2988PtrTable[_andyObject->spriteNum];
+ memset(_video->_backgroundLayer, 0, Video::W * Video::H);
+ resetScreen();
+ if (_andyObject->screenNum != screenNum) {
+ preloadLevelScreenData(_andyObject->screenNum, kNoScreen);
+ }
+ setupScreen(_andyObject->screenNum);
+}
+
+void Game::playAndyFallingCutscene(int type) {
+ bool play = false;
+ if (type == 0) {
+ play = true;
+ } else if (_fallingAndyFlag) {
+ ++_fallingAndyCounter;
+ if (_fallingAndyCounter >= 2) {
+ play = true;
+ }
+ }
+ if (!_paf->_skipCutscenes && play) {
+ switch (_currentLevel) {
+ case kLvl_rock:
+ if (_andyObject->spriteNum == 0) {
+ _paf->play(kPafAnimation_CanyonAndyFallingCannon); // Andy falls with cannon and helmet
+ } else {
+ _paf->play(kPafAnimation_CanyonAndyFalling); // Andy falls without cannon
+ }
+ break;
+ case kLvl_fort:
+ if (_res->_currentScreenResourceNum == 0) {
+ _paf->play(kPafAnimation_CanyonAndyFalling);
+ }
+ break;
+ case kLvl_isld:
+ _paf->play(kPafAnimation_IslandAndyFalling);
+ break;
+ }
+ }
+ if (type != 0 && play) {
+ restartLevel();
+ }
+}
+
+int8_t Game::updateLvlObjectScreen(LvlObject *ptr) {
+ int8_t ret = 0;
+
+ if ((_plasmaCannonFlags & 1) == 0 && _plasmaCannonDirection == 0) {
+ int xPosPrev = ptr->xPos;
+ int xPos = ptr->xPos + ptr->posTable[3].x;
+ int yPosPrev = ptr->yPos;
+ int yPos = ptr->yPos + ptr->posTable[3].y;
+ uint8_t num = ptr->screenNum;
+ if (xPos < 0) {
+ ptr->screenNum = _res->_screensGrid[num][kPosLeftScreen];
+ ptr->xPos = xPosPrev + Video::W;
+ } else if (xPos > Video::W) {
+ ptr->screenNum = _res->_screensGrid[num][kPosRightScreen];
+ ptr->xPos = xPosPrev - Video::W;
+ }
+ if (ptr->screenNum != kNoScreen) {
+ if (yPos < 0) {
+ ptr->screenNum = _res->_screensGrid[ptr->screenNum][kPosTopScreen];
+ ptr->yPos = yPosPrev + Video::H;
+ } else if (yPos > Video::H) {
+ ptr->screenNum = _res->_screensGrid[ptr->screenNum][kPosBottomScreen];
+ ptr->yPos = yPosPrev - Video::H;
+ }
+ }
+ if (ptr->screenNum == kNoScreen) {
+ debug(kDebug_GAME, "Changing screen from -1 to %d, pos=%d,%d (%d,%d)", num, xPos, yPos, xPosPrev, yPosPrev);
+ ptr->screenNum = num;
+ ptr->xPos = xPosPrev;
+ ptr->yPos = yPosPrev;
+ ret = -1;
+ } else if (ptr->screenNum != num) {
+ debug(kDebug_GAME, "Changing screen from %d to %d, pos=%d,%d", num, ptr->screenNum, xPos, yPos);
+ ret = 1;
+ AndyLvlObjectData *data = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ data->boundingBox.x1 = ptr->xPos;
+ data->boundingBox.x2 = ptr->xPos + ptr->width - 1;
+ data->boundingBox.y1 = ptr->yPos;
+ data->boundingBox.y2 = ptr->yPos + ptr->height - 1;
+ }
+ }
+ _currentScreen = ptr->screenNum;
+ _currentLeftScreen = _res->_screensGrid[_currentScreen][kPosLeftScreen];
+ _currentRightScreen = _res->_screensGrid[_currentScreen][kPosRightScreen];
+ return ret;
+}
+
+void Game::setAndyAnimationForArea(BoundingBox *box, int dx) {
+ static uint8_t _prevAndyFlags0 = 0;
+ BoundingBox objBox;
+ objBox.x1 = _andyObject->xPos;
+ objBox.x2 = _andyObject->xPos + _andyObject->posTable[3].x;
+ objBox.y1 = _andyObject->yPos;
+ objBox.y2 = _andyObject->yPos + _andyObject->posTable[3].y;
+ const uint8_t _bl = _andyObject->flags0 & 0x1F;
+ if (clipBoundingBox(box, &objBox)) {
+ if ((_andyObject->actionKeyMask & 1) == 0) {
+ _andyObject->actionKeyMask |= 8;
+ }
+ if (objBox.x2 >= box->x1 + dx && objBox.x2 <= box->x2 - dx) {
+ uint8_t _al = 0;
+ if (_currentLevel != kLvl_rock) {
+ if (_bl == 1) {
+ if (((_andyObject->flags0 >> 5) & 7) == 3) {
+ _al = 0x80;
+ }
+ }
+ } else {
+ _andyObject->actionKeyMask &= ~1;
+ _andyObject->directionKeyMask &= ~4;
+ _andyObject->actionKeyMask |= 8;
+ }
+ if (_bl != 2) {
+ if (_prevAndyFlags0 == 2) {
+ _al = 0x80;
+ }
+ if (_al != 0 && _al > _actionDirectionKeyMaskIndex) {
+ _actionDirectionKeyMaskIndex = _al;
+ _actionDirectionKeyMaskCounter = 0;
+ }
+ }
+ }
+ }
+ _prevAndyFlags0 = _bl;
+}
+
+void Game::setAndyLvlObjectPlasmaCannonKeyMask() {
+ if (_actionDirectionKeyMaskCounter == 0) {
+ switch (_actionDirectionKeyMaskIndex >> 4) {
+ case 0:
+ _actionDirectionKeyMaskCounter = 6;
+ break;
+ case 1:
+ case 8:
+ case 10:
+ _actionDirectionKeyMaskCounter = 2;
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ _actionDirectionKeyMaskCounter = 1;
+ break;
+ case 6:
+ case 7:
+ case 9:
+ break;
+ }
+ }
+ if (_actionDirectionKeyMaskIndex != 0) {
+ if (_actionDirectionKeyMaskIndex == 0xA4 && !_fadePalette) { // game over
+ _levelRestartCounter = 10;
+ _plasmaCannonFlags |= 1;
+ } else {
+ if (_andyObject->spriteNum == 2 && _actionDirectionKeyMaskIndex >= 16) {
+ removeLvlObject(_andyObject);
+ }
+ _andyActionKeysFlags = 0;
+ }
+ _andyObject->actionKeyMask = _actionDirectionKeyMaskTable[_actionDirectionKeyMaskIndex * 2];
+ _andyObject->directionKeyMask = _actionDirectionKeyMaskTable[_actionDirectionKeyMaskIndex * 2 + 1];
+ }
+ --_actionDirectionKeyMaskCounter;
+ if (_actionDirectionKeyMaskCounter == 0) {
+ _actionDirectionKeyMaskIndex = 0;
+ }
+}
+
+int Game::setAndySpecialAnimation(uint8_t mask) {
+ if (mask > _actionDirectionKeyMaskIndex) {
+ _actionDirectionKeyMaskIndex = mask;
+ _actionDirectionKeyMaskCounter = 0;
+ return 1;
+ }
+ return 0;
+}
+
+int Game::clipBoundingBox(BoundingBox *coords, BoundingBox *box) {
+ if (coords->x1 > coords->x2) {
+ SWAP(coords->x1, coords->x2);
+ }
+ if (coords->y1 > coords->y2) {
+ SWAP(coords->y1, coords->y2);
+ }
+ if (box->x1 > box->x2) {
+ SWAP(box->x1, box->x2);
+ }
+ if (box->y1 > box->y2) {
+ SWAP(box->y1, box->y2);
+ }
+ if (coords->x1 > box->x2 || coords->x2 < box->x1 || coords->y1 > box->y2 || coords->y2 < box->y1) {
+ return 0;
+ }
+ _clipBoxOffsetX = (box->x2 - coords->x1) / 2 + coords->x1;
+ _clipBoxOffsetY = (box->y2 - coords->y1) / 2 + coords->y1;
+ return 1;
+}
+
+int Game::updateBoundingBoxClippingOffset(BoundingBox *vc, BoundingBox *ve, const uint8_t *coords, int direction) {
+ int ret = 0;
+ int count = *coords++;
+ if (count == 0) {
+ return clipBoundingBox(vc, ve);
+ }
+ switch (direction) {
+ case 1:
+ for (; count-- != 0; coords += 4) {
+ if (vc->x1 > ve->x2 - coords[0] || vc->x2 < ve->x2 - coords[2]) {
+ continue;
+ }
+ if (vc->y1 > ve->y1 + coords[3] || vc->y2 < ve->y1 + coords[1]) {
+ continue;
+ }
+ break;
+ }
+ break;
+ case 2:
+ for (; count-- != 0; coords += 4) {
+ if (vc->x1 > coords[2] + ve->x1 || vc->x2 < coords[0] + ve->x1) {
+ continue;
+ }
+ if (vc->y1 > ve->y2 - coords[1] || vc->y2 < ve->y2 - coords[3]) {
+ continue;
+ }
+ break;
+ }
+ break;
+ case 3:
+ for (; count-- != 0; coords += 4) {
+ if (vc->x1 > ve->x2 - coords[0] || vc->x2 < ve->x2 - coords[2]) {
+ continue;
+ }
+ if (vc->y1 > ve->y2 - coords[1] || vc->y2 < ve->y2 - coords[3]) {
+ continue;
+ }
+ break;
+ }
+ break;
+ default:
+ for (; count-- != 0; coords += 4) {
+ if (vc->x1 > coords[2] + ve->x1 || vc->x2 < coords[0] + ve->x1) {
+ continue;
+ }
+ if (vc->y1 > coords[3] + ve->y1 || vc->y2 < coords[1] + ve->y1) {
+ continue;
+ }
+ break;
+ }
+ break;
+ }
+ if (count != 0) {
+ _clipBoxOffsetX = (vc->x2 - vc->x1) / 2 + vc->x1;
+ _clipBoxOffsetY = (vc->y2 - vc->y1) / 2 + vc->y1;
+ ret = 1;
+ }
+ return ret;
+}
+
+int Game::clipLvlObjectsBoundingBoxHelper(LvlObject *o1, BoundingBox *box1, LvlObject *o2, BoundingBox *box2) {
+ int ret = 0;
+ const uint8_t *coords1 = _res->getLvlSpriteCoordPtr(o1->levelData0x2988, o1->currentSprite);
+ const uint8_t *coords2 = _res->getLvlSpriteCoordPtr(o2->levelData0x2988, o2->currentSprite);
+ if (clipBoundingBox(box1, box2)) {
+ int count = *coords1++;
+ if (count != 0) {
+ const int direction = (o1->flags1 >> 4) & 3;
+ switch (direction) {
+ case 1:
+ for (; count-- != 0 && !ret; coords1 += 4) {
+ BoundingBox tmp;
+ tmp.x1 = box1->x2 - coords1[2];
+ tmp.x2 = box1->x2 - coords1[0];
+ tmp.y1 = box1->y1 + coords1[1];
+ tmp.y2 = box1->y2 + coords1[3];
+ ret = updateBoundingBoxClippingOffset(&tmp, box2, coords2, (o2->flags1 >> 4) & 3);
+ }
+ break;
+ case 2:
+ for (; count-- != 0 && !ret; coords1 += 4) {
+ BoundingBox tmp;
+ tmp.x1 = box1->x1 + coords1[0];
+ tmp.x2 = box1->x1 + coords1[2];
+ tmp.y1 = box1->y2 - coords1[3];
+ tmp.y2 = box1->y2 - coords1[1];
+ ret = updateBoundingBoxClippingOffset(&tmp, box2, coords2, (o2->flags1 >> 4) & 3);
+ }
+ break;
+ case 3:
+ for (; count-- != 0 && !ret; coords1 += 4) {
+ BoundingBox tmp;
+ tmp.x1 = box1->x2 - coords1[2];
+ tmp.x2 = box1->x2 - coords1[0];
+ tmp.y1 = box1->y2 - coords1[3];
+ tmp.y2 = box1->y2 - coords1[1];
+ ret = updateBoundingBoxClippingOffset(&tmp, box2, coords2, (o2->flags1 >> 4) & 3);
+ }
+ break;
+ default:
+ for (; count-- != 0 && !ret; coords1 += 4) {
+ BoundingBox tmp;
+ tmp.x1 = box1->x1 + coords1[0];
+ tmp.x2 = box1->x1 + coords1[2];
+ tmp.y1 = box1->y1 + coords1[1];
+ tmp.y2 = box1->y1 + coords1[3];
+ ret = updateBoundingBoxClippingOffset(&tmp, box2, coords2, (o2->flags1 >> 4) & 3);
+ }
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+int Game::clipLvlObjectsBoundingBox(LvlObject *o, LvlObject *ptr, int type) {
+ BoundingBox obj1, obj2;
+
+ obj1.x1 = o->xPos + _res->_screensBasePos[o->screenNum].u;
+ obj1.y1 = obj1.y2 = o->yPos + _res->_screensBasePos[o->screenNum].v;
+
+ obj2.x1 = ptr->xPos + _res->_screensBasePos[ptr->screenNum].u;
+ obj2.y1 = obj2.y2 = ptr->yPos + _res->_screensBasePos[ptr->screenNum].v;
+
+ switch (type - 17) {
+ case 1:
+ obj1.x2 = obj1.x1 + o->posTable[1].x;
+ obj1.x1 += o->posTable[0].x;
+ obj1.y2 += o->posTable[1].y;
+ obj1.y1 += o->posTable[0].y;
+ obj2.x2 = obj2.x1 + ptr->width - 1;
+ obj2.y2 += ptr->height - 1;
+ return clipBoundingBox(&obj1, &obj2);
+ case 17:
+ obj1.x2 = obj1.x1 + o->posTable[1].x;
+ obj1.x1 += o->posTable[0].x;
+ obj1.y2 += o->posTable[1].y;
+ obj1.y1 += o->posTable[0].y;
+ obj2.x2 = obj2.x1 + ptr->posTable[1].x;
+ obj2.x1 += ptr->posTable[0].x;
+ obj2.y2 += ptr->posTable[1].y;
+ obj2.y1 += ptr->posTable[0].y;
+ return clipBoundingBox(&obj1, &obj2);
+ case 49:
+ obj1.x2 = obj1.x1 + o->posTable[1].x;
+ obj1.x1 += o->posTable[0].x;
+ obj1.y2 += o->posTable[1].y;
+ obj1.y1 += o->posTable[0].y;
+ obj2.x2 = obj2.x1 + ptr->width - 1;
+ obj2.y2 += ptr->height - 1;
+ if (clipBoundingBox(&obj1, &obj2)) {
+ updateBoundingBoxClippingOffset(&obj1, &obj2, _res->getLvlSpriteCoordPtr(ptr->levelData0x2988, ptr->currentSprite), (ptr->flags1 >> 4) & 3);
+ }
+ break;
+ case 16:
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ obj2.y1 += ptr->posTable[0].y;
+ obj2.x2 = obj2.x1 + ptr->posTable[1].x;
+ obj2.x1 += ptr->posTable[0].x;
+ obj2.y2 += ptr->posTable[1].y;
+ return clipBoundingBox(&obj1, &obj2);
+ case 0:
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ obj2.x2 = obj2.x1 + ptr->width - 1;
+ obj2.y2 += ptr->height - 1;
+ return clipBoundingBox(&obj1, &obj2);
+ case 48:
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ obj2.x2 = obj2.x1 + ptr->width - 1;
+ obj2.y2 += ptr->height - 1;
+ if (clipBoundingBox(&obj1, &obj2)) {
+ return updateBoundingBoxClippingOffset(&obj1, &obj2, _res->getLvlSpriteCoordPtr(ptr->levelData0x2988, ptr->currentSprite), (ptr->flags1 >> 4) & 3);
+ }
+ break;
+ case 19:
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ obj2.x2 = obj2.x1 + ptr->posTable[1].x;
+ obj2.x1 += ptr->posTable[0].x;
+ obj2.y1 += ptr->posTable[0].y;
+ obj2.y2 += ptr->posTable[1].y;
+ if (clipBoundingBox(&obj2, &obj1)) {
+ return updateBoundingBoxClippingOffset(&obj2, &obj1, _res->getLvlSpriteCoordPtr(o->levelData0x2988, o->currentSprite), (o->flags1 >> 4) & 3);
+ }
+ break;
+ case 3:
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ obj2.x2 = obj2.x1 + ptr->width - 1;
+ obj2.y2 += ptr->height - 1;
+ if (clipBoundingBox(&obj2, &obj1)) {
+ return updateBoundingBoxClippingOffset(&obj2, &obj1, _res->getLvlSpriteCoordPtr(o->levelData0x2988, o->currentSprite), (o->flags1 >> 4) & 3);
+ }
+ break;
+ case 51:
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ obj2.x2 = obj2.x1 + ptr->width - 1;
+ obj2.y2 += ptr->height - 1;
+ return clipLvlObjectsBoundingBoxHelper(o, &obj1, ptr, &obj2);
+ case 115:
+ if (o->width == 3) {
+ obj1.y2 += 7;
+ obj1.x2 = obj1.x1 + 7;
+ } else {
+ obj1.x2 = obj1.x1 + o->width - 1;
+ obj1.y2 += o->height - 1;
+ }
+ obj2.x2 = obj2.x1 + ptr->width - 9;
+ obj2.x1 += 4;
+ obj2.y2 += ptr->height - 13;
+ obj2.y1 += 6;
+ if (clipBoundingBox(&obj2, &obj1)) {
+ return updateBoundingBoxClippingOffset(&obj2, &obj1, _res->getLvlSpriteCoordPtr(o->levelData0x2988, o->currentSprite), (o->flags1 >> 4) & 3);
+ }
+ break;
+ default:
+ warning("Unhandled clipLvlObjectsBoundingBox type %d (%d)", type, type - 17);
+ break;
+ }
+ return 0;
+}
+
+int Game::clipLvlObjectsSmall(LvlObject *o1, LvlObject *o2, int type) {
+ if (o1->width > 3 || o1->height > 3) {
+ return clipLvlObjectsBoundingBox(o1, o2, type);
+ }
+ LvlObject tmpObject;
+ memcpy(&tmpObject, o1, sizeof(LvlObject));
+ tmpObject.type = 2;
+ if (o1->frame == 0) {
+ updateAndyObject(&tmpObject);
+ }
+ updateAndyObject(&tmpObject);
+ return clipLvlObjectsBoundingBox(&tmpObject, o2, type);
+}
+
+int Game::restoreAndyCollidesLava() {
+ int ret = 0;
+ if (_lvlObjectsList1 && !_hideAndyObjectFlag && (_mstFlags & 0x80000000) == 0) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ for (LvlObject *o = _lvlObjectsList1; o; o = o->nextPtr) {
+ if (o->spriteNum != 21 || o->screenNum != _res->_currentScreenResourceNum) {
+ continue;
+ }
+ BoundingBox b;
+ b.x1 = o->xPos + o->posTable[0].x;
+ b.x2 = o->xPos + o->posTable[1].x;
+ b.y1 = o->yPos + o->posTable[0].y;
+ b.y2 = o->yPos + o->posTable[1].y;
+ if (!clipBoundingBox(&data->boundingBox, &b)) {
+ ret = clipLvlObjectsBoundingBox(_andyObject, o, 68);
+ if (ret) {
+ setAndySpecialAnimation(0xA3);
+ }
+ }
+ }
+ }
+ return ret;
+}
+
+int Game::updateAndyLvlObject() {
+ if (!_andyObject) {
+ return 0;
+ }
+ if (_actionDirectionKeyMaskIndex != 0) {
+ setAndyLvlObjectPlasmaCannonKeyMask();
+ }
+ assert(_andyObject->callbackFuncPtr);
+ (this->*_andyObject->callbackFuncPtr)(_andyObject);
+ if (_currentLevel != kLvl_isld) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ _andyObject->xPos += data->dxPos;
+ _andyObject->yPos += data->dyPos;
+ }
+ const uint8_t flags = _andyObject->flags0 & 255;
+ if ((flags & 0x1F) == 0xB) {
+ if (_andyObject->spriteNum == 2) {
+ removeLvlObject(_andyObject);
+ }
+ if ((flags & 0xE0) == 0x40) {
+ setAndySpecialAnimation(0xA4);
+ }
+ }
+ const int ret = updateLvlObjectScreen(_andyObject);
+ if (ret > 0) {
+ // changed screen
+ return 1;
+ } else if (ret == 0) {
+ if (_currentLevel != kLvl_rock && _currentLevel != kLvl_lar2 && _currentLevel != kLvl_test) {
+ return 0;
+ }
+ if (_plasmaExplosionObject) {
+ _plasmaExplosionObject->screenNum = _andyObject->screenNum;
+ lvlObjectType1Callback(_plasmaExplosionObject);
+ if (_andyObject->actionKeyMask & 4) {
+ addToSpriteList(_plasmaExplosionObject);
+ }
+ } else if (_andyObject->spriteNum == 0) {
+ lvlObjectType1Init(_andyObject);
+ }
+ return 0;
+ }
+ // moved to invalid screen (-1), restart
+ if ((_andyObject->flags0 & 0x1F) != 0xB) {
+ playAndyFallingCutscene(0);
+ }
+ restartLevel();
+ return 1;
+}
+
+void Game::drawPlasmaCannon() {
+ int index = _plasmaCannonFirstIndex;
+ int lastIndex = _plasmaCannonLastIndex1;
+ if (lastIndex == 0) {
+ lastIndex = _plasmaCannonLastIndex2;
+ }
+ int x1 = _plasmaCannonPosX[index];
+ int y1 = _plasmaCannonPosY[index];
+ index += 4;
+ do {
+ int x2 = _plasmaCannonXPointsTable1[index];
+ int y2 = _plasmaCannonYPointsTable1[index];
+ if (_plasmaCannonDirection == 1) {
+ _video->drawLine(x1 - 1, y1, x2 - 1, y2, 0xA9);
+ _video->drawLine(x1 + 1, y1, x2 + 1, y2, 0xA9);
+ } else {
+ _video->drawLine(x1, y1 - 1, x2, y2 - 1, 0xA9);
+ _video->drawLine(x1, y1 + 1, x2, y2 + 1, 0xA9);
+ }
+ _video->drawLine(x1, y1, x2, y2, 0xA6);
+ x1 = x2;
+ y1 = y2;
+ index += 4;
+ } while (index <= lastIndex);
+ _plasmaCannonLastIndex1 = 0;
+ _plasmaCannonPointsMask = 0;
+ _plasmaCannonExplodeFlag = false;
+ _plasmaCannonObject = 0;
+}
+
+void Game::drawScreen() {
+ memcpy(_video->_frontLayer, _video->_backgroundLayer, Video::W * Video::H);
+
+ // redraw background animation sprites
+ LvlBackgroundData *dat = &_res->_resLvlScreenBackgroundDataTable[_res->_currentScreenResourceNum];
+ if (_res->_isPsx) {
+ for (Sprite *spr = _typeSpritesList[0]; spr; spr = spr->nextPtr) {
+ assert((spr->num & 0x1F) == 0);
+ assert(spr->w == 0xFFFF && spr->h == 0xFFFF);
+ _video->decodeBackgroundOverlayPsx(spr->bitmapBits);
+ }
+ } else {
+ for (Sprite *spr = _typeSpritesList[0]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x1F) == 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_backgroundLayer, spr->xPos, spr->yPos, 0, spr->w, spr->h);
+ }
+ }
+ }
+ memset(_video->_shadowLayer, 0, Video::W * Video::H + 1);
+ for (int i = 1; i < 8; ++i) {
+ for (Sprite *spr = _typeSpritesList[i]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x2000) != 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_shadowLayer, spr->xPos, spr->yPos, (spr->num >> 0xE) & 3, spr->w, spr->h);
+ }
+ }
+ }
+ for (int i = 1; i < 4; ++i) {
+ for (Sprite *spr = _typeSpritesList[i]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x1000) != 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_frontLayer, spr->xPos, spr->yPos, (spr->num >> 0xE) & 3, spr->w, spr->h);
+ }
+ }
+ }
+ if (_andyObject->spriteNum == 0 && (_andyObject->flags2 & 0x1F) == 4) {
+ if (_plasmaCannonFirstIndex < _plasmaCannonLastIndex2) {
+ drawPlasmaCannon();
+ }
+ }
+ for (int i = 4; i < 8; ++i) {
+ for (Sprite *spr = _typeSpritesList[i]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x1000) != 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_frontLayer, spr->xPos, spr->yPos, (spr->num >> 0xE) & 3, spr->w, spr->h);
+ }
+ }
+ }
+ for (int i = 0; i < 24; ++i) {
+ for (Sprite *spr = _typeSpritesList[i]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x2000) != 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_shadowLayer, spr->xPos, spr->yPos, (spr->num >> 0xE) & 3, spr->w, spr->h);
+ }
+ }
+ }
+ for (int i = 0; i < dat->shadowCount; ++i) {
+ _video->applyShadowColors(_shadowScreenMasksTable[i].x,
+ _shadowScreenMasksTable[i].y,
+ _shadowScreenMasksTable[i].w,
+ _shadowScreenMasksTable[i].h,
+ 256,
+ _shadowScreenMasksTable[i].w,
+ _video->_shadowLayer,
+ _video->_frontLayer,
+ _shadowScreenMasksTable[i].projectionDataPtr,
+ _shadowScreenMasksTable[i].shadowPalettePtr);
+ }
+ for (int i = 1; i < 12; ++i) {
+ for (Sprite *spr = _typeSpritesList[i]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x1000) != 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_frontLayer, spr->xPos, spr->yPos, (spr->num >> 0xE) & 3, spr->w, spr->h);
+ }
+ }
+ }
+ if (_andyObject->spriteNum == 0 && (_andyObject->flags2 & 0x1F) == 0xC) {
+ if (_plasmaCannonFirstIndex < _plasmaCannonLastIndex2) {
+ drawPlasmaCannon();
+ }
+ }
+ for (int i = 12; i <= 24; ++i) {
+ for (Sprite *spr = _typeSpritesList[i]; spr; spr = spr->nextPtr) {
+ if ((spr->num & 0x1000) != 0) {
+ _video->decodeSPR(spr->bitmapBits, _video->_frontLayer, spr->xPos, spr->yPos, (spr->num >> 0xE) & 3, spr->w, spr->h);
+ }
+ }
+ }
+}
+
+void Game::mainLoop(int level, int checkpoint, bool levelChanged) {
+ if (_playDemo && _res->loadHodDem()) {
+ _rnd._rndSeed = _res->_dem.randSeed;
+ level = _res->_dem.level;
+ checkpoint = _res->_dem.checkpoint;
+ _difficulty = _res->_dem.difficulty;
+ _res->_demOffset = 0;
+ } else if (_resumeGame) {
+ const int num = _setupConfig.currentPlayer;
+ level = _setupConfig.players[num].levelNum;
+ if (level > kLvl_dark) {
+ level = kLvl_dark;
+ }
+ checkpoint = _setupConfig.players[num].checkpointNum;
+ if (checkpoint != 0 && checkpoint >= _res->_datHdr.levelCheckpointsCount[level]) {
+ checkpoint = _setupConfig.players[num].progress[level];
+ }
+ _paf->_playedMask = _setupConfig.players[num].cutscenesMask;
+ debug(kDebug_GAME, "Restart at level %d checkpoint %d cutscenes 0x%x", level, checkpoint, _paf->_playedMask);
+ // resume once, on the starting level
+ _resumeGame = false;
+ }
+ _video->_font = _res->_fontBuffer;
+ assert(level < kLvl_test);
+ _currentLevel = level;
+ createLevel();
+ assert(checkpoint < _res->_datHdr.levelCheckpointsCount[level]);
+ _level->_checkpoint = checkpoint;
+ _mix._lock(1);
+ _res->loadLevelData(_currentLevel);
+ clearSoundObjects();
+ _mix._lock(0);
+ _mstAndyCurrentScreenNum = -1;
+ const int rounds = _playDemo ? _res->_dem.randRounds : ((g_system->getTimeStamp() & 15) + 1);
+ _rnd.initTable(rounds);
+ const int screenNum = _level->getCheckpointData(checkpoint)->screenNum;
+ if (_mstDisabled) {
+ _specialAnimMask = 0;
+ _mstCurrentAnim = 0;
+ _mstOriginPosX = Video::W / 2;
+ _mstOriginPosY = Video::H / 2;
+ } else {
+ _currentScreen = screenNum; // bugfix: clear previous level screen number
+ initMstCode();
+ }
+ memset(_level->_screenCounterTable, 0, sizeof(_level->_screenCounterTable));
+ clearDeclaredLvlObjectsList();
+ initLvlObjects();
+ resetPlasmaCannonState();
+ for (int i = 0; i < _res->_lvlHdr.screensCount; ++i) {
+ _res->_screensState[i].s2 = 0;
+ }
+ _res->_currentScreenResourceNum = _andyObject->screenNum;
+ _currentRightScreen = _res->_screensGrid[_res->_currentScreenResourceNum][kPosRightScreen];
+ _currentLeftScreen = _res->_screensGrid[_res->_currentScreenResourceNum][kPosLeftScreen];
+ if (!_mstDisabled) {
+ startMstCode();
+ }
+ if (!_paf->_skipCutscenes && _level->_checkpoint == 0 && !levelChanged) {
+ const uint8_t num = _cutscenes[_currentLevel];
+ _paf->preload(num);
+ _paf->play(num);
+ _paf->unload(num);
+ if (g_system->inp.quit) {
+ return;
+ }
+ }
+ _endLevel = false;
+ resetShootLvlObjectDataTable();
+ callLevel_initialize();
+ restartLevel();
+ do {
+ const int frameTimeStamp = g_system->getTimeStamp() + _frameMs;
+ levelMainLoop();
+ if (g_system->inp.quit) {
+ break;
+ }
+ const int delay = MAX(10, frameTimeStamp - g_system->getTimeStamp());
+ g_system->sleep(delay);
+ } while (!_endLevel);
+ _animBackgroundDataCount = 0;
+ callLevel_terminate();
+}
+
+void Game::mixAudio(int16_t *buf, int len) {
+
+ if (_snd_muted) {
+ return;
+ }
+
+ const int kStereoSamples = _res->_isPsx ? 1792 * 2 : 1764 * 2; // stereo
+
+ static int count = 0;
+
+ static int16_t buffer[4096];
+ static int bufferOffset = 0;
+ static int bufferSize = 0;
+
+ // flush samples from previous run
+ if (bufferSize > 0) {
+ const int count = len < bufferSize ? len : bufferSize;
+ memcpy(buf, buffer + bufferOffset, count * sizeof(int16_t));
+ buf += count;
+ len -= count;
+ bufferOffset += count;
+ bufferSize -= count;
+ }
+
+ while (len > 0) {
+ // this enqueues 1764*2 bytes for mono samples and 3528*2 bytes for stereo
+ _mix._mixingQueueSize = 0;
+ // 17640 + 17640 * 25 / 100 == 22050 (1.25x)
+ if (count == 4) {
+ mixSoundObjects17640(true);
+ count = 0;
+ } else {
+ mixSoundObjects17640(false);
+ ++count;
+ }
+
+ if (len >= kStereoSamples) {
+ _mix.mix(buf, kStereoSamples);
+ buf += kStereoSamples;
+ len -= kStereoSamples;
+ } else {
+ memset(buffer, 0, sizeof(buffer));
+ _mix.mix(buffer, kStereoSamples);
+ memcpy(buf, buffer, len * sizeof(int16_t));
+ bufferOffset = len;
+ bufferSize = kStereoSamples - len;
+ break;
+ }
+ }
+}
+
+void Game::updateLvlObjectList(LvlObject **list) {
+ LvlObject *ptr = *list;
+ while (ptr) {
+ LvlObject *next = ptr->nextPtr; // get 'next' as callback can modify linked list (eg. remove)
+ if (ptr->callbackFuncPtr == &Game::lvlObjectList3Callback && list != &_lvlObjectsList3) {
+ warning("lvlObject %p has callbackType3 and not in _lvlObjectsList3", ptr);
+ ptr = next;
+ continue;
+ }
+ if (ptr->callbackFuncPtr) {
+ (this->*(ptr->callbackFuncPtr))(ptr);
+ }
+ if (ptr->bitmapBits && list != &_lvlObjectsList3) {
+ addToSpriteList(ptr);
+ }
+ ptr = next;
+ }
+}
+
+void Game::updateLvlObjectLists() {
+ updateLvlObjectList(&_lvlObjectsList0);
+ updateLvlObjectList(&_lvlObjectsList1);
+ updateLvlObjectList(&_lvlObjectsList2);
+ updateLvlObjectList(&_lvlObjectsList3);
+}
+
+LvlObject *Game::updateAnimatedLvlObjectType0(LvlObject *ptr) {
+ const bool isPsx = _res->_isPsx;
+ const int soundDataLen = isPsx ? sizeof(uint32_t) : sizeof(uint16_t);
+ AnimBackgroundData *vg = (AnimBackgroundData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAnimBackgroundData);
+ const uint8_t *data = vg->currentSpriteData + soundDataLen;
+ if (_res->_currentScreenResourceNum == ptr->screenNum) {
+ if (ptr->currentSound != 0xFFFF) {
+ playSound(ptr->currentSound, ptr, 0, 3);
+ ptr->currentSound = 0xFFFF;
+ }
+ Sprite *spr = _spritesNextPtr;
+ if (spr && READ_LE_UINT16(data + 2) > 8) {
+ if (isPsx) {
+ assert((ptr->flags2 & 0x1F) == 0);
+ spr->bitmapBits = data;
+ spr->w = spr->h = 0xFFFF;
+ } else {
+ spr->xPos = data[0];
+ spr->yPos = data[1];
+ spr->w = READ_LE_UINT16(data + 4);
+ spr->h = READ_LE_UINT16(data + 6);
+ spr->bitmapBits = data + 8;
+ }
+ spr->num = ptr->flags2;
+ addToSpriteList(spr);
+ }
+ }
+ int16_t soundNum = -1;
+ const int len = READ_LE_UINT16(data + 2);
+ const uint8_t *nextSpriteData = len + data + 2;
+ switch (ptr->objectUpdateType - 1) {
+ case 6:
+ vg->currentSpriteData = vg->nextSpriteData;
+ if (vg->currentFrame == 0) {
+ vg->currentFrame = 1;
+ soundNum = isPsx ? READ_LE_UINT32(vg->nextSpriteData) : READ_LE_UINT16(vg->nextSpriteData);
+ }
+ ptr->objectUpdateType = 4;
+ break;
+ case 5:
+ vg->currentFrame = 0;
+ vg->currentSpriteData = vg->otherSpriteData;
+ ptr->objectUpdateType = 1;
+ break;
+ case 3:
+ ++vg->currentFrame;
+ if (vg->currentFrame < vg->framesCount) {
+ vg->currentSpriteData = nextSpriteData;
+ } else {
+ vg->currentFrame = 0;
+ vg->currentSpriteData = vg->otherSpriteData;
+ ptr->objectUpdateType = 1;
+ }
+ soundNum = isPsx ? READ_LE_UINT32(vg->currentSpriteData) : READ_LE_UINT16(vg->currentSpriteData);
+ break;
+ case 4:
+ ++vg->currentFrame;
+ if (vg->currentFrame < vg->framesCount) { // bugfix: original uses '<=' (out of bounds)
+ vg->currentSpriteData = nextSpriteData;
+ } else {
+ vg->currentFrame = 0;
+ vg->currentSpriteData = vg->otherSpriteData;
+ ptr->objectUpdateType = 1;
+ }
+ soundNum = isPsx ? READ_LE_UINT32(vg->currentSpriteData) : READ_LE_UINT16(vg->currentSpriteData);
+ break;
+ case 2:
+ while (vg->currentFrame < vg->framesCount - 2) {
+ ++vg->currentFrame;
+ vg->currentSpriteData = nextSpriteData;
+ nextSpriteData += soundDataLen;
+ const int len = READ_LE_UINT16(nextSpriteData + 2);
+ nextSpriteData += len + 2;
+ }
+ data = vg->currentSpriteData + soundDataLen;
+ if (_res->_currentScreenResourceNum == ptr->screenNum) {
+ Sprite *spr = _spritesNextPtr;
+ if (spr && READ_LE_UINT16(data + 2) > 8) {
+ if (isPsx) {
+ assert((ptr->flags2 & 0x1F) == 0);
+ spr->bitmapBits = data;
+ spr->w = spr->h = 0xFFFF;
+ } else {
+ spr->w = READ_LE_UINT16(data + 4);
+ spr->h = READ_LE_UINT16(data + 6);
+ spr->bitmapBits = data + 8;
+ spr->xPos = data[0];
+ spr->yPos = data[1];
+ }
+ spr->num = ptr->flags2;
+ addToSpriteList(spr);
+ }
+ }
+ ptr->objectUpdateType = 1;
+ return ptr->nextPtr;
+ case 1:
+ ++vg->currentFrame;
+ if (vg->currentFrame < vg->framesCount - 1) {
+ vg->currentSpriteData = nextSpriteData;
+ soundNum = isPsx ? READ_LE_UINT32(vg->currentSpriteData) : READ_LE_UINT16(vg->currentSpriteData);
+ } else {
+ if (vg->currentFrame > vg->framesCount) {
+ vg->currentFrame = vg->framesCount;
+ }
+ ptr->objectUpdateType = 1;
+ return ptr->nextPtr;
+ }
+ break;
+ case 0:
+ return ptr->nextPtr;
+ default:
+ soundNum = isPsx ? READ_LE_UINT32(vg->currentSpriteData) : READ_LE_UINT16(vg->currentSpriteData);
+ if (ptr->hitCount == 0) {
+ ++vg->currentFrame;
+ if (vg->currentFrame >= vg->framesCount) {
+ vg->currentSpriteData = vg->nextSpriteData;
+ vg->currentFrame = 1;
+ } else {
+ vg->currentSpriteData = nextSpriteData;
+ }
+ } else {
+ --ptr->hitCount;
+ }
+ break;
+ }
+ if (soundNum != -1) {
+ playSound(soundNum, ptr, 0, 3);
+ }
+ return ptr->nextPtr;
+}
+
+LvlObject *Game::updateAnimatedLvlObjectType1(LvlObject *ptr) {
+ if (ptr->screenNum == _res->_currentScreenResourceNum) {
+ if (_res->_screensState[_res->_currentScreenResourceNum].s0 == ptr->screenState || ptr->screenState == 0xFF) {
+ if (ptr->currentSound != 0xFFFF) {
+ playSound(ptr->currentSound, 0, 0, 3);
+ ptr->currentSound = 0xFFFF;
+ }
+ const uint8_t *data = (const uint8_t *)getLvlObjectDataPtr(ptr, kObjectDataTypeLvlBackgroundSound);
+ Sprite *spr = _spritesNextPtr;
+ if (spr && READ_LE_UINT16(data + 2) > 8) {
+ spr->w = READ_LE_UINT16(data + 4);
+ spr->h = READ_LE_UINT16(data + 6);
+ spr->bitmapBits = data + 8;
+ spr->xPos = data[0];
+ spr->yPos = data[1];
+ spr->num = ptr->flags2;
+ addToSpriteList(spr);
+ }
+ }
+ }
+ return ptr->nextPtr;
+}
+
+LvlObject *Game::updateAnimatedLvlObjectType2(LvlObject *ptr) {
+ LvlObject *next, *o;
+
+ o = next = ptr->nextPtr;
+ if ((ptr->spriteNum > 15 && ptr->dataPtr == 0) || ptr->levelData0x2988 == 0) {
+ if (ptr->childPtr) {
+ o = ptr->childPtr->nextPtr;
+ }
+ return o;
+ }
+ const int num = ptr->screenNum;
+ if (_currentScreen != num && _currentRightScreen != num && _currentLeftScreen != num) {
+ return o;
+ }
+ if (!ptr->callbackFuncPtr) {
+ warning("updateAnimatedLvlObjectType2: no callback ptr");
+ } else {
+ if ((this->*(ptr->callbackFuncPtr))(ptr) == 0) {
+ return o;
+ }
+ }
+ if ((ptr->flags1 & 6) == 2) {
+ const int index = (15 < ptr->spriteNum) ? 5 : 7;
+ ptr->yPos += calcScreenMaskDy(ptr->xPos + ptr->posTable[index].x, ptr->yPos + ptr->posTable[index].y, ptr->screenNum);
+ }
+ if (!ptr->bitmapBits) {
+ return o;
+ }
+ if (_currentScreen == ptr->screenNum) {
+ const uint8_t *bitmap = ptr->bitmapBits;
+
+ LvlObjectData *dat = ptr->levelData0x2988;
+ LvlAnimHeader *ah = (LvlAnimHeader *)(dat->animsInfoData + kLvlAnimHdrOffset) + ptr->anim;
+ LvlAnimSeqHeader *ash = (LvlAnimSeqHeader *)(dat->animsInfoData + ah->seqOffset) + ptr->frame;
+
+ const int f1 = (ptr->flags1 >> 4) & 3;
+ const int f2 = (ash->flags1 >> 4) & 3;
+ const int num = ((f1 ^ f2) << 14) | ptr->flags2;
+ Sprite *spr = _spritesNextPtr;
+ if (spr && bitmap) {
+ spr->yPos = ptr->yPos;
+ spr->xPos = ptr->xPos;
+ spr->w = ptr->width;
+ spr->h = ptr->height;
+ spr->bitmapBits = bitmap;
+ spr->num = num;
+ addToSpriteList(spr);
+ }
+ }
+ if (ptr->spriteNum <= 15 || ptr->dataPtr == 0) {
+ if (ptr->currentSound != 0xFFFF) {
+ playSound(ptr->currentSound, ptr, 0, 3);
+ }
+ return o;
+ }
+ int a, c;
+ if (ptr->dataPtr >= &_monsterObjects1Table[0] && ptr->dataPtr < &_monsterObjects1Table[kMaxMonsterObjects1]) {
+ MonsterObject1 *m = (MonsterObject1 *)ptr->dataPtr;
+ if (m->flagsA6 & 2) {
+ assert(ptr == m->o16);
+ ptr->actionKeyMask = _mstCurrentActionKeyMask;
+ ptr->directionKeyMask = _andyObject->directionKeyMask;
+ }
+ a = m->monster1Index;
+ c = 1;
+ } else {
+ assert(ptr->dataPtr >= &_monsterObjects2Table[0] && ptr->dataPtr < &_monsterObjects2Table[kMaxMonsterObjects2]);
+ MonsterObject1 *m = ((MonsterObject2 *)ptr->dataPtr)->monster1;
+ if (m) {
+ a = m->monster1Index;
+ c = 2;
+ } else {
+ a = 4;
+ c = 0;
+ }
+ }
+ if (ptr->currentSound != 0xFFFF) {
+ playSound(ptr->currentSound, ptr, c, a);
+ }
+ return o;
+}
+
+LvlObject *Game::updateAnimatedLvlObjectTypeDefault(LvlObject *ptr) {
+ return ptr->nextPtr;
+}
+
+LvlObject *Game::updateAnimatedLvlObject(LvlObject *o) {
+ switch (o->type) {
+ case 0:
+ o = updateAnimatedLvlObjectType0(o);
+ break;
+ case 1:
+ o = updateAnimatedLvlObjectType1(o);
+ break;
+ case 2:
+ o = updateAnimatedLvlObjectType2(o);
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ o = updateAnimatedLvlObjectTypeDefault(o);
+ break;
+ default:
+ error("updateAnimatedLvlObject unhandled type %d", o->type);
+ break;
+ }
+ return o;
+}
+
+void Game::updateAnimatedLvlObjectsLeftRightCurrentScreens() {
+ LvlObject *ptr = _screenLvlObjectsList[_res->_currentScreenResourceNum];
+ while (ptr) {
+ if (ptr->screenState == 0xFF || ptr->screenState == _res->_screensState[_res->_currentScreenResourceNum].s0) {
+ ptr = updateAnimatedLvlObject(ptr);
+ } else {
+ ptr = ptr->nextPtr;
+ }
+ }
+ int index = _res->_screensGrid[_res->_currentScreenResourceNum][kPosRightScreen];
+ if (index != kNoScreen && _res->_screensState[index].s2 != 0) {
+ ptr = _screenLvlObjectsList[index];
+ while (ptr) {
+ if (ptr->screenState == 0xFF || ptr->screenState == _res->_screensState[index].s0) {
+ ptr = updateAnimatedLvlObject(ptr);
+ } else {
+ ptr = ptr->nextPtr;
+ }
+ }
+ }
+ index = _res->_screensGrid[_res->_currentScreenResourceNum][kPosLeftScreen];
+ if (index != kNoScreen && _res->_screensState[index].s2 != 0) {
+ ptr = _screenLvlObjectsList[index];
+ while (ptr) {
+ if (ptr->screenState == 0xFF || ptr->screenState == _res->_screensState[index].s0) {
+ ptr = updateAnimatedLvlObject(ptr);
+ } else {
+ ptr = ptr->nextPtr;
+ }
+ }
+ }
+}
+
+void Game::updatePlasmaCannonExplosionLvlObject(LvlObject *ptr) {
+ ptr->actionKeyMask = 0;
+ ptr->directionKeyMask = 0;
+ if (_plasmaCannonDirection != 0 && _plasmaCannonLastIndex1 != 0) {
+ if (_plasmaCannonObject) {
+ const int _al = (_plasmaCannonObject->xPos <= _andyObject->xPos) ? 0 : 0xFF;
+ ptr->directionKeyMask = (_al & ~5) | 8;
+ if (_plasmaCannonObject->yPos > _andyObject->yPos) {
+ ptr->directionKeyMask |= 4;
+ } else {
+ ptr->directionKeyMask |= 1;
+ }
+ } else {
+ ptr->directionKeyMask = 2;
+ if (_plasmaCannonPointsMask != 0) {
+ if ((_plasmaCannonPointsMask & 1) != 0 || (_plasmaCannonDirection & 4) != 0) {
+ ptr->directionKeyMask = 4;
+ } else if ((_plasmaCannonDirection & 8) != 0) {
+ ptr->directionKeyMask = 8;
+ }
+ }
+ }
+ if ((_andyObject->flags0 & 0x1F) == 4 && (_andyObject->flags0 & 0xE0) == 0xC0) {
+ ptr->directionKeyMask = 1;
+ }
+ if (_plasmaCannonExplodeFlag) {
+ ptr->actionKeyMask = 4;
+ if ((_rnd._rndSeed & 1) != 0 && addLvlObjectToList3(1)) {
+ _lvlObjectsList3->flags0 = _andyObject->flags0;
+ _lvlObjectsList3->flags1 = _andyObject->flags1;
+ _lvlObjectsList3->screenNum = _andyObject->screenNum;
+ _lvlObjectsList3->flags2 = _andyObject->flags2 & ~0x2000;
+ if ((ptr->directionKeyMask & 1) == 0) {
+ _lvlObjectsList3->anim = 12;
+ _lvlObjectsList3->flags1 ^= 0x20;
+ } else {
+ _lvlObjectsList3->anim = 11;
+ }
+ _lvlObjectsList3->frame = 0;
+ setLvlObjectPosRelativeToPoint(_lvlObjectsList3, 0, _plasmaCannonXPointsTable1[_plasmaCannonLastIndex1], _plasmaCannonYPointsTable1[_plasmaCannonLastIndex1]);
+ }
+ }
+ setLvlObjectPosRelativeToPoint(ptr, 0, _plasmaCannonXPointsTable1[_plasmaCannonLastIndex1], _plasmaCannonYPointsTable1[_plasmaCannonLastIndex1]);
+ }
+ updateAndyObject(ptr);
+ ptr->screenNum = _andyObject->screenNum;
+ ptr->flags2 = merge_bits(ptr->flags2, _andyObject->flags2, 0x18);
+ ptr->flags2 = merge_bits(ptr->flags2, _andyObject->flags2 + 1, 7);
+ addToSpriteList(ptr);
+}
+
+void Game::resetPlasmaCannonState() {
+ _plasmaCannonDirection = 0;
+ _plasmaCannonPrevDirection = 0;
+ _plasmaCannonPointsSetupCounter = 0;
+ _plasmaCannonLastIndex1 = 0;
+ _plasmaCannonExplodeFlag = false;
+ _plasmaCannonPointsMask = 0;
+ _plasmaCannonFirstIndex = 16;
+ _plasmaCannonLastIndex2 = 16;
+}
+
+void Game::updateAndyMonsterObjects() {
+ uint8_t _dl = 3;
+ LvlObject *ptr = _andyObject;
+ switch (_actionDirectionKeyMaskIndex >> 4) {
+ case 6:
+ _hideAndyObjectFlag = false;
+ if (_actionDirectionKeyMaskIndex == 0x61) {
+ assert(_specialAnimLvlObject);
+ _mstOriginPosX += _specialAnimLvlObject->posTable[6].x + _specialAnimLvlObject->xPos;
+ _mstOriginPosY += _specialAnimLvlObject->posTable[6].y + _specialAnimLvlObject->yPos;
+ }
+ ptr->childPtr = 0;
+ break;
+ case 7: // replace Andy sprite with a custom animation
+ _hideAndyObjectFlag = true;
+ if (_actionDirectionKeyMaskIndex == 0x71) {
+ assert(_specialAnimLvlObject);
+ _mstOriginPosX += _specialAnimLvlObject->posTable[6].x + _specialAnimLvlObject->xPos;
+ _mstOriginPosY += _specialAnimLvlObject->posTable[6].y + _specialAnimLvlObject->yPos;
+ ptr->childPtr = _specialAnimLvlObject;
+ ptr->screenNum = _specialAnimLvlObject->screenNum;
+ } else {
+ ptr->childPtr = 0;
+ }
+ break;
+ case 10:
+ if (_actionDirectionKeyMaskIndex != 0xA3) {
+ return;
+ }
+ ptr->actionKeyMask = _actionDirectionKeyMaskTable[0x146];
+ ptr->directionKeyMask = _actionDirectionKeyMaskTable[_actionDirectionKeyMaskIndex * 2 + 1];
+ updateAndyObject(ptr);
+ _actionDirectionKeyMaskIndex = 0;
+ _hideAndyObjectFlag = false;
+ _mstFlags |= 0x80000000;
+ _dl = 1;
+ break;
+ default:
+ return;
+ }
+ if (_dl & 2) {
+ _actionDirectionKeyMaskIndex = 0;
+ ptr->anim = _mstCurrentAnim;
+ ptr->frame = 0;
+ ptr->flags1 = merge_bits(ptr->flags1, _specialAnimMask, 0x30);
+ setupLvlObjectBitmap(ptr);
+ setLvlObjectPosRelativeToPoint(ptr, 3, _mstOriginPosX, _mstOriginPosY);
+ }
+ _andyActionKeysFlags = 0;
+ if (ptr->spriteNum == 2) {
+ removeLvlObject(ptr);
+ }
+}
+
+void Game::updateInput() {
+ const uint8_t inputMask = g_system->inp.mask;
+ if (inputMask & SYS_INP_RUN) {
+ _actionKeyMask |= kActionKeyMaskRun;
+ }
+ if (inputMask & SYS_INP_JUMP) {
+ _actionKeyMask |= kActionKeyMaskJump;
+ }
+ if (inputMask & SYS_INP_SHOOT) {
+ _actionKeyMask |= kActionKeyMaskShoot;
+ }
+ if (inputMask & SYS_INP_UP) {
+ _directionKeyMask |= kDirectionKeyMaskUp;
+ } else if (inputMask & SYS_INP_DOWN) {
+ _directionKeyMask |= kDirectionKeyMaskDown;
+ }
+ if (inputMask & SYS_INP_RIGHT) {
+ _directionKeyMask |= kDirectionKeyMaskRight;
+ } else if (inputMask & SYS_INP_LEFT) {
+ _directionKeyMask |= kDirectionKeyMaskLeft;
+ }
+}
+
+void Game::levelMainLoop() {
+ memset(_typeSpritesList, 0, sizeof(_typeSpritesList));
+ _spritesNextPtr = &_spritesTable[0];
+ for (int i = 0; i < kMaxSprites - 1; ++i) {
+ _spritesTable[i].nextPtr = &_spritesTable[i + 1];
+ }
+ _spritesTable[kMaxSprites - 1].nextPtr = 0;
+ _directionKeyMask = 0;
+ _actionKeyMask = 0;
+ updateInput();
+ if (_playDemo && _res->_demOffset < _res->_dem.keyMaskLen) {
+ _andyObject->actionKeyMask = _res->_dem.actionKeyMask[_res->_demOffset];
+ _andyObject->directionKeyMask = _res->_dem.directionKeyMask[_res->_demOffset];
+ ++_res->_demOffset;
+ } else {
+ _andyObject->directionKeyMask = _directionKeyMask;
+ _andyObject->actionKeyMask = _actionKeyMask;
+ }
+ _video->clearBackBuffer();
+ if (_andyObject->screenNum != _res->_currentScreenResourceNum) {
+ preloadLevelScreenData(_andyObject->screenNum, _res->_currentScreenResourceNum);
+ setupScreen(_andyObject->screenNum);
+ } else if (_fadePalette && _levelRestartCounter == 0) {
+ restartLevel();
+ } else {
+ callLevel_postScreenUpdate(_res->_currentScreenResourceNum);
+ if (_currentLeftScreen != kNoScreen) {
+ callLevel_postScreenUpdate(_currentLeftScreen);
+ }
+ if (_currentRightScreen != kNoScreen) {
+ callLevel_postScreenUpdate(_currentRightScreen);
+ }
+ }
+ _currentLevelCheckpoint = _level->_checkpoint;
+ if (updateAndyLvlObject() != 0) {
+ callLevel_tick();
+// _time_counter1 -= _time_counter2;
+ return;
+ }
+ executeMstCode();
+ updateLvlObjectLists();
+ callLevel_tick();
+ updateAndyMonsterObjects();
+ if (!_hideAndyObjectFlag) {
+ addToSpriteList(_andyObject);
+ }
+ ((AndyLvlObjectData *)_andyObject->dataPtr)->dxPos = 0;
+ ((AndyLvlObjectData *)_andyObject->dataPtr)->dyPos = 0;
+ updateAnimatedLvlObjectsLeftRightCurrentScreens();
+ if (_currentLevel == kLvl_rock || _currentLevel == kLvl_lar2 || _currentLevel == kLvl_test) {
+ if (_andyObject->spriteNum == 0 && _plasmaExplosionObject && _plasmaExplosionObject->nextPtr != 0) {
+ updatePlasmaCannonExplosionLvlObject(_plasmaExplosionObject->nextPtr);
+ }
+ }
+ if (_res->_sssHdr.infosDataCount != 0) {
+ // sound thread signaling
+ }
+ if (_video->_paletteChanged) {
+ _video->_paletteChanged = false;
+ _video->updateGamePalette(_video->_displayPaletteBuffer);
+ g_system->copyRectWidescreen(Video::W, Video::H, _video->_backgroundLayer, _video->_palette);
+ }
+ drawScreen();
+ if (g_system->inp.screenshot) {
+ g_system->inp.screenshot = false;
+ captureScreenshot();
+ }
+ if (_cheats != 0) {
+ char buffer[256];
+ snprintf(buffer, sizeof(buffer), "P%d S%02d %d R%d", _currentLevel, _andyObject->screenNum, _res->_screensState[_andyObject->screenNum].s0, _level->_checkpoint);
+ _video->drawString(buffer, (Video::W - strlen(buffer) * 8) / 2, 8, _video->findWhiteColor(), _video->_frontLayer);
+ }
+ if (_shakeScreenDuration != 0 || _levelRestartCounter != 0 || _video->_displayShadowLayer) {
+ shakeScreen();
+ uint8_t *p = _video->_shadowLayer;
+ if (!_video->_displayShadowLayer) {
+ p = _video->_frontLayer;
+ }
+ _video->updateGameDisplay(p);
+ } else {
+ _video->updateGameDisplay(_video->_frontLayer);
+ }
+ _rnd.update();
+ g_system->processEvents();
+ if (g_system->inp.keyPressed(SYS_INP_ESC) || g_system->inp.exit) { // display exit confirmation screen
+ if (displayHintScreen(-1, 0)) {
+ g_system->inp.quit = true;
+ return;
+ }
+ } else {
+ // displayHintScreen(1, 0);
+ _video->updateScreen();
+ }
+}
+
+void Game::callLevel_postScreenUpdate(int num) {
+ _level->postScreenUpdate(num);
+}
+
+void Game::callLevel_preScreenUpdate(int num) {
+ _level->preScreenUpdate(num);
+}
+
+Level *Game::createLevel() {
+ switch (_currentLevel) {
+ case 0:
+ _level = Level_rock_create();
+ break;
+ case 1:
+ _level = Level_fort_create();
+ break;
+ case 2:
+ _level = Level_pwr1_create();
+ break;
+ case 3:
+ _level = Level_isld_create();
+ break;
+ case 4:
+ _level = Level_lava_create();
+ break;
+ case 5:
+ _level = Level_pwr2_create();
+ break;
+ case 6:
+ _level = Level_lar1_create();
+ break;
+ case 7:
+ _level = Level_lar2_create();
+ break;
+ case 8:
+ _level = Level_dark_create();
+ break;
+ }
+ return _level;
+}
+
+void Game::callLevel_initialize() {
+ _level->setPointers(this, _andyObject, _paf, _res, _video);
+ _level->initialize();
+}
+
+void Game::callLevel_tick() {
+ _level->tick();
+}
+
+void Game::callLevel_terminate() {
+ _level->terminate();
+ delete _level;
+ _level = 0;
+}
+
+void Game::displayLoadingScreen() {
+ if (_loadingScreenEnabled && _res->loadDatLoadingImage(_video->_frontLayer, _video->_palette)) {
+ g_system->setPalette(_video->_palette, 256, 6);
+ g_system->copyRect(0, 0, Video::W, Video::H, _video->_frontLayer, 256);
+ g_system->updateScreen(false);
+ }
+}
+
+int Game::displayHintScreen(int num, int pause) {
+ static const int kQuitYes = 0;
+ static const int kQuitNo = 1;
+ int quit = kQuitYes;
+ bool confirmQuit = false;
+ uint8_t *quitBuffers[] = {
+ _video->_frontLayer,
+ _video->_shadowLayer,
+ };
+ muteSound();
+ if (num == -1) {
+ num = _res->_datHdr.yesNoQuitImage; // 'Yes'
+ _res->loadDatHintImage(num + 1, _video->_shadowLayer, _video->_palette); // 'No'
+ confirmQuit = true;
+ }
+ if (_res->loadDatHintImage(num, _video->_frontLayer, _video->_palette)) {
+ g_system->setPalette(_video->_palette, 256, 6);
+ g_system->copyRect(0, 0, Video::W, Video::H, _video->_frontLayer, 256);
+ g_system->updateScreen(false);
+ do {
+ g_system->processEvents();
+ if (confirmQuit) {
+ const int currentQuit = quit;
+ if (g_system->inp.keyReleased(SYS_INP_LEFT)) {
+ quit = kQuitNo;
+ }
+ if (g_system->inp.keyReleased(SYS_INP_RIGHT)) {
+ quit = kQuitYes;
+ }
+ if (currentQuit != quit) {
+ g_system->copyRect(0, 0, Video::W, Video::H, quitBuffers[quit], 256);
+ g_system->updateScreen(false);
+ }
+ }
+ g_system->sleep(30);
+ } while (!g_system->inp.quit && !g_system->inp.keyReleased(SYS_INP_JUMP));
+ _video->_paletteChanged = true;
+ }
+ unmuteSound();
+ return confirmQuit && quit == kQuitYes;
+}
+
+void Game::prependLvlObjectToList(LvlObject **list, LvlObject *ptr) {
+ ptr->nextPtr = *list;
+ *list = ptr;
+}
+
+void Game::removeLvlObjectFromList(LvlObject **list, LvlObject *ptr) {
+ LvlObject *current = *list;
+ if (current && ptr) {
+ if (current == ptr) {
+ *list = ptr->nextPtr;
+ } else {
+ LvlObject *prev = 0;
+ do {
+ prev = current;
+ current = current->nextPtr;
+ } while (current && current != ptr);
+ assert(prev);
+ prev->nextPtr = current->nextPtr;
+ }
+ }
+}
+
+void *Game::getLvlObjectDataPtr(LvlObject *o, int type) const {
+ switch (type) {
+ case kObjectDataTypeAndy:
+ assert(o == _andyObject);
+ assert(o->dataPtr == &_andyObjectScreenData);
+ break;
+ case kObjectDataTypeAnimBackgroundData:
+ assert(o->dataPtr >= &_animBackgroundDataTable[0] && o->dataPtr < &_animBackgroundDataTable[kMaxBackgroundAnims]);
+ break;
+ case kObjectDataTypeShoot:
+ assert(o->dataPtr >= &_shootLvlObjectDataTable[0] && o->dataPtr < &_shootLvlObjectDataTable[kMaxShootLvlObjectData]);
+ break;
+ case kObjectDataTypeLvlBackgroundSound:
+ assert(o->type == 1);
+ // dataPtr is _res->_resLvlScreenBackgroundDataTable[num].backgroundSoundTable + 2
+ assert(o->dataPtr);
+ break;
+ case kObjectDataTypeMonster1:
+ assert(o->dataPtr >= &_monsterObjects1Table[0] && o->dataPtr < &_monsterObjects1Table[kMaxMonsterObjects1]);
+ break;
+ case kObjectDataTypeMonster2:
+ assert(o->dataPtr >= &_monsterObjects2Table[0] && o->dataPtr < &_monsterObjects2Table[kMaxMonsterObjects2]);
+ break;
+ }
+ return o->dataPtr;
+}
+
+// Andy
+void Game::lvlObjectType0Init(LvlObject *ptr) {
+ uint8_t num = ptr->spriteNum;
+ if (_currentLevel == kLvl_rock && _level->_checkpoint >= 5) {
+ num = 2; // sprite without 'plasma cannon'
+ }
+ _andyObject = declareLvlObject(ptr->type, num);
+ assert(_andyObject);
+ _andyObject->xPos = ptr->xPos;
+ _andyObject->yPos = ptr->yPos;
+ _andyObject->screenNum = ptr->screenNum;
+ _andyObject->anim = ptr->anim;
+ _andyObject->frame = ptr->frame;
+ _andyObject->flags2 = ptr->flags2;
+ _andyObject->dataPtr = &_andyObjectScreenData;
+ memset(&_andyObjectScreenData, 0, sizeof(_andyObjectScreenData));
+}
+
+// Plasma cannon explosion
+void Game::lvlObjectType1Init(LvlObject *ptr) {
+ AndyLvlObjectData *dataPtr = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ if (dataPtr->shootLvlObject) {
+ return;
+ }
+ LvlObject *o = declareLvlObject(8, 1);
+ assert(o);
+ o->xPos = ptr->xPos;
+ o->yPos = ptr->yPos;
+ o->anim = 13;
+ o->frame = 0;
+ o->screenNum = ptr->screenNum;
+ o->flags1 = merge_bits(o->flags1, ptr->flags1, 0x30); // vg->flags1 ^= (vg->flags1 ^ ptr->flags1) & 0x30;
+ o->flags2 = ptr->flags2 & ~0x2000;
+ setupLvlObjectBitmap(o);
+ prependLvlObjectToList(&_plasmaExplosionObject, o);
+
+ o = declareLvlObject(8, 1);
+ assert(o);
+ dataPtr->shootLvlObject = o;
+ o->xPos = ptr->xPos;
+ o->yPos = ptr->yPos;
+ o->anim = 5;
+ o->frame = 0;
+ o->screenNum = ptr->screenNum;
+ o->flags1 = merge_bits(o->flags1, ptr->flags1, 0x30); // vg->flags1 ^= (vg->flags1 ^ ptr->flags1) & 0x30;
+ o->flags2 = ptr->flags2 & ~0x2000;
+ setupLvlObjectBitmap(o);
+ prependLvlObjectToList(&_plasmaExplosionObject, o);
+}
+
+void Game::lvlObjectTypeInit(LvlObject *o) {
+ switch (o->spriteNum) {
+ case 0: // Andy with plasma cannon and helmet
+ case 2: // Andy
+ lvlObjectType0Init(o);
+ break;
+ case 1: // plasma cannon explosion
+ lvlObjectType1Init(o);
+ break;
+ default:
+ error("lvlObjectTypeInit unhandled case %d", o->spriteNum);
+ break;
+ }
+}
+
+void Game::lvlObjectType0CallbackHelper1() {
+ uint8_t _bl, _cl, _dl;
+
+ _cl = _dl = _andyObject->flags0;
+ _bl = _andyObject->actionKeyMask;
+
+ _dl &= 0x1F;
+ _cl >>= 5;
+ _cl &= 7;
+
+ if (_currentLevel == kLvl_dark && (_bl & 4) != 0) {
+ _bl &= ~4;
+ _bl |= 8;
+ }
+ if (_dl == 3) {
+ if (_cl == _dl) {
+ _andyActionKeysFlags |= 2;
+ }
+ } else if (_dl == 7) {
+ if (_cl == 5) {
+ _andyActionKeysFlags |= _bl & 4;
+ } else {
+ _andyActionKeysFlags &= ~4;
+ }
+ }
+ if ((_andyActionKeysFlags & 2) != 0) {
+ if (_bl & 2) {
+ _bl &= ~2;
+ } else {
+ _andyActionKeysFlags &= ~2;
+ }
+ }
+ if (_andyActionKeysFlags & 4) {
+ _bl |= 4;
+ }
+ if (_andyObject->spriteNum == 2 && (_bl & 5) == 5) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ LvlObject *o = data->shootLvlObject;
+ if (o) {
+ ShootLvlObjectData *dataUnk1 = (ShootLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ if (dataUnk1->type < 4) {
+ _bl |= 0xC0;
+ }
+ }
+ }
+ if (_plasmaCannonFlags & 2) {
+ _bl &= ~4;
+ }
+ _andyObject->actionKeyMask = (_bl & _andyActionKeyMaskAnd) | _andyActionKeyMaskOr;
+ _bl = _andyObject->directionKeyMask;
+ _andyObject->directionKeyMask = (_bl & _andyDirectionKeyMaskAnd) | _andyDirectionKeyMaskOr;
+}
+
+int Game::calcScreenMaskDx(int x, int y, int num) {
+ const uint32_t offset = screenMaskOffset(x, y);
+ int ret = -(x & 7);
+ if (num & 1) {
+ ret += 8;
+ if (_screenMaskBuffer[offset] & 2) {
+ return ret;
+ } else if (_screenMaskBuffer[offset - 1] & 2) {
+ return ret - 8;
+ }
+ } else {
+ --ret;
+ if (_screenMaskBuffer[offset] & 2) {
+ return ret;
+ } else if (_screenMaskBuffer[offset + 1] & 2) {
+ return ret + 8;
+ } else if (_screenMaskBuffer[offset + 2] & 2) {
+ return ret + 16;
+ }
+ }
+ return 0;
+}
+
+void Game::lvlObjectType0CallbackBreathBubbles(LvlObject *ptr) {
+
+ AndyLvlObjectData *vf = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+
+ BoundingBox b;
+ b.x1 = b.x2 = ptr->xPos + ptr->posTable[7].x;
+ b.y1 = b.y2 = ptr->yPos + ptr->posTable[7].y;
+
+ const int num = _pwr1_screenTransformLut[_res->_currentScreenResourceNum * 2 + 1];
+ if (!clipBoundingBox(&_screenTransformRects[num], &b)) {
+ ++vf->unk6; // apnea counter/time
+ } else {
+ vf->unk6 = 0;
+ }
+ b.y1 -= 24;
+ if (vf->unk2 == 0 && !clipBoundingBox(&_screenTransformRects[num], &b)) {
+
+ if (addLvlObjectToList3(4)) {
+ _lvlObjectsList3->xPos = b.x1;
+ _lvlObjectsList3->yPos = b.y1 + 24;
+ _lvlObjectsList3->screenNum = ptr->screenNum;
+ _lvlObjectsList3->anim = 0;
+ _lvlObjectsList3->frame = 0;
+ _lvlObjectsList3->flags2 = ptr->flags2 + 1;
+ _lvlObjectsList3->flags0 = (_lvlObjectsList3->flags0 & ~0x19) | 6;
+ _lvlObjectsList3->flags1 &= ~0x20;
+ }
+
+ int currentApneaLevel = vf->unk3;
+ static const int16_t _pwr1_apneaDuration[] = { 625, 937, 1094, 1250 };
+ int newApneaLevel = 0;
+ while (newApneaLevel < 4 && vf->unk6 >= _pwr1_apneaDuration[newApneaLevel]) {
+ ++newApneaLevel;
+ }
+ vf->unk3 = newApneaLevel;
+ static const uint8_t _pwr1_apneaBubble[] = { 22, 20, 18, 16, 14 };
+ vf->unk2 = _pwr1_apneaBubble[newApneaLevel];
+ // play Andy animation when apnea level changes
+ switch (currentApneaLevel) {
+ case 2:
+ if (newApneaLevel == 3) {
+ if (_actionDirectionKeyMaskIndex < 1) {
+ _actionDirectionKeyMaskIndex = 1;
+ _actionDirectionKeyMaskCounter = 0;
+ }
+ }
+ break;
+ case 3:
+ if (newApneaLevel == 4) {
+ if (_actionDirectionKeyMaskIndex < 2) {
+ _actionDirectionKeyMaskIndex = 2;
+ _actionDirectionKeyMaskCounter = 0;
+ }
+ }
+ break;
+ case 4:
+ if (vf->unk6 >= 1250) {
+ if (_actionDirectionKeyMaskIndex < 160) {
+ _actionDirectionKeyMaskIndex = 160;
+ _actionDirectionKeyMaskCounter = 0;
+ }
+ }
+ break;
+ }
+ assert(_lvlObjectsList3);
+ switch (newApneaLevel) {
+ case 0:
+ _lvlObjectsList3->actionKeyMask = 1;
+ break;
+ case 1:
+ _lvlObjectsList3->actionKeyMask = 2;
+ break;
+ case 2:
+ _lvlObjectsList3->actionKeyMask = 4;
+ break;
+ case 3:
+ _lvlObjectsList3->actionKeyMask = 8;
+ break;
+ default:
+ _lvlObjectsList3->actionKeyMask = 16;
+ break;
+ }
+ }
+ if (vf->unk2 != 0) {
+ --vf->unk2;
+ }
+}
+
+void Game::setupSpecialPowers(LvlObject *ptr) {
+ assert(ptr == _andyObject);
+ AndyLvlObjectData *vf = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ LvlObject *vg = vf->shootLvlObject;
+ const uint8_t pos = ptr->flags0 & 0x1F;
+ uint8_t var1 = (ptr->flags0 >> 5) & 7;
+ if (pos == 4) { // release
+ assert(vg->dataPtr);
+ ShootLvlObjectData *va = (ShootLvlObjectData *)getLvlObjectDataPtr(vg, kObjectDataTypeShoot);
+ vg->callbackFuncPtr = &Game::lvlObjectSpecialPowersCallback;
+ uint8_t _cl = (ptr->flags1 >> 4) & 3;
+ if (va->type == 4) {
+ va->counter = 33;
+ switch (var1) {
+ case 0:
+ va->state = ((_cl & 1) != 0) ? 5 : 0;
+ break;
+ case 1:
+ va->state = ((_cl & 1) != 0) ? 3 : 1;
+ break;
+ case 2:
+ va->state = ((_cl & 1) != 0) ? 4 : 2;
+ break;
+ case 3:
+ va->state = ((_cl & 1) != 0) ? 1 : 3;
+ break;
+ case 4:
+ va->state = ((_cl & 1) != 0) ? 2 : 4;
+ break;
+ case 5:
+ va->state = ((_cl & 1) != 0) ? 0 : 5;
+ break;
+ case 6:
+ va->state = 6;
+ break;
+ case 7:
+ va->state = 7;
+ break;
+ }
+ // dx, dy
+ static const uint8_t _byte_43E670[16] = {
+ 0x0F, 0x00, 0xF1, 0xF6, 0xF1, 0x0A, 0x0F, 0xF6, 0x0F, 0x0A, 0xF1, 0x00, 0x00, 0xF1, 0x00, 0x0F
+ };
+ va->dxPos = (int8_t)_byte_43E670[va->state * 2];
+ va->dyPos = (int8_t)_byte_43E670[va->state * 2 + 1];
+ vg->anim = 10;
+ } else {
+ va->counter = 17;
+ switch (var1) {
+ case 0:
+ vg->anim = 13;
+ va->state = ((_cl & 1) != 0) ? 5 : 0;
+ break;
+ case 1:
+ vg->anim = 12;
+ va->state = ((_cl & 1) != 0) ? 3 : 1;
+ _cl ^= 1;
+ break;
+ case 2:
+ vg->anim = 12;
+ va->state = ((_cl & 1) != 0) ? 4 : 2;
+ _cl ^= 3;
+ break;
+ case 3:
+ vg->anim = 12;
+ va->state = ((_cl & 1) != 0) ? 1 : 3;
+ break;
+ case 4:
+ vg->anim = 12;
+ va->state = ((_cl & 1) != 0) ? 2 : 4;
+ _cl ^= 2;
+ break;
+ case 5:
+ vg->anim = 13;
+ va->state = ((_cl & 1) != 0) ? 0 : 5;
+ _cl ^= 1;
+ break;
+ case 6:
+ vg->anim = 11;
+ va->state = 6;
+ break;
+ case 7:
+ vg->anim = 11;
+ va->state = 7;
+ _cl ^= 2;
+ break;
+ }
+ va->dxPos = (int8_t)_specialPowersDxDyTable[va->state * 2];
+ va->dyPos = (int8_t)_specialPowersDxDyTable[va->state * 2 + 1];
+ }
+ vg->frame = 0;
+ vg->flags1 = (vg->flags1 & ~0x30) | ((_cl & 3) << 4);
+ setupLvlObjectBitmap(vg);
+ vg->screenNum = ptr->screenNum;
+ setLvlObjectPosRelativeToObject(vg, 7, ptr, 6);
+ if (_currentLevel == kLvl_isld) {
+ AndyLvlObjectData *vc = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ vg->xPos += vc->dxPos;
+ }
+ vf->shootLvlObject = 0;
+ } else if (pos == 7) { // hold
+ switch (var1) {
+ case 0:
+ if (!vg) {
+ if (!vf->shootLvlObject) {
+ LvlObject *vd = declareLvlObject(8, 3);
+ vf->shootLvlObject = vd;
+ vd->dataPtr = _shootLvlObjectDataNextPtr;
+ if (_shootLvlObjectDataNextPtr) {
+ _shootLvlObjectDataNextPtr = _shootLvlObjectDataNextPtr->nextPtr;
+ memset(vd->dataPtr, 0, sizeof(ShootLvlObjectData));
+ } else {
+ warning("Nothing free in _shootLvlObjectDataNextPtr");
+ }
+ vd->xPos = ptr->xPos;
+ vd->yPos = ptr->yPos;
+ vd->flags1 &= ~0x30;
+ vd->screenNum = ptr->screenNum;
+ vd->anim = 7;
+ vd->frame = 0;
+ vd->bitmapBits = 0;
+ vd->flags2 = (ptr->flags2 & ~0x2000) - 1;
+ prependLvlObjectToList(&_lvlObjectsList0, vd);
+ }
+ AndyLvlObjectData *vc = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ LvlObject *va = vc->shootLvlObject;
+ if (va) {
+ if (!va->dataPtr) {
+ warning("lvlObject %p with NULL dataPtr", va);
+ break;
+ }
+ ShootLvlObjectData *vd = (ShootLvlObjectData *)getLvlObjectDataPtr(va, kObjectDataTypeShoot);
+ vd->type = 0;
+ }
+ } else {
+ if (!vg->dataPtr) {
+ warning("lvlObject %p with NULL dataPtr", vg);
+ break;
+ }
+ ShootLvlObjectData *va = (ShootLvlObjectData *)getLvlObjectDataPtr(vg, kObjectDataTypeShoot);
+ vg->anim = (va->type == 4) ? 14 : 15;
+ updateAndyObject(vg);
+ setLvlObjectPosRelativeToObject(vg, 0, ptr, 6);
+ if (_currentLevel == kLvl_isld) {
+ AndyLvlObjectData *vd = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ assert(vd == vf);
+ vg->xPos += vd->dxPos;
+ }
+ }
+ break;
+ case 2: {
+ if (!vf->shootLvlObject) {
+ LvlObject *vd = declareLvlObject(8, 3);
+ vf->shootLvlObject = vd;
+ vd->dataPtr = _shootLvlObjectDataNextPtr;
+ if (_shootLvlObjectDataNextPtr) {
+ _shootLvlObjectDataNextPtr = _shootLvlObjectDataNextPtr->nextPtr;
+ memset(vd->dataPtr, 0, sizeof(ShootLvlObjectData));
+ } else {
+ warning("Nothing free in _shootLvlObjectDataNextPtr");
+ }
+ vd->xPos = ptr->xPos;
+ vd->yPos = ptr->yPos;
+ vd->flags1 &= ~0x30;
+ vd->screenNum = ptr->screenNum;
+ vd->anim = 7;
+ vd->frame = 0;
+ vd->bitmapBits = 0;
+ vd->flags2 = (ptr->flags2 & ~0x2000) - 1;
+ prependLvlObjectToList(&_lvlObjectsList0, vd);
+ }
+ AndyLvlObjectData *vc = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ assert(vc == vf);
+ LvlObject *va = vc->shootLvlObject;
+ if (va) {
+ if (!va->dataPtr) {
+ warning("lvlObject %p with NULL dataPtr", va);
+ break;
+ }
+ ShootLvlObjectData *vd = (ShootLvlObjectData *)getLvlObjectDataPtr(va, kObjectDataTypeShoot);
+ vd->type = 0;
+ }
+ }
+ break;
+ case 1:
+ if (vg) {
+ updateAndyObject(vg);
+ vg->bitmapBits = 0;
+ }
+ break;
+ case 3: {
+ if (!vf->shootLvlObject) {
+ LvlObject *vd = declareLvlObject(8, 3);
+ vf->shootLvlObject = vd;
+ vd->dataPtr = _shootLvlObjectDataNextPtr;
+ if (_shootLvlObjectDataNextPtr) {
+ _shootLvlObjectDataNextPtr = _shootLvlObjectDataNextPtr->nextPtr;
+ memset(vd->dataPtr, 0, sizeof(ShootLvlObjectData));
+ } else {
+ warning("Nothing free in _shootLvlObjectDataNextPtr");
+ }
+ vd->xPos = ptr->xPos;
+ vd->yPos = ptr->yPos;
+ vd->flags1 &= ~0x30;
+ vd->screenNum = ptr->screenNum;
+ vd->anim = 7;
+ vd->frame = 0;
+ vd->bitmapBits = 0;
+ vd->flags2 = (ptr->flags2 & ~0x2000) - 1;
+ prependLvlObjectToList(&_lvlObjectsList0, vd);
+ }
+ AndyLvlObjectData *vc = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ LvlObject *va = vc->shootLvlObject;
+ if (va) {
+ if (!va->dataPtr) {
+ warning("lvlObject %p with NULL dataPtr", va);
+ break;
+ }
+ ShootLvlObjectData *vd = (ShootLvlObjectData *)getLvlObjectDataPtr(va, kObjectDataTypeShoot);
+ vd->type = 4; // large power
+ }
+ }
+ break;
+ case 4:
+ if (vg) {
+ vf->shootLvlObject = 0;
+ removeLvlObjectFromList(&_lvlObjectsList0, vg);
+ destroyLvlObject(vg);
+ }
+ break;
+ }
+ }
+}
+
+int Game::lvlObjectType0Callback(LvlObject *ptr) {
+ AndyLvlObjectData *vf = 0;
+ if (!_hideAndyObjectFlag) {
+ vf = (AndyLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAndy);
+ vf->unk4 = ptr->flags0 & 0x1F;
+ vf->unk5 = (ptr->flags0 >> 5) & 7;
+ lvlObjectType0CallbackHelper1();
+ updateAndyObject(ptr);
+ if (vf->unk4 == (ptr->flags0 & 0x1F) && vf->unk4 == 5) {
+ _fallingAndyFlag = true;
+ } else {
+ _fallingAndyFlag = false;
+ _fallingAndyCounter = 0;
+ }
+ vf->boundingBox.x1 = ptr->xPos;
+ vf->boundingBox.x2 = ptr->xPos + ptr->width - 1;
+ vf->boundingBox.y1 = ptr->yPos;
+ vf->boundingBox.y2 = ptr->yPos + ptr->height - 1;
+ if ((ptr->flags0 & 0x300) == 0x100) {
+ const int y = _res->_screensBasePos[_res->_currentScreenResourceNum].v + ptr->posTable[3].y + ptr->yPos;
+ const int x = _res->_screensBasePos[_res->_currentScreenResourceNum].u + ptr->posTable[3].x + ptr->xPos;
+ ptr->xPos += calcScreenMaskDx(x, y, (ptr->flags1 >> 4) & 3);
+ } else if ((ptr->flags1 & 6) == 2) {
+ ptr->yPos += calcScreenMaskDy(ptr->posTable[7].x + ptr->xPos, ptr->posTable[7].y + ptr->yPos, ptr->screenNum);
+ }
+ } else if (ptr->childPtr) {
+ assert(_specialAnimLvlObject);
+ if (_specialAnimLvlObject->screenNum != ptr->screenNum) {
+ setLvlObjectPosRelativeToObject(ptr, 3, _specialAnimLvlObject, 6);
+ }
+ }
+ switch (_currentLevel) {
+ case 0:
+ case 7:
+ if (ptr->spriteNum == 0) {
+ setupPlasmaCannonPoints(ptr);
+ }
+ break;
+ case 9: // test_hod
+ if (ptr->spriteNum == 0) {
+ setupPlasmaCannonPoints(ptr);
+ } else {
+ setupSpecialPowers(ptr);
+ }
+ break;
+ case 2: // pwr1_hod
+ if (!_hideAndyObjectFlag && vf->unk4 == 6) {
+ lvlObjectType0CallbackBreathBubbles(ptr);
+ }
+ // fall through
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ setupSpecialPowers(ptr);
+ break;
+ case 1:
+ case 8:
+ break;
+ }
+ if (!_hideAndyObjectFlag) {
+ assert(vf);
+ vf->unk0 = ptr->actionKeyMask;
+ }
+ return _hideAndyObjectFlag;
+}
+
+int Game::lvlObjectType1Callback(LvlObject *ptr) {
+ if (ptr) {
+ ptr->actionKeyMask = 0;
+ switch (_plasmaCannonDirection - 1) {
+ case 0:
+ ptr->directionKeyMask = 1;
+ break;
+ case 2:
+ ptr->directionKeyMask = 3;
+ break;
+ case 1:
+ ptr->directionKeyMask = 2;
+ break;
+ case 5:
+ ptr->directionKeyMask = 6;
+ break;
+ case 3:
+ ptr->directionKeyMask = 4;
+ break;
+ case 11:
+ ptr->directionKeyMask = 12;
+ break;
+ case 7:
+ ptr->directionKeyMask = 8;
+ break;
+ case 8:
+ ptr->directionKeyMask = 9;
+ break;
+ default:
+ ptr->directionKeyMask = 0;
+ break;
+ }
+ setLvlObjectPosRelativeToPoint(ptr, 0, _plasmaCannonPosX[_plasmaCannonFirstIndex], _plasmaCannonPosY[_plasmaCannonFirstIndex]);
+ updateAndyObject(ptr);
+ ptr->flags2 = merge_bits(ptr->flags2, _andyObject->flags2, 0x18);
+ ptr->flags2 = merge_bits(ptr->flags2, _andyObject->flags2 + 1, 7);
+ }
+ return 0;
+}
+
+int Game::lvlObjectType7Callback(LvlObject *ptr) {
+ ShootLvlObjectData *dat = (ShootLvlObjectData *)getLvlObjectDataPtr(ptr, kObjectDataTypeShoot);
+ if (!dat) {
+ return 0;
+ }
+ if ((ptr->flags0 & 0x1F) == 1) {
+ dat->xPosObject = ptr->posTable[7].x + ptr->xPos;
+ dat->yPosObject = ptr->posTable[7].y + ptr->yPos;
+ ptr->xPos += dat->dxPos;
+ ptr->yPos += dat->dyPos;
+ if (!_hideAndyObjectFlag && ptr->screenNum == _andyObject->screenNum && (_andyObject->flags0 & 0x1F) != 0xB && clipLvlObjectsBoundingBox(_andyObject, ptr, 68) && (_mstFlags & 0x80000000) == 0 && (_cheats & kCheatSpectreFireballNoHit) == 0) {
+ dat->unk3 = 0x80;
+ dat->xPosShoot = _clipBoxOffsetX;
+ dat->yPosShoot = _clipBoxOffsetY;
+ setAndySpecialAnimation(0xA3);
+ }
+ if (dat->unk3 != 0x80 && dat->counter != 0) {
+ if (dat->type == 7) {
+ ptr->yPos += calcScreenMaskDy(ptr->xPos + ptr->posTable[7].x, ptr->yPos + ptr->posTable[7].y + 4, ptr->screenNum);
+
+ }
+ const uint8_t ret = lvlObjectCallbackCollideScreen(ptr);
+ if (ret != 0 && (dat->type != 7 || (ret & 1) == 0)) {
+ dat->unk3 = 0x80;
+ }
+ }
+ assert(dat->state < 8);
+ static const uint8_t animData1[16] = {
+ 0x06, 0x0B, 0x05, 0x09, 0x05, 0x09, 0x05, 0x09, 0x05, 0x09, 0x06, 0x0B, 0x04, 0x07, 0x04, 0x07
+ };
+ static const uint8_t animData2[16] = {
+ 0x06, 0x0E, 0x05, 0x0E, 0x05, 0x0E, 0x05, 0x0E, 0x05, 0x0E, 0x06, 0x0E, 0x04, 0x0E, 0x04, 0x0E
+ };
+ const uint8_t *anim = (dat->type >= 7) ? &animData2[dat->state * 2] : &animData1[dat->state * 2];
+ if (dat->counter != 0 && dat->unk3 != 0x80) {
+ if (addLvlObjectToList3(ptr->spriteNum)) {
+ LvlObject *o = _lvlObjectsList3;
+ o->flags0 = ptr->flags0;
+ o->flags1 = ptr->flags1;
+ o->screenNum = ptr->screenNum;
+ o->flags2 = ptr->flags2;
+ o->anim = anim[1];
+ if (_rnd._rndSeed & 1) {
+ ++o->anim;
+ }
+ o->frame = 0;
+ if (dat->xPosShoot >= Video::W) {
+ dat->xPosShoot -= _res->_screensBasePos[ptr->screenNum].u;
+ }
+ if (dat->yPosShoot >= Video::H) {
+ dat->yPosShoot -= _res->_screensBasePos[ptr->screenNum].v;
+ }
+ setupLvlObjectBitmap(o);
+ setLvlObjectPosRelativeToPoint(o, 0, dat->xPosObject, dat->yPosObject);
+ }
+ } else {
+ dat->type = (dat->type < 7) ? 3 : 9;
+ if (dat->counter != 0 && _actionDirectionKeyMaskIndex != 0xA3) {
+ ptr->anim = anim[0];
+ } else {
+ ptr->anim = 3;
+ }
+ ptr->frame = 0;
+ if (dat->xPosShoot >= Video::W) {
+ dat->xPosShoot -= _res->_screensBasePos[ptr->screenNum].u;
+ }
+ if (dat->yPosShoot >= Video::H) {
+ dat->yPosShoot -= _res->_screensBasePos[ptr->screenNum].v;
+ }
+ setupLvlObjectBitmap(ptr);
+ setLvlObjectPosRelativeToPoint(ptr, 0, dat->xPosShoot, dat->yPosShoot);
+ return 0;
+ }
+ } else if ((ptr->flags0 & 0x1F) == 11) {
+ dat->counter = ((ptr->flags0 & 0xE0) == 0x40) ? 0 : 1;
+ }
+ if (dat->counter == 0) {
+ if (ptr->spriteNum == 3) {
+ removeLvlObjectFromList(&_lvlObjectsList0, ptr);
+ } else {
+ removeLvlObjectFromList(&_lvlObjectsList2, ptr);
+ }
+ destroyLvlObject(ptr);
+ } else {
+ --dat->counter;
+ updateAndyObject(ptr);
+ }
+ if (setLvlObjectPosInScreenGrid(ptr, 3) < 0) {
+ dat->counter = 0;
+ }
+ return 0;
+}
+
+int Game::lvlObjectType8Callback(LvlObject *ptr) {
+ if (_mstDisabled) {
+ ptr->actionKeyMask = _andyObject->actionKeyMask;
+ ptr->directionKeyMask = _andyObject->directionKeyMask;
+ if (_andyObject->spriteNum == 2 && _lvlObjectsList0) {
+ warning("lvlObjectType8CallbackHelper unimplemented");
+ // lvlObjectType8CallbackHelper(ptr);
+ }
+ updateAndyObject(ptr);
+ setLvlObjectPosInScreenGrid(ptr, 7);
+ } else {
+ const void *dataPtr = ptr->dataPtr;
+ if (!dataPtr) {
+ ptr->bitmapBits = 0;
+ return 0;
+ }
+ int vb, var4;
+ MonsterObject1 *m = 0; // ve
+ if (dataPtr >= &_monsterObjects1Table[0] && dataPtr < &_monsterObjects1Table[kMaxMonsterObjects1]) {
+ m = (MonsterObject1 *)ptr->dataPtr;
+ vb = 1;
+ var4 = m->monster1Index;
+ if (m->flagsA6 & 2) {
+ assert(ptr == m->o16);
+ m->o16->actionKeyMask = _mstCurrentActionKeyMask;
+ m->o16->directionKeyMask = _andyObject->directionKeyMask;
+ }
+ if (m->flagsA6 & 8) {
+ ptr->bitmapBits = 0;
+ return 0;
+ }
+ } else {
+ assert(dataPtr >= &_monsterObjects2Table[0] && dataPtr < &_monsterObjects2Table[kMaxMonsterObjects2]);
+ MonsterObject2 *mo = (MonsterObject2 *)dataPtr;
+ m = mo->monster1;
+ if (m) {
+ vb = 2;
+ var4 = m->monster1Index;
+ } else {
+ vb = 0;
+ var4 = 4;
+ }
+ m = 0; // ve = 0
+ if (mo->flags24 & 8) {
+ ptr->bitmapBits = 0;
+ return 0;
+ }
+ }
+ LvlObject *o = 0; // vf
+ updateAndyObject(ptr);
+ if (m && m->o20) {
+ o = m->o20;
+ o->actionKeyMask = ptr->actionKeyMask;
+ o->directionKeyMask = ptr->directionKeyMask;
+ updateAndyObject(o);
+ setLvlObjectPosRelativeToObject(ptr, 6, o, 6);
+ addToSpriteList(o);
+ setLvlObjectPosInScreenGrid(o, 7);
+ }
+ setLvlObjectPosInScreenGrid(ptr, 7);
+ if (ptr->screenNum == _currentScreen || ptr->screenNum == _currentLeftScreen || ptr->screenNum == _currentRightScreen || o || (_currentLevel == kLvl_lar2 && ptr->spriteNum == 27) || (_currentLevel == kLvl_isld && ptr->spriteNum == 26)) {
+ if (ptr->currentSound != 0xFFFF) {
+ playSound(ptr->currentSound, ptr, vb, var4);
+ }
+ if (o && o->currentSound != 0xFFFF) {
+ playSound(o->currentSound, o, vb, var4);
+ }
+ }
+ }
+ if ((ptr->flags1 & 6) == 2) {
+ ptr->yPos += calcScreenMaskDy(ptr->xPos + ptr->posTable[5].x, ptr->yPos + ptr->posTable[5].y, ptr->screenNum);
+ }
+ return 0;
+}
+
+int Game::lvlObjectList3Callback(LvlObject *o) {
+ const uint8_t flags = o->flags0 & 0xFF;
+ if ((o->spriteNum <= 7 && (flags & 0x1F) == 0xB) || (o->spriteNum > 7 && flags == 0x1F)) {
+ if (_lvlObjectsList3 && o) {
+ if (o != _lvlObjectsList3) {
+ LvlObject *prev = 0;
+ LvlObject *ptr = _lvlObjectsList3;
+ do {
+ prev = ptr;
+ ptr = ptr->nextPtr;
+ } while (ptr && ptr != o);
+ assert(ptr);
+ prev->nextPtr = ptr->nextPtr;
+ } else {
+ _lvlObjectsList3 = o->nextPtr;
+ }
+ }
+ if (o->type == 8) {
+ _res->decLvlSpriteDataRefCounter(o);
+ o->nextPtr = _declaredLvlObjectsNextPtr;
+ --_declaredLvlObjectsListCount;
+ _declaredLvlObjectsNextPtr = o;
+ switch (o->spriteNum) {
+ case 0:
+ case 2:
+ o->dataPtr = 0;
+ break;
+ case 3:
+ case 7:
+ if (o->dataPtr) {
+ clearShootLvlObjectData(o);
+ }
+ break;
+ }
+ }
+ if (o->sssObject) {
+ removeSound(o);
+ }
+ o->sssObject = 0;
+ o->bitmapBits = 0;
+ } else {
+ updateAndyObject(o);
+ o->actionKeyMask = 0;
+ o->directionKeyMask = 0;
+ if (o->currentSound != 0xFFFF) {
+ playSound(o->currentSound, o, 0, 3);
+ }
+ if (o->bitmapBits) {
+ addToSpriteList(o);
+ }
+ }
+ return 0;
+}
+
+void Game::lvlObjectSpecialPowersCallbackHelper1(LvlObject *o) {
+ int xPos = o->xPos + o->posTable[3].x;
+ int yPos = o->yPos + o->posTable[3].y;
+ ShootLvlObjectData *dat = (ShootLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ const uint8_t val = dat->unk3;
+ if (val == 0x80) {
+ dat->xPosShoot = xPos;
+ dat->yPosShoot = yPos;
+ }
+ uint8_t screenNum = o->screenNum;
+ if (xPos < 0) {
+ xPos += Video::W;
+ screenNum = _res->_screensGrid[screenNum][kPosLeftScreen];
+ } else if (xPos >= Video::W) {
+ xPos -= Video::W;
+ screenNum = _res->_screensGrid[screenNum][kPosRightScreen];
+ }
+ if (screenNum != kNoScreen) {
+ if (yPos < 0) {
+ yPos += Video::H;
+ screenNum = _res->_screensGrid[screenNum][kPosTopScreen];
+ } else if (yPos >= Video::H) {
+ yPos -= Video::H;
+ screenNum = _res->_screensGrid[screenNum][kPosBottomScreen];
+ }
+ }
+ int8_t dy = 255 - (yPos & 7);
+ if (screenNum != kNoScreen) {
+ const int xLevelPos = _res->_screensBasePos[screenNum].u + xPos;
+ const int yLevelPos = _res->_screensBasePos[screenNum].v + yPos + 8;
+ int offset = screenMaskOffset(xLevelPos, yLevelPos);
+ if (_screenMaskBuffer[offset] & 1) {
+ dy = -8;
+ goto set_dat03;
+ } else if (_screenMaskBuffer[offset + 512] & 1) {
+ const int vg = screenGridOffset(xPos, yPos);
+ int i = _res->findScreenGridIndex(screenNum);
+ if (i < 0) {
+ goto set_dat03;
+ }
+ const uint8_t *p = _res->_resLevelData0x470CTablePtrData + (xPos & 7);
+ dy += (int8_t)p[_screenPosTable[i][vg] * 8];
+ goto set_dat03;
+ } else if (_screenMaskBuffer[offset - 1024] & 1) {
+ dy -= 16;
+ goto set_dat03;
+ } else {
+ dy = val;
+ if (val < 0x18) {
+ dat->unk3 = val + 4;
+ }
+ goto set_dxpos;
+ }
+ }
+set_dat03:
+ if (val == 0x18) {
+ dat->type = 6;
+ } else {
+ dat->unk3 = 8;
+ }
+set_dxpos:
+ if (dat->type != 6 && dat->unk3 == 0x80) {
+ dat->yPosShoot += dy;
+ setLvlObjectPosRelativeToPoint(o, 3, dat->xPosShoot, dat->yPosShoot);
+ } else {
+ dat->unk3 = 0x80;
+ dat->dxPos = 0;
+ dat->dyPos = 0;
+ }
+}
+
+uint8_t Game::lvlObjectCallbackCollideScreen(LvlObject *o) {
+ uint8_t ret = 0;
+ uint8_t screenNum = o->screenNum;
+ uint8_t var30 = 0;
+
+ int yPos = o->yPos; // va
+ if ((o->flags0 & 0xE0) != 0x20) {
+ yPos += o->posTable[6].y;
+ } else {
+ yPos += o->posTable[3].y;
+ }
+ int xPos = o->xPos + o->posTable[3].x; // vc
+
+ int var1C = 0;
+ int var20 = 0;
+ if (xPos < 0) {
+ xPos += Video::W;
+ var20 = -Video::W;
+ screenNum = _res->_screensGrid[screenNum][kPosLeftScreen];
+ } else if (xPos >= Video::W) {
+ xPos -= Video::W;
+ var20 = Video::W;
+ screenNum = _res->_screensGrid[screenNum][kPosRightScreen];
+ }
+ if (screenNum != kNoScreen) {
+ if (yPos < 0) {
+ yPos += Video::H;
+ var1C = -Video::H;
+ screenNum = _res->_screensGrid[screenNum][kPosTopScreen];
+ } else if (yPos >= Video::H) {
+ yPos -= Video::H;
+ var1C = Video::H;
+ screenNum = _res->_screensGrid[screenNum][kPosBottomScreen];
+ }
+ }
+ if (screenNum == kNoScreen) {
+ return 0;
+ }
+ ShootLvlObjectData *dat = (ShootLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ static const uint8_t data1[] = {
+ 0xFF, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x17, 0x00, 0x08, 0x08, 0x00, 0x00, 0xF8, 0xF8, 0xF0, 0xF0,
+ 0x08, 0xF8, 0x00, 0xFF, 0xF8, 0x07, 0xF0, 0x0F, 0xFF, 0x08, 0x07, 0x00, 0x0F, 0xF8, 0x17, 0xF0,
+ 0xFF, 0xF8, 0x07, 0xFF, 0x0F, 0x07, 0x17, 0x0F, 0x08, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xF0, 0x00,
+ 0x00, 0x08, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0xFF, 0x00, 0x07, 0x00, 0x0F
+ };
+ static const uint8_t data2[] = {
+ 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x08, 0xF8, 0x08,
+ 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0x08, 0xF8, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xF8, 0xF8, 0xF8
+ };
+ static const int offsets1[] = {
+ 0, 1, 1, 1, 0, -513, -513, -513, 0, 511, 511, 511, 0, -511, -511, -511, 0, 513, 513, 513, 0, -1, -1, -1, 0, -512, -512, -512, 0, 512, 512, 512
+ };
+ static const int offsets2[] = {
+ 0, 1, 512, -1, 0, -1, 512, 1, 0, 1, -512, -1, 0, -1, -512, 1
+ };
+ uint8_t _bl, _cl = dat->type;
+ const uint8_t *var10;
+ const int *vg;
+ if (_cl == 4) {
+ _bl = _cl;
+ const uint8_t num = (o->flags1 >> 4) & 3;
+ var10 = data2 + num * 8;
+ vg = offsets2 + num * 4;
+ } else {
+ const uint8_t num = dat->state;
+ var10 = data1 + num * 8;
+ vg = offsets1 + num * 4;
+ _bl = (_cl != 2) ? 4 : 2;
+ }
+ int num;
+ int var2E = _bl;
+ int vd = _res->_screensBasePos[screenNum].v + yPos;
+ int vf = _res->_screensBasePos[screenNum].u + xPos; // vf
+ vd = screenMaskOffset(vf, vd);
+ int var4 = screenGridOffset(xPos, yPos);
+ if (_cl >= 4) {
+ num = 0;
+ vd += vg[num];
+ int ve = vd;
+ uint8_t _cl, _al = 0;
+ while (1) {
+ _cl = _screenMaskBuffer[vd];
+ if ((_cl & 6) != 0) {
+ ret = _al = 1;
+ break;
+ }
+ ++num;
+ if (num >= var2E) {
+ _al = ret;
+ break;
+ }
+ vd += vg[num];
+ }
+
+ _bl = dat->state; // var18
+ if (_bl != 6 && _bl != 1 && _bl != 3) {
+ vd = ve;
+ ++vg;
+ num = 0;
+ while (1) {
+ var30 = _screenMaskBuffer[vd];
+ if ((var30 & 1) != 0) {
+ _al = 1;
+ break;
+ }
+ ++num;
+ if (num >= var2E) {
+ _al = ret;
+ break;
+ }
+ vd += *vg++;
+ }
+ }
+ if (_cl & 6) {
+ var30 = _cl;
+ }
+ if (_al == 0) {
+ return 0;
+ }
+
+ } else {
+ _bl = dat->state;
+ const uint8_t mask = (_bl == 6 || _bl == 1 || _bl == 3) ? 6 : 7;
+ num = 0;
+ vd += *vg++;
+ while (1) {
+ var30 = _screenMaskBuffer[vd];
+ if ((var30 & mask) != 0) {
+ break;
+ }
+ ++num;
+ if (num >= var2E) {
+ return ret;
+ }
+ vd += *vg++;
+ }
+ }
+ dat->xPosShoot = (int8_t)var10[num * 2 ] + var20 + (xPos & ~7);
+ dat->yPosShoot = (int8_t)var10[num * 2 + 1] + var1C + (yPos & ~7);
+ _bl = dat->state;
+ if (_bl != 2 && _bl != 4 && _bl != 7) {
+ return var30;
+ }
+ num = _res->findScreenGridIndex(screenNum);
+ if (num < 0) {
+ dat->yPosShoot += 4;
+ return var30;
+ }
+ const int vc = (o->posTable[3].x + o->xPos) & 7;
+ const uint8_t *p = _res->_resLevelData0x470CTablePtrData + vc;
+ dat->yPosShoot += (int8_t)p[_screenPosTable[num][var4] * 8];
+ return var30;
+}
+
+int Game::lvlObjectSpecialPowersCallback(LvlObject *o) {
+ if (!o->dataPtr) {
+ return 0;
+ }
+ ShootLvlObjectData *dat = (ShootLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ const uint16_t fl = o->flags0 & 0x1F;
+ if (fl == 1) {
+ if (dat->unk3 != 0x80 && dat->counter != 0) {
+ uint8_t _al = lvlObjectCallbackCollideScreen(o);
+ if (_al != 0) {
+ if (dat->type == 4 && (_al & 1) != 0 && (dat->state == 4 || dat->state == 2)) {
+ dat->type = 5;
+ _al -= 4;
+ dat->state = (_al != 0) ? 5 : 0;
+ } else {
+ dat->unk3 = 0x80;
+ }
+ }
+ }
+ if (dat->type == 5) {
+ dat->dyPos = 0;
+ if (dat->unk3 != 0x80) {
+ lvlObjectSpecialPowersCallbackHelper1(o);
+ }
+ }
+ static const uint8_t animData1[] = {
+ 0x04, 0x14, 0x03, 0x16, 0x03, 0x16, 0x03, 0x16, 0x03, 0x16, 0x04, 0x14, 0x02, 0x18, 0x02, 0x18
+ };
+ static const uint8_t animData2[] = {
+ 0x04, 0x08, 0x03, 0x08, 0x03, 0x08, 0x03, 0x08, 0x03, 0x08, 0x04, 0x08, 0x02, 0x08, 0x02, 0x08
+ };
+ const uint8_t *p = (dat->type >= 4) ? &animData2[dat->state * 2] : &animData1[dat->state * 2];
+ if (dat->unk3 != 0x80 && dat->counter != 0) {
+ if (addLvlObjectToList3(o->spriteNum)) {
+ LvlObject *ptr = _lvlObjectsList3;
+ ptr->flags0 = o->flags0;
+ ptr->flags1 = o->flags1;
+ ptr->flags2 = o->flags2;
+ ptr->screenNum = o->screenNum;
+ ptr->anim = p[1];
+ if (_rnd._rndSeed & 1) {
+ ++ptr->anim;
+ }
+ ptr->frame = 0;
+ setupLvlObjectBitmap(ptr);
+ setLvlObjectPosRelativeToObject(ptr, 0, o, 7);
+ o->xPos += dat->dxPos;
+ o->yPos += dat->dyPos;
+ }
+ } else {
+ o->anim = p[0];
+ if (dat->xPosShoot >= Video::W) {
+ dat->xPosShoot -= _res->_screensBasePos[o->screenNum].u;
+ }
+ if (dat->yPosShoot >= Video::H) {
+ dat->yPosShoot -= _res->_screensBasePos[o->screenNum].v;
+ }
+ if (dat->o && (dat->o->actionKeyMask & 7) == 7) {
+ const uint8_t num = dat->o->spriteNum;
+ if ((num >= 8 && num <= 16) || num == 28) {
+ o->anim = 16;
+ }
+ } else {
+ if (dat->counter == 0) {
+ o->anim = 16;
+ }
+ }
+ if (dat->type >= 4) {
+ dat->type = 6;
+ if (dat->dxPos <= 0) {
+ dat->xPosShoot += 8;
+ }
+ if (dat->dyPos <= 0) {
+ dat->yPosShoot += 8;
+ }
+ } else {
+ dat->type = 1;
+ }
+ dat->dxPos = 0;
+ dat->dyPos = 0;
+ o->frame = 0;
+ setupLvlObjectBitmap(o);
+ setLvlObjectPosRelativeToPoint(o, 0, dat->xPosShoot, dat->yPosShoot);
+ return 0;
+ }
+ } else if (fl == 11) {
+ if ((o->flags0 & 0xE0) == 0x40) {
+ dat->counter = 0;
+ } else {
+ dat->counter = 1;
+ }
+ }
+ if (dat->counter == 0) {
+ if (o->spriteNum == 3) {
+ removeLvlObjectFromList(&_lvlObjectsList0, o);
+ } else {
+ removeLvlObjectFromList(&_lvlObjectsList2, o);
+ }
+ destroyLvlObject(o);
+ } else {
+ --dat->counter;
+ updateAndyObject(o);
+ }
+ if (setLvlObjectPosInScreenGrid(o, 3) < 0) {
+ dat->counter = 0;
+ }
+ return 0;
+}
+
+void Game::lvlObjectTypeCallback(LvlObject *o) {
+ switch (o->spriteNum) {
+ case 0:
+ case 2:
+ o->callbackFuncPtr = &Game::lvlObjectType0Callback;
+ break;
+ case 1: // plasma cannon explosion
+ o->callbackFuncPtr = &Game::lvlObjectType1Callback;
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ o->callbackFuncPtr = 0;
+ break;
+ case 7: // flying spectre fireball
+ o->callbackFuncPtr = &Game::lvlObjectType7Callback;
+ break;
+ case 8: // lizard
+ case 9: // spider
+ case 10: // flying spectre
+ case 11: // spectre
+ case 12: // spectre
+ case 13: // spectre
+ case 14:
+ case 15:
+ case 16: // double spectre
+ case 17:
+ case 18:
+ case 19:
+ case 20:
+ case 21: // lava
+ case 22:
+ case 23:
+ case 24: // rope
+ case 25: // swamp snake
+ case 26:
+ case 27: // green fire-fly
+ case 28:
+ case 29:
+ o->callbackFuncPtr = &Game::lvlObjectType8Callback;
+ break;
+ default:
+ warning("lvlObjectTypeCallback unhandled case %d", o->spriteNum);
+ break;
+ }
+}
+
+LvlObject *Game::addLvlObject(int type, int x, int y, int screen, int num, int o_anim, int o_flags1, int o_flags2, int actionKeyMask, int directionKeyMask) {
+ LvlObject *ptr = 0;
+ switch (type) {
+ case 0:
+ addLvlObjectToList1(8, num);
+ ptr = _lvlObjectsList1;
+ break;
+ case 1:
+ if (screen != _currentScreen && screen != _currentLeftScreen && screen != _currentRightScreen) {
+ return 0;
+ }
+ ptr = findLvlObjectType2(num, screen);
+ break;
+ case 2:
+ addLvlObjectToList3(num);
+ ptr = _lvlObjectsList3;
+ break;
+ }
+ if (!ptr) {
+ return 0;
+ }
+ ptr->anim = o_anim;
+ ptr->flags2 = o_flags2;
+ ptr->frame = 0;
+ ptr->flags1 = ((o_flags1 & 3) << 4) | (ptr->flags1 & ~0x30);
+ ptr->actionKeyMask = actionKeyMask;
+ ptr->directionKeyMask = directionKeyMask;
+ setupLvlObjectBitmap(ptr);
+ ptr->xPos = x - ptr->posTable[7].x;
+ ptr->yPos = y - ptr->posTable[7].y;
+ ptr->screenNum = screen;
+ return ptr;
+}
+
+int Game::setLvlObjectPosInScreenGrid(LvlObject *o, int pos) {
+ int ret = 0;
+ if (o->screenNum < _res->_lvlHdr.screensCount) {
+ int xPrev = o->xPos;
+ int x = o->xPos + o->posTable[pos].x;
+ int yPrev = o->yPos;
+ int y = o->yPos + o->posTable[pos].y;
+ int numPrev = o->screenNum;
+ int screenNum = o->screenNum;
+ if (x < 0) {
+ o->screenNum = _res->_screensGrid[screenNum][kPosLeftScreen];
+ o->xPos = xPrev + Video::W;
+ } else if (x >= Video::W) {
+ o->screenNum = _res->_screensGrid[screenNum][kPosRightScreen];
+ o->xPos = xPrev - Video::W;
+ }
+ screenNum = o->screenNum;
+ if (screenNum != kNoScreen) {
+ if (y < 0) {
+ o->screenNum = _res->_screensGrid[screenNum][kPosTopScreen];
+ o->yPos = yPrev + Video::H;
+ } else if (y >= Video::H) {
+ o->screenNum = _res->_screensGrid[screenNum][kPosBottomScreen];
+ o->yPos = yPrev - Video::H;
+ }
+ }
+ screenNum = o->screenNum;
+ if (screenNum == kNoScreen) {
+ o->xPos = xPrev;
+ o->yPos = yPrev;
+ o->screenNum = numPrev;
+ ret = -1;
+ } else if (screenNum != numPrev) {
+ ret = 1;
+ }
+ }
+ return ret;
+}
+
+LvlObject *Game::declareLvlObject(uint8_t type, uint8_t num) {
+ if (type != 8 || _res->_resLevelData0x2988PtrTable[num] != 0) {
+ if (_declaredLvlObjectsListCount < kMaxLvlObjects) {
+ assert(_declaredLvlObjectsNextPtr);
+ LvlObject *ptr = _declaredLvlObjectsNextPtr;
+ _declaredLvlObjectsNextPtr = _declaredLvlObjectsNextPtr->nextPtr;
+ assert(ptr);
+ ++_declaredLvlObjectsListCount;
+ ptr->spriteNum = num;
+ ptr->type = type;
+ if (type == 8) {
+ _res->incLvlSpriteDataRefCounter(ptr);
+ lvlObjectTypeCallback(ptr);
+ }
+ ptr->currentSprite = 0;
+ ptr->sssObject = 0;
+ ptr->nextPtr = 0;
+ ptr->bitmapBits = 0;
+ return ptr;
+ }
+ }
+ return 0;
+}
+
+void Game::clearDeclaredLvlObjectsList() {
+ memset(_declaredLvlObjectsList, 0, sizeof(_declaredLvlObjectsList));
+ for (int i = 0; i < kMaxLvlObjects - 1; ++i) {
+ _declaredLvlObjectsList[i].nextPtr = &_declaredLvlObjectsList[i + 1];
+ }
+ _declaredLvlObjectsList[kMaxLvlObjects - 1].nextPtr = 0;
+ _declaredLvlObjectsNextPtr = &_declaredLvlObjectsList[0];
+ _declaredLvlObjectsListCount = 0;
+}
+
+void Game::initLvlObjects() {
+ for (int i = 0; i < _res->_lvlHdr.screensCount; ++i) {
+ _screenLvlObjectsList[i] = 0;
+ }
+ LvlObject *prevLvlObj = 0;
+ for (int i = 0; i < _res->_lvlHdr.staticLvlObjectsCount; ++i) {
+ LvlObject *ptr = &_res->_resLvlScreenObjectDataTable[i];
+ int index = ptr->screenNum;
+ ptr->nextPtr = _screenLvlObjectsList[index];
+ _screenLvlObjectsList[index] = ptr;
+ switch (ptr->type) {
+ case 0:
+ assert(_animBackgroundDataCount < kMaxBackgroundAnims);
+ ptr->dataPtr = &_animBackgroundDataTable[_animBackgroundDataCount++];
+ memset(ptr->dataPtr, 0, sizeof(AnimBackgroundData));
+ break;
+ case 1:
+ if (ptr->dataPtr) {
+ warning("Trying to free _resLvlScreenBackgroundDataTable.backgroundSoundTable (i=%d index=%d)", i, index);
+ }
+ ptr->xPos = 0;
+ ptr->yPos = 0;
+ break;
+ case 2:
+ if (prevLvlObj == &_res->_dummyObject) {
+ prevLvlObj = 0;
+ ptr->childPtr = ptr->nextPtr;
+ } else {
+ prevLvlObj = ptr->childPtr;
+ }
+ break;
+ }
+ }
+ for (int i = _res->_lvlHdr.staticLvlObjectsCount; i < _res->_lvlHdr.staticLvlObjectsCount + _res->_lvlHdr.otherLvlObjectsCount; ++i) {
+ LvlObject *ptr = &_res->_resLvlScreenObjectDataTable[i];
+ lvlObjectTypeInit(ptr);
+ }
+}
+
+void Game::setLvlObjectSprite(LvlObject *ptr, uint8_t type, uint8_t num) {
+ if (ptr->type == 8) {
+ _res->decLvlSpriteDataRefCounter(ptr);
+ ptr->spriteNum = num;
+ ptr->type = type;
+ _res->incLvlSpriteDataRefCounter(ptr);
+ }
+}
+
+LvlObject *Game::findLvlObject(uint8_t type, uint8_t spriteNum, int screenNum) {
+ LvlObject *ptr = _screenLvlObjectsList[screenNum];
+ while (ptr) {
+ if (ptr->type == type && ptr->spriteNum == spriteNum) {
+ break;
+ }
+ ptr = ptr->nextPtr;
+ }
+ return ptr;
+}
+
+LvlObject *Game::findLvlObject2(uint8_t type, uint8_t dataNum, int screenNum) {
+ LvlObject *ptr = _screenLvlObjectsList[screenNum];
+ while (ptr) {
+ if (ptr->type == type && ptr->dataNum == dataNum) {
+ break;
+ }
+ ptr = ptr->nextPtr;
+ }
+ return ptr;
+}
+
+LvlObject *Game::findLvlObjectType2(int spriteNum, int screenNum) {
+ LvlObject *ptr = _screenLvlObjectsList[screenNum];
+ while (ptr) {
+ if (ptr->type == 2 && ptr->spriteNum == spriteNum && !ptr->dataPtr) {
+ break;
+ }
+ ptr = ptr->nextPtr;
+ }
+ return ptr;
+}
+
+LvlObject *Game::findLvlObjectBoundingBox(BoundingBox *box) {
+ LvlObject *ptr = _lvlObjectsList0;
+ while (ptr) {
+ if ((ptr->flags0 & 0x1F) != 0xB || (ptr->flags0 & 0xE0) != 0x40) {
+ BoundingBox b;
+ b.x1 = ptr->xPos;
+ b.x2 = ptr->xPos + ptr->width - 1;
+ b.y1 = ptr->yPos;
+ b.y2 = ptr->yPos + ptr->height - 1;
+ const uint8_t *coords = _res->getLvlSpriteCoordPtr(ptr->levelData0x2988, ptr->currentSprite);
+ if (updateBoundingBoxClippingOffset(box, &b, coords, (ptr->flags1 >> 4) & 3)) {
+ return ptr;
+ }
+ }
+ ptr = ptr->nextPtr;
+ }
+ return 0;
+}
+
+void Game::setLavaAndyAnimation(int yPos) {
+ const uint8_t flags = (_andyObject->flags0) & 0x1F;
+ if ((_cheats & kCheatWalkOnLava) == 0 && !_hideAndyObjectFlag) {
+ if ((_mstFlags & 0x80000000) == 0) {
+ uint8_t mask = 0;
+ const int y = _andyObject->yPos;
+ if (_andyObject->posTable[5].y + y >= yPos || _andyObject->posTable[4].y + y >= yPos) {
+ mask = 0xA3;
+ }
+ if (flags != 2 && _andyObject->posTable[7].y + y >= yPos) {
+ mask = 0xA3;
+ }
+ if (mask != 0 && mask > _actionDirectionKeyMaskIndex) {
+ _actionDirectionKeyMaskIndex = mask;
+ _actionDirectionKeyMaskCounter = 0;
+ }
+ } else if (flags == 0xB) {
+ _mstFlags &= 0x7FFFFFFF;
+ }
+ }
+}
+
+void Game::updateGatesLar(LvlObject *o, uint8_t *p, int num) {
+ uint32_t mask = 1 << num; // ve
+ uint8_t _cl = p[0] & 15;
+ if (_cl >= 3) {
+ if ((o->flags0 & 0x1F) == 0) {
+ if (p[3] == 0) {
+ if (_cl == 3) {
+ p[0] = (p[0] & ~0xB) | 4;
+ p[3] = p[1];
+ o->directionKeyMask = 1;
+ o->actionKeyMask = 0;
+ } else {
+ p[0] = (p[0] & ~0xC) | 3;
+ p[3] = p[2];
+ o->directionKeyMask = 4;
+ o->actionKeyMask = 0;
+ }
+ } else {
+ --p[3];
+ o->directionKeyMask = 0;
+ o->actionKeyMask = 0;
+ }
+ }
+ } else {
+ num = p[1];
+ if ((p[1] | p[2]) != 0) {
+ uint8_t _dl = p[0] >> 4;
+ if (_cl != _dl) {
+ uint8_t _al = (p[0] & 0xF0) | _dl;
+ p[0] = _al;
+ if (_al & 0xF0) {
+ p[3] = p[1];
+ } else {
+ p[3] = p[2];
+ }
+ }
+ if (p[3] == 0) {
+ if (p[0] & 0xF) {
+ o->directionKeyMask = 1;
+ _mstAndyVarMask &= ~mask;
+ } else {
+ o->directionKeyMask = 4;
+ _mstAndyVarMask |= mask;
+ }
+ _mstLevelGatesMask |= mask;
+ } else {
+ --p[3];
+ o->actionKeyMask = 0;
+ o->directionKeyMask = 0;
+ }
+ } else {
+ uint8_t _dl = p[0] >> 4;
+ if (_cl != _dl) {
+ if (p[3] != 0) {
+ --p[3];
+ } else {
+ uint8_t _al = (p[0] & 0xF0) | _dl;
+ p[0] = _al;
+ if (_al & 0xF0) {
+ o->directionKeyMask = 1;
+ _mstAndyVarMask &= ~mask;
+ } else {
+ o->directionKeyMask = 4;
+ _mstAndyVarMask |= mask;
+ }
+ _mstLevelGatesMask |= mask;
+ if (o->screenNum != _currentScreen && o->screenNum != _currentLeftScreen && o->screenNum != _currentRightScreen) {
+ o->actionKeyMask = 1;
+ } else {
+ o->actionKeyMask = 0;
+ }
+ }
+ }
+ }
+ }
+ int y1 = o->yPos + o->posTable[1].y; // ve
+ int h1 = o->posTable[1].y - o->posTable[2].y - 7; // vc
+ int x1 = o->xPos + o->posTable[1].x; // vd
+ if (x1 < 0) {
+ x1 = 0;
+ }
+ if (y1 < 0) {
+ y1 = 0;
+ }
+ uint32_t offset = screenMaskOffset(_res->_screensBasePos[o->screenNum].u + x1, _res->_screensBasePos[o->screenNum].v + y1);
+ if (h1 < 0) {
+ h1 = -h1;
+ for (int i = 0; i < h1 / 8; ++i) {
+ memset(_screenMaskBuffer + offset, 0, 4);
+ offset += 512;
+ }
+ } else {
+ for (int i = 0; i < h1 / 8; ++i) {
+ memset(_screenMaskBuffer + offset, 2, 4);
+ offset += 512;
+ }
+ }
+ if (o->screenNum == _currentScreen || (o->screenNum == _currentRightScreen && _res->_resLevelData0x2B88SizeTable[_currentRightScreen] != 0) || (o->screenNum == _currentLeftScreen && _res->_resLevelData0x2B88SizeTable[_currentLeftScreen] != 0)) {
+ if (o->levelData0x2988) {
+ updateAndyObject(o);
+ }
+ }
+ int y2 = o->yPos + o->posTable[1].y; // vb
+ int h2 = o->posTable[2].y - o->posTable[1].y + 7; // vc
+ int x2 = o->xPos + o->posTable[1].x; // vd
+ if (x2 < 0) {
+ x2 = 0;
+ }
+ if (y2 < 0) {
+ y2 = 0;
+ }
+ offset = screenMaskOffset(_res->_screensBasePos[o->screenNum].u + x2, _res->_screensBasePos[o->screenNum].v + y2);
+ if (h2 < 0) {
+ h2 = -h2;
+ for (int i = 0; i < h2 / 8; ++i) {
+ memset(_screenMaskBuffer + offset, 0, 4);
+ offset += 512;
+ }
+ } else {
+ for (int i = 0; i < h2 / 8; ++i) {
+ memset(_screenMaskBuffer + offset, 2, 4);
+ offset += 512;
+ }
+ }
+ // gate closing on Andy
+ if ((_cheats & kCheatGateNoCrush) == 0 && o->screenNum == _res->_currentScreenResourceNum && o->directionKeyMask == 4) {
+ if ((o->flags0 & 0x1F) == 1 && (o->flags0 & 0xE0) == 0x40) {
+ if (!_hideAndyObjectFlag && (_mstFlags & 0x80000000) == 0) {
+ if (clipLvlObjectsBoundingBox(_andyObject, o, 132)) {
+ setAndySpecialAnimation(0xA1);
+ }
+ }
+ }
+ }
+}
+
+void Game::updateSwitchesLar(int count, uint8_t *switchesData, BoundingBox *switchesBoundingBox, uint8_t *gatesData) {
+ for (int i = 0; i < count; ++i) {
+ switchesData[i * 4 + 1] &= ~0x40;
+ }
+ for (int i = 0; i < count; ++i) {
+ if (_andyObject->screenNum == switchesData[i * 4]) {
+ if ((switchesData[i * 4 + 1] & 0x10) == 0x10) { // can be actioned by a spectre
+ updateSwitchesLar_checkSpectre(i, &switchesData[i * 4], &switchesBoundingBox[i], gatesData);
+ }
+ AndyLvlObjectData *data = (AndyLvlObjectData *)getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ updateSwitchesLar_checkAndy(i, &switchesData[i * 4], &data->boundingBox, &switchesBoundingBox[i], gatesData);
+ }
+ }
+ for (int i = 0; i < count; ++i) {
+ const uint8_t _al = switchesData[i * 4 + 1];
+ const uint8_t _dl = _al & 0x40;
+ if (_dl == 0 && (_al & 0x80) == 0) {
+ switchesData[i * 4 + 1] |= 0x80;
+ } else if (_dl != 0 && (_al & 4) != 0) {
+ switchesData[i * 4 + 1] &= ~1;
+ }
+ }
+}
+
+void Game::updateSwitchesLar_checkSpectre(int num, uint8_t *p, BoundingBox *r, uint8_t *gatesData) {
+ bool found = false;
+ for (LvlObject *o = _lvlObjectsList1; o && !found; o = o->nextPtr) {
+ if (o->screenNum != p[0]) {
+ continue;
+ }
+ if (!((o->spriteNum >= 11 && o->spriteNum <= 13) || o->spriteNum == 16)) {
+ // not a spectre
+ continue;
+ }
+ BoundingBox b;
+ b.x1 = o->xPos;
+ b.y1 = o->yPos;
+ b.x2 = b.x1 + o->width - 1;
+ b.y2 = b.y1 + o->height - 1;
+ uint8_t *vf;
+ if ((p[1] & 0x40) == 0 && clipBoundingBox(r, &b)) {
+ found = true;
+ if ((p[2] & 0x80) == 0 && !updateSwitchesLar_toggle(true, p[2], p[0], num, (p[1] >> 5) & 1, r)) {
+ continue;
+ }
+ p[1] |= 0x40;
+ if ((p[1] & 0x8) != 0) {
+ continue;
+ }
+ vf = &gatesData[p[3] * 4];
+ uint8_t _al = (p[1] >> 1) & 1;
+ uint8_t _bl = (vf[0] >> 4);
+ if (_bl != _al) {
+ _bl = (_al << 4) | (vf[0] & 15);
+ vf[0] = _bl;
+ const uint8_t _cl = (p[1] >> 5) & 1;
+ if (_cl == 1 && _al == _cl) {
+ vf[3] = 4;
+ }
+ }
+ } else {
+ if ((p[1] & 0xC) == 0 && (p[1] & 0x80) != 0) {
+ vf = &gatesData[p[3] * 4];
+ uint8_t _al = ((~p[1]) >> 1) & 1;
+ uint8_t _bl = (vf[0] >> 4);
+ if (_bl != _al) {
+ _bl = (_al << 4) | (vf[0] & 15);
+ vf[0] = _bl;
+ const uint8_t _cl = (p[1] >> 5) & 1;
+ if (_cl == 1 && _al == _cl) {
+ vf[3] = 4;
+ }
+ }
+ }
+ }
+ }
+}
+
+int Game::updateSwitchesLar_checkAndy(int num, uint8_t *p, BoundingBox *b1, BoundingBox *b2, uint8_t *gatesData) {
+ int ret = 0;
+ //const uint8_t flags = _andyObject->flags0 & 0x1F;
+ if ((p[1] & 0x40) == 0 && (ret = clipBoundingBox(b1, b2)) != 0) {
+ if ((p[1] & 1) == 0) { // switch already actioned
+ return ret;
+ }
+ const int flag = (p[1] >> 5) & 1;
+ uint8_t _al = clipAndyLvlObjectLar(b1, b2, flag);
+ _al = updateSwitchesLar_toggle(_al, p[2], p[0], num, flag, b2);
+ _al = ((_al & 1) << 6) | (p[1] & ~0x40);
+ p[1] = _al;
+ if ((_al & 0x40) == 0) {
+ return ret;
+ }
+ ret = 1;
+ if ((_al & 8) != 0) {
+ const int mask = (_al & 2) != 0 ? p[3] : -p[3];
+ updateGateMaskLar(mask);
+ return ret;
+ }
+ const uint8_t _cl = (_al >> 5) & 1;
+ _al = (_al >> 1) & 1;
+ p = &gatesData[p[3] * 4];
+ uint8_t _bl = p[0] >> 4;
+ if (_bl != _al) {
+ _bl = (_al << 4) | (p[0] & 0xF);
+ p[0] = _bl;
+ if (_cl == 1 && _al == _cl) {
+ p[3] = 4;
+ }
+ }
+ return ret;
+ }
+ if ((p[1] & 0xC) == 0 && (p[1] & 0x80) != 0) {
+ p = &gatesData[p[3] * 4];
+ const uint8_t _cl = (p[1] >> 5) & 1;
+ uint8_t _al = ((~p[1]) >> 1) & 1;
+
+ uint8_t _bl = p[0] >> 4;
+ if (_bl != _al) {
+ _bl = (_al << 4) | (p[0] & 0xF);
+ p[0] = _bl;
+ if (_cl == 1 && _al == _cl) {
+ p[3] = 4;
+ }
+ }
+ }
+ return ret;
+}
+
+int Game::updateSwitchesLar_toggle(bool flag, uint8_t dataNum, int screenNum, int switchNum, int anim, const BoundingBox *box) {
+ uint8_t _al = (_andyObject->flags0 >> 5) & 7;
+ uint8_t _cl = (_andyObject->flags0 & 0x1F);
+ int ret = 0; // _bl
+ if ((dataNum & 0x80) == 0) {
+ const int dy = box->y2 - box->y1;
+ const int dx = box->x2 - box->x1;
+ if (dx >= dy) {
+ ret = 1;
+ } else {
+ const uint8_t _dl = ((_andyObject->flags1) >> 4) & 3;
+ if (anim != _dl) {
+ return 0;
+ }
+ if (_cl == 3 || _cl == 5 || _cl == 2 || _al == 2 || _cl == 0 || _al == 7) {
+ ret = 1;
+ } else {
+ setAndySpecialAnimation(3);
+ }
+ }
+ } else {
+ ret = 1;
+ }
+ if (ret) {
+ if (!flag) {
+ ret = (_andyObject->anim == 224) ? 1 : 0;
+ }
+ if (ret) {
+ LvlObject *o = findLvlObject2(0, dataNum, screenNum);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ }
+ }
+ return ret;
+}
+
+void Game::dumpSwitchesLar(int switchesCount, const uint8_t *switchesData, const BoundingBox *switchesBoundingBox, int gatesCount, const uint8_t *gatesData) {
+ fprintf(stdout, "_mstAndyVarMask 0x%x _mstLevelGatesMask 0x%x\n", _mstAndyVarMask, _mstLevelGatesMask);
+ for (int i = 0; i < gatesCount; ++i) {
+ const uint8_t *p = gatesData + i * 4;
+ fprintf(stdout, "gate %2d: state 0x%02x\n", i, p[0]);
+ }
+ for (int i = 0; i < switchesCount; ++i) {
+ const uint8_t *p = switchesData + i * 4;
+ const BoundingBox *b = &switchesBoundingBox[i];
+ fprintf(stdout, "switch %2d: screen %2d (%3d,%3d,%3d,%3d) flags 0x%02x sprite %2d gate %2d\n", i, p[0], b->x1, b->y1, b->x2, b->y2, p[1], (int8_t)p[2], p[3]);
+ }
+}
+
+void Game::updateScreenMaskLar(uint8_t *p, uint8_t flag) {
+ if (p[1] != flag) {
+ p[1] = flag;
+ const uint8_t screenNum = p[4];
+ uint32_t maskOffset = screenMaskOffset(_res->_screensBasePos[screenNum].u + p[2], _res->_screensBasePos[screenNum].v + p[3]);
+ uint8_t *dst = _screenMaskBuffer + maskOffset;
+ assert(p[0] < 6);
+ const int16_t *src = _lar_screenMaskOffsets[p[0]];
+ const int count = *src++;
+ if (!flag) {
+ for (int i = 0; i < count; ++i) {
+ const int16_t offset = *src++;
+ dst += offset;
+ *dst &= ~8;
+ }
+ } else {
+ for (int i = 0; i < count; ++i) {
+ const int16_t offset = *src++;
+ dst += offset;
+ *dst |= 8;
+ }
+ }
+ }
+}
+
+void Game::updateGateMaskLar(int num) {
+ int offset, type;
+ if (num < 0) {
+ offset = -num * 6;
+ updateScreenMaskLar(_lar1_maskData + offset, 0);
+ type = 5;
+ } else {
+ offset = num * 6;
+ updateScreenMaskLar(_lar1_maskData + offset, 1);
+ type = 2;
+ }
+ LvlObject *o = findLvlObject2(0, _lar1_maskData[offset + 5], _lar1_maskData[offset + 4]);
+ if (o) {
+ o->objectUpdateType = type;
+ }
+}
+
+int Game::clipAndyLvlObjectLar(BoundingBox *a /* unused */, BoundingBox *b, bool flag) {
+ int ret = 0;
+ const uint8_t flags = _andyObject->flags0 & 0x1F;
+ if (flags != 3 && flags != 5 && !flag) {
+ BoundingBox b1;
+ b1.x1 = _andyObject->xPos + _andyObject->posTable[5].x - 3;
+ b1.x2 = b1.x1 + 6;
+ b1.y1 = _andyObject->yPos + _andyObject->posTable[5].y - 3;
+ b1.y2 = b1.y1 + 6;
+ ret = clipBoundingBox(&b1, b);
+ if (!ret) {
+ BoundingBox b2;
+ b2.x1 = _andyObject->xPos + _andyObject->posTable[4].x - 3;
+ b2.x2 = b2.x1 + 6;
+ b2.y1 = _andyObject->yPos + _andyObject->posTable[4].y - 3;
+ b2.y2 = b2.y1 + 6;
+ ret = clipBoundingBox(&b2, b);
+ }
+ }
+ return ret;
+}
+
+void Game::resetWormHoleSprites() {
+ memset(_wormHoleSpritesTable, 0, sizeof(_wormHoleSpritesTable));
+ _wormHoleSpritesCount = 0;
+}
+
+void Game::updateWormHoleSprites() {
+ const uint8_t screenNum = _res->_currentScreenResourceNum;
+ WormHoleSprite *spr = 0;
+ for (int i = 0; i < _wormHoleSpritesCount; ++i) {
+ if (_wormHoleSpritesTable[i].screenNum == screenNum) {
+ spr = &_wormHoleSpritesTable[i];
+ break;
+ }
+ }
+ if (!spr) {
+ return;
+ }
+ LvlObject tmp;
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.screenNum = screenNum;
+ tmp.flags2 = 0x1001;
+ tmp.type = 8;
+ tmp.spriteNum = 20;
+ _res->incLvlSpriteDataRefCounter(&tmp);
+ uint32_t yOffset = 0;
+ for (int i = 0; i < 6; ++i) {
+ const uint32_t *flags = &spr->flags[i];
+ if (*flags != 0) {
+ int xOffset = 0;
+ for (int j = 0; j < 11; ++j) {
+ uint8_t _al = (*flags >> (j * 2)) & 3;
+ if (_al != 0 && _spritesNextPtr) {
+ const int xPos = spr->xPos + xOffset + 12;
+ const int yPos = spr->yPos + yOffset + 16;
+ if (rect_contains(spr->rect1_x1, spr->rect1_y1, spr->rect1_x2, spr->rect1_y2, xPos, yPos)) {
+ _al += 3;
+ } else if (rect_contains(spr->rect2_x1, spr->rect2_y1, spr->rect2_x2, spr->rect2_y2, xPos, yPos)) {
+ _al += 6;
+ }
+ tmp.anim = _al;
+ setupLvlObjectBitmap(&tmp);
+ setLvlObjectPosRelativeToPoint(&tmp, 7, xPos, yPos);
+ if (j & 1) {
+ tmp.yPos -= 16;
+ }
+ if (tmp.bitmapBits) {
+ Sprite *spr = _spritesNextPtr;
+ spr->xPos = tmp.xPos;
+ spr->yPos = tmp.yPos;
+ spr->w = tmp.width;
+ spr->h = tmp.height;
+ spr->bitmapBits = tmp.bitmapBits;
+ spr->num = tmp.flags2 & 0x3FFF;
+ addToSpriteList(spr);
+ }
+ }
+ xOffset += 24;
+ }
+ }
+ yOffset += 32;
+ }
+ _res->decLvlSpriteDataRefCounter(&tmp);
+}
+
+bool Game::loadSetupCfg(bool resume) {
+ _resumeGame = resume;
+ if (_res->readSetupCfg(&_setupConfig)) {
+ return true;
+ }
+ memset(&_setupConfig, 0, sizeof(_setupConfig));
+ return false;
+}
+
+void Game::saveSetupCfg() {
+ const int num = _setupConfig.currentPlayer;
+ if (_currentLevelCheckpoint > _setupConfig.players[num].progress[_currentLevel]) {
+ _setupConfig.players[num].progress[_currentLevel] = _currentLevelCheckpoint;
+ }
+ _setupConfig.players[num].levelNum = _currentLevel;
+ _setupConfig.players[num].checkpointNum = _currentLevelCheckpoint;
+ _setupConfig.players[num].cutscenesMask = _paf->_playedMask;
+ _setupConfig.players[num].difficulty = _difficulty;
+ _setupConfig.players[num].volume = _snd_masterVolume;
+ if (_currentLevel > _setupConfig.players[num].lastLevelNum) {
+ _setupConfig.players[num].lastLevelNum = _currentLevel;
+ }
+ _res->writeSetupCfg(&_setupConfig);
+}
+
+void Game::captureScreenshot() {
+ static int screenshot = 1;
+
+ char name[64];
+ snprintf(name, sizeof(name), "screenshot-%03d.bmp", screenshot);
+ FILE *fp = _fs.openSaveFile(name, true);
+ if (fp) {
+ saveBMP(fp, _video->_frontLayer, _video->_palette, Video::W, Video::H);
+ _fs.closeFile(fp);
+ }
+ if (_cheats != 0) {
+ snprintf(name, sizeof(name), "screenshot-%03d-background.bmp", screenshot);
+ fp = _fs.openSaveFile(name, true);
+ if (fp) {
+ saveBMP(fp, _video->_backgroundLayer, _video->_palette, Video::W, Video::H);
+ _fs.closeFile(fp);
+ }
+ snprintf(name, sizeof(name), "screenshot-%03d-shadow.bmp", screenshot);
+ fp = _fs.openSaveFile(name, true);
+ if (fp) {
+ saveBMP(fp, _video->_shadowLayer, _video->_palette, Video::W, Video::H);
+ _fs.closeFile(fp);
+ }
+ snprintf(name, sizeof(name), "screenshot-%03d-palette.bmp", screenshot);
+ fp = _fs.openSaveFile(name, true);
+ if (fp) {
+ static const int kPaletteRectSize = 8;
+ uint8_t paletteBuffer[8 * 256 * 8];
+ for (int x = 0; x < 256; ++x) {
+ const int xOffset = x * kPaletteRectSize;
+ for (int y = 0; y < kPaletteRectSize; ++y) {
+ memset(paletteBuffer + xOffset + y * 256 * kPaletteRectSize, x, kPaletteRectSize);
+ }
+ }
+ saveBMP(fp, paletteBuffer, _video->_palette, 256 * kPaletteRectSize, kPaletteRectSize);
+ _fs.closeFile(fp);
+ }
+ snprintf(name, sizeof(name), "screenshot-%03d-postable.txt", screenshot);
+ fp = _fs.openSaveFile(name, true);
+ if (fp) {
+ for (int y = 0; y < 24; ++y) {
+ for (int x = 0; x < 32; ++x) {
+ fprintf(fp, "%02x ", _screenPosTable[4][y * 32 + x]);
+ }
+ fputc('\n', fp);
+ }
+ _fs.closeFile(fp);
+ }
+ snprintf(name, sizeof(name), "screenshot-%03d-mask.txt", screenshot);
+ fp = _fs.openSaveFile(name, true);
+ if (fp) {
+ for (int y = 0; y < 24; ++y) {
+ for (int x = 0; x < 32; ++x) {
+ fprintf(fp, "%02x ", _screenTempMaskBuffer[y * 32 + x]);
+ }
+ fputc('\n', fp);
+ }
+ _fs.closeFile(fp);
+ }
+ }
+ ++screenshot;
+}
diff --git a/Src/Vita_&_Switch/game.h b/Src/Vita_&_Switch/game.h
new file mode 100644
index 0000000..952c9f0
--- /dev/null
+++ b/Src/Vita_&_Switch/game.h
@@ -0,0 +1,572 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef GAME_H__
+#define GAME_H__
+
+#include "intern.h"
+#include "defs.h"
+#include "fileio.h"
+#include "fs.h"
+#include "mixer.h"
+#include "random.h"
+#include "resource.h"
+
+struct Game;
+struct Level;
+struct PafPlayer;
+struct Video;
+
+struct CheckpointData {
+ int16_t xPos;
+ int16_t yPos;
+ uint16_t flags2;
+ uint16_t anim;
+ uint8_t screenNum;
+ uint8_t spriteNum;
+};
+
+enum {
+ kObjectDataTypeAndy,
+ kObjectDataTypeAnimBackgroundData,
+ kObjectDataTypeShoot,
+ kObjectDataTypeLvlBackgroundSound,
+ kObjectDataTypeMonster1,
+ kObjectDataTypeMonster2
+};
+
+enum {
+ kCheatSpectreFireballNoHit = 1 << 0,
+ kCheatOneHitPlasmaCannon = 1 << 1,
+ kCheatOneHitSpecialPowers = 1 << 2,
+ kCheatWalkOnLava = 1 << 3,
+ kCheatGateNoCrush = 1 << 4
+};
+
+struct Game {
+ enum {
+ kMaxTasks = 128,
+ kMaxVars = 40,
+ kMaxLocals = 8,
+ kMaxAndyShoots = 8,
+ kMaxShootLvlObjectData = 32,
+ kMaxSssObjects = 32,
+ kMaxMonsterObjects1 = 32,
+ kMaxMonsterObjects2 = 64,
+ kMaxBackgroundAnims = 64,
+ kMaxSprites = 128,
+ kMaxLvlObjects = 160,
+ kMaxBoundingBoxes = 64,
+
+ kDefaultSoundPanning = 64,
+ kDefaultSoundVolume = 128,
+
+ kFrameTimeStamp = 50 // original is 80ms (12.5hz)
+ };
+
+ static const uint8_t _specialPowersDxDyTable[];
+ static const uint8_t _pointDstIndexTable[];
+ static const uint8_t _pointRandomizeShiftTable[];
+ static const uint8_t _pointSrcIndex2Table[];
+ static const uint8_t _pointSrcIndex1Table[];
+ static const uint8_t _actionDirectionKeyMaskTable[];
+ static const uint8_t _pwr1_screenTransformData[];
+ static const uint8_t _pwr2_screenTransformData[];
+ static const uint8_t _pwr1_screenTransformLut[];
+ static const uint8_t _lava_screenTransformLut[];
+ static const uint8_t _pwr2_screenTransformLut[];
+ static const uint8_t _pwr1_spritesData[];
+ static const uint8_t _isld_spritesData[];
+ static const uint8_t _lava_spritesData[];
+ static const uint8_t _lar1_spritesData[];
+ static const int16_t *_lar_screenMaskOffsets[];
+ static uint8_t _lar1_maskData[];
+
+ FileSystem _fs;
+ Level *_level;
+ Mixer _mix;
+ PafPlayer *_paf;
+ Random _rnd;
+ Resource *_res;
+ Video *_video;
+ uint32_t _cheats;
+ int _frameMs;
+ int _difficulty;
+ bool _loadingScreenEnabled;
+
+ SetupConfig _setupConfig;
+ bool _playDemo;
+ bool _resumeGame;
+
+ LvlObject *_screenLvlObjectsList[kMaxScreens]; // LvlObject linked list for each screen
+ LvlObject *_andyObject;
+ LvlObject *_plasmaExplosionObject;
+ LvlObject *_plasmaCannonObject;
+ LvlObject *_specialAnimLvlObject;
+ LvlObject *_currentSoundLvlObject;
+ int _currentLevel;
+ int _currentLevelCheckpoint;
+ bool _endLevel;
+ Sprite _spritesTable[kMaxSprites];
+ Sprite *_spritesNextPtr; // pointer to the next free entry
+ Sprite *_typeSpritesList[kMaxSpriteTypes];
+ uint8_t _directionKeyMask;
+ uint8_t _actionKeyMask;
+ uint8_t _currentRightScreen;
+ uint8_t _currentLeftScreen;
+ uint8_t _currentScreen;
+ int8_t _levelRestartCounter;
+ bool _fadePalette;
+ bool _hideAndyObjectFlag;
+ ShootLvlObjectData _shootLvlObjectDataTable[kMaxShootLvlObjectData];
+ ShootLvlObjectData *_shootLvlObjectDataNextPtr; // pointer to the next free entry
+ LvlObject *_lvlObjectsList0;
+ LvlObject *_lvlObjectsList1;
+ LvlObject *_lvlObjectsList2;
+ LvlObject *_lvlObjectsList3;
+ uint8_t _screenPosTable[5][24 * 32];
+ uint8_t _screenTempMaskBuffer[24 * 32];
+ uint8_t _screenMaskBuffer[(16 * 6) * 24 * 32]; // level screens mask : 16 horizontal screens x 6 vertical screens
+ int _mstAndyCurrentScreenNum;
+ uint8_t _shakeScreenDuration;
+ const uint8_t *_shakeScreenTable;
+ uint8_t _plasmaCannonDirection;
+ uint8_t _plasmaCannonPrevDirection;
+ uint8_t _plasmaCannonPointsSetupCounter;
+ uint8_t _plasmaCannonLastIndex1;
+ bool _plasmaCannonExplodeFlag;
+ uint8_t _plasmaCannonPointsMask;
+ uint8_t _plasmaCannonFirstIndex;
+ uint8_t _plasmaCannonLastIndex2;
+ uint8_t _plasmaCannonFlags;
+ uint8_t _actionDirectionKeyMaskCounter;
+ bool _fallingAndyFlag;
+ uint8_t _fallingAndyCounter;
+ uint8_t _actionDirectionKeyMaskIndex;
+ uint8_t _andyActionKeyMaskAnd, _andyActionKeyMaskOr;
+ uint8_t _andyDirectionKeyMaskAnd, _andyDirectionKeyMaskOr;
+ int32_t _plasmaCannonPosX[129];
+ int32_t _plasmaCannonPosY[129];
+ int32_t _plasmaCannonXPointsTable1[129];
+ int32_t _plasmaCannonYPointsTable1[129];
+ int32_t _plasmaCannonXPointsTable2[127];
+ int32_t _plasmaCannonYPointsTable2[127];
+ ScreenMask _shadowScreenMasksTable[8];
+
+ uint16_t _mstCurrentAnim;
+ uint16_t _specialAnimMask;
+ int16_t _mstOriginPosX;
+ int16_t _mstOriginPosY;
+ bool _specialAnimFlag;
+ AndyShootData _andyShootsTable[kMaxAndyShoots];
+ int _andyShootsCount;
+ uint8_t _mstOp68_type, _mstOp68_arg9, _mstOp67_type;
+ uint8_t _mstOp67_flags1;
+ uint16_t _mstOp67_unk;
+ int _mstOp67_x1, _mstOp67_x2, _mstOp67_y1, _mstOp67_y2;
+ int8_t _mstOp67_screenNum;
+ uint16_t _mstOp68_flags1;
+ int _mstOp68_x1, _mstOp68_x2, _mstOp68_y1, _mstOp68_y2;
+ int8_t _mstOp68_screenNum;
+ uint32_t _mstLevelGatesMask;
+ int _runTaskOpcodesCount;
+ int32_t _mstVars[kMaxVars];
+ uint32_t _mstFlags;
+ int _clipBoxOffsetX, _clipBoxOffsetY;
+ Task *_currentTask;
+ int _mstOp54Counter;
+ int _mstOp56Counter;
+ uint8_t _mstOp54Table[32];
+ bool _mstDisabled;
+ LvlObject _declaredLvlObjectsList[kMaxLvlObjects];
+ LvlObject *_declaredLvlObjectsNextPtr; // pointer to the next free entry
+ int _declaredLvlObjectsListCount;
+ AndyLvlObjectData _andyObjectScreenData;
+ AnimBackgroundData _animBackgroundDataTable[kMaxBackgroundAnims];
+ int _animBackgroundDataCount;
+ uint8_t _andyActionKeysFlags;
+ int _executeMstLogicCounter;
+ int _executeMstLogicPrevCounter;
+ Task _tasksTable[kMaxTasks];
+ Task *_tasksList;
+ Task *_monsterObjects1TasksList;
+ Task *_monsterObjects2TasksList;
+ int _mstAndyLevelPrevPosX;
+ int _mstAndyLevelPrevPosY;
+ int _mstAndyLevelPosX;
+ int _mstAndyLevelPosY;
+ int _mstAndyScreenPosX;
+ int _mstAndyScreenPosY;
+ int _mstAndyRectNum;
+ int _m43Num1;
+ int _m43Num2;
+ int _m43Num3;
+ int _xMstPos1, _yMstPos1;
+ int _xMstPos2, _yMstPos2; // xMstDist1, yMstDist1
+ int _xMstPos3, _yMstPos3;
+ int _mstHelper1Count;
+ int _mstActionNum;
+ uint32_t _mstAndyVarMask;
+ int _mstChasingMonstersCount;
+ int _mstPosXmin, _mstPosXmax;
+ int _mstPosYmin, _mstPosYmax;
+ int _mstTemp_x1, _mstTemp_x2, _mstTemp_y1, _mstTemp_y2;
+ MonsterObject1 _monsterObjects1Table[kMaxMonsterObjects1];
+ MonsterObject2 _monsterObjects2Table[kMaxMonsterObjects2];
+ int _mstTickDelay;
+ uint8_t _mstCurrentActionKeyMask;
+ int _mstCurrentPosX, _mstCurrentPosY;
+ int _mstBoundingBoxesCount;
+ MstBoundingBox _mstBoundingBoxesTable[kMaxBoundingBoxes];
+ Task *_mstCurrentTask;
+ MstCollision _mstCollisionTable[2][32]; // 0:facingRight, 1:facingLeft
+ int _wormHoleSpritesCount;
+ WormHoleSprite _wormHoleSpritesTable[6];
+
+ Game(const char *dataPath, const char *savePath, uint32_t cheats);
+ ~Game();
+
+ // 32*24 pitch=512
+ static uint32_t screenMaskOffset(int x, int y) {
+ return ((y << 6) & ~511) + (x >> 3);
+ // return ((y & ~7) << 6) + (x >> 3)
+ }
+
+ // 32*24 pitch=32
+ static uint32_t screenGridOffset(int x, int y) {
+ return ((y & ~7) << 2) + (x >> 3);
+ }
+
+ // benchmark.cpp
+ uint32_t benchmarkLoop(const uint8_t *p, int count);
+ uint32_t benchmarkCpu();
+
+ // game.cpp
+ void mainLoop(int level, int checkpoint, bool levelChanged);
+ void mixAudio(int16_t *buf, int len);
+ void resetShootLvlObjectDataTable();
+ void clearShootLvlObjectData(LvlObject *ptr);
+ void setShakeScreen(int type, int counter);
+ void fadeScreenPalette();
+ void shakeScreen();
+ void transformShadowLayer(int delta);
+ void loadTransformLayerData(const uint8_t *data);
+ void unloadTransformLayerData();
+ void decodeShadowScreenMask(LvlBackgroundData *lvl);
+ void playSound(int num, LvlObject *ptr, int a, int b);
+ void removeSound(LvlObject *ptr);
+ void setupBackgroundBitmap();
+ void addToSpriteList(Sprite *spr);
+ void addToSpriteList(LvlObject *ptr);
+ int16_t calcScreenMaskDy(int16_t xPos, int16_t yPos, int num);
+ void setupScreenPosTable(uint8_t num);
+ void setupScreenMask(uint8_t num);
+ void resetScreenMask();
+ void setScreenMaskRectHelper(int x1, int y1, int x2, int y2, int screenNum);
+ void setScreenMaskRect(int x1, int y1, int x2, int y2, int pos);
+ void updateScreenMaskBuffer(int x, int y, int type);
+ void setupLvlObjectBitmap(LvlObject *ptr);
+ void randomizeInterpolatePoints(int32_t *pts, int count);
+ int fixPlasmaCannonPointsScreenMask(int num);
+ void setupPlasmaCannonPointsHelper();
+ void destroyLvlObjectPlasmaExplosion(LvlObject *o);
+ void shuffleArray(uint8_t *p, int count);
+ void destroyLvlObject(LvlObject *o);
+ void setupPlasmaCannonPoints(LvlObject *ptr);
+ int testPlasmaCannonPointsDirection(int x1, int y1, int x2, int y2);
+ void preloadLevelScreenData(uint8_t num, uint8_t prev);
+ void setLvlObjectPosRelativeToObject(LvlObject *ptr1, int num1, LvlObject *ptr2, int num2);
+ void setLvlObjectPosRelativeToPoint(LvlObject *ptr, int num, int x, int y);
+ void clearLvlObjectsList0();
+ void clearLvlObjectsList1();
+ void clearLvlObjectsList2();
+ void clearLvlObjectsList3();
+ LvlObject *addLvlObjectToList0(int num);
+ LvlObject *addLvlObjectToList1(int type, int num);
+ LvlObject *addLvlObjectToList2(int num);
+ LvlObject *addLvlObjectToList3(int num);
+ void removeLvlObject(LvlObject *ptr);
+ void removeLvlObject2(LvlObject *o);
+ void setAndySprite(int num);
+ void setupAndyLvlObject();
+ void setupScreenLvlObjects(int num);
+ void resetDisplay();
+ void setupScreen(uint8_t num);
+ void resetScreen();
+ void restartLevel();
+ void playAndyFallingCutscene(int type);
+ int8_t updateLvlObjectScreen(LvlObject *ptr);
+ void setAndyAnimationForArea(BoundingBox *box, int dx);
+ void setAndyLvlObjectPlasmaCannonKeyMask();
+ int setAndySpecialAnimation(uint8_t mask);
+ int clipBoundingBox(BoundingBox *coords, BoundingBox *box);
+ int updateBoundingBoxClippingOffset(BoundingBox *_ecx, BoundingBox *_ebp, const uint8_t *coords, int direction);
+ int clipLvlObjectsBoundingBoxHelper(LvlObject *o1, BoundingBox *box1, LvlObject *o2, BoundingBox *box2);
+ int clipLvlObjectsBoundingBox(LvlObject *o, LvlObject *ptr, int count);
+ int clipLvlObjectsSmall(LvlObject *o, LvlObject *ptr, int count);
+ int restoreAndyCollidesLava();
+ int updateAndyLvlObject();
+ void drawPlasmaCannon();
+ void drawScreen();
+ void updateLvlObjectList(LvlObject **list);
+ void updateLvlObjectLists();
+ LvlObject *updateAnimatedLvlObjectType0(LvlObject *ptr);
+ LvlObject *updateAnimatedLvlObjectType1(LvlObject *ptr);
+ LvlObject *updateAnimatedLvlObjectType2(LvlObject *ptr);
+ LvlObject *updateAnimatedLvlObjectTypeDefault(LvlObject *ptr);
+ LvlObject *updateAnimatedLvlObject(LvlObject *o);
+ void updateAnimatedLvlObjectsLeftRightCurrentScreens();
+ void updatePlasmaCannonExplosionLvlObject(LvlObject *ptr);
+ void resetPlasmaCannonState();
+ void updateAndyMonsterObjects();
+ void updateInput();
+ void levelMainLoop();
+ Level *createLevel();
+ void callLevel_postScreenUpdate(int num);
+ void callLevel_preScreenUpdate(int num);
+ void callLevel_initialize();
+ void callLevel_tick();
+ void callLevel_terminate();
+ void displayLoadingScreen();
+ int displayHintScreen(int num, int pause);
+ void prependLvlObjectToList(LvlObject **list, LvlObject *ptr);
+ void removeLvlObjectFromList(LvlObject **list, LvlObject *ptr);
+ void *getLvlObjectDataPtr(LvlObject *o, int type) const;
+ void lvlObjectType0Init(LvlObject *ptr);
+ void lvlObjectType1Init(LvlObject *ptr);
+ void lvlObjectTypeInit(LvlObject *ptr);
+ void lvlObjectType0CallbackHelper1();
+ int calcScreenMaskDx(int x, int y, int num);
+ void lvlObjectType0CallbackBreathBubbles(LvlObject *ptr);
+ void setupSpecialPowers(LvlObject *ptr);
+ int lvlObjectType0Callback(LvlObject *ptr);
+ int lvlObjectType1Callback(LvlObject *ptr);
+ int lvlObjectType7Callback(LvlObject *o);
+ int lvlObjectType8Callback(LvlObject *o);
+ int lvlObjectList3Callback(LvlObject *o);
+ void lvlObjectSpecialPowersCallbackHelper1(LvlObject *o);
+ uint8_t lvlObjectCallbackCollideScreen(LvlObject *o);
+ int lvlObjectSpecialPowersCallback(LvlObject *o);
+ void lvlObjectTypeCallback(LvlObject *o);
+ LvlObject *addLvlObject(int type, int x, int y, int screen, int num, int o_anim, int o_flags1, int o_flags2, int actionKeyMask, int directionKeyMask);
+ int setLvlObjectPosInScreenGrid(LvlObject *o, int pos);
+ LvlObject *declareLvlObject(uint8_t type, uint8_t num);
+ void clearDeclaredLvlObjectsList();
+ void initLvlObjects();
+ void setLvlObjectSprite(LvlObject *ptr, uint8_t _dl, uint8_t num);
+ LvlObject *findLvlObject(uint8_t type, uint8_t spriteNum, int screenNum);
+ LvlObject *findLvlObject2(uint8_t type, uint8_t dataNum, int screenNum);
+ LvlObject *findLvlObjectType2(int spriteNum, int screenNum);
+ LvlObject *findLvlObjectBoundingBox(BoundingBox *box);
+ void setLavaAndyAnimation(int yPos);
+ void updateGatesLar(LvlObject *o, uint8_t *p, int num);
+ void updateSwitchesLar(int count, uint8_t *data, BoundingBox *r, uint8_t *gatesData);
+ void updateSwitchesLar_checkSpectre(int num, uint8_t *p1, BoundingBox *r, uint8_t *gatesData);
+ int updateSwitchesLar_checkAndy(int num, uint8_t *p1, BoundingBox *b, BoundingBox *r, uint8_t *gatesData);
+ int updateSwitchesLar_toggle(bool flag, uint8_t dataNum, int screenNum, int switchNum, int anim, const BoundingBox *switchBoundingBox);
+ void dumpSwitchesLar(int switchesCount, const uint8_t *switchesData, const BoundingBox *switchesBoundingBox, int gatesCount, const uint8_t *gatesData);
+ void updateScreenMaskLar(uint8_t *p, uint8_t flag);
+ void updateGateMaskLar(int num);
+ int clipAndyLvlObjectLar(BoundingBox *a, BoundingBox *b, bool flag);
+ void resetWormHoleSprites();
+ void updateWormHoleSprites();
+ bool loadSetupCfg(bool resume);
+ void saveSetupCfg();
+ void captureScreenshot();
+
+ // level1_rock.cpp
+ int objectUpdate_rock_case0(LvlObject *o);
+ void objectUpdate_rockShadow(LvlObject *ptr, uint8_t *p);
+ bool plasmaCannonHit(LvlObject *ptr);
+ int objectUpdate_rock_case1(LvlObject *o);
+ int objectUpdate_rock_case2(LvlObject *o);
+ int objectUpdate_rock_case3(LvlObject *o);
+ int objectUpdate_rock_case4(LvlObject *o);
+
+ // monsters.cpp
+ void mstMonster1ResetData(MonsterObject1 *m);
+ void mstMonster1ResetWalkPath(MonsterObject1 *m);
+ bool mstUpdateInRange(MstMonsterAction *m48);
+ bool addChasingMonster(MstMonsterAction *m48, uint8_t flag);
+ void mstMonster1ClearChasingMonster(MonsterObject1 *m);
+ void mstTaskSetMonster1BehaviorState(Task *t, MonsterObject1 *m, int num);
+ int mstTaskStopMonsterObject1(Task *t);
+ void mstMonster1SetScreenPosition(MonsterObject1 *m);
+ bool mstMonster1SetWalkingBounds(MonsterObject1 *m);
+ bool mstMonster1UpdateWalkPath(MonsterObject1 *m);
+ void mstMonster2InitFirefly(MonsterObject2 *m);
+ void mstMonster2ResetData(MonsterObject2 *m);
+ uint32_t mstMonster1GetNextWalkCode(MonsterObject1 *m);
+ int mstTaskSetNextWalkCode(Task *t);
+
+ void mstBoundingBoxClear(MonsterObject1 *m, int dir);
+ int mstBoundingBoxCollides1(int num, int x1, int y1, int x2, int y2) const;
+ int mstBoundingBoxUpdate(int num, int monster1Index, int x1, int y1, int x2, int y2);
+ int mstBoundingBoxCollides2(int monster1Index, int x1, int y1, int x2, int y2) const;
+
+ void mstTaskSetMonster2ScreenPosition(Task *t);
+ int getMstDistance(int y, const AndyShootData *p) const;
+ void mstTaskUpdateScreenPosition(Task *t);
+ void shuffleMstMonsterActionIndex(MstMonsterActionIndex *p);
+
+ void initMstCode();
+ void resetMstCode();
+ void startMstCode();
+ void executeMstCode();
+ void mstWalkPathUpdateIndex(MstWalkPath *walkPath, int index);
+ int mstWalkPathUpdateWalkNode(MstWalkPath *walkPath, MstWalkNode *walkNode, int num, int index);
+ void executeMstCodeHelper1();
+ void mstUpdateMonster1ObjectsPosition();
+ void mstLvlObjectSetActionDirection(LvlObject *o, const uint8_t *ptr, uint8_t mask1, uint8_t mask2);
+ void mstMonster1UpdateGoalPosition(MonsterObject1 *m);
+ void mstMonster1MoveTowardsGoal1(MonsterObject1 *m);
+ bool mstMonster1TestGoalDirection(MonsterObject1 *m);
+ void mstMonster1MoveTowardsGoal2(MonsterObject1 *m);
+ int mstTaskStopMonster1(Task *t, MonsterObject1 *m);
+ int mstTaskUpdatePositionActionDirection(Task *t, MonsterObject1 *m);
+ int mstMonster1FindWalkPathRect(MonsterObject1 *m, MstWalkPath *walkPath, int x, int y);
+ bool mstTestActionDirection(MonsterObject1 *m, int num);
+ bool lvlObjectCollidesAndy1(LvlObject *o, int type) const;
+ bool lvlObjectCollidesAndy2(LvlObject *o, int type) const;
+ bool lvlObjectCollidesAndy4(LvlObject *o, int type) const;
+ bool lvlObjectCollidesAndy3(LvlObject *o, int type) const;
+ bool mstCollidesByFlags(MonsterObject1 *m, uint32_t flags);
+ bool mstMonster1Collide(MonsterObject1 *m, const uint8_t *p);
+ int mstUpdateTaskMonsterObject1(Task *t);
+ int mstUpdateTaskMonsterObject2(Task *t);
+ void mstUpdateRefPos();
+ void mstUpdateMonstersRect();
+ void mstRemoveMonsterObject2(Task *t, Task **tasksList);
+ void mstTaskAttack(Task *t, uint32_t codeData, uint8_t flags);
+ void mstRemoveMonsterObject1(Task *t, Task **tasksList);
+ void mstOp26_removeMstTaskScreen(Task **tasksList, int screenNum);
+ void mstOp27_removeMstTaskScreenType(Task **tasksList, int screenNum, int type);
+ int mstOp49_setMovingBounds(int a, int b, int c, int d, int screen, Task *t, int num);
+ void mstOp52();
+ bool mstHasMonsterInRange(const MstMonsterAction *m, uint8_t flag);
+ void mstOp53(MstMonsterAction *m);
+ void mstOp54();
+ int mstOp56_specialAction(Task *t, int code, int num);
+ void mstOp57_addWormHoleSprite(int x, int y, int screenNum);
+ void mstOp58_addLvlObject(Task *t, int num);
+ void mstOp59_addShootSpecialPowers(int x, int y, int screenNum, int state, uint16_t flags);
+ void mstOp59_addShootFireball(int x, int y, int screenNum, int type, int state, uint16_t flags);
+ void mstTaskResetMonster1WalkPath(Task *t);
+ bool mstSetCurrentPos(MonsterObject1 *m, int x, int y);
+ void mstMonster1SetGoalHorizontal(MonsterObject1 *m);
+ void mstResetCollisionTable();
+ void mstTaskRestart(Task *t);
+ bool mstMonster1CheckLevelBounds(MonsterObject1 *m, int x, int y, uint8_t dir);
+ int mstTaskResetMonster1Direction(Task *t);
+ int mstTaskInitMonster1Type1(Task *t);
+ int mstTaskInitMonster1Type2(Task *t, int flag);
+ void mstOp67_addMonster(Task *t, int y1, int y2, int x1, int x2, int screen, int type, int o_flags1, int o_flags2, int arg1C, int arg20, int arg24);
+ void mstOp68_addMonsterGroup(Task *t, const uint8_t *p, int a, int b, int c, int d);
+ int mstTaskSetActionDirection(Task *t, int num, int value);
+
+ // Task list
+ Task *findFreeTask();
+ Task *createTask(const uint8_t *codeData);
+ void updateTask(Task *t, int num, const uint8_t *codeData);
+ void resetTask(Task *t, const uint8_t *codeData);
+ void removeTask(Task **tasksList, Task *t);
+ void appendTask(Task **tasksList, Task *t);
+
+ // Task storage
+ int getTaskVar(Task *t, int index, int type) const;
+ void setTaskVar(Task *t, int index, int type, int value);
+ int getTaskAndyVar(int index, Task *t) const;
+ int getTaskOtherVar(int index, Task *t) const;
+ int getTaskFlag(Task *t, int index, int type) const;
+
+ // Task.run functions
+ int mstTask_main(Task *t);
+ int mstTask_wait1(Task *t);
+ int mstTask_wait2(Task *t);
+ int mstTask_wait3(Task *t);
+ int mstTask_idle(Task *t);
+ int mstTask_mstOp231(Task *t);
+ int mstTask_mstOp232(Task *t);
+ int mstTask_mstOp233(Task *t);
+ int mstTask_mstOp234(Task *t);
+ int mstTask_monsterWait1(Task *t);
+ int mstTask_monsterWait2(Task *t);
+ int mstTask_monsterWait3(Task *t);
+ int mstTask_monsterWait4(Task *t);
+ int mstTask_monsterWait5(Task *t);
+ int mstTask_monsterWait6(Task *t);
+ int mstTask_monsterWait7(Task *t);
+ int mstTask_monsterWait8(Task *t);
+ int mstTask_monsterWait9(Task *t);
+ int mstTask_monsterWait10(Task *t);
+ int mstTask_monsterWait11(Task *t);
+
+ // sound.cpp
+ bool _sssDisabled;
+ bool _snd_muted;
+ int _snd_masterPanning;
+ int _snd_masterVolume;
+ SssObject _sssObjectsTable[kMaxSssObjects];
+ bool _sssObjectsChanged;
+ int _sssObjectsCount;
+ SssObject *_sssObjectsList1; // playing
+ SssObject *_sssObjectsList2; // paused/idle
+ SssObject *_lowRankSssObject;
+ bool _sssUpdatedObjectsTable[kMaxSssObjects];
+ int _playingSssObjectsMax;
+ int _playingSssObjectsCount;
+
+ void muteSound();
+ void unmuteSound();
+ void resetSound();
+ SssObject *findLowestRankSoundObject() const;
+ void removeSoundObjectFromList(SssObject *so);
+ void updateSoundObject(SssObject *so);
+ void sssOp4_removeSounds(uint32_t flags);
+ void sssOp12_removeSounds2(int num, uint8_t source, uint8_t sampleIndex);
+ void sssOp16_resumeSound(SssObject *so);
+ void sssOp17_pauseSound(SssObject *so);
+ const uint8_t *executeSssCode(SssObject *so, const uint8_t *code, bool tempSssObject = false);
+ SssObject *addSoundObject(SssPcm *pcm, int priority, uint32_t flags_a, uint32_t flags_b);
+ void prependSoundObjectToList(SssObject *so);
+ void updateSssGroup2(uint32_t flags);
+ SssObject *createSoundObject(int bankIndex, int sampleIndex, uint32_t flags);
+ SssObject *startSoundObject(int bankIndex, int sampleIndex, uint32_t flags);
+ void playSoundObject(SssInfo *s, int source, int b);
+ void clearSoundObjects();
+ void setLowPrioritySoundObject(SssObject *so);
+ int getSoundObjectPanning(SssObject *so) const;
+ void setSoundObjectPanning(SssObject *so);
+ void expireSoundObjects(uint32_t flags);
+ void mixSoundObjects17640(bool flag);
+ void queueSoundObjectsPcmStride();
+
+ // andy.cpp
+ AndyMoveData _andyMoveData;
+ int32_t _andyPosX;
+ int32_t _andyPosY;
+ uint8_t _andyMoveMask;
+ bool _andyUpdatePositionFlag;
+ const Point16_t *_andyLevelData0x288PosTablePtr;
+ uint8_t _andyMoveState[32];
+ int _andyMaskBufferPos1;
+ int _andyMaskBufferPos2;
+ int _andyMaskBufferPos4;
+ int _andyMaskBufferPos5;
+ int _andyMaskBufferPos3;
+ int _andyMaskBufferPos0;
+ int _andyMaskBufferPos7;
+ int _andyMaskBufferPos6;
+
+ int moveAndyObjectOp1(int op);
+ int moveAndyObjectOp2(int op);
+ int moveAndyObjectOp3(int op);
+ int moveAndyObjectOp4(int op);
+ void setupAndyObjectMoveData(LvlObject *ptr);
+ void setupAndyObjectMoveState();
+ void updateAndyObject(LvlObject *ptr);
+};
+
+#endif // GAME_H__
diff --git a/Src/Vita_&_Switch/intern.h b/Src/Vita_&_Switch/intern.h
new file mode 100644
index 0000000..73df74e
--- /dev/null
+++ b/Src/Vita_&_Switch/intern.h
@@ -0,0 +1,129 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef INTERN_H__
+#define INTERN_H__
+
+#include
+#include
+#include
+#include
+#include
+
+#if defined(_WIN32) || defined(PSP) || defined(__SWITCH__) || defined(__vita__)
+#define le16toh(x) x
+#define le32toh(x) x
+#define htole16(x) x
+#define htole32(x) x
+static const bool kByteSwapData = false; // no byteswap needed on little endian
+#elif defined(__APPLE__)
+#include
+#define le16toh(x) OSSwapLittleToHostInt16(x)
+#define le32toh(x) OSSwapLittleToHostInt32(x)
+#define htole16(x) OSSwapHostToLittleInt16(x)
+#define htole32(x) OSSwapHostToLittleInt32(x)
+#include
+static const bool kByteSwapData = (BYTE_ORDER == BIG_ENDIAN);
+#else
+#if defined(WII) // big endian
+#include
+#define le16toh(x) __bswap16(x)
+#define le32toh(x) __bswap32(x)
+#define htole16(x) __bswap16(x)
+#define htole32(x) __bswap32(x)
+static const bool kByteSwapData = true;
+#else
+#include
+static const bool kByteSwapData = (__BYTE_ORDER == __BIG_ENDIAN);
+#endif
+#endif
+
+#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))
+#define PACKED __attribute__((packed))
+
+inline uint16_t READ_LE_UINT16(const void *ptr) {
+ if (1 && (((uintptr_t)ptr) & 1) != 0) {
+ uint16_t value;
+ memcpy(&value, ptr, sizeof(uint16_t));
+ return le16toh(value);
+ } else {
+ return le16toh(*(const uint16_t *)ptr);
+ }
+}
+
+inline uint32_t READ_LE_UINT32(const void *ptr) {
+ if (1 && (((uintptr_t)ptr) & 3) != 0) {
+ uint32_t value;
+ memcpy(&value, ptr, sizeof(uint32_t));
+ return le32toh(value);
+ } else {
+ return le32toh(*(const uint32_t *)ptr);
+ }
+}
+
+inline void WRITE_LE_UINT16(void *ptr, uint16_t v) {
+ if (1 && (((uintptr_t)ptr) & 1) != 0) {
+ const uint16_t value = htole16(v);
+ memcpy(ptr, &value, sizeof(uint16_t));
+ } else {
+ *((uint16_t *)ptr) = htole16(v);
+ }
+}
+
+inline void WRITE_LE_UINT32(void *ptr, uint32_t v) {
+ if (1 && (((uintptr_t)ptr) & 3) != 0) {
+ const uint32_t value = htole32(v);
+ memcpy(ptr, &value, sizeof(uint32_t));
+ } else {
+ *((uint32_t *)ptr) = htole32(v);
+ }
+}
+
+#undef MIN
+template
+inline T MIN(T v1, T v2) {
+ return (v1 < v2) ? v1 : v2;
+}
+
+#undef MAX
+template
+inline T MAX(T v1, T v2) {
+ return (v1 > v2) ? v1 : v2;
+}
+
+template
+inline T ABS(T t) {
+ return (t < 0) ? -t : t;
+}
+
+template
+inline T CLIP(T t, T tmin, T tmax) {
+ return (t < tmin ? tmin : (t > tmax ? tmax : t));
+}
+
+template
+inline void SWAP(T &a, T &b) {
+ T tmp = a; a = b; b = tmp;
+}
+
+inline uint32_t merge_bits(uint32_t dst, uint32_t src, uint32_t mask) {
+// return (dst & ~mask) | (src & mask);
+ return ((src ^ dst) & mask) ^ dst;
+}
+
+inline bool compare_bits(uint32_t a, uint32_t b, uint32_t mask) {
+// return (a & mask) == (b & mask);
+ return ((a ^ b) & mask) == 0;
+}
+
+inline bool rect_contains(int left, int top, int right, int bottom, int x, int y) {
+ return x >= left && x <= right && y >= top && y <= bottom;
+}
+
+inline bool rect_intersects(int left1, int top1, int right1, int bottom1, int left2, int top2, int right2, int bottom2) {
+ return right2 >= left1 && left2 <= right1 && bottom2 >= top1 && top2 <= bottom1;
+}
+
+#endif // INTERN_H__
diff --git a/Src/Vita_&_Switch/level.h b/Src/Vita_&_Switch/level.h
new file mode 100644
index 0000000..5c69d06
--- /dev/null
+++ b/Src/Vita_&_Switch/level.h
@@ -0,0 +1,53 @@
+
+#ifndef LEVEL_H__
+#define LEVEL_H__
+
+struct CheckpointData;
+struct Game;
+struct LvlObject;
+struct Resource;
+struct PafPlayer;
+struct Video;
+
+struct Level {
+ virtual ~Level() {
+ }
+
+ void setPointers(Game *g, LvlObject *andyObject, PafPlayer *paf, Resource *r, Video *video) {
+ _g = g;
+ _andyObject = andyObject;
+ _paf = paf;
+ _res = r;
+ _video = video;
+ }
+
+ virtual const CheckpointData *getCheckpointData(int num) const = 0;
+ virtual const uint8_t *getScreenRestartData() const = 0;
+ virtual void initialize() {}
+ virtual void terminate() {}
+ virtual void tick() {}
+ virtual void preScreenUpdate(int screenNum) = 0;
+ virtual void postScreenUpdate(int screenNum) = 0;
+ virtual void setupScreenCheckpoint(int screenNum) {}
+
+ Game *_g;
+ LvlObject *_andyObject;
+ PafPlayer *_paf;
+ Resource *_res;
+ Video *_video;
+
+ uint8_t _screenCounterTable[kMaxScreens];
+ int _checkpoint;
+};
+
+Level *Level_rock_create();
+Level *Level_fort_create();
+Level *Level_pwr1_create();
+Level *Level_isld_create();
+Level *Level_lava_create();
+Level *Level_pwr2_create();
+Level *Level_lar1_create();
+Level *Level_lar2_create();
+Level *Level_dark_create();
+
+#endif
diff --git a/Src/Vita_&_Switch/level1_rock.cpp b/Src/Vita_&_Switch/level1_rock.cpp
new file mode 100644
index 0000000..7782c3d
--- /dev/null
+++ b/Src/Vita_&_Switch/level1_rock.cpp
@@ -0,0 +1,919 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+// rock_hod - "canyon of death"
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "util.h"
+#include "video.h"
+
+static const CheckpointData _rock_checkpointData[8] = {
+ { 96, 34, 0x300c, 22, 0, 0 },
+ { 10, 126, 0x300c, 48, 2, 0 },
+ { 6, 128, 0x300c, 48, 4, 0 },
+ { 18, 125, 0x300c, 48, 5, 0 },
+ { 21, 124, 0x300c, 48, 7, 0 },
+ { 105, 52, 0x3004, 232, 9, 2 },
+ { 18, 121, 0x3004, 232, 13, 2 },
+ { 43, 96, 0x3004, 39, 17, 2 }
+};
+
+static const uint8_t _rock_screenStartData[56] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x30, 0x00, 0x00, 0x01, 0x40, 0x02, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_rock: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_rock_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _rock_screenStartData; }
+ virtual void initialize();
+ virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_rock_screen0();
+ void postScreenUpdate_rock_screen4();
+ void postScreenUpdate_rock_screen8();
+ void postScreenUpdate_rock_screen9();
+ void postScreenUpdate_rock_screen10();
+ void postScreenUpdate_rock_screen11();
+ void postScreenUpdate_rock_screen13();
+ void postScreenUpdate_rock_screen15();
+ void postScreenUpdate_rock_screen16();
+ void postScreenUpdate_rock_screen18();
+ void postScreenUpdate_rock_screen19();
+
+ void preScreenUpdate_rock_screen0();
+ void preScreenUpdate_rock_screen1();
+ void preScreenUpdate_rock_screen2();
+ void preScreenUpdate_rock_screen3();
+ void preScreenUpdate_rock_screen4();
+ void preScreenUpdate_rock_screen5();
+ void preScreenUpdate_rock_screen7();
+ void preScreenUpdate_rock_screen9();
+ void preScreenUpdate_rock_screen10();
+ void preScreenUpdate_rock_screen13();
+ void preScreenUpdate_rock_screen14();
+ void preScreenUpdate_rock_screen15();
+ void preScreenUpdate_rock_screen16();
+ void preScreenUpdate_rock_screen17();
+ void preScreenUpdate_rock_screen18();
+ void preScreenUpdate_rock_screen19();
+
+ void setupScreenCheckpoint_rock_screen2();
+ void setupScreenCheckpoint_rock_screen3();
+ void setupScreenCheckpoint_rock_screen18();
+};
+
+Level *Level_rock_create() {
+ return new Level_rock;
+}
+
+void Level_rock::postScreenUpdate_rock_screen0() {
+ switch (_res->_screensState[0].s0) {
+ case 0:
+ if ((_andyObject->flags0 & 0x1F) == 0 && ((_andyObject->flags0 >> 5) & 7) == 6) {
+ _res->_screensState[0].s0 = 2;
+ }
+ break;
+ case 2:
+ ++_screenCounterTable[0];
+ if (_screenCounterTable[0] > 25) {
+ _res->_screensState[0].s0 = 1;
+ }
+ if (_screenCounterTable[0] == 2) {
+ _g->setShakeScreen(3, 12);
+ }
+ break;
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen4() {
+ switch (_res->_screensState[4].s0) {
+ case 0:
+ if (_g->_plasmaCannonDirection != 0 && _res->_currentScreenResourceNum == 4) {
+ if (_g->testPlasmaCannonPointsDirection(162, 24, 224, 68) == 0) {
+ if (_g->testPlasmaCannonPointsDirection(202, 0, 255, 48) == 0) {
+ if (_g->testPlasmaCannonPointsDirection(173, 8, 201, 23) == 0) {
+ _g->_plasmaCannonLastIndex1 = 0;
+ return;
+ }
+ }
+ }
+ ++_screenCounterTable[4];
+ if (_screenCounterTable[4] >= 20) {
+ _res->_screensState[4].s0 = 2;
+ }
+ assert(_g->_plasmaExplosionObject);
+ _g->_plasmaExplosionObject->screenNum = _res->_currentScreenResourceNum;
+ _g->_plasmaCannonExplodeFlag = true;
+ if (_g->_shakeScreenDuration == 0) {
+ _g->setShakeScreen(3, 2);
+ } else {
+ ++_g->_shakeScreenDuration;
+ }
+ }
+ break;
+ case 2:
+ ++_screenCounterTable[4];
+ if (_screenCounterTable[4] == 33) {
+ _res->_resLvlScreenBackgroundDataTable[4].currentMaskId = 1;
+ _g->setupScreenMask(4);
+ } else if (_screenCounterTable[4] > 46) {
+ _res->_screensState[4].s0 = 1;
+ }
+ if (_screenCounterTable[4] == 31) {
+ _g->setShakeScreen(2, 12);
+ }
+ break;
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen8() {
+ if (_res->_currentScreenResourceNum == 8) {
+ if ((_andyObject->flags0 & 0x1F) == 3) {
+ _andyObject->flags2 = 0x3008;
+ } else if (_andyObject->yPos + _andyObject->height / 2 < 89) {
+ _andyObject->flags2 = 0x3004;
+ } else {
+ _andyObject->flags2 = 0x300C;
+ }
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen9() {
+ int xPos;
+ if (_res->_currentScreenResourceNum == 9) {
+ switch (_res->_screensState[9].s0) {
+ case 0:
+ xPos = 68;
+ if ((_andyObject->flags0 & 0xE0) != 0) {
+ xPos -= 14;
+ }
+ if (_andyObject->xPos > xPos && _andyObject->yPos < 86) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(1);
+ _res->_resLvlScreenBackgroundDataTable[9].currentBackgroundId = 1;
+ _video->_paletteChanged = true;
+ }
+ if (_checkpoint == 4) {
+ _checkpoint = 5;
+ }
+ _res->_screensState[9].s0 = 1;
+ _g->setAndySprite(2);
+ _andyObject->xPos = 105;
+ _andyObject->yPos = 52;
+ _andyObject->anim = 232;
+ _andyObject->frame = 0;
+ _g->setupLvlObjectBitmap(_andyObject);
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ break;
+ case 1:
+ _g->_plasmaCannonFlags |= 2;
+ break;
+ }
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen10() {
+ if (_res->_currentScreenResourceNum == 10) {
+ BoundingBox box = { 64, 0, 267, 191 };
+ _g->setAndyAnimationForArea(&box, 12);
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen11() {
+ if (_res->_currentScreenResourceNum == 11) {
+ BoundingBox box = { -12, 0, 162, 191 };
+ _g->setAndyAnimationForArea(&box, 12);
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ _g->_plasmaCannonFlags &= ~1;
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen15() {
+ if (_res->_currentScreenResourceNum == 15) {
+ _g->_plasmaCannonFlags &= ~1;
+ postScreenUpdate_rock_screen16();
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen16() {
+ switch (_res->_screensState[16].s0) {
+ case 0:
+ if (_screenCounterTable[16] < 2) {
+ if ((_andyObject->flags0 & 0x1F) == 2) {
+ break;
+ }
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (data->unk4 != 2 || _andyObject->xPos <= 155 || _andyObject->yPos >= 87) {
+ break;
+ }
+ ++_screenCounterTable[16];
+ _g->setShakeScreen(3, 4);
+ } else {
+ _res->_screensState[16].s0 = 2;
+ _g->_plasmaCannonFlags |= 1;
+ _g->setShakeScreen(1, 18);
+ }
+ break;
+ case 2:
+ ++_screenCounterTable[16];
+ if (_screenCounterTable[16] == 5) {
+ _res->_resLvlScreenBackgroundDataTable[16].currentMaskId = 1;
+ _g->setupScreenMask(16);
+ } else if (_screenCounterTable[16] == 23) {
+ _andyObject->flags1 &= ~0x30;
+ _andyObject->xPos = 131;
+ _andyObject->yPos = 177;
+ _andyObject->anim = 4;
+ _andyObject->frame = 0;
+ _g->_plasmaCannonFlags &= ~1;
+ } else if (_screenCounterTable[16] == 37) {
+ _res->_screensState[16].s0 = 3;
+ _res->_resLvlScreenBackgroundDataTable[16].currentBackgroundId = 1;
+ }
+ break;
+ case 3:
+ ++_screenCounterTable[16];
+ if (_screenCounterTable[16] == 55) {
+ _res->_screensState[16].s0 = 1;
+ }
+ break;
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen18() {
+ LvlObject *o;
+ switch (_res->_screensState[18].s0) {
+ case 0:
+ if (_andyObject->yPos + _andyObject->height < 162) {
+ if ((_andyObject->flags0 & 0x1F) == 0 && _screenCounterTable[18] == 0) {
+ _screenCounterTable[18] = 1;
+ } else {
+ ++_screenCounterTable[18];
+ if (_screenCounterTable[18] == 24) {
+ _res->_screensState[18].s0 = 2;
+ _res->_resLvlScreenBackgroundDataTable[18].currentMaskId = 2;
+ _g->setupScreenMask(18);
+ }
+ }
+ }
+ break;
+ case 2:
+ o = _g->findLvlObject(2, 0, 18);
+ ++_screenCounterTable[18];
+ if (_res->_version >= Resource::V1_2) {
+ o->actionKeyMask = 1;
+ } else if (_screenCounterTable[18] == 29) {
+ o->actionKeyMask = 1;
+ break;
+ }
+ if (_screenCounterTable[18] == 43) {
+ _g->setShakeScreen(2, 5);
+ _res->_resLvlScreenBackgroundDataTable[18].currentMaskId = 1;
+ _g->setupScreenMask(18);
+ } else if (_screenCounterTable[18] < (_res->_version >= Resource::V1_2 ? 51 : 57)) {
+ if ((o->flags0 & 0x1F) != 11 || (_andyObject->flags0 & 0x1F) == 11) {
+ break;
+ }
+ BoundingBox box = { 24, 98, 108, 165 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&box, &data->boundingBox)) {
+ _andyObject->anim = 155;
+ _andyObject->xPos = 59;
+ _andyObject->yPos = 150;
+ _andyObject->frame = 0;
+ _andyObject->flags2 = 0x300C;
+ _g->setupLvlObjectBitmap(_andyObject);
+ }
+ } else {
+ _res->_screensState[18].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[18].currentBackgroundId = 1;
+ }
+ break;
+ }
+}
+
+void Level_rock::postScreenUpdate_rock_screen19() {
+ int fl;
+ switch (_res->_screensState[19].s0) {
+ case 0: {
+ BoundingBox box = { 155, 69, 210, 88 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (!_g->clipBoundingBox(&box, &data->boundingBox)) {
+ return;
+ }
+ _res->_screensState[19].s0 = 3;
+ }
+ break;
+ case 1:
+ if (_andyObject->xPos <= 139) {
+ _andyObject->actionKeyMask = 0x80;
+ _andyObject->directionKeyMask = 0;
+ }
+ fl = _andyObject->flags0 & 0x1F;
+ if (fl == 0 || fl == 2) {
+ fl = (_andyObject->flags0 >> 5) & 7;
+ if (fl == 7 || fl == 2) {
+ _res->_screensState[19].s0 = 4;
+ }
+ }
+ break;
+ case 2:
+ if (_andyObject->yPos < 1) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(2);
+ _paf->unload(2);
+ if (_res->_isDemo && !_paf->_skipCutscenes) {
+ _paf->play(21);
+ }
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ }
+ break;
+ case 3:
+ ++_screenCounterTable[19];
+ if (_screenCounterTable[19] == 1) {
+ _res->_resLvlScreenBackgroundDataTable[19].currentMaskId = 1;
+ _g->setupScreenMask(19);
+ _g->setAndySpecialAnimation(0x12);
+ } else if (_screenCounterTable[19] > 12) {
+ _res->_screensState[19].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[19].currentBackgroundId = 1;
+ }
+ break;
+ case 4:
+ ++_screenCounterTable[19];
+ if (_screenCounterTable[19] == 25) {
+ _g->setShakeScreen(2, 5);
+ _res->_resLvlScreenBackgroundDataTable[19].currentMaskId = 2;
+ _g->setupScreenMask(19);
+ } else if (_screenCounterTable[19] == 33) {
+ _res->_screensState[19].s0 = 2;
+ _res->_resLvlScreenBackgroundDataTable[19].currentBackgroundId = 2;
+ }
+ break;
+ }
+}
+
+void Level_rock::postScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ postScreenUpdate_rock_screen0();
+ break;
+ case 4:
+ postScreenUpdate_rock_screen4();
+ break;
+ case 8:
+ postScreenUpdate_rock_screen8();
+ break;
+ case 9:
+ postScreenUpdate_rock_screen9();
+ break;
+ case 10:
+ postScreenUpdate_rock_screen10();
+ break;
+ case 11:
+ postScreenUpdate_rock_screen11();
+ break;
+ case 13:
+ postScreenUpdate_rock_screen13();
+ break;
+ case 15:
+ postScreenUpdate_rock_screen15();
+ break;
+ case 16:
+ postScreenUpdate_rock_screen16();
+ break;
+ case 18:
+ postScreenUpdate_rock_screen18();
+ break;
+ case 19:
+ postScreenUpdate_rock_screen19();
+ break;
+ }
+}
+
+int Game::objectUpdate_rock_case0(LvlObject *o) {
+ return 1;
+}
+
+static const uint8_t _level1OpHelper1KeyMaskTable[112] = {
+ 8, 0, 8, 0, 8, 0, 8, 0, 8, 4, 9, 0, 9, 0, 9, 4,
+ 9, 0, 9, 0, 8, 4, 9, 0, 9, 4, 8, 0, 8, 4, 8, 4,
+ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4,
+ 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4,
+ 3, 0, 2, 4, 3, 0, 2, 4, 3, 4, 2, 0, 2, 4, 2, 4,
+ 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 4, 8, 4, 4, 0,
+ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 0, 8, 0, 4, 0
+};
+
+void Game::objectUpdate_rockShadow(LvlObject *ptr, uint8_t *p) {
+ const bool sameScreen = (_andyObject->screenNum == ptr->screenNum);
+ int i = (_andyObject->width / 2 + _andyObject->xPos) / 8;
+ if (i < 0 || ptr->screenNum != _res->_currentScreenResourceNum) {
+ i = 0;
+ } else if (i > 31) {
+ i = 31;
+ }
+ LvlObject *o = ptr->childPtr;
+ assert(o);
+ ptr->directionKeyMask = p[32 + i];
+ if (ptr->directionKeyMask != 0x80) {
+ p[67] = 0;
+ ptr->actionKeyMask = p[i];
+ } else {
+ if (p[67] != 0) {
+ --p[67];
+ } else {
+ p[67] = (_rnd.getNextNumber() >> 4) + 4;
+ p[66] = _rnd.getNextNumber() & 7;
+ }
+ const int index = p[i] * 8 + p[66];
+ ptr->directionKeyMask = _level1OpHelper1KeyMaskTable[index * 2];
+ ptr->actionKeyMask = _level1OpHelper1KeyMaskTable[index * 2 + 1];
+ }
+ if ((ptr->actionKeyMask & 4) != 0 && sameScreen && (ptr->flags0 & 0x300) == 0x300) {
+ if (clipLvlObjectsBoundingBox(_andyObject, ptr, 20)) {
+ _mstFlags |= 0x80000000;
+ setAndySpecialAnimation(0x80);
+ }
+ }
+ if ((ptr->directionKeyMask & 4) != 0) {
+ if (o->anim != 1 || o->frame != p[64]) {
+ ptr->directionKeyMask &= ~4;
+ }
+ }
+ if (_plasmaCannonDirection && (ptr->flags0 & 0x1F) != 0xB) {
+ if (plasmaCannonHit(ptr->childPtr)) {
+ ptr->actionKeyMask |= 0x20;
+ ++ptr->hitCount;
+ if (ptr->hitCount > p[65] || (_cheats & kCheatOneHitPlasmaCannon) != 0) {
+ ptr->hitCount = 0;
+ ptr->actionKeyMask |= 7;
+ }
+ _plasmaCannonExplodeFlag = true;
+ _plasmaCannonObject = ptr->childPtr;
+ }
+ }
+ o->directionKeyMask = ptr->directionKeyMask;
+ o->actionKeyMask = ptr->actionKeyMask;
+ updateAndyObject(ptr);
+ updateAndyObject(o);
+}
+
+bool Game::plasmaCannonHit(LvlObject *ptr) {
+ assert(ptr);
+ if (ptr->bitmapBits) {
+ int dx = 0;
+ if (ptr->screenNum == _currentLeftScreen) {
+ dx = -Video::W;
+ } else if (ptr->screenNum == _currentRightScreen) {
+ dx = Video::W;
+ } else if (ptr->screenNum != _currentScreen) {
+ return false;
+ }
+ if (testPlasmaCannonPointsDirection(ptr->xPos + dx, ptr->yPos, ptr->xPos + dx + ptr->width, ptr->yPos + ptr->height)) {
+ return true;
+ }
+ assert(_plasmaExplosionObject);
+ _plasmaExplosionObject->screenNum = ptr->screenNum;
+ }
+ return false;
+}
+
+// shadow screen2
+int Game::objectUpdate_rock_case1(LvlObject *o) {
+ static uint8_t data[68] = {
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x0C, 0x07, 0x00, 0x00
+ };
+ if (_level->_screenCounterTable[2] == 0) {
+ objectUpdate_rockShadow(o, data);
+ if ((o->flags0 & 0x3FF) == 0x4B) {
+ _level->_screenCounterTable[2] = 1;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+// shadow screen3
+int Game::objectUpdate_rock_case2(LvlObject *o) {
+ static uint8_t data[68] = {
+ 0x00, 0x00, 0x00, 0x00, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x04, 0x04, 0x04, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x06, 0x0B, 0x00, 0x00
+ };
+ if (_level->_screenCounterTable[3] == 0) {
+ objectUpdate_rockShadow(o, data);
+ if ((o->flags0 & 0x3FF) == 0x4B) {
+ _level->_screenCounterTable[3] = 1;
+ }
+ } else {
+ o->bitmapBits = 0;
+ o = o->childPtr;
+ assert(o);
+ o->anim = 0;
+ o->frame = 0;
+ o->xPos = 204;
+ o->yPos = 0;
+ setupLvlObjectBitmap(o);
+ }
+ return 1;
+}
+
+int Game::objectUpdate_rock_case3(LvlObject *o) {
+ updateAndyObject(o);
+ return 1;
+}
+
+int Game::objectUpdate_rock_case4(LvlObject *o) {
+ updateAndyObject(o);
+ updateAndyObject(o->childPtr);
+ return 1;
+}
+
+void Level_rock::preScreenUpdate_rock_screen0() {
+ switch (_res->_screensState[0].s0) {
+ case 0:
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 0;
+ break;
+ default:
+ _res->_screensState[0].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 1;
+ break;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen1() {
+ if (_checkpoint != 0) {
+ _res->_screensState[0].s0 = 1;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen2() {
+ if (_res->_currentScreenResourceNum == 2 && _checkpoint == 0) {
+ _checkpoint = 1;
+ } else if (_checkpoint > 1) {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 2);
+ if (ptr) {
+ ptr->anim = 0;
+ ptr->frame = 0;
+ }
+ _g->setupLvlObjectBitmap(ptr);
+ ptr = _g->findLvlObject(2, 1, 2);
+ if (ptr) {
+ ptr->anim = 0;
+ ptr->frame = 0;
+ }
+ _g->setupLvlObjectBitmap(ptr);
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen3() {
+ if (_checkpoint > 1) {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 3);
+ if (ptr) {
+ ptr->anim = 0;
+ ptr->frame = 0;
+ }
+ _g->setupLvlObjectBitmap(ptr);
+ ptr = _g->findLvlObject(2, 1, 3);
+ if (ptr) {
+ ptr->anim = 0;
+ ptr->frame = 0;
+ }
+ _g->setupLvlObjectBitmap(ptr);
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen4() {
+ int num;
+ switch (_res->_screensState[4].s0) {
+ case 0:
+ num = 0;
+ break;
+ case 1:
+ num = 1;
+ break;
+ default:
+ _res->_screensState[4].s0 = 1;
+ num = 1;
+ break;
+ }
+ _res->_resLvlScreenBackgroundDataTable[4].currentBackgroundId = num;
+ // bugfix: glitch when re-entering screen 4 (state 1) from screen 3.
+ // drawScreen() calls applyShadowColors() from 0 to shadowsCount. Setting
+ // currentShadowId to 1 will result in screen 3 shadow mask (index #0) to
+ // be applied where it shouldn't. Another fix would be iterating from
+ // currentShadowId to shadowsCount, similar to decodeShadowScreenMask.
+ // _res->_resLvlScreenBackgroundDataTable[4].currentShadowId = num;
+ _res->_resLvlScreenBackgroundDataTable[4].currentMaskId = num;
+ if (_res->_currentScreenResourceNum == 4 && _checkpoint == 1) {
+ _checkpoint = 2;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen5() {
+ if (_res->_currentScreenResourceNum == 5 && _checkpoint == 2) {
+ _checkpoint = 3;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen7() {
+ if (_res->_currentScreenResourceNum == 7 && _checkpoint == 3) {
+ _checkpoint = 4;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen9() {
+ switch (_res->_screensState[9].s0) {
+ case 0:
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(1);
+ }
+ _res->_resLvlScreenBackgroundDataTable[9].currentBackgroundId = 0;
+ break;
+ default:
+ _res->_screensState[9].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[9].currentBackgroundId = 1;
+ break;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen10() {
+ if (_res->_currentScreenResourceNum == 10) {
+ if (_checkpoint == 4) {
+ _checkpoint = 5;
+ }
+ if (!_paf->_skipCutscenes) {
+ _paf->unload(22);
+ _paf->preload(23);
+ }
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ if (_checkpoint == 5) {
+ _checkpoint = 6;
+ }
+ if (!_paf->_skipCutscenes) {
+ _paf->unload(1);
+ }
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen14() {
+ if (_res->_currentScreenResourceNum == 14) {
+ _res->_screensState[16].s0 = 0;
+ _screenCounterTable[16] = 0;
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen15() {
+ if (_res->_currentScreenResourceNum == 15) {
+ if (_res->_screensState[16].s0 != 0) {
+ _g->_fallingAndyCounter = 2;
+ _g->_fallingAndyFlag = true;
+ }
+ _g->playAndyFallingCutscene(1);
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen16() {
+ uint8_t _al;
+ switch (_res->_screensState[16].s0) {
+ case 0:
+ _al = 0;
+ break;
+ default:
+ _res->_screensState[16].s0 = 1;
+ _g->_plasmaCannonFlags &= ~1;
+ _al = 1;
+ break;
+ }
+ _res->_resLvlScreenBackgroundDataTable[16].currentBackgroundId = _al;
+ _res->_resLvlScreenBackgroundDataTable[16].currentMaskId = _al;
+ if (_res->_currentScreenResourceNum == 16) {
+ _g->playAndyFallingCutscene(1);
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen17() {
+ if (_res->_currentScreenResourceNum == 17) {
+ if (_checkpoint == 6) {
+ _checkpoint = 7;
+ }
+ _g->playAndyFallingCutscene(1);
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen18() {
+ uint8_t _al;
+ switch (_res->_screensState[18].s0) {
+ case 0:
+ _al = 0;
+ break;
+ default:
+ _res->_screensState[18].s0 = 1;
+ _al = 1;
+ break;
+ }
+ _res->_resLvlScreenBackgroundDataTable[18].currentBackgroundId = _al;
+ _res->_resLvlScreenBackgroundDataTable[18].currentMaskId = _al;
+ if (_res->_currentScreenResourceNum == 18) {
+ _g->playAndyFallingCutscene(1);
+ }
+}
+
+void Level_rock::preScreenUpdate_rock_screen19() {
+ uint8_t _al;
+ switch (_res->_screensState[19].s0) {
+ case 0:
+ _al = 0;
+ break;
+ case 1:
+ _al = 1;
+ break;
+ default:
+ _res->_screensState[19].s0 = 2;
+ _al = 2;
+ break;
+ }
+ _res->_resLvlScreenBackgroundDataTable[19].currentBackgroundId = _al;
+ _res->_resLvlScreenBackgroundDataTable[19].currentMaskId = _al;
+ if (_res->_currentScreenResourceNum == 19) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(2);
+ }
+ }
+}
+
+void Level_rock::preScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ preScreenUpdate_rock_screen0();
+ break;
+ case 1:
+ preScreenUpdate_rock_screen1();
+ break;
+ case 2:
+ preScreenUpdate_rock_screen2();
+ break;
+ case 3:
+ preScreenUpdate_rock_screen3();
+ break;
+ case 4:
+ preScreenUpdate_rock_screen4();
+ break;
+ case 5:
+ preScreenUpdate_rock_screen5();
+ break;
+ case 7:
+ preScreenUpdate_rock_screen7();
+ break;
+ case 9:
+ preScreenUpdate_rock_screen9();
+ break;
+ case 10:
+ preScreenUpdate_rock_screen10();
+ break;
+ case 13:
+ preScreenUpdate_rock_screen13();
+ break;
+ case 14:
+ preScreenUpdate_rock_screen14();
+ break;
+ case 15:
+ preScreenUpdate_rock_screen15();
+ break;
+ case 16:
+ preScreenUpdate_rock_screen16();
+ break;
+ case 17:
+ preScreenUpdate_rock_screen17();
+ break;
+ case 18:
+ preScreenUpdate_rock_screen18();
+ break;
+ case 19:
+ preScreenUpdate_rock_screen19();
+ break;
+ }
+}
+
+void Level_rock::initialize() {
+ if (!_paf->_skipCutscenes) {
+ if (_andyObject->spriteNum == 0) {
+ _paf->preload(22);
+ } else {
+ _paf->preload(23);
+ }
+ }
+}
+
+void Level_rock::tick() {
+ if (_res->_currentScreenResourceNum > 9) {
+ _g->_plasmaCannonFlags |= 2;
+ }
+}
+
+void Level_rock::terminate() {
+ if (!_paf->_skipCutscenes) {
+ if (_andyObject->spriteNum == 0) {
+ _paf->unload(22);
+ } else {
+ _paf->unload(23);
+ }
+ }
+}
+
+void Level_rock::setupScreenCheckpoint_rock_screen2() {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 2);
+ if (ptr) {
+ ptr->xPos = 146;
+ ptr->yPos = 0;
+ ptr->anim = 1;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ }
+ ptr = _g->findLvlObject(2, 1, 2);
+ if (ptr) {
+ ptr->xPos = 88;
+ ptr->yPos = 0;
+ ptr->anim = 1;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ }
+}
+
+void Level_rock::setupScreenCheckpoint_rock_screen3() {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 3);
+ if (ptr) {
+ ptr->xPos = 198;
+ ptr->yPos = 0;
+ ptr->anim = 1;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ }
+ ptr = _g->findLvlObject(2, 1, 3);
+ if (ptr) {
+ ptr->xPos = 116;
+ ptr->yPos = 0;
+ ptr->anim = 1;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ }
+}
+
+void Level_rock::setupScreenCheckpoint_rock_screen18() {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 18);
+ if (ptr) {
+ ptr->xPos = 16;
+ ptr->yPos = 78;
+ ptr->anim = 0;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ }
+}
+
+void Level_rock::setupScreenCheckpoint(int num) {
+ switch (num) {
+ case 2:
+ setupScreenCheckpoint_rock_screen2();
+ break;
+ case 3:
+ setupScreenCheckpoint_rock_screen3();
+ break;
+ case 18:
+ setupScreenCheckpoint_rock_screen18();
+ break;
+ }
+}
diff --git a/Src/Vita_&_Switch/level2_fort.cpp b/Src/Vita_&_Switch/level2_fort.cpp
new file mode 100644
index 0000000..cf3310e
--- /dev/null
+++ b/Src/Vita_&_Switch/level2_fort.cpp
@@ -0,0 +1,347 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+// fort_hod - "swamp lands"
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "util.h"
+#include "video.h"
+
+static const CheckpointData _fort_checkpointData[5] = {
+ { 105, 46, 0x300c, 232, 0, 2 },
+ { 1, 103, 0x300c, 232, 2, 2 },
+ { 43, 96, 0x300c, 232, 6, 2 },
+ { 32, 97, 0x300c, 232, 9, 2 },
+ { 105, 52, 0x300c, 232, 17, 2 }
+};
+
+static const uint8_t _fort_screenStartData[56] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_fort: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_fort_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _fort_screenStartData; }
+ //virtual void initialize();
+ //virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_fort_screen1();
+ void postScreenUpdate_fort_screen6();
+ void postScreenUpdate_fort_screen7();
+ void postScreenUpdate_fort_screen8();
+ void postScreenUpdate_fort_screen16();
+ void postScreenUpdate_fort_screen17();
+ void postScreenUpdate_fort_screen21();
+
+ void preScreenUpdate_fort_screen1();
+ void preScreenUpdate_fort_screen2();
+ void preScreenUpdate_fort_screen6();
+ void preScreenUpdate_fort_screen9();
+ void preScreenUpdate_fort_screen14();
+ void preScreenUpdate_fort_screen16();
+ void preScreenUpdate_fort_screen17();
+ void preScreenUpdate_fort_screen21();
+
+ void setupScreenCheckpoint_fort_screen1();
+};
+
+Level *Level_fort_create() {
+ return new Level_fort;
+}
+
+void Level_fort::postScreenUpdate_fort_screen1() {
+ if (_res->_screensState[1].s0 == 2) {
+ LvlObject *o = _g->findLvlObject(2, 0, 1);
+ if (o) {
+ o->actionKeyMask = 1;
+ }
+ ++_screenCounterTable[1];
+ if (_screenCounterTable[1] == 25) {
+ _res->_resLvlScreenBackgroundDataTable[1].currentMaskId = 1;
+ _g->setupScreenMask(1);
+ } else if (_screenCounterTable[1] == 59) {
+ _res->_screensState[1].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[1].currentBackgroundId = 1;
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate_fort_screen6() {
+ if (_res->_currentScreenResourceNum == 6) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ BoundingBox b = { 0, 0, 43, 191 };
+ if (!_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _andyObject->actionKeyMask &= ~3;
+ _andyObject->actionKeyMask |= 8;
+ if (_andyObject->directionKeyMask & 4) {
+ _andyObject->directionKeyMask &= ~10;
+ ++_screenCounterTable[6];
+ if (_screenCounterTable[6] >= 188) {
+ _andyObject->directionKeyMask &= ~4;
+ _screenCounterTable[7] = 0;
+ }
+ }
+ if (_screenCounterTable[7] < 25) {
+ ++_screenCounterTable[7];
+ _andyObject->directionKeyMask &= ~4;
+ } else if ((_andyObject->directionKeyMask & 4) == 0) {
+ _screenCounterTable[6] = 0;
+ }
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate_fort_screen7() {
+ if (_res->_currentScreenResourceNum == 7) {
+ _andyObject->actionKeyMask &= ~3;
+ _andyObject->actionKeyMask |= 8;
+ if (_andyObject->directionKeyMask & 4) {
+ _andyObject->directionKeyMask &= ~0xA;
+ ++_screenCounterTable[6];
+ if (_screenCounterTable[6] >= 188) {
+ _andyObject->directionKeyMask &= ~4;
+ _screenCounterTable[7] = 0;
+ }
+ }
+ if (_screenCounterTable[7] < 25) {
+ ++_screenCounterTable[7];
+ _andyObject->directionKeyMask &= ~4;
+ } else if ((_andyObject->directionKeyMask & 4) == 0) {
+ _screenCounterTable[6] = 0;
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate_fort_screen8() {
+ if (_res->_currentScreenResourceNum == 8) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ BoundingBox b = { 138, 0, 255, 191 };
+ if (!_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _andyObject->actionKeyMask &= ~3;
+ _andyObject->actionKeyMask |= 8;
+ if (_andyObject->directionKeyMask & 4) {
+ _andyObject->directionKeyMask &= ~0xA;
+ ++_screenCounterTable[6];
+ if (_screenCounterTable[6] >= 188) {
+ _andyObject->directionKeyMask &= ~4;
+ _screenCounterTable[7] = 0;
+ }
+ }
+ if (_screenCounterTable[7] < 25) {
+ ++_screenCounterTable[7];
+ _andyObject->directionKeyMask &= ~4;
+ } else if ((_andyObject->directionKeyMask & 4) == 0) {
+ _screenCounterTable[6] = 0;
+ }
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate_fort_screen16() {
+ if (_res->_currentScreenResourceNum == 16) {
+ if (_res->_screensState[16].s0 != 1) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ BoundingBox b = { 150, 0, 187, 60 };
+ if (!_g->clipBoundingBox(&b, &data->boundingBox)) {
+ return;
+ }
+ if ((_andyObject->flags0 & 0x1F) == 0 && (_andyObject->flags0 & 0xE0) == 0xE0) {
+ _res->_screensState[16].s0 = 1;
+ _screenCounterTable[16] = 0;
+ } else {
+ _g->setAndySpecialAnimation(3);
+ }
+ } else {
+ ++_screenCounterTable[16];
+ if (_screenCounterTable[16] == 5) {
+ if (_checkpoint == 3) {
+ _checkpoint = 4;
+ }
+ if (!_paf->_skipCutscenes) {
+ _paf->play(3);
+ _paf->unload(3);
+ }
+ _video->clearPalette();
+ _g->restartLevel();
+ }
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate_fort_screen17() {
+ if (_res->_screensState[17].s0 == 1) {
+ ++_screenCounterTable[17];
+ if (_screenCounterTable[17] == 68) {
+ _res->_screensState[17].s0 = 0;
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate_fort_screen21() {
+ if (_res->_currentScreenResourceNum == 21) {
+ switch (_res->_screensState[21].s0) {
+ case 1:
+ ++_screenCounterTable[21];
+ if (_screenCounterTable[21] == 22) {
+ _res->_screensState[21].s0 = 2;
+ }
+ break;
+ case 2:
+ if (!_paf->_skipCutscenes) {
+ _paf->play(4);
+ _paf->unload(4);
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ break;
+ }
+ }
+}
+
+void Level_fort::postScreenUpdate(int num) {
+ switch (num) {
+ case 1:
+ postScreenUpdate_fort_screen1();
+ break;
+ case 6:
+ postScreenUpdate_fort_screen6();
+ break;
+ case 7:
+ postScreenUpdate_fort_screen7();
+ break;
+ case 8:
+ postScreenUpdate_fort_screen8();
+ break;
+ case 16:
+ postScreenUpdate_fort_screen16();
+ break;
+ case 17:
+ postScreenUpdate_fort_screen17();
+ break;
+ case 21:
+ postScreenUpdate_fort_screen21();
+ break;
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen1() {
+ if (_res->_currentScreenResourceNum == 1 && _checkpoint >= 1) {
+ _res->_screensState[1].s0 = 1;
+ }
+ const int num = _res->_screensState[1].s0 == 0 ? 0 : 1;
+ _res->_resLvlScreenBackgroundDataTable[1].currentBackgroundId = num;
+ _res->_resLvlScreenBackgroundDataTable[1].currentMaskId = num;
+}
+
+void Level_fort::preScreenUpdate_fort_screen2() {
+ if (_res->_currentScreenResourceNum == 2 && _checkpoint == 0) {
+ _checkpoint = 1;
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen6() {
+ if (_res->_currentScreenResourceNum == 6 && _checkpoint == 1) {
+ _checkpoint = 2;
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen9() {
+ if (_res->_currentScreenResourceNum == 9 && _checkpoint == 2) {
+ _checkpoint = 3;
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen14() {
+ if (_res->_currentScreenResourceNum == 14) {
+ _res->_resLvlScreenBackgroundDataTable[14].currentBackgroundId = _res->_screensState[14].s0 != 0 ? 1 : 0;
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen16() {
+ if (_res->_currentScreenResourceNum == 16) {
+ _res->_screensState[16].s0 = 0;
+ _andyObject->xPos += 9;
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(3);
+ }
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen17() {
+ if (_res->_currentScreenResourceNum == 17) {
+ _res->_screensState[17].s0 = _screenCounterTable[17] == 0 ? 1 : 0;
+ }
+}
+
+void Level_fort::preScreenUpdate_fort_screen21() {
+ if (_res->_currentScreenResourceNum == 21) {
+ _res->_screensState[21].s0 = 0;
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(4);
+ }
+ }
+}
+
+void Level_fort::tick() {
+ _g->_plasmaCannonFlags |= 2;
+}
+
+void Level_fort::preScreenUpdate(int num) {
+ switch (num) {
+ case 1:
+ preScreenUpdate_fort_screen1();
+ break;
+ case 2:
+ preScreenUpdate_fort_screen2();
+ break;
+ case 6:
+ preScreenUpdate_fort_screen6();
+ break;
+ case 9:
+ preScreenUpdate_fort_screen9();
+ break;
+ case 14:
+ preScreenUpdate_fort_screen14();
+ break;
+ case 16:
+ preScreenUpdate_fort_screen16();
+ break;
+ case 17:
+ preScreenUpdate_fort_screen17();
+ break;
+ case 21:
+ preScreenUpdate_fort_screen21();
+ break;
+ }
+}
+
+void Level_fort::setupScreenCheckpoint_fort_screen1() {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 1);
+ if (ptr) {
+ ptr->xPos = 129;
+ ptr->yPos = 97;
+ ptr->anim = 0;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->actionKeyMask = 0;
+ }
+}
+
+void Level_fort::setupScreenCheckpoint(int num) {
+ switch (num) {
+ case 1:
+ setupScreenCheckpoint_fort_screen1();
+ break;
+ }
+}
diff --git a/Src/Vita_&_Switch/level3_pwr1.cpp b/Src/Vita_&_Switch/level3_pwr1.cpp
new file mode 100644
index 0000000..52d718a
--- /dev/null
+++ b/Src/Vita_&_Switch/level3_pwr1.cpp
@@ -0,0 +1,532 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+// pwr1_hod - "magic lake"
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "util.h"
+#include "video.h"
+
+static const CheckpointData _pwr1_checkpointData[10] = {
+ { 190, 48, 0x300c, 196, 1, 2 },
+ { 158, 89, 0x300c, 232, 6, 2 },
+ { 71, 128, 0x300c, 196, 9, 2 },
+ { 7, 145, 0x300c, 196, 15, 2 },
+ { 50, 62, 0x300c, 196, 18, 2 },
+ { 9, 25, 0x300c, 232, 21, 2 },
+ { 19, 73, 0x300c, 232, 27, 2 },
+ { 225, 73, 0x300c, 232, 26, 2 },
+ { 100, 121, 0x300c, 232, 29, 2 },
+ { 80, 121, 0x700c, 232, 31, 2 }
+};
+
+static const uint8_t _pwr1_screenStartData[80] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_pwr1: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_pwr1_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _pwr1_screenStartData; }
+ virtual void initialize();
+ virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ //virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_pwr1_helper(BoundingBox *b, int dx, int dy);
+ void postScreenUpdate_pwr1_screen6();
+ void postScreenUpdate_pwr1_screen10();
+ void postScreenUpdate_pwr1_screen12();
+ void postScreenUpdate_pwr1_screen14();
+ void postScreenUpdate_pwr1_screen16();
+ void postScreenUpdate_pwr1_screen18();
+ void postScreenUpdate_pwr1_screen23();
+ void postScreenUpdate_pwr1_screen27();
+ void postScreenUpdate_pwr1_screen35();
+
+ void preScreenUpdate_pwr1_screen4();
+ void preScreenUpdate_pwr1_screen6();
+ void preScreenUpdate_pwr1_screen9();
+ void preScreenUpdate_pwr1_screen15();
+ void preScreenUpdate_pwr1_screen21();
+ void preScreenUpdate_pwr1_screen23();
+ void preScreenUpdate_pwr1_screen24();
+ void preScreenUpdate_pwr1_screen26();
+ void preScreenUpdate_pwr1_screen27();
+ void preScreenUpdate_pwr1_screen29();
+ void preScreenUpdate_pwr1_screen31();
+ void preScreenUpdate_pwr1_screen35();
+};
+
+Level *Level_pwr1_create() {
+ return new Level_pwr1;
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_helper(BoundingBox *b, int dx, int dy) {
+ LvlObject *o = _g->_lvlObjectsList3;
+ while (o) {
+ if (o->spriteNum == 4 && o->anim <= 9) {
+ BoundingBox b2;
+ b2.x1 = o->posTable[3].x + o->xPos - 1;
+ b2.x2 = o->posTable[3].x + o->xPos + 1;
+ b2.y1 = o->posTable[3].y + o->yPos - 1;
+ b2.y2 = o->posTable[3].y + o->yPos + 1;
+ if (_g->clipBoundingBox(b, &b2)) {
+ o->xPos += dx;
+ o->yPos += dy;
+ }
+ }
+ o = o->nextPtr;
+ }
+ BoundingBox b2;
+ b2.x1 = _andyObject->posTable[3].x + _andyObject->xPos - 1;
+ b2.x2 = _andyObject->posTable[3].x + _andyObject->xPos + 1;
+ b2.y1 = _andyObject->posTable[3].y + _andyObject->yPos - 1;
+ b2.y2 = _andyObject->posTable[3].y + _andyObject->yPos + 1;
+ if (_g->clipBoundingBox(b, &b2)) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ data->dxPos += dx;
+ data->dyPos += dy;
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen6() {
+ LvlBackgroundData *dat = &_res->_resLvlScreenBackgroundDataTable[6];
+ switch (_res->_screensState[6].s0) {
+ case 1: {
+ BoundingBox b = { 185, 78, 225, 136 };
+ LvlObject *o = _g->findLvlObjectBoundingBox(&b);
+ if (o) {
+ ShootLvlObjectData *oosd = (ShootLvlObjectData *)_g->getLvlObjectDataPtr(o, kObjectDataTypeShoot);
+ if (oosd->type == 6) {
+ _res->_screensState[6].s0 = 4;
+ dat->currentMaskId = 2;
+ }
+ }
+ }
+ break;
+ case 2:
+ break;
+ case 3:
+ ++_screenCounterTable[6];
+ if (_screenCounterTable[6] >= 41) {
+ _res->_screensState[6].s0 = 1;
+ dat->currentMaskId = 1;
+ dat->currentBackgroundId = 1;
+ if (_checkpoint == 0) {
+ _checkpoint = 1;
+ }
+ }
+ break;
+ case 4:
+ ++_screenCounterTable[6];
+ if (_screenCounterTable[6] >= 54) {
+ _res->_screensState[6].s0 = 2;
+ dat->currentBackgroundId = 1;
+ }
+ break;
+ default: {
+ BoundingBox b = { 93, 17, 255, 156 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _res->_screensState[6].s0 = 3;
+ _g->setShakeScreen(3, 31);
+ }
+ }
+ break;
+ }
+ if (_res->_screensState[6].s3 != dat->currentMaskId) {
+ _g->setupScreenMask(6);
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen10() {
+ if (_res->_currentScreenResourceNum == 10) {
+ BoundingBox b1 = { 50, 0, 100, 122 };
+ postScreenUpdate_pwr1_helper(&b1, 0, 2);
+ BoundingBox b2 = { 58, 50, 84, 132 };
+ postScreenUpdate_pwr1_helper(&b2, 0, 2);
+ BoundingBox b3 = { 42, 80, 72, 142 };
+ postScreenUpdate_pwr1_helper(&b3, 4, 0);
+ BoundingBox b4 = { 73, 100, 94, 142 };
+ postScreenUpdate_pwr1_helper(&b4, -2, 0);
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen12() {
+ if (_res->_currentScreenResourceNum == 12) {
+ BoundingBox b1 = { 64, 124, 255, 166 };
+ postScreenUpdate_pwr1_helper(&b1, -1, 0);
+ BoundingBox b2 = { 56, 134, 160, 160 };
+ postScreenUpdate_pwr1_helper(&b2, -2, 0);
+ BoundingBox b3 = { 55, 124, 82, 146 };
+ postScreenUpdate_pwr1_helper(&b3, 0, 2);
+ BoundingBox b4 = { 55, 147, 82, 166 };
+ postScreenUpdate_pwr1_helper(&b4, 0, -2);
+ BoundingBox b5 = { 64, 46, 255, 90 };
+ postScreenUpdate_pwr1_helper(&b5, -2, 0);
+ BoundingBox b6 = { 56, 56, 255, 80 };
+ postScreenUpdate_pwr1_helper(&b6, -3, 0);
+ BoundingBox b7 = { 55, 46, 82, 68 };
+ postScreenUpdate_pwr1_helper(&b7, 0, 2);
+ BoundingBox b8 = { 55, 69, 82, 90 };
+ postScreenUpdate_pwr1_helper(&b8, 0, -2);
+ BoundingBox b9 = { 154, 52, 198, 191 };
+ postScreenUpdate_pwr1_helper(&b9, 0, -2);
+ BoundingBox bA = { 164, 44, 188, 191 };
+ postScreenUpdate_pwr1_helper(&bA, 0, -2);
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen14() {
+ if (_res->_currentScreenResourceNum == 14) {
+ BoundingBox b1 = { 0, 136, 104, 191 };
+ postScreenUpdate_pwr1_helper(&b1, 2, 0);
+ BoundingBox b2 = { 0, 152, 112, 178 };
+ postScreenUpdate_pwr1_helper(&b2, 2, 0);
+ BoundingBox b3 = { 76, 148, 116, 164 };
+ postScreenUpdate_pwr1_helper(&b3, 0, 2);
+ BoundingBox b4 = { 76, 164, 116, 186 };
+ postScreenUpdate_pwr1_helper(&b4, 0, -2);
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen16() {
+ if (_res->_currentScreenResourceNum == 16) {
+ BoundingBox b1 = { 100, 130, 160, 176 };
+ postScreenUpdate_pwr1_helper(&b1, 2, 0);
+ BoundingBox b2 = { 88, 140, 180, 167 };
+ postScreenUpdate_pwr1_helper(&b2, 2, 0);
+ BoundingBox b3 = { 88, 82, 185, 120 };
+ postScreenUpdate_pwr1_helper(&b3, 2, 0);
+ BoundingBox b4 = { 120, 92, 190, 114 };
+ postScreenUpdate_pwr1_helper(&b4, 2, 0);
+ BoundingBox b5 = { 104, 40, 147, 120 };
+ postScreenUpdate_pwr1_helper(&b5, 0, -1);
+ BoundingBox b6 = { 115, 24, 138, 90 };
+ postScreenUpdate_pwr1_helper(&b6, 0, -1);
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen18() {
+ if (_res->_currentScreenResourceNum == 18) {
+ if (_checkpoint == 3) {
+ BoundingBox b = { 0, 0, 123, 125 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ ++_checkpoint;
+ }
+ }
+ BoundingBox b1 = { 156, 80, 204, 144 };
+ postScreenUpdate_pwr1_helper(&b1, 0, -2);
+ BoundingBox b2 = { 166, 88, 194, 152 };
+ postScreenUpdate_pwr1_helper(&b2, 0, -2);
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen23() {
+ switch (_res->_screensState[23].s0) {
+ case 2:
+ ++_screenCounterTable[23];
+ if (_screenCounterTable[23] == 26) {
+ _res->_screensState[23].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[23].currentMaskId = 1;
+ _res->_resLvlScreenBackgroundDataTable[23].currentBackgroundId = 1;
+ _g->setupScreenMask(23);
+ }
+ break;
+ case 0:
+ if (_res->_currentScreenResourceNum == 23) {
+ BoundingBox b = { 26, 94, 63, 127 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ uint8_t flags = _andyObject->flags0;
+ if ((flags & 0x1F) == 0 && (flags & 0xE0) == 0xE0) {
+ _res->_screensState[23].s0 = 2;
+ } else {
+ _g->setAndySpecialAnimation(3);
+ }
+ }
+ }
+ break;
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen27() {
+ switch (_res->_screensState[27].s0) {
+ case 2:
+ ++_screenCounterTable[27];
+ if (_screenCounterTable[27] == 37) {
+ _res->_screensState[27].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[27].currentMaskId = 1;
+ _res->_resLvlScreenBackgroundDataTable[27].currentBackgroundId = 1;
+ _g->setupScreenMask(27);
+ }
+ break;
+ case 0:
+ if (_res->_currentScreenResourceNum == 27 && (_andyObject->flags0 & 0x1F) == 6) {
+ BoundingBox b1;
+ b1.x1 = _andyObject->xPos + _andyObject->posTable[7].x - 3;
+ b1.x2 = _andyObject->xPos + _andyObject->posTable[7].x + 4;
+ b1.y1 = _andyObject->yPos + _andyObject->posTable[7].y - 2;
+ b1.y2 = _andyObject->yPos + _andyObject->posTable[7].y + 2;
+ BoundingBox b2 = { 25, 163, 31, 188 };
+ if (_g->clipBoundingBox(&b2, &b1)) {
+ ++_screenCounterTable[27];
+ if (_screenCounterTable[27] == 9) {
+ _res->_screensState[27].s0 = 2;
+ }
+ }
+ }
+ break;
+ }
+}
+
+void Level_pwr1::postScreenUpdate_pwr1_screen35() {
+ if (_res->_currentScreenResourceNum == 35) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ BoundingBox b = { 0, 0, 193, 88 };
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _andyObject->actionKeyMask &= ~3;
+ _andyObject->directionKeyMask &= ~4;
+ _andyObject->actionKeyMask |= 8;
+ }
+ if (_res->_screensState[35].s0 != 0) {
+ ++_screenCounterTable[35];
+ if (_screenCounterTable[35] == 46) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(5);
+ _paf->unload(5);
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ }
+ }
+ }
+}
+
+void Level_pwr1::postScreenUpdate(int num) {
+ switch (num) {
+ case 6:
+ postScreenUpdate_pwr1_screen6();
+ break;
+ case 10:
+ postScreenUpdate_pwr1_screen10();
+ break;
+ case 12:
+ postScreenUpdate_pwr1_screen12();
+ break;
+ case 14:
+ postScreenUpdate_pwr1_screen14();
+ break;
+ case 16:
+ postScreenUpdate_pwr1_screen16();
+ break;
+ case 18:
+ postScreenUpdate_pwr1_screen18();
+ break;
+ case 23:
+ postScreenUpdate_pwr1_screen23();
+ break;
+ case 27:
+ postScreenUpdate_pwr1_screen27();
+ break;
+ case 35:
+ postScreenUpdate_pwr1_screen35();
+ break;
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen4() {
+ if (_res->_currentScreenResourceNum == 4) {
+ const uint8_t num = (_res->_screensState[4].s0 == 0) ? 0 : 1;
+ _res->_resLvlScreenBackgroundDataTable[4].currentBackgroundId = num;
+ _res->_resLvlScreenBackgroundDataTable[4].currentMaskId = num;
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen6() {
+ if (_res->_currentScreenResourceNum == 6 || _res->_currentScreenResourceNum == 5) {
+ if (_res->_screensState[6].s0 == 0) {
+ if (_checkpoint != 1) {
+ _screenCounterTable[6] = 0;
+ _res->_resLvlScreenBackgroundDataTable[6].currentBackgroundId = 0;
+ _res->_resLvlScreenBackgroundDataTable[6].currentMaskId = 0;
+ _res->_screensState[6].s0 = 0;
+ } else {
+ _screenCounterTable[6] = 41;
+ _res->_resLvlScreenBackgroundDataTable[6].currentBackgroundId = 1;
+ _res->_resLvlScreenBackgroundDataTable[6].currentMaskId = 1;
+ _res->_screensState[6].s0 = 1;
+ }
+ } else {
+ _screenCounterTable[6] = 54;
+ _res->_resLvlScreenBackgroundDataTable[6].currentBackgroundId = 2;
+ _res->_resLvlScreenBackgroundDataTable[6].currentMaskId = 2;
+ _res->_screensState[6].s0 = 2;
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen9() {
+ if (_res->_currentScreenResourceNum == 9) {
+ if (_checkpoint == 1) {
+ _checkpoint = 2;
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen15() {
+ if (_res->_currentScreenResourceNum == 15) {
+ if (_checkpoint == 2) {
+ _checkpoint = 3;
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen21() {
+ if (_res->_currentScreenResourceNum == 21) {
+ if (_checkpoint == 4) {
+ _checkpoint = 5;
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen23() {
+ if (_res->_currentScreenResourceNum == 23 || _res->_currentScreenResourceNum == 26) {
+ const uint8_t num = _res->_screensState[23].s0 != 0 ? 1 : 0;
+ _res->_resLvlScreenBackgroundDataTable[23].currentBackgroundId = num;
+ _res->_resLvlScreenBackgroundDataTable[23].currentMaskId = num;
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen24() {
+ if (_res->_currentScreenResourceNum == 24) {
+ if (_res->_screensState[27].s0 != 0) {
+ if (_checkpoint == 6) {
+ _checkpoint = 7;
+ }
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen26() {
+ if (_checkpoint >= 7) {
+ _res->_screensState[23].s0 = 1;
+ }
+ preScreenUpdate_pwr1_screen23();
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen27() {
+ if (_res->_currentScreenResourceNum == 27) {
+ uint8_t num = 0;
+ if (_res->_screensState[27].s0 != 0 || _checkpoint >= 7) {
+ num = 1;
+ _screenCounterTable[27] = 37;
+ } else if (_checkpoint <= 5) {
+ _screenCounterTable[27] = 0;
+ if (_checkpoint == 5) {
+ _checkpoint = 6;
+ }
+ }
+ _res->_screensState[27].s0 = num;
+ _res->_resLvlScreenBackgroundDataTable[27].currentBackgroundId = num;
+ _res->_resLvlScreenBackgroundDataTable[27].currentMaskId = num;
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen29() {
+ if (_res->_currentScreenResourceNum == 29) {
+ if (_checkpoint == 7) {
+ _checkpoint = 8;
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen31() {
+ if (_res->_currentScreenResourceNum == 31) {
+ if (_checkpoint == 8) {
+ _checkpoint = 9;
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate_pwr1_screen35() {
+ if (_res->_currentScreenResourceNum == 35) {
+ _screenCounterTable[35] = 0;
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(5);
+ }
+ }
+}
+
+void Level_pwr1::preScreenUpdate(int num) {
+ switch (num) {
+ case 4:
+ preScreenUpdate_pwr1_screen4();
+ break;
+ case 6:
+ preScreenUpdate_pwr1_screen6();
+ break;
+ case 9:
+ preScreenUpdate_pwr1_screen9();
+ break;
+ case 15:
+ preScreenUpdate_pwr1_screen15();
+ break;
+ case 21:
+ preScreenUpdate_pwr1_screen21();
+ break;
+ case 23:
+ preScreenUpdate_pwr1_screen23();
+ break;
+ case 24:
+ preScreenUpdate_pwr1_screen24();
+ break;
+ case 26:
+ preScreenUpdate_pwr1_screen26();
+ break;
+ case 27:
+ preScreenUpdate_pwr1_screen27();
+ break;
+ case 29:
+ preScreenUpdate_pwr1_screen29();
+ break;
+ case 31:
+ preScreenUpdate_pwr1_screen31();
+ break;
+ case 35:
+ preScreenUpdate_pwr1_screen35();
+ break;
+ }
+}
+
+void Level_pwr1::initialize() {
+ _g->loadTransformLayerData(Game::_pwr1_screenTransformData);
+ _g->resetWormHoleSprites();
+}
+
+void Level_pwr1::terminate() {
+ _g->unloadTransformLayerData();
+}
+
+const uint8_t Game::_pwr1_screenTransformLut[] = {
+ 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0,
+ 1, 2, 1, 0, 1, 3, 1, 0, 1, 0, 1, 4, 1, 0, 1, 0, 1, 0,
+ 1, 5, 1, 0, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0,
+ 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+void Level_pwr1::tick() {
+ _video->_displayShadowLayer = Game::_pwr1_screenTransformLut[_res->_currentScreenResourceNum * 2] != 0;
+ _g->updateWormHoleSprites();
+}
diff --git a/Src/Vita_&_Switch/level4_isld.cpp b/Src/Vita_&_Switch/level4_isld.cpp
new file mode 100644
index 0000000..5acddf9
--- /dev/null
+++ b/Src/Vita_&_Switch/level4_isld.cpp
@@ -0,0 +1,418 @@
+
+// isld_hod - "space island"
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "video.h"
+
+static const CheckpointData _isld_checkpointData[5] = {
+ { 46, 84, 0x300c, 207, 0, 2 },
+ { 99, 48, 0x300c, 39, 3, 2 },
+ { 27, 112, 0x300c, 39, 9, 2 },
+ { 171, 144, 0x300c, 39, 14, 2 },
+ { 195, 128, 0x300c, 39, 16, 2 }
+};
+
+static const uint8_t _isld_screenStartData[56] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_isld: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_isld_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _isld_screenStartData; }
+ virtual void initialize();
+ virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ //virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_isld_screen0();
+ void postScreenUpdate_isld_screen1();
+ void postScreenUpdate_isld_screen2();
+ void postScreenUpdate_isld_screen3();
+ void postScreenUpdate_isld_screen4();
+ void postScreenUpdate_isld_screen8();
+ void postScreenUpdate_isld_screen9();
+ void postScreenUpdate_isld_screen13();
+ void postScreenUpdate_isld_screen15();
+ void postScreenUpdate_isld_screen19();
+ void postScreenUpdate_isld_screen20();
+ void postScreenUpdate_isld_screen21();
+
+ void preScreenUpdate_isld_screen1();
+ void preScreenUpdate_isld_screen2();
+ void preScreenUpdate_isld_screen3();
+ void preScreenUpdate_isld_screen9();
+ void preScreenUpdate_isld_screen14();
+ void preScreenUpdate_isld_screen15();
+ void preScreenUpdate_isld_screen16();
+ void preScreenUpdate_isld_screen21();
+};
+
+Level *Level_isld_create() {
+ return new Level_isld;
+}
+
+static const uint8_t _isld_stairsDxTable[] = {
+ 0, 1, 2, 3, 5, 7, 9, 10, 11, 12, 12, 12, 12, 11, 10, 9, 7, 5, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+void Level_isld::postScreenUpdate_isld_screen0() {
+ if (_res->_currentScreenResourceNum == 0) {
+ if (_screenCounterTable[0] < 5) {
+ ++_screenCounterTable[0];
+ _andyObject->actionKeyMask |= 1;
+ _andyObject->directionKeyMask |= 2;
+ }
+ }
+}
+
+void Level_isld::postScreenUpdate_isld_screen1() {
+ if (_res->_screensState[1].s0 != 2) {
+ _screenCounterTable[1] = 0;
+ } else {
+ ++_screenCounterTable[1];
+ if (_screenCounterTable[1] > 21) {
+ _res->_screensState[1].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[1].currentBackgroundId = 1;
+ }
+ }
+}
+
+void Level_isld::postScreenUpdate_isld_screen2() {
+ if (_res->_screensState[2].s0 == 1) {
+ if (_res->_currentScreenResourceNum == 2) {
+ ++_screenCounterTable[2];
+ if (_screenCounterTable[2] == 1) {
+ LvlObject *o = _g->findLvlObject(2, 0, 2);
+ if (o) {
+ o->actionKeyMask = 1;
+ }
+ } else if (_screenCounterTable[2] == 6) {
+ _res->_resLvlScreenBackgroundDataTable[2].currentMaskId = 1;
+ _g->setupScreenMask(2);
+ _g->_plasmaCannonFlags |= 1;
+ } else if (_screenCounterTable[2] == 38 && !_g->_fadePalette) {
+ if ((_andyObject->flags0 & 0x1F) != 0xB) {
+ _checkpoint = 1;
+ }
+ _g->_levelRestartCounter = 6;
+ }
+ }
+ }
+}
+
+void Level_isld::postScreenUpdate_isld_screen3() {
+ if (_res->_currentScreenResourceNum == 3) {
+ if (_andyObject->xPos < 150) {
+ LvlObject *o = _g->findLvlObject2(0, 1, 3);
+ if (o) {
+ AnimBackgroundData *backgroundData = (AnimBackgroundData *)_g->getLvlObjectDataPtr(o, kObjectDataTypeAnimBackgroundData);
+ AndyLvlObjectData *andyData = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ andyData->dxPos += _isld_stairsDxTable[backgroundData->currentFrame];
+ }
+ }
+ }
+}
+
+void Level_isld::postScreenUpdate_isld_screen4() {
+ if (_res->_currentScreenResourceNum == 4) {
+ if (_andyObject->xPos < 150) {
+ LvlObject *o = _g->findLvlObject2(0, 1, 4);
+ if (o) {
+ AnimBackgroundData *backgroundData = (AnimBackgroundData *)_g->getLvlObjectDataPtr(o, kObjectDataTypeAnimBackgroundData);
+ AndyLvlObjectData *andyData = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ andyData->dxPos += _isld_stairsDxTable[backgroundData->currentFrame];
+ }
+ }
+ }
+}
+
+void Level_isld::postScreenUpdate_isld_screen8() {
+ if (_res->_currentScreenResourceNum == 8) {
+ const int xPos = _andyObject->xPos + _andyObject->posTable[3].x;
+ if (xPos > 246) {
+ _g->_fallingAndyCounter = 2;
+ _g->_fallingAndyFlag = true;
+ _g->playAndyFallingCutscene(1);
+ }
+ }
+}
+
+void Level_isld::postScreenUpdate_isld_screen9() {
+ if (_res->_currentScreenResourceNum == 9) {
+ const int xPos = _andyObject->xPos + _andyObject->posTable[3].x;
+ if (xPos < 10 || xPos > 246) {
+ _g->_fallingAndyCounter = 2;
+ _g->_fallingAndyFlag = true;
+ _g->playAndyFallingCutscene(1);
+ }
+ }
+}
+
+void Level_isld::postScreenUpdate_isld_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ postScreenUpdate_isld_screen15();
+ }
+}
+
+void Level_isld::postScreenUpdate_isld_screen15() {
+ switch (_res->_screensState[15].s0) {
+ case 2:
+ ++_screenCounterTable[15];
+ if (_screenCounterTable[15] >= 60) {
+ _res->_screensState[15].s0 = 1;
+ }
+ break;
+ case 0:
+ if (_res->_currentScreenResourceNum == 15 && (_andyObject->flags0 & 0x1F) == 1 && (_andyObject->flags0 & 0x30) == 0) {
+ BoundingBox b = { 72, 108, 110, 162 };
+ AndyLvlObjectData *andyData = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &andyData->boundingBox)) {
+ _g->setAndySpecialAnimation(3);
+ _res->_screensState[15].s0 = 2;
+ _res->_screensState[14].s0 = 1;
+ }
+ }
+ break;
+ }
+}
+
+void Level_isld::postScreenUpdate_isld_screen19() {
+ if (_res->_currentScreenResourceNum == 19) {
+ const int xPos = _andyObject->xPos + _andyObject->posTable[3].x;
+ if (xPos < 10 || xPos > 246) {
+ _g->_fallingAndyCounter = 2;
+ _g->_fallingAndyFlag = true;
+ _g->playAndyFallingCutscene(1);
+ }
+ }
+}
+
+void Level_isld::postScreenUpdate_isld_screen20() {
+ if (_res->_currentScreenResourceNum == 20) {
+ const int xPos = _andyObject->xPos + _andyObject->posTable[3].x;
+ if (xPos < 10 || xPos > 246) {
+ _g->_fallingAndyCounter = 2;
+ _g->_fallingAndyFlag = true;
+ _g->playAndyFallingCutscene(1);
+ }
+ }
+}
+
+void Level_isld::postScreenUpdate_isld_screen21() {
+ if (_res->_currentScreenResourceNum == 21) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ BoundingBox b1 = { 64, 0, 166, 120 };
+ if (_g->clipBoundingBox(&b1, &data->boundingBox)) {
+ const uint8_t _cl = _andyObject->flags0 & 0x1F;
+ const uint8_t _al = (_andyObject->flags0 >> 5) & 7;
+ if (_cl == 3 && _al == 4) {
+ _andyObject->directionKeyMask &= ~0xA;
+ BoundingBox b2 = { 64, 0, 166, 75 };
+ if (_g->clipBoundingBox(&b2, &data->boundingBox)) {
+ _g->setAndySpecialAnimation(4);
+ }
+ } else if (_cl == 8 && _al == 1) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(6);
+ _paf->unload(6);
+ _paf->unload(kPafAnimation_IslandAndyFalling);
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ }
+ }
+ }
+}
+
+void Level_isld::postScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ postScreenUpdate_isld_screen0();
+ break;
+ case 1:
+ postScreenUpdate_isld_screen1();
+ break;
+ case 2:
+ postScreenUpdate_isld_screen2();
+ break;
+ case 3:
+ postScreenUpdate_isld_screen3();
+ break;
+ case 4:
+ postScreenUpdate_isld_screen4();
+ break;
+ case 8:
+ postScreenUpdate_isld_screen8();
+ break;
+ case 9:
+ postScreenUpdate_isld_screen9();
+ break;
+ case 13:
+ postScreenUpdate_isld_screen13();
+ break;
+ case 15:
+ postScreenUpdate_isld_screen15();
+ break;
+ case 19:
+ postScreenUpdate_isld_screen19();
+ break;
+ case 20:
+ postScreenUpdate_isld_screen20();
+ break;
+ case 21:
+ postScreenUpdate_isld_screen21();
+ break;
+ }
+}
+
+void Level_isld::preScreenUpdate_isld_screen1() {
+ if (_res->_currentScreenResourceNum == 1) {
+ switch (_res->_screensState[1].s0) {
+ case 1:
+ case 2:
+ _res->_resLvlScreenBackgroundDataTable[1].currentBackgroundId = 1;
+ _res->_screensState[1].s0 = 1;
+ break;
+ default:
+ _res->_resLvlScreenBackgroundDataTable[1].currentBackgroundId = 0;
+ _res->_screensState[1].s0 = 0;
+ break;
+ }
+ }
+}
+
+void Level_isld::preScreenUpdate_isld_screen2() {
+ if (_res->_currentScreenResourceNum == 2) {
+ _res->_screensState[2].s0 = 0;
+ _res->_resLvlScreenBackgroundDataTable[2].currentMaskId = 0;
+ _screenCounterTable[2] = 0;
+ LvlObject *o = _g->findLvlObject(2, 0, 2);
+ if (o) {
+ o->actionKeyMask = 0;
+ o->xPos = 30;
+ o->yPos = 83;
+ o->frame = 0;
+ _g->setupLvlObjectBitmap(o);
+ }
+ }
+}
+
+void Level_isld::preScreenUpdate_isld_screen3() {
+ if (_res->_currentScreenResourceNum == 3) {
+ LvlObject *o = _g->findLvlObject(2, 0, 3);
+ if (o) {
+ o->xPos = 70;
+ o->yPos = 0;
+ o->frame = 22;
+ _g->setupLvlObjectBitmap(o);
+ }
+ }
+}
+
+void Level_isld::preScreenUpdate_isld_screen9() {
+ if (_res->_currentScreenResourceNum == 9) {
+ if (_checkpoint == 1) {
+ _checkpoint = 2;
+ }
+ }
+}
+
+void Level_isld::preScreenUpdate_isld_screen14() {
+ if (_res->_screensState[14].s0 != 0) {
+ _res->_resLvlScreenBackgroundDataTable[14].currentBackgroundId = 1;
+ _res->_resLvlScreenBackgroundDataTable[14].currentMaskId = 1;
+ } else {
+ _res->_resLvlScreenBackgroundDataTable[14].currentBackgroundId = 0;
+ _res->_resLvlScreenBackgroundDataTable[14].currentMaskId = 0;
+ }
+ if (_res->_currentScreenResourceNum == 14) {
+ if (_checkpoint == 2) {
+ _checkpoint = 3;
+ }
+ }
+}
+
+void Level_isld::preScreenUpdate_isld_screen15() {
+ if (_res->_screensState[15].s0 != 0) {
+ _res->_resLvlScreenBackgroundDataTable[15].currentBackgroundId = 1;
+ _res->_resLvlScreenBackgroundDataTable[15].currentSoundId = 1;
+ _res->_screensState[14].s0 = 1;
+ } else {
+ _res->_resLvlScreenBackgroundDataTable[15].currentBackgroundId = 0;
+ _res->_resLvlScreenBackgroundDataTable[15].currentSoundId = 0;
+ }
+}
+
+void Level_isld::preScreenUpdate_isld_screen16() {
+ if (_res->_currentScreenResourceNum == 16) {
+ if (_checkpoint == 3) {
+ _checkpoint = 4;
+ }
+ if (_res->_screensState[14].s0 == 0) {
+ _res->_screensState[14].s0 = 1;
+ _res->_screensState[15].s0 = 1;
+ }
+ }
+}
+
+void Level_isld::preScreenUpdate_isld_screen21() {
+ if (_res->_currentScreenResourceNum == 21) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(6);
+ }
+ }
+}
+
+void Level_isld::preScreenUpdate(int num) {
+ switch (num) {
+ case 1:
+ preScreenUpdate_isld_screen1();
+ break;
+ case 2:
+ preScreenUpdate_isld_screen2();
+ break;
+ case 3:
+ preScreenUpdate_isld_screen3();
+ break;
+ case 9:
+ preScreenUpdate_isld_screen9();
+ break;
+ case 14:
+ preScreenUpdate_isld_screen14();
+ break;
+ case 15:
+ preScreenUpdate_isld_screen15();
+ break;
+ case 16:
+ preScreenUpdate_isld_screen16();
+ break;
+ case 21:
+ preScreenUpdate_isld_screen21();
+ break;
+ }
+}
+
+void Level_isld::initialize() {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(kPafAnimation_IslandAndyFalling);
+ }
+ _g->resetWormHoleSprites();
+}
+
+void Level_isld::tick() {
+ _g->updateWormHoleSprites();
+}
+
+void Level_isld::terminate() {
+ if (!_paf->_skipCutscenes) {
+ // bugfix: original calls preload()
+ _paf->unload(kPafAnimation_IslandAndyFalling);
+ }
+}
diff --git a/Src/Vita_&_Switch/level5_lava.cpp b/Src/Vita_&_Switch/level5_lava.cpp
new file mode 100644
index 0000000..a7f8079
--- /dev/null
+++ b/Src/Vita_&_Switch/level5_lava.cpp
@@ -0,0 +1,619 @@
+
+// lava_hod - "river of fire"
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "util.h"
+#include "video.h"
+
+static const CheckpointData _lava_checkpointData[6] = {
+ { 114, 54, 0x300c, 2, 0, 2 },
+ { 12, 121, 0x300c, 232, 3, 2 },
+ { -5, 96, 0x300c, 39, 5, 2 },
+ { 131, 112, 0x300c, 39, 7, 2 },
+ { 8, 105, 0x300c, 232, 11, 2 },
+ { 19, 112, 0x700c, 39, 13, 2 }
+};
+
+static const uint8_t _lava_screenStartData[40] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_lava: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_lava_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _lava_screenStartData; }
+ virtual void initialize();
+ virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_lava_screen0();
+ void postScreenUpdate_lava_screen1();
+ void postScreenUpdate_lava_screen2_updateMask(int x, int y, int h, int unk, int screenNum, const uint8_t *p);
+ void postScreenUpdate_lava_screen2_addLvlObjects(const uint8_t *p);
+ void postScreenUpdate_lava_screen2();
+ void postScreenUpdate_lava_screen3_updateMask(int x, int y, int w, int h, int screenNum, uint8_t mask);
+ void postScreenUpdate_lava_screen3_updatePlatform(LvlObject *o, BoundingBox *b);
+ void postScreenUpdate_lava_screen3();
+ void postScreenUpdate_lava_screen4();
+ void postScreenUpdate_lava_screen5();
+ void postScreenUpdate_lava_screen6();
+ void postScreenUpdate_lava_screen7();
+ void postScreenUpdate_lava_screen8();
+ void postScreenUpdate_lava_screen10();
+ void postScreenUpdate_lava_screen11();
+ void postScreenUpdate_lava_screen12();
+ void postScreenUpdate_lava_screen13();
+ void postScreenUpdate_lava_screen14();
+ void postScreenUpdate_lava_screen15();
+
+ void preScreenUpdate_lava_screen0();
+ void preScreenUpdate_lava_screen3();
+ void preScreenUpdate_lava_screen6();
+ void preScreenUpdate_lava_screen10();
+ void preScreenUpdate_lava_screen13();
+ void preScreenUpdate_lava_screen15();
+
+ void setupScreenCheckpoint_lava_screen3();
+
+ uint8_t _screen1Counter;
+ uint8_t _screen2Counter;
+};
+
+Level *Level_lava_create() {
+ return new Level_lava;
+}
+
+static LvlObject *findLvlObject_lava(LvlObject *o) {
+ LvlObject *cur = o->nextPtr;
+ while (cur) {
+ if (o->type == cur->type && o->spriteNum == cur->spriteNum && o->screenNum == cur->screenNum) {
+ return cur;
+ }
+ cur = cur->nextPtr;
+ }
+ return 0;
+}
+
+void Level_lava::postScreenUpdate_lava_screen0() {
+ switch (_res->_screensState[0].s0) {
+ case 2:
+ ++_screenCounterTable[0];
+ if (_screenCounterTable[0] >= 11) {
+ _res->_screensState[0].s0 = 1;
+ }
+ break;
+ case 0:
+ if (_andyObject->anim == 2 && _andyObject->frame > 2) {
+ _res->_screensState[0].s0 = 2;
+ }
+ break;
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen1() {
+ BoundingBox b;
+ b.x1 = _andyObject->xPos + _andyObject->posTable[4].x;
+ b.y1 = _andyObject->yPos + _andyObject->posTable[4].y;
+ b.x2 = _andyObject->xPos + _andyObject->posTable[5].x;
+ b.y2 = _andyObject->yPos + _andyObject->posTable[5].y;
+ BoundingBox b2 = { 48, 156, 79, 167 };
+ if (_res->_currentScreenResourceNum == 1 && (_andyObject->flags0 & 0x1F) != 2 && _g->clipBoundingBox(&b, &b2)) {
+ LvlObject *o = _g->findLvlObject2(0, 0, 1);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ _screen1Counter = 51;
+ } else {
+ if (_screen1Counter != 0) {
+ --_screen1Counter;
+ }
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen2_updateMask(int x, int y, int h, int flag, int screenNum, const uint8_t *p) {
+ if (x < 0) {
+ x = 0;
+ }
+ if (y < 0) {
+ y = 0;
+ }
+ uint32_t maskOffset = Game::screenMaskOffset(_res->_screensBasePos[screenNum].u + x, _res->_screensBasePos[screenNum].v + y);
+ uint8_t *dst = _g->_screenMaskBuffer + maskOffset;
+ if (flag < 0) {
+ h >>= 3;
+ const int count = -flag;
+ for (int i = 0; i < h; ++i) {
+ memset(dst + (int8_t)p[0], 0, count); p += 2;
+ dst -= 512;
+ }
+ } else {
+ h >>= 3;
+ const int count = flag;
+ for (int i = 0; i < h; ++i) {
+ memset(dst + (int8_t)p[0], p[1], count); p += 2;
+ dst -= 512;
+ }
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen2_addLvlObjects(const uint8_t *p) {
+ do {
+ LvlObject *ptr = 0;
+ if (_g->_declaredLvlObjectsListCount < Game::kMaxLvlObjects) {
+ ptr = _g->_declaredLvlObjectsNextPtr;
+ _g->_declaredLvlObjectsNextPtr = _g->_declaredLvlObjectsNextPtr->nextPtr;
+ ++_g->_declaredLvlObjectsListCount;
+ ptr->spriteNum = 22;
+ ptr->type = 8;
+ _res->incLvlSpriteDataRefCounter(ptr);
+ _g->lvlObjectTypeCallback(ptr);
+ ptr->currentSprite = 0;
+ ptr->sssObject = 0;
+ ptr->nextPtr = 0;
+ }
+ if (ptr) {
+ ptr->nextPtr = _g->_lvlObjectsList3;
+ _g->_lvlObjectsList3 = ptr;
+ ptr->callbackFuncPtr = &Game::lvlObjectList3Callback;
+ ptr->screenNum = _res->_currentScreenResourceNum;
+ const uint16_t flags2 = READ_LE_UINT16(p + 4);
+ ptr->flags2 = flags2;
+ ptr->flags1 = (ptr->flags1 & ~0x30) | ((flags2 >> 10) & 0x30);
+ ptr->anim = READ_LE_UINT16(p + 2);
+ ptr->frame = 0;
+ _g->setupLvlObjectBitmap(ptr);
+ _g->setLvlObjectPosRelativeToPoint(ptr, 7, p[0], p[1]);
+ }
+ p += 6;
+ } while (READ_LE_UINT16(p + 4) != 0xFFFF);
+}
+
+void Level_lava::postScreenUpdate_lava_screen2() {
+ BoundingBox b;
+ b.x1 = _andyObject->xPos + _andyObject->posTable[4].x;
+ b.y1 = _andyObject->yPos + _andyObject->posTable[4].y;
+ b.x2 = _andyObject->xPos + _andyObject->posTable[5].x;
+ b.y2 = _andyObject->yPos + _andyObject->posTable[5].y;
+ BoundingBox b2 = { 40, 156, 72, 165 };
+ if (_res->_currentScreenResourceNum == 2 && (_andyObject->flags0 & 0x1F) != 2 && _g->clipBoundingBox(&b, &b2)) {
+ LvlObject *o = _g->findLvlObject2(0, 0, 2);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ _screen2Counter = 13;
+ } else {
+ if (_screen2Counter != 0) {
+ --_screen2Counter;
+ }
+ }
+ LvlObject *o = _g->findLvlObject(2, 0, 2);
+ assert(o);
+ if (_screen2Counter == 0) {
+ o->directionKeyMask = 4;
+ } else {
+ o->directionKeyMask = 1;
+ }
+ static const uint8_t maskData1[3 * 8] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
+ 0x00, 0x02, 0x00, 0x02, 0x01, 0x02, 0x01, 0x02,
+ 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02
+ };
+ postScreenUpdate_lava_screen2_updateMask(o->xPos + o->posTable[7].x, o->yPos + o->height - 1, o->height - 1, -3, o->screenNum, maskData1);
+ if (_res->_currentScreenResourceNum <= 2 && (o->flags0 & 0x1F) == 2) {
+ _g->setShakeScreen(2, 2);
+ }
+ if (o->levelData0x2988) {
+ _g->updateAndyObject(o);
+ }
+ postScreenUpdate_lava_screen2_updateMask(o->xPos + o->posTable[7].x, o->yPos + o->height - 1, o->height - 1, 3, o->screenNum, maskData1);
+ if (_res->_currentScreenResourceNum == 2 && (o->flags0 & 0x1F) == 1) {
+ if ((o->flags0 & 0xE0) == 0x20) {
+ static const uint8_t data[] = {
+ 0x75, 0xAA, 0x06, 0, 0x0F, 0x90,
+ 0x65, 0x9E, 0x09, 0, 0x0F, 0x10,
+ 0x80, 0x9E, 0x08, 0, 0x09, 0x10,
+ 0xFF, 0xFF, 0xFF, 0, 0xFF, 0xFF
+ };
+ postScreenUpdate_lava_screen2_addLvlObjects(data);
+ }
+ }
+ o = _g->findLvlObject(2, 1, 2);
+ assert(o);
+ if (_screen1Counter == 0) {
+ o->directionKeyMask = 1;
+ } else {
+ o->directionKeyMask = 4;
+ }
+ static const uint8_t maskData2[5 * 8] = {
+ 0x00, 0x00, 0x00, 0x01, 0xFF, 0x02, 0xFF, 0x02,
+ 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFE, 0x02,
+ 0xFE, 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFE, 0x02,
+ 0xFE, 0x02, 0xFD, 0x02, 0xFD, 0x02, 0xFD, 0x02,
+ 0xFD, 0x02, 0xFD, 0x02, 0xFD, 0x02, 0x00, 0x00
+ };
+ postScreenUpdate_lava_screen2_updateMask(o->xPos + o->posTable[7].x, o->yPos + o->height - 1, o->height - 1, -5, o->screenNum, maskData2);
+ if (_res->_currentScreenResourceNum <= 2 && (o->flags0 & 0x1F) == 2) {
+ _g->setShakeScreen(2, 2);
+ }
+ if (o->levelData0x2988) {
+ _g->updateAndyObject(o);
+ }
+ postScreenUpdate_lava_screen2_updateMask(o->xPos + o->posTable[7].x, o->yPos + o->height - 1, o->height - 1, 5, o->screenNum, maskData2);
+ if ((o->flags0 & 0x1F) == 1) {
+ if (_res->_currentScreenResourceNum == 2) {
+ if ((o->flags0 & 0xE0) == 0x20) {
+ static const uint8_t data[] = {
+ 0xC8, 0xAC, 0x06, 0, 0x0F, 0x90,
+ 0xB9, 0xA5, 0x08, 0, 0x0F, 0x10,
+ 0xDC, 0xA5, 0x09, 0, 0x09, 0x50,
+ 0xAB, 0xA0, 0x09, 0, 0x0F, 0x10,
+ 0xFF, 0xFF, 0xFF, 0, 0xFF, 0xFF
+ };
+ postScreenUpdate_lava_screen2_addLvlObjects(data);
+ }
+ }
+ if (_res->_currentScreenResourceNum <= 2) {
+ _g->setShakeScreen(3, 2);
+ }
+ }
+ if (_res->_currentScreenResourceNum == 2 && (o->flags0 & 0x1F) != 0 && _g->clipLvlObjectsBoundingBox(_andyObject, o, 0x44)) {
+ const int x = o->xPos + o->width / 2;
+ if (_andyObject->xPos > x) {
+ _andyObject->xPos += 10;
+ _g->setAndySpecialAnimation(0x10);
+ } else {
+ _andyObject->xPos -= 10;
+ _g->setAndySpecialAnimation(0x11);
+ }
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen3_updateMask(int x, int y, int w, int h, int screenNum, uint8_t mask) {
+ if (x < 0) {
+ x = 0;
+ }
+ if (y < 0) {
+ y = 0;
+ }
+ if (x + w >= 256) {
+ w = 256 - x;
+ }
+ if (y + h >= 192) {
+ h = 192 - y;
+ }
+ uint32_t maskOffset = Game::screenMaskOffset(_res->_screensBasePos[screenNum].u + x, _res->_screensBasePos[screenNum].v + y);
+ uint32_t gridOffset = Game::screenGridOffset(x, y);
+ w >>= 3;
+ h >>= 3;
+ for (int y = 0; y < h; ++y) {
+ memset(_g->_screenMaskBuffer + maskOffset, mask, w);
+ maskOffset += w;
+ memset(_g->_screenPosTable[4] + gridOffset, 1, w);
+ gridOffset += w;
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen3_updatePlatform(LvlObject *o, BoundingBox *b2) {
+ BoundingBox b;
+ b.x1 = o->xPos + o->posTable[1].x;
+ b.y1 = o->yPos + o->posTable[1].y - 2;
+ b.x2 = o->xPos + o->posTable[2].x;
+ b.y2 = o->yPos + o->posTable[1].y + 4;
+ postScreenUpdate_lava_screen3_updateMask(b.x1, b.y2, 32, 8, o->screenNum, 0);
+ if ((o->flags0 & 0x1F) != 0xB) {
+ if ((o->flags0 & 0x1F) != 2 && _g->clipBoundingBox(&b, b2)) {
+ o->directionKeyMask = 4;
+ }
+ _g->updateAndyObject(o);
+ const int x = o->xPos + o->posTable[1].x + 6;
+ const int y = o->yPos + o->posTable[1].y + 4;
+ postScreenUpdate_lava_screen3_updateMask(x, y, 32, 8, o->screenNum, 1);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen3() {
+ if (_res->_currentScreenResourceNum == 3) {
+ BoundingBox b;
+ b.x1 = _andyObject->xPos + _andyObject->posTable[4].x;
+ b.y1 = _andyObject->yPos + _andyObject->posTable[4].y;
+ b.x2 = _andyObject->xPos + _andyObject->posTable[5].x;
+ b.y2 = _andyObject->yPos + _andyObject->posTable[5].y;
+ LvlObject *o = _g->findLvlObject(2, 0, 3);
+ postScreenUpdate_lava_screen3_updatePlatform(o, &b);
+ o = findLvlObject_lava(o);
+ postScreenUpdate_lava_screen3_updatePlatform(o, &b);
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen4() {
+ if (_res->_currentScreenResourceNum == 4) {
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen5() {
+ if (_res->_currentScreenResourceNum == 5) {
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen6() {
+ if (_res->_currentScreenResourceNum == 6) {
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen7() {
+ if (_res->_currentScreenResourceNum == 7) {
+ if (_checkpoint == 2) {
+ BoundingBox b = { 104, 0, 239, 50 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _checkpoint = 3;
+ }
+ }
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen8() {
+ if (_res->_currentScreenResourceNum == 8) {
+ if (_andyObject->xPos + _andyObject->posTable[5].x < 72 || _andyObject->xPos + _andyObject->posTable[4].x < 72) {
+ const uint8_t flags = _andyObject->flags0 & 0x1F;
+ if (flags != 3 && flags != 7 && flags != 4) {
+ _g->setLavaAndyAnimation(175);
+ }
+ }
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen10() {
+ if (_res->_currentScreenResourceNum == 10) {
+ if (_screenCounterTable[10] < 37) {
+ if (_andyObject->yPos + _andyObject->posTable[3].y < 142) {
+ _andyObject->actionKeyMask = 0x40;
+ _andyObject->directionKeyMask = 0;
+ if (_checkpoint == 3) {
+ _checkpoint = 4;
+ _res->_screensState[10].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[10].currentMaskId = 1;
+ _g->setupScreenMask(10);
+ }
+ ++_screenCounterTable[10];
+ if (_screenCounterTable[10] == 13) {
+ _g->_levelRestartCounter = 12;
+ } else {
+ ++_screenCounterTable[10];
+ if (_screenCounterTable[10] == 37) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(7);
+ _paf->unload(7);
+ _video->clearPalette();
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen11() {
+ if (_res->_currentScreenResourceNum == 11) {
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen12() {
+ if (_res->_currentScreenResourceNum == 12) {
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ _g->setLavaAndyAnimation(175);
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen14() {
+ if (_res->_currentScreenResourceNum == 14) {
+ const int x = _andyObject->xPos;
+ const Point16_t *pos = _andyObject->posTable;
+ if (x + pos[5].x < 114 || x + pos[4].x < 114 || x + pos[3].x < 114 || x + pos[0].x < 114) {
+ _g->setLavaAndyAnimation(175);
+ }
+ }
+}
+
+void Level_lava::postScreenUpdate_lava_screen15() {
+ if (_res->_screensState[15].s0 != 0) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(8);
+ _paf->unload(8);
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ }
+}
+
+void Level_lava::postScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ postScreenUpdate_lava_screen0();
+ break;
+ case 1:
+ postScreenUpdate_lava_screen1();
+ break;
+ case 2:
+ postScreenUpdate_lava_screen2();
+ break;
+ case 3:
+ postScreenUpdate_lava_screen3();
+ break;
+ case 4:
+ postScreenUpdate_lava_screen4();
+ break;
+ case 5:
+ postScreenUpdate_lava_screen5();
+ break;
+ case 6:
+ postScreenUpdate_lava_screen6();
+ break;
+ case 7:
+ postScreenUpdate_lava_screen7();
+ break;
+ case 8:
+ postScreenUpdate_lava_screen8();
+ break;
+ case 10:
+ postScreenUpdate_lava_screen10();
+ break;
+ case 11:
+ postScreenUpdate_lava_screen11();
+ break;
+ case 12:
+ postScreenUpdate_lava_screen12();
+ break;
+ case 13:
+ postScreenUpdate_lava_screen13();
+ break;
+ case 14:
+ postScreenUpdate_lava_screen14();
+ break;
+ case 15:
+ postScreenUpdate_lava_screen15();
+ break;
+ }
+}
+
+void Level_lava::preScreenUpdate_lava_screen0() {
+ if (_res->_screensState[0].s0 != 0) {
+ _res->_screensState[0].s0 = 1;
+ }
+}
+
+void Level_lava::preScreenUpdate_lava_screen3() {
+ if (_res->_currentScreenResourceNum == 3) {
+ if (_checkpoint == 0) {
+ _checkpoint = 1;
+ }
+ }
+}
+
+void Level_lava::preScreenUpdate_lava_screen6() {
+ if (_res->_currentScreenResourceNum == 6) {
+ if (_checkpoint == 1) {
+ _checkpoint = 2;
+ }
+ }
+}
+
+void Level_lava::preScreenUpdate_lava_screen10() {
+ const int num = (_res->_screensState[10].s0 == 0) ? 0 : 1;
+ if (_res->_screensState[10].s0 != 0 && _res->_screensState[10].s0 != 1) {
+ _res->_screensState[10].s0 = 1;
+ }
+ _res->_resLvlScreenBackgroundDataTable[10].currentMaskId = num;
+ if (_res->_currentScreenResourceNum == 10) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(7);
+ }
+ }
+}
+
+void Level_lava::preScreenUpdate_lava_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ if (_checkpoint == 4) {
+ _checkpoint = 5;
+ }
+ }
+}
+
+void Level_lava::preScreenUpdate_lava_screen15() {
+ if (_res->_screensState[15].s0 == 0) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(8);
+ }
+ }
+}
+
+void Level_lava::preScreenUpdate(int num) {
+ switch (num) {
+ case 1:
+ preScreenUpdate_lava_screen0();
+ break;
+ case 3:
+ preScreenUpdate_lava_screen3();
+ break;
+ case 6:
+ preScreenUpdate_lava_screen6();
+ break;
+ case 10:
+ preScreenUpdate_lava_screen10();
+ break;
+ case 13:
+ preScreenUpdate_lava_screen13();
+ break;
+ case 15:
+ preScreenUpdate_lava_screen15();
+ break;
+ }
+}
+
+void Level_lava::initialize() {
+ _g->loadTransformLayerData(Game::_pwr2_screenTransformData);
+ _g->resetWormHoleSprites();
+ _screen1Counter = 0;
+ _screen2Counter = 0;
+}
+
+void Level_lava::terminate() {
+ _g->unloadTransformLayerData();
+}
+
+const uint8_t Game::_lava_screenTransformLut[] = {
+ 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+void Level_lava::tick() {
+ _video->_displayShadowLayer = Game::_lava_screenTransformLut[_res->_currentScreenResourceNum * 2] != 0;
+ assert(Game::_lava_screenTransformLut[_res->_currentScreenResourceNum * 2 + 1] == 0);
+ _g->restoreAndyCollidesLava();
+ _g->updateWormHoleSprites();
+}
+
+void Level_lava::setupScreenCheckpoint_lava_screen3() {
+ LvlObject *ptr = _g->findLvlObject(2, 0, 3);
+ assert(ptr);
+ ptr->flags0 &= 0xFC00;
+ ptr->xPos = 138;
+ ptr->yPos = 157;
+ ptr->anim = 0;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr = findLvlObject_lava(ptr);
+ assert(ptr);
+ ptr->flags0 &= 0xFC00;
+ ptr->anim = 0;
+ ptr->frame = 0;
+ ptr->directionKeyMask = 0;
+ ptr->xPos = 66;
+ ptr->yPos = 157;
+}
+
+void Level_lava::setupScreenCheckpoint(int num) {
+ switch (num) {
+ case 3:
+ setupScreenCheckpoint_lava_screen3();
+ break;
+ }
+}
diff --git a/Src/Vita_&_Switch/level6_pwr2.cpp b/Src/Vita_&_Switch/level6_pwr2.cpp
new file mode 100644
index 0000000..a5b6b88
--- /dev/null
+++ b/Src/Vita_&_Switch/level6_pwr2.cpp
@@ -0,0 +1,205 @@
+
+// pwr2_hod - "caverns of doom"
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "video.h"
+
+static const CheckpointData _pwr2_checkpointData[3] = {
+ { 127, 121, 0x300c, 232, 0, 2 },
+ { 10, 132, 0x300c, 232, 3, 2 },
+ { 104, 129, 0x300c, 232, 7, 2 }
+};
+
+static const uint8_t _pwr2_screenStartData[32] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_pwr2: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_pwr2_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _pwr2_screenStartData; }
+ virtual void initialize();
+ virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ //virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_pwr2_screen2();
+ void postScreenUpdate_pwr2_screen3();
+ void postScreenUpdate_pwr2_screen5();
+ void postScreenUpdate_pwr2_screen8();
+
+ void preScreenUpdate_pwr2_screen2();
+ void preScreenUpdate_pwr2_screen3();
+ void preScreenUpdate_pwr2_screen5();
+ void preScreenUpdate_pwr2_screen7();
+};
+
+Level *Level_pwr2_create() {
+ return new Level_pwr2;
+}
+
+void Level_pwr2::postScreenUpdate_pwr2_screen2() {
+ uint8_t mask;
+ switch (_res->_screensState[2].s0) {
+ case 1:
+ mask = 1;
+ break;
+ case 2:
+ mask = 2;
+ break;
+ default:
+ mask = 0;
+ break;
+ }
+ _res->_resLvlScreenBackgroundDataTable[2].currentMaskId = mask;
+ if (_res->_screensState[2].s3 != mask) {
+ _g->setupScreenMask(2);
+ }
+}
+
+void Level_pwr2::postScreenUpdate_pwr2_screen3() {
+ if (_res->_currentScreenResourceNum == 3) {
+ if ((_andyObject->flags1 & 0x30) == 0) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ BoundingBox b = { 115, 27, 127, 55 };
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ const uint8_t flags = _andyObject->flags0;
+ if ((flags & 0x1F) == 0 && (flags & 0xE0) == 0xE0) {
+ LvlObject *o = _g->findLvlObject2(0, 0, 3);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ } else {
+ LvlObject *o = _g->findLvlObject2(0, 0, 3);
+ if (o) {
+ if (o->objectUpdateType == 4) {
+ _res->_screensState[2].s0 = 2;
+ } else {
+ _g->setAndySpecialAnimation(3);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void Level_pwr2::postScreenUpdate_pwr2_screen5() {
+ if (_res->_screensState[5].s0 == 1) {
+ _res->_screensState[5].s0 = 2;
+ if (_checkpoint == 1) {
+ _checkpoint = 2;
+ }
+ if (!_paf->_skipCutscenes) {
+ _paf->play(9);
+ _paf->unload(9);
+ }
+ _video->clearPalette();
+ _g->restartLevel();
+ }
+}
+
+void Level_pwr2::postScreenUpdate_pwr2_screen8() {
+ if (_res->_screensState[8].s0 != 0) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(10);
+ _paf->unload(10);
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ }
+}
+
+void Level_pwr2::postScreenUpdate(int num) {
+ switch (num) {
+ case 2:
+ postScreenUpdate_pwr2_screen2();
+ break;
+ case 3:
+ postScreenUpdate_pwr2_screen3();
+ break;
+ case 5:
+ postScreenUpdate_pwr2_screen5();
+ break;
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ postScreenUpdate_pwr2_screen8();
+ break;
+ }
+}
+
+void Level_pwr2::preScreenUpdate_pwr2_screen2() {
+ if (_checkpoint == 1) {
+ if (_res->_screensState[2].s0 == 0) {
+ _res->_screensState[2].s0 = 2;
+ _res->_resLvlScreenBackgroundDataTable[2].currentMaskId = 2;
+ }
+ }
+}
+
+void Level_pwr2::preScreenUpdate_pwr2_screen3() {
+ if (_res->_currentScreenResourceNum == 3 && _checkpoint == 0) {
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ BoundingBox b = { 0, 103, 122, 191 };
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _checkpoint = 1;
+ }
+ }
+}
+
+void Level_pwr2::preScreenUpdate_pwr2_screen5() {
+ if (_res->_currentScreenResourceNum == 5) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(9);
+ }
+ }
+}
+
+void Level_pwr2::preScreenUpdate_pwr2_screen7() {
+ if (_res->_currentScreenResourceNum == 7) {
+ _res->_screensState[5].s0 = 2;
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(10);
+ }
+ }
+}
+
+void Level_pwr2::preScreenUpdate(int num) {
+ switch (num) {
+ case 2:
+ preScreenUpdate_pwr2_screen2();
+ break;
+ case 3:
+ preScreenUpdate_pwr2_screen3();
+ break;
+ case 5:
+ preScreenUpdate_pwr2_screen5();
+ break;
+ case 7:
+ preScreenUpdate_pwr2_screen7();
+ break;
+ }
+}
+
+void Level_pwr2::initialize() {
+ _g->loadTransformLayerData(Game::_pwr1_screenTransformData);
+}
+
+void Level_pwr2::terminate() {
+ _g->unloadTransformLayerData();
+}
+
+const uint8_t Game::_pwr2_screenTransformLut[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 9, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+void Level_pwr2::tick() {
+ _video->_displayShadowLayer = Game::_pwr2_screenTransformLut[_res->_currentScreenResourceNum * 2] != 0;
+}
diff --git a/Src/Vita_&_Switch/level7_lar1.cpp b/Src/Vita_&_Switch/level7_lar1.cpp
new file mode 100644
index 0000000..c8db812
--- /dev/null
+++ b/Src/Vita_&_Switch/level7_lar1.cpp
@@ -0,0 +1,912 @@
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "util.h"
+#include "video.h"
+
+static const CheckpointData _lar1_checkpointData[9] = {
+ { -8, 145, 0x300c, 207, 0, 2 },
+ { 60, 49, 0x300c, 232, 2, 2 },
+ { 6, 41, 0x300c, 232, 6, 2 },
+ { 32, 37, 0x300c, 232, 8, 2 },
+ { 172, 105, 0x300c, 232, 9, 2 },
+ { 40, 145, 0x300c, 232, 14, 2 },
+ { 213, 25, 0x700c, 232, 16, 2 },
+ { 123, 57, 0x300c, 232, 20, 2 },
+ { 224, 43, 0x700c, 232, 5, 2 }
+};
+
+static const uint8_t _lar1_screenStartData[56] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_lar1: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_lar1_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _lar1_screenStartData; }
+ virtual void initialize();
+ virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_lar1_screen0();
+ void postScreenUpdate_lar1_screen3();
+ void postScreenUpdate_lar1_screen4();
+ void postScreenUpdate_lar1_screen5();
+ void postScreenUpdate_lar1_screen8();
+ void postScreenUpdate_lar1_screen9();
+ void postScreenUpdate_lar1_screen12();
+ void postScreenUpdate_lar1_screen13();
+ void postScreenUpdate_lar1_screen14();
+ void postScreenUpdate_lar1_screen15();
+ void postScreenUpdate_lar1_screen16();
+ void postScreenUpdate_lar1_screen18();
+ void postScreenUpdate_lar1_screen19();
+ void postScreenUpdate_lar1_screen20();
+ void postScreenUpdate_lar1_screen22();
+ void postScreenUpdate_lar1_screen24();
+
+ void preScreenUpdate_lar1_screen0();
+ void preScreenUpdate_lar1_screen2();
+ void preScreenUpdate_lar1_screen6();
+ void preScreenUpdate_lar1_screen8();
+ void preScreenUpdate_lar1_screen10();
+ void preScreenUpdate_lar1_screen11();
+ void preScreenUpdate_lar1_screen12();
+ void preScreenUpdate_lar1_screen13();
+ void preScreenUpdate_lar1_screen14();
+ void preScreenUpdate_lar1_screen15();
+ void preScreenUpdate_lar1_screen16();
+ void preScreenUpdate_lar1_screen17();
+ void preScreenUpdate_lar1_screen18();
+ void preScreenUpdate_lar1_screen19();
+ void preScreenUpdate_lar1_screen20();
+ void preScreenUpdate_lar1_screen23();
+ void preScreenUpdate_lar1_screen24();
+
+ void setupScreenCheckpoint_lar1_screen24_initGates();
+ void setupScreenCheckpoint_lar1_screen24_initAndy(int num);
+ void setupScreenCheckpoint_lar1_screen24();
+};
+
+Level *Level_lar1_create() {
+ return new Level_lar1;
+}
+
+static uint8_t _lar1_gatesData[13 * 4] = {
+ 0x02, 0x00, 0x00, 0x00, // screen 4
+ 0x12, 0x00, 0x00, 0x00, // screen 5
+ 0x32, 0x09, 0x02, 0x00, // screen 5
+ 0x32, 0x0E, 0x02, 0x00, // screen 5
+ 0x02, 0x00, 0x00, 0x00, // screen 8
+ 0x02, 0x00, 0x00, 0x00, // screen 8
+ 0x02, 0x00, 0x00, 0x00, // screen 9
+ 0x02, 0x00, 0x00, 0x00, // screen 13
+ 0x02, 0x00, 0x00, 0x00, // screen 14
+ 0x02, 0x00, 0x00, 0x00, // screen 15
+ 0x02, 0x00, 0x00, 0x00, // screen 16
+ 0x02, 0x00, 0x00, 0x00, // screen 18
+ 0x02, 0x00, 0x00, 0x00 // screen 18
+};
+
+static BoundingBox _lar1_switchesBbox[24] = {
+ { 203, 162, 213, 166 },
+ { 68, 86, 78, 90 },
+ { 195, 58, 205, 62 },
+ { 111, 171, 125, 175 },
+ { 111, 171, 125, 175 },
+ { 202, 171, 212, 175 },
+ { 158, 29, 170, 57 },
+ { 158, 29, 170, 57 },
+ { 158, 29, 170, 57 },
+ { 158, 29, 170, 57 },
+ { 230, 139, 242, 167 },
+ { 230, 139, 242, 167 },
+ { 230, 139, 242, 167 },
+ { 86, 37, 98, 65 },
+ { 86, 37, 98, 65 },
+ { 86, 37, 98, 65 },
+ { 238, 18, 250, 46 },
+ { 14, 138, 26, 166 },
+ { 14, 138, 26, 166 },
+ { 51, 157, 61, 161 },
+ { 51, 157, 61, 161 },
+ { 51, 157, 61, 161 },
+ { 202, 157, 212, 161 },
+ { 32, 145, 44, 173 }
+};
+
+static uint8_t _lar1_switchesData[24 * 4] = { // screenNum,state,spriteNum,gateNum
+ 0x04, 0x07, 0x01, 0x00, 0x05, 0x01, 0x01, 0x01, 0x08, 0x0F, 0x02, 0x00, 0x08, 0x07, 0x03, 0x04,
+ 0x08, 0x07, 0xFF, 0x05, 0x08, 0x07, 0x04, 0x04, 0x09, 0x27, 0xFF, 0x06, 0x09, 0x29, 0x03, 0x02,
+ 0x09, 0x29, 0xFF, 0x03, 0x09, 0x29, 0xFF, 0x04, 0x0A, 0x0B, 0x04, 0x02, 0x0A, 0x0B, 0xFF, 0x03,
+ 0x0A, 0x0B, 0xFF, 0x04, 0x0D, 0x07, 0xFF, 0x07, 0x0D, 0x0B, 0x00, 0x00, 0x0D, 0x0B, 0xFF, 0x01,
+ 0x0E, 0x07, 0x02, 0x08, 0x0F, 0x27, 0x01, 0x09, 0x0F, 0x2F, 0xFF, 0x0B, 0x12, 0x07, 0x01, 0x0B,
+ 0x12, 0x0B, 0xFF, 0x0D, 0x12, 0x0B, 0xFF, 0x0C, 0x12, 0x15, 0x02, 0x0B, 0x15, 0x2F, 0x00, 0x0E
+};
+
+static const uint8_t _lar1_setupScreen24Data[8 * 3] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x02, 0x08, 0x02, 0x0B,
+ 0x08, 0x10, 0x0C, 0x09, 0x11, 0x0E, 0x0D, 0x17
+};
+
+static void setLvlObjectUpdateType3_lar1(Game *g, int screenNum) {
+ const uint8_t *p = Game::_lar1_maskData;
+ for (int i = 0; i < 15; ++i) {
+ if (p[4] == screenNum && p[1] == 1) {
+ LvlObject *o = g->findLvlObject2(0, p[5], p[4]);
+ if (o) {
+ o->objectUpdateType = 3;
+ }
+ }
+ p += 6;
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen0() {
+ switch (_res->_screensState[0].s0) {
+ case 0:
+ _res->_screensState[0].s0 = 3;
+ break;
+ case 1:
+ if (_res->_currentScreenResourceNum == 0) {
+ BoundingBox b = { 0, 0, 63, 78 };
+ LvlObject *o = _g->findLvlObjectBoundingBox(&b);
+ if (o) {
+ if (((ShootLvlObjectData *)_g->getLvlObjectDataPtr(o, kObjectDataTypeShoot))->type == 6) {
+ _res->_screensState[0].s0 = 4;
+ }
+ }
+ }
+ break;
+ case 2:
+ if (_res->_currentScreenResourceNum == 0) {
+ BoundingBox b = { 0, 0, 14, 74 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ if (!_g->_fadePalette) {
+ _checkpoint = 1;
+ _g->_levelRestartCounter = 6;
+ } else {
+ _andyObject->directionKeyMask = 0;
+ }
+ }
+ }
+ break;
+ case 3:
+ ++_screenCounterTable[0];
+ if (_screenCounterTable[0] < 7) {
+ _andyObject->actionKeyMask = 1;
+ _andyObject->directionKeyMask = 2;
+ } else if (_screenCounterTable[0] == 7) {
+ _res->_resLvlScreenBackgroundDataTable[0].currentMaskId = 1;
+ _g->setupScreenMask(0);
+ } else if (_screenCounterTable[0] >= 45) {
+ _res->_screensState[0].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 1;
+ } else if (_screenCounterTable[0] == 11) {
+ _g->setShakeScreen(3, 2);
+ } else if (_screenCounterTable[0] == 13) {
+ _g->setShakeScreen(2, 4);
+ } else if (_screenCounterTable[0] == 19) {
+ _g->setShakeScreen(2, 4);
+ } else if (_screenCounterTable[0] == 25) {
+ _g->setShakeScreen(2, 6);
+ }
+ break;
+ case 4:
+ ++_screenCounterTable[0];
+ if (_screenCounterTable[0] >= 64) {
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 2;
+ _res->_resLvlScreenBackgroundDataTable[0].currentMaskId = 2;
+ _g->setupScreenMask(0);
+ _res->_screensState[0].s0 = 2;
+ }
+ break;
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen3() {
+ if (_res->_currentScreenResourceNum == 3) {
+ BoundingBox b = { 46, 0, 210, 106 };
+ _g->setAndyAnimationForArea(&b, 16);
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen4() {
+ LvlObject *o = _g->findLvlObject(2, 0, 4);
+ _g->updateGatesLar(o, &_lar1_gatesData[0 * 4], 0);
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen5() {
+ LvlObject *o1 = _g->findLvlObject(2, 0, 5);
+ _g->updateGatesLar(o1, &_lar1_gatesData[1 * 4], 1);
+ LvlObject *o2 = _g->findLvlObject(2, 1, 5);
+ _g->updateGatesLar(o2, &_lar1_gatesData[2 * 4], 2);
+ LvlObject *o3 = _g->findLvlObject(2, 2, 5);
+ _g->updateGatesLar(o3, &_lar1_gatesData[3 * 4], 3);
+ if (_res->_currentScreenResourceNum == 5) {
+ if (_checkpoint >= 1 && _checkpoint <= 3) {
+ BoundingBox b = { 194, 0, 255, 88 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox) && (_lar1_gatesData[6 * 4] & 0xF0) == 0x10) {
+ _checkpoint = 2;
+ _screenCounterTable[26] = (_lar1_gatesData[7 * 4] & 0xF0) != 0 ? 3 : 1;
+ }
+ }
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen8() {
+ LvlObject *o1 = _g->findLvlObject(2, 0, 8);
+ _g->updateGatesLar(o1, &_lar1_gatesData[4 * 4], 4);
+ LvlObject *o2 = _g->findLvlObject(2, 1, 8);
+ _g->updateGatesLar(o2, &_lar1_gatesData[5 * 4], 5);
+ if (_res->_currentScreenResourceNum == 8) {
+ if (_checkpoint >= 1 && _checkpoint <= 3) {
+ BoundingBox b = { 104, 0, 255, 80 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _checkpoint = 3;
+ _screenCounterTable[26] = (_lar1_gatesData[6 * 4] & 0xF0) != 0 ? 5 : 4;
+ if ((_lar1_gatesData[7 * 4] & 0xF0) == 0x10) {
+ _screenCounterTable[26] += 2;
+ }
+ }
+ }
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen9() {
+ LvlObject *o = _g->findLvlObject(2, 0, 9);
+ _g->updateGatesLar(o, &_lar1_gatesData[6 * 4], 6);
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen12() {
+ if (_res->_currentScreenResourceNum == 12) {
+ const int counter = _screenCounterTable[12];
+ if (counter < 8 && (_andyObject->flags0 & 0x1F) == 3) {
+ static const uint8_t data[8 * 4] = {
+ 0x05, 0x00, 0x00, 0x04, 0x06, 0x00, 0x00, 0x00, 0x07, 0xFB, 0x04, 0x05, 0x08, 0xFA, 0x04, 0x05,
+ 0x09, 0xF9, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x04, 0xF8, 0xF7, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04
+ };
+ const uint8_t num = (_andyObject->flags0 >> 5) & 7;
+ const int offset = counter * 4;
+ if (data[offset + 2] == num || data[offset + 3] == num) {
+ BoundingBox b[] = {
+ { 205, 0, 227, 97 },
+ { 200, 16, 207, 55 },
+ { 128, 16, 159, 71 },
+ { 56, 32, 87, 87 },
+ { 179, 112, 207, 167 },
+ { 179, 64, 207, 103 },
+ { 32, 112, 87, 167 },
+ { 32, 112, 87, 167 }
+ };
+ AndyLvlObjectData *andyData = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b[counter], &andyData->boundingBox)) {
+ ++_screenCounterTable[12];
+ _g->updateGateMaskLar((int8_t)data[offset]);
+ _g->updateGateMaskLar((int8_t)data[offset + 1]);
+ }
+ }
+ }
+ _g->restoreAndyCollidesLava();
+ _g->setLavaAndyAnimation(182);
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen13() {
+ LvlObject *o = _g->findLvlObject(2, 0, 13);
+ _g->updateGatesLar(o, &_lar1_gatesData[7 * 4], 7);
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen14() {
+ if (_res->_currentScreenResourceNum == 14) {
+ switch (_res->_screensState[14].s0) {
+ case 0:
+ if (_g->_currentLevelCheckpoint == 4) {
+ if (_checkpoint == 5) {
+ _g->_currentLevelCheckpoint = _checkpoint;
+ if (!_paf->_skipCutscenes) {
+ _paf->play(11);
+ _paf->unload(11);
+ }
+ _video->clearPalette();
+ _g->restartLevel();
+ }
+ } else {
+ BoundingBox b = { 33, 60, 76, 89 };
+ LvlObject *o = _g->findLvlObjectBoundingBox(&b);
+ if (o) {
+ if (((ShootLvlObjectData *)_g->getLvlObjectDataPtr(o, kObjectDataTypeShoot))->type == 6) {
+ _res->_screensState[14].s0 = 3;
+ }
+ }
+ }
+ break;
+ case 1: {
+ BoundingBox b = { 172, 23, 222, 53 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ if ((_andyObject->flags0 & 0x1F) == 0 && (_andyObject->flags0 & 0xE0) == 0xE0) {
+ _res->_screensState[14].s0 = 4;
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(12);
+ }
+ } else {
+ _g->setAndySpecialAnimation(3);
+ }
+ }
+ }
+ break;
+ case 3:
+ ++_screenCounterTable[14];
+ if (_screenCounterTable[14] == 1) {
+ _res->_resLvlScreenBackgroundDataTable[14].currentMaskId = 1;
+ _g->setupScreenMask(14);
+ } else if (_screenCounterTable[14] >= 20) {
+ _res->_screensState[14].s0 = 1;
+ } else if (_screenCounterTable[14] == 7 || _screenCounterTable[14] == 9 || _screenCounterTable[14] == 11 || _screenCounterTable[14] == 13 || _screenCounterTable[14] == 15) {
+ _g->setShakeScreen(3, 2);
+ }
+ break;
+ case 4:
+ ++_screenCounterTable[14];
+ if (_screenCounterTable[14] >= 37) {
+ _res->_screensState[14].s0 = 2;
+ _res->_resLvlScreenBackgroundDataTable[14].currentBackgroundId = 1;
+ _res->_resLvlScreenBackgroundDataTable[14].currentMaskId = 2;
+ if (!_paf->_skipCutscenes) {
+ _paf->play(12);
+ _paf->unload(12);
+ }
+ _video->clearPalette();
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ break;
+ }
+ }
+ LvlObject *o = _g->findLvlObject(2, 0, 14);
+ _g->updateGatesLar(o, &_lar1_gatesData[8 * 4], 8);
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen15() {
+ LvlObject *o = _g->findLvlObject(2, 0, 15);
+ _g->updateGatesLar(o, &_lar1_gatesData[9 * 4], 9);
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen16() {
+ LvlObject *o = _g->findLvlObject(2, 0, 16);
+ _g->updateGatesLar(o, &_lar1_gatesData[10 * 4], 10);
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen18() {
+ LvlObject *o1 = _g->findLvlObject(2, 0, 18);
+ _g->updateGatesLar(o1, &_lar1_gatesData[11 * 4], 11);
+ LvlObject *o2 = _g->findLvlObject(2, 1, 18);
+ _g->updateGatesLar(o2, &_lar1_gatesData[12 * 4], 12);
+ if ((_lar1_switchesData[0x59] & 0x40) == 0 && (_lar1_switchesData[0x59] & 0x80) != 0) {
+ if ((_lar1_switchesData[0x4D] & 1) == 0) {
+ _lar1_switchesData[0x4D] |= 1;
+ }
+ }
+ if ((_lar1_switchesData[0x4D] & 0x40) == 0 && (_lar1_switchesData[0x4D] & 0x80) != 0) {
+ if ((_lar1_switchesData[0x59] & 1) == 0) {
+ _lar1_switchesData[0x59] |= 1;
+ }
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen19() {
+ if (_screenCounterTable[19] == 0) {
+ if (_res->_currentScreenResourceNum == 19) {
+ BoundingBox b = { 160, 0, 209, 71 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ _g->_plasmaCannonFlags |= 2;
+ if (!_paf->_skipCutscenes) {
+ _paf->play(13);
+ _paf->unload(13);
+ }
+ _video->clearPalette();
+ ++_screenCounterTable[19]; // bugfix: conditioned with _pafSkipCutscenes
+ _g->setupScreen(_andyObject->screenNum);
+ Game::_lar1_maskData[12 * 6 + 1] = 0;
+ Game::_lar1_maskData[13 * 6 + 1] = 0;
+ _andyObject->xPos = 204;
+ _andyObject->yPos = 25;
+ _andyObject->anim = 232;
+ _andyObject->frame = 0;
+ _andyObject->flags1 = (_andyObject->flags1 & ~0x20) | 0x10;
+ _andyObject->directionKeyMask = 0;
+ _andyObject->actionKeyMask = 0;
+ _g->setupLvlObjectBitmap(_andyObject);
+ _g->removeLvlObject(_andyObject);
+ _g->clearLvlObjectsList0();
+ }
+ }
+ } else if (_res->_screensState[19].s0 == 2) {
+ ++_screenCounterTable[19];
+ if (_screenCounterTable[19] == 2) {
+ _res->_resLvlScreenBackgroundDataTable[19].currentMaskId = 1;
+ _g->setupScreenMask(19);
+ } else if (_screenCounterTable[19] >= 14) {
+ _res->_screensState[19].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[19].currentBackgroundId = 1;
+ }
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen20() {
+ if (_res->_currentScreenResourceNum == 20) {
+ postScreenUpdate_lar1_screen19();
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen22() {
+ if (_res->_currentScreenResourceNum == 22) {
+ BoundingBox b = { 36, 0, 208, 82 };
+ _g->setAndyAnimationForArea(&b, 16);
+ }
+}
+
+void Level_lar1::postScreenUpdate_lar1_screen24() {
+ if (_res->_currentScreenResourceNum == 24) {
+ if ((_andyObject->flags0 & 0x1F) == 5) {
+ _g->_plasmaCannonFlags |= 1;
+ }
+ BoundingBox b = { 50, 168, 113, 191 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(14);
+ _paf->unload(14);
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ }
+ }
+}
+
+void Level_lar1::postScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ postScreenUpdate_lar1_screen0();
+ break;
+ case 3:
+ postScreenUpdate_lar1_screen3();
+ break;
+ case 4:
+ postScreenUpdate_lar1_screen4();
+ break;
+ case 5:
+ postScreenUpdate_lar1_screen5();
+ break;
+ case 8:
+ postScreenUpdate_lar1_screen8();
+ break;
+ case 9:
+ postScreenUpdate_lar1_screen9();
+ break;
+ case 12:
+ postScreenUpdate_lar1_screen12();
+ break;
+ case 13:
+ postScreenUpdate_lar1_screen13();
+ break;
+ case 14:
+ postScreenUpdate_lar1_screen14();
+ break;
+ case 15:
+ postScreenUpdate_lar1_screen15();
+ break;
+ case 16:
+ postScreenUpdate_lar1_screen16();
+ break;
+ case 18:
+ postScreenUpdate_lar1_screen18();
+ break;
+ case 19:
+ postScreenUpdate_lar1_screen19();
+ break;
+ case 20:
+ postScreenUpdate_lar1_screen20();
+ break;
+ case 22:
+ postScreenUpdate_lar1_screen22();
+ break;
+ case 24:
+ postScreenUpdate_lar1_screen24();
+ break;
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen0() {
+ if (_res->_currentScreenResourceNum == 0) {
+ switch (_res->_screensState[0].s0) {
+ case 0:
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 0;
+ _res->_resLvlScreenBackgroundDataTable[0].currentMaskId = 0;
+ _screenCounterTable[0] = 0;
+ break;
+ case 3:
+ _res->_screensState[0].s0 = 1;
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 1;
+ _res->_resLvlScreenBackgroundDataTable[0].currentMaskId = 1;
+ _screenCounterTable[0] = 45;
+ break;
+ case 4:
+ _screenCounterTable[0] = 64;
+ _res->_screensState[0].s0 = 2;
+ _res->_resLvlScreenBackgroundDataTable[0].currentBackgroundId = 2;
+ _res->_resLvlScreenBackgroundDataTable[0].currentMaskId = 2;
+ break;
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen2() {
+ if (_res->_currentScreenResourceNum == 2) {
+ if (_checkpoint == 0) {
+ _checkpoint = 1;
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen6() {
+ if (_res->_currentScreenResourceNum == 6) {
+ if (_checkpoint >= 1 && _checkpoint <= 3) {
+ if ((_lar1_gatesData[6 * 4] & 0xF0) == 0) {
+ _checkpoint = 2;
+ _screenCounterTable[26] = ((_lar1_gatesData[7 * 4] & 0xF0) != 0) ? 2 : 0;
+ }
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen8() {
+ if (_res->_currentScreenResourceNum == 8) {
+ setLvlObjectUpdateType3_lar1(_g, 8);
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen10() {
+ if (_res->_currentScreenResourceNum == 10) {
+ setLvlObjectUpdateType3_lar1(_g, 10);
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen11() {
+ if (_res->_currentScreenResourceNum == 11) {
+ if (_checkpoint >= 2 && _checkpoint <= 3) {
+ if ((_lar1_gatesData[7 * 4] & 0xF) == 1 && (_lar1_gatesData[6 * 4] & 0xF) == 1) {
+ _checkpoint = 4;
+ }
+ }
+ _g->unloadTransformLayerData();
+ _video->_displayShadowLayer = false;
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen12() {
+ if (_res->_currentScreenResourceNum == 12) {
+ setLvlObjectUpdateType3_lar1(_g, 12);
+ _g->loadTransformLayerData(Game::_pwr2_screenTransformData);
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ _g->unloadTransformLayerData();
+ _video->_displayShadowLayer = false;
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen14() {
+ switch (_res->_screensState[14].s0) {
+ case 0:
+ _res->_resLvlScreenBackgroundDataTable[14].currentBackgroundId = 0;
+ _res->_resLvlScreenBackgroundDataTable[14].currentMaskId = 0;
+ _screenCounterTable[14] = 0;
+ break;
+ case 3:
+ _res->_screensState[14].s0 = 1;
+ _screenCounterTable[14] = 20;
+ break;
+ case 4:
+ _res->_screensState[14].s0 = 2;
+ _screenCounterTable[14] = 37;
+ break;
+ }
+ if (_res->_currentScreenResourceNum == 14) {
+ if (_res->_screensState[14].s0 == 0 && _g->_currentLevelCheckpoint == 4) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(11);
+ }
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen15() {
+ if (_res->_currentScreenResourceNum == 15) {
+ setLvlObjectUpdateType3_lar1(_g, 15);
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen16() {
+ if (_res->_currentScreenResourceNum == 16) {
+ if (_checkpoint == 5) {
+ _checkpoint = 6;
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen17() {
+ if (_res->_currentScreenResourceNum == 17) {
+ setLvlObjectUpdateType3_lar1(_g, 17);
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen18() {
+ if (_checkpoint >= 7) {
+ _lar1_gatesData[12 * 4] = (_lar1_gatesData[12 * 4] & 0xF) | 0x10;
+ }
+ if (_res->_currentScreenResourceNum == 18) {
+ setLvlObjectUpdateType3_lar1(_g, 18);
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen19() {
+ if (_res->_currentScreenResourceNum == 19) {
+ if (_checkpoint == 6) {
+ _res->_screensState[19].s0 = 0;
+ }
+ _res->_resLvlScreenBackgroundDataTable[19].currentBackgroundId = 0;
+ _res->_resLvlScreenBackgroundDataTable[19].currentMaskId = 0;
+ if (_res->_screensState[19].s0 != 0) {
+ _res->_screensState[19].s0 = 1;
+ _screenCounterTable[19] = 14;
+ if (_g->_difficulty != 0) {
+ _res->_resLvlScreenBackgroundDataTable[19].currentBackgroundId = 1;
+ _res->_resLvlScreenBackgroundDataTable[19].currentMaskId = 1;
+ }
+ } else {
+ _screenCounterTable[19] = (_g->_plasmaCannonFlags >> 1) & 1;
+ }
+ if (_screenCounterTable[19] == 0) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(13);
+ }
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen20() {
+ if (_res->_currentScreenResourceNum == 20) {
+ if (_checkpoint == 6) {
+ if ((_andyObject->flags0 & 0x1F) == 0xB) {
+ _checkpoint = 7;
+ }
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen23() {
+ if (_res->_currentScreenResourceNum == 23) {
+ setLvlObjectUpdateType3_lar1(_g, 23);
+ if (_g->_plasmaCannonFlags & 2) {
+ _lar1_gatesData[10 * 4] = (_lar1_gatesData[10 * 4] & 0xF) | 0x10;
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate_lar1_screen24() {
+ if (_res->_currentScreenResourceNum == 24) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(14);
+ }
+ }
+}
+
+void Level_lar1::preScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ preScreenUpdate_lar1_screen0();
+ break;
+ case 2:
+ preScreenUpdate_lar1_screen2();
+ break;
+ case 6:
+ preScreenUpdate_lar1_screen6();
+ break;
+ case 8:
+ preScreenUpdate_lar1_screen8();
+ break;
+ case 10:
+ preScreenUpdate_lar1_screen10();
+ break;
+ case 11:
+ preScreenUpdate_lar1_screen11();
+ break;
+ case 12:
+ preScreenUpdate_lar1_screen12();
+ break;
+ case 13:
+ preScreenUpdate_lar1_screen13();
+ break;
+ case 14:
+ preScreenUpdate_lar1_screen14();
+ break;
+ case 15:
+ preScreenUpdate_lar1_screen15();
+ break;
+ case 16:
+ preScreenUpdate_lar1_screen16();
+ break;
+ case 17:
+ preScreenUpdate_lar1_screen17();
+ break;
+ case 18:
+ preScreenUpdate_lar1_screen18();
+ break;
+ case 19:
+ preScreenUpdate_lar1_screen19();
+ break;
+ case 20:
+ preScreenUpdate_lar1_screen20();
+ break;
+ case 23:
+ preScreenUpdate_lar1_screen23();
+ break;
+ case 24:
+ preScreenUpdate_lar1_screen24();
+ break;
+ }
+}
+
+void Level_lar1::initialize() {
+ _g->resetWormHoleSprites();
+ _screenCounterTable[26] = 0;
+}
+
+void Level_lar1::terminate() {
+ _g->unloadTransformLayerData();
+}
+
+void Level_lar1::tick() {
+ _g->updateSwitchesLar(24, _lar1_switchesData, _lar1_switchesBbox, _lar1_gatesData);
+ _g->updateWormHoleSprites();
+ if (_screenCounterTable[19] != 0) {
+ _g->_plasmaCannonFlags |= 2;
+ }
+ if (_res->_currentScreenResourceNum == 12) {
+ _video->_displayShadowLayer = true;
+ }
+}
+
+void Level_lar1::setupScreenCheckpoint_lar1_screen24_initGates() {
+ const int num = _checkpoint;
+ for (int i = _lar1_setupScreen24Data[num * 3 + 2]; i < 24; ++i) {
+ const int offset = i * 4;
+ _lar1_switchesData[offset + 1] &= ~0x40;
+ _lar1_switchesData[offset + 1] |= 1; // actioned
+ }
+ for (int i = _lar1_setupScreen24Data[num * 3 + 2]; i != 0; --i) {
+ const int offset = (i - 1) * 4;
+ _lar1_switchesData[offset + 1] &= ~1;
+ _lar1_switchesData[offset + 1] |= 0x40;
+ }
+ static const uint8_t data[] = { 0, 1, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ for (int i = _lar1_setupScreen24Data[num * 3 + 1]; i < 13; ++i) {
+ const int j = i;
+ const uint8_t _al = (data[j] << 4) | 2;
+ _lar1_gatesData[j * 4] = _al;
+ const uint32_t mask = 1 << j;
+ if (_al & 0xF0) {
+ _g->_mstAndyVarMask &= ~mask;
+ } else {
+ _g->_mstAndyVarMask |= mask;
+ }
+ _g->_mstLevelGatesMask |= mask;
+ }
+ for (int i = _lar1_setupScreen24Data[num * 3 + 1]; i != 0; --i) {
+ const int j = i - 1;
+ const uint8_t _al = (((data[j] == 0) ? 1 : 0) << 4) | 2;
+ _lar1_gatesData[j * 4] = _al;
+ const uint32_t mask = 1 << j;
+ if (_al & 0xF0) {
+ _g->_mstAndyVarMask &= ~mask;
+ } else {
+ _g->_mstAndyVarMask |= mask;
+ }
+ _g->_mstLevelGatesMask |= mask;
+ }
+ _lar1_gatesData[2 * 4] = (_lar1_gatesData[2 * 4] & 0xF) | 0x30;
+ _lar1_gatesData[3 * 4] = (_lar1_gatesData[3 * 4] & 0xF) | 0x30;
+}
+
+void Level_lar1::setupScreenCheckpoint_lar1_screen24_initAndy(int num) {
+ static const uint8_t data[8 * 6] = {
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // checkpoint, gate states
+ 0x08, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x01, 0x00, 0x01, 0x01, 0x01,
+ 0x08, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x03, 0x01, 0x00, 0x00, 0x01, 0x01,
+ 0x03, 0x01, 0x01, 0x00, 0x01, 0x01
+ };
+ const uint8_t *p = &data[(num & 7) * 6];
+ num = *p++;
+ const CheckpointData *dat = &_lar1_checkpointData[num];
+ _andyObject->xPos = dat->xPos;
+ _andyObject->yPos = dat->yPos;
+ _andyObject->flags2 = dat->flags2;
+ _andyObject->anim = dat->anim;
+ _andyObject->flags1 &= ~0x30;
+ _andyObject->flags1 |= (dat->flags2 >> 10) & 0x30;
+ _andyObject->screenNum = dat->screenNum;
+ _andyObject->frame = 0;
+ _g->setupLvlObjectBitmap(_andyObject);
+ static const uint8_t gatesNum[5] = { 7, 6, 0, 4, 5 };
+ for (int i = 0; i < 5; ++i) {
+ uint8_t _al = *p++;
+ num = gatesNum[i];
+ _al <<= 4;
+ _al |= _lar1_gatesData[num * 4] & 15;
+ _lar1_gatesData[num * 4] = _al;
+ const uint32_t mask = 1 << num;
+ if (_al & 0xF0) {
+ _g->_mstAndyVarMask &= ~mask;
+ } else {
+ _g->_mstAndyVarMask |= mask;
+ }
+ _g->_mstLevelGatesMask |= mask;
+ }
+}
+
+void Level_lar1::setupScreenCheckpoint_lar1_screen24() {
+ setupScreenCheckpoint_lar1_screen24_initGates();
+ const int num = _checkpoint;
+ for (int b = _lar1_setupScreen24Data[num * 3]; b < 15; ++b) {
+ const int offset = b * 6;
+ Game::_lar1_maskData[offset + 1] = 1;
+ _g->updateScreenMaskLar(Game::_lar1_maskData + offset, 0);
+ LvlObject *o = _g->findLvlObject2(0, Game::_lar1_maskData[offset + 5], Game::_lar1_maskData[offset + 4]);
+ if (o) {
+ o->objectUpdateType = 6;
+ }
+ }
+ for (int b = _lar1_setupScreen24Data[num * 3]; b != 0; --b) {
+ const int offset = (b - 1) * 6;
+ Game::_lar1_maskData[offset + 1] = 0;
+ _g->updateScreenMaskLar(Game::_lar1_maskData + offset, 1);
+ LvlObject *o = _g->findLvlObject2(0, Game::_lar1_maskData[offset + 5], Game::_lar1_maskData[offset + 4]);
+ if (o) {
+ o->objectUpdateType = 2;
+ }
+ }
+ if (num == 2 || num == 3) {
+ if (_screenCounterTable[26] == 0 && num == 3) {
+ _screenCounterTable[26] = 5; // bugfix: original is 4, but gate 6 (screen 8) will be closed
+ }
+ setupScreenCheckpoint_lar1_screen24_initAndy(_screenCounterTable[26]);
+ }
+ if (g_debugMask & kDebug_SWITCHES) {
+ _g->dumpSwitchesLar(24, _lar1_switchesData, _lar1_switchesBbox, 13, _lar1_gatesData);
+ }
+}
+
+void Level_lar1::setupScreenCheckpoint(int num) {
+ switch (num) {
+ case 24:
+ setupScreenCheckpoint_lar1_screen24();
+ break;
+ }
+}
diff --git a/Src/Vita_&_Switch/level8_lar2.cpp b/Src/Vita_&_Switch/level8_lar2.cpp
new file mode 100644
index 0000000..2ce5354
--- /dev/null
+++ b/Src/Vita_&_Switch/level8_lar2.cpp
@@ -0,0 +1,577 @@
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "util.h"
+#include "video.h"
+
+static const CheckpointData _lar2_checkpointData[12] = {
+ { 111, 123, 0x300c, 48, 0, 0 },
+ { 11, 123, 0x300c, 48, 2, 0 },
+ { 17, 51, 0x300c, 48, 4, 0 },
+ { 69, 91, 0x300c, 48, 6, 0 },
+ { 154, 91, 0x700c, 48, 6, 0 },
+ { 154, 91, 0x700c, 48, 6, 0 },
+ { 137, 35, 0x300c, 48, 11, 0 },
+ { 154, 91, 0x700c, 48, 6, 0 },
+ { 70, 139, 0x700c, 48, 5, 0 },
+ { 106, 139, 0x700c, 48, 4, 0 },
+ { 75, 107, 0x300c, 48, 15, 0 },
+ { 75, 107, 0x300c, 48, 15, 0 }
+};
+
+static const uint8_t _lar2_screenStartData[64] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct Level_lar2: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_lar2_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _lar2_screenStartData; }
+ //virtual void initialize();
+ //virtual void terminate();
+ virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ virtual void setupScreenCheckpoint(int screenNum);
+
+ bool postScreenUpdate_lar2_screen2_updateGateSwitches(BoundingBox *b);
+
+ void postScreenUpdate_lar2_screen2();
+ void postScreenUpdate_lar2_screen3();
+ void postScreenUpdate_lar2_screen4();
+ void postScreenUpdate_lar2_screen5();
+ void postScreenUpdate_lar2_screen6();
+ void postScreenUpdate_lar2_screen7();
+ void postScreenUpdate_lar2_screen8();
+ void postScreenUpdate_lar2_screen10();
+ void postScreenUpdate_lar2_screen11();
+ void postScreenUpdate_lar2_screen12();
+ void postScreenUpdate_lar2_screen13();
+ void postScreenUpdate_lar2_screen19();
+
+ void preScreenUpdate_lar2_screen2();
+ void preScreenUpdate_lar2_screen4();
+ void preScreenUpdate_lar2_screen5();
+ void preScreenUpdate_lar2_screen6();
+ void preScreenUpdate_lar2_screen7();
+ void preScreenUpdate_lar2_screen8();
+ void preScreenUpdate_lar2_screen9();
+ void preScreenUpdate_lar2_screen15();
+ void preScreenUpdate_lar2_screen19();
+
+ void setupScreenCheckpoint_lar2_screen19();
+};
+
+Level *Level_lar2_create() {
+ return new Level_lar2;
+}
+
+static uint8_t _lar2_gatesData[10 * 4] = {
+ 0x02, 0x00, 0x00, 0x00, // screen 2
+ 0x02, 0x00, 0x00, 0x00, // screen 3
+ 0x02, 0x00, 0x00, 0x00, // screen 4
+ 0x12, 0x00, 0x00, 0x00, // screen 5
+ 0x02, 0x00, 0x00, 0x00, // screen 10
+ 0x02, 0x00, 0x00, 0x00, // screen 11
+ 0x02, 0x00, 0x00, 0x00, // screen 11
+ 0x02, 0x00, 0x00, 0x00, // screen 8
+ 0x12, 0x00, 0x00, 0x00, // screen 12
+ 0x02, 0x00, 0x00, 0x00 // screen 12
+};
+
+static BoundingBox _lar2_switchesBbox[13] = {
+ { 210, 155, 220, 159 },
+ { 224, 146, 234, 150 },
+ { 193, 84, 203, 88 },
+ { 209, 83, 219, 87 },
+ { 65, 179, 75, 183 },
+ { 65, 179, 75, 183 },
+ { 145, 179, 155, 183 },
+ { 145, 179, 155, 183 },
+ { 224, 179, 234, 183 },
+ { 16, 84, 26, 88 },
+ { 16, 84, 26, 88 },
+ { 16, 164, 26, 168 },
+ { 16, 164, 26, 168 }
+};
+
+static uint8_t _lar2_switchesData[13 * 4] = { // screenNum,state,spriteNum,gateNum
+ 0x03, 0x07, 0x00, 0x01, 0x0A, 0x07, 0x00, 0x04, 0x09, 0x03, 0x00, 0x07, 0x08, 0x03, 0x00, 0x07,
+ 0x0B, 0x17, 0x00, 0x06, 0x0B, 0x15, 0xFF, 0x05, 0x0B, 0x15, 0x01, 0x06, 0x0B, 0x17, 0xFF, 0x05,
+ 0x0B, 0x17, 0x02, 0x06, 0x0D, 0x05, 0x00, 0x09, 0x0D, 0x07, 0xFF, 0x08, 0x0D, 0x05, 0x01, 0x08,
+ 0x0D, 0x07, 0xFF, 0x09
+};
+
+static const uint8_t _lar2_setupScreen19Data[13 * 3] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x05, 0x02, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x09, 0x0C, 0x00, 0x0A, 0x0C,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+bool Level_lar2::postScreenUpdate_lar2_screen2_updateGateSwitches(BoundingBox *b) {
+ bool ret = false;
+ BoundingBox b1 = { 121, 158, 131, 162 };
+ if (_g->clipBoundingBox(&b1, b)) {
+ ret = true;
+ _lar2_gatesData[0] &= 0xF;
+ LvlObject *o = _g->findLvlObject2(0, 0, 2);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ }
+ BoundingBox b2 = { 170, 158, 180, 162 };
+ if (_g->clipBoundingBox(&b2, b)) {
+ ret = true;
+ _lar2_gatesData[0] &= 0xF;
+ LvlObject *o = _g->findLvlObject2(0, 1, 2);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ }
+ BoundingBox b3 = { 215, 158, 225, 162 };
+ if (_g->clipBoundingBox(&b3, b)) {
+ ret = true;
+ _lar2_gatesData[0] &= 0xF;
+ LvlObject *o = _g->findLvlObject2(0, 2, 2);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ }
+ return ret;
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen2() {
+ LvlObject *o = _g->findLvlObject(2, 0, 2);
+ _g->updateGatesLar(o, _lar2_gatesData, 0);
+ if (_res->_currentScreenResourceNum == 2) {
+ bool ret = false;
+ for (LvlObject *o = _g->_lvlObjectsList1; o; o = o->nextPtr) {
+ if (o->spriteNum == 16 && o->screenNum == _res->_currentScreenResourceNum) {
+ BoundingBox b;
+ b.x1 = o->xPos;
+ b.y1 = o->yPos;
+ b.x2 = o->xPos + o->width - 1;
+ b.y2 = o->yPos + o->height - 1;
+ if (postScreenUpdate_lar2_screen2_updateGateSwitches(&b)) {
+ ret = true;
+ }
+ }
+ }
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (postScreenUpdate_lar2_screen2_updateGateSwitches(&data->boundingBox) == 0) {
+ BoundingBox b = { 107, 77, 117, 81 };
+ if (_g->clipBoundingBox(&b, &data->boundingBox)) {
+ LvlObject *o = _g->findLvlObject2(0, 3, 2);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ if (!ret) {
+ _lar2_gatesData[0] = (_lar2_gatesData[0] & 0xF) | 0x10;
+ }
+ }
+ }
+ }
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen3() {
+ LvlObject *o = _g->findLvlObject(2, 0, 3);
+ _g->updateGatesLar(o, _lar2_gatesData + 4, 1);
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen4() {
+ if (_g->_currentLevelCheckpoint == 8 && _checkpoint == 9) {
+ _lar2_gatesData[8] = (_lar2_gatesData[8] & 0xF) | 0x10;
+ if (!_paf->_skipCutscenes) {
+ _paf->play(18);
+ _paf->unload(18);
+ _video->clearPalette();
+ }
+ _g->_currentLevelCheckpoint = _checkpoint; // bugfix: conditioned with _pafSkipCutscenes
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ LvlObject *o = _g->findLvlObject(2, 0, 4);
+ _g->updateGatesLar(o, _lar2_gatesData + 8, 2);
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen5() {
+ if (_g->_currentLevelCheckpoint == 7 && _checkpoint == 8) {
+ _lar2_gatesData[0xC] &= 0xF;
+ }
+ LvlObject *o = _g->findLvlObject(2, 0, 5);
+ _g->updateGatesLar(o, _lar2_gatesData + 0xC, 3);
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen6() {
+ if (_res->_currentScreenResourceNum == 6) {
+ if (_checkpoint == 7) {
+ if (_g->_currentLevelCheckpoint == 6) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(17);
+ _paf->unload(17);
+ _video->clearPalette();
+ }
+ _g->_currentLevelCheckpoint = _checkpoint; // bugfix: conditioned with _pafSkipCutscenes
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ } else if (_checkpoint == 3) {
+ if (_res->_screensState[6].s0 != 0) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(15);
+ _paf->unload(15);
+ _video->clearPalette();
+ }
+ _res->_screensState[6].s0 = 0; // bugfix: conditioned with _pafSkipCutscenes
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ }
+ }
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen7() {
+ if (_res->_currentScreenResourceNum == 7) {
+ if (_checkpoint == 5 && _res->_screensState[7].s0 > 1) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(16);
+ _paf->unload(16);
+ _video->clearPalette();
+ }
+ _res->_screensState[7].s0 = 1; // bugfix: conditioned with _pafSkipCutscenes
+ _g->setupScreen(_andyObject->screenNum);
+ }
+ }
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen8() {
+ LvlObject *o = _g->findLvlObject(2, 0, 8);
+ _g->updateGatesLar(o, _lar2_gatesData + 0x1C, 7);
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen10() {
+ LvlObject *o = _g->findLvlObject(2, 0, 10);
+ _g->updateGatesLar(o, _lar2_gatesData + 0x10, 4);
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen11() {
+ LvlObject *o = _g->findLvlObject(2, 0, 11);
+ _g->updateGatesLar(o, _lar2_gatesData + 0x14, 5);
+ o = _g->findLvlObject(2, 1, 11);
+ _g->updateGatesLar(o, _lar2_gatesData + 0x18, 6);
+ int offset = 0x18;
+ if ((_lar2_switchesData[0x11] & 1) == 0 && (_lar2_switchesData[0x11] & 0x40) != 0 && (_lar2_switchesData[0x19] & 1) == 0) {
+ _lar2_switchesData[0x19] = (_lar2_switchesData[0x19] | 1) & ~0x40;
+ offset = 0x1C;
+ _lar2_switchesData[0x1D] = (_lar2_switchesData[0x1D] | 1) & ~0x40;
+ }
+ if ((_lar2_switchesData[offset + 1] & 1) == 0 && (_lar2_switchesData[offset + 1] & 0x40) != 0) {
+ if ((_lar2_switchesData[0x21] & 1) != 0) {
+ goto next;
+ }
+ _lar2_switchesData[0x21] = (_lar2_switchesData[0x21] | 1) & ~0x40;
+ }
+ if ((_lar2_switchesData[0x21] & 1) == 0 && (_lar2_switchesData[0x21] & 0x40) != 0 && (_lar2_switchesData[offset + 1] & 1) == 0) {
+ _lar2_switchesData[offset + 1] = (_lar2_switchesData[offset + 1] | 1) & ~0x40;
+ _lar2_switchesData[0x1D] = (_lar2_switchesData[0x1D] | 1) & ~0x40;
+ offset = 0x1C;
+ }
+next:
+ if ((_lar2_switchesData[offset + 1] & 1) == 0 && (_lar2_switchesData[offset + 1] & 0x40) != 0 && (_lar2_switchesData[0x11] & 1) == 0) {
+ _lar2_switchesData[0x11] = (_lar2_switchesData[0x11] | 1) & ~0x40;
+ _lar2_switchesData[0x15] = (_lar2_switchesData[0x15] | 1) & ~0X40;
+ }
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen12() {
+ LvlObject *o = _g->findLvlObject(2, 0, 12);
+ _g->updateGatesLar(o, _lar2_gatesData + 0x20, 8);
+ o = _g->findLvlObject(2, 1, 12);
+ _g->updateGatesLar(o, _lar2_gatesData + 0x24, 9);
+ if (_res->_currentScreenResourceNum == 12) {
+ BoundingBox b1 = { 65, 84, 75, 88 };
+ AndyLvlObjectData *data = (AndyLvlObjectData *)_g->getLvlObjectDataPtr(_andyObject, kObjectDataTypeAndy);
+ if (_g->clipBoundingBox(&b1, &data->boundingBox)) {
+ _lar2_gatesData[0x20] &= 0xF;
+ o = _g->findLvlObject2(0, 0, 12);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ } else {
+ BoundingBox b2 = { 65, 163, 75, 167 };
+ if (_g->clipBoundingBox(&b2, &data->boundingBox)) {
+ _lar2_gatesData[0x24] &= 0xF;
+ o = _g->findLvlObject2(0, 1, 12);
+ if (o) {
+ o->objectUpdateType = 7;
+ }
+ }
+ }
+ }
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen13() {
+ if (_res->_currentScreenResourceNum == 13) {
+ int offset = 0x2C;
+ if ((_lar2_switchesData[0x25] & 1) == 0 && (_lar2_switchesData[0x25] & 0x40) != 0 && (_lar2_switchesData[0x2D] & 1) == 0) {
+ offset = 0x30;
+ _lar2_switchesData[0x2D] = (_lar2_switchesData[0x2D] | 1) & ~0x40;
+ _lar2_switchesData[0x31] = (_lar2_switchesData[0x31] | 1) & ~0x40;
+ }
+ if ((_lar2_switchesData[offset + 1] & 1) == 0 && (_lar2_switchesData[offset + 1] & 0x40) != 0 && (_lar2_switchesData[0x25] & 1) == 0) {
+ _lar2_switchesData[0x25] = (_lar2_switchesData[0x25] | 1) & ~0x40;
+ _lar2_switchesData[0x29] = (_lar2_switchesData[0x29] | 1) & ~0x40;
+ }
+ }
+}
+
+void Level_lar2::postScreenUpdate_lar2_screen19() {
+ if (_res->_currentScreenResourceNum == 19) {
+ if (_g->_currentLevelCheckpoint == 10 && _checkpoint == 11) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(19);
+ _paf->unload(19);
+ }
+ _video->clearPalette();
+ _g->_endLevel = true;
+ }
+ }
+}
+
+void Level_lar2::postScreenUpdate(int num) {
+ switch (num) {
+ case 2:
+ postScreenUpdate_lar2_screen2();
+ break;
+ case 3:
+ postScreenUpdate_lar2_screen3();
+ break;
+ case 4:
+ postScreenUpdate_lar2_screen4();
+ break;
+ case 5:
+ postScreenUpdate_lar2_screen5();
+ break;
+ case 6:
+ postScreenUpdate_lar2_screen6();
+ break;
+ case 7:
+ postScreenUpdate_lar2_screen7();
+ break;
+ case 8:
+ postScreenUpdate_lar2_screen8();
+ break;
+ case 10:
+ postScreenUpdate_lar2_screen10();
+ break;
+ case 11:
+ postScreenUpdate_lar2_screen11();
+ break;
+ case 12:
+ postScreenUpdate_lar2_screen12();
+ break;
+ case 13:
+ postScreenUpdate_lar2_screen13();
+ break;
+ case 19:
+ postScreenUpdate_lar2_screen19();
+ break;
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen2() {
+ LvlObject *o = _g->findLvlObject(2, 0, 2);
+ _g->updateGatesLar(o, _lar2_gatesData, 1);
+ if (_res->_currentScreenResourceNum == 2) {
+ if (_checkpoint == 0) {
+ _checkpoint = 1;
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen4() {
+ if (_res->_currentScreenResourceNum == 4) {
+ if (_checkpoint == 1) {
+ _checkpoint = 2;
+ }
+ if (_checkpoint >= 2) {
+ _lar2_gatesData[4] &= 0xF;
+ if (_checkpoint == 8) {
+ _lar2_gatesData[8] &= 0xF;
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(18);
+ }
+ }
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen5() {
+ if (_res->_currentScreenResourceNum == 5) {
+ if (_checkpoint == 7) {
+ _lar2_gatesData[0xC] = (_lar2_gatesData[0xC] & 0xF) | 0x10;
+ } else if (_checkpoint >= 3) {
+ _lar2_gatesData[0xC] &= 0xF;
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen6() {
+ if (_res->_currentScreenResourceNum == 6) {
+ if (_checkpoint == 2) {
+ _checkpoint = 3;
+ }
+ if (_checkpoint == 3) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(15);
+ }
+ _lar2_gatesData[0xC] &= 0xF; // bugfix: conditioned with _pafSkipCutscenes
+ } else if (_checkpoint == 6) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(17);
+ }
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen7() {
+ if (_res->_currentScreenResourceNum == 7) {
+ if (_checkpoint >= 4 && _checkpoint < 7) {
+ _res->_screensState[7].s0 = 1;
+ } else {
+ _res->_screensState[7].s0 = 0;
+ }
+ if (_checkpoint == 5) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(16);
+ }
+ }
+ if (_res->_screensState[7].s0 != 0) {
+ _res->_resLvlScreenBackgroundDataTable[7].currentBackgroundId = 1;
+ _res->_resLvlScreenBackgroundDataTable[7].currentMaskId = 1;
+ } else {
+ _res->_resLvlScreenBackgroundDataTable[7].currentBackgroundId = 0;
+ _res->_resLvlScreenBackgroundDataTable[7].currentMaskId = 0;
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen8() {
+ if (_res->_currentScreenResourceNum == 8 && _lar2_gatesData[0x1E] > 1) {
+ _lar2_gatesData[0x1E] = 1;
+ }
+ LvlObject *o = _g->findLvlObject(2, 0, 8);
+ _g->updateGatesLar(o, _lar2_gatesData + 0x1C, 7);
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen9() {
+ if (_res->_currentScreenResourceNum == 9) {
+ _lar2_gatesData[0x1E] = 0x24;
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen15() {
+ if (_res->_currentScreenResourceNum == 15) {
+ if (_checkpoint == 9) {
+ _checkpoint = 10;
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate_lar2_screen19() {
+ if (_res->_currentScreenResourceNum == 19) {
+ if (_checkpoint == 10) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(19);
+ }
+ }
+ }
+}
+
+void Level_lar2::preScreenUpdate(int num) {
+ switch (num) {
+ case 2:
+ preScreenUpdate_lar2_screen2();
+ break;
+ case 4:
+ preScreenUpdate_lar2_screen4();
+ break;
+ case 5:
+ preScreenUpdate_lar2_screen5();
+ break;
+ case 6:
+ preScreenUpdate_lar2_screen6();
+ break;
+ case 7:
+ preScreenUpdate_lar2_screen7();
+ break;
+ case 8:
+ case 11:
+ preScreenUpdate_lar2_screen8();
+ break;
+ case 9:
+ preScreenUpdate_lar2_screen9();
+ break;
+ case 15:
+ preScreenUpdate_lar2_screen15();
+ break;
+ case 19:
+ preScreenUpdate_lar2_screen19();
+ break;
+ }
+}
+
+void Level_lar2::tick() {
+ _g->updateSwitchesLar(13, _lar2_switchesData, _lar2_switchesBbox, _lar2_gatesData);
+}
+
+void Level_lar2::setupScreenCheckpoint_lar2_screen19() {
+ int num1 = _lar2_setupScreen19Data[_checkpoint * 3 + 1];
+ for (int i = num1; i < 13; ++i) {
+ const int offset = i * 4;
+ _lar2_switchesData[offset + 1] &= ~0x40;
+ _lar2_switchesData[offset + 1] |= 1;
+ }
+ for (int i = num1; i != 0; --i) {
+ const int offset = (i - 1) * 4;
+ _lar2_switchesData[offset + 1] &= ~1;
+ _lar2_switchesData[offset + 1] |= 0x40;
+ }
+ static const uint8_t data[17] = {
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ const int gateIndex = _lar2_setupScreen19Data[_checkpoint * 3];
+ for (int i = gateIndex; i < 10; ++i) {
+ const int num = i;
+ _lar2_gatesData[num * 4] = (data[num] << 4) | 2;
+ const uint32_t mask = 1 << num;
+ if (_lar2_gatesData[num * 4] & 0xF0) {
+ _g->_mstAndyVarMask &= ~mask;
+ } else {
+ _g->_mstAndyVarMask |= mask;
+ }
+ _g->_mstLevelGatesMask |= mask;
+ }
+ for (int i = gateIndex; i != 0; --i) {
+ const int num = i - 1;
+ _lar2_gatesData[num * 4] = (((data[num] == 0) ? 1 : 0) << 4) | 2;
+ const uint32_t mask = 1 << num;
+ if (_lar2_gatesData[num * 4] & 0xF0) {
+ _g->_mstAndyVarMask &= ~mask;
+ } else {
+ _g->_mstAndyVarMask |= mask;
+ }
+ _g->_mstLevelGatesMask |= mask;
+ }
+ if (g_debugMask & kDebug_SWITCHES) {
+ _g->dumpSwitchesLar(13, _lar2_switchesData, _lar2_switchesBbox, 10, _lar2_gatesData);
+ }
+}
+
+void Level_lar2::setupScreenCheckpoint(int num) {
+ switch (num) {
+ case 19:
+ setupScreenCheckpoint_lar2_screen19();
+ break;
+ }
+}
diff --git a/Src/Vita_&_Switch/level9_dark.cpp b/Src/Vita_&_Switch/level9_dark.cpp
new file mode 100644
index 0000000..46f5df4
--- /dev/null
+++ b/Src/Vita_&_Switch/level9_dark.cpp
@@ -0,0 +1,79 @@
+
+// dark_hod
+
+#include "game.h"
+#include "level.h"
+#include "paf.h"
+#include "video.h"
+
+static const CheckpointData _dark_checkpointData[1] = {
+ { 117, 115, 0x300c, 48, 0, 0 }
+};
+
+static const uint8_t _dark_screenStartData[2] = {
+ 0x00, 0x00
+};
+
+struct Level_dark: Level {
+ virtual const CheckpointData *getCheckpointData(int num) const { return &_dark_checkpointData[num]; }
+ virtual const uint8_t *getScreenRestartData() const { return _dark_screenStartData; }
+ //virtual void initialize();
+ //virtual void terminate();
+ //virtual void tick();
+ virtual void preScreenUpdate(int screenNum);
+ virtual void postScreenUpdate(int screenNum);
+ //virtual void setupScreenCheckpoint(int screenNum);
+
+ void postScreenUpdate_dark_screen0();
+
+ void preScreenUpdate_dark_screen0();
+};
+
+Level *Level_dark_create() {
+ return new Level_dark;
+}
+
+void Level_dark::postScreenUpdate_dark_screen0() {
+ if (_res->_screensState[0].s0 != 0) {
+ ++_screenCounterTable[0];
+ if (_screenCounterTable[0] == 29) {
+ if (!_paf->_skipCutscenes) {
+ _paf->play(20);
+ _paf->unload(20);
+ _g->displayHintScreen(_res->_datHdr.yesNoQuitImage - 1, 25);
+ _paf->preload(21);
+ _paf->play(21);
+ _paf->unload(21);
+ }
+ _video->clearPalette();
+ _video->clearBackBuffer();
+ _g->_endLevel = true;
+ }
+ }
+}
+
+void Level_dark::postScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ postScreenUpdate_dark_screen0();
+ break;
+ }
+}
+
+void Level_dark::preScreenUpdate_dark_screen0() {
+ if (_res->_currentScreenResourceNum == 0) {
+ if (!_paf->_skipCutscenes) {
+ _paf->preload(20);
+ }
+ _screenCounterTable[0] = 0;
+ _res->_screensState[0].s0 = 0;
+ }
+}
+
+void Level_dark::preScreenUpdate(int num) {
+ switch (num) {
+ case 0:
+ preScreenUpdate_dark_screen0();
+ break;
+ }
+}
diff --git a/Src/Vita_&_Switch/lzw.cpp b/Src/Vita_&_Switch/lzw.cpp
new file mode 100644
index 0000000..676c103
--- /dev/null
+++ b/Src/Vita_&_Switch/lzw.cpp
@@ -0,0 +1,109 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "intern.h"
+
+enum {
+ kCodeWidth = 9,
+ kClearCode = 1 << (kCodeWidth - 1),
+ kEndCode = kClearCode + 1,
+ kNewCodes = kEndCode + 1,
+ kStackSize = 8192,
+ kMaxBits = 12
+};
+
+struct LzwDecoder {
+
+ uint16_t _prefix[1 << kMaxBits];
+ uint8_t _stack[kStackSize];
+ const uint8_t *_buf;
+ uint32_t _currentBits;
+ uint8_t _bitsLeft;
+
+ uint32_t nextCode(int codeSize);
+ int decode(uint8_t *dst);
+};
+
+static struct LzwDecoder _lzw;
+
+uint32_t LzwDecoder::nextCode(int codeSize) { // 9 to 12bits
+ _currentBits |= (*_buf++) << _bitsLeft;
+ _bitsLeft += 8;
+ if (_bitsLeft < codeSize) {
+ _currentBits |= (*_buf++) << _bitsLeft;
+ _bitsLeft += 8;
+ }
+ const uint32_t code = _currentBits & ((1 << codeSize) - 1);
+ _currentBits >>= codeSize;
+ _bitsLeft -= codeSize;
+ return code;
+}
+
+int LzwDecoder::decode(uint8_t *dst) {
+ uint8_t *p = dst;
+ uint8_t *stackPtr = &_stack[kStackSize - 1];
+ uint32_t previousCode = 0;
+ uint32_t lastCode = 0;
+ uint32_t currentCode;
+ uint32_t currentSlot = kNewCodes;
+ uint32_t topSlot = 1 << kCodeWidth;
+ int codeSize = kCodeWidth;
+ while ((currentCode = nextCode(codeSize)) != kEndCode) {
+ if (currentCode == kClearCode) {
+ currentSlot = kNewCodes;
+ topSlot = 1 << kCodeWidth;
+ codeSize = kCodeWidth;
+ while ((currentCode = nextCode(codeSize)) == kClearCode) {
+ }
+ if (currentCode == kEndCode) {
+ break;
+ } else if (currentCode >= kNewCodes) {
+ currentCode = 0;
+ }
+ previousCode = lastCode = currentCode;
+ *p++ = (uint8_t)currentCode;
+ } else {
+ uint8_t *currentStackPtr = stackPtr;
+ uint32_t slot = currentSlot;
+ uint32_t code = currentCode;
+ if (currentCode >= slot) {
+ code = lastCode;
+ currentStackPtr = &_stack[kStackSize - 2];
+ *currentStackPtr = (uint8_t)previousCode;
+ }
+ while (code >= kNewCodes) {
+ --currentStackPtr;
+ assert(currentStackPtr >= &_stack[0]);
+ *currentStackPtr = _stack[code];
+ code = _prefix[code];
+ }
+ --currentStackPtr;
+ *currentStackPtr = (uint8_t)code;
+ if (slot < topSlot) {
+ _stack[slot] = (uint8_t)code;
+ previousCode = code;
+ _prefix[slot] = (uint16_t)lastCode;
+ lastCode = currentCode;
+ ++slot;
+ currentSlot = slot;
+ }
+ if (slot >= topSlot && codeSize < kMaxBits) {
+ topSlot <<= 1;
+ ++codeSize;
+ }
+ while (currentStackPtr < stackPtr) {
+ *p++ = *currentStackPtr++;
+ }
+ assert(currentStackPtr == stackPtr);
+ }
+ }
+ return p - dst;
+}
+
+int decodeLZW(const uint8_t *src, uint8_t *dst) {
+ memset(&_lzw, 0, sizeof(_lzw));
+ _lzw._buf = src;
+ return _lzw.decode(dst);
+}
diff --git a/Src/Vita_&_Switch/lzw.h b/Src/Vita_&_Switch/lzw.h
new file mode 100644
index 0000000..c677484
--- /dev/null
+++ b/Src/Vita_&_Switch/lzw.h
@@ -0,0 +1,12 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef LZW_H__
+#define LZW_H__
+
+int decodeLZW(const uint8_t *src, uint8_t *dst);
+
+#endif // LZW_H__
+
diff --git a/Src/Vita_&_Switch/main.cpp b/Src/Vita_&_Switch/main.cpp
new file mode 100644
index 0000000..261b0c5
--- /dev/null
+++ b/Src/Vita_&_Switch/main.cpp
@@ -0,0 +1,280 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#if !defined(PSP) && !defined(WII)
+#include
+#endif
+#if defined(WII)
+#include
+#endif
+#include
+#include
+
+#include "3p/inih/ini.h"
+
+#include "game.h"
+#include "menu.h"
+#include "mixer.h"
+#include "paf.h"
+#include "util.h"
+#include "resource.h"
+#include "system.h"
+#include "video.h"
+
+#ifdef __SWITCH__
+#include
+#endif
+
+static const char *_title = "Heart of Darkness";
+
+#ifdef __vita__
+static const char *_configIni = "ux0:data/hode/hode.ini";
+#else
+static const char *_configIni = "hode.ini";
+#endif
+
+static const char *_usage =
+ "hode - Heart of Darkness Interpreter\n"
+ "Usage: %s [OPTIONS]...\n"
+ " --datapath=PATH Path to data files (default '.')\n"
+ " --savepath=PATH Path to save files (default '.')\n"
+ " --level=NUM Start at level NUM\n"
+ " --checkpoint=NUM Start at checkpoint NUM\n"
+;
+
+static bool _fullscreen = false;
+static bool _widescreen = false;
+
+static const bool _runBenchmark = false;
+static bool _runMenu = true;
+
+static void lockAudio(int flag) {
+ if (flag) {
+ g_system->lockAudio();
+ } else {
+ g_system->unlockAudio();
+ }
+}
+
+static void mixAudio(void *userdata, int16_t *buf, int len) {
+ ((Game *)userdata)->mixAudio(buf, len);
+}
+
+static void setupAudio(Game *g) {
+ g->_mix._lock = lockAudio;
+ AudioCallback cb;
+ cb.proc = mixAudio;
+ cb.userdata = g;
+ g_system->startAudio(cb);
+}
+
+static const char *_defaultDataPath = ".";
+
+static const char *_defaultSavePath = ".";
+
+static const char *_levelNames[] = {
+ "rock",
+ "fort",
+ "pwr1",
+ "isld",
+ "lava",
+ "pwr2",
+ "lar1",
+ "lar2",
+ "dark",
+ 0
+};
+
+static bool configBool(const char *value) {
+ return strcasecmp(value, "true") == 0 || (strlen(value) == 2 && (value[0] == 't' || value[0] == '1'));
+}
+
+static int handleConfigIni(void *userdata, const char *section, const char *name, const char *value) {
+ Game *g = (Game *)userdata;
+ // fprintf(stdout, "config.ini: section '%s' name '%s' value '%s'\n", section, name, value);
+ if (strcmp(section, "engine") == 0) {
+ if (strcmp(name, "disable_paf") == 0) {
+ if (!g->_paf->_skipCutscenes) { // .paf file not found
+ g->_paf->_skipCutscenes = configBool(value);
+ }
+ } else if (strcmp(name, "disable_mst") == 0) {
+ g->_mstDisabled = configBool(value);
+ } else if (strcmp(name, "disable_sss") == 0) {
+ g->_sssDisabled = configBool(value);
+ } else if (strcmp(name, "disable_menu") == 0) {
+ _runMenu = !configBool(value);
+ } else if (strcmp(name, "max_active_sounds") == 0) {
+ g->_playingSssObjectsMax = atoi(value);
+ } else if (strcmp(name, "difficulty") == 0) {
+ g->_difficulty = atoi(value);
+ } else if (strcmp(name, "frame_duration") == 0) {
+ g->_frameMs = atoi(value);
+ } else if (strcmp(name, "loading_screen") == 0) {
+ g->_loadingScreenEnabled = configBool(value);
+ }
+ } else if (strcmp(section, "display") == 0) {
+ if (strcmp(name, "scale_factor") == 0) {
+ const int scale = atoi(value);
+ g_system->setScaler(0, scale);
+ } else if (strcmp(name, "scale_algorithm") == 0) {
+ g_system->setScaler(value, 0);
+ } else if (strcmp(name, "gamma") == 0) {
+ g_system->setGamma(atof(value));
+ } else if (strcmp(name, "fullscreen") == 0) {
+ _fullscreen = configBool(value);
+ } else if (strcmp(name, "widescreen") == 0) {
+ _widescreen = configBool(value);
+ }
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+#ifdef __SWITCH__
+ socketInitializeDefault();
+ nxlinkStdio();
+#endif
+#ifdef __vita__
+ const char *dataPath = "ux0:data/hode";
+ const char *savePath = "ux0:data/hode";
+#else
+ char *dataPath = 0;
+ char *savePath = 0;
+#endif
+ int level = 0;
+ int checkpoint = 0;
+ bool resume = true; // resume game from 'setup.cfg'
+
+ g_debugMask = 0; //kDebug_GAME | kDebug_RESOURCE | kDebug_SOUND | kDebug_MONSTER;
+ int cheats = 0;
+
+#ifdef WII
+ fatInitDefault();
+ static const char *pathsWII[] = {
+ "sd:/hode",
+ "usb:/hode",
+ 0
+ };
+ for (int i = 0; pathsWII[i]; ++i) {
+ struct stat st;
+ if (stat(pathsWII[i], &st) == 0 && S_ISDIR(st.st_mode)) {
+ dataPath = strdup(pathsWII[i]);
+ savePath = strdup(pathsWII[i]);
+ break;
+ }
+ }
+#else
+#if !defined(PSP)
+ if (argc == 2) {
+ // data path as the only command line argument
+ struct stat st;
+ if (stat(argv[1], &st) == 0 && S_ISDIR(st.st_mode)) {
+ dataPath = strdup(argv[1]);
+ }
+ }
+ while (1) {
+ static struct option options[] = {
+ { "datapath", required_argument, 0, 1 },
+ { "savepath", required_argument, 0, 2 },
+ { "level", required_argument, 0, 3 },
+ { "checkpoint", required_argument, 0, 4 },
+ { "debug", required_argument, 0, 5 },
+ { "cheats", required_argument, 0, 6 },
+ { 0, 0, 0, 0 },
+ };
+ int index;
+ const int c = getopt_long(argc, argv, "", options, &index);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 1:
+ dataPath = strdup(optarg);
+ break;
+ case 2:
+ savePath = strdup(optarg);
+ break;
+ case 3:
+ if (optarg[0] >= '0' && optarg[0] <= '9') {
+ level = atoi(optarg);
+ } else {
+ for (int i = 0; _levelNames[i]; ++i) {
+ if (strcmp(_levelNames[i], optarg) == 0) {
+ level = i;
+ break;
+ }
+ }
+ }
+ resume = false;
+ break;
+ case 4:
+ checkpoint = atoi(optarg);
+ resume = false;
+ break;
+ case 5:
+ g_debugMask |= atoi(optarg);
+ break;
+ case 6:
+ cheats |= atoi(optarg);
+ break;
+ default:
+ fprintf(stdout, "%s\n", _usage);
+ return -1;
+ }
+ }
+#endif
+#endif
+ Game *g = new Game(dataPath ? dataPath : _defaultDataPath, savePath ? savePath : _defaultSavePath, cheats);
+ ini_parse(_configIni, handleConfigIni, g);
+ if (_runBenchmark) {
+ g->benchmarkCpu();
+ }
+ // load setup.dat and detects if these are PC or PSX datafiles
+ g->_res->loadSetupDat();
+ const bool isPsx = g->_res->_isPsx;
+ g_system->init(_title, Video::W, Video::H, _fullscreen, _widescreen, isPsx);
+ setupAudio(g);
+ g->loadSetupCfg(resume);
+ bool runGame = true;
+ g->_video->init(isPsx);
+ g->displayLoadingScreen();
+ if (_runMenu && resume && !isPsx) {
+ Menu *m = new Menu(g, g->_paf, g->_res, g->_video);
+ runGame = m->mainLoop();
+ delete m;
+ }
+ if (runGame && !g_system->inp.quit) {
+ bool levelChanged = false;
+ do {
+ g->displayLoadingScreen();
+ g->mainLoop(level, checkpoint, levelChanged);
+ // do not save progress when game is started from a specific level/checkpoint
+ if (resume) {
+ g->saveSetupCfg();
+ }
+ if (g->_res->_isDemo) {
+ break;
+ }
+ level = g->_currentLevel + 1;
+ checkpoint = 0;
+ levelChanged = true;
+ } while (!g_system->inp.quit && level < kLvl_test);
+ }
+ g_system->stopAudio();
+ g_system->destroy();
+ delete g;
+#ifndef __vita__
+ free(dataPath);
+ free(savePath);
+#endif
+#ifdef WII
+ fatUnmount("sd:/");
+ fatUnmount("usb:/");
+#endif
+#ifdef __SWITCH__
+ socketExit();
+#endif
+ return 0;
+}
diff --git a/Src/Vita_&_Switch/mdec.cpp b/Src/Vita_&_Switch/mdec.cpp
new file mode 100644
index 0000000..5e8731a
--- /dev/null
+++ b/Src/Vita_&_Switch/mdec.cpp
@@ -0,0 +1,227 @@
+
+#include
+#include "intern.h"
+#include "mdec.h"
+#include "mdec_coeffs.h"
+
+struct BitStream { // most significant 16 bits
+ const uint8_t *_src;
+ uint32_t _bits;
+ int _len;
+ const uint8_t *_end;
+
+ BitStream(const uint8_t *src, int size)
+ : _src(src), _bits(0), _len(0), _end(src + size) {
+ }
+
+ int bitsAvailable() const {
+ return (_end - _src) * 8 + _len;
+ }
+
+ int getBits(int count) { // 6 to 16 bits
+ if (_len < count) {
+ _bits <<= 16;
+ assert(_src < _end);
+ _bits |= READ_LE_UINT16(_src); _src += 2;
+ _len += 16;
+ }
+ assert(_len >= count);
+ const int value = (_bits >> (_len - count)) & ((1 << count) - 1);
+ _len -= count;
+ return value;
+ }
+ int getSignedBits(int len) {
+ const int shift = 32 - len;
+ const int32_t value = getBits(len);
+ return (value << shift) >> shift;
+ }
+ bool getBit() {
+ if (_len == 0) {
+ assert(_src < _end);
+ _bits = READ_LE_UINT16(_src); _src += 2;
+ _len = 16;
+ }
+ --_len;
+ return (_bits & (1 << _len)) != 0;
+ }
+};
+
+static int readDC(BitStream *bs, int version) {
+ assert(version == 2);
+ return bs->getSignedBits(10);
+}
+
+static void readAC(BitStream *bs, int *coefficients) {
+ int count = 0;
+ int node = 0;
+ while (bs->bitsAvailable() > 0) {
+ const uint16_t value = _acHuffTree[node].value;
+ switch (value) {
+ case kAcHuff_EscapeCode: {
+ const int zeroes = bs->getBits(6);
+ count += zeroes + 1;
+ assert(count < 63);
+ coefficients += zeroes;
+ *coefficients++ = bs->getSignedBits(10);
+ }
+ break;
+ case kAcHuff_EndOfBlock:
+ return;
+ case 0:
+ if (bs->getBit()) {
+ node = _acHuffTree[node].right;
+ } else {
+ node = _acHuffTree[node].left;
+ }
+ continue;
+ default: {
+ const int zeroes = value >> 8;
+ count += zeroes + 1;
+ assert(count < 63);
+ coefficients += zeroes;
+ const int nonZeroes = value & 255;
+ *coefficients++ = bs->getBit() ? -nonZeroes : nonZeroes;
+ }
+ break;
+ }
+ node = 0; // root
+ }
+}
+
+static const uint8_t _zigZagTable[8 * 8] = {
+ 0, 1, 5, 6, 14, 15, 27, 28,
+ 2, 4, 7, 13, 16, 26, 29, 42,
+ 3, 8, 12, 17, 25, 30, 41, 43,
+ 9, 11, 18, 24, 31, 40, 44, 53,
+ 10, 19, 23, 32, 39, 45, 52, 54,
+ 20, 22, 33, 38, 46, 51, 55, 60,
+ 21, 34, 37, 47, 50, 56, 59, 61,
+ 35, 36, 48, 49, 57, 58, 62, 63
+};
+
+static const uint8_t _quantizationTable[8 * 8] = {
+ 2, 16, 19, 22, 26, 27, 29, 34,
+ 16, 16, 22, 24, 27, 29, 34, 37,
+ 19, 22, 26, 27, 29, 34, 34, 38,
+ 22, 22, 26, 27, 29, 34, 37, 40,
+ 22, 26, 27, 29, 32, 35, 40, 48,
+ 26, 27, 29, 32, 35, 40, 48, 58,
+ 26, 27, 29, 34, 38, 46, 56, 69,
+ 27, 29, 35, 38, 46, 56, 69, 83
+};
+
+static void dequantizeBlock(int *coefficients, float *block, int scale) {
+ block[0] = coefficients[0] * _quantizationTable[0]; // DC
+ for (int i = 1; i < 8 * 8; i++) {
+ block[i] = coefficients[_zigZagTable[i]] * _quantizationTable[i] * scale / 8.f;
+ }
+}
+
+static const double _idct8x8[8][8] = {
+ { 0.353553390593274, 0.490392640201615, 0.461939766255643, 0.415734806151273, 0.353553390593274, 0.277785116509801, 0.191341716182545, 0.097545161008064 },
+ { 0.353553390593274, 0.415734806151273, 0.191341716182545, -0.097545161008064, -0.353553390593274, -0.490392640201615, -0.461939766255643, -0.277785116509801 },
+ { 0.353553390593274, 0.277785116509801, -0.191341716182545, -0.490392640201615, -0.353553390593274, 0.097545161008064, 0.461939766255643, 0.415734806151273 },
+ { 0.353553390593274, 0.097545161008064, -0.461939766255643, -0.277785116509801, 0.353553390593274, 0.415734806151273, -0.191341716182545, -0.490392640201615 },
+ { 0.353553390593274, -0.097545161008064, -0.461939766255643, 0.277785116509801, 0.353553390593274, -0.415734806151273, -0.191341716182545, 0.490392640201615 },
+ { 0.353553390593274, -0.277785116509801, -0.191341716182545, 0.490392640201615, -0.353553390593273, -0.097545161008064, 0.461939766255643, -0.415734806151273 },
+ { 0.353553390593274, -0.415734806151273, 0.191341716182545, 0.097545161008064, -0.353553390593274, 0.490392640201615, -0.461939766255643, 0.277785116509801 },
+ { 0.353553390593274, -0.490392640201615, 0.461939766255643, -0.415734806151273, 0.353553390593273, -0.277785116509801, 0.191341716182545, -0.097545161008064 }
+};
+
+static void idct(float *dequantData, float *result) {
+ float tmp[8 * 8];
+ // 1D IDCT rows
+ for (int y = 0; y < 8; y++) {
+ for (int x = 0; x < 8; x++) {
+ float p = 0;
+ for (int i = 0; i < 8; ++i) {
+ p += dequantData[i] * _idct8x8[x][i];
+ }
+ tmp[y + x * 8] = p;
+ }
+ dequantData += 8;
+ }
+ // 1D IDCT columns
+ for (int x = 0; x < 8; x++) {
+ const float *u = tmp + x * 8;
+ for (int y = 0; y < 8; y++) {
+ float p = 0;
+ for (int i = 0; i < 8; ++i) {
+ p += u[i] * _idct8x8[y][i];
+ }
+ result[y * 8 + x] = p;
+ }
+ }
+}
+
+static void decodeBlock(BitStream *bs, int x8, int y8, uint8_t *dst, int dstPitch, int scale, int version) {
+ int coefficients[8 * 8];
+ memset(coefficients, 0, sizeof(coefficients));
+ coefficients[0] = readDC(bs, version);
+ readAC(bs, &coefficients[1]);
+
+ float dequantData[8 * 8];
+ dequantizeBlock(coefficients, dequantData, scale);
+
+ float idctData[8 * 8];
+ idct(dequantData, idctData);
+
+ dst += (y8 * dstPitch + x8) * 8;
+ for (int y = 0; y < 8; y++) {
+ for (int x = 0; x < 8; x++) {
+ const int val = (int)round(idctData[y * 8 + x]); // (-128,127) range
+ dst[x] = (val < -128) ? 0 : ((val > 127) ? 255 : (128 + val));
+ }
+ dst += dstPitch;
+ }
+}
+
+int decodeMDEC(const uint8_t *src, int len, const uint8_t *mborder, int mblen, int w, int h, MdecOutput *out) {
+ BitStream bs(src, len);
+ bs.getBits(16);
+ const uint16_t vlc = bs.getBits(16);
+ assert(vlc == 0x3800);
+ const uint16_t qscale = bs.getBits(16);
+ const uint16_t version = bs.getBits(16);
+ // fprintf(stdout, "mdec qscale %d version %d w %d h %d\n", qscale, version, w, h);
+ assert(version == 2);
+
+ const int blockW = (w + 15) / 16;
+ const int blockH = (h + 15) / 16;
+
+ const int yPitch = out->planes[kOutputPlaneY].pitch;
+ uint8_t *yPtr = out->planes[kOutputPlaneY].ptr + out->y * yPitch + out->x;
+ const int cbPitch = out->planes[kOutputPlaneCb].pitch;
+ uint8_t *cbPtr = out->planes[kOutputPlaneCb].ptr + out->y / 2 * cbPitch + out->x / 2;
+ const int crPitch = out->planes[kOutputPlaneCr].pitch;
+ uint8_t *crPtr = out->planes[kOutputPlaneCr].ptr + out->y / 2 * crPitch + out->x / 2;
+
+ int z = 0;
+ for (int x = 0; x < blockW; ++x) {
+ for (int y = 0; y < blockH; ++y) {
+ if (z < mblen) {
+ const uint8_t xy = mborder[z];
+ if ((xy & 15) != x || (xy >> 4) != y) {
+ continue;
+ }
+ ++z;
+ }
+ decodeBlock(&bs, x, y, crPtr, crPitch, qscale, version);
+ decodeBlock(&bs, x, y, cbPtr, cbPitch, qscale, version);
+ decodeBlock(&bs, 2 * x, 2 * y, yPtr, yPitch, qscale, version);
+ decodeBlock(&bs, 2 * x + 1, 2 * y, yPtr, yPitch, qscale, version);
+ decodeBlock(&bs, 2 * x, 2 * y + 1, yPtr, yPitch, qscale, version);
+ decodeBlock(&bs, 2 * x + 1, 2 * y + 1, yPtr, yPitch, qscale, version);
+ if (mborder && z == mblen) {
+ goto end;
+ }
+ }
+ }
+end:
+ if (!mborder && bs.bitsAvailable() >= 11) {
+ const int eof = bs.getBits(11);
+ assert(eof == 0x3FE); // v2 frame
+ }
+
+ return bs._src - src;
+}
diff --git a/Src/Vita_&_Switch/mdec.h b/Src/Vita_&_Switch/mdec.h
new file mode 100644
index 0000000..6739055
--- /dev/null
+++ b/Src/Vita_&_Switch/mdec.h
@@ -0,0 +1,24 @@
+
+#ifndef MDEC_H__
+#define MDEC_H__
+
+#include
+
+enum {
+ kOutputPlaneY = 0,
+ kOutputPlaneCb = 1,
+ kOutputPlaneCr = 2
+};
+
+struct MdecOutput {
+ int x, y;
+ int w, h;
+ struct {
+ uint8_t *ptr;
+ int pitch;
+ } planes[3];
+};
+
+int decodeMDEC(const uint8_t *src, int len, const uint8_t *mborder, int mblen, int w, int h, MdecOutput *out);
+
+#endif // MDEC_H__
diff --git a/Src/Vita_&_Switch/mdec_coeffs.h b/Src/Vita_&_Switch/mdec_coeffs.h
new file mode 100644
index 0000000..d08658e
--- /dev/null
+++ b/Src/Vita_&_Switch/mdec_coeffs.h
@@ -0,0 +1,235 @@
+static const uint16_t kAcHuff_EscapeCode = 0xfffe;
+static const uint16_t kAcHuff_EndOfBlock = 0xffff;
+struct AcHuff {
+ int16_t left;
+ int16_t right;
+ uint16_t value;
+};
+static const AcHuff _acHuffTree[] = {
+ { 3, 1, 0 },
+ { 225, 2, 0 },
+ { -1, -1, 0x0001 },
+ { 9, 4, 0 },
+ { 6, 5, 0 },
+ { -1, -1, 0x0101 },
+ { 7, 8, 0 },
+ { -1, -1, 0x0002 },
+ { -1, -1, 0x0201 },
+ { 16, 10, 0 },
+ { 11, 13, 0 },
+ { 32, 12, 0 },
+ { -1, -1, 0x0003 },
+ { 14, 15, 0 },
+ { -1, -1, 0x0401 },
+ { -1, -1, 0x0301 },
+ { 24, 17, 0 },
+ { 18, 21, 0 },
+ { 19, 20, 0 },
+ { -1, -1, 0x0701 },
+ { -1, -1, 0x0601 },
+ { 22, 23, 0 },
+ { -1, -1, 0x0102 },
+ { -1, -1, 0x0501 },
+ { 47, 25, 0 },
+ { 26, 29, 0 },
+ { 27, 28, 0 },
+ { -1, -1, 0x0202 },
+ { -1, -1, 0x0901 },
+ { 30, 31, 0 },
+ { -1, -1, 0x0004 },
+ { -1, -1, 0x0801 },
+ { 33, 40, 0 },
+ { 34, 37, 0 },
+ { 35, 36, 0 },
+ { -1, -1, 0x0d01 },
+ { -1, -1, 0x0006 },
+ { 38, 39, 0 },
+ { -1, -1, 0x0c01 },
+ { -1, -1, 0x0b01 },
+ { 41, 44, 0 },
+ { 42, 43, 0 },
+ { -1, -1, 0x0302 },
+ { -1, -1, 0x0103 },
+ { 45, 46, 0 },
+ { -1, -1, 0x0005 },
+ { -1, -1, 0x0a01 },
+ { 48, 224, 0 },
+ { 64, 49, 0 },
+ { 50, 57, 0 },
+ { 51, 54, 0 },
+ { 52, 53, 0 },
+ { -1, -1, 0x1001 },
+ { -1, -1, 0x0502 },
+ { 55, 56, 0 },
+ { -1, -1, 0x0007 },
+ { -1, -1, 0x0203 },
+ { 58, 61, 0 },
+ { 59, 60, 0 },
+ { -1, -1, 0x0104 },
+ { -1, -1, 0x0f01 },
+ { 62, 63, 0 },
+ { -1, -1, 0x0e01 },
+ { -1, -1, 0x0402 },
+ { 96, 65, 0 },
+ { 66, 81, 0 },
+ { 67, 74, 0 },
+ { 68, 71, 0 },
+ { 69, 70, 0 },
+ { -1, -1, 0x000b },
+ { -1, -1, 0x0802 },
+ { 72, 73, 0 },
+ { -1, -1, 0x0403 },
+ { -1, -1, 0x000a },
+ { 75, 78, 0 },
+ { 76, 77, 0 },
+ { -1, -1, 0x0204 },
+ { -1, -1, 0x0702 },
+ { 79, 80, 0 },
+ { -1, -1, 0x1501 },
+ { -1, -1, 0x1401 },
+ { 82, 89, 0 },
+ { 83, 86, 0 },
+ { 84, 85, 0 },
+ { -1, -1, 0x0009 },
+ { -1, -1, 0x1301 },
+ { 87, 88, 0 },
+ { -1, -1, 0x1201 },
+ { -1, -1, 0x0105 },
+ { 90, 93, 0 },
+ { 91, 92, 0 },
+ { -1, -1, 0x0303 },
+ { -1, -1, 0x0008 },
+ { 94, 95, 0 },
+ { -1, -1, 0x0602 },
+ { -1, -1, 0x1101 },
+ { 128, 97, 0 },
+ { 98, 113, 0 },
+ { 99, 106, 0 },
+ { 100, 103, 0 },
+ { 101, 102, 0 },
+ { -1, -1, 0x0a02 },
+ { -1, -1, 0x0902 },
+ { 104, 105, 0 },
+ { -1, -1, 0x0503 },
+ { -1, -1, 0x0304 },
+ { 107, 110, 0 },
+ { 108, 109, 0 },
+ { -1, -1, 0x0205 },
+ { -1, -1, 0x0107 },
+ { 111, 112, 0 },
+ { -1, -1, 0x0106 },
+ { -1, -1, 0x000f },
+ { 114, 121, 0 },
+ { 115, 118, 0 },
+ { 116, 117, 0 },
+ { -1, -1, 0x000e },
+ { -1, -1, 0x000d },
+ { 119, 120, 0 },
+ { -1, -1, 0x000c },
+ { -1, -1, 0x1a01 },
+ { 122, 125, 0 },
+ { 123, 124, 0 },
+ { -1, -1, 0x1901 },
+ { -1, -1, 0x1801 },
+ { 126, 127, 0 },
+ { -1, -1, 0x1701 },
+ { -1, -1, 0x1601 },
+ { 160, 129, 0 },
+ { 130, 145, 0 },
+ { 131, 138, 0 },
+ { 132, 135, 0 },
+ { 133, 134, 0 },
+ { -1, -1, 0x001f },
+ { -1, -1, 0x001e },
+ { 136, 137, 0 },
+ { -1, -1, 0x001d },
+ { -1, -1, 0x001c },
+ { 139, 142, 0 },
+ { 140, 141, 0 },
+ { -1, -1, 0x001b },
+ { -1, -1, 0x001a },
+ { 143, 144, 0 },
+ { -1, -1, 0x0019 },
+ { -1, -1, 0x0018 },
+ { 146, 153, 0 },
+ { 147, 150, 0 },
+ { 148, 149, 0 },
+ { -1, -1, 0x0017 },
+ { -1, -1, 0x0016 },
+ { 151, 152, 0 },
+ { -1, -1, 0x0015 },
+ { -1, -1, 0x0014 },
+ { 154, 157, 0 },
+ { 155, 156, 0 },
+ { -1, -1, 0x0013 },
+ { -1, -1, 0x0012 },
+ { 158, 159, 0 },
+ { -1, -1, 0x0011 },
+ { -1, -1, 0x0010 },
+ { 192, 161, 0 },
+ { 162, 177, 0 },
+ { 163, 170, 0 },
+ { 164, 167, 0 },
+ { 165, 166, 0 },
+ { -1, -1, 0x0028 },
+ { -1, -1, 0x0027 },
+ { 168, 169, 0 },
+ { -1, -1, 0x0026 },
+ { -1, -1, 0x0025 },
+ { 171, 174, 0 },
+ { 172, 173, 0 },
+ { -1, -1, 0x0024 },
+ { -1, -1, 0x0023 },
+ { 175, 176, 0 },
+ { -1, -1, 0x0022 },
+ { -1, -1, 0x0021 },
+ { 178, 185, 0 },
+ { 179, 182, 0 },
+ { 180, 181, 0 },
+ { -1, -1, 0x0020 },
+ { -1, -1, 0x010e },
+ { 183, 184, 0 },
+ { -1, -1, 0x010d },
+ { -1, -1, 0x010c },
+ { 186, 189, 0 },
+ { 187, 188, 0 },
+ { -1, -1, 0x010b },
+ { -1, -1, 0x010a },
+ { 190, 191, 0 },
+ { -1, -1, 0x0109 },
+ { -1, -1, 0x0108 },
+ { -1, 193, 0 },
+ { 194, 209, 0 },
+ { 195, 202, 0 },
+ { 196, 199, 0 },
+ { 197, 198, 0 },
+ { -1, -1, 0x0112 },
+ { -1, -1, 0x0111 },
+ { 200, 201, 0 },
+ { -1, -1, 0x0110 },
+ { -1, -1, 0x010f },
+ { 203, 206, 0 },
+ { 204, 205, 0 },
+ { -1, -1, 0x0603 },
+ { -1, -1, 0x1002 },
+ { 207, 208, 0 },
+ { -1, -1, 0x0f02 },
+ { -1, -1, 0x0e02 },
+ { 210, 217, 0 },
+ { 211, 214, 0 },
+ { 212, 213, 0 },
+ { -1, -1, 0x0d02 },
+ { -1, -1, 0x0c02 },
+ { 215, 216, 0 },
+ { -1, -1, 0x0b02 },
+ { -1, -1, 0x1f01 },
+ { 218, 221, 0 },
+ { 219, 220, 0 },
+ { -1, -1, 0x1e01 },
+ { -1, -1, 0x1d01 },
+ { 222, 223, 0 },
+ { -1, -1, 0x1c01 },
+ { -1, -1, 0x1b01 },
+ { -1, -1, 0xfffe },
+ { -1, -1, 0xffff },
+};
diff --git a/Src/Vita_&_Switch/menu.cpp b/Src/Vita_&_Switch/menu.cpp
new file mode 100644
index 0000000..5266b41
--- /dev/null
+++ b/Src/Vita_&_Switch/menu.cpp
@@ -0,0 +1,1338 @@
+
+#include "game.h"
+#include "menu.h"
+#include "lzw.h"
+#include "paf.h"
+#include "resource.h"
+#include "system.h"
+#include "util.h"
+#include "video.h"
+
+enum {
+ kTitleScreen_AssignPlayer = 0,
+ kTitleScreen_Play = 1,
+ kTitleScreen_Options = 2,
+ kTitleScreen_Quit = 3,
+ kTitleScreenPSX_Load = 3, // PSX does not have 'Quit'
+ kTitleScreenPSX_Save = 4
+};
+
+enum {
+ kMenu_NewGame = 0,
+ kMenu_CurrentGame = 2,
+ kMenu_Load = 4,
+ kMenu_ResumeGame = 7,
+ kMenu_Settings = 8,
+ kMenu_Quit = 15,
+ kMenu_Cutscenes = 17
+};
+
+enum {
+ kSound_0x60 = 0x60 / 8,
+ kSound_0x70 = 0x70 / 8,
+ kSound_0x78 = 0x78 / 8,
+ kSound_0x80 = 0x80 / 8,
+ kSound_0x88 = 0x88 / 8,
+ kSound_0x90 = 0x90 / 8,
+ kSound_0x98 = 0x98 / 8,
+ kSound_0xA0 = 0xA0 / 8
+};
+
+enum {
+ kSettingNum_Controls = 0,
+ kSettingNum_Difficulty = 1,
+ kSettingNum_Sound = 2,
+ kSettingNum_Confirm = 3
+};
+
+enum {
+ kSoundNum_Stereo = 0,
+ kSoundNum_Volume = 1,
+ kSoundNum_Confirm = 2,
+ kSoundNum_Test = 3,
+ kSoundNum_Cancel = 4,
+ kSoundNum_Reset = 5
+};
+
+static void setDefaultsSetupCfg(SetupConfig *config, int num) {
+ assert(num >= 0 && num < 4);
+ memset(config->players[num].progress, 0, 10);
+ config->players[num].levelNum = 0;
+ config->players[num].checkpointNum = 0;
+ config->players[num].cutscenesMask = 0;
+ memset(config->players[num].controls, 0, 32);
+ config->players[num].controls[0x0] = 0x11;
+ config->players[num].controls[0x4] = 0x22;
+ config->players[num].controls[0x8] = 0x84;
+ config->players[num].controls[0xC] = 0x48;
+ config->players[num].difficulty = 1;
+ config->players[num].stereo = 1;
+ config->players[num].volume = Game::kDefaultSoundVolume;
+ config->players[num].lastLevelNum = 0;
+}
+
+static bool isEmptySetupCfg(SetupConfig *config, int num) {
+ return config->players[num].levelNum == 0 && config->players[num].checkpointNum == 0 && config->players[num].cutscenesMask == 0;
+}
+
+Menu::Menu(Game *g, PafPlayer *paf, Resource *res, Video *video)
+ : _g(g), _paf(paf), _res(res), _video(video) {
+
+ _config = &_g->_setupConfig;
+}
+
+void Menu::setVolume() {
+ const int volume = _config->players[_config->currentPlayer].volume;
+ if (volume != _g->_snd_masterVolume) {
+ _g->_snd_masterVolume = volume;
+ if (volume == 0) {
+ _g->muteSound();
+ } else {
+ _g->unmuteSound();
+ }
+ }
+}
+
+static uint32_t readBitmapsGroup(int count, DatBitmapsGroup *bitmapsGroup, uint32_t ptrOffset, int paletteSize) {
+ const uint32_t baseOffset = ptrOffset;
+ for (int i = 0; i < count; ++i) {
+ int size;
+ if (paletteSize == 0) { // PSX
+ size = le32toh(bitmapsGroup[i].offset);
+ } else {
+ size = bitmapsGroup[i].w * bitmapsGroup[i].h;
+ }
+ bitmapsGroup[i].offset = ptrOffset - baseOffset;
+ bitmapsGroup[i].palette = bitmapsGroup[i].offset + size;
+ ptrOffset += size + paletteSize;
+ }
+ return ptrOffset - baseOffset;
+}
+
+static uint32_t readSoundData(uint8_t *soundData, uint32_t soundDataSize) {
+ const int soundListsCount = READ_LE_UINT32(soundData + 4);
+ const uint8_t *listData = soundData + 8 + soundListsCount * 8;
+ for (int i = 0; i < soundListsCount; ++i) {
+ WRITE_LE_UINT32(soundData + 8 + i * 8, listData - soundData);
+ const int count = READ_LE_UINT32(soundData + 8 + i * 8 + 4);
+ listData += count * sizeof(uint16_t);
+ }
+ assert((uint32_t)(listData - soundData) == soundDataSize);
+ return soundDataSize;
+}
+
+void Menu::loadData() {
+ _g->_mix._lock(1);
+ _res->loadDatMenuBuffers();
+ _g->clearSoundObjects();
+ _g->_mix._lock(0);
+
+ const int version = _res->_datHdr.version;
+
+ const int paletteSize = _res->_isPsx ? 0 : 256 * 3;
+
+ const uint8_t *ptr = _res->_menuBuffer1;
+ uint32_t hdrOffset, ptrOffset = 0;
+
+ if (version == 10) {
+
+ _titleSprites = (DatSpritesGroup *)(ptr + ptrOffset);
+ _titleSprites->size = le32toh(_titleSprites->size);
+ _titleSprites->count = le16toh(_titleSprites->count);
+ ptrOffset += sizeof(DatSpritesGroup) + _titleSprites->size;
+ _titleSprites->firstFrameOffset = 0;
+
+ _playerSprites = (DatSpritesGroup *)(ptr + ptrOffset);
+ _playerSprites->size = le32toh(_playerSprites->size);
+ _playerSprites->count = le16toh(_playerSprites->count);
+ ptrOffset += sizeof(DatSpritesGroup) + _playerSprites->size;
+ _playerSprites->firstFrameOffset = 0;
+
+ _titleBitmapSize = READ_LE_UINT32(ptr + ptrOffset);
+ _titleBitmapData = ptr + ptrOffset + sizeof(DatBitmap);
+ ptrOffset += sizeof(DatBitmap) + _titleBitmapSize + paletteSize;
+
+ _playerBitmapSize = READ_LE_UINT32(ptr + ptrOffset);
+ _playerBitmapData = ptr + ptrOffset + sizeof(DatBitmap);
+ ptrOffset += sizeof(DatBitmap) + _playerBitmapSize + paletteSize;
+
+ const int size = READ_LE_UINT32(ptr + ptrOffset); ptrOffset += 4;
+ assert((size % (16 * 10)) == 0);
+ _digitsData = ptr + ptrOffset;
+ ptrOffset += size;
+
+ const int cutscenesCount = _res->_datHdr.cutscenesCount;
+ _cutscenesBitmaps = (DatBitmapsGroup *)(ptr + ptrOffset);
+ ptrOffset += cutscenesCount * sizeof(DatBitmapsGroup);
+ _cutscenesBitmapsData = ptr + ptrOffset;
+ ptrOffset += readBitmapsGroup(cutscenesCount, _cutscenesBitmaps, ptrOffset, paletteSize);
+
+ for (int i = 0; i < kCheckpointLevelsCount; ++i) {
+ DatBitmapsGroup *bitmapsGroup = (DatBitmapsGroup *)(ptr + ptrOffset);
+ _checkpointsBitmaps[i] = bitmapsGroup;
+ const int count = _res->_datHdr.levelCheckpointsCount[i];
+ ptrOffset += count * sizeof(DatBitmapsGroup);
+ _checkpointsBitmapsData[i] = ptr + ptrOffset;
+ ptrOffset += readBitmapsGroup(count, bitmapsGroup, ptrOffset, paletteSize);
+ }
+
+ _soundData = ptr + ptrOffset;
+ readSoundData(_res->_menuBuffer1 + ptrOffset, _res->_datHdr.soundDataSize);
+ ptrOffset += _res->_datHdr.soundDataSize;
+
+ } else if (version == 11) {
+
+ hdrOffset = 4;
+
+ ptrOffset = 4 + (2 + kOptionsCount) * sizeof(DatBitmap);
+ ptrOffset += _res->_datHdr.cutscenesCount * sizeof(DatBitmapsGroup);
+ for (int i = 0; i < kCheckpointLevelsCount; ++i) {
+ ptrOffset += _res->_datHdr.levelCheckpointsCount[i] * sizeof(DatBitmapsGroup);
+ }
+ ptrOffset += _res->_datHdr.levelsCount * sizeof(DatBitmapsGroup);
+
+ _titleBitmapSize = READ_LE_UINT32(ptr + hdrOffset);
+ hdrOffset += sizeof(DatBitmap);
+ _titleBitmapData = ptr + ptrOffset;
+ ptrOffset += _titleBitmapSize + paletteSize;
+
+ _playerBitmapSize = READ_LE_UINT32(ptr + hdrOffset);
+ hdrOffset += sizeof(DatBitmap);
+ _playerBitmapData = ptr + ptrOffset;
+ ptrOffset += _playerBitmapSize + paletteSize;
+
+ for (int i = 0; i < kOptionsCount; ++i) {
+ _optionsBitmapSize[i] = READ_LE_UINT32(ptr + hdrOffset);
+ hdrOffset += sizeof(DatBitmap);
+ if (_optionsBitmapSize[i] != 0) {
+ _optionsBitmapData[i] = ptr + ptrOffset;
+ ptrOffset += _optionsBitmapSize[i] + paletteSize;
+ } else {
+ _optionsBitmapData[i] = 0;
+ }
+ }
+
+ const int cutscenesCount = _res->_datHdr.cutscenesCount;
+ _cutscenesBitmaps = (DatBitmapsGroup *)(ptr + hdrOffset);
+ _cutscenesBitmapsData = ptr + ptrOffset;
+ hdrOffset += cutscenesCount * sizeof(DatBitmapsGroup);
+ ptrOffset += readBitmapsGroup(cutscenesCount, _cutscenesBitmaps, ptrOffset, paletteSize);
+
+ for (int i = 0; i < kCheckpointLevelsCount; ++i) {
+ DatBitmapsGroup *bitmapsGroup = (DatBitmapsGroup *)(ptr + hdrOffset);
+ _checkpointsBitmaps[i] = bitmapsGroup;
+ _checkpointsBitmapsData[i] = ptr + ptrOffset;
+ const int count = _res->_datHdr.levelCheckpointsCount[i];
+ hdrOffset += count * sizeof(DatBitmapsGroup);
+ ptrOffset += readBitmapsGroup(count, bitmapsGroup, ptrOffset, paletteSize);
+ }
+
+ const int levelsCount = _res->_datHdr.levelsCount;
+ _levelsBitmaps = (DatBitmapsGroup *)(ptr + hdrOffset);
+ _levelsBitmapsData = ptr + ptrOffset;
+ ptrOffset += readBitmapsGroup(levelsCount, _levelsBitmaps, ptrOffset, paletteSize);
+ }
+
+ ptr = _res->_menuBuffer0;
+ ptrOffset = 0;
+
+ if (version == 11) {
+
+ _titleSprites = (DatSpritesGroup *)(ptr + ptrOffset);
+ _titleSprites->size = le32toh(_titleSprites->size);
+ ptrOffset += sizeof(DatSpritesGroup) + _titleSprites->size;
+ _titleSprites->count = le16toh(_titleSprites->count);
+ _titleSprites->firstFrameOffset = 0;
+
+ _playerSprites = (DatSpritesGroup *)(ptr + ptrOffset);
+ _playerSprites->size = le32toh(_playerSprites->size);
+ ptrOffset += sizeof(DatSpritesGroup) + _playerSprites->size;
+ _playerSprites->count = le16toh(_playerSprites->count);
+ _playerSprites->firstFrameOffset = 0;
+
+ _optionData = ptr + ptrOffset;
+ ptrOffset += _res->_datHdr.menusCount * 8;
+
+ const int size = READ_LE_UINT32(ptr + ptrOffset); ptrOffset += 4;
+ assert((size % (16 * 10)) == 0);
+ _digitsData = ptr + ptrOffset;
+ ptrOffset += size;
+
+ _soundData = ptr + ptrOffset;
+ readSoundData(_res->_menuBuffer0 + ptrOffset, _res->_datHdr.soundDataSize);
+ ptrOffset += _res->_datHdr.soundDataSize;
+ }
+
+ if (_res->_isPsx) {
+ return;
+ }
+
+ hdrOffset = ptrOffset;
+ _iconsSprites = (DatSpritesGroup *)(ptr + ptrOffset);
+ const int iconsCount = _res->_datHdr.iconsCount;
+ ptrOffset += iconsCount * sizeof(DatSpritesGroup);
+ _iconsSpritesData = ptr + ptrOffset;
+ for (int i = 0; i < iconsCount; ++i) {
+ _iconsSprites[i].size = le32toh(_iconsSprites[i].size);
+ _iconsSprites[i].count = le16toh(_iconsSprites[i].count);
+ _iconsSprites[i].firstFrameOffset = ptr + ptrOffset - _iconsSpritesData;
+ ptrOffset += _iconsSprites[i].size;
+ }
+
+ _optionsButtonSpritesCount = READ_LE_UINT32(ptr + ptrOffset); ptrOffset += 4;
+ if (_optionsButtonSpritesCount != 0) {
+ _optionsButtonSpritesData = ptr + ptrOffset;
+ hdrOffset = ptrOffset;
+ ptrOffset += _optionsButtonSpritesCount * 20;
+ for (int i = 0; i < _optionsButtonSpritesCount; ++i) {
+ DatSpritesGroup *spritesGroup = (DatSpritesGroup *)(ptr + hdrOffset + 4);
+ spritesGroup->size = le32toh(spritesGroup->size);
+ spritesGroup->count = le16toh(spritesGroup->count);
+ spritesGroup->firstFrameOffset = ptr + ptrOffset - _optionsButtonSpritesData;
+ hdrOffset += 20;
+ ptrOffset += spritesGroup->size;
+ }
+ } else {
+ _optionsButtonSpritesData = 0;
+ }
+
+ if (version == 10) {
+
+ _optionData = ptr + ptrOffset;
+ ptrOffset += _res->_datHdr.menusCount * 8;
+
+ hdrOffset = ptrOffset;
+ ptrOffset += kOptionsCount * sizeof(DatBitmap);
+ for (int i = 0; i < kOptionsCount; ++i) {
+ _optionsBitmapSize[i] = READ_LE_UINT32(ptr + hdrOffset);
+ hdrOffset += sizeof(DatBitmap);
+ if (_optionsBitmapSize[i] != 0) {
+ _optionsBitmapData[i] = ptr + ptrOffset;
+ ptrOffset += _optionsBitmapSize[i] + paletteSize;
+ } else {
+ _optionsBitmapData[i] = 0;
+ }
+ }
+
+ const int levelsCount = _res->_datHdr.levelsCount;
+ hdrOffset = ptrOffset;
+ ptrOffset += levelsCount * sizeof(DatBitmapsGroup);
+ _levelsBitmaps = (DatBitmapsGroup *)(ptr + hdrOffset);
+ _levelsBitmapsData = ptr + ptrOffset;
+ readBitmapsGroup(levelsCount, _levelsBitmaps, ptrOffset, paletteSize);
+ }
+}
+
+int Menu::getSoundNum(int num, int index) const {
+ const int soundListsCount = READ_LE_UINT32(_soundData + 4);
+ if (num < soundListsCount) {
+ const int count = READ_LE_UINT32(_soundData + 8 + num * 8 + 4);
+ assert(index < count);
+ const uint8_t *data = _soundData + READ_LE_UINT32(_soundData + 8 + num * 8);
+ return (int16_t)READ_LE_UINT16(data + index * 2);
+ }
+ return -1;
+}
+
+void Menu::playSound(int num) {
+ num = getSoundNum(num);
+ debug(kDebug_MENU, "playSound %d", num);
+ if (num != -1) {
+ _g->playSound(num, 0, 0, 5);
+ }
+}
+
+void Menu::drawSprite(const DatSpritesGroup *spriteGroup, const uint8_t *ptr, uint32_t num, int x, int y) {
+ ptr += spriteGroup->firstFrameOffset;
+ for (uint32_t i = 0; i < spriteGroup->count; ++i) {
+ const uint16_t size = READ_LE_UINT16(ptr + 2);
+ if (num == i) {
+ if (_res->_isPsx) {
+ _video->decodeBackgroundOverlayPsx(ptr);
+ } else {
+ if (x < 0) {
+ x = ptr[0];
+ }
+ if (y < 0) {
+ y = ptr[1];
+ }
+ _video->decodeSPR(ptr + 8, _video->_frontLayer, x, y, 0, READ_LE_UINT16(ptr + 4), READ_LE_UINT16(ptr + 6));
+ }
+ break;
+ }
+ ptr += size + 2;
+ }
+}
+
+void Menu::drawSpriteAnim(DatSpritesGroup *spriteGroup, const uint8_t *ptr, uint32_t num) {
+ if (spriteGroup[num].num == 0) {
+ spriteGroup[num].currentFrameOffset = spriteGroup[num].firstFrameOffset;
+ }
+ ptr += spriteGroup[num].currentFrameOffset;
+ if (_res->_isPsx) {
+ _video->decodeBackgroundOverlayPsx(ptr);
+ } else {
+ _video->decodeSPR(ptr + 8, _video->_frontLayer, ptr[0], ptr[1], 0, READ_LE_UINT16(ptr + 4), READ_LE_UINT16(ptr + 6));
+ }
+ ++spriteGroup[num].num;
+ if (spriteGroup[num].num < spriteGroup[num].count) {
+ const uint16_t size = READ_LE_UINT16(ptr + 2);
+ spriteGroup[num].currentFrameOffset += size + 2;
+ } else {
+ spriteGroup[num].num = 0; // restart from frame #0
+ spriteGroup[num].currentFrameOffset = spriteGroup[num].firstFrameOffset;
+ }
+}
+
+void Menu::refreshScreen(bool updatePalette) {
+ if (updatePalette) {
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ }
+ _video->updateGameDisplay(_video->_frontLayer);
+ g_system->updateScreen(false);
+}
+
+void Menu::pafCallback(int frameNum, const uint8_t *frameData) {
+ if (_currentOptionButtonSound != 0) {
+ const int num = getSoundNum(_currentOptionButtonSound, frameNum);
+ if (num != -1) {
+ _g->playSound(num, 0, 0, 5);
+ }
+ }
+ if (_currentOptionButtonSprite && frameNum == _currentOptionButtonSprite->num) {
+ memcpy(_video->_frontLayer, frameData, Video::W * Video::H);
+ drawSpriteAnim(_currentOptionButtonSprite, _optionsButtonSpritesData, 0);
+ g_system->copyRect(0, 0, Video::W, Video::H, _video->_frontLayer, Video::W);
+ }
+}
+
+static void menuPafCallback(void *userdata, int frame, const uint8_t *buffer) {
+ ((Menu *)userdata)->pafCallback(frame, buffer);
+}
+
+bool Menu::mainLoop() {
+ bool ret = false;
+ loadData();
+ while (!g_system->inp.quit) {
+ const int option = handleTitleScreen();
+ if (option == kTitleScreen_AssignPlayer) {
+ handleAssignPlayer();
+ debug(kDebug_MENU, "currentPlayer %d", _config->currentPlayer);
+ } else if (option == kTitleScreen_Play) {
+ return true;
+ } else if (option == kTitleScreen_Options) {
+ PafCallback pafCb;
+ pafCb.proc = menuPafCallback;
+ pafCb.userdata = this;
+ _paf->setCallback(&pafCb);
+ playSound(kSound_0xA0);
+ handleOptions();
+ debug(kDebug_MENU, "optionNum %d", _optionNum);
+ _g->resetSound();
+ _paf->setCallback(0);
+ if (_optionNum == kMenu_NewGame + 1 || _optionNum == kMenu_CurrentGame + 1 || _optionNum == kMenu_ResumeGame) {
+ ret = true;
+ break;
+ } else if (_optionNum == kMenu_Quit + 1) {
+ break;
+ }
+ } else if (option == kTitleScreen_Quit) {
+ break;
+ }
+ }
+ _res->unloadDatMenuBuffers();
+ return ret;
+}
+
+void Menu::drawTitleScreen(int option) {
+ if (_res->_isPsx) {
+ _video->decodeBackgroundPsx(_titleBitmapData, _titleBitmapSize, Video::W, Video::H);
+ } else {
+ decodeLZW(_titleBitmapData, _video->_frontLayer);
+ g_system->setPalette(_titleBitmapData + _titleBitmapSize, 256, 6);
+ }
+ drawSprite(_titleSprites, (const uint8_t *)&_titleSprites[1], option);
+ refreshScreen(false);
+}
+
+int Menu::handleTitleScreen() {
+ const int firstOption = kTitleScreen_AssignPlayer;
+ const int lastOption = _res->_isPsx ? kTitleScreen_Play : kTitleScreen_Quit;
+ int currentOption = kTitleScreen_Play;
+ while (!g_system->inp.quit) {
+ g_system->processEvents();
+ if (g_system->inp.keyReleased(SYS_INP_UP)) {
+ if (currentOption > firstOption) {
+ playSound(kSound_0x70);
+ --currentOption;
+ }
+ }
+ if (g_system->inp.keyReleased(SYS_INP_DOWN)) {
+ if (currentOption < lastOption) {
+ playSound(kSound_0x70);
+ ++currentOption;
+ }
+ }
+ if (g_system->inp.keyReleased(SYS_INP_SHOOT) || g_system->inp.keyReleased(SYS_INP_JUMP)) {
+ playSound(kSound_0x78);
+ break;
+ }
+ drawTitleScreen(currentOption);
+ g_system->sleep(15);
+ }
+ return currentOption;
+}
+
+void Menu::drawDigit(int x, int y, int num) {
+ assert(x >= 0 && x + 16 < Video::W && y >= 0 && y + 10 < Video::H);
+ assert(num < 16);
+ uint8_t *dst = _video->_frontLayer + y * Video::W + x;
+ const uint8_t *src = _digitsData + num * 16;
+ for (int j = 0; j < 10; ++j) {
+ for (int i = 0; i < 16; ++i) {
+ if (src[i] != 0) {
+ dst[i] = src[i];
+ }
+ }
+ dst += Video::W;
+ src += Video::W;
+ }
+}
+
+void Menu::drawBitmap(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int x, int y, int w, int h, uint8_t baseColor) {
+ if (_res->_isPsx) {
+ const int size = bitmapsGroup->palette - bitmapsGroup->offset;
+ _video->decodeBackgroundPsx(bitmapData, size, w, h, x, y);
+ return;
+ }
+ const int srcPitch = w;
+ if (x < 0) {
+ bitmapData -= x;
+ w += x;
+ x = 0;
+ }
+ if (x + w > Video::W) {
+ w = Video::W - x;
+ }
+ if (y < 0) {
+ bitmapData -= y * srcPitch;
+ h += y;
+ y = 0;
+ }
+ if (y + h > Video::H) {
+ h = Video::H - y;
+ }
+ uint8_t *dst = _video->_frontLayer + y * Video::W + x;
+ for (int j = 0; j < h; ++j) {
+ for (int i = 0; i < w; ++i) {
+ dst[i] = baseColor - bitmapsGroup->colors + bitmapData[i];
+ }
+ dst += Video::W;
+ bitmapData += srcPitch;
+ }
+}
+
+void Menu::setCurrentPlayer(int num) {
+ debug(kDebug_MENU, "setCurrentPlayer %d", num);
+ setLevelCheckpoint(num);
+ _levelNum = _config->players[num].levelNum;
+ if (_levelNum > kLvl_dark) {
+ _levelNum = kLvl_dark;
+ }
+ if (_levelNum == kLvl_dark) {
+ _levelNum = 7;
+ _checkpointNum = 11;
+ } else {
+ _checkpointNum = _config->players[num].checkpointNum;
+ if (_checkpointNum >= _res->_datHdr.levelCheckpointsCount[_levelNum]) {
+ _checkpointNum = _res->_datHdr.levelCheckpointsCount[_levelNum] - 1;
+ }
+ }
+ const DatBitmapsGroup *bitmap = &_checkpointsBitmaps[_levelNum][_checkpointNum];
+ memcpy(_paletteBuffer + 205 * 3, _checkpointsBitmapsData[_levelNum] + bitmap->palette, 50 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+}
+
+void Menu::setLevelCheckpoint(int num) {
+ for (int i = 0; i < kCheckpointLevelsCount; ++i) {
+ uint8_t checkpoint = _config->players[num].progress[i] + 1;
+ if (checkpoint >= _res->_datHdr.levelCheckpointsCount[i]) {
+ checkpoint = _res->_datHdr.levelCheckpointsCount[i];
+ }
+ _lastLevelCheckpointNum[i] = checkpoint;
+ }
+}
+
+void Menu::drawPlayerProgress(int state, int cursor) {
+ if (_res->_isPsx) {
+ _video->decodeBackgroundPsx(_playerBitmapData, _playerBitmapSize, Video::W, Video::H);
+ } else {
+ decodeLZW(_playerBitmapData, _video->_frontLayer);
+ }
+ int player = 0;
+ for (int y = 96; y < 164; y += 17) {
+ if (isEmptySetupCfg(_config, player)) {
+ drawSprite(_playerSprites, (const uint8_t *)&_playerSprites[1], 3, 82, y - 3); // empty
+ } else {
+ int levelNum = _config->players[player].levelNum;
+ int checkpointNum;
+ if (levelNum == kLvl_dark) {
+ levelNum = 7;
+ checkpointNum = 11;
+ } else {
+ checkpointNum = _config->players[player].checkpointNum;
+ }
+ drawDigit(145, y, levelNum + 1);
+ drawDigit(234, y, checkpointNum + 1);
+ }
+ ++player;
+ }
+ player = (cursor == 0 || cursor == 5) ? _config->currentPlayer : (cursor - 1);
+ uint8_t *p = _video->_frontLayer;
+ const int offset = (player * 17) + 92;
+ for (int i = 0; i < 16; ++i) { // player
+ memcpy(p + i * Video::W + 6935, p + i * Video::W + (offset + 1) * 256 + 8, 72);
+ }
+ for (int i = 0; i < 16; ++i) { // level
+ memcpy(p + i * Video::W + 11287, p + i * Video::W + (offset + 1) * 256 + 83, 76);
+ }
+ for (int i = 0; i < 16; ++i) { // checkpoint
+ memcpy(p + i * Video::W + 15639, p + i * Video::W + (offset + 1) * 256 + 172, 76);
+ }
+ if (!isEmptySetupCfg(_config, player)) {
+ DatBitmapsGroup *bitmap = &_checkpointsBitmaps[_levelNum][_checkpointNum];
+ const uint8_t *src = _checkpointsBitmapsData[_levelNum] + bitmap->offset;
+ drawBitmap(bitmap, src, 132, 0, bitmap->w, bitmap->h, 205);
+ }
+ if (cursor > 0) {
+ if (cursor <= 4) { // highlight one player
+ drawSprite(_playerSprites, (const uint8_t *)&_playerSprites[1], 8, 2, cursor * 17 + 74);
+ drawSprite(_playerSprites, (const uint8_t *)&_playerSprites[1], 6);
+ } else if (cursor == 5) { // cancel
+ drawSprite(_playerSprites, (const uint8_t *)&_playerSprites[1], 7);
+ }
+ }
+ drawSprite(_playerSprites, (const uint8_t *)&_playerSprites[1], state); // Yes/No
+ if (state > 2) {
+ drawSprite(_playerSprites, (const uint8_t *)&_playerSprites[1], 2); // MessageBox
+ }
+ refreshScreen(false);
+}
+
+void Menu::handleAssignPlayer() {
+ memcpy(_paletteBuffer, _playerBitmapData + _playerBitmapSize, 256 * 3);
+ int state = 1;
+ int cursor = 0;
+ setCurrentPlayer(_config->currentPlayer);
+ drawPlayerProgress(state, cursor);
+ while (!g_system->inp.quit) {
+ g_system->processEvents();
+ if (g_system->inp.keyReleased(SYS_INP_SHOOT) || g_system->inp.keyReleased(SYS_INP_JUMP)) {
+ if (state != 0 && cursor == 5) {
+ playSound(kSound_0x80);
+ } else {
+ playSound(kSound_0x78);
+ }
+ if (state == 0) {
+ // return to title screen
+ return;
+ }
+ if (cursor == 0) {
+ cursor = _config->currentPlayer + 1;
+ } else {
+ if (cursor == 5) {
+ cursor = 0;
+ } else if (state == 1) { // select
+ --cursor;
+ _config->currentPlayer = cursor;
+ // setVolume();
+ cursor = 0;
+ } else if (state == 2) { // clear
+ state = 5; // 'No'
+ } else {
+ if (state == 4) { // 'Yes', clear confirmation
+ --cursor;
+ setDefaultsSetupCfg(_config, cursor);
+ // setVolume();
+ }
+ setCurrentPlayer(_config->currentPlayer);
+ state = 2;
+ cursor = 0;
+ }
+ }
+ }
+ if (cursor != 0 && state < 3) {
+ if (g_system->inp.keyReleased(SYS_INP_UP)) {
+ if (cursor > 1) {
+ playSound(kSound_0x70);
+ --cursor;
+ setCurrentPlayer(cursor - 1);
+ }
+ }
+ if (g_system->inp.keyReleased(SYS_INP_DOWN)) {
+ if (cursor < 5) {
+ playSound(kSound_0x70);
+ ++cursor;
+ setCurrentPlayer((cursor == 5) ? _config->currentPlayer : (cursor - 1));
+ }
+ }
+ } else {
+ if (g_system->inp.keyReleased(SYS_INP_LEFT)) {
+ if (state == 1 || state == 2 || state == 5) {
+ playSound(kSound_0x70);
+ --state;
+ }
+ }
+ if (g_system->inp.keyReleased(SYS_INP_RIGHT)) {
+ if (state == 0 || state == 1 || state == 4) {
+ playSound(kSound_0x70);
+ ++state;
+ }
+ }
+ }
+ drawPlayerProgress(state, cursor);
+ g_system->sleep(15);
+ }
+}
+
+void Menu::updateBitmapsCircularList(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int num, int count) {
+ _bitmapCircularListIndex[1] = num;
+ if (count > 1) {
+ _bitmapCircularListIndex[2] = (num + 1) % count;
+ } else {
+ _bitmapCircularListIndex[2] = -1;
+ }
+ if (count > 2) {
+ _bitmapCircularListIndex[0] = (num + count - 1) % count;
+ } else {
+ _bitmapCircularListIndex[0] = -1;
+ }
+ if (bitmapsGroup == _cutscenesBitmaps) {
+ for (int i = 0; i < 3; ++i) {
+ const int num = _bitmapCircularListIndex[i];
+ if (num != -1 && num < _cutsceneIndexesCount) {
+ _bitmapCircularListIndex[i] = _cutsceneIndexes[num];
+ }
+ }
+ }
+ for (int i = 0; i < 3; ++i) {
+ if (_bitmapCircularListIndex[i] != -1) {
+ const DatBitmapsGroup *bitmap = &bitmapsGroup[_bitmapCircularListIndex[i]];
+ memcpy(_paletteBuffer + (105 + 50 * i) * 3, bitmapData + bitmap->palette, 50 * 3);
+ }
+ }
+ g_system->setPalette(_paletteBuffer, 256, 6);
+}
+
+void Menu::drawBitmapsCircularList(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int num, int count, bool updatePalette) {
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 768);
+ if (updatePalette) {
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ }
+ updateBitmapsCircularList(bitmapsGroup, bitmapData, num, count);
+ static const int xPos[] = { -60, 68, 196 };
+ for (int i = 0; i < 3; ++i) {
+ if (_bitmapCircularListIndex[i] != -1) {
+ const DatBitmapsGroup *bitmap = &bitmapsGroup[_bitmapCircularListIndex[i]];
+ drawBitmap(bitmap, bitmapData + bitmap->offset, xPos[i], 14, bitmap->w, bitmap->h, 105 + 50 * i);
+ }
+ }
+}
+
+void Menu::drawCheckpointScreen() {
+ decodeLZW(_optionsBitmapData[_optionNum], _video->_frontLayer);
+ drawBitmapsCircularList(_checkpointsBitmaps[_levelNum], _checkpointsBitmapsData[_levelNum], _checkpointNum, _lastLevelCheckpointNum[_levelNum], false);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 5);
+ drawSprite(&_iconsSprites[0], _iconsSpritesData, (_checkpointNum + 1) / 10, 119, 108);
+ drawSprite(&_iconsSprites[0], _iconsSpritesData, (_checkpointNum + 1) % 10, 127, 108);
+ const int num = _loadCheckpointButtonState;
+ if (num > 1 && num < 7) {
+ drawSprite(&_iconsSprites[9], _iconsSpritesData, num - 2);
+ } else {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (num != 0) ? 8 : 7);
+ }
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 6);
+ refreshScreen(false);
+}
+
+void Menu::drawLevelScreen() {
+ decodeLZW(_optionsBitmapData[_optionNum], _video->_frontLayer);
+ drawSprite(&_iconsSprites[1], _iconsSpritesData, _levelNum);
+ DatBitmapsGroup *bitmap = &_levelsBitmaps[_levelNum];
+ drawBitmap(bitmap, _levelsBitmapsData + bitmap->offset, 23, 10, bitmap->w, bitmap->h, 192);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 4);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (_loadLevelButtonState != 0) ? 3 : 2);
+ refreshScreen(false);
+}
+
+void Menu::drawCutsceneScreen() {
+ decodeLZW(_optionsBitmapData[_optionNum], _video->_frontLayer);
+ drawBitmapsCircularList(_cutscenesBitmaps, _cutscenesBitmapsData, _cutsceneNum, _cutsceneIndexesCount, false);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 10);
+ drawSprite(&_iconsSprites[0], _iconsSpritesData, (_cutsceneNum + 1) / 10, 119, 108);
+ drawSprite(&_iconsSprites[0], _iconsSpritesData, (_cutsceneNum + 1) % 10, 127, 108);
+ const int num = _loadCutsceneButtonState;
+ if (num > 1 && num < 7) {
+ drawSprite(&_iconsSprites[14], _iconsSpritesData, num - 2);
+ } else {
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (num != 0) ? 13 : 12);
+ }
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 11);
+ refreshScreen(false);
+}
+
+void Menu::drawSettingsScreen() {
+ decodeLZW(_optionsBitmapData[_optionNum], _video->_frontLayer);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, 0x2A);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (_settingNum == kSettingNum_Controls) ? 0x27 : 0x24);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (_settingNum == kSettingNum_Difficulty) ? 0x26 : 0x23);
+ drawSpriteAnim(_iconsSprites, _iconsSpritesData, (_settingNum == kSettingNum_Sound) ? 0x28 : 0x25);
+ drawSprite(&_iconsSprites[0x29], _iconsSpritesData, (_settingNum == kSettingNum_Confirm) ? 1 : 0);
+ refreshScreen(true);
+}
+
+void Menu::handleSettingsScreen(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == 0) {
+ if (_settingNum == kSettingNum_Controls) {
+ playSound(kSound_0x78);
+ _condMask = 0x10;
+ } else if (_settingNum == kSettingNum_Difficulty) {
+ playSound(kSound_0x78);
+ _condMask = 0x20;
+ } else if (_settingNum == kSettingNum_Sound) {
+ playSound(kSound_0x78);
+ _condMask = 0x40;
+ } else if (_settingNum == kSettingNum_Confirm) {
+ playSound(kSound_0x78);
+ _condMask = 0x80;
+ }
+ return;
+ } else if (num == 1) {
+ if (_settingNum != kSettingNum_Confirm && _settingNum > 0 && 0) { // 'controls' not implemented
+ playSound(kSound_0x70);
+ --_settingNum;
+ _iconsSprites[0x27].num = 0;
+ _iconsSprites[0x26].num = 0;
+ _iconsSprites[0x28].num = 0;
+ }
+ } else if (num == 2) {
+ if (_settingNum != kSettingNum_Confirm && _settingNum < 2 && 0) { // 'volume' not implemented
+ playSound(kSound_0x70);
+ ++_settingNum;
+ _iconsSprites[0x27].num = 0;
+ _iconsSprites[0x26].num = 0;
+ _iconsSprites[0x28].num = 0;
+ }
+ } else if (num == 3) {
+ if (_settingNum == kSettingNum_Confirm) {
+ playSound(kSound_0x70);
+ _settingNum = kSettingNum_Difficulty;
+ _iconsSprites[0x26].num = 0;
+ }
+ } else if (num == 4) {
+ if (_settingNum != kSettingNum_Confirm) {
+ playSound(kSound_0x70);
+ }
+ _settingNum = kSettingNum_Confirm;
+ }
+ drawSettingsScreen();
+ _condMask = 8;
+ g_system->sleep(30);
+}
+
+void Menu::drawDifficultyScreen() {
+ decodeLZW(_optionsBitmapData[_optionNum], _video->_frontLayer);
+ for (int i = 0; i < 3; ++i) {
+ if (i != _difficultyNum) {
+ drawSprite(&_iconsSprites[0xF], _iconsSpritesData, i * 2);
+ }
+ }
+ drawSprite(&_iconsSprites[0xF], _iconsSpritesData, _difficultyNum * 2 + 1);
+ refreshScreen(true);
+}
+
+void Menu::handleDifficultyScreen(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == 0) {
+ playSound(kSound_0x78);
+ _config->players[_config->currentPlayer].difficulty = _g->_difficulty = _difficultyNum;
+ _condMask = 0x80;
+ } else if (num == 1) {
+ if (_difficultyNum > 0) {
+ playSound(kSound_0x70);
+ --_difficultyNum;
+ }
+ } else if (num == 2) {
+ if (_difficultyNum < 2) {
+ playSound(kSound_0x70);
+ ++_difficultyNum;
+ }
+ }
+ drawDifficultyScreen();
+ g_system->sleep(30);
+}
+
+void Menu::drawSoundScreen() {
+ decodeLZW(_optionsBitmapData[_optionNum], _video->_frontLayer);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, (_soundNum == kSoundNum_Stereo) ? 1 : 0);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, (_soundNum == kSoundNum_Volume) ? 3 : 2);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, (_soundNum == kSoundNum_Confirm) ? 5 : 4);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, (_soundNum == kSoundNum_Test) ? 7 : 6);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, (_soundNum == kSoundNum_Cancel) ? 9 : 8);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, (_soundNum == kSoundNum_Reset) ? 11 : 10);
+ // volume bar
+ const int w = ((_g->_snd_masterVolume * 3) << 5) >> 7;
+ for (int y = 0; y < 15; ++y) {
+ memset(_video->_frontLayer + 18807 + 256 * y, 0xE0, w);
+ }
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 21);
+ if (_soundNum == kSoundNum_Test) {
+// drawSprite(&_iconsSprites[0x12], _iconsSpritesData, _soundTestNum);
+ }
+ if (_g->_snd_masterVolume != 0) {
+ if (_config->players[_config->currentPlayer].stereo) {
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 13);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 16);
+ } else {
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 14);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 15);
+ }
+ }
+ if (0) { // (soundUnk1 != 0) && (_soundCounter & 1)
+ if (0) { // soundUnk1 == 1
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 18);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 19);
+ } else {
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 17);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 20);
+ }
+ } else {
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 17);
+ drawSprite(&_iconsSprites[0x12], _iconsSpritesData, 19);
+ }
+ refreshScreen(true);
+}
+
+void Menu::handleSoundScreen(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == 0) {
+ if (_soundNum == kSoundNum_Confirm) {
+ playSound(kSound_0x78);
+ _config->players[_config->currentPlayer].volume = _g->_snd_masterVolume;
+ _condMask = 0x80;
+ } else if (_soundNum == kSoundNum_Test) {
+ playSound(kSound_0x60);
+ // ...
+ } else if (_soundNum == kSoundNum_Cancel) {
+ playSound(kSound_0x80);
+ _config->players[_config->currentPlayer].volume = _g->_snd_masterVolume = _soundVolume;
+ _condMask = 0x80;
+ } else if (_soundNum == kSoundNum_Reset) {
+ playSound(kSound_0x88);
+ _config->players[_config->currentPlayer].volume = _g->_snd_masterVolume = Game::kDefaultSoundVolume;
+ }
+ } else if (num == 1) {
+ if (_soundNum == 0) {
+ // ...
+ } else if (_soundNum == kSoundNum_Volume) {
+ if (_g->_snd_masterVolume > 0) {
+ playSound(kSound_0x90);
+ --_g->_snd_masterVolume;
+ _config->players[_config->currentPlayer].volume = _g->_snd_masterVolume;
+ _condMask = 8;
+ }
+ } else if (_soundNum == 3) {
+ playSound(kSound_0x70);
+ _soundNum = 4;
+ } else if (_soundNum == 4) {
+ playSound(kSound_0x70);
+ _soundNum = 2;
+ }
+ } else if (num == 2) {
+ if (_soundNum == 0) {
+ // ...
+ } else if (_soundNum == kSoundNum_Volume) {
+ if (_g->_snd_masterVolume < 128) {
+ playSound(kSound_0x90);
+ ++_g->_snd_masterVolume;
+ _config->players[_config->currentPlayer].volume = _g->_snd_masterVolume;
+ _condMask = 8;
+ }
+ } else if (_soundNum == 2) {
+ playSound(kSound_0x70);
+ _soundNum = 4;
+ } else if (_soundNum == 4) {
+ if (_g->_snd_masterVolume != 0) {
+ playSound(kSound_0x70);
+ _soundNum = 3;
+ }
+ }
+ } else if (num == 3) {
+ if (_soundNum != kSoundNum_Volume) {
+ playSound(kSound_0x70);
+ }
+ if ((_soundNum >= 2 && _soundNum <= 4) || _soundNum == 5) {
+ _soundNum = 1;
+ }
+ } else if (num == 4) {
+ if (_soundNum != 5) {
+ playSound(kSound_0x70);
+ }
+ if (_soundNum == 0) {
+ _soundNum = 1;
+ } else if (_soundNum == 1) {
+ _soundNum = 4;
+ } else if (_soundNum >= 2 && _soundNum <= 4) {
+ _soundNum = 5;
+ }
+ }
+ drawSoundScreen();
+ g_system->sleep(30);
+}
+
+void Menu::changeToOption(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ const int button = data[6];
+ if (button != 0xFF) {
+ assert(button < _optionsButtonSpritesCount);
+ _currentOptionButtonSound = READ_LE_UINT32(_optionsButtonSpritesData + button * 20);
+ _currentOptionButtonSprite = (DatSpritesGroup *)(_optionsButtonSpritesData + button * 20 + 4);
+ _currentOptionButtonSprite->num = 0; // start from frame #0
+ } else {
+ _currentOptionButtonSound = 0;
+ _currentOptionButtonSprite = 0;
+ }
+ if (!_paf->_skipCutscenes) {
+ _paf->play(data[5]);
+ }
+ if (_optionNum == kMenu_NewGame + 1) {
+ _config->players[_config->currentPlayer].levelNum = 0;
+ _config->players[_config->currentPlayer].checkpointNum = 0;
+ } else if (_optionNum == kMenu_CurrentGame + 1) {
+ // _config->players[_config->currentPlayer].levelNum = _g->_currentLevel;
+ // _config->players[_config->currentPlayer].checkpointNum = _g->_currentLevelCheckpoint;
+ } else if (_optionNum == kMenu_Load + 1) {
+ _loadLevelButtonState = 0;
+ memcpy(_paletteBuffer, _optionsBitmapData[5] + _optionsBitmapSize[5], 768);
+ memcpy(_paletteBuffer + 192 * 3, _levelsBitmapsData + _levelsBitmaps[_levelNum].palette, 64 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ drawLevelScreen();
+ } else if (_optionNum == kMenu_Load + 2) {
+ _loadCheckpointButtonState = 0;
+ _checkpointNum = 0;
+ setLevelCheckpoint(_config->currentPlayer);
+ memcpy(_paletteBuffer, _optionsBitmapData[6] + _optionsBitmapSize[6], 768);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ drawCheckpointScreen();
+ } else if (_optionNum == kMenu_Settings + 1) {
+ _settingNum = 1;
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 768);
+ handleSettingsScreen(5);
+ } else if (_optionNum == kMenu_Cutscenes + 1) {
+ _loadCutsceneButtonState = 0;
+ _cutsceneNum = 0;
+ drawCutsceneScreen();
+ } else if (_optionsBitmapSize[_optionNum] != 0) {
+ decodeLZW(_optionsBitmapData[_optionNum], _video->_frontLayer);
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 256 * 3);
+ refreshScreen(true);
+ }
+}
+
+void Menu::handleLoadLevel(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == 16) {
+ if (_loadLevelButtonState != 0) {
+ playSound(kSound_0x70);
+ }
+ _loadLevelButtonState = 0;
+ } else if (num == 17) {
+ if (_loadLevelButtonState == 0) {
+ playSound(kSound_0x70);
+ }
+ _loadLevelButtonState = 1;
+ } else if (num == 18) {
+ if (_loadLevelButtonState != 0) {
+ playSound(kSound_0x80);
+ _condMask = 0x80;
+ } else {
+ playSound(kSound_0x78);
+ _condMask = 0x10;
+ }
+ } else if (num == 19) {
+ if (_lastLevelNum > 1) {
+ playSound(kSound_0x70);
+ ++_levelNum;
+ _checkpointNum = 0;
+ if (_levelNum >= _lastLevelNum) {
+ _levelNum = 0;
+ }
+ memcpy(_paletteBuffer + 192 * 3, _levelsBitmapsData + _levelsBitmaps[_levelNum].palette, 64 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ _condMask = 0x20;
+ }
+ } else if (num == 20) {
+ if (_lastLevelNum > 1) {
+ playSound(kSound_0x70);
+ --_levelNum;
+ _checkpointNum = 0;
+ if (_levelNum < 0) {
+ _levelNum = _lastLevelNum - 1;
+ }
+ memcpy(_paletteBuffer + 192 * 3, _levelsBitmapsData + _levelsBitmaps[_levelNum].palette, 64 * 3);
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ _condMask = 0x40;
+ }
+ }
+ drawLevelScreen();
+}
+
+void Menu::handleLoadCheckpoint(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == 11) {
+ if (_loadCheckpointButtonState != 0) {
+ playSound(kSound_0x70);
+ _loadCheckpointButtonState = 0;
+ }
+ } else if (num == 12) {
+ if (_loadCheckpointButtonState == 0) {
+ playSound(kSound_0x70);
+ _loadCheckpointButtonState = 1;
+ }
+ } else if (num == 13) {
+ if (_loadCheckpointButtonState != 0) {
+ playSound(kSound_0x80);
+ _condMask = 0x80;
+ } else {
+ playSound(kSound_0x78);
+ _loadCheckpointButtonState = 2;
+ _condMask = 0x08;
+ if (_levelNum == 7 && _checkpointNum == 11) {
+ _config->players[_config->currentPlayer].levelNum = kLvl_dark;
+ _config->players[_config->currentPlayer].checkpointNum = 0;
+ } else {
+ _config->players[_config->currentPlayer].levelNum = _levelNum;
+ _config->players[_config->currentPlayer].checkpointNum = _checkpointNum;
+ }
+ debug(kDebug_MENU, "Restart game at level %d checkpoint %d", _levelNum, _checkpointNum);
+ return;
+ }
+ } else if (num == 14) {
+ if (_lastLevelCheckpointNum[_levelNum] > 2 || _loadCheckpointButtonState == 0) {
+ playSound(kSound_0x70);
+ ++_checkpointNum;
+ if (_checkpointNum >= _lastLevelCheckpointNum[_levelNum]) {
+ _checkpointNum = 0;
+ }
+ }
+ } else if (num == 15) {
+ if (_lastLevelCheckpointNum[_levelNum] > 2 || _loadCheckpointButtonState == 1) {
+ playSound(kSound_0x70);
+ --_checkpointNum;
+ if (_checkpointNum < 0) {
+ _checkpointNum = _lastLevelCheckpointNum[_levelNum] - 1;
+ }
+ }
+ }
+ drawCheckpointScreen();
+}
+
+void Menu::handleLoadCutscene(int num) {
+ const uint8_t *data = &_optionData[num * 8];
+ num = data[5];
+ if (num == 6) {
+ if (_loadCutsceneButtonState != 0) {
+ playSound(kSound_0x70);
+ _loadCutsceneButtonState = 0;
+ }
+ } else if (num == 7) {
+ if (_loadCutsceneButtonState == 0) {
+ playSound(kSound_0x70);
+ _loadCutsceneButtonState = 1;
+ }
+ } else if (num == 8) {
+ if (_loadCutsceneButtonState != 0) {
+ playSound(kSound_0x80);
+ _condMask = 0x80;
+ } else {
+ playSound(kSound_0x78);
+ _loadCutsceneButtonState = 2;
+ if (!_paf->_skipCutscenes) {
+ const int num = _cutscenesBitmaps[_cutsceneIndexes[_cutsceneNum]].data;
+ _paf->play(num);
+ if (num == kPafAnimation_end) {
+ _paf->play(kPafAnimation_cinema);
+ }
+ }
+ playSound(kSound_0x98);
+ playSound(kSound_0xA0);
+ _loadCutsceneButtonState = 0;
+ }
+ } else if (num == 9) {
+ if (_cutsceneIndexesCount > 2 || _loadCutsceneButtonState == 0) {
+ playSound(kSound_0x70);
+ ++_cutsceneNum;
+ if (_cutsceneNum >= _cutsceneIndexesCount) {
+ _cutsceneNum = 0;
+ }
+ }
+ } else if (num == 10) {
+ if (_cutsceneIndexesCount > 2 || _loadCutsceneButtonState == 1) {
+ playSound(kSound_0x70);
+ --_cutsceneNum;
+ if (_cutsceneNum < 0) {
+ _cutsceneNum = _cutsceneIndexesCount - 1;
+ }
+ }
+ }
+ drawCutsceneScreen();
+}
+
+static bool matchInput(uint8_t menu, uint8_t type, uint8_t mask, const PlayerInput &inp, uint8_t optionMask) {
+ if (type != 0) {
+ if ((mask & 1) != 0 && inp.keyReleased(SYS_INP_RUN)) {
+ return true;
+ }
+ if ((mask & 2) != 0 && inp.keyReleased(SYS_INP_JUMP)) {
+ return true;
+ }
+ if ((mask & 4) != 0 && inp.keyReleased(SYS_INP_SHOOT)) {
+ return true;
+ }
+ if ((mask & optionMask) != 0) {
+ return true;
+ }
+ } else {
+ if ((mask & 1) != 0 && inp.keyReleased(SYS_INP_UP)) {
+ return true;
+ }
+ if ((mask & 2) != 0 && inp.keyReleased(SYS_INP_RIGHT)) {
+ return true;
+ }
+ if ((mask & 4) != 0 && inp.keyReleased(SYS_INP_DOWN)) {
+ return true;
+ }
+ if ((mask & 8) != 0 && inp.keyReleased(SYS_INP_LEFT)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void Menu::handleOptions() {
+ _lastLevelNum = _config->players[_config->currentPlayer].lastLevelNum + 1;
+ if (_lastLevelNum > _res->_datHdr.levelsCount) {
+ _lastLevelNum = _res->_datHdr.levelsCount;
+ }
+ _levelNum = _config->players[_config->currentPlayer].levelNum;
+ if (_levelNum > kLvl_dark) {
+ _levelNum = kLvl_dark;
+ }
+ if (_levelNum == kLvl_dark) {
+ _levelNum = 7;
+ _checkpointNum = 11;
+ }
+ _cutsceneIndexesCount = 0;
+ const uint32_t playedCutscenesMask = _config->players[_config->currentPlayer].cutscenesMask;
+ for (int i = 0; i < _res->_datHdr.cutscenesCount; ++i) {
+ if ((playedCutscenesMask & (1 << i)) != 0) {
+ _cutsceneIndexes[_cutsceneIndexesCount] = i;
+ ++_cutsceneIndexesCount;
+ }
+ }
+ if (_cutsceneIndexesCount > _res->_datHdr.cutscenesCount) {
+ _cutsceneIndexesCount = _res->_datHdr.cutscenesCount;
+ }
+ _optionNum = kMenu_Settings;
+ changeToOption(0);
+ _condMask = 0;
+ while (!g_system->inp.quit) {
+ g_system->processEvents();
+ if (g_system->inp.keyPressed(SYS_INP_ESC)) {
+ _optionNum = -1;
+ break;
+ }
+ // get transition from inputs and menu return code (_condMask)
+ int num = -1;
+ for (int i = 0; i < _res->_datHdr.menusCount; ++i) {
+ const uint8_t *data = _optionData + i * 8;
+ if (data[0] == _optionNum && matchInput(data[0], data[1] & 1, data[2], g_system->inp, _condMask)) {
+ num = i;
+ break;
+ }
+ }
+ _condMask = 0;
+ if (num == -1) {
+ g_system->sleep(15);
+ continue;
+ }
+ const uint8_t *data = &_optionData[num * 8];
+ const int prevOptionNum = _optionNum;
+ _optionNum = data[3];
+ debug(kDebug_MENU, "handleOptions option %d code %d", _optionNum, data[4]);
+ switch (data[4]) {
+ case 0:
+ if (prevOptionNum != _optionNum) {
+ _iconsSprites[0x2A].num = 0;
+ _iconsSprites[0x27].num = 0;
+ _iconsSprites[0x26].num = 0;
+ _iconsSprites[0x28].num = 0;
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 768);
+ }
+ handleSettingsScreen(num);
+ break;
+ // case 1: // controls
+ case 4:
+ if (prevOptionNum != _optionNum) {
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 768);
+ _difficultyNum = _config->players[_config->currentPlayer].difficulty;
+ }
+ handleDifficultyScreen(num);
+ break;
+ case 5:
+ if (prevOptionNum != _optionNum) {
+ memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 768);
+ _soundNum = 2;
+ _soundVolume = _g->_snd_masterVolume;
+ }
+ handleSoundScreen(num);
+ break;
+ case 6:
+ playSound(kSound_0x70);
+ changeToOption(num);
+ break;
+ case 7:
+ playSound(kSound_0x78);
+ changeToOption(num);
+ break;
+ case 8:
+ changeToOption(num);
+ break;
+ case 9:
+ handleLoadCutscene(num);
+ break;
+ case 10:
+ handleLoadLevel(num);
+ break;
+ case 11:
+ break;
+ case 12:
+ setLevelCheckpoint(_config->currentPlayer);
+ handleLoadCheckpoint(num);
+ break;
+ default:
+ warning("Unhandled option %d %d", _optionNum, data[4]);
+ break;
+ }
+ if (_optionNum == kMenu_Quit + 1 || _optionNum == kMenu_NewGame + 1 || _optionNum == kMenu_CurrentGame + 1 || _optionNum == kMenu_ResumeGame) {
+ // 'setup.cfg' is saved when exiting the main loop
+ break;
+ }
+ }
+}
diff --git a/Src/Vita_&_Switch/menu.h b/Src/Vita_&_Switch/menu.h
new file mode 100644
index 0000000..d0bbc51
--- /dev/null
+++ b/Src/Vita_&_Switch/menu.h
@@ -0,0 +1,136 @@
+
+#ifndef MENU_H__
+#define MENU_H__
+
+#include "intern.h"
+
+struct Game;
+struct PafPlayer;
+struct Resource;
+struct Video;
+
+struct DatSpritesGroup {
+ uint32_t currentFrameOffset; // 0
+ uint32_t firstFrameOffset; // 4
+ uint32_t size; // 8 following this header
+ uint16_t count; // 12
+ uint16_t num; // 14
+} PACKED; // sizeof == 16
+
+struct DatBitmap {
+ uint32_t size; // 0
+ uint32_t unk4; // 4
+ // 8 lzw + 768 palette
+} PACKED; // sizeof == 8
+
+struct DatBitmapsGroup {
+ uint8_t w;
+ uint8_t h;
+ uint8_t colors;
+ uint8_t data;
+ uint32_t offset; // 4
+ uint32_t palette; // 8
+} PACKED; // sizeof == 12
+
+struct Menu {
+ enum {
+ kCheckpointLevelsCount = 8,
+ kCutsceneIndexesCount = 22, // kPafAnimation_cinema + 1
+ kOptionsCount = 19
+ };
+
+ Game *_g;
+ PafPlayer *_paf;
+ Resource *_res;
+ Video *_video;
+
+ SetupConfig *_config;
+ int _checkpointNum;
+ int _levelNum;
+
+ DatSpritesGroup *_titleSprites;
+ DatSpritesGroup *_playerSprites;
+ const uint8_t *_titleBitmapData;
+ uint32_t _titleBitmapSize;
+ const uint8_t *_playerBitmapData;
+ uint32_t _playerBitmapSize;
+ uint32_t _optionsBitmapSize[kOptionsCount];
+ const uint8_t *_optionsBitmapData[kOptionsCount];
+ DatBitmapsGroup *_cutscenesBitmaps;
+ const uint8_t *_cutscenesBitmapsData;
+ DatBitmapsGroup *_checkpointsBitmaps[kCheckpointLevelsCount];
+ const uint8_t *_checkpointsBitmapsData[kCheckpointLevelsCount];
+ DatBitmapsGroup *_levelsBitmaps;
+ const uint8_t *_levelsBitmapsData;
+ DatSpritesGroup *_iconsSprites;
+ const uint8_t *_iconsSpritesData;
+ int _optionsButtonSpritesCount;
+ const uint8_t *_optionsButtonSpritesData;
+ DatSpritesGroup *_currentOptionButtonSprite;
+ int _currentOptionButtonSound;
+
+ const uint8_t *_digitsData;
+ const uint8_t *_optionData;
+ const uint8_t *_soundData;
+
+ uint8_t _paletteBuffer[256 * 3];
+ uint8_t _loadLevelButtonState;
+ uint8_t _optionNum;
+ int _lastLevelNum;
+ int _lastLevelCheckpointNum[kCheckpointLevelsCount];
+ uint8_t _condMask;
+ uint8_t _loadCheckpointButtonState;
+ int _bitmapCircularListIndex[3];
+ int _cutsceneIndexesCount;
+ int _cutsceneNum;
+ uint8_t _loadCutsceneButtonState;
+ int _cutsceneIndexes[kCutsceneIndexesCount];
+ int _settingNum;
+ int _difficultyNum;
+ int _soundNum;
+ uint8_t _soundVolume;
+
+ Menu(Game *g, PafPlayer *paf, Resource *res, Video *video);
+
+ void setVolume();
+
+ void loadData();
+
+ int getSoundNum(int num, int index = 0) const;
+ void playSound(int num);
+
+ void drawSprite(const DatSpritesGroup *spriteGroup, const uint8_t *ptr, uint32_t num, int x = -1, int y = -1);
+ void drawSpriteAnim(DatSpritesGroup *spriteGroup, const uint8_t *ptr, uint32_t num);
+
+ void pafCallback(int frameNum, const uint8_t *frameData);
+ void refreshScreen(bool updatePalette);
+
+ bool mainLoop();
+
+ void drawTitleScreen(int option);
+ int handleTitleScreen();
+ void drawDigit(int x, int y, int num);
+ void drawBitmap(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int x, int y, int w, int h, uint8_t baseColor = 0);
+ void setCurrentPlayer(int num);
+ void setLevelCheckpoint(int num);
+ void drawPlayerProgress(int state, int cursor);
+ void handleAssignPlayer();
+ void updateBitmapsCircularList(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int num, int count);
+ void drawBitmapsCircularList(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int num, int count, bool updatePalette);
+ void drawCheckpointScreen();
+ void drawLevelScreen();
+ void drawCutsceneScreen();
+ void drawSettingsScreen();
+ void handleSettingsScreen(int num);
+ void drawDifficultyScreen();
+ void handleDifficultyScreen(int num);
+ void drawSoundScreen();
+ void handleSoundScreen(int num);
+ void changeToOption(int num);
+ void handleLoadLevel(int num);
+ void handleLoadCheckpoint(int num);
+ void handleLoadCutscene(int num);
+ void handleOptions();
+};
+
+#endif // MENU_H__
diff --git a/Src/Vita_&_Switch/mixer.cpp b/Src/Vita_&_Switch/mixer.cpp
new file mode 100644
index 0000000..15e983d
--- /dev/null
+++ b/Src/Vita_&_Switch/mixer.cpp
@@ -0,0 +1,87 @@
+
+#include "mixer.h"
+#include "util.h"
+
+static void nullMixerLock(int lock) {
+}
+
+Mixer::Mixer()
+ : _lock(nullMixerLock) {
+ memset(_mixingQueue, 0, sizeof(_mixingQueue));
+ _mixingQueueSize = 0;
+}
+
+Mixer::~Mixer() {
+}
+
+void Mixer::queue(const int16_t *ptr, const int16_t *end, int panType, int panL, int panR, bool stereo) {
+ if (_mixingQueueSize >= kPcmChannels) {
+ warning("MixingQueue overflow %d", _mixingQueueSize);
+ return;
+ }
+ MixerChannel *channel = &_mixingQueue[_mixingQueueSize];
+ channel->ptr = ptr;
+ channel->end = end;
+ channel->panL = panL;
+ channel->panR = panR;
+ channel->panType = panType;
+ channel->stereo = stereo;
+ ++_mixingQueueSize;
+}
+
+template
+static void mixS16(int16_t *dst, const int16_t *src, int len, int panL, int panR) {
+
+ static const int kPanBits = 14; // 0..16384
+
+ for (int j = 0; j < len; j += 2, dst += 2) {
+ const int16_t sampleL = *src++;
+ const int16_t sampleR = stereo ? *src++ : sampleL;
+ if (panning != 1) {
+ dst[0] = CLIP(dst[0] + ((panL * sampleL) >> kPanBits), -32768, 32767);
+ }
+ if (panning != 2) {
+ dst[1] = CLIP(dst[1] + ((panR * sampleR) >> kPanBits), -32768, 32767);
+ }
+ }
+}
+
+void Mixer::mix(int16_t *buf, int len) {
+ // stereo s16
+ assert((len & 1) == 0);
+ if (_mixingQueueSize == 0) {
+ return;
+ }
+ for (int i = 0; i < _mixingQueueSize; ++i) {
+ const MixerChannel *channel = &_mixingQueue[i];
+ const int panL = channel->panL;
+ const int panR = channel->panR;
+ if (channel->stereo) {
+ assert(channel->ptr + len <= channel->end);
+ switch (channel->panType) {
+ case 1:
+ mixS16(buf, channel->ptr, len, panL, panR);
+ break;
+ case 2:
+ mixS16(buf, channel->ptr, len, panL, panR);
+ break;
+ default:
+ mixS16(buf, channel->ptr, len, panL, panR);
+ break;
+ }
+ } else {
+ assert(channel->ptr + len / 2 <= channel->end);
+ switch (channel->panType) {
+ case 1:
+ mixS16(buf, channel->ptr, len, panL, panR);
+ break;
+ case 2:
+ mixS16(buf, channel->ptr, len, panL, panR);
+ break;
+ default:
+ mixS16(buf, channel->ptr, len, panL, panR);
+ break;
+ }
+ }
+ }
+}
diff --git a/Src/Vita_&_Switch/mixer.h b/Src/Vita_&_Switch/mixer.h
new file mode 100644
index 0000000..b6dc124
--- /dev/null
+++ b/Src/Vita_&_Switch/mixer.h
@@ -0,0 +1,44 @@
+
+#ifndef MIXER_H__
+#define MIXER_H__
+
+#include "intern.h"
+
+struct MixerChannel {
+ const int16_t *ptr;
+ const int16_t *end;
+ int panL;
+ int panR;
+ uint8_t panType;
+ bool stereo;
+};
+
+struct Mixer {
+
+ static const int kPcmChannels = 32;
+
+ void (*_lock)(int);
+
+ MixerChannel _mixingQueue[kPcmChannels];
+ int _mixingQueueSize;
+
+ Mixer();
+ ~Mixer();
+
+ void queue(const int16_t *ptr, const int16_t *end, int panType, int panL, int panR, bool stereo);
+
+ void mix(int16_t *buf, int len);
+};
+
+struct MixerLock {
+ Mixer *_mix;
+ MixerLock(Mixer *mix)
+ : _mix(mix) {
+ _mix->_lock(1);
+ }
+ ~MixerLock() {
+ _mix->_lock(0);
+ }
+};
+
+#endif
diff --git a/SRC/monsters.cpp b/Src/Vita_&_Switch/monsters.cpp
similarity index 99%
rename from SRC/monsters.cpp
rename to Src/Vita_&_Switch/monsters.cpp
index ff784ce..fb53847 100644
--- a/SRC/monsters.cpp
+++ b/Src/Vita_&_Switch/monsters.cpp
@@ -1,3 +1,7 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
#include "game.h"
#include "level.h"
@@ -2912,7 +2916,9 @@ void Game::mstUpdateRefPos() {
AndyShootData *p = _andyShootsTable;
for (LvlObject *o = _lvlObjectsList0; o; o = o->nextPtr) {
p->o = o;
- assert(o->dataPtr);
+ if (!o->dataPtr) {
+ continue;
+ }
ShootLvlObjectData *ptr = (ShootLvlObjectData *)getLvlObjectDataPtr(o, kObjectDataTypeShoot);
p->shootObjectData = ptr;
if (ptr->unk3 == 0x80) {
@@ -3169,7 +3175,6 @@ Task *Game::findFreeTask() {
Task *Game::createTask(const uint8_t *codeData) {
Task *t = findFreeTask();
if (t) {
- memset(t, 0, sizeof(Task));
resetTask(t, codeData);
t->prevPtr = 0;
t->nextPtr = _tasksList;
@@ -3221,7 +3226,6 @@ void Game::updateTask(Task *t, int num, const uint8_t *codeData) {
if (codeData) {
t = findFreeTask();
if (t) {
- memset(t, 0, sizeof(Task));
resetTask(t, codeData);
t->prevPtr = 0;
t->nextPtr = _tasksList;
@@ -3235,7 +3239,7 @@ void Game::updateTask(Task *t, int num, const uint8_t *codeData) {
}
void Game::resetTask(Task *t, const uint8_t *codeData) {
- debug(kDebug_MONSTER, "resetTask t %p offset 0x%04x", t, codeData - _res->_mstCodeData);
+ debug(kDebug_MONSTER, "resetTask t %p offset 0x%04x monster1 %p monster2 %p", t, codeData - _res->_mstCodeData, t->monster1, t->monster2);
assert(codeData);
t->state |= 2;
t->codeData = codeData;
@@ -5579,7 +5583,7 @@ int Game::mstOp56_specialAction(Task *t, int code, int num) {
if (op204Data->arg3 != 6 && o) {
LvlObject *tmpObject = t->monster1->o16;
const uint8_t flags = getLvlObjectFlag(op204Data->arg3 & 255, tmpObject, _andyObject);
- _specialAnimMask = ((flags & 3) << 4) | (_specialAnimMask & 0xFFCF);
+ _specialAnimMask = ((flags & 3) << 4) | (_specialAnimMask & ~0x30);
// _specialAnimScreenNum = tmpObject->screenNum;
_specialAnimLvlObject = tmpObject;
_mstOriginPosX = op204Data->arg1 & 0xFFFF;
@@ -5758,7 +5762,7 @@ int Game::mstOp56_specialAction(Task *t, int code, int num) {
o = _andyObject;
}
const uint8_t flags = getLvlObjectFlag(op204Data->arg2 & 255, o, _andyObject);
- _andyObject->flags1 = ((flags & 3) << 4) | (_andyObject->flags1 & 0xFFCF);
+ _andyObject->flags1 = ((flags & 3) << 4) | (_andyObject->flags1 & ~0x30);
const int x3 = _andyObject->posTable[3].x;
const int y3 = _andyObject->posTable[3].y;
setupLvlObjectBitmap(_andyObject);
@@ -6797,7 +6801,6 @@ void Game::mstOp67_addMonster(Task *currentTask, int x1, int x2, int y1, int y2,
removeLvlObject2(o);
return;
}
- memset(t, 0, sizeof(Task));
resetTask(t, kUndefinedMonsterByteCode);
t->monster2 = mo;
t->monster1 = 0;
@@ -6819,7 +6822,6 @@ void Game::mstOp67_addMonster(Task *currentTask, int x1, int x2, int y1, int y2,
removeLvlObject2(o);
return;
}
- memset(t, 0, sizeof(Task));
resetTask(t, kUndefinedMonsterByteCode);
t->monster1 = m;
t->monster2 = 0;
diff --git a/Src/Vita_&_Switch/paf.cpp b/Src/Vita_&_Switch/paf.cpp
new file mode 100644
index 0000000..0254e5f
--- /dev/null
+++ b/Src/Vita_&_Switch/paf.cpp
@@ -0,0 +1,536 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "fs.h"
+#include "paf.h"
+#include "system.h"
+#include "util.h"
+
+static const char *_filenames[] = {
+ "hod.paf",
+ "hod_demo.paf",
+ "hod_demo2.paf",
+ 0
+};
+
+static bool openPaf(FileSystem *fs, File *f) {
+ for (int i = 0; _filenames[i]; ++i) {
+ FILE *fp = fs->openAssetFile(_filenames[i]);
+ if (fp) {
+ f->setFp(fp);
+ return true;
+ }
+ }
+ return false;
+}
+
+static void closePaf(FileSystem *fs, File *f) {
+ if (f->_fp) {
+ fs->closeFile(f->_fp);
+ f->_fp = 0;
+ }
+}
+
+PafPlayer::PafPlayer(FileSystem *fs)
+ : _fs(fs) {
+ _skipCutscenes = !openPaf(_fs, &_file);
+ _videoNum = -1;
+ memset(&_pafHdr, 0, sizeof(_pafHdr));
+ memset(_pageBuffers, 0, sizeof(_pageBuffers));
+ _demuxAudioFrameBlocks = 0;
+ _demuxVideoFrameBlocks = 0;
+ _audioQueue = _audioQueueTail = 0;
+ _playedMask = 0;
+ memset(&_pafCb, 0, sizeof(_pafCb));
+}
+
+PafPlayer::~PafPlayer() {
+ unload();
+ closePaf(_fs, &_file);
+}
+
+void PafPlayer::preload(int num) {
+ debug(kDebug_PAF, "preload %d", num);
+ assert(num >= 0 && num < kMaxVideosCount);
+ if (_videoNum != num) {
+ unload(_videoNum);
+ _videoNum = num;
+ }
+ _file.seek(num * 4, SEEK_SET);
+ _videoOffset = _file.readUint32();
+ _file.seek(_videoOffset, SEEK_SET);
+ memset(&_pafHdr, 0, sizeof(_pafHdr));
+ if (!readPafHeader()) {
+ unload();
+ return;
+ }
+ uint8_t *buffer = (uint8_t *)calloc(kPageBufferSize * 4 + 256 * 4, 1);
+ if (!buffer) {
+ warning("preloadPaf() Unable to allocate page buffers");
+ unload();
+ return;
+ }
+ for (int i = 0; i < 4; ++i) {
+ _pageBuffers[i] = buffer + i * kPageBufferSize;
+ }
+ _demuxVideoFrameBlocks = (uint8_t *)calloc(_pafHdr.maxVideoFrameBlocksCount, _pafHdr.readBufferSize);
+ if (_pafHdr.maxAudioFrameBlocksCount != 0) {
+ _demuxAudioFrameBlocks = (uint8_t *)calloc(_pafHdr.maxAudioFrameBlocksCount, _pafHdr.readBufferSize);
+ _flushAudioSize = (_pafHdr.maxAudioFrameBlocksCount - 1) * _pafHdr.readBufferSize;
+ } else {
+ _demuxAudioFrameBlocks = 0;
+ _flushAudioSize = 0;
+ }
+ _audioBufferOffsetRd = 0;
+ _audioBufferOffsetWr = 0;
+ _audioQueue = _audioQueueTail = 0;
+}
+
+void PafPlayer::play(int num) {
+ debug(kDebug_PAF, "play %d", num);
+ if (_videoNum != num) {
+ preload(num);
+ }
+ if (_videoNum == num) {
+ _playedMask |= 1 << num;
+ mainLoop();
+ }
+}
+
+void PafPlayer::unload(int num) {
+ if (_videoNum < 0) {
+ return;
+ }
+ free(_pageBuffers[0]);
+ memset(_pageBuffers, 0, sizeof(_pageBuffers));
+ free(_demuxVideoFrameBlocks);
+ _demuxVideoFrameBlocks = 0;
+ free(_demuxAudioFrameBlocks);
+ _demuxAudioFrameBlocks = 0;
+ free(_pafHdr.frameBlocksCountTable);
+ free(_pafHdr.framesOffsetTable);
+ free(_pafHdr.frameBlocksOffsetTable);
+ memset(&_pafHdr, 0, sizeof(_pafHdr));
+ _videoNum = -1;
+ while (_audioQueue) {
+ PafAudioQueue *next = _audioQueue->next;
+ free(_audioQueue->buffer);
+ free(_audioQueue);
+ _audioQueue = next;
+ }
+ _audioQueueTail = 0;
+}
+
+bool PafPlayer::readPafHeader() {
+ static const char *kSignature = "Packed Animation File V1.0\n(c) 1992-96 Amazing Studio\n";
+ _file.read(_bufferBlock, kBufferBlockSize);
+ if (memcmp(_bufferBlock, kSignature, strlen(kSignature)) != 0) {
+ warning("readPafHeader() Unexpected signature");
+ return false;
+ }
+ _pafHdr.frameRate = READ_LE_UINT32(_bufferBlock + 0x88);
+ _pafHdr.startOffset = READ_LE_UINT32(_bufferBlock + 0xA4);
+ _pafHdr.preloadFrameBlocksCount = READ_LE_UINT32(_bufferBlock + 0x9C);
+ _pafHdr.readBufferSize = READ_LE_UINT32(_bufferBlock + 0x98);
+ assert(_pafHdr.readBufferSize == kBufferBlockSize);
+ _pafHdr.framesCount = READ_LE_UINT32(_bufferBlock + 0x84);
+ if (_pafHdr.framesCount <= 0) {
+ warning("readPafHeader() Invalid number of frames %d", _pafHdr.framesCount);
+ return false;
+ }
+ _pafHdr.maxVideoFrameBlocksCount = READ_LE_UINT32(_bufferBlock + 0xA8);
+ _pafHdr.maxAudioFrameBlocksCount = READ_LE_UINT32(_bufferBlock + 0xAC);
+ _pafHdr.frameBlocksCount = READ_LE_UINT32(_bufferBlock + 0xA0);
+ if (_pafHdr.frameBlocksCount <= 0) {
+ warning("readPafHeader() Invalid number of blocks %d", _pafHdr.frameBlocksCount);
+ return false;
+ }
+ _pafHdr.frameBlocksCountTable = readPafHeaderTable(_pafHdr.framesCount);
+ _pafHdr.framesOffsetTable = readPafHeaderTable(_pafHdr.framesCount);
+ _pafHdr.frameBlocksOffsetTable = readPafHeaderTable(_pafHdr.frameBlocksCount);
+ return _pafHdr.frameBlocksCountTable != 0 && _pafHdr.framesOffsetTable != 0 && _pafHdr.frameBlocksOffsetTable != 0;
+}
+
+uint32_t *PafPlayer::readPafHeaderTable(int count) {
+ uint32_t *dst = (uint32_t *)malloc(count * sizeof(uint32_t));
+ if (!dst) {
+ warning("readPafHeaderTable() Unable to allocate %d bytes", count * sizeof(uint32_t));
+ return 0;
+ }
+ for (int i = 0; i < count; ++i) {
+ dst[i] = _file.readUint32();
+ }
+ const int align = (count * 4) & 0x7FF;
+ if (align != 0) {
+ _file.seek(0x800 - align, SEEK_CUR);
+ }
+ return dst;
+}
+
+void PafPlayer::decodeVideoFrame(const uint8_t *src) {
+ const uint8_t *base = src;
+ const int code = *src++;
+ if (code & 0x20) {
+ for (int i = 0; i < 4; ++i) {
+ memset(_pageBuffers[i], 0, kPageBufferSize);
+ }
+ memset(_paletteBuffer, 0, sizeof(_paletteBuffer));
+ _paletteChanged = true;
+ _currentPageBuffer = 0;
+ }
+ if (code & 0x40) {
+ int index = src[0];
+ int count = (src[1] + 1) * 3;
+ assert(index * 3 + count <= 768);
+ src += 2;
+ memcpy(_paletteBuffer + index * 3, src, count);
+ _paletteChanged = true;
+ src += count;
+ }
+ switch (code & 0xF) {
+ case 0:
+ decodeVideoFrameOp0(base, src, code);
+ break;
+ case 1:
+ decodeVideoFrameOp1(src);
+ break;
+ case 2:
+ decodeVideoFrameOp2(src);
+ break;
+ case 4:
+ decodeVideoFrameOp4(src);
+ break;
+ }
+}
+
+static void pafCopy4x4h(uint8_t *dst, const uint8_t *src) {
+ for (int i = 0; i < 4; ++i) {
+ memcpy(dst, src, 4);
+ src += 4;
+ dst += 256;
+ }
+}
+
+static void pafCopy4x4v(uint8_t *dst, const uint8_t *src) {
+ for (int i = 0; i < 4; ++i) {
+ memcpy(dst, src, 4);
+ src += 256;
+ dst += 256;
+ }
+}
+
+static void pafCopySrcMask(uint8_t mask, uint8_t *dst, const uint8_t *src) {
+ for (int i = 0; i < 4; ++i) {
+ if (mask & (1 << (3 - i))) {
+ dst[i] = src[i];
+ }
+ }
+}
+
+static void pafCopyColorMask(uint8_t mask, uint8_t *dst, uint8_t color) {
+ for (int i = 0; i < 4; ++i) {
+ if (mask & (1 << (3 - i))) {
+ dst[i] = color;
+ }
+ }
+}
+
+static const char *updateSequences[] = {
+ "",
+ "\x02",
+ "\x05\x07",
+ "\x05",
+ "\x06",
+ "\x05\x07\x05\x07",
+ "\x05\x07\x05",
+ "\x05\x07\x06",
+ "\x05\x05",
+ "\x03",
+ "\x06\x06",
+ "\x02\x04",
+ "\x02\x04\x05\x07",
+ "\x02\x04\x05",
+ "\x02\x04\x06",
+ "\x02\x04\x05\x07\x05\x07"
+};
+
+uint8_t *PafPlayer::getVideoPageOffset(uint16_t val) {
+ const int x = val & 0x7F; val >>= 7;
+ const int y = val & 0x7F; val >>= 7;
+ return _pageBuffers[val] + (y * kVideoWidth + x) * 2;
+}
+
+void PafPlayer::decodeVideoFrameOp0(const uint8_t *base, const uint8_t *src, uint8_t code) {
+ int count = *src++;
+ if (count != 0) {
+ if ((code & 0x10) != 0) {
+ int align = src - base;
+ align &= 3;
+ if (align != 0) {
+ src += 4 - align;
+ }
+ }
+ for (int i = 0; i < count; ++i) {
+ uint8_t *dst = getVideoPageOffset((src[0] << 8) | src[1]);
+ uint32_t offset = (src[1] & 0x7F) * 2;
+ uint32_t end = READ_LE_UINT16(src + 2); src += 4;
+ end += offset;
+ do {
+ ++offset;
+ pafCopy4x4h(dst, src);
+ src += 16;
+ if ((offset & 0x3F) == 0) {
+ dst += kVideoWidth * 3;
+ }
+ dst += 4;
+ } while (offset < end);
+ }
+ }
+
+ uint8_t *dst = _pageBuffers[_currentPageBuffer];
+ for (int y = 0; y < kVideoHeight; y += 4, dst += kVideoWidth * 3) {
+ for (int x = 0; x < kVideoWidth; x += 4, dst += 4) {
+ const uint8_t *src2 = getVideoPageOffset((src[0] << 8) | src[1]); src += 2;
+ pafCopy4x4v(dst, src2);
+ }
+ }
+
+ const uint32_t opcodesSize = READ_LE_UINT16(src); src += 4;
+ const uint8_t *opcodesData = src;
+ src += opcodesSize;
+
+ uint8_t mask = 0;
+ uint8_t color = 0;
+ const uint8_t *src2 = 0;
+
+ dst = _pageBuffers[_currentPageBuffer];
+ for (int y = 0; y < kVideoHeight; y += 4, dst += kVideoWidth * 3) {
+ for (int x = 0; x < kVideoWidth; x += 8) {
+ uint8_t updateIndex = *opcodesData++;
+ for (int i = 0; i < 2; ++i, dst += 4) {
+ const char *opcodes = updateSequences[updateIndex >> 4];
+ updateIndex <<= 4;
+ while (*opcodes) {
+ uint32_t offset = kVideoWidth * 2;
+ const int code = *opcodes++;
+ switch (code) {
+ case 2:
+ offset = 0;
+ case 3:
+ color = *src++;
+ case 4:
+ mask = *src++;
+ pafCopyColorMask(mask >> 4, dst + offset, color);
+ offset += kVideoWidth;
+ pafCopyColorMask(mask & 15, dst + offset, color);
+ break;
+ case 5:
+ offset = 0;
+ case 6:
+ src2 = getVideoPageOffset((src[0] << 8) | src[1]); src += 2;
+ case 7:
+ mask = *src++;
+ pafCopySrcMask(mask >> 4, dst + offset, src2 + offset);
+ offset += kVideoWidth;
+ pafCopySrcMask(mask & 15, dst + offset, src2 + offset);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+void PafPlayer::decodeVideoFrameOp1(const uint8_t *src) {
+ memcpy(_pageBuffers[_currentPageBuffer], src + 2, kVideoWidth * kVideoHeight);
+}
+
+void PafPlayer::decodeVideoFrameOp2(const uint8_t *src) {
+ const int page = *src++;
+ if (page != _currentPageBuffer) {
+ memcpy(_pageBuffers[_currentPageBuffer], _pageBuffers[page], kVideoWidth * kVideoHeight);
+ }
+}
+
+void PafPlayer::decodeVideoFrameOp4(const uint8_t *src) {
+ uint8_t *dst = _pageBuffers[_currentPageBuffer];
+ src += 2;
+ int size = kVideoWidth * kVideoHeight;
+ while (size != 0) {
+ int8_t code = *src++;
+ int count;
+ if (code < 0) {
+ count = 1 - code;
+ const uint8_t color = *src++;
+ memset(dst, color, count);
+ } else {
+ count = code + 1;
+ memcpy(dst, src, count);
+ src += count;
+ }
+ dst += count;
+ size -= count;
+ }
+}
+
+static void decodeAudioFrame2205(const uint8_t *src, int len, int16_t *dst) {
+ static const int offset = 256 * sizeof(int16_t);
+ for (int i = 0; i < len * 2; ++i) { // stereo
+ dst[i] = READ_LE_UINT16(src + src[offset + i] * sizeof(int16_t));
+ }
+}
+
+void PafPlayer::decodeAudioFrame(const uint8_t *src, uint32_t offset, uint32_t size) {
+ assert(size == _pafHdr.readBufferSize);
+
+ // copy should be sequential
+ if (offset != _audioBufferOffsetWr) {
+ warning("Unexpected offset 0x%x wr 0x%x rd 0x%x num %d", offset, _audioBufferOffsetWr, _audioBufferOffsetRd, _videoNum);
+ assert(offset == 0);
+ // this happens in paf #3 of Italian release, there is a flush at 0x16800 instead of 0x1f000
+ _audioBufferOffsetWr = 0;
+ _audioBufferOffsetRd = 0;
+ }
+
+ _audioBufferOffsetWr = offset + size;
+
+ const int count = (_audioBufferOffsetWr - _audioBufferOffsetRd) / kAudioStrideSize;
+ if (count != 0) {
+ PafAudioQueue *sq = (PafAudioQueue *)malloc(sizeof(PafAudioQueue));
+ if (sq) {
+ sq->offset = 0;
+ sq->size = count * kAudioSamples * 2;
+ sq->buffer = (int16_t *)calloc(sq->size, sizeof(int16_t));
+ if (sq->buffer) {
+ for (int i = 0; i < count; ++i) {
+ decodeAudioFrame2205(src + _audioBufferOffsetRd + i * kAudioStrideSize, kAudioSamples, sq->buffer + i * kAudioSamples * 2);
+ }
+ }
+ sq->next = 0;
+
+ g_system->lockAudio();
+ if (_audioQueueTail) {
+ _audioQueueTail->next = sq;
+ } else {
+ assert(!_audioQueue);
+ _audioQueue = sq;
+ }
+ _audioQueueTail = sq;
+ g_system->unlockAudio();
+ }
+ }
+ _audioBufferOffsetRd += count * kAudioStrideSize;
+ if (_audioBufferOffsetWr == _flushAudioSize) {
+ _audioBufferOffsetWr = 0;
+ _audioBufferOffsetRd = 0;
+ }
+}
+
+void PafPlayer::mix(int16_t *buf, int samples) {
+ while (_audioQueue && samples > 0) {
+ assert(_audioQueue->size != 0);
+ *buf++ = _audioQueue->buffer[_audioQueue->offset++];
+ *buf++ = _audioQueue->buffer[_audioQueue->offset++];
+ samples -= 2;
+ if (_audioQueue->offset >= _audioQueue->size) {
+ assert(_audioQueue->offset == _audioQueue->size);
+ PafAudioQueue *next = _audioQueue->next;
+ free(_audioQueue->buffer);
+ free(_audioQueue);
+ _audioQueue = next;
+ }
+ }
+ if (!_audioQueue) {
+ _audioQueueTail = 0;
+ }
+ if (samples > 0) {
+ warning("PafPlayer::mix() soundQueue underrun %d", samples);
+ }
+}
+
+static void mixAudio(void *userdata, int16_t *buf, int len) {
+ ((PafPlayer *)userdata)->mix(buf, len);
+}
+
+void PafPlayer::mainLoop() {
+ _file.seek(_videoOffset + _pafHdr.startOffset, SEEK_SET);
+ for (int i = 0; i < 4; ++i) {
+ memset(_pageBuffers[i], 0, kVideoWidth * kVideoHeight);
+ }
+ memset(_paletteBuffer, 0, sizeof(_paletteBuffer));
+ _paletteChanged = true;
+ _currentPageBuffer = 0;
+ int currentFrameBlock = 0;
+
+ AudioCallback prevAudioCb;
+ if (_demuxAudioFrameBlocks) {
+ AudioCallback audioCb;
+ audioCb.proc = mixAudio;
+ audioCb.userdata = this;
+ prevAudioCb = g_system->setAudioCallback(audioCb);
+ }
+
+ const uint32_t framesPerSec = (_demuxAudioFrameBlocks != 0) ? kFramesPerSec : 15;
+ uint32_t frameTime = g_system->getTimeStamp() + 1000 / framesPerSec;
+
+ uint32_t blocksCountForFrame = _pafHdr.preloadFrameBlocksCount;
+ for (int i = 0; i < (int)_pafHdr.framesCount; ++i) {
+ // read buffering blocks
+ blocksCountForFrame += _pafHdr.frameBlocksCountTable[i];
+ while (blocksCountForFrame != 0) {
+ _file.read(_bufferBlock, _pafHdr.readBufferSize);
+ const uint32_t dstOffset = _pafHdr.frameBlocksOffsetTable[currentFrameBlock] & ~(1 << 31);
+ if (_pafHdr.frameBlocksOffsetTable[currentFrameBlock] & (1 << 31)) {
+ assert(dstOffset + _pafHdr.readBufferSize <= _pafHdr.maxAudioFrameBlocksCount * _pafHdr.readBufferSize);
+ memcpy(_demuxAudioFrameBlocks + dstOffset, _bufferBlock, _pafHdr.readBufferSize);
+ decodeAudioFrame(_demuxAudioFrameBlocks, dstOffset, _pafHdr.readBufferSize);
+ } else {
+ assert(dstOffset + _pafHdr.readBufferSize <= _pafHdr.maxVideoFrameBlocksCount * _pafHdr.readBufferSize);
+ memcpy(_demuxVideoFrameBlocks + dstOffset, _bufferBlock, _pafHdr.readBufferSize);
+ }
+ ++currentFrameBlock;
+ --blocksCountForFrame;
+ }
+ // decode video data
+ decodeVideoFrame(_demuxVideoFrameBlocks + _pafHdr.framesOffsetTable[i]);
+
+ if (_pafCb.proc) {
+ _pafCb.proc(_pafCb.userdata, i, _pageBuffers[_currentPageBuffer]);
+ } else {
+ g_system->copyRect(0, 0, kVideoWidth, kVideoHeight, _pageBuffers[_currentPageBuffer], kVideoWidth);
+ }
+ if (_paletteChanged) {
+ g_system->setPalette(_paletteBuffer, 256, 6);
+ }
+ g_system->updateScreen(false);
+ g_system->processEvents();
+ if (g_system->inp.keyPressed(SYS_INP_ESC) || g_system->inp.skip) {
+ break;
+ }
+
+ const int delay = MAX(10, frameTime - g_system->getTimeStamp());
+ g_system->sleep(delay);
+ frameTime = g_system->getTimeStamp() + 1000 / framesPerSec;
+
+ // set next decoding video page
+ ++_currentPageBuffer;
+ _currentPageBuffer &= 3;
+ }
+
+ // restore audio callback
+ if (_demuxAudioFrameBlocks) {
+ g_system->setAudioCallback(prevAudioCb);
+ }
+
+ unload();
+}
+
+void PafPlayer::setCallback(const PafCallback *pafCb) {
+ if (pafCb) {
+ _pafCb = *pafCb;
+ } else {
+ memset(&_pafCb, 0, sizeof(_pafCb));
+ }
+}
diff --git a/Src/Vita_&_Switch/paf.h b/Src/Vita_&_Switch/paf.h
new file mode 100644
index 0000000..3afc709
--- /dev/null
+++ b/Src/Vita_&_Switch/paf.h
@@ -0,0 +1,126 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef PAF_PLAYER_H__
+#define PAF_PLAYER_H__
+
+#include "intern.h"
+#include "fileio.h"
+
+struct PafHeader {
+ uint32_t preloadFrameBlocksCount;
+ uint32_t *frameBlocksCountTable;
+ uint32_t *framesOffsetTable;
+ uint32_t *frameBlocksOffsetTable;
+ int32_t framesCount;
+ int32_t frameBlocksCount;
+ uint32_t startOffset;
+ uint32_t readBufferSize;
+ int32_t maxVideoFrameBlocksCount;
+ int32_t maxAudioFrameBlocksCount;
+ int32_t frameRate;
+};
+
+// names taken from the PSX filenames
+enum {
+ kPafAnimation_intro = 0,
+ kPafAnimation_cine14l = 1,
+ kPafAnimation_rapt = 2,
+ kPafAnimation_glisse = 3,
+ kPafAnimation_meeting = 4,
+ kPafAnimation_island = 5,
+ kPafAnimation_islefall = 6,
+ kPafAnimation_vicious = 7,
+ kPafAnimation_together = 8,
+ kPafAnimation_power = 9,
+ kPafAnimation_back = 10,
+ kPafAnimation_dogfree1 = 11,
+ kPafAnimation_dogfree2 = 12,
+ kPafAnimation_meteor = 13,
+ kPafAnimation_cookie = 14,
+ kPafAnimation_plot = 15,
+ kPafAnimation_puzzle = 16,
+ kPafAnimation_lstpiece = 17,
+ kPafAnimation_dogfall = 18,
+ kPafAnimation_lastfall = 19,
+ kPafAnimation_end = 20,
+ kPafAnimation_cinema = 21,
+ kPafAnimation_CanyonAndyFallingCannon = 22, // kPafAnimation_ghct
+ kPafAnimation_CanyonAndyFalling = 23, // kPafAnimation_chute
+ kPafAnimation_IslandAndyFalling = 24 // kPafAnimation_chute_i
+};
+
+struct FileSystem;
+
+struct PafAudioQueue {
+ int16_t *buffer; // stereo samples
+ int offset, size;
+ PafAudioQueue *next;
+};
+
+struct PafCallback {
+ void (*proc)(void *userdata, int num, const uint8_t *frame);
+ void *userdata;
+};
+
+struct PafPlayer {
+
+ enum {
+ kMaxVideosCount = 50,
+ kBufferBlockSize = 2048,
+ kVideoWidth = 256,
+ kVideoHeight = 192,
+ kPageBufferSize = 256 * 256,
+ kFramesPerSec = 10, // 10hz
+ kAudioSamples = 2205,
+ kAudioStrideSize = 4922 // 256 * sizeof(int16_t) + 2205 * 2
+ };
+
+ bool _skipCutscenes;
+ FileSystem *_fs;
+ File _file;
+ int _videoNum;
+ uint32_t _videoOffset;
+ PafHeader _pafHdr;
+ int _currentPageBuffer;
+ uint8_t *_pageBuffers[4];
+ uint8_t _paletteBuffer[256 * 3];
+ bool _paletteChanged;
+ uint8_t _bufferBlock[kBufferBlockSize];
+ uint8_t *_demuxVideoFrameBlocks;
+ uint8_t *_demuxAudioFrameBlocks;
+ uint32_t _audioBufferOffsetRd;
+ uint32_t _audioBufferOffsetWr;
+ PafAudioQueue *_audioQueue, *_audioQueueTail;
+ uint32_t _flushAudioSize;
+ uint32_t _playedMask;
+ PafCallback _pafCb;
+
+ PafPlayer(FileSystem *fs);
+ ~PafPlayer();
+
+ void preload(int num);
+ void play(int num);
+ void unload(int num = -1);
+
+ bool readPafHeader();
+ uint32_t *readPafHeaderTable(int count);
+
+ void decodeVideoFrame(const uint8_t *src);
+ uint8_t *getVideoPageOffset(uint16_t val);
+ void decodeVideoFrameOp0(const uint8_t *base, const uint8_t *src, uint8_t code);
+ void decodeVideoFrameOp1(const uint8_t *src);
+ void decodeVideoFrameOp2(const uint8_t *src);
+ void decodeVideoFrameOp4(const uint8_t *src);
+
+ void decodeAudioFrame(const uint8_t *src, uint32_t offset, uint32_t size);
+
+ void mix(int16_t *buf, int samples);
+ void mainLoop();
+
+ void setCallback(const PafCallback *pafCb);
+};
+
+#endif // PAF_PLAYER_H__
diff --git a/Src/Vita_&_Switch/random.cpp b/Src/Vita_&_Switch/random.cpp
new file mode 100644
index 0000000..fe2c021
--- /dev/null
+++ b/Src/Vita_&_Switch/random.cpp
@@ -0,0 +1,79 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "random.h"
+
+void Random::setSeed() {
+ _rndSeed = 0x101A1010;
+}
+
+void Random::initMstTable() {
+ for (int i = 0; i < 8; ++i) {
+ for (int j = 0; j < 32; ++j) {
+ _mstRandomTable[i][j] = j;
+ }
+ for (int j = 0; j < 64; ++j) {
+ const int index1 = update() & 31;
+ const int index2 = update() & 31;
+ SWAP(_mstRandomTable[i][index1], _mstRandomTable[i][index2]);
+ }
+ }
+}
+
+void Random::initTable(int rounds) {
+ for (int i = 0; i < 100; ++i) {
+ _rndRandomTable[i] = i + 1;
+ }
+ for (int j = 0; j < rounds; ++j) {
+ for (int i = 0; i < 256; ++i) {
+ const int a = update() % 100;
+ const int b = update() % 100;
+ SWAP(_rndRandomTable[a], _rndRandomTable[b]);
+ }
+ }
+ _rndRandomTableIndex = 0;
+}
+
+uint32_t Random::update() {
+ const bool overflow = (_rndSeed & (1 << 31)) != 0;
+ _rndSeed *= 2;
+ if (!overflow) {
+ _rndSeed ^= 0xDEADBEEF;
+ }
+ return _rndSeed;
+}
+
+uint8_t Random::getNextNumber() {
+ const uint8_t num = _rndRandomTable[_rndRandomTableIndex];
+ ++_rndRandomTableIndex;
+ if (_rndRandomTableIndex == 100) {
+ _rndRandomTableIndex = 0;
+ }
+ return num;
+}
+
+void Random::resetMst(uint8_t *p) {
+ p[0] = update() & 7; // row
+ p[1] = update() & 31; // start
+ p[2] = 32; // count
+}
+
+uint8_t Random::getMstNextNumber(uint8_t *p) {
+ const uint8_t num = _mstRandomTable[p[0]][p[1]];
+ ++p[1];
+ if (p[1] >= 32) {
+ p[1] = 0;
+ }
+ --p[2];
+ if (p[2] == 0) { // move to next row
+ ++p[0];
+ if (p[0] >= 8) {
+ p[0] = 0;
+ }
+ p[1] = update() & 31;
+ p[2] = 32;
+ }
+ return num;
+}
diff --git a/Src/Vita_&_Switch/random.h b/Src/Vita_&_Switch/random.h
new file mode 100644
index 0000000..2eecdcc
--- /dev/null
+++ b/Src/Vita_&_Switch/random.h
@@ -0,0 +1,26 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef RANDOM_H__
+#define RANDOM_H__
+
+#include "intern.h"
+
+struct Random {
+ uint32_t _rndSeed;
+ uint8_t _rndRandomTable[100];
+ int _rndRandomTableIndex;
+ uint8_t _mstRandomTable[8][32];
+
+ void setSeed();
+ void initMstTable();
+ void initTable(int rounds = 1);
+ uint32_t update();
+ uint8_t getNextNumber();
+ void resetMst(uint8_t *p);
+ uint8_t getMstNextNumber(uint8_t *p);
+};
+
+#endif // RANDOM_H__
diff --git a/Src/Vita_&_Switch/resource.cpp b/Src/Vita_&_Switch/resource.cpp
new file mode 100644
index 0000000..2f395a8
--- /dev/null
+++ b/Src/Vita_&_Switch/resource.cpp
@@ -0,0 +1,2071 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "fileio.h"
+#include "fs.h"
+#include "game.h"
+#include "lzw.h"
+#include "resource.h"
+#include "util.h"
+
+// load and uncompress .sss pcm on level start
+static const bool kPreloadSssPcm = true;
+
+static const bool kPreloadLvlBackgroundData = true;
+
+static const bool kCheckSssBytecode = false;
+
+// menu settings and player progress
+static const char *_setupCfg = "setup.cfg";
+
+static const char *_setupDat = "SETUP.DAT";
+static const char *_setupDax = "SETUP.DAX";
+
+static const char *_hodDem = "HOD.DEM";
+
+static const char *_prefixes[] = {
+ "rock",
+ "fort",
+ "pwr1",
+ "isld",
+ "lava",
+ "pwr2",
+ "lar1",
+ "lar2",
+ "dark",
+ "test"
+};
+
+static bool openDat(FileSystem *fs, const char *name, File *f) {
+ FILE *fp = fs->openAssetFile(name);
+ if (fp) {
+ f->setFp(fp);
+ f->seek(0, SEEK_SET);
+ return true;
+ }
+ return false;
+}
+
+static void closeDat(FileSystem *fs, File *f) {
+ if (f->_fp) {
+ fs->closeFile(f->_fp);
+ f->setFp(0);
+ }
+}
+
+static int skipBytesAlign(File *f, int len) {
+ const int size = (len + 3) & ~3;
+ f->seek(size, SEEK_CUR);
+ return size;
+}
+
+static int readBytesAlign(File *f, uint8_t *buf, int len) {
+ f->read(buf, len);
+ if ((len & 3) != 0) {
+ f->seek(4 - (len & 3), SEEK_CUR);
+ }
+ return (len + 3) & ~3;
+}
+
+Resource::Resource(FileSystem *fs)
+ : _fs(fs), _isPsx(false), _isDemo(false), _version(V1_1) {
+
+ memset(_screensGrid, 0, sizeof(_screensGrid));
+ memset(_screensBasePos, 0, sizeof(_screensBasePos));
+ memset(_screensState, 0, sizeof(_screensState));
+
+ // masks
+ _resLevelData0x470CTable = 0;
+ _resLevelData0x470CTablePtrHdr = 0;
+ _resLevelData0x470CTablePtrData = 0;
+
+ _lvlSssOffset = 0;
+
+ // sprites
+ memset(_resLevelData0x2988SizeTable, 0, sizeof(_resLevelData0x2988SizeTable));
+ memset(_resLevelData0x2988Table, 0, sizeof(_resLevelData0x2988Table));
+ memset(_resLevelData0x2988PtrTable, 0, sizeof(_resLevelData0x2988PtrTable));
+ memset(_resLvlSpriteDataPtrTable, 0, sizeof(_resLvlSpriteDataPtrTable));
+
+ // backgrounds
+ memset(_resLvlScreenBackgroundDataTable, 0, sizeof(_resLvlScreenBackgroundDataTable));
+ memset(_resLvlScreenBackgroundDataPtrTable, 0, sizeof(_resLvlScreenBackgroundDataPtrTable));
+ memset(_resLevelData0x2B88SizeTable, 0, sizeof(_resLevelData0x2B88SizeTable));
+
+ memset(_resLvlScreenObjectDataTable, 0, sizeof(_resLvlScreenObjectDataTable));
+ memset(&_dummyObject, 0, sizeof(_dummyObject));
+
+ if (sectorAlignedGameData()) {
+ _datFile = new SectorFile;
+ _lvlFile = new SectorFile;
+ _mstFile = new SectorFile;
+ _sssFile = new SectorFile;
+ // from v1.2, game data files are 'sector aligned'
+ _version = V1_2;
+ } else {
+ _datFile = new File;
+ _lvlFile = new File;
+ _mstFile = new File;
+ _sssFile = new File;
+ // detect if this is version 1.0 by reading the size of the first screen background using the v1.1 offset
+ char filename[32];
+ snprintf(filename, sizeof(filename), "%s_HOD.LVL", _prefixes[0]);
+ if (openDat(_fs, filename, _lvlFile)) {
+ _lvlFile->seek(0x2B88, SEEK_SET);
+ _lvlFile->skipUint32();
+ const int size = _lvlFile->readUint32();
+ if (size == 0) {
+ _version = V1_0;
+ }
+ closeDat(_fs, _lvlFile);
+ }
+ }
+ // detect if this is a demo version by trying to open the second level data files
+ char filename[32];
+ snprintf(filename, sizeof(filename), "%s_HOD.LVL", _prefixes[1]);
+ if (openDat(_fs, filename, _lvlFile)) {
+ closeDat(_fs, _lvlFile);
+ } else {
+ _isDemo = true;
+ }
+ debug(kDebug_RESOURCE, "psx %d demo %d version %d", _isPsx, _isDemo, _version);
+ memset(&_datHdr, 0, sizeof(_datHdr));
+ memset(&_lvlHdr, 0, sizeof(_lvlHdr));
+ memset(&_mstHdr, 0, sizeof(_mstHdr));
+ memset(&_sssHdr, 0, sizeof(_sssHdr));
+
+ _lvlSpritesOffset = 0x288 + 96 * (_version == V1_0 ? 96 : 104);
+ _lvlBackgroundsOffset = _lvlSpritesOffset + 32 * 16;
+ _lvlMasksOffset = _lvlBackgroundsOffset + kMaxScreens * (16 + 160);
+
+ _loadingImageBuffer = 0;
+ _fontBuffer = 0;
+ _menuBuffer0 = 0;
+ _menuBuffer1 = 0;
+
+ memset(&_dem, 0, sizeof(_dem));
+ _demOffset = 0;
+}
+
+Resource::~Resource() {
+ delete _datFile;
+ delete _lvlFile;
+ delete _mstFile;
+ delete _sssFile;
+}
+
+bool Resource::sectorAlignedGameData() {
+ FILE *fp = _fs->openAssetFile(_setupDat);
+ if (!fp) {
+ fp = _fs->openAssetFile(_setupDax);
+ if (!fp) {
+ error("Unable to open '%s' or '%s'", _setupDat, _setupDax);
+ return false;
+ }
+ }
+ bool ret = false;
+ uint8_t buf[2048];
+ if (fread(buf, 1, sizeof(buf), fp) == sizeof(buf)) {
+ ret = fioUpdateCRC(0, buf, sizeof(buf)) == 0;
+ }
+ _fs->closeFile(fp);
+ return ret;
+}
+
+void Resource::loadSetupDat() {
+ if (!openDat(_fs, _setupDat, _datFile)) {
+ _isPsx = openDat(_fs, _setupDax, _datFile);
+ }
+
+ _datHdr.version = _datFile->readUint32();
+ if (_datHdr.version != 10 && _datHdr.version != 11) {
+ warning("Unhandled .dat version %d", _datHdr.version);
+ return;
+ }
+
+ _datHdr.bufferSize0 = _datFile->readUint32();
+ _datHdr.bufferSize1 = _datFile->readUint32();
+ _datHdr.sssOffset = _datFile->readUint32();
+ _datHdr.iconsCount = _datFile->readUint32();
+ _datHdr.menusCount = _datFile->readUint32();
+ _datHdr.cutscenesCount = _datFile->readUint32();
+ _datHdr.levelsCount = _datFile->readUint32();
+ for (int i = 0; i < kLvl_dark; ++i) { // last level has a single checkpoint
+ _datHdr.levelCheckpointsCount[i] = _datFile->readUint32();
+ }
+ _datHdr.yesNoQuitImage = _datFile->readUint32();
+ _datHdr.soundDataSize = _datFile->readUint32();
+ _datHdr.loadingImageSize = _datFile->readUint32();
+ const int hintsCount = (_datHdr.version == 11) ? 46 : 20;
+ for (int i = 0; i < hintsCount; ++i) {
+ _datHdr.hintsImageOffsetTable[i] = _datFile->readUint32();
+ }
+ for (int i = 0; i < hintsCount; ++i) {
+ _datHdr.hintsImageSizeTable[i] = _datFile->readUint32();
+ }
+ _datFile->seek(2048, SEEK_SET); // align to next sector
+
+ _loadingImageBuffer = (uint8_t *)malloc(_datHdr.loadingImageSize);
+ if (_loadingImageBuffer) {
+ _datFile->read(_loadingImageBuffer, _datHdr.loadingImageSize);
+
+ uint32_t offset = 0;
+
+ if (!_isPsx) {
+ // loading image
+ uint32_t size = READ_LE_UINT32(_loadingImageBuffer + offset); offset += 8;
+ offset += size + 768;
+
+ // loading animation
+ size = READ_LE_UINT32(_loadingImageBuffer + offset + 8); offset += 16;
+ offset += size;
+ }
+
+ // font
+ static const int kFontSize = 16 * 16 * 64;
+ _fontBuffer = (uint8_t *)malloc(kFontSize);
+ if (_fontBuffer) {
+ /* size = READ_LE_UINT32(_loadingImageBuffer + offset); */ offset += 4;
+ if (_datHdr.version == 11) {
+ const uint32_t uncompressedSize = decodeLZW(_loadingImageBuffer + offset, _fontBuffer);
+ assert(uncompressedSize == kFontSize);
+ } else {
+ memcpy(_fontBuffer, _loadingImageBuffer + offset, kFontSize);
+ }
+ }
+ }
+ assert(_datHdr.yesNoQuitImage == hintsCount - 3);
+ _menuBuffersOffset = _datHdr.hintsImageOffsetTable[_datHdr.yesNoQuitImage + 2];
+}
+
+bool Resource::loadDatHintImage(int num, uint8_t *dst, uint8_t *pal) {
+ if (!_isPsx) {
+ const int offset = _datHdr.hintsImageOffsetTable[num];
+ const int size = _datHdr.hintsImageSizeTable[num];
+ assert(size == 256 * 192);
+ _datFile->seek(offset, SEEK_SET);
+ _datFile->read(dst, size);
+ if (_datHdr.version == 11) {
+ _datFile->seek(offset + fioAlignSizeTo2048(size), SEEK_SET); // align to next sector
+ }
+ _datFile->read(pal, 768);
+ return true;
+ }
+ return false;
+}
+
+bool Resource::loadDatLoadingImage(uint8_t *dst, uint8_t *pal) {
+ if (!_isPsx && _loadingImageBuffer) {
+ const uint32_t bufferSize = READ_LE_UINT32(_loadingImageBuffer);
+ const int size = decodeLZW(_loadingImageBuffer + 8, dst);
+ assert(size == 256 * 192);
+ // palette follows compressed bitmap
+ memcpy(pal, _loadingImageBuffer + 8 + bufferSize, 256 * 3);
+ return true;
+ }
+ return false;
+}
+
+void Resource::loadDatMenuBuffers() {
+ assert((_datHdr.sssOffset & 0x7FF) == 0);
+ _datFile->seek(_datHdr.sssOffset, SEEK_SET);
+ loadSssData(_datFile, _datHdr.sssOffset);
+
+ const uint32_t baseOffset = _menuBuffersOffset;
+ _datFile->seek(baseOffset, SEEK_SET);
+ _menuBuffer1 = (uint8_t *)malloc(_datHdr.bufferSize1);
+ if (_menuBuffer1) {
+ _datFile->read(_menuBuffer1, _datHdr.bufferSize1);
+ }
+ if (_datHdr.version == 11) {
+ _datFile->seek(baseOffset + fioAlignSizeTo2048(_datHdr.bufferSize1), SEEK_SET); // align to next sector
+ }
+ _menuBuffer0 = (uint8_t *)malloc(_datHdr.bufferSize0);
+ if (_menuBuffer0) {
+ _datFile->read(_menuBuffer0, _datHdr.bufferSize0);
+ }
+}
+
+void Resource::unloadDatMenuBuffers() {
+ free(_menuBuffer1);
+ _menuBuffer1 = 0;
+ free(_menuBuffer0);
+ _menuBuffer0 = 0;
+}
+
+void Resource::loadLevelData(int levelNum) {
+
+ char filename[32];
+ const char *levelName = _prefixes[levelNum];
+
+ closeDat(_fs, _lvlFile);
+ snprintf(filename, sizeof(filename), "%s_HOD.LVL", levelName);
+ if (openDat(_fs, filename, _lvlFile)) {
+ loadLvlData(_lvlFile);
+ } else {
+ error("Unable to open '%s'", filename);
+ }
+
+ closeDat(_fs, _mstFile);
+ snprintf(filename, sizeof(filename), "%s_HOD.MST", levelName);
+ if (openDat(_fs, filename, _mstFile)) {
+ loadMstData(_mstFile);
+ } else {
+ warning("Unable to open '%s'", filename);
+ memset(&_mstHdr, 0, sizeof(_mstHdr));
+ }
+
+ closeDat(_fs, _sssFile);
+ snprintf(filename, sizeof(filename), "%s_HOD.SSS", levelName);
+ if (openDat(_fs, filename, _sssFile)) {
+ loadSssData(_sssFile);
+ } else if (_isPsx) {
+ assert((_lvlSssOffset & 0x7FF) == 0);
+ _lvlFile->seek(_lvlSssOffset, SEEK_SET);
+ loadSssData(_lvlFile, _lvlSssOffset);
+ } else {
+ warning("Unable to open '%s'", filename);
+ memset(&_sssHdr, 0, sizeof(_sssHdr));
+ }
+}
+
+void Resource::loadLvlScreenObjectData(LvlObject *dat, const uint8_t *src) {
+ const uint8_t *start = src;
+ dat->xPos = READ_LE_UINT32(src); src += 4;
+ dat->yPos = READ_LE_UINT32(src); src += 4;
+ dat->screenNum = *src++;
+ dat->screenState = *src++;
+ dat->dataNum = *src++;
+ dat->frame = *src++;
+ dat->anim = READ_LE_UINT16(src); src += 2;
+ dat->type = *src++;
+ dat->spriteNum = *src++;
+ dat->flags0 = READ_LE_UINT16(src); src += 2;
+ dat->flags1 = READ_LE_UINT16(src); src += 2;
+ dat->flags2 = READ_LE_UINT16(src); src += 2;
+ dat->objectUpdateType = *src++;
+ dat->hitCount = *src++;
+ const uint32_t objRef = READ_LE_UINT32(src); src += 4;
+ if (objRef) {
+ dat->childPtr = &_dummyObject;
+ debug(kDebug_RESOURCE, "loadLvlObj dat %p linkObjRef 0x%x", dat, objRef);
+ }
+ dat->width = READ_LE_UINT16(src); src += 2;
+ dat->height = READ_LE_UINT16(src); src += 2;
+ dat->directionKeyMask = *src++;
+ dat->actionKeyMask = *src++;
+ dat->currentSprite = READ_LE_UINT16(src); src += 2;
+ dat->currentSound = READ_LE_UINT16(src); src += 2;
+ src += 2; // 0x26
+ dat->bitmapBits = 0; src += 4;
+ dat->callbackFuncPtr = 0; src += 4;
+ dat->dataPtr = 0; src += 4;
+ dat->sssObject = 0; src += 4;
+ dat->levelData0x2988 = 0; src += 4;
+ for (int i = 0; i < 8; ++i) {
+ dat->posTable[i].x = READ_LE_UINT16(src); src += 2;
+ dat->posTable[i].y = READ_LE_UINT16(src); src += 2;
+ }
+ dat->nextPtr = 0; src += 4;
+ assert((src - start) == 96);
+}
+
+static uint32_t resFixPointersLevelData0x2988(uint8_t *src, uint8_t *ptr, LvlObjectData *dat, bool isPsx) {
+ uint8_t *base = src;
+
+ dat->unk0 = *src++;
+ dat->spriteNum = *src++;
+ dat->framesCount = READ_LE_UINT16(src); src += 2;
+ dat->hotspotsCount = READ_LE_UINT16(src); src += 2;
+ dat->movesCount = READ_LE_UINT16(src); src += 2;
+ dat->coordsCount = READ_LE_UINT16(src); src += 2;
+ dat->refCount = *src++;
+ dat->frame = *src++;
+ dat->anim = READ_LE_UINT16(src); src += 2;
+ src += 2; // 0xE
+ src += 4; // 0x10
+ uint32_t movesDataOffset = READ_LE_UINT32(src); src += 4; // 0x14
+ src += 4; // 0x18
+ uint32_t framesDataOffset = READ_LE_UINT32(src); src += 4; // 0x1C
+ src += 4; // 0x20
+ uint32_t coordsDataOffset = READ_LE_UINT32(src); src += 4; // 0x24
+ uint32_t hotspotsDataOffset = READ_LE_UINT32(src); src += 4; // 0x28
+
+ if (dat->refCount != 0) {
+ return 0;
+ }
+
+ assert(src == base + kLvlAnimHdrOffset);
+ dat->animsInfoData = base;
+ dat->refCount = 0xFF;
+ dat->framesData = (framesDataOffset == 0) ? 0 : base + framesDataOffset;
+ dat->hotspotsData = (hotspotsDataOffset == 0) ? 0 : base + hotspotsDataOffset;
+ dat->movesData = (movesDataOffset == 0) ? 0 : base + movesDataOffset;
+ dat->coordsData = (coordsDataOffset == 0) ? 0 : base + coordsDataOffset;
+ if (kByteSwapData) {
+ for (int i = 0; i < dat->hotspotsCount; ++i) {
+ LvlAnimHeader *ah = ((LvlAnimHeader *)(base + kLvlAnimHdrOffset)) + i;
+ ah->unk0 = le16toh(ah->unk0);
+ ah->seqOffset = le32toh(ah->seqOffset);
+ if (ah->seqOffset == 0) {
+ continue;
+ }
+ for (int j = 0; j < ah->seqCount; ++j) {
+ LvlAnimSeqHeader *ash = ((LvlAnimSeqHeader *)(base + ah->seqOffset)) + j;
+ ash->firstFrame = le16toh(ash->firstFrame);
+ ash->unk2 = le16toh(ash->unk2);
+ ash->sound = le16toh(ash->sound);
+ ash->flags0 = le16toh(ash->flags0);
+ ash->flags1 = le16toh(ash->flags1);
+ ash->unkE = le16toh(ash->unkE);
+ ash->offset = le32toh(ash->offset);
+ if (ash->offset == 0) {
+ continue;
+ }
+ for (int k = 0; k < ash->count; ++k) {
+ LvlAnimSeqFrameHeader *asfh = ((LvlAnimSeqFrameHeader *)(base + ash->offset)) + k;
+ asfh->move = le16toh(asfh->move);
+ asfh->anim = le16toh(asfh->anim);
+ }
+ }
+ }
+ for (int i = 0; i < dat->movesCount; ++i) {
+ LvlSprMoveData *smd = ((LvlSprMoveData *)dat->movesData) + i;
+ smd->op3 = le16toh(smd->op3);
+ smd->op4 = le16toh(smd->op4);
+ }
+ }
+
+ dat->framesOffsetsTable = ptr;
+ if (dat->coordsData) {
+ dat->coordsOffsetsTable = ptr + dat->framesCount * 4;
+ } else {
+ dat->coordsOffsetsTable = 0;
+ }
+
+ if (dat->unk0 == 1) { // fixed size offset table
+ assert(isPsx);
+ dat->framesOffsetsTable = (uint8_t *)malloc(dat->framesCount * sizeof(uint32_t));
+ uint32_t framesOffset = 6 * dat->framesCount;
+ if (READ_LE_UINT16(dat->framesData + framesOffset) == 0) {
+ framesOffset += 2;
+ }
+ for (int i = 0; i < dat->framesCount; ++i) {
+ const int size = READ_LE_UINT16(dat->framesData + i * 6);
+ WRITE_LE_UINT32(dat->framesOffsetsTable + i * sizeof(uint32_t), framesOffset);
+ framesOffset += size;
+ }
+ dat->coordsOffsetsTable = ptr;
+ return 0;
+ }
+
+ uint32_t framesOffset = 0;
+ for (int i = 0; i < dat->framesCount; ++i) {
+ const int size = READ_LE_UINT16(dat->framesData + framesOffset);
+ WRITE_LE_UINT32(dat->framesOffsetsTable + i * sizeof(uint32_t), framesOffset);
+ framesOffset += size;
+ }
+ uint32_t coordsOffset = 0;
+ for (int i = 0; i < dat->coordsCount; ++i) {
+ const int count = dat->coordsData[coordsOffset];
+ WRITE_LE_UINT32(dat->coordsOffsetsTable + i * sizeof(uint32_t), coordsOffset);
+ coordsOffset += count * 4 + 1;
+ }
+
+ return (dat->framesCount + dat->coordsCount) * sizeof(uint32_t);
+}
+
+void Resource::loadLvlSpriteData(int num, const uint8_t *buf) {
+ assert((unsigned int)num < kMaxSpriteTypes);
+
+ static const uint32_t baseOffset = _lvlSpritesOffset;
+
+ uint8_t header[3 * sizeof(uint32_t)];
+ if (!buf) {
+ _lvlFile->seekAlign(baseOffset + num * 16);
+ _lvlFile->read(header, sizeof(header));
+ buf = header;
+ }
+ const uint32_t offset = READ_LE_UINT32(&buf[0]);
+ const uint32_t size = READ_LE_UINT32(&buf[4]);
+ if (size == 0) {
+ return;
+ }
+ const uint32_t readSize = READ_LE_UINT32(&buf[8]);
+ assert(readSize <= size);
+ uint8_t *ptr = (uint8_t *)malloc(size);
+ _lvlFile->seek(_isPsx ? _lvlSssOffset + offset : offset, SEEK_SET);
+ _lvlFile->read(ptr, readSize);
+
+ LvlObjectData *dat = &_resLevelData0x2988Table[num];
+ const uint32_t readOffsetsSize = resFixPointersLevelData0x2988(ptr, ptr + readSize, dat, _isPsx);
+ const uint32_t allocatedOffsetsSize = size - readSize;
+ assert(allocatedOffsetsSize == readOffsetsSize);
+
+ _resLevelData0x2988PtrTable[dat->spriteNum] = dat;
+ _resLvlSpriteDataPtrTable[num] = ptr;
+ _resLevelData0x2988SizeTable[num] = size;
+}
+
+const uint8_t *Resource::getLvlScreenMaskDataPtr(int num) const {
+ assert((unsigned int)num < kMaxScreens * 4);
+ const uint32_t offset = READ_LE_UINT32(_resLevelData0x470CTablePtrHdr + num * 8);
+ return (offset != 0) ? _resLevelData0x470CTable + offset : 0;
+}
+
+const uint8_t *Resource::getLvlScreenPosDataPtr(int num) const {
+ assert((unsigned int)num < kMaxScreens * 4);
+ const uint32_t offset = READ_LE_UINT32(_resLevelData0x470CTablePtrHdr + num * 8 + 4);
+ return (offset != 0) ? _resLevelData0x470CTable + offset : 0;
+}
+
+void Resource::loadLvlScreenMaskData() {
+ _lvlFile->seekAlign(_lvlMasksOffset);
+ const uint32_t offset = _lvlFile->readUint32();
+ const uint32_t size = _lvlFile->readUint32();
+ _resLevelData0x470CTable = (uint8_t *)malloc(size);
+ _lvlFile->seek(offset, SEEK_SET);
+ _lvlFile->read(_resLevelData0x470CTable, size);
+ _resLevelData0x470CTablePtrHdr = _resLevelData0x470CTable;
+ _resLevelData0x470CTablePtrData = _resLevelData0x470CTable + (kMaxScreens * 4) * (2 * sizeof(uint32_t));
+ // .sss is embedded in .lvl on PSX
+ _lvlSssOffset = offset + fioAlignSizeTo2048(size);
+}
+
+static const uint32_t _lvlTag = 0x484F4400; // 'HOD\x00'
+
+void Resource::loadLvlData(File *fp) {
+
+ assert(fp == _lvlFile);
+
+ unloadLvlData();
+
+ const uint32_t tag = _lvlFile->readUint32();
+ if (tag != _lvlTag) {
+ error("Unhandled .lvl tag 0x%x", tag);
+ closeDat(_fs, _lvlFile);
+ return;
+ }
+
+ _lvlHdr.screensCount = _lvlFile->readByte();
+ _lvlHdr.staticLvlObjectsCount = _lvlFile->readByte();
+ _lvlHdr.otherLvlObjectsCount = _lvlFile->readByte();
+ _lvlHdr.spritesCount = _lvlFile->readByte();
+ debug(kDebug_RESOURCE, "Resource::loadLvlData() %d %d %d %d", _lvlHdr.screensCount, _lvlHdr.staticLvlObjectsCount, _lvlHdr.otherLvlObjectsCount, _lvlHdr.spritesCount);
+
+ _lvlFile->seekAlign(0x8);
+ for (int i = 0; i < _lvlHdr.screensCount; ++i) {
+ _lvlFile->read(_screensGrid[i], 4);
+ }
+ _lvlFile->seekAlign(0xA8);
+ for (int i = 0; i < _lvlHdr.screensCount; ++i) {
+ LvlScreenVector *dat = &_screensBasePos[i];
+ dat->u = _lvlFile->readUint32();
+ dat->v = _lvlFile->readUint32();
+ }
+ _lvlFile->seekAlign(0x1E8);
+ for (int i = 0; i < _lvlHdr.screensCount; ++i) {
+ LvlScreenState *dat = &_screensState[i];
+ dat->s0 = _lvlFile->readByte();
+ dat->s1 = _lvlFile->readByte();
+ dat->s2 = _lvlFile->readByte();
+ dat->s3 = _lvlFile->readByte();
+ }
+ _lvlFile->seekAlign(0x288);
+ static const int kSizeOfLvlObject = 96;
+ const int lvlObjectsCount = (_lvlSpritesOffset - 0x288) / kSizeOfLvlObject;
+ debug(kDebug_RESOURCE, "Resource::loadLvlData() lvlObjectsCount %d", lvlObjectsCount);
+ for (int i = 0; i < lvlObjectsCount; ++i) {
+ LvlObject *dat = &_resLvlScreenObjectDataTable[i];
+ uint8_t buf[kSizeOfLvlObject];
+ _lvlFile->read(buf, kSizeOfLvlObject);
+ loadLvlScreenObjectData(dat, buf);
+ }
+
+ loadLvlScreenMaskData();
+
+ memset(_resLevelData0x2988SizeTable, 0, sizeof(_resLevelData0x2988SizeTable));
+ memset(_resLevelData0x2988PtrTable, 0, sizeof(_resLevelData0x2988PtrTable));
+
+ _lvlFile->seekAlign(_lvlSpritesOffset);
+ uint8_t spr[kMaxSpriteTypes * 16];
+ assert(_lvlHdr.spritesCount <= kMaxSpriteTypes);
+ _lvlFile->read(spr, _lvlHdr.spritesCount * 16);
+ for (int i = 0; i < _lvlHdr.spritesCount; ++i) {
+ loadLvlSpriteData(i, spr + i * 16);
+ }
+
+ memset(_resLevelData0x2B88SizeTable, 0, sizeof(_resLevelData0x2B88SizeTable));
+
+ if (kPreloadLvlBackgroundData) {
+ _lvlFile->seekAlign(_lvlBackgroundsOffset);
+ uint8_t buf[kMaxScreens * 16];
+ assert(_lvlHdr.screensCount <= kMaxScreens);
+ _lvlFile->read(buf, _lvlHdr.screensCount * 16);
+ for (unsigned int i = 0; i < _lvlHdr.screensCount; ++i) {
+ loadLvlScreenBackgroundData(i, buf + i * 16);
+ }
+ }
+}
+
+void Resource::unloadLvlData() {
+ free(_resLevelData0x470CTable);
+ _resLevelData0x470CTable = 0;
+ for (unsigned int i = 0; i < kMaxScreens; ++i) {
+ unloadLvlScreenBackgroundData(i);
+ }
+ for (unsigned int i = 0; i < kMaxSpriteTypes; ++i) {
+ LvlObjectData *dat = &_resLevelData0x2988Table[i];
+ if (dat->unk0 == 1) {
+ free(dat->framesOffsetsTable);
+ dat->framesOffsetsTable = 0;
+ }
+ free(_resLvlSpriteDataPtrTable[i]);
+ _resLvlSpriteDataPtrTable[i] = 0;
+ }
+}
+
+static uint32_t resFixPointersLevelData0x2B88(const uint8_t *src, uint8_t *ptr, uint8_t *offsetsPtr, LvlBackgroundData *dat, bool isPsx) {
+ const uint8_t *start = src;
+
+ dat->backgroundCount = *src++;
+ dat->currentBackgroundId = *src++;
+ dat->maskCount = *src++;
+ dat->currentMaskId = *src++;
+ dat->shadowCount = *src++;
+ dat->currentShadowId = *src++;
+ dat->soundCount = *src++;
+ dat->currentSoundId = *src++;
+ dat->dataUnk3Count = *src++;
+ dat->unk9 = *src++;
+ dat->dataUnk45Count = *src++;
+ dat->unkB = *src++;
+ dat->backgroundPaletteId = READ_LE_UINT16(src); src += 2;
+ dat->backgroundBitmapId = READ_LE_UINT16(src); src += 2;
+ for (int i = 0; i < 4; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ dat->backgroundPaletteTable[i] = (offs != 0) ? ptr + offs : 0;
+ }
+ for (int i = 0; i < 4; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ dat->backgroundBitmapTable[i] = (offs != 0) ? ptr + offs : 0;
+ }
+ for (int i = 0; i < 4; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ dat->dataUnk0Table[i] = (offs != 0) ? ptr + offs : 0;
+ }
+ for (int i = 0; i < 4; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ dat->backgroundMaskTable[i] = (offs != 0) ? ptr + offs : 0;
+ }
+ for (int i = 0; i < 4; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ dat->backgroundSoundTable[i] = (offs != 0) ? ptr + offs : 0;
+ }
+ for (int i = 0; i < 8; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ dat->backgroundAnimationTable[i] = (offs != 0) ? ptr + offs : 0;
+ }
+ uint32_t offsetsSize = 0;
+ for (int i = 0; i < 8; ++i) {
+ const uint32_t offs = READ_LE_UINT32(src); src += 4;
+ if (offs != 0) {
+ dat->backgroundLvlObjectDataTable[i] = (LvlObjectData *)malloc(sizeof(LvlObjectData));
+ offsetsSize += resFixPointersLevelData0x2988(ptr + offs, offsetsPtr + offsetsSize, dat->backgroundLvlObjectDataTable[i], isPsx);
+ } else {
+ dat->backgroundLvlObjectDataTable[i] = 0;
+ }
+ }
+ assert((src - start) == 160);
+ return offsetsSize;
+}
+
+void Resource::loadLvlScreenBackgroundData(int num, const uint8_t *buf) {
+ assert((unsigned int)num < kMaxScreens);
+
+ static const uint32_t baseOffset = _lvlBackgroundsOffset;
+
+ uint8_t header[3 * sizeof(uint32_t)];
+ if (!buf) {
+ _lvlFile->seekAlign(baseOffset + num * 16);
+ _lvlFile->read(header, sizeof(header));
+ buf = header;
+ }
+ const uint32_t offset = READ_LE_UINT32(&buf[0]);
+ const uint32_t size = READ_LE_UINT32(&buf[4]);
+ if (size == 0) {
+ return;
+ }
+ const uint32_t readSize = READ_LE_UINT32(&buf[8]);
+ assert(readSize <= size);
+ uint8_t *ptr = (uint8_t *)malloc(size);
+ _lvlFile->seek(_isPsx ? _lvlSssOffset + offset : offset, SEEK_SET);
+ _lvlFile->read(ptr, readSize);
+
+ uint8_t hdr[160];
+ _lvlFile->seekAlign(baseOffset + kMaxScreens * 16 + num * 160);
+ _lvlFile->read(hdr, 160);
+ LvlBackgroundData *dat = &_resLvlScreenBackgroundDataTable[num];
+ const uint32_t readOffsetsSize = resFixPointersLevelData0x2B88(hdr, ptr, ptr + readSize, dat, _isPsx);
+ const uint32_t allocatedOffsetsSize = size - readSize;
+ assert(allocatedOffsetsSize == readOffsetsSize);
+
+ _resLvlScreenBackgroundDataPtrTable[num] = ptr;
+ _resLevelData0x2B88SizeTable[num] = size;
+}
+
+void Resource::unloadLvlScreenBackgroundData(int num) {
+ if (_resLevelData0x2B88SizeTable[num] != 0) {
+ free(_resLvlScreenBackgroundDataPtrTable[num]);
+ _resLvlScreenBackgroundDataPtrTable[num] = 0;
+ _resLevelData0x2B88SizeTable[num] = 0;
+
+ LvlBackgroundData *dat = &_resLvlScreenBackgroundDataTable[num];
+ for (int i = 0; i < 4; ++i) {
+ free(dat->backgroundLvlObjectDataTable[i]);
+ }
+ memset(dat, 0, sizeof(LvlBackgroundData));
+ }
+}
+
+bool Resource::isLvlSpriteDataLoaded(int num) const {
+ return _resLevelData0x2988SizeTable[num] != 0;
+}
+
+bool Resource::isLvlBackgroundDataLoaded(int num) const {
+ return _resLevelData0x2B88SizeTable[num] != 0;
+}
+
+void Resource::incLvlSpriteDataRefCounter(LvlObject *ptr) {
+ LvlObjectData *dat = _resLevelData0x2988PtrTable[ptr->spriteNum];
+ assert(dat);
+ ++dat->refCount;
+ ptr->levelData0x2988 = dat;
+}
+
+void Resource::decLvlSpriteDataRefCounter(LvlObject *ptr) {
+ LvlObjectData *dat = _resLevelData0x2988PtrTable[ptr->spriteNum];
+ if (dat) {
+ --dat->refCount;
+ }
+}
+
+const uint8_t *Resource::getLvlSpriteFramePtr(LvlObjectData *dat, int frame, uint16_t *w, uint16_t *h) const {
+ assert(frame < dat->framesCount);
+ const uint8_t *p = dat->framesData;
+ if (dat->unk0 == 1) {
+ p += frame * 6;
+ const uint16_t size = READ_LE_UINT16(p);
+ *w = READ_LE_UINT16(p + 2);
+ *h = READ_LE_UINT16(p + 4);
+ if (size > 8) {
+ return dat->framesData + READ_LE_UINT32(dat->framesOffsetsTable + frame * sizeof(uint32_t));
+ }
+ } else {
+ p += READ_LE_UINT32(dat->framesOffsetsTable + frame * sizeof(uint32_t));
+ const uint16_t size = READ_LE_UINT16(p);
+ *w = READ_LE_UINT16(p + 2);
+ *h = READ_LE_UINT16(p + 4);
+ if (size > 8) {
+ return p + 6;
+ }
+ }
+ return 0;
+}
+
+const uint8_t *Resource::getLvlSpriteCoordPtr(LvlObjectData *dat, int num) const {
+ assert(num < dat->coordsCount);
+ return dat->coordsData + READ_LE_UINT32(dat->coordsOffsetsTable + num * sizeof(uint32_t));
+}
+
+int Resource::findScreenGridIndex(int screenNum) const {
+ for (int i = 0; i < 4; ++i) {
+ if (_screensGrid[_currentScreenResourceNum][i] == screenNum) {
+ return i;
+ }
+ }
+ if (_currentScreenResourceNum == screenNum) {
+ return 4;
+ }
+ return -1;
+}
+
+void Resource::loadSssData(File *fp, const uint32_t baseOffset) {
+
+ assert(fp == _sssFile || fp == _datFile || fp == _lvlFile);
+
+ if (_sssHdr.bufferSize != 0) {
+ unloadSssData();
+ _sssHdr.bufferSize = 0;
+ _sssHdr.infosDataCount = 0;
+ }
+ _sssHdr.version = fp->readUint32();
+ if (_sssHdr.version != 6 && _sssHdr.version != 10 && _sssHdr.version != 12) {
+ warning("Unhandled .sss version %d", _sssHdr.version);
+ closeDat(_fs, _sssFile);
+ return;
+ }
+
+ _sssHdr.bufferSize = fp->readUint32();
+ _sssHdr.preloadPcmCount = fp->readUint32();
+ _sssHdr.preloadInfoCount = fp->readUint32();
+ debug(kDebug_RESOURCE, "_sssHdr.bufferSize %d _sssHdr.preloadPcmCount %d _sssHdr.preloadInfoCount %d", _sssHdr.bufferSize, _sssHdr.preloadPcmCount, _sssHdr.preloadInfoCount);
+ _sssHdr.infosDataCount = fp->readUint32();
+ _sssHdr.filtersDataCount = fp->readUint32();
+ _sssHdr.banksDataCount = fp->readUint32();
+ debug(kDebug_RESOURCE, "_sssHdr.infosDataCount %d _sssHdr.filtersDataCount %d _sssHdr.banksDataCount %d", _sssHdr.infosDataCount, _sssHdr.filtersDataCount, _sssHdr.banksDataCount);
+ _sssHdr.samplesDataCount = fp->readUint32();
+ _sssHdr.codeSize = fp->readUint32();
+ debug(kDebug_RESOURCE, "_sssHdr.samplesDataCount %d _sssHdr.codeSize %d", _sssHdr.samplesDataCount, _sssHdr.codeSize);
+ if (_sssHdr.version == 10 || _sssHdr.version == 12) {
+ _sssHdr.preloadData1Count = fp->readUint32() & 255; // pcm
+ _sssHdr.preloadData2Count = fp->readUint32() & 255; // sprites
+ _sssHdr.preloadData3Count = fp->readUint32() & 255; // mst
+ }
+ _sssHdr.pcmCount = fp->readUint32();
+
+ const int bufferSize = _sssHdr.bufferSize + _sssHdr.filtersDataCount * 52 + _sssHdr.banksDataCount * 56;
+ debug(kDebug_RESOURCE, "bufferSize %d", bufferSize);
+
+ const bool preloadPcm = (fp == _datFile) || (kPreloadSssPcm && !_isPsx);
+
+ // fp->flush();
+ fp->seek(baseOffset + 2048, SEEK_SET); // align to the next sector
+
+ // _sssBuffer1
+ int bytesRead = 0;
+
+ _sssInfosData.allocate(_sssHdr.infosDataCount);
+ for (int i = 0; i < _sssHdr.infosDataCount; ++i) {
+ _sssInfosData[i].sssBankIndex = fp->readUint16(); // index _sssBanksData
+ _sssInfosData[i].sampleIndex = fp->readByte();
+ _sssInfosData[i].targetVolume = fp->readByte();
+ _sssInfosData[i].targetPriority = fp->readByte();
+ _sssInfosData[i].targetPanning = fp->readByte();
+ _sssInfosData[i].concurrencyMask = fp->readByte();
+ fp->skipByte(); // padding to 8 bytes
+ bytesRead += 8;
+ }
+ _sssDefaultsData.allocate(_sssHdr.filtersDataCount);
+ for (int i = 0; i < _sssHdr.filtersDataCount; ++i) {
+ _sssDefaultsData[i].defaultVolume = fp->readByte();
+ _sssDefaultsData[i].defaultPriority = fp->readByte();
+ _sssDefaultsData[i].defaultPanning = fp->readByte();
+ fp->skipByte(); // padding to 4 bytes
+ bytesRead += 4;
+ }
+ _sssBanksData.allocate(_sssHdr.banksDataCount);
+ for (int i = 0; i < _sssHdr.banksDataCount; ++i) {
+ _sssBanksData[i].flags = fp->readByte();
+ _sssBanksData[i].count = fp->readByte();
+ assert(_sssBanksData[i].count <= 4); // matches sizeof(_sssDataUnk6.unk0)
+ _sssBanksData[i].sssFilter = fp->readUint16();
+ _sssBanksData[i].firstSampleIndex = fp->readUint32();
+ debug(kDebug_RESOURCE, "SssBank #%d count %d codeOffset 0x%x", i, _sssBanksData[i].count, _sssBanksData[i].firstSampleIndex);
+ bytesRead += 8;
+ }
+ _sssSamplesData.allocate(_sssHdr.samplesDataCount);
+ for (int i = 0; i < _sssHdr.samplesDataCount; ++i) {
+ _sssSamplesData[i].pcm = fp->readUint16();
+ _sssSamplesData[i].framesCount = fp->readUint16();
+ _sssSamplesData[i].initVolume = fp->readByte();
+ _sssSamplesData[i].unk5 = fp->readByte();
+ _sssSamplesData[i].initPriority = fp->readByte();
+ _sssSamplesData[i].initPanning = fp->readByte();
+ _sssSamplesData[i].codeOffset1 = fp->readUint32();
+ _sssSamplesData[i].codeOffset2 = fp->readUint32();
+ _sssSamplesData[i].codeOffset3 = fp->readUint32();
+ _sssSamplesData[i].codeOffset4 = fp->readUint32();
+ debug(kDebug_RESOURCE, "SssSample #%d pcm %d frames %d", i, _sssSamplesData[i].pcm, _sssSamplesData[i].framesCount);
+ bytesRead += 24;
+ }
+ _sssCodeData = (uint8_t *)malloc(_sssHdr.codeSize);
+ fp->read(_sssCodeData, _sssHdr.codeSize);
+ bytesRead += _sssHdr.codeSize;
+ if (_sssHdr.version == 10 || _sssHdr.version == 12) {
+
+ // _sssPreloadData1
+ fp->seek(_sssHdr.preloadData1Count * 4, SEEK_CUR);
+ bytesRead += _sssHdr.preloadData1Count * 4;
+ // _sssPreloadData2
+ fp->seek(_sssHdr.preloadData2Count * 4, SEEK_CUR);
+ bytesRead += _sssHdr.preloadData2Count * 4;
+ // _sssPreloadData3
+ fp->seek(_sssHdr.preloadData3Count * 4, SEEK_CUR);
+ bytesRead += _sssHdr.preloadData3Count * 4;
+
+ _sssPreload1Table.allocate(_sssHdr.preloadData1Count);
+ const int ptrSize = (_sssHdr.version == 12) ? 2 : 1;
+ for (int i = 0; i < _sssHdr.preloadData1Count; ++i) {
+ const int count = (ptrSize == 2) ? fp->readUint16() : fp->readByte();
+ debug(kDebug_RESOURCE, "sssPreloadData1 #%d count %d", i, count);
+ _sssPreload1Table[i].count = count;
+ _sssPreload1Table[i].ptrSize = ptrSize;
+ const int tableSize = ptrSize * count;
+ _sssPreload1Table[i].ptr = (uint8_t *)malloc(tableSize);
+ fp->read(_sssPreload1Table[i].ptr, tableSize);
+ bytesRead += tableSize + ptrSize;
+ }
+ for (int i = 0; i < _sssHdr.preloadData2Count; ++i) {
+ const int count = fp->readByte();
+ fp->seek(count, SEEK_CUR);
+ debug(kDebug_RESOURCE, "sssPreloadData2 #%d count %d", i, count);
+ bytesRead += count + 1;
+ }
+ for (int i = 0; i < _sssHdr.preloadData3Count; ++i) {
+ const int count = fp->readByte();
+ fp->seek(count, SEEK_CUR);
+ debug(kDebug_RESOURCE, "sssPreloadData3 #%d count %d", i, count);
+ bytesRead += count + 1;
+ }
+ {
+ const int count = fp->readByte();
+ fp->seek(count, SEEK_CUR);
+ bytesRead += count + 1;
+ }
+ // _sssPreloadInfosData = data;
+ }
+ // data += _sssHdr.preloadInfoCount * 8;
+ _sssPreloadInfosData.allocate(_sssHdr.preloadInfoCount);
+ for (int i = 0; i < _sssHdr.preloadInfoCount; ++i) {
+ _sssPreloadInfosData[i].count = fp->readUint32();
+ fp->readUint32();
+ debug(kDebug_RESOURCE, "_sssPreloadInfosData #%d/%d count %d offset 0x%x", i, _sssHdr.preloadInfoCount, _sssPreloadInfosData[i].count);
+ bytesRead += 8;
+ }
+ if (_sssHdr.version == 10 || _sssHdr.version == 12) {
+ static const int kSizeOfPreloadInfoData_V10 = 32;
+ for (int i = 0; i < _sssHdr.preloadInfoCount; ++i) {
+ const int count = _sssPreloadInfosData[i].count;
+ _sssPreloadInfosData[i].data = (SssPreloadInfoData *)calloc(count, sizeof(SssPreloadInfoData));
+ for (int j = 0; j < count; ++j) {
+ SssPreloadInfoData *preloadInfoData = &_sssPreloadInfosData[i].data[j];
+ preloadInfoData->pcmBlockOffset = fp->readUint16();
+ preloadInfoData->pcmBlockSize = fp->readUint16();
+ fp->seek(12, SEEK_CUR);
+ preloadInfoData->screenNum = fp->readByte();
+ const int preload3Index = fp->readByte(); // mst
+ assert(preload3Index < _sssHdr.preloadData3Count);
+ preloadInfoData->preload3Index = preload3Index;
+ const int preload1Index = fp->readByte(); // pcm
+ assert(preload1Index < _sssHdr.preloadData1Count);
+ preloadInfoData->preload1Index = preload1Index;
+ const int preload2Index = fp->readByte(); // lvl
+ assert(preload2Index < _sssHdr.preloadData2Count);
+ preloadInfoData->preload2Index = preload2Index;
+ fp->seek(8, SEEK_CUR);
+ preloadInfoData->unk1C = fp->readUint32();
+ bytesRead += kSizeOfPreloadInfoData_V10;
+ }
+ for (int j = 0; j < count; ++j) {
+ const int len = _sssPreloadInfosData[i].data[j].unk1C * 4;
+ bytesRead += skipBytesAlign(fp, len);
+ }
+ }
+ } else if (_sssHdr.version == 6) {
+ static const int kSizeOfPreloadInfoData_V6 = 68;
+ assert((unsigned int)_sssHdr.preloadInfoCount < kMaxScreens);
+ uint8_t buffer[kMaxScreens * kSizeOfPreloadInfoData_V6];
+
+ for (int i = 0; i < _sssHdr.preloadInfoCount; ++i) {
+ const int count = _sssPreloadInfosData[i].count;
+ _sssPreloadInfosData[i].data = (SssPreloadInfoData *)calloc(count, sizeof(SssPreloadInfoData));
+
+ fp->read(buffer, kSizeOfPreloadInfoData_V6 * count);
+ bytesRead += kSizeOfPreloadInfoData_V6 * count;
+
+ for (int j = 0; j < count; ++j) {
+ SssPreloadInfoData *preloadInfoData = &_sssPreloadInfosData[i].data[j];
+ preloadInfoData->screenNum = buffer[j * kSizeOfPreloadInfoData_V6];
+
+ // 0x2C is pcm preload list
+ preloadInfoData->preload1Data_V6.count = READ_LE_UINT32(buffer + j * kSizeOfPreloadInfoData_V6 + 0x2C);
+ preloadInfoData->preload1Data_V6.ptrSize = 2;
+ const int preload1DataLen = ((preloadInfoData->preload1Data_V6.count * 2) + 3) & ~3;
+ preloadInfoData->preload1Data_V6.ptr = (uint8_t *)malloc(preload1DataLen);
+ bytesRead += fp->read(preloadInfoData->preload1Data_V6.ptr, preload1DataLen);
+
+ static const int8_t offsets[7] = { 0x30, 0x34, 0x04, 0x08, 0x0C, 0x10, 0x14 };
+ static const int8_t lengths[7] = { 1, 1, 2, 2, 1, 1, 1 };
+ for (int k = 0; k < 7; ++k) {
+ const int len = READ_LE_UINT32(buffer + j * kSizeOfPreloadInfoData_V6 + offsets[k]) * lengths[k];
+ bytesRead += skipBytesAlign(fp, len);
+ }
+ }
+ }
+ }
+
+ _sssPcmTable.allocate(_sssHdr.pcmCount);
+ uint32_t sssPcmOffset = baseOffset;
+ for (int i = 0; i < _sssHdr.pcmCount; ++i) {
+ _sssPcmTable[i].ptr = 0; fp->skipUint32();
+ _sssPcmTable[i].offset = fp->readUint32();
+ _sssPcmTable[i].totalSize = fp->readUint32();
+ _sssPcmTable[i].strideSize = fp->readUint32();
+ _sssPcmTable[i].strideCount = fp->readUint16();
+ _sssPcmTable[i].flags = fp->readUint16();
+ debug(kDebug_RESOURCE, "sssPcmTable #%d/%d offset 0x%x size %d", i, _sssHdr.pcmCount, _sssPcmTable[i].offset, _sssPcmTable[i].totalSize);
+ if (_sssPcmTable[i].totalSize != 0) {
+ assert((_sssPcmTable[i].totalSize % _sssPcmTable[i].strideSize) == 0);
+ assert(_sssPcmTable[i].totalSize == _sssPcmTable[i].strideSize * _sssPcmTable[i].strideCount);
+ if (sssPcmOffset != 0) {
+ assert(fp != _datFile || _sssPcmTable[i].offset == 0x2800); // .dat
+ _sssPcmTable[i].offset += sssPcmOffset;
+ sssPcmOffset += _sssPcmTable[i].totalSize;
+ }
+ if (_isPsx) {
+ assert(_sssPcmTable[i].strideSize == 512);
+ _sssPcmTable[i].pcmSize = (_sssPcmTable[i].totalSize / 16) * 56 * sizeof(int16_t);
+ } else {
+ assert(_sssPcmTable[i].strideSize == 2276 || _sssPcmTable[i].strideSize == 4040);
+ _sssPcmTable[i].pcmSize = (_sssPcmTable[i].totalSize - 256 * sizeof(int16_t) * _sssPcmTable[i].strideCount) * sizeof(int16_t);
+ }
+ } else {
+ _sssPcmTable[i].pcmSize = 0;
+ }
+ bytesRead += 20;
+ }
+
+ // allocate structure but skip read as table is cleared and initialized in clearSoundObjects()
+ static const int kSizeOfSssFilter = 52;
+ fp->seek(_sssHdr.filtersDataCount * kSizeOfSssFilter, SEEK_CUR);
+ _sssFilters.allocate(_sssHdr.filtersDataCount);
+ bytesRead += _sssHdr.filtersDataCount * kSizeOfSssFilter;
+
+ _sssDataUnk6.allocate(_sssHdr.banksDataCount);
+ for (int i = 0; i < _sssHdr.banksDataCount; ++i) {
+ _sssDataUnk6[i].unk0[0] = fp->readUint32();
+ _sssDataUnk6[i].unk0[1] = fp->readUint32();
+ _sssDataUnk6[i].unk0[2] = fp->readUint32();
+ _sssDataUnk6[i].unk0[3] = fp->readUint32();
+ _sssDataUnk6[i].mask = fp->readUint32();
+ bytesRead += 20;
+ debug(kDebug_RESOURCE, "sssDataUnk6 #%d/%d unk10 0x%x", i, _sssHdr.banksDataCount);
+ }
+
+ const int lutSize = _sssHdr.banksDataCount * sizeof(uint32_t);
+ // allocate structures but skip read as tables are initialized in clearSoundObjects()
+ fp->seek(lutSize * 3 * 3, SEEK_CUR);
+ bytesRead += lutSize * 3 * 3;
+ for (int i = 0; i < 3; ++i) {
+ _sssGroup1[i] = (uint32_t *)malloc(lutSize);
+ _sssGroup2[i] = (uint32_t *)malloc(lutSize);
+ _sssGroup3[i] = (uint32_t *)malloc(lutSize);
+ }
+ // _sssPreloadedPcmTotalSize = 0;
+
+ if (kCheckSssBytecode) {
+ checkSssCode(_sssCodeData, _sssHdr.codeSize);
+ }
+ for (int i = 0; i < _sssHdr.banksDataCount; ++i) {
+ if (_sssBanksData[i].count != 0) {
+ // const int num = _sssBanksData[i].firstSampleIndex;
+ // _sssBanksData[i].codeOffset = &_sssSamplesData[num];
+ } else {
+ _sssBanksData[i].firstSampleIndex = kNone;
+ }
+ }
+ // fixup _sssSamplesData[i].codeOffset
+ debug(kDebug_RESOURCE, "bufferSize %d bytesRead %d", bufferSize, bytesRead);
+ if (bufferSize != bytesRead) {
+ error("Unexpected number of bytes read %d (%d)", bytesRead, bufferSize);
+ }
+ if (preloadPcm) {
+ fp->seek(_sssPcmTable[0].offset, SEEK_SET);
+ for (int i = 0; i < _sssHdr.pcmCount; ++i) {
+ if (_sssPcmTable[i].pcmSize != 0) {
+ loadSssPcm(fp, &_sssPcmTable[i]);
+ }
+ }
+ }
+ for (int i = 0; i < _sssHdr.banksDataCount; ++i) {
+ uint32_t mask = 1;
+ _sssDataUnk6[i].mask = 0;
+ const SssSample *codeOffset = &_sssSamplesData[_sssBanksData[i].firstSampleIndex];
+ uint32_t *ptr = _sssDataUnk6[i].unk0;
+ for (int j = 0; j < _sssBanksData[i].count; ++j) {
+ for (int k = 0; k < codeOffset->unk5; ++k) {
+ if (mask == 0) {
+ break;
+ }
+ *ptr |= mask;
+ mask <<= 1;
+ }
+ ++codeOffset;
+ _sssDataUnk6[i].mask |= *ptr;
+ ++ptr;
+ }
+ }
+ resetSssFilters();
+ // clearSoundObjects();
+ clearSssGroup3();
+}
+
+void Resource::unloadSssData() {
+ for (unsigned int i = 0; i < _sssPreload1Table.count; ++i) {
+ free(_sssPreload1Table[i].ptr);
+ _sssPreload1Table[i].ptr = 0;
+ }
+ for (unsigned int i = 0; i < _sssPreloadInfosData.count; ++i) {
+ if (_sssHdr.version == 6) {
+ for (unsigned int j = 0; j < _sssPreloadInfosData[i].count; ++j) {
+ SssPreloadInfoData *preloadInfoData = &_sssPreloadInfosData[i].data[j];
+ free(preloadInfoData->preload1Data_V6.ptr);
+ preloadInfoData->preload1Data_V6.ptr = 0;
+ }
+ }
+ free(_sssPreloadInfosData[i].data);
+ _sssPreloadInfosData[i].data = 0;
+ }
+ for (unsigned int i = 0; i < _sssPcmTable.count; ++i) {
+ free(_sssPcmTable[i].ptr);
+ _sssPcmTable[i].ptr = 0;
+ }
+ for (int i = 0; i < 3; ++i) {
+ free(_sssGroup1[i]);
+ _sssGroup1[i] = 0;
+ free(_sssGroup2[i]);
+ _sssGroup2[i] = 0;
+ free(_sssGroup3[i]);
+ _sssGroup3[i] = 0;
+ }
+ free(_sssCodeData);
+ _sssCodeData = 0;
+}
+
+void Resource::checkSssCode(const uint8_t *buf, int size) const {
+ static const uint8_t opcodesLength[] = {
+ 4, 0, 4, 0, 4, 12, 8, 0, 16, 4, 4, 4, 4, 8, 12, 0, 4, 8, 8, 4, 4, 4, 8, 4, 8, 4, 8, 16, 8, 4
+ };
+ int offset = 0;
+ while (offset < size) {
+ const int len = opcodesLength[buf[offset]];
+ if (len == 0) {
+ warning("Invalid .sss opcode %d", buf[offset]);
+ break;
+ }
+ offset += len;
+ }
+ assert(offset == size);
+}
+
+static int8_t sext8(uint8_t x, int bits) {
+ const int shift = 8 - bits;
+ return ((int8_t)(x << shift)) >> shift;
+}
+
+static int _pcmL1, _pcmL0;
+
+static void decodeSssSpuAdpcmUnit(const uint8_t *src, int16_t *dst) { // src: 16bytes, dst: 112bytes
+ static const int16_t K0_1024[] = { 0, 960, 1840, 1568, 1952 };
+ static const int16_t K1_1024[] = { 0, 0, -832, -880, -960 };
+ const uint8_t param = *src++;
+ const int shift = 12 - (param & 15);
+ assert(shift >= 0);
+ const int filter = param >> 4;
+ assert(filter < 5);
+ const int flag = *src++;
+ assert(flag < 7);
+ for (int i = 0; i < 14; ++i) {
+ const uint8_t b = *src++;
+ const int t1 = sext8(b & 15, 4);
+ const int s1 = (t1 << shift) + ((_pcmL0 * K0_1024[filter] + _pcmL1 * K1_1024[filter] + 512) >> 10);
+ _pcmL1 = _pcmL0;
+ _pcmL0 = s1;
+ dst[0] = (_pcmL1 + _pcmL0) >> 1;
+ dst[1] = CLIP(_pcmL0, -32768, 32767);
+ dst += 2;
+ const int t2 = sext8(b >> 4, 4);
+ const int s2 = (t2 << shift) + ((_pcmL0 * K0_1024[filter] + _pcmL1 * K1_1024[filter] + 512) >> 10);
+ _pcmL1 = _pcmL0;
+ _pcmL0 = s2;
+ dst[0] = (_pcmL1 + _pcmL0) >> 1;
+ dst[1] = CLIP(_pcmL0, -32768, 32767);
+ dst += 2;
+ }
+}
+
+void Resource::loadSssPcm(File *fp, SssPcm *pcm) {
+ assert(!pcm->ptr);
+ const uint32_t decompressedSize = pcm->pcmSize;
+ debug(kDebug_SOUND, "Loading PCM %p decompressedSize %d", pcm, decompressedSize);
+ int16_t *p = (int16_t *)malloc(decompressedSize);
+ if (!p) {
+ warning("Failed to allocate %d bytes for PCM", decompressedSize);
+ return;
+ }
+ pcm->ptr = p;
+ if (_isPsx) {
+ assert(pcm->strideSize == 512);
+ uint8_t strideBuffer[512];
+ for (int i = 0; i < pcm->strideCount; ++i) {
+ fp->read(strideBuffer, sizeof(strideBuffer));
+ _pcmL1 = _pcmL0 = 0;
+ for (unsigned int j = 0; j < 512; j += 16) {
+ decodeSssSpuAdpcmUnit(strideBuffer + j, p);
+ p += 56;
+ }
+ }
+ } else {
+ if (fp != _datFile) {
+ fp->seek(pcm->offset, SEEK_SET);
+ }
+ const uint32_t strideSize = pcm->strideSize;
+ assert(strideSize == 2276 || strideSize == 4040);
+ uint8_t strideBuffer[4040]; // maximum stride size
+ for (int i = 0; i < pcm->strideCount; ++i) {
+ fp->read(strideBuffer, strideSize);
+ for (unsigned int j = 256 * sizeof(int16_t); j < strideSize; ++j) {
+ *p++ = READ_LE_UINT16(strideBuffer + strideBuffer[j] * sizeof(int16_t));
+ }
+ }
+ }
+ assert((p - pcm->ptr) * sizeof(int16_t) == decompressedSize);
+}
+
+void Resource::clearSssGroup3() {
+ if (_sssHdr.banksDataCount != 0) {
+ for (int i = 0; i < 3; ++i) {
+ memset(_sssGroup3[i], 0, _sssHdr.banksDataCount * sizeof(uint32_t));
+ }
+ }
+}
+
+void Resource::resetSssFilters() {
+ for (int i = 0; i < _sssHdr.filtersDataCount; ++i) {
+ memset(&_sssFilters[i], 0, sizeof(SssFilter));
+ const int volume = _sssDefaultsData[i].defaultVolume;
+ _sssFilters[i].volumeCurrent = volume << 16;
+ _sssFilters[i].volume = volume;
+ const int panning = _sssDefaultsData[i].defaultPanning;
+ _sssFilters[i].panningCurrent = panning << 16;
+ _sssFilters[i].panning = panning;
+ const int priority = _sssDefaultsData[i].defaultPriority;
+ _sssFilters[i].priorityCurrent = priority;
+ _sssFilters[i].priority = priority;
+ }
+}
+
+void Resource::preloadSssPcmList(const SssPreloadInfoData *preloadInfoData) {
+ File *fp = _sssFile;
+ if (_isPsx) {
+ const uint16_t offset = preloadInfoData->pcmBlockOffset;
+ if (offset == 0xFFFF) {
+ return;
+ }
+ _lvlFile->seek(offset * 2048, SEEK_SET);
+ fp = _lvlFile;
+ } else if (kPreloadSssPcm) {
+ return;
+ }
+ const SssPreloadList *preloadList = (_sssHdr.version == 6) ? &preloadInfoData->preload1Data_V6 : &_sssPreload1Table[preloadInfoData->preload1Index];
+ for (int i = 0; i < preloadList->count; ++i) {
+ const int num = (preloadList->ptrSize == 2) ? READ_LE_UINT16(preloadList->ptr + i * 2) : preloadList->ptr[i];
+ if (_sssPcmTable[num].pcmSize != 0) {
+ if (!_sssPcmTable[num].ptr) {
+ loadSssPcm(fp, &_sssPcmTable[num]);
+ } else if (_isPsx) {
+ fp->seek(_sssPcmTable[num].strideCount * 512, SEEK_CUR);
+ }
+ }
+ }
+}
+
+void Resource::loadMstData(File *fp) {
+ assert(fp == _mstFile);
+
+ if (_mstHdr.dataSize != 0) {
+ unloadMstData();
+ _mstHdr.dataSize = 0;
+ }
+
+ _mstHdr.version = fp->readUint32();
+ if (_mstHdr.version != 160) {
+ warning("Unhandled .mst version %d", _mstHdr.version);
+ closeDat(_fs, _mstFile);
+ return;
+ }
+
+ _mstHdr.dataSize = fp->readUint32();
+ _mstHdr.walkBoxDataCount = fp->readUint32();
+ _mstHdr.walkCodeDataCount = fp->readUint32();
+ _mstHdr.movingBoundsIndexDataCount = fp->readUint32();
+ _mstHdr.levelCheckpointCodeDataCount = fp->readUint32();
+ _mstHdr.screenAreaDataCount = fp->readUint32();
+ _mstHdr.screenAreaIndexDataCount = fp->readUint32();
+ _mstHdr.behaviorIndexDataCount = fp->readUint32();
+ _mstHdr.monsterActionIndexDataCount = fp->readUint32();
+ _mstHdr.walkPathDataCount = fp->readUint32();
+ _mstHdr.infoMonster2Count = fp->readUint32();
+ _mstHdr.behaviorDataCount = fp->readUint32();
+ _mstHdr.attackBoxDataCount = fp->readUint32();
+ _mstHdr.monsterActionDataCount = fp->readUint32();
+ _mstHdr.infoMonster1Count = fp->readUint32();
+ _mstHdr.movingBoundsDataCount = fp->readUint32();
+ _mstHdr.shootDataCount = fp->readUint32();
+ _mstHdr.shootIndexDataCount = fp->readUint32();
+ _mstHdr.actionDirectionDataCount = fp->readUint32();
+ _mstHdr.op223DataCount = fp->readUint32();
+ _mstHdr.op226DataCount = fp->readUint32();
+ _mstHdr.op227DataCount = fp->readUint32();
+ _mstHdr.op234DataCount = fp->readUint32();
+ _mstHdr.op2DataCount = fp->readUint32();
+ _mstHdr.op197DataCount = fp->readUint32();
+ _mstHdr.op211DataCount = fp->readUint32();
+ _mstHdr.op240DataCount = fp->readUint32();
+ _mstHdr.unk0x70 = fp->readUint32();
+ _mstHdr.unk0x74 = fp->readUint32();
+ _mstHdr.op204DataCount = fp->readUint32();
+ _mstHdr.codeSize = fp->readUint32();
+ _mstHdr.screensCount = fp->readUint32();
+ debug(kDebug_RESOURCE, "_mstHdr.version %d _mstHdr.codeSize %d", _mstHdr.version, _mstHdr.codeSize);
+
+ fp->seek(2048, SEEK_SET); // align to the next sector
+
+ int bytesRead = 0;
+
+ _mstPointOffsets.allocate(_mstHdr.screensCount);
+ for (int i = 0; i < _mstHdr.screensCount; ++i) {
+ _mstPointOffsets[i].xOffset = fp->readUint32();
+ _mstPointOffsets[i].yOffset = fp->readUint32();
+ bytesRead += 8;
+ }
+
+ _mstWalkBoxData.allocate(_mstHdr.walkBoxDataCount);
+ for (int i = 0; i < _mstHdr.walkBoxDataCount; ++i) {
+ _mstWalkBoxData[i].right = fp->readUint32();
+ _mstWalkBoxData[i].left = fp->readUint32();
+ _mstWalkBoxData[i].bottom = fp->readUint32();
+ _mstWalkBoxData[i].top = fp->readUint32();
+ _mstWalkBoxData[i].flags[0] = fp->readByte();
+ _mstWalkBoxData[i].flags[1] = fp->readByte();
+ _mstWalkBoxData[i].flags[2] = fp->readByte();
+ _mstWalkBoxData[i].flags[3] = fp->readByte();
+ bytesRead += 20;
+ }
+
+ _mstWalkCodeData.allocate(_mstHdr.walkCodeDataCount);
+ for (int i = 0; i < _mstHdr.walkCodeDataCount; ++i) {
+ fp->skipUint32();
+ _mstWalkCodeData[i].codeDataCount = fp->readUint32();
+ _mstWalkCodeData[i].codeData = (uint32_t *)malloc(_mstWalkCodeData[i].codeDataCount * sizeof(uint32_t));
+ fp->skipUint32();
+ _mstWalkCodeData[i].indexDataCount = fp->readUint32();
+ if (_mstWalkCodeData[i].indexDataCount != 0) {
+ _mstWalkCodeData[i].indexData = (uint8_t *)malloc(_mstWalkCodeData[i].indexDataCount);
+ } else {
+ _mstWalkCodeData[i].indexData = 0;
+ }
+ bytesRead += 16;
+ }
+ for (int i = 0; i < _mstHdr.walkCodeDataCount; ++i) {
+ for (uint32_t j = 0; j < _mstWalkCodeData[i].codeDataCount; ++j) {
+ _mstWalkCodeData[i].codeData[j] = fp->readUint32();
+ bytesRead += 4;
+ }
+ if (_mstWalkCodeData[i].indexDataCount != 0) {
+ bytesRead += readBytesAlign(fp, _mstWalkCodeData[i].indexData, _mstWalkCodeData[i].indexDataCount);
+ }
+ }
+
+ _mstMovingBoundsIndexData.allocate(_mstHdr.movingBoundsIndexDataCount);
+ for (int i = 0; i < _mstHdr.movingBoundsIndexDataCount; ++i) {
+ _mstMovingBoundsIndexData[i].indexUnk49 = fp->readUint32();
+ _mstMovingBoundsIndexData[i].unk4 = fp->readUint32();
+ _mstMovingBoundsIndexData[i].unk8 = fp->readUint32();
+ bytesRead += 12;
+ }
+
+ _mstTickDelay = fp->readUint32();
+ _mstTickCodeData = fp->readUint32();
+ bytesRead += 8;
+
+ _mstLevelCheckpointCodeData.allocate(_mstHdr.levelCheckpointCodeDataCount);
+ for (int i = 0; i < _mstHdr.levelCheckpointCodeDataCount; ++i) {
+ _mstLevelCheckpointCodeData[i] = fp->readUint32();
+ bytesRead += 4;
+ }
+
+ _mstScreenAreaData.allocate(_mstHdr.screenAreaDataCount);
+ for (int i = 0; i < _mstHdr.screenAreaDataCount; ++i) {
+ MstScreenArea *msac = &_mstScreenAreaData[i];
+ msac->x1 = fp->readUint32();
+ msac->x2 = fp->readUint32();
+ msac->y1 = fp->readUint32();
+ msac->y2 = fp->readUint32();
+ msac->nextByPos = fp->readUint32();
+ msac->prev = fp->readUint32();
+ msac->nextByValue = fp->readUint32();
+ msac->unk0x1C = fp->readByte();
+ msac->unk0x1D = fp->readByte();
+ msac->unk0x1E = fp->readUint16();
+ msac->codeData = fp->readUint32();
+ bytesRead += 36;
+ }
+
+ _mstScreenAreaByValueIndexData.allocate(_mstHdr.screenAreaIndexDataCount);
+ for (int i = 0; i < _mstHdr.screenAreaIndexDataCount; ++i) {
+ _mstScreenAreaByValueIndexData[i] = fp->readUint32();
+ bytesRead += 4;
+ }
+
+ _mstScreenAreaByPosIndexData.allocate(_mstHdr.screensCount);
+ for (int i = 0; i < _mstHdr.screensCount; ++i) {
+ _mstScreenAreaByPosIndexData[i] = fp->readUint32();
+ bytesRead += 4;
+ }
+
+ _mstUnk41.allocate(_mstHdr.screensCount);
+ for (int i = 0; i < _mstHdr.screensCount; ++i) {
+ _mstUnk41[i] = fp->readUint32();
+ bytesRead += 4;
+ }
+
+ _mstBehaviorIndexData.allocate(_mstHdr.behaviorIndexDataCount);
+ for (int i = 0; i < _mstHdr.behaviorIndexDataCount; ++i) {
+ fp->skipUint32();
+ _mstBehaviorIndexData[i].count1 = fp->readUint32();
+ _mstBehaviorIndexData[i].behavior = (uint32_t *)malloc(_mstBehaviorIndexData[i].count1 * sizeof(uint32_t));
+ fp->skipUint32();
+ _mstBehaviorIndexData[i].dataCount = fp->readUint32();
+ if (_mstBehaviorIndexData[i].dataCount != 0) {
+ _mstBehaviorIndexData[i].data = (uint8_t *)malloc(_mstBehaviorIndexData[i].dataCount);
+ } else {
+ _mstBehaviorIndexData[i].data = 0;
+ }
+ bytesRead += 16;
+ }
+ for (int i = 0; i < _mstHdr.behaviorIndexDataCount; ++i) {
+ for (uint32_t j = 0; j < _mstBehaviorIndexData[i].count1; ++j) {
+ _mstBehaviorIndexData[i].behavior[j] = fp->readUint32();
+ bytesRead += 4;
+ }
+ if (_mstBehaviorIndexData[i].dataCount != 0) {
+ bytesRead += readBytesAlign(fp, _mstBehaviorIndexData[i].data, _mstBehaviorIndexData[i].dataCount);
+ }
+ }
+
+ _mstMonsterActionIndexData.allocate(_mstHdr.monsterActionIndexDataCount);
+ for (int i = 0; i < _mstHdr.monsterActionIndexDataCount; ++i) {
+ fp->skipUint32();
+ _mstMonsterActionIndexData[i].count1 = fp->readUint32();
+ _mstMonsterActionIndexData[i].indexUnk48 = (uint32_t *)malloc(_mstMonsterActionIndexData[i].count1 * sizeof(uint32_t));
+ fp->skipUint32();
+ _mstMonsterActionIndexData[i].dataCount = fp->readUint32();
+ if (_mstMonsterActionIndexData[i].dataCount != 0) {
+ _mstMonsterActionIndexData[i].data = (uint8_t *)malloc(_mstMonsterActionIndexData[i].dataCount);
+ } else {
+ _mstMonsterActionIndexData[i].data = 0;
+ }
+ bytesRead += 16;
+ }
+ for (int i = 0; i < _mstHdr.monsterActionIndexDataCount; ++i) {
+ for (uint32_t j = 0; j < _mstMonsterActionIndexData[i].count1; ++j) {
+ _mstMonsterActionIndexData[i].indexUnk48[j] = fp->readUint32();
+ bytesRead += 4;
+ }
+ if (_mstMonsterActionIndexData[i].dataCount != 0) {
+ bytesRead += readBytesAlign(fp, _mstMonsterActionIndexData[i].data, _mstMonsterActionIndexData[i].dataCount);
+ }
+ }
+
+ _mstWalkPathData.allocate(_mstHdr.walkPathDataCount);
+ for (int i = 0; i < _mstHdr.walkPathDataCount; ++i) {
+ fp->skipUint32();
+ fp->skipUint32();
+ _mstWalkPathData[i].mask = fp->readUint32();
+ _mstWalkPathData[i].count = fp->readUint32();
+ bytesRead += 16;
+ }
+ for (int i = 0; i < _mstHdr.walkPathDataCount; ++i) {
+ const int count = _mstWalkPathData[i].count;
+ _mstWalkPathData[i].data = (MstWalkNode *)malloc(sizeof(MstWalkNode) * count);
+ for (int j = 0; j < count; ++j) {
+ uint8_t data[104];
+ fp->read(data, sizeof(data));
+ bytesRead += 104;
+ _mstWalkPathData[i].data[j].x1 = READ_LE_UINT32(data);
+ _mstWalkPathData[i].data[j].x2 = READ_LE_UINT32(data + 4);
+ _mstWalkPathData[i].data[j].y1 = READ_LE_UINT32(data + 8);
+ _mstWalkPathData[i].data[j].y2 = READ_LE_UINT32(data + 12);
+ _mstWalkPathData[i].data[j].walkBox = READ_LE_UINT32(data + 16); // sizeof == 20
+ _mstWalkPathData[i].data[j].walkCodeStage1 = READ_LE_UINT32(data + 20); // sizeof == 16
+ _mstWalkPathData[i].data[j].walkCodeStage2 = READ_LE_UINT32(data + 24); // sizeof == 16
+ _mstWalkPathData[i].data[j].movingBoundsIndex1 = READ_LE_UINT32(data + 28); // sizeof == 12
+ _mstWalkPathData[i].data[j].movingBoundsIndex2 = READ_LE_UINT32(data + 32); // sizeof == 12
+ _mstWalkPathData[i].data[j].walkCodeReset[0] = READ_LE_UINT32(data + 36); // sizeof == 16
+ _mstWalkPathData[i].data[j].walkCodeReset[1] = READ_LE_UINT32(data + 40); // sizeof == 16
+ for (int k = 0; k < 4; ++k) {
+ _mstWalkPathData[i].data[j].coords[k][0] = READ_LE_UINT32(data + 0x2C + k * 8);
+ _mstWalkPathData[i].data[j].coords[k][1] = READ_LE_UINT32(data + 0x30 + k * 8);
+ }
+ _mstWalkPathData[i].data[j].neighborWalkNode[0] = READ_LE_UINT32(data + 76); // sizeof == 104
+ _mstWalkPathData[i].data[j].neighborWalkNode[1] = READ_LE_UINT32(data + 80); // sizeof == 104
+ _mstWalkPathData[i].data[j].neighborWalkNode[2] = READ_LE_UINT32(data + 84); // sizeof == 104
+ _mstWalkPathData[i].data[j].neighborWalkNode[3] = READ_LE_UINT32(data + 88); // sizeof == 104
+ _mstWalkPathData[i].data[j].nextWalkNode = READ_LE_UINT32(data + 92); // sizeof == 104
+ if (count != 0) {
+ _mstWalkPathData[i].data[j].unk60[0] = (uint8_t *)malloc(count);
+ _mstWalkPathData[i].data[j].unk60[1] = (uint8_t *)malloc(count);
+ } else {
+ _mstWalkPathData[i].data[j].unk60[0] = 0;
+ _mstWalkPathData[i].data[j].unk60[1] = 0;
+ }
+ }
+ _mstWalkPathData[i].walkNodeData = (uint32_t *)malloc(_mstHdr.screensCount * sizeof(uint32_t));
+ for (int j = 0; j < _mstHdr.screensCount; ++j) {
+ _mstWalkPathData[i].walkNodeData[j] = fp->readUint32();
+ bytesRead += 4;
+ }
+ for (int j = 0; j < count; ++j) {
+ for (int k = 0; k < 2; ++k) {
+ bytesRead += readBytesAlign(fp, _mstWalkPathData[i].data[j].unk60[k], count);
+ }
+ }
+ }
+
+ _mstInfoMonster2Data.allocate(_mstHdr.infoMonster2Count);
+ for (int i = 0; i < _mstHdr.infoMonster2Count; ++i) {
+ _mstInfoMonster2Data[i].type = fp->readByte();
+ _mstInfoMonster2Data[i].shootMask = fp->readByte();
+ _mstInfoMonster2Data[i].anim = fp->readUint16();
+ _mstInfoMonster2Data[i].codeData = fp->readUint32();
+ _mstInfoMonster2Data[i].codeData2 = fp->readUint32();
+ bytesRead += 12;
+ }
+
+ _mstBehaviorData.allocate(_mstHdr.behaviorDataCount);
+ for (int i = 0; i < _mstHdr.behaviorDataCount; ++i) {
+ fp->skipUint32();
+ _mstBehaviorData[i].count = fp->readUint32();
+ bytesRead += 8;
+ }
+ for (int i = 0; i < _mstHdr.behaviorDataCount; ++i) {
+ _mstBehaviorData[i].data = (MstBehaviorState *)malloc(_mstBehaviorData[i].count * sizeof(MstBehaviorState));
+ for (uint32_t j = 0; j < _mstBehaviorData[i].count; ++j) {
+ uint8_t data[44];
+ fp->read(data, sizeof(data));
+ bytesRead += 44;
+ _mstBehaviorData[i].data[j].indexMonsterInfo = READ_LE_UINT32(data);
+ _mstBehaviorData[i].data[j].anim = READ_LE_UINT16(data + 0x04);
+ _mstBehaviorData[i].data[j].unk6 = READ_LE_UINT16(data + 0x06);
+ _mstBehaviorData[i].data[j].unk8 = READ_LE_UINT32(data + 0x08);
+ _mstBehaviorData[i].data[j].energy = READ_LE_UINT32(data + 0x0C);
+ _mstBehaviorData[i].data[j].unk10 = READ_LE_UINT32(data + 0x10);
+ _mstBehaviorData[i].data[j].count = READ_LE_UINT32(data + 0x14);
+ _mstBehaviorData[i].data[j].unk18 = READ_LE_UINT32(data + 0x18);
+ _mstBehaviorData[i].data[j].indexUnk51 = READ_LE_UINT32(data + 0x1C);
+ _mstBehaviorData[i].data[j].walkPath = READ_LE_UINT32(data + 0x20);
+ _mstBehaviorData[i].data[j].attackBox = READ_LE_UINT32(data + 0x24);
+ _mstBehaviorData[i].data[j].codeData = READ_LE_UINT32(data + 0x28);
+ }
+ }
+
+ _mstAttackBoxData.allocate(_mstHdr.attackBoxDataCount);
+ for (int i = 0; i < _mstHdr.attackBoxDataCount; ++i) {
+ fp->skipUint32();
+ _mstAttackBoxData[i].count = fp->readUint32();
+ bytesRead += 8;
+ }
+ for (int i = 0; i < _mstHdr.attackBoxDataCount; ++i) {
+ _mstAttackBoxData[i].data = (uint8_t *)malloc(_mstAttackBoxData[i].count * 20);
+ fp->read(_mstAttackBoxData[i].data, _mstAttackBoxData[i].count * 20);
+ bytesRead += _mstAttackBoxData[i].count * 20;
+ }
+
+ _mstMonsterActionData.allocate(_mstHdr.monsterActionDataCount);
+ for (int i = 0; i < _mstHdr.monsterActionDataCount; ++i) {
+ MstMonsterAction *m = &_mstMonsterActionData[i];
+ m->unk0 = fp->readUint16();
+ m->unk2 = fp->readUint16();
+ m->unk4 = fp->readByte();
+ m->direction = fp->readByte();
+ m->unk6 = fp->readByte();
+ m->unk7 = fp->readByte();
+ m->codeData = fp->readUint32();
+ m->area = 0; fp->skipUint32();
+ m->areaCount = fp->readUint32();
+ fp->seek(16, SEEK_CUR);
+ m->count[0] = fp->readUint32();
+ m->count[1] = fp->readUint32();
+ bytesRead += 44;
+ }
+ for (int i = 0; i < _mstHdr.monsterActionDataCount; ++i) {
+ MstMonsterAction *m = &_mstMonsterActionData[i];
+ for (int j = 0; j < 2; ++j) {
+ const int count = m->count[j];
+ if (count != 0) {
+ m->data1[j] = (uint32_t *)malloc(count * sizeof(uint32_t));
+ for (int k = 0; k < count; ++k) {
+ m->data1[j][k] = fp->readUint32();
+ }
+ bytesRead += count * 4;
+ m->data2[j] = (uint32_t *)malloc(count * sizeof(uint32_t));
+ for (int k = 0; k < count; ++k) {
+ m->data2[j][k] = fp->readUint32();
+ }
+ bytesRead += count * 4;
+ } else {
+ m->data1[j] = 0;
+ m->data2[j] = 0;
+ }
+ }
+ MstMonsterArea *m12 = (MstMonsterArea *)malloc(m->areaCount * sizeof(MstMonsterArea));
+ for (int j = 0; j < m->areaCount; ++j) {
+ m12[j].unk0 = fp->readByte();
+ fp->skipByte();
+ fp->skipByte();
+ fp->skipByte();
+ m12[j].data = 0; fp->skipUint32();
+ m12[j].count = fp->readUint32();
+ bytesRead += 12;
+ }
+ for (int j = 0; j < m->areaCount; ++j) {
+ m12[j].data = (MstMonsterAreaAction *)malloc(m12[j].count * sizeof(MstMonsterAreaAction));
+ for (uint32_t k = 0; k < m12[j].count; ++k) {
+ uint8_t data[28];
+ fp->read(data, sizeof(data));
+ m12[j].data[k].unk0 = READ_LE_UINT32(data);
+ m12[j].data[k].indexUnk51 = READ_LE_UINT32(data + 0x4);
+ m12[j].data[k].xPos = READ_LE_UINT32(data + 0x8);
+ m12[j].data[k].yPos = READ_LE_UINT32(data + 0xC);
+ m12[j].data[k].codeData = READ_LE_UINT32(data + 0x10);
+ m12[j].data[k].codeData2 = READ_LE_UINT32(data + 0x14);
+ m12[j].data[k].unk18 = data[0x18];
+ m12[j].data[k].direction = data[0x19];
+ m12[j].data[k].screenNum = data[0x1A];
+ m12[j].data[k].monster1Index = data[0x1B];
+ bytesRead += 28;
+ }
+ }
+ m->area = m12;
+ }
+
+ const int mapDataSize = _mstHdr.infoMonster1Count * kMonsterInfoDataSize;
+ _mstMonsterInfos = (uint8_t *)malloc(mapDataSize);
+ fp->read(_mstMonsterInfos, mapDataSize);
+ bytesRead += mapDataSize;
+
+ _mstMovingBoundsData.allocate(_mstHdr.movingBoundsDataCount);
+ for (int i = 0; i < _mstHdr.movingBoundsDataCount; ++i) {
+ _mstMovingBoundsData[i].indexMonsterInfo = fp->readUint32();
+ fp->skipUint32();
+ _mstMovingBoundsData[i].count1 = fp->readUint32();
+ fp->skipUint32();
+ _mstMovingBoundsData[i].indexDataCount = fp->readUint32();
+ _mstMovingBoundsData[i].unk14 = fp->readByte();
+ _mstMovingBoundsData[i].unk15 = fp->readByte();
+ _mstMovingBoundsData[i].unk16 = fp->readByte();
+ _mstMovingBoundsData[i].unk17 = fp->readByte();
+ bytesRead += 24;
+ }
+ for (int i = 0; i < _mstHdr.movingBoundsDataCount; ++i) {
+ _mstMovingBoundsData[i].data1 = (MstMovingBoundsUnk1 *)malloc(_mstMovingBoundsData[i].count1 * sizeof(MstMovingBoundsUnk1));
+ const int start = _mstMovingBoundsData[i].indexMonsterInfo;
+ assert(start < _mstHdr.infoMonster1Count);
+ for (uint32_t j = 0; j < _mstMovingBoundsData[i].count1; ++j) {
+ fp->skipUint32();
+ _mstMovingBoundsData[i].data1[j].unk4 = fp->readUint32();
+ _mstMovingBoundsData[i].data1[j].unk8 = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unk9 = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unkA = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unkB = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unkC = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unkD = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unkE = fp->readByte();
+ _mstMovingBoundsData[i].data1[j].unkF = fp->readByte();
+ const uint32_t num = _mstMovingBoundsData[i].data1[j].unk4;
+ assert(num < 32);
+ _mstMovingBoundsData[i].data1[j].offsetMonsterInfo = start * kMonsterInfoDataSize + num * 28;
+ bytesRead += 16;
+ }
+ if (_mstMovingBoundsData[i].indexDataCount != 0) {
+ _mstMovingBoundsData[i].indexData = (uint8_t *)malloc(_mstMovingBoundsData[i].indexDataCount);
+ fp->read(_mstMovingBoundsData[i].indexData, _mstMovingBoundsData[i].indexDataCount);
+ bytesRead += _mstMovingBoundsData[i].indexDataCount;
+ } else {
+ _mstMovingBoundsData[i].indexData = 0;
+ }
+ }
+
+ _mstShootData.allocate(_mstHdr.shootDataCount);
+ for (int i = 0; i < _mstHdr.shootDataCount; ++i) {
+ _mstShootData[i].data = 0; fp->skipUint32();
+ _mstShootData[i].count = fp->readUint32();
+ bytesRead += 8;
+ }
+ for (int i = 0; i < _mstHdr.shootDataCount; ++i) {
+ _mstShootData[i].data = (MstShootAction *)malloc(_mstShootData[i].count * sizeof(MstShootAction));
+ for (uint32_t j = 0; j < _mstShootData[i].count; ++j) {
+ _mstShootData[i].data[j].codeData = fp->readUint32();
+ _mstShootData[i].data[j].unk4 = fp->readUint32();
+ _mstShootData[i].data[j].unk8 = fp->readUint32();
+ _mstShootData[i].data[j].xPos = fp->readUint32();
+ _mstShootData[i].data[j].yPos = fp->readUint32();
+ _mstShootData[i].data[j].width = fp->readUint32();
+ _mstShootData[i].data[j].height = fp->readUint32();
+ _mstShootData[i].data[j].hSize = fp->readUint32();
+ _mstShootData[i].data[j].vSize = fp->readUint32();
+ _mstShootData[i].data[j].unk24 = fp->readUint32();
+ bytesRead += 40;
+ }
+ }
+
+ _mstShootIndexData.allocate(_mstHdr.shootIndexDataCount);
+ for (int i = 0; i < _mstHdr.shootIndexDataCount; ++i) {
+ _mstShootIndexData[i].indexUnk50 = fp->readUint32();
+ assert(_mstShootIndexData[i].indexUnk50 < (uint32_t)_mstHdr.shootDataCount);
+ _mstShootIndexData[i].indexUnk50Unk1 = 0; fp->skipUint32();
+ _mstShootIndexData[i].count = fp->readUint32();
+ bytesRead += 12;
+ }
+ for (int i = 0; i < _mstHdr.shootIndexDataCount; ++i) {
+ _mstShootIndexData[i].indexUnk50Unk1 = (uint32_t *)malloc(_mstShootIndexData[i].count * 9 * sizeof(uint32_t));
+ for (uint32_t j = 0; j < _mstShootIndexData[i].count * 9; ++j) {
+ _mstShootIndexData[i].indexUnk50Unk1[j] = fp->readUint32();
+ assert(_mstShootIndexData[i].indexUnk50Unk1[j] < _mstShootData[_mstShootIndexData[i].indexUnk50].count);
+ bytesRead += 4;
+ }
+ }
+ _mstActionDirectionData.allocate(_mstHdr.actionDirectionDataCount);
+ for (int i = 0; i < _mstHdr.actionDirectionDataCount; ++i) {
+ _mstActionDirectionData[i].unk0 = fp->readByte();
+ _mstActionDirectionData[i].unk1 = fp->readByte();
+ _mstActionDirectionData[i].unk2 = fp->readByte();
+ _mstActionDirectionData[i].unk3 = fp->readByte();
+ bytesRead += 4;
+ }
+
+ _mstOp223Data.allocate(_mstHdr.op223DataCount);
+ for (int i = 0; i < _mstHdr.op223DataCount; ++i) {
+ _mstOp223Data[i].indexVar1 = fp->readUint16();
+ _mstOp223Data[i].indexVar2 = fp->readUint16();
+ _mstOp223Data[i].indexVar3 = fp->readUint16();
+ _mstOp223Data[i].indexVar4 = fp->readUint16();
+ _mstOp223Data[i].unk8 = fp->readByte();
+ _mstOp223Data[i].unk9 = fp->readByte();
+ _mstOp223Data[i].indexVar5 = fp->readByte();
+ _mstOp223Data[i].unkB = fp->readByte();
+ _mstOp223Data[i].unkC = fp->readUint16();
+ _mstOp223Data[i].unkE = fp->readUint16();
+ _mstOp223Data[i].maskVars = fp->readUint32();
+ bytesRead += 20;
+ }
+
+ _mstOp226Data.allocate(_mstHdr.op226DataCount);
+ for (int i = 0; i < _mstHdr.op226DataCount; ++i) {
+ _mstOp226Data[i].unk0 = fp->readByte();
+ _mstOp226Data[i].unk1 = fp->readByte();
+ _mstOp226Data[i].unk2 = fp->readByte();
+ _mstOp226Data[i].unk3 = fp->readByte();
+ _mstOp226Data[i].unk4 = fp->readByte();
+ _mstOp226Data[i].unk5 = fp->readByte();
+ _mstOp226Data[i].unk6 = fp->readByte();
+ _mstOp226Data[i].unk7 = fp->readByte();
+ bytesRead += 8;
+ }
+
+ _mstOp227Data.allocate(_mstHdr.op227DataCount);
+ for (int i = 0; i < _mstHdr.op227DataCount; ++i) {
+ _mstOp227Data[i].indexVar1 = fp->readUint16();
+ _mstOp227Data[i].indexVar2 = fp->readUint16();
+ _mstOp227Data[i].compare = fp->readByte();
+ _mstOp227Data[i].maskVars = fp->readByte();
+ _mstOp227Data[i].codeData = fp->readUint16();
+ bytesRead += 8;
+ }
+
+ _mstOp234Data.allocate(_mstHdr.op234DataCount);
+ for (int i = 0; i < _mstHdr.op234DataCount; ++i) {
+ _mstOp234Data[i].indexVar1 = fp->readUint16();
+ _mstOp234Data[i].indexVar2 = fp->readUint16();
+ _mstOp234Data[i].compare = fp->readByte();
+ _mstOp234Data[i].maskVars = fp->readByte();
+ _mstOp234Data[i].codeData = fp->readUint16();
+ bytesRead += 8;
+ }
+
+ _mstOp2Data.allocate(_mstHdr.op2DataCount);
+ for (int i = 0; i < _mstHdr.op2DataCount; ++i) {
+ _mstOp2Data[i].indexVar1 = fp->readUint32();
+ _mstOp2Data[i].indexVar2 = fp->readUint32();
+ _mstOp2Data[i].maskVars = fp->readByte();
+ _mstOp2Data[i].unk9 = fp->readByte();
+ _mstOp2Data[i].unkA = fp->readByte();
+ _mstOp2Data[i].unkB = fp->readByte();
+ bytesRead += 12;
+ }
+
+ _mstOp197Data.allocate(_mstHdr.op197DataCount);
+ for (int i = 0; i < _mstHdr.op197DataCount; ++i) {
+ _mstOp197Data[i].unk0 = fp->readUint16();
+ _mstOp197Data[i].unk2 = fp->readUint16();
+ _mstOp197Data[i].unk4 = fp->readUint16();
+ _mstOp197Data[i].unk6 = fp->readUint16();
+ _mstOp197Data[i].maskVars = fp->readUint32();
+ _mstOp197Data[i].indexUnk49 = fp->readUint16();
+ _mstOp197Data[i].unkE = fp->readByte();
+ _mstOp197Data[i].unkF = fp->readByte();
+ bytesRead += 16;
+ }
+
+ _mstOp211Data.allocate(_mstHdr.op211DataCount);
+ for (int i = 0; i < _mstHdr.op211DataCount; ++i) {
+ _mstOp211Data[i].indexVar1 = fp->readUint16();
+ _mstOp211Data[i].indexVar2 = fp->readUint16();
+ _mstOp211Data[i].unk4 = fp->readUint16();
+ _mstOp211Data[i].unk6 = fp->readUint16();
+ _mstOp211Data[i].unk8 = fp->readByte();
+ _mstOp211Data[i].unk9 = fp->readByte();
+ _mstOp211Data[i].unkA = fp->readByte();
+ _mstOp211Data[i].unkB = fp->readByte();
+ _mstOp211Data[i].indexVar3 = fp->readByte();
+ _mstOp211Data[i].unkD = fp->readByte();
+ _mstOp211Data[i].maskVars = fp->readUint16();
+ bytesRead += 16;
+ }
+
+ _mstOp240Data.allocate(_mstHdr.op240DataCount);
+ for (int i = 0; i < _mstHdr.op240DataCount; ++i) {
+ _mstOp240Data[i].flags = fp->readUint32();
+ _mstOp240Data[i].codeData = fp->readUint32();
+ bytesRead += 8;
+ }
+
+ _mstUnk60.allocate(_mstHdr.unk0x70);
+ for (int i = 0; i < _mstHdr.unk0x70; ++i) {
+ _mstUnk60[i] = fp->readUint32();
+ bytesRead += 4;
+ }
+
+ fp->seek(_mstHdr.unk0x74 * 4, SEEK_CUR); // _mstUnk61
+ bytesRead += _mstHdr.unk0x74 * 4;
+
+ _mstOp204Data.allocate(_mstHdr.op204DataCount);
+ for (int i = 0; i < _mstHdr.op204DataCount; ++i) {
+ _mstOp204Data[i].arg0 = fp->readUint32();
+ _mstOp204Data[i].arg1 = fp->readUint32();
+ _mstOp204Data[i].arg2 = fp->readUint32();
+ _mstOp204Data[i].arg3 = fp->readUint32();
+ bytesRead += 16;
+ }
+
+ _mstCodeData = (uint8_t *)malloc(_mstHdr.codeSize * 4);
+ fp->read(_mstCodeData, _mstHdr.codeSize * 4);
+ bytesRead += _mstHdr.codeSize * 4;
+
+ if (bytesRead != _mstHdr.dataSize) {
+ warning("Unexpected .mst bytesRead %d dataSize %d", bytesRead, _mstHdr.dataSize);
+ }
+}
+
+void Resource::unloadMstData() {
+ for (int i = 0; i < _mstHdr.walkCodeDataCount; ++i) {
+ free(_mstWalkCodeData[i].codeData);
+ _mstWalkCodeData[i].codeData = 0;
+ free(_mstWalkCodeData[i].indexData);
+ _mstWalkCodeData[i].indexData = 0;
+ }
+ for (int i = 0; i < _mstHdr.behaviorIndexDataCount; ++i) {
+ free(_mstBehaviorIndexData[i].behavior);
+ _mstBehaviorIndexData[i].behavior = 0;
+ free(_mstBehaviorIndexData[i].data);
+ _mstBehaviorIndexData[i].data = 0;
+ }
+ for (int i = 0; i < _mstHdr.monsterActionIndexDataCount; ++i) {
+ free(_mstMonsterActionIndexData[i].indexUnk48);
+ _mstMonsterActionIndexData[i].indexUnk48 = 0;
+ free(_mstMonsterActionIndexData[i].data);
+ _mstMonsterActionIndexData[i].data = 0;
+ }
+ for (int i = 0; i < _mstHdr.walkPathDataCount; ++i) {
+ free(_mstWalkPathData[i].data);
+ _mstWalkPathData[i].data = 0;
+ free(_mstWalkPathData[i].walkNodeData);
+ _mstWalkPathData[i].walkNodeData = 0;
+ }
+ for (int i = 0; i < _mstHdr.behaviorDataCount; ++i) {
+ free(_mstBehaviorData[i].data);
+ _mstBehaviorData[i].data = 0;
+ }
+ for (int i = 0; i < _mstHdr.attackBoxDataCount; ++i) {
+ free(_mstAttackBoxData[i].data);
+ _mstAttackBoxData[i].data = 0;
+ }
+ for (int i = 0; i < _mstHdr.monsterActionDataCount; ++i) {
+ MstMonsterAction *m = &_mstMonsterActionData[i];
+ for (int j = 0; j < 2; ++j) {
+ free(m->data1[j]);
+ m->data1[j] = 0;
+ free(m->data2[j]);
+ m->data2[j] = 0;
+ }
+ free(m->area);
+ m->area = 0;
+ }
+
+ free(_mstMonsterInfos);
+ _mstMonsterInfos = 0;
+
+ for (int i = 0; i < _mstHdr.movingBoundsDataCount; ++i) {
+ free(_mstMovingBoundsData[i].data1);
+ _mstMovingBoundsData[i].data1 = 0;
+ free(_mstMovingBoundsData[i].indexData);
+ _mstMovingBoundsData[i].indexData = 0;
+ }
+ for (int i = 0; i < _mstHdr.shootDataCount; ++i) {
+ free(_mstShootData[i].data);
+ _mstShootData[i].data = 0;
+ }
+ for (int i = 0; i < _mstHdr.shootIndexDataCount; ++i) {
+ free(_mstShootIndexData[i].indexUnk50Unk1);
+ _mstShootIndexData[i].indexUnk50Unk1 = 0;
+ }
+
+ free(_mstCodeData);
+ _mstCodeData = 0;
+}
+
+const MstScreenArea *Resource::findMstCodeForPos(int num, int xPos, int yPos) const {
+ uint32_t i = _mstScreenAreaByPosIndexData[num];
+ while (i != kNone) {
+ const MstScreenArea *msac = &_mstScreenAreaData[i];
+ if (msac->x1 <= xPos && msac->x2 >= xPos && msac->unk0x1D != 0 && msac->y1 <= yPos && msac->y2 >= yPos) {
+ return msac;
+ }
+ i = msac->nextByPos;
+ }
+ return 0;
+}
+
+void Resource::flagMstCodeForPos(int num, uint8_t value) {
+ uint32_t i = _mstScreenAreaByValueIndexData[num];
+ while (i != kNone) {
+ MstScreenArea *msac = &_mstScreenAreaData[i];
+ msac->unk0x1D = value;
+ i = msac->nextByValue;
+ }
+}
+
+enum {
+ kModeSave,
+ kModeLoad
+};
+
+static uint8_t _checksum;
+
+static void persistUint8(FILE *fp, const uint8_t &val) {
+ fputc(val, fp);
+ _checksum ^= val;
+}
+
+static void persistUint8(FILE *fp, uint8_t &val) {
+ val = fgetc(fp);
+ _checksum ^= val;
+}
+
+static void persistUint32(FILE *fp, const uint32_t &val) {
+ for (int i = 0; i < 4; ++i) {
+ const uint8_t b = (val >> (i * 8)) & 0xFF;
+ fputc(b, fp);
+ _checksum ^= b;
+ }
+}
+
+static void persistUint32(FILE *fp, uint32_t &val) {
+ val = 0;
+ for (int i = 0; i < 4; ++i) {
+ const uint8_t b = fgetc(fp);
+ val |= b << (i * 8);
+ _checksum ^= b;
+ }
+}
+
+template
+static void persistSetupCfg(FILE *fp, T *config) {
+ _checksum = 0;
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 10; ++j) {
+ persistUint8(fp, config->players[i].progress[j]);
+ }
+ persistUint8(fp, config->players[i].levelNum);
+ persistUint8(fp, config->players[i].checkpointNum);
+ persistUint32(fp, config->players[i].cutscenesMask);
+ persistUint8(fp, config->players[i].difficulty);
+ for (int j = 0; j < 32; ++j) {
+ persistUint8(fp, config->players[i].controls[j]);
+ }
+ persistUint8(fp, config->players[i].stereo);
+ persistUint8(fp, config->players[i].volume);
+ persistUint8(fp, config->players[i].lastLevelNum);
+ }
+ persistUint8(fp, config->unkD0);
+ persistUint8(fp, config->currentPlayer);
+ const uint8_t checksum = _checksum;
+ persistUint8(fp, config->unkD2);
+ if (M == kModeSave) {
+ persistUint8(fp, checksum);
+ } else {
+ persistUint8(fp, config->checksum);
+ if (M == kModeLoad && checksum != config->checksum) {
+ warning("Invalid checksum 0x%x (0x%x) for 'setup.cfg'", config->checksum, checksum);
+ }
+ }
+}
+
+static const uint32_t _demTag = 0x31434552; // 'REC1'
+
+bool Resource::loadHodDem() {
+ bool ret = false;
+ File f;
+ if (openDat(_fs, _hodDem, &f)) {
+ const uint32_t tag = f.readUint32();
+ if (tag == _demTag) {
+ f.skipUint32();
+ _dem.randSeed = f.readUint32();
+ _dem.keyMaskLen = f.readUint32();
+ _dem.level = f.readByte();
+ _dem.checkpoint = f.readByte();
+ _dem.difficulty = f.readByte();
+ _dem.randRounds = f.readByte();
+ f.skipUint32();
+ f.skipUint32();
+ _dem.actionKeyMask = (uint8_t *)malloc(_dem.keyMaskLen);
+ f.read(_dem.actionKeyMask, _dem.keyMaskLen);
+ _dem.directionKeyMask = (uint8_t *)malloc(_dem.keyMaskLen);
+ f.read(_dem.directionKeyMask, _dem.keyMaskLen);
+ ret = true;
+ }
+ closeDat(_fs, &f);
+ }
+ return ret;
+}
+
+void Resource::unloadHodDem() {
+ free(_dem.actionKeyMask);
+ free(_dem.directionKeyMask);
+ memset(&_dem, 0, sizeof(_dem));
+}
+
+bool Resource::writeSetupCfg(const SetupConfig *config) {
+ FILE *fp = _fs->openSaveFile(_setupCfg, true);
+ if (fp) {
+ persistSetupCfg(fp, config);
+ _fs->closeFile(fp);
+ return true;
+ }
+ warning("Failed to save '%s'", _setupCfg);
+ return false;
+}
+
+bool Resource::readSetupCfg(SetupConfig *config) {
+ FILE *fp = _fs->openSaveFile(_setupCfg, false);
+ if (fp) {
+ persistSetupCfg(fp, config);
+ _fs->closeFile(fp);
+ return true;
+ }
+ return false;
+}
diff --git a/Src/Vita_&_Switch/resource.h b/Src/Vita_&_Switch/resource.h
new file mode 100644
index 0000000..7d56433
--- /dev/null
+++ b/Src/Vita_&_Switch/resource.h
@@ -0,0 +1,697 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef RESOURCE_H__
+#define RESOURCE_H__
+
+#include "defs.h"
+#include "intern.h"
+
+struct DatHdr {
+ uint32_t version; // 0x0
+ uint32_t bufferSize0; // 0x4
+ uint32_t bufferSize1; // 0x8
+ uint32_t sssOffset; // 0xC
+ int32_t iconsCount; // 0x10
+ int32_t menusCount; // 0x14
+ int32_t cutscenesCount; // 0x18
+ int32_t levelsCount; // 0x1C
+ int32_t levelCheckpointsCount[8]; // 0x20..0x3C
+ int32_t yesNoQuitImage; // 0x40
+ uint32_t soundDataSize; // 0x44
+ uint32_t loadingImageSize; // 0x48
+ uint32_t hintsImageOffsetTable[46];
+ uint32_t hintsImageSizeTable[46];
+};
+
+struct LvlHdr {
+ uint8_t screensCount;
+ uint8_t staticLvlObjectsCount;
+ uint8_t otherLvlObjectsCount;
+ uint8_t spritesCount;
+};
+
+struct LvlScreenVector {
+ int32_t u;
+ int32_t v;
+};
+
+struct LvlScreenState {
+ uint8_t s0;
+ uint8_t s1;
+ uint8_t s2;
+ uint8_t s3; // maskData
+};
+
+struct LvlBackgroundData {
+ uint8_t backgroundCount;
+ uint8_t currentBackgroundId;
+ uint8_t maskCount;
+ uint8_t currentMaskId;
+ uint8_t shadowCount;
+ uint8_t currentShadowId;
+ uint8_t soundCount;
+ uint8_t currentSoundId;
+ uint8_t dataUnk3Count;
+ uint8_t unk9;
+ uint8_t dataUnk45Count;
+ uint8_t unkB;
+ uint16_t backgroundPaletteId;
+ uint16_t backgroundBitmapId;
+ uint8_t *backgroundPaletteTable[4];
+ uint8_t *backgroundBitmapTable[4];
+ uint8_t *dataUnk0Table[4]; // unused
+ uint8_t *backgroundMaskTable[4];
+ uint8_t *backgroundSoundTable[4];
+ uint8_t *backgroundAnimationTable[8];
+ LvlObjectData *backgroundLvlObjectDataTable[8];
+};
+
+struct MstHdr {
+ int version;
+ int dataSize;
+ int walkBoxDataCount; // 8
+ int walkCodeDataCount; // C
+ int movingBoundsIndexDataCount; // 10
+ int levelCheckpointCodeDataCount; // 14
+ int screenAreaDataCount; // 18
+ int screenAreaIndexDataCount; // 1C
+ int behaviorIndexDataCount; // 20
+ int monsterActionIndexDataCount; // 24
+ int walkPathDataCount; // 28
+ int infoMonster2Count; // 2C
+ int behaviorDataCount; // 30
+ int attackBoxDataCount; // 34
+ int monsterActionDataCount; // 38
+ int infoMonster1Count; // 3C
+ int movingBoundsDataCount; // 40
+ int shootDataCount; // 44
+ int shootIndexDataCount; // 48
+ int actionDirectionDataCount; // 4C
+ int op223DataCount; // 50
+ int op226DataCount; // 54
+ int op227DataCount; // 58
+ int op234DataCount; // 5C
+ int op2DataCount; // 60
+ int op197DataCount; // 64
+ int op211DataCount; // 68
+ int op240DataCount; // 6C
+ int unk0x70; // mstUnk60DataCount
+ int unk0x74; // mstUnk61DataCount
+ int op204DataCount; // 78
+ int codeSize; // sizeof(uint32_t)
+ int screensCount; // 80
+};
+
+struct MstPointOffset {
+ int32_t xOffset;
+ int32_t yOffset;
+}; // sizeof == 8
+
+struct MstScreenArea {
+ int32_t x1; // 0
+ int32_t x2; // 4
+ int32_t y1; // 8
+ int32_t y2; // 0xC
+ uint32_t nextByPos; // 0x10 _mstScreenAreaByPosIndexData
+ uint32_t prev; // 0x14 unused
+ uint32_t nextByValue; // 0x18 _mstScreenAreaByValueIndexData
+ uint8_t unk0x1C; // 0x1C _mstScreenAreaByValueIndexData
+ uint8_t unk0x1D; // 0x1D value
+ uint16_t unk0x1E; // 0x1E unused
+ uint32_t codeData; // 0x20, offset _mstCodeData
+}; // sizeof == 36
+
+struct MstWalkBox { // u34
+ int32_t right; // 0
+ int32_t left; // 4
+ int32_t bottom; // 8
+ int32_t top; // 0xC
+ uint8_t flags[4]; // 0x10
+}; // sizeof == 20
+
+struct MstWalkCode { // u35
+ uint32_t *codeData; // 0, offset _mstCodeData
+ uint32_t codeDataCount; // 4
+ uint8_t *indexData; // 8
+ uint32_t indexDataCount; // C
+}; // sizeof == 16
+
+struct MstMovingBoundsIndex { // u36
+ uint32_t indexUnk49; // indexes _mstMovingBoundsData
+ uint32_t unk4; // indexes _mstMovingBoundsData.data1
+ int32_t unk8;
+}; // sizeof == 12
+
+struct MstBehaviorIndex { // u42
+ uint32_t *behavior; // 0 indexes _mstBehaviorData
+ uint32_t count1; // 4
+ uint8_t *data; // 8 lut - indexes .behavior
+ uint32_t dataCount; // C
+}; // sizeof == 16
+
+struct MstMonsterActionIndex { // u43
+ uint32_t *indexUnk48; // indexes _mstMonsterActionData
+ uint32_t count1; // 4
+ uint8_t *data; // 8 lut - indexes .indexUnk48
+ uint32_t dataCount; // C
+}; // sizeof == 16
+
+struct MstWalkNode { // u44u1
+ int32_t x1; // 0
+ int32_t x2; // 4
+ int32_t y1; // 8
+ int32_t y2; // 0xC
+ uint32_t walkBox; // 0x10
+ uint32_t walkCodeStage1; // 0x14
+ uint32_t walkCodeStage2; // 0x18
+ uint32_t movingBoundsIndex1; // 0x1C
+ uint32_t movingBoundsIndex2; // 0x20
+ uint32_t walkCodeReset[2]; // 0x24
+ int32_t coords[4][2]; // 0x2C, 0x34, 0x3C, 0x44
+ uint32_t neighborWalkNode[4]; // per direction 0x4C
+ uint32_t nextWalkNode; // next 0x5C
+ uint8_t *unk60[2]; // 0x60
+}; // sizeof == 104
+
+struct MstWalkPath { // u44
+ MstWalkNode *data;
+ uint32_t *walkNodeData; // indexed by screen number
+ uint32_t mask; // 0x8
+ uint32_t count; // 0xC
+}; // sizeof == 16
+
+struct MstInfoMonster2 { // u45
+ uint8_t type; // 0x0
+ uint8_t shootMask; // 0x1
+ uint16_t anim; // 0x2
+ uint32_t codeData; // 4 init
+ uint32_t codeData2; // 8 shoot
+}; // sizeof == 12
+
+struct MstBehaviorState { // u46u1
+ uint32_t indexMonsterInfo; // 0, indexes _mstMonsterInfos
+ uint16_t anim; // 4
+ uint16_t unk6; // 6
+ uint32_t unk8; // 0x8
+ uint32_t energy; // 0xC
+ uint32_t unk10; // 0x10
+ uint32_t count; // 0x14
+ uint32_t unk18; // 0x18
+ uint32_t indexUnk51; // 0x1C indexes _mstShootIndexData
+ uint32_t walkPath; // 0x20 indexes _mstWalkPathData
+ uint32_t attackBox; // 0x24 indexes _mstAttackBoxData
+ uint32_t codeData; // 0x28 indexes _mstCodeData
+}; // sizeof == 44
+
+struct MstBehavior { // u46
+ MstBehaviorState *data;
+ uint32_t count;
+}; // sizeof == 8
+
+struct MstAttackBox { // u47
+ uint8_t *data; // sizeof == 20 (uint32_t * 5)
+ uint32_t count;
+}; // sizeof == 8
+
+struct MstMonsterAreaAction {
+ uint32_t unk0; // 0x0, indexes _mstMonsterInfos
+ uint32_t indexUnk51; // 0x4
+ int32_t xPos; // 0x8
+ int32_t yPos; // 0xC
+ uint32_t codeData; // 0x10
+ uint32_t codeData2; // 0x14 stop
+ uint8_t unk18; // 0x18
+ uint8_t direction; // 0x19
+ int8_t screenNum; // 0x1A
+ uint8_t monster1Index; // 0x1B
+}; // sizeof == 28
+
+struct MstMonsterArea {
+ uint8_t unk0;
+ MstMonsterAreaAction *data; // 0x4 sizeof == 28
+ uint32_t count;
+}; // sizeof == 12
+
+struct MstMonsterAction { // u48
+ uint16_t unk0;
+ uint16_t unk2;
+ uint8_t unk4;
+ uint8_t direction;
+ uint8_t unk6;
+ uint8_t unk7;
+ uint32_t codeData; // 0x8 indexes _mstCodeData
+ MstMonsterArea *area; // 0xC
+ int areaCount; // 0x10
+ uint32_t *data1[2]; // 0x14, 0x18
+ uint32_t *data2[2]; // 0x1C, 0x20
+ uint32_t count[2]; // 0x24, 0x28
+}; // sizeof == 44
+
+struct MstMovingBoundsUnk1 { // u49u1
+ uint32_t offsetMonsterInfo; // 0, offset _mstMonsterInfos[32]
+ uint32_t unk4;
+ uint8_t unk8;
+ uint8_t unk9;
+ uint8_t unkA;
+ uint8_t unkB;
+ uint8_t unkC;
+ uint8_t unkD;
+ uint8_t unkE;
+ uint8_t unkF;
+}; // sizeof == 16
+
+struct MstMovingBounds { // u49
+ uint32_t indexMonsterInfo; // 0, indexes _mstMonsterInfos
+ MstMovingBoundsUnk1 *data1; // 0x4, sizeof == 16
+ uint32_t count1; // 0x8
+ uint8_t *indexData; // 0xC
+ uint32_t indexDataCount; // 0x10
+ uint8_t unk14; // 0x14
+ uint8_t unk15; // 0x15
+ uint8_t unk16; // 0x16
+ uint8_t unk17; // 0x17
+}; // sizeof == 24
+
+struct MstShootAction { // u50u1
+ uint32_t codeData;
+ uint32_t unk4;
+ uint32_t unk8;
+ uint32_t xPos; // C
+ uint32_t yPos; // 10
+ uint32_t width; // 14
+ uint32_t height; // 18
+ uint32_t hSize; // 1C
+ uint32_t vSize; // 20
+ int32_t unk24;
+}; // sizeof == 40
+
+struct MstShoot { // u50
+ MstShootAction *data;
+ uint32_t count;
+}; // sizeof == 8
+
+struct MstShootIndex { // u51
+ uint32_t indexUnk50;
+ uint32_t *indexUnk50Unk1; // sizeof == 40
+ uint32_t count;
+}; // sizeof == 12
+
+struct MstActionDirectionMask { // u52
+ uint8_t unk0;
+ uint8_t unk1;
+ uint8_t unk2;
+ uint8_t unk3;
+}; // sizeof == 4
+
+struct MstOp240Data {
+ uint32_t flags;
+ uint32_t codeData;
+}; // sizeof == 8
+
+struct MstOp223Data {
+ int16_t indexVar1;
+ int16_t indexVar2; // 2
+ int16_t indexVar3; // 4
+ int16_t indexVar4; // 6
+ uint8_t unk8; // 8
+ uint8_t unk9;
+ int8_t indexVar5; // A
+ int8_t unkB; // B
+ uint16_t unkC; // C
+ uint16_t unkE; // E
+ uint32_t maskVars; // 0x10
+}; // sizeof == 20
+
+struct MstOp227Data {
+ int16_t indexVar1; // 0
+ int16_t indexVar2; // 2
+ uint8_t compare; // 4
+ uint8_t maskVars; // 5
+ uint16_t codeData; // 6
+}; // sizeof == 8
+
+struct MstOp234Data {
+ int16_t indexVar1; // 0
+ int16_t indexVar2; // 2
+ uint8_t compare; // 4
+ uint8_t maskVars; // 5
+ uint32_t codeData;
+}; // sizeof == 8
+
+struct MstOp2Data {
+ int32_t indexVar1; // 0
+ int32_t indexVar2; // 4
+ uint8_t maskVars; // 8
+ uint8_t unk9;
+ uint8_t unkA;
+ uint8_t unkB;
+}; // sizeof == 12
+
+struct MstOp226Data {
+ uint8_t unk0;
+ uint8_t unk1;
+ uint8_t unk2;
+ uint8_t unk3;
+ uint8_t unk4;
+ uint8_t unk5;
+ uint8_t unk6;
+ uint8_t unk7;
+}; // sizeof == 8
+
+struct MstOp204Data {
+ uint32_t arg0; // 0
+ uint32_t arg1; // 4
+ uint32_t arg2; // 8
+ uint32_t arg3; // C
+}; // sizeof == 16
+
+struct MstOp197Data {
+ int16_t unk0;
+ int16_t unk2;
+ int16_t unk4;
+ int16_t unk6;
+ uint32_t maskVars;
+ uint16_t indexUnk49;
+ int8_t unkE;
+ int8_t unkF;
+}; // sizeof == 16
+
+struct MstOp211Data {
+ int16_t indexVar1; // 0
+ int16_t indexVar2; // 2
+ uint16_t unk4; // 4
+ int16_t unk6; // 6
+ uint8_t unk8; // 8
+ uint8_t unk9; // 9
+ uint8_t unkA; // A
+ uint8_t unkB; // B
+ int8_t indexVar3; // C
+ uint8_t unkD; // D
+ uint16_t maskVars; // E
+}; // sizeof == 16
+
+struct SssHdr {
+ int version;
+ int bufferSize;
+ int preloadPcmCount;
+ int preloadInfoCount;
+ int infosDataCount;
+ int filtersDataCount;
+ int banksDataCount;
+ int samplesDataCount;
+ int codeSize;
+ int preloadData1Count; // 24
+ int preloadData2Count; // 28
+ int preloadData3Count; // 2C
+ int pcmCount; // 30
+};
+
+struct SssInfo {
+ uint16_t sssBankIndex; // 0 indexes _sssBanksData
+ int8_t sampleIndex; // 2
+ uint8_t targetVolume; // 3
+ int8_t targetPriority; // 4
+ int8_t targetPanning; // 5
+ uint8_t concurrencyMask; // 6
+};
+
+struct SssDefaults {
+ uint8_t defaultVolume;
+ int8_t defaultPriority;
+ int8_t defaultPanning;
+};
+
+struct SssBank {
+ uint8_t flags; // 0 flags0
+ int8_t count; // 1 codeOffsetCount
+ uint16_t sssFilter; // 2 indexes _sssFilters
+ uint32_t firstSampleIndex; // 4 indexes _sssSamplesData
+};
+
+struct SssSample {
+ uint16_t pcm; // indexes _sssPcmTable
+ uint16_t framesCount;
+ uint8_t initVolume; // 0x4
+ uint8_t unk5; // unused
+ int8_t initPriority; // 0x6
+ int8_t initPanning; // 0x7
+ uint32_t codeOffset1; // 0x8 indexes _sssCodeData
+ uint32_t codeOffset2; // 0xC indexes _sssCodeData
+ uint32_t codeOffset3; // 0x10 indexes _sssCodeData
+ uint32_t codeOffset4; // 0x14 indexes _sssCodeData
+}; // sizeof == 24
+
+struct SssPreloadList {
+ int count;
+ int ptrSize;
+ uint8_t *ptr; // uint16_t for v6 or v12, uint8_t for v10
+};
+
+struct SssPreloadInfoData {
+ uint16_t pcmBlockOffset;
+ uint16_t pcmBlockSize;
+ uint8_t screenNum;
+ uint8_t preload3Index;
+ uint8_t preload1Index;
+ uint8_t preload2Index;
+ uint32_t unk1C;
+ SssPreloadList preload1Data_V6;
+}; // sizeof == 32 (v10,v12) 68 (v6)
+
+struct SssPreloadInfo {
+ uint32_t count;
+ SssPreloadInfoData *data;
+};
+
+struct SssFilter {
+ int32_t volume;
+ int32_t volumeCurrent; // fp16
+ int32_t volumeDelta;
+ int32_t volumeSteps;
+ int32_t panning;
+ int32_t panningCurrent; // fp16
+ int32_t panningDelta;
+ int32_t panningSteps;
+ int32_t priority;
+ int32_t priorityCurrent;
+ bool changed; // 0x30
+}; // sizeof == 52
+
+struct SssPcm {
+ int16_t *ptr; // 0 PCM data
+ uint32_t offset; // 4 offset in .sss
+ uint32_t totalSize; // 8 size in .sss (256 int16_t words + followed by indexes)
+ uint32_t strideSize; // 12
+ uint16_t strideCount; // 16
+ uint16_t flags; // 18 1:stereo
+ uint32_t pcmSize; // decompressed PCM size in bytes
+};
+
+struct SssUnk6 {
+ uint32_t unk0[4]; // 0
+ uint32_t mask; // 10
+};
+
+struct Dem {
+ uint32_t randSeed;
+ uint32_t keyMaskLen;
+ uint8_t level;
+ uint8_t checkpoint;
+ uint8_t difficulty;
+ uint8_t randRounds;
+ uint8_t *actionKeyMask;
+ uint8_t *directionKeyMask;
+};
+
+template
+struct ResStruct {
+ T *ptr;
+ unsigned int count;
+
+ ResStruct()
+ : ptr(0), count(0) {
+ }
+ ~ResStruct() {
+ deallocate();
+ }
+
+ void deallocate() {
+ free(ptr);
+ ptr = 0;
+ count = 0;
+ }
+ void allocate(unsigned int size) {
+ free(ptr);
+ count = size;
+ ptr = (T *)malloc(size * sizeof(T));
+ }
+
+ const T& operator[](int i) const {
+ assert((unsigned int)i < count);
+ return ptr[i];
+ }
+ T& operator[](int i) {
+ assert((unsigned int)i < count);
+ return ptr[i];
+ }
+};
+
+struct FileSystem;
+
+struct Resource {
+ enum {
+ V1_0,
+ V1_1,
+ V1_2 // 1.2 or newer
+ };
+
+ FileSystem *_fs;
+
+ DatHdr _datHdr;
+ File *_datFile;
+ LvlHdr _lvlHdr;
+ File *_lvlFile;
+ MstHdr _mstHdr;
+ File *_mstFile;
+ SssHdr _sssHdr;
+ File *_sssFile;
+
+ bool _isPsx;
+ bool _isDemo;
+ int _version;
+
+ uint8_t *_loadingImageBuffer;
+ uint8_t *_fontBuffer;
+ uint8_t *_menuBuffer0;
+ uint8_t *_menuBuffer1;
+ uint32_t _menuBuffersOffset;
+
+ Dem _dem;
+ uint32_t _demOffset;
+
+ uint8_t _currentScreenResourceNum;
+
+ uint8_t _screensGrid[kMaxScreens][4];
+ LvlScreenVector _screensBasePos[kMaxScreens];
+ LvlScreenState _screensState[kMaxScreens];
+ uint8_t *_resLevelData0x470CTable; // masks
+ uint8_t *_resLevelData0x470CTablePtrHdr;
+ uint8_t *_resLevelData0x470CTablePtrData;
+ uint32_t _lvlSpritesOffset;
+ uint32_t _lvlBackgroundsOffset;
+ uint32_t _lvlMasksOffset;
+ uint32_t _lvlSssOffset; // .sss offset (PSX)
+ uint32_t _resLevelData0x2988SizeTable[kMaxSpriteTypes]; // sprites
+ LvlObjectData _resLevelData0x2988Table[kMaxSpriteTypes];
+ LvlObjectData *_resLevelData0x2988PtrTable[kMaxSpriteTypes];
+ uint8_t *_resLvlSpriteDataPtrTable[kMaxSpriteTypes];
+ uint32_t _resLevelData0x2B88SizeTable[kMaxScreens]; // backgrounds
+ LvlBackgroundData _resLvlScreenBackgroundDataTable[kMaxScreens];
+ uint8_t *_resLvlScreenBackgroundDataPtrTable[kMaxScreens];
+
+ LvlObject _resLvlScreenObjectDataTable[104];
+ LvlObject _dummyObject; // (LvlObject *)0xFFFFFFFF
+
+ ResStruct _sssInfosData;
+ ResStruct _sssDefaultsData;
+ ResStruct _sssBanksData;
+ ResStruct _sssSamplesData;
+ ResStruct _sssPreload1Table; // pcm
+ ResStruct _sssPreloadInfosData; // indexed by screen number
+ ResStruct _sssFilters;
+ ResStruct _sssPcmTable;
+ ResStruct _sssDataUnk6;
+ uint32_t *_sssGroup1[3];
+ uint32_t *_sssGroup2[3];
+ uint32_t *_sssGroup3[3];
+ uint8_t *_sssCodeData;
+
+ ResStruct _mstPointOffsets;
+ ResStruct _mstWalkBoxData;
+ ResStruct _mstWalkCodeData;
+ ResStruct _mstMovingBoundsIndexData;
+ uint32_t _mstTickDelay;
+ uint32_t _mstTickCodeData;
+ ResStruct _mstLevelCheckpointCodeData;
+ ResStruct _mstScreenAreaData;
+ ResStruct _mstScreenAreaByValueIndexData;
+ ResStruct _mstScreenAreaByPosIndexData;
+ ResStruct _mstUnk41;
+ ResStruct _mstBehaviorIndexData;
+ ResStruct _mstMonsterActionIndexData;
+ ResStruct _mstWalkPathData;
+ ResStruct _mstInfoMonster2Data;
+ ResStruct _mstBehaviorData;
+ ResStruct _mstAttackBoxData;
+ ResStruct _mstMonsterActionData;
+ uint8_t *_mstMonsterInfos; // sizeof == 948
+ ResStruct _mstMovingBoundsData;
+ ResStruct _mstShootData;
+ ResStruct _mstShootIndexData;
+ ResStruct _mstActionDirectionData;
+ ResStruct _mstOp223Data;
+ ResStruct _mstOp227Data;
+ ResStruct _mstOp234Data;
+ ResStruct _mstOp2Data;
+ ResStruct _mstOp197Data;
+ ResStruct _mstOp211Data;
+ ResStruct _mstOp240Data;
+ ResStruct _mstUnk60; // indexes _mstCodeData
+ ResStruct _mstOp204Data;
+ uint8_t *_mstCodeData;
+ ResStruct _mstOp226Data;
+
+ Resource(FileSystem *fs);
+ ~Resource();
+
+ bool sectorAlignedGameData();
+
+ void loadSetupDat();
+ bool loadDatHintImage(int num, uint8_t *dst, uint8_t *pal);
+ bool loadDatLoadingImage(uint8_t *dst, uint8_t *pal);
+ void loadDatMenuBuffers();
+ void unloadDatMenuBuffers();
+
+ void loadLevelData(int levelNum);
+
+ void loadLvlScreenObjectData(LvlObject *dat, const uint8_t *src);
+ void loadLvlData(File *fp);
+ void unloadLvlData();
+ void loadLvlSpriteData(int num, const uint8_t *buf = 0);
+ const uint8_t *getLvlScreenMaskDataPtr(int num) const;
+ const uint8_t *getLvlScreenPosDataPtr(int num) const;
+ void loadLvlScreenMaskData();
+ void loadLvlScreenBackgroundData(int num, const uint8_t *buf = 0);
+ void unloadLvlScreenBackgroundData(int num);
+ bool isLvlSpriteDataLoaded(int num) const;
+ bool isLvlBackgroundDataLoaded(int num) const;
+ void incLvlSpriteDataRefCounter(LvlObject *ptr);
+ void decLvlSpriteDataRefCounter(LvlObject *ptr);
+ const uint8_t *getLvlSpriteFramePtr(LvlObjectData *dat, int frame, uint16_t *w, uint16_t *h) const;
+ const uint8_t *getLvlSpriteCoordPtr(LvlObjectData *dat, int num) const;
+ int findScreenGridIndex(int screenNum) const;
+
+ void loadSssData(File *fp, const uint32_t baseOffset = 0);
+ void unloadSssData();
+ void checkSssCode(const uint8_t *buf, int size) const;
+ void loadSssPcm(File *fp, SssPcm *pcm);
+ void clearSssGroup3();
+ void resetSssFilters();
+ void preloadSssPcmList(const SssPreloadInfoData *preloadInfoData);
+
+ void loadMstData(File *fp);
+ void unloadMstData();
+ const MstScreenArea *findMstCodeForPos(int num, int xPos, int yPos) const;
+ void flagMstCodeForPos(int num, uint8_t value);
+
+ bool loadHodDem();
+ void unloadHodDem();
+
+ bool writeSetupCfg(const SetupConfig *config);
+ bool readSetupCfg(SetupConfig *config);
+};
+
+#endif // RESOURCE_H__
diff --git a/SRC/scaler.h b/Src/Vita_&_Switch/scaler.h
similarity index 77%
rename from SRC/scaler.h
rename to Src/Vita_&_Switch/scaler.h
index fd6ab73..695da8c 100644
--- a/SRC/scaler.h
+++ b/Src/Vita_&_Switch/scaler.h
@@ -1,3 +1,7 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
#ifndef SCALER_H__
#define SCALER_H__
diff --git a/SRC/scaler_nearest.cpp b/Src/Vita_&_Switch/scaler_nearest.cpp
similarity index 100%
rename from SRC/scaler_nearest.cpp
rename to Src/Vita_&_Switch/scaler_nearest.cpp
diff --git a/SRC/scaler_xbr.cpp b/Src/Vita_&_Switch/scaler_xbr.cpp
similarity index 100%
rename from SRC/scaler_xbr.cpp
rename to Src/Vita_&_Switch/scaler_xbr.cpp
diff --git a/Src/Vita_&_Switch/screenshot.cpp b/Src/Vita_&_Switch/screenshot.cpp
new file mode 100644
index 0000000..59e6f36
--- /dev/null
+++ b/Src/Vita_&_Switch/screenshot.cpp
@@ -0,0 +1,61 @@
+
+#include
+#include "screenshot.h"
+
+static void fwriteUint16LE(FILE *fp, uint16_t n) {
+ fputc(n & 0xFF, fp);
+ fputc(n >> 8, fp);
+}
+
+static void fwriteUint32LE(FILE *fp, uint32_t n) {
+ fwriteUint16LE(fp, n & 0xFFFF);
+ fwriteUint16LE(fp, n >> 16);
+}
+
+static const uint16_t TAG_BM = 0x4D42;
+
+void saveBMP(FILE *fp, const uint8_t *bits, const uint8_t *pal, int w, int h) {
+ const int alignWidth = (w + 3) & ~3;
+ const int imageSize = alignWidth * h;
+
+ // Write file header
+ fwriteUint16LE(fp, TAG_BM);
+ fwriteUint32LE(fp, 14 + 40 + 4 * 256 + imageSize);
+ fwriteUint16LE(fp, 0); // reserved1
+ fwriteUint16LE(fp, 0); // reserved2
+ fwriteUint32LE(fp, 14 + 40 + 4 * 256);
+
+ // Write info header
+ fwriteUint32LE(fp, 40);
+ fwriteUint32LE(fp, w);
+ fwriteUint32LE(fp, h);
+ fwriteUint16LE(fp, 1); // planes
+ fwriteUint16LE(fp, 8); // bit_count
+ fwriteUint32LE(fp, 0); // compression
+ fwriteUint32LE(fp, imageSize); // size_image
+ fwriteUint32LE(fp, 0); // x_pels_per_meter
+ fwriteUint32LE(fp, 0); // y_pels_per_meter
+ fwriteUint32LE(fp, 0); // num_colors_used
+ fwriteUint32LE(fp, 0); // num_colors_important
+
+ // Write palette data
+ for (int i = 0; i < 256; ++i) {
+ fputc(pal[2], fp);
+ fputc(pal[1], fp);
+ fputc(pal[0], fp);
+ fputc(0, fp);
+ pal += 3;
+ }
+
+ // Write bitmap data
+ const int pitch = w;
+ bits += h * pitch;
+ for (int i = 0; i < h; ++i) {
+ bits -= pitch;
+ fwrite(bits, w, 1, fp);
+ int pad = alignWidth - w;
+ while (pad--) {
+ fputc(0, fp);
+ }
+ }
+}
diff --git a/Src/Vita_&_Switch/screenshot.h b/Src/Vita_&_Switch/screenshot.h
new file mode 100644
index 0000000..003f1a1
--- /dev/null
+++ b/Src/Vita_&_Switch/screenshot.h
@@ -0,0 +1,10 @@
+
+#ifndef SCREENSHOT_H__
+#define SCREENSHOT_H__
+
+#include
+#include
+
+void saveBMP(FILE *fp, const uint8_t *bits, const uint8_t *pal, int w, int h);
+
+#endif
diff --git a/SRC/sound.cpp b/Src/Vita_&_Switch/sound.cpp
similarity index 97%
rename from SRC/sound.cpp
rename to Src/Vita_&_Switch/sound.cpp
index 388adda..29d32bc 100644
--- a/SRC/sound.cpp
+++ b/Src/Vita_&_Switch/sound.cpp
@@ -62,7 +62,7 @@ static bool compareSssGroup(uint32_t flags_a, uint32_t flags_b) {
return false;
}
// we can instead simply compare masked integers
- return (flags_a & 0xFFF00FFF) == (flags_b & 0xFFF00FFF);
+ return compare_bits(flags_a, flags_b, 0xFFF00FFF);
}
// returns the active samples for the table/source/bank
@@ -337,7 +337,7 @@ void Game::sssOp4_removeSounds(uint32_t flags) {
const uint32_t mask = 1 << (flags >> 24);
*getSssGroupPtr(_res, 1, flags) &= ~mask;
for (SssObject *so = _sssObjectsList1; so; so = so->nextPtr) {
- if (((so->flags1 ^ flags) & 0xFFFF0FFF) == 0) { // (a & m) == (b & m)
+ if (compare_bits(so->flags1, flags, 0xFFFF0FFF)) {
so->codeDataStage3 = 0;
if (so->codeDataStage4 == 0) {
removeSoundObjectFromList(so);
@@ -347,7 +347,7 @@ void Game::sssOp4_removeSounds(uint32_t flags) {
}
}
for (SssObject *so = _sssObjectsList2; so; so = so->nextPtr) {
- if (((so->flags1 ^ flags) & 0xFFFF0FFF) == 0) {
+ if (compare_bits(so->flags1, flags, 0xFFFF0FFF)) {
so->codeDataStage3 = 0;
if (so->codeDataStage4 == 0) {
removeSoundObjectFromList(so);
@@ -770,8 +770,7 @@ SssObject *Game::createSoundObject(int bankIndex, int sampleIndex, uint32_t flag
return 0;
}
const int firstSampleIndex = bank->firstSampleIndex;
- assert(firstSampleIndex >= 0 && firstSampleIndex < _res->_sssHdr.samplesDataCount);
- SssSample *sample = &_res->_sssSamplesData[firstSampleIndex];
+ const SssSample *sample = &_res->_sssSamplesData[firstSampleIndex];
int framesCount = 0;
for (int i = 0; i < bank->count; ++i) {
if (sample->pcm != 0xFFFF) {
@@ -833,14 +832,8 @@ SssObject *Game::startSoundObject(int bankIndex, int sampleIndex, uint32_t flags
SssBank *bank = &_res->_sssBanksData[bankIndex];
const int sampleNum = bank->firstSampleIndex + sampleIndex;
debug(kDebug_SOUND, "startSoundObject sample %d", sampleNum);
- assert(sampleNum >= 0 && sampleNum < _res->_sssHdr.samplesDataCount);
- SssSample *sample = &_res->_sssSamplesData[sampleNum];
-
- // original loads the PCM data in a seperate thread
+ const SssSample *sample = &_res->_sssSamplesData[sampleNum];
SssPcm *pcm = &_res->_sssPcmTable[sample->pcm];
- if (!pcm->ptr) {
- _res->loadSssPcm(_res->_sssFile, pcm);
- }
if (sample->framesCount != 0) {
SssFilter *filter = &_res->_sssFilters[bank->sssFilter];
@@ -1138,7 +1131,7 @@ void Game::expireSoundObjects(uint32_t flags) {
*getSssGroupPtr(_res, 1, flags) &= ~mask;
*getSssGroupPtr(_res, 2, flags) &= ~mask;
for (SssObject *so = _sssObjectsList1; so; so = so->nextPtr) {
- if (((so->flags0 ^ flags) & 0xFFFF0FFF) == 0) {
+ if (compare_bits(so->flags0, flags, 0xFFFF0FFF)) {
so->codeDataStage3 = 0;
if (so->codeDataStage4 == 0) {
removeSoundObjectFromList(so);
@@ -1148,7 +1141,7 @@ void Game::expireSoundObjects(uint32_t flags) {
}
}
for (SssObject *so = _sssObjectsList2; so; so = so->nextPtr) {
- if (((so->flags0 ^ flags) & 0xFFFF0FFF) == 0) {
+ if (compare_bits(so->flags0, flags, 0xFFFF0FFF)) {
so->codeDataStage3 = 0;
if (so->codeDataStage4 == 0) {
removeSoundObjectFromList(so);
@@ -1212,7 +1205,8 @@ void Game::queueSoundObjectsPcmStride() {
if (so->currentPcmPtr < ptr) {
continue;
}
- const int16_t *end = ptr + _res->getSssPcmSize(pcm) / sizeof(int16_t);
+ const uint32_t pcmSize = pcm->pcmSize / sizeof(int16_t);
+ const int16_t *end = ptr + pcmSize;
if (so->currentPcmPtr >= end) {
continue;
}
@@ -1220,8 +1214,8 @@ void Game::queueSoundObjectsPcmStride() {
continue;
}
_mix.queue(so->currentPcmPtr, end, so->panType, so->panL, so->panR, so->stereo);
- const int strideSize = (pcm->strideSize - 256 * sizeof(int16_t));
- assert(strideSize == 1764 || strideSize == 3528); // words
+ const int strideSize = pcmSize / pcm->strideCount;
+ assert(strideSize == 1764 || strideSize == 3528 || strideSize == 1792); // words
so->currentPcmPtr += strideSize;
}
}
diff --git a/Src/Vita_&_Switch/staticres.cpp b/Src/Vita_&_Switch/staticres.cpp
new file mode 100644
index 0000000..a3190fb
--- /dev/null
+++ b/Src/Vita_&_Switch/staticres.cpp
@@ -0,0 +1,672 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#include "game.h"
+#include "video.h"
+
+// dx, dy
+const uint8_t Game::_specialPowersDxDyTable[16] = {
+ 0x1F, 0x00, 0xE5, 0xEE, 0xE5, 0x12, 0x1B, 0xEE, 0x1B, 0x12, 0xE1, 0x00, 0x00, 0xE1, 0x00, 0x1F
+};
+
+const uint8_t Game::_pointDstIndexTable[] = {
+ 0x40, 0x20, 0x60, 0x10, 0x50, 0x30, 0x70, 0x08, 0x48, 0x28, 0x68, 0x18, 0x58, 0x38, 0x78, 0x04,
+ 0x44, 0x24, 0x64, 0x14, 0x54, 0x34, 0x74, 0x0C, 0x4C, 0x2C, 0x6C, 0x1C, 0x5C, 0x3C, 0x7C, 0x02,
+ 0x42, 0x22, 0x62, 0x12, 0x52, 0x32, 0x72, 0x0A, 0x4A, 0x2A, 0x6A, 0x1A, 0x5A, 0x3A, 0x7A, 0x06,
+ 0x46, 0x26, 0x66, 0x16, 0x56, 0x36, 0x76, 0x0E, 0x4E, 0x2E, 0x6E, 0x1E, 0x5E, 0x3E, 0x7E, 0x01,
+ 0x41, 0x21, 0x61, 0x11, 0x51, 0x31, 0x71, 0x09, 0x49, 0x29, 0x69, 0x19, 0x59, 0x39, 0x79, 0x05,
+ 0x45, 0x25, 0x65, 0x15, 0x55, 0x35, 0x75, 0x0D, 0x4D, 0x2D, 0x6D, 0x1D, 0x5D, 0x3D, 0x7D, 0x03,
+ 0x43, 0x23, 0x63, 0x13, 0x53, 0x33, 0x73, 0x0B, 0x4B, 0x2B, 0x6B, 0x1B, 0x5B, 0x3B, 0x7B, 0x07,
+ 0x47, 0x27, 0x67, 0x17, 0x57, 0x37, 0x77, 0x0F, 0x4F, 0x2F, 0x6F, 0x1F, 0x5F, 0x3F, 0x7F, 0x00
+};
+
+// lut[x] = 26 + log2(x + 1)
+const uint8_t Game::_pointRandomizeShiftTable[] = {
+ 0x1A, 0x1B, 0x1B, 0x1C, 0x1C, 0x1C, 0x1C, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1E,
+ 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1F,
+ 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F,
+ 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00
+};
+
+const uint8_t Game::_pointSrcIndex2Table[] = {
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x04, 0x06, 0x00, 0x08, 0x08, 0x0A, 0x08, 0x0C, 0x0C, 0x0E,
+ 0x00, 0x10, 0x10, 0x12, 0x10, 0x14, 0x14, 0x16, 0x10, 0x18, 0x18, 0x1A, 0x18, 0x1C, 0x1C, 0x1E,
+ 0x00, 0x20, 0x20, 0x22, 0x20, 0x24, 0x24, 0x26, 0x20, 0x28, 0x28, 0x2A, 0x28, 0x2C, 0x2C, 0x2E,
+ 0x20, 0x30, 0x30, 0x32, 0x30, 0x34, 0x34, 0x36, 0x30, 0x38, 0x38, 0x3A, 0x38, 0x3C, 0x3C, 0x3E,
+ 0x00, 0x40, 0x40, 0x42, 0x40, 0x44, 0x44, 0x46, 0x40, 0x48, 0x48, 0x4A, 0x48, 0x4C, 0x4C, 0x4E,
+ 0x40, 0x50, 0x50, 0x52, 0x50, 0x54, 0x54, 0x56, 0x50, 0x58, 0x58, 0x5A, 0x58, 0x5C, 0x5C, 0x5E,
+ 0x40, 0x60, 0x60, 0x62, 0x60, 0x64, 0x64, 0x66, 0x60, 0x68, 0x68, 0x6A, 0x68, 0x6C, 0x6C, 0x6E,
+ 0x60, 0x70, 0x70, 0x72, 0x70, 0x74, 0x74, 0x76, 0x70, 0x78, 0x78, 0x7A, 0x78, 0x7C, 0x7C, 0x7E
+};
+
+const uint8_t Game::_pointSrcIndex1Table[] = {
+ 0x00, 0x02, 0x04, 0x04, 0x08, 0x06, 0x08, 0x08, 0x10, 0x0A, 0x0C, 0x0C, 0x10, 0x0E, 0x10, 0x10,
+ 0x20, 0x12, 0x14, 0x14, 0x18, 0x16, 0x18, 0x18, 0x20, 0x1A, 0x1C, 0x1C, 0x20, 0x1E, 0x20, 0x20,
+ 0x40, 0x22, 0x24, 0x24, 0x28, 0x26, 0x28, 0x28, 0x30, 0x2A, 0x2C, 0x2C, 0x30, 0x2E, 0x30, 0x30,
+ 0x40, 0x32, 0x34, 0x34, 0x38, 0x36, 0x38, 0x38, 0x40, 0x3A, 0x3C, 0x3C, 0x40, 0x3E, 0x40, 0x40,
+ 0x80, 0x42, 0x44, 0x44, 0x48, 0x46, 0x48, 0x48, 0x50, 0x4A, 0x4C, 0x4C, 0x50, 0x4E, 0x50, 0x50,
+ 0x60, 0x52, 0x54, 0x54, 0x58, 0x56, 0x58, 0x58, 0x60, 0x5A, 0x5C, 0x5C, 0x60, 0x5E, 0x60, 0x60,
+ 0x80, 0x62, 0x64, 0x64, 0x68, 0x66, 0x68, 0x68, 0x70, 0x6A, 0x6C, 0x6C, 0x70, 0x6E, 0x70, 0x70,
+ 0x80, 0x72, 0x74, 0x74, 0x78, 0x76, 0x78, 0x78, 0x80, 0x7A, 0x7C, 0x7C, 0x80, 0x7E, 0x80, 0x80
+};
+
+const uint8_t Game::_actionDirectionKeyMaskTable[0x160] = {
+ 0x00, 0x00, 0x40, 0x01, 0x40, 0x04, 0x80, 0x00, 0x40, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x02, 0x20, 0x08, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x80, 0x04, 0x80, 0x01, 0x30, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+const uint8_t Game::_pwr1_screenTransformData[4536] = {
+ 0x00, 0x07, 0x0C, 0x10, 0x40, 0xB0, 0xA0, 0x41, 0x81, 0x02, 0x09, 0x28, 0x24, 0x50, 0xA0, 0xA1,
+ 0xC3, 0x87, 0x0F, 0x17, 0x4A, 0x5C, 0x88, 0xB0, 0xA2, 0xC5, 0x81, 0x06, 0x09, 0x5E, 0x1C, 0xA0,
+ 0xF0, 0x62, 0xC6, 0x00, 0x20, 0x43, 0x82, 0xCC, 0x48, 0xB2, 0xA4, 0x80, 0x8D, 0x1C, 0x27, 0xAA,
+ 0x94, 0x08, 0xB1, 0x65, 0xC3, 0x95, 0x04, 0x50, 0xCA, 0x9C, 0x49, 0x13, 0x26, 0xC3, 0x96, 0x36,
+ 0x29, 0xA2, 0xFC, 0x28, 0x32, 0xA4, 0xC9, 0x83, 0x18, 0x4B, 0x56, 0x9C, 0xE8, 0xD2, 0xA5, 0x4D,
+ 0x9A, 0x08, 0x49, 0xA2, 0xEC, 0x68, 0x91, 0x67, 0xCF, 0x9F, 0x50, 0x65, 0xE6, 0x54, 0x59, 0x34,
+ 0x22, 0x4C, 0x99, 0x1A, 0x91, 0x6A, 0x4D, 0xB9, 0x12, 0xE7, 0xD4, 0x99, 0x4E, 0x9F, 0x42, 0x3D,
+ 0xF9, 0xD3, 0x22, 0xCB, 0xAA, 0x56, 0xAF, 0x6E, 0xC5, 0x28, 0x95, 0x69, 0xC5, 0xB0, 0x3E, 0xC7,
+ 0x0A, 0xDD, 0x38, 0x95, 0x2A, 0xDA, 0x02, 0x6A, 0x37, 0x02, 0x5D, 0xBB, 0xD4, 0x66, 0xDA, 0x9C,
+ 0x34, 0xE1, 0xC6, 0x8D, 0x6A, 0xD2, 0xEC, 0xD9, 0xBB, 0x78, 0x8F, 0xF2, 0x6D, 0xEB, 0x36, 0x29,
+ 0x5C, 0xB9, 0x85, 0xE9, 0xD6, 0x5D, 0x78, 0x17, 0xB0, 0xC7, 0x8C, 0x8B, 0x25, 0xFB, 0x7D, 0xF9,
+ 0x35, 0xB0, 0xC1, 0x9E, 0x62, 0x21, 0xEF, 0x4D, 0x78, 0xB8, 0xB2, 0xE5, 0xCC, 0x86, 0x75, 0xBE,
+ 0x75, 0x2A, 0x7A, 0xEE, 0xC5, 0xC9, 0xA5, 0xBD, 0xE6, 0x5D, 0xAD, 0x14, 0x35, 0x69, 0xD8, 0x8A,
+ 0x91, 0x7E, 0x06, 0x2D, 0xB2, 0x75, 0xD6, 0xD4, 0x37, 0x11, 0x27, 0xCE, 0x6D, 0x9B, 0xAB, 0x6A,
+ 0x81, 0xBE, 0x45, 0xF7, 0xC5, 0x1D, 0x1C, 0x62, 0xE7, 0xA6, 0x91, 0x51, 0x33, 0x27, 0x0E, 0x76,
+ 0x37, 0xEF, 0x91, 0xAD, 0x35, 0x37, 0x37, 0xFD, 0x5C, 0xBA, 0x4A, 0xE8, 0xC9, 0xC7, 0x2E, 0x87,
+ 0x5D, 0xB4, 0xEE, 0xE5, 0xE8, 0x8B, 0xA7, 0xAF, 0x5C, 0x2B, 0x18, 0xB4, 0xEF, 0xD7, 0xB1, 0xD1,
+ 0x4E, 0xB6, 0x3D, 0x1B, 0x79, 0xF8, 0xA8, 0xDA, 0xC9, 0x3B, 0xEF, 0xEE, 0xB8, 0x6C, 0x7A, 0xF5,
+ 0xDF, 0xB1, 0x67, 0xDD, 0x75, 0x01, 0x64, 0x07, 0xDC, 0x76, 0xF2, 0xC1, 0xB6, 0x96, 0x79, 0xE0,
+ 0xDD, 0x57, 0x1B, 0x7C, 0xCC, 0xED, 0xC7, 0x9F, 0x7D, 0xF8, 0x2D, 0x08, 0xE0, 0x44, 0x7C, 0xB5,
+ 0xE7, 0x1E, 0x64, 0x10, 0x2A, 0x24, 0x9C, 0x43, 0xCC, 0x8D, 0xA7, 0x20, 0x6D, 0x0E, 0x3E, 0x38,
+ 0x94, 0x7A, 0x7F, 0x51, 0x47, 0x61, 0x85, 0x48, 0x5D, 0x88, 0x61, 0x86, 0x1A, 0x86, 0x26, 0xDE,
+ 0x89, 0x94, 0x7D, 0x38, 0x9C, 0x8B, 0xEA, 0x9D, 0x57, 0xE2, 0x68, 0x08, 0x01, 0x08, 0xE2, 0x7C,
+ 0x3A, 0xFA, 0xD7, 0x22, 0x8E, 0xC7, 0x69, 0x45, 0x12, 0x81, 0x32, 0x0A, 0xD9, 0x63, 0x7C, 0x09,
+ 0x12, 0x39, 0x5D, 0x90, 0x3B, 0xE6, 0xA7, 0xDF, 0x8D, 0xA7, 0x91, 0xC8, 0x22, 0x63, 0x44, 0xC2,
+ 0x18, 0xE3, 0x86, 0x33, 0x2E, 0x59, 0xE3, 0x87, 0x4E, 0xE6, 0x08, 0xA5, 0x83, 0x22, 0xEA, 0x07,
+ 0xE4, 0x98, 0xE8, 0x49, 0xE9, 0x62, 0x75, 0x72, 0x21, 0x99, 0xA4, 0x6B, 0xB7, 0x79, 0x68, 0x23,
+ 0x95, 0x61, 0x32, 0x68, 0x65, 0x94, 0x1D, 0x9A, 0x79, 0x66, 0x83, 0x5D, 0x96, 0xB9, 0xE6, 0x4E,
+ 0xA2, 0xB9, 0xF9, 0x26, 0x66, 0x07, 0xCE, 0x49, 0x67, 0x98, 0x87, 0x16, 0x19, 0xD4, 0x8E, 0x27,
+ 0xE5, 0xF9, 0x65, 0x55, 0x7B, 0xF2, 0xD9, 0xA7, 0xA3, 0x17, 0x62, 0xD5, 0x9A, 0xA0, 0xBD, 0x4D,
+ 0x4A, 0x94, 0xA1, 0x75, 0xCA, 0xF9, 0x63, 0x80, 0x92, 0x92, 0x79, 0xE0, 0xA3, 0xE5, 0xD9, 0x89,
+ 0xA6, 0x92, 0xA3, 0x56, 0x0A, 0xE8, 0xA5, 0x98, 0x62, 0x77, 0xE5, 0xA6, 0x36, 0x76, 0x5A, 0xEA,
+ 0x8B, 0x77, 0x8A, 0x3A, 0x2A, 0x98, 0x55, 0x9E, 0x9A, 0x26, 0x8D, 0x59, 0xEA, 0x95, 0x5C, 0xAB,
+ 0xAE, 0xB2, 0x08, 0x6B, 0xAC, 0x88, 0xCE, 0xAA, 0xE8, 0xA2, 0x78, 0xF2, 0xEA, 0x29, 0x77, 0xF5,
+ 0x85, 0x3A, 0xA9, 0x97, 0xBD, 0xEA, 0x6A, 0x12, 0xB0, 0xC1, 0x2A, 0x39, 0x2C, 0xB1, 0x44, 0x1A,
+ 0xDB, 0x58, 0x7F, 0x8C, 0x36, 0x0A, 0x2D, 0xA9, 0xDA, 0x82, 0xEA, 0xEB, 0x7B, 0x6A, 0x02, 0x38,
+ 0xAE, 0x6F, 0xD4, 0x16, 0xC8, 0x61, 0x9C, 0x08, 0x62, 0x7B, 0x21, 0xA4, 0xB4, 0xD6, 0x7A, 0x9F,
+ 0xA3, 0xB8, 0xAE, 0xC7, 0xA6, 0x81, 0x94, 0x9A, 0x2B, 0xED, 0xB4, 0xD4, 0x1A, 0x68, 0x97, 0xA1,
+ 0x9F, 0xA2, 0x08, 0xEF, 0xB1, 0xDD, 0x16, 0x74, 0xAB, 0x70, 0x2A, 0x3A, 0xFB, 0xAC, 0x71, 0x38,
+ 0x9E, 0x1B, 0x68, 0xBA, 0xFE, 0xFE, 0x0B, 0x70, 0xC0, 0x53, 0x1A, 0x25, 0x91, 0xC3, 0x8C, 0x1E,
+ 0xCC, 0x6C, 0xBC, 0xFB, 0xEE, 0xCA, 0xF0, 0x9F, 0x0A, 0xFF, 0x94, 0x6E, 0xB5, 0xD1, 0x75, 0x35,
+ 0xB1, 0x6C, 0x15, 0xA3, 0x1C, 0x53, 0xC7, 0xF8, 0xB2, 0x5B, 0xEF, 0xC5, 0x96, 0xCE, 0x9B, 0xAF,
+ 0x98, 0x21, 0x97, 0x34, 0x32, 0xC9, 0x26, 0x7E, 0x7C, 0xB2, 0xC5, 0x29, 0x4B, 0xB8, 0xED, 0x8A,
+ 0x05, 0x2B, 0xFB, 0xF2, 0xB1, 0x40, 0x87, 0x37, 0xF3, 0x93, 0x2C, 0x17, 0x74, 0xB3, 0xBA, 0x1E,
+ 0xEB, 0xBC, 0xB3, 0xCF, 0x75, 0x0D, 0xAC, 0x68, 0xC1, 0x3C, 0x5E, 0x1B, 0xEE, 0xCA, 0x49, 0xA3,
+ 0xFA, 0xB1, 0xAA, 0x35, 0x6F, 0x29, 0x28, 0x9C, 0xF9, 0x3D, 0xAD, 0x72, 0x4E, 0x4D, 0xFE, 0x4C,
+ 0x75, 0xD5, 0x4C, 0x42, 0xFD, 0x33, 0xB2, 0xB6, 0x2A, 0x0B, 0x32, 0xB7, 0x63, 0x61, 0x0A, 0x00,
+ 0x00, 0xD7, 0x81, 0xAD, 0x9D, 0xD8, 0x63, 0x6F, 0xC6, 0xF3, 0xD4, 0x67, 0xFF, 0xC6, 0x30, 0x62,
+ 0xF6, 0xCA, 0x6B, 0xF4, 0xD1, 0x21, 0x76, 0x4D, 0x90, 0xDC, 0x73, 0xD3, 0xCD, 0x25, 0x8F, 0xCB,
+ 0xE1, 0xAD, 0xB6, 0xDE, 0x7B, 0x63, 0x0D, 0x77, 0xD0, 0x42, 0x6F, 0x2C, 0x79, 0xD1, 0x6D, 0x7F,
+ 0xFB, 0x36, 0xE6, 0x47, 0x0A, 0x9A, 0x78, 0xE2, 0xBC, 0xE5, 0x7C, 0xB7, 0xE3, 0x29, 0x92, 0x2D,
+ 0xF5, 0xE5, 0x6C, 0x77, 0x5B, 0x68, 0xD9, 0xDB, 0x9E, 0x5D, 0x2E, 0xCD, 0x82, 0x2B, 0xED, 0xF9,
+ 0xE7, 0x8A, 0xBF, 0x59, 0x13, 0xB8, 0xA4, 0xF7, 0x9C, 0x62, 0xD6, 0x2D, 0x5B, 0xAD, 0xB6, 0xE1,
+ 0xCA, 0x11, 0x5E, 0x78, 0xEC, 0x87, 0xBB, 0x49, 0x3B, 0xE8, 0xB6, 0x63, 0xB9, 0x2C, 0xE9, 0x89,
+ 0x9A, 0xAC, 0x2D, 0xEF, 0xEB, 0x7E, 0xCB, 0xFA, 0xE5, 0xAE, 0x0B, 0x8F, 0xDB, 0xBE, 0xC6, 0x1F,
+ 0x3F, 0x77, 0xE8, 0x8C, 0xBB, 0xDD, 0x2E, 0xDE, 0xB8, 0x9D, 0x8E, 0xB1, 0xEA, 0x95, 0x3F, 0x4F,
+ 0x3C, 0xB9, 0xA9, 0x72, 0x4D, 0x7C, 0xF6, 0xDA, 0x73, 0xDF, 0x3D, 0xBB, 0xB8, 0x8B, 0x1D, 0xA1,
+ 0xF9, 0xE7, 0xCB, 0x0C, 0xBF, 0xF8, 0xC0, 0xCB, 0xF5, 0xBA, 0xBE, 0xEB, 0x13, 0xA8, 0x3D, 0xF2,
+ 0xC9, 0x7B, 0x1D, 0xF3, 0x28, 0x66, 0xBA, 0xC8, 0xA1, 0xAE, 0x6F, 0x7E, 0x73, 0xDA, 0xD8, 0xF2,
+ 0xB7, 0xB0, 0x4E, 0x1D, 0x10, 0x2A, 0x48, 0xFA, 0xDF, 0xF6, 0x16, 0x97, 0x40, 0xEF, 0xC5, 0xEF,
+ 0x69, 0xE1, 0x33, 0xE0, 0xF8, 0x32, 0x56, 0xBE, 0xDF, 0xD5, 0x2F, 0x7A, 0x16, 0x54, 0xDF, 0xE4,
+ 0x06, 0xD4, 0x13, 0x09, 0x4E, 0x70, 0x50, 0x7E, 0x5A, 0x5E, 0xEE, 0x32, 0xB8, 0x40, 0xE8, 0x69,
+ 0xCA, 0x77, 0xE2, 0x4A, 0x9D, 0xFD, 0x42, 0xC8, 0xBF, 0xFE, 0x81, 0xC6, 0x84, 0x27, 0x1C, 0xCC,
+ 0xFB, 0x20, 0x37, 0x40, 0x16, 0x7A, 0x90, 0x81, 0xC1, 0xD3, 0x5C, 0x0B, 0x47, 0x38, 0x43, 0xCD,
+ 0x45, 0x2B, 0x76, 0xD7, 0xC1, 0x61, 0xED, 0x32, 0xB5, 0x43, 0xE7, 0x0D, 0x90, 0x33, 0xBA, 0x0B,
+ 0xDC, 0x07, 0x5B, 0x06, 0xBF, 0xE0, 0xCC, 0x06, 0x82, 0x4C, 0x6B, 0x20, 0x0F, 0x47, 0x14, 0xB2,
+ 0x24, 0xE2, 0xD0, 0x7D, 0xDD, 0x83, 0xC9, 0x13, 0x1F, 0xC7, 0xC3, 0x43, 0xAD, 0x0A, 0x32, 0x3A,
+ 0xCC, 0x99, 0x18, 0xA9, 0x23, 0x32, 0x9C, 0x89, 0x6E, 0x6B, 0xDF, 0x8B, 0x14, 0x11, 0x05, 0xE0,
+ 0x45, 0x13, 0x82, 0xD1, 0x60, 0x6A, 0x1A, 0x63, 0xE9, 0x0A, 0x48, 0xC0, 0xB5, 0xC9, 0xB0, 0x73,
+ 0x4C, 0x24, 0x14, 0x0D, 0xA5, 0xF8, 0xC7, 0x40, 0x6A, 0x4D, 0x8C, 0x57, 0x8B, 0x21, 0xE7, 0xE8,
+ 0x58, 0xC7, 0xFF, 0xDD, 0x11, 0x8F, 0xA9, 0xD2, 0xE3, 0x1E, 0xB7, 0x48, 0xC8, 0xFA, 0x3D, 0xB2,
+ 0x82, 0xC3, 0x5B, 0xA4, 0x21, 0x77, 0xE5, 0x44, 0x03, 0x2A, 0xF2, 0x8F, 0x8C, 0xBC, 0xA1, 0x1D,
+ 0x29, 0x08, 0x49, 0xEF, 0x49, 0xB2, 0x8F, 0x94, 0xAC, 0xE4, 0x1C, 0x65, 0x47, 0x4A, 0x4C, 0x72,
+ 0x91, 0x73, 0xAD, 0x14, 0xA4, 0x11, 0x01, 0x97, 0xAB, 0x39, 0x36, 0xB2, 0x7D, 0xB1, 0xF4, 0xD6,
+ 0x2C, 0x4F, 0x09, 0xC5, 0xC9, 0x4C, 0x12, 0x75, 0x36, 0x6C, 0xA5, 0xF5, 0x5A, 0x67, 0xB3, 0x41,
+ 0xC9, 0xB2, 0x8A, 0xB4, 0xAC, 0x65, 0x21, 0x45, 0x29, 0xC1, 0xBA, 0x79, 0x4C, 0x62, 0x92, 0x9C,
+ 0x5F, 0x2F, 0x3F, 0x29, 0x38, 0x24, 0xD9, 0xED, 0x95, 0x32, 0xBC, 0xE4, 0x31, 0x15, 0x38, 0x3D,
+ 0x8E, 0x69, 0x92, 0x37, 0xA3, 0xCC, 0x65, 0x1E, 0x79, 0xD9, 0x3C, 0x89, 0x29, 0xD3, 0x59, 0x6E,
+ 0xBA, 0xA6, 0xA9, 0xB2, 0x99, 0xCB, 0x52, 0xDE, 0x2F, 0x99, 0xCD, 0xFA, 0x66, 0x09, 0x9B, 0xA9,
+ 0x4D, 0x57, 0xFA, 0xEE, 0x89, 0x3E, 0x3C, 0x27, 0x3A, 0xAD, 0xB9, 0x4D, 0x38, 0xC2, 0xAC, 0x56,
+ 0xF5, 0x74, 0x27, 0x37, 0xBB, 0xF9, 0xCF, 0x6A, 0x32, 0xF3, 0x78, 0x04, 0xBA, 0xD2, 0x3B, 0x79,
+ 0xE9, 0xC3, 0xE6, 0x6D, 0xA5, 0x98, 0xFC, 0x6C, 0x22, 0x1B, 0x01, 0x69, 0x4C, 0xB4, 0xA5, 0x2D,
+ 0x6F, 0x05, 0xB5, 0xE5, 0x41, 0x11, 0x1A, 0x50, 0x5D, 0x2E, 0x94, 0xA1, 0xBE, 0xFC, 0x25, 0x30,
+ 0xD1, 0xF4, 0xB5, 0x7E, 0xFA, 0x93, 0x7A, 0x10, 0x4D, 0x23, 0x27, 0xEF, 0x89, 0x51, 0x3F, 0xB6,
+ 0x51, 0x24, 0xE1, 0xAC, 0xA8, 0x3D, 0x2F, 0xD8, 0xC3, 0x90, 0xA2, 0xD2, 0x8F, 0x63, 0xC2, 0xD4,
+ 0x1B, 0xE1, 0x48, 0x52, 0x99, 0x0A, 0x14, 0x9A, 0xA7, 0xF3, 0xA6, 0x3C, 0x43, 0x12, 0xD3, 0x4D,
+ 0xF6, 0x13, 0xA8, 0x7A, 0x8C, 0x5A, 0x4B, 0x05, 0x44, 0x42, 0x67, 0x9A, 0xF4, 0x63, 0xA7, 0x32,
+ 0x6A, 0x9A, 0x90, 0x9A, 0xC8, 0xB5, 0xBD, 0x14, 0xA6, 0x8E, 0xEC, 0xA8, 0x47, 0x07, 0x1A, 0xCD,
+ 0xA9, 0x78, 0x92, 0x68, 0x39, 0x2D, 0xE9, 0x53, 0xB9, 0x72, 0x46, 0x0E, 0x76, 0xD0, 0x72, 0x19,
+ 0x25, 0x22, 0x38, 0xB3, 0xDA, 0xCE, 0x99, 0xAA, 0x30, 0xA9, 0x5E, 0xFD, 0xEA, 0x48, 0x31, 0xD6,
+ 0xAA, 0x9D, 0x1A, 0x89, 0x6A, 0xF4, 0x1A, 0x5A, 0x5A, 0x87, 0x0A, 0x12, 0x7A, 0xB6, 0x75, 0xAB,
+ 0x54, 0xC5, 0x67, 0x5C, 0xE5, 0xFA, 0x50, 0x8A, 0x26, 0xD4, 0xAE, 0x9E, 0xA1, 0x9C, 0xDB, 0xDC,
+ 0x25, 0xD4, 0x65, 0xCE, 0x13, 0x97, 0x3E, 0x75, 0xAB, 0x70, 0x0C, 0x40, 0x59, 0x03, 0x9C, 0x4C,
+ 0xA9, 0x84, 0x2D, 0x6C, 0x53, 0x9D, 0xFA, 0xBE, 0xC4, 0x92, 0x6F, 0xB1, 0x7A, 0xDD, 0x2B, 0x3B,
+ 0x1F, 0xCB, 0xD1, 0xC8, 0x02, 0x96, 0xA5, 0x0E, 0xA9, 0x6C, 0x65, 0x27, 0x86, 0xD9, 0xA5, 0xDE,
+ 0x75, 0xB3, 0x97, 0x44, 0x4D, 0xDF, 0xF2, 0x8A, 0xB0, 0x78, 0x3A, 0x16, 0xAB, 0xA5, 0x95, 0xEA,
+ 0x51, 0x2F, 0x9A, 0x5A, 0xD5, 0xAE, 0x76, 0x4E, 0xAD, 0xC5, 0x28, 0x53, 0x59, 0x99, 0x4E, 0xC4,
+ 0x96, 0xD5, 0xAC, 0x42, 0x0C, 0xAD, 0x4B, 0xF9, 0x45, 0xDA, 0xCF, 0x69, 0x55, 0xB2, 0x77, 0xF1,
+ 0xAD, 0x6A, 0x81, 0x3B, 0xD8, 0xCC, 0xEA, 0xC6, 0x6B, 0x2A, 0xDD, 0xEA, 0x75, 0xF1, 0xAA, 0xB1,
+ 0xDA, 0xAA, 0xF2, 0xB6, 0x44, 0xCD, 0xAD, 0x6E, 0x19, 0x17, 0xD8, 0x86, 0x48, 0x77, 0xBA, 0xCA,
+ 0x45, 0xAD, 0x6D, 0xE9, 0xAA, 0x53, 0xE3, 0xBA, 0xF0, 0x90, 0xA8, 0x75, 0x6D, 0x54, 0x37, 0x9A,
+ 0xC3, 0xF1, 0x92, 0x97, 0xB7, 0xE6, 0x3D, 0x2F, 0x65, 0xD3, 0x8B, 0xDF, 0xF5, 0x4A, 0xAA, 0xAE,
+ 0xEE, 0x05, 0xE2, 0xB3, 0xCA, 0xFB, 0xD5, 0xE3, 0x12, 0x17, 0xB7, 0x00, 0x34, 0xED, 0x69, 0x69,
+ 0x9A, 0x5F, 0xFD, 0x5A, 0x16, 0x9E, 0x7C, 0x94, 0xAF, 0x67, 0x43, 0x59, 0xDC, 0x00, 0xAF, 0x32,
+ 0x39, 0x22, 0xE2, 0x94, 0x6D, 0xAF, 0x8A, 0xE0, 0x25, 0xDA, 0xF7, 0xBE, 0x0C, 0x76, 0xF0, 0x6F,
+ 0x09, 0x4A, 0xE0, 0x84, 0x41, 0x09, 0xC0, 0x63, 0x15, 0xB0, 0x42, 0x3F, 0xEA, 0xDD, 0xC6, 0x72,
+ 0x38, 0xBC, 0xCE, 0x7D, 0xEE, 0x82, 0xDF, 0x0A, 0x11, 0x11, 0x3F, 0x98, 0xC4, 0x25, 0xF6, 0x2F,
+ 0xDC, 0x50, 0xDC, 0xD9, 0xF7, 0x36, 0x8D, 0xC0, 0x9E, 0x34, 0x70, 0xF1, 0xEA, 0x78, 0x58, 0x10,
+ 0x72, 0xB5, 0xC6, 0x36, 0x46, 0x2B, 0x22, 0x49, 0xBC, 0xDD, 0x03, 0x73, 0xB6, 0xC7, 0x53, 0x34,
+ 0x72, 0x72, 0xA9, 0xFB, 0x49, 0x2C, 0xDE, 0x8C, 0x8A, 0x7F, 0x43, 0x8B, 0x8D, 0x6F, 0x1C, 0xD4,
+ 0x32, 0x92, 0x11, 0xA7, 0x3B, 0x6E, 0x6F, 0x8A, 0x2F, 0x8C, 0x61, 0x29, 0xB1, 0x96, 0x9A, 0x2F,
+ 0xEE, 0x97, 0x94, 0x09, 0xBC, 0x65, 0x25, 0x77, 0x52, 0xAE, 0x60, 0xC6, 0x2E, 0xCE, 0x34, 0xAB,
+ 0xD8, 0xEE, 0x32, 0x96, 0x60, 0xCC, 0xBD, 0x72, 0x10, 0xA5, 0xA7, 0xE5, 0x36, 0xE3, 0x38, 0xBE,
+ 0xE5, 0x24, 0x9A, 0x9C, 0x9B, 0xE6, 0x63, 0xBB, 0x1D, 0xB9, 0xC5, 0x82, 0x4E, 0xE9, 0xC8, 0xB0,
+ 0x0C, 0x68, 0x3F, 0x77, 0x79, 0xC9, 0x7F, 0xC6, 0xF3, 0x90, 0x23, 0x3A, 0xE6, 0x45, 0x16, 0x91,
+ 0xC5, 0x54, 0xDE, 0x6B, 0x9A, 0x81, 0xB5, 0x67, 0x4C, 0x23, 0x39, 0xC9, 0x8F, 0x86, 0x74, 0x55,
+ 0x15, 0x89, 0x5D, 0x42, 0xAB, 0x58, 0x6B, 0x87, 0x46, 0xB4, 0x55, 0xF3, 0xBC, 0xE8, 0x4E, 0x67,
+ 0xB9, 0xCF, 0x22, 0x76, 0x33, 0x90, 0x03, 0xCD, 0x31, 0xD8, 0xA2, 0x90, 0xCE, 0xC8, 0x9D, 0xF2,
+ 0x99, 0x85, 0xBA, 0x69, 0x31, 0xEB, 0x6F, 0x75, 0xB0, 0x8E, 0x75, 0xA8, 0xDF, 0x0C, 0xE7, 0x18,
+ 0xDA, 0x3A, 0xBB, 0xC3, 0xFD, 0xAC, 0xAE, 0x77, 0x9D, 0x68, 0xC3, 0xAA, 0x59, 0xCA, 0xAF, 0xAE,
+ 0x8A, 0xA3, 0x47, 0x0D, 0xE8, 0x69, 0x76, 0xE7, 0xD8, 0x73, 0x4E, 0x76, 0xB2, 0xCE, 0x0A, 0x30,
+ 0x55, 0xB2, 0xFA, 0xD9, 0xBF, 0x06, 0xB6, 0xB4, 0xA7, 0xDD, 0x52, 0x62, 0x97, 0x5B, 0x8A, 0xD8,
+ 0x36, 0xF4, 0xBD, 0xB6, 0xBD, 0xEC, 0x6E, 0xBB, 0x58, 0xD1, 0x9C, 0x76, 0x75, 0xAA, 0x1B, 0x2C,
+ 0xEC, 0x61, 0xCF, 0x1A, 0x69, 0x2B, 0xA2, 0xB4, 0x40, 0x27, 0xCC, 0xEE, 0x79, 0xAB, 0x1A, 0x98,
+ 0xDF, 0xE6, 0x71, 0xB8, 0xB9, 0xDD, 0x92, 0x2D, 0x73, 0xF9, 0xDC, 0xD5, 0xA6, 0x75, 0x41, 0x63,
+ 0xA4, 0xEE, 0x75, 0x97, 0x88, 0xB6, 0x3B, 0xF3, 0x36, 0xBC, 0x7D, 0x3D, 0x70, 0x3E, 0x07, 0xDB,
+ 0xC1, 0xB2, 0x4E, 0x38, 0x34, 0xC5, 0x65, 0xEB, 0x86, 0x3B, 0x3C, 0x73, 0xD1, 0x8E, 0x38, 0xAF,
+ 0x27, 0x4E, 0xF1, 0x15, 0x03, 0xD9, 0xE0, 0x91, 0xC6, 0xEF, 0xA7, 0xF0, 0x97, 0xEF, 0xD8, 0xCA,
+ 0x56, 0xD9, 0x2E, 0x03, 0x5F, 0x95, 0x49, 0x5E, 0x61, 0x68, 0x37, 0x9A, 0xDC, 0x08, 0x67, 0xF0,
+ 0xCA, 0x59, 0x2E, 0x18, 0x53, 0xF3, 0xBB, 0x88, 0xB3, 0x86, 0xB0, 0xD9, 0x68, 0xAE, 0x6F, 0x2D,
+ 0xE2, 0xD7, 0xE0, 0x07, 0xFF, 0xB2, 0xB9, 0xD3, 0x02, 0x4F, 0x2B, 0x19, 0xB3, 0x38, 0xA0, 0x74,
+ 0x75, 0xC2, 0x85, 0x3E, 0x3E, 0x3D, 0xCB, 0xFB, 0xE4, 0x38, 0x17, 0x29, 0x90, 0x75, 0x7E, 0xA3,
+ 0xC9, 0x49, 0x15, 0xEA, 0x51, 0x37, 0xF2, 0xD4, 0xD1, 0xDA, 0x53, 0x70, 0x57, 0xDC, 0xE2, 0xE3,
+ 0x06, 0x35, 0xB5, 0xCB, 0x5B, 0xED, 0x7F, 0x76, 0x0E, 0xD5, 0x99, 0xE9, 0xF7, 0xD8, 0xA7, 0x57,
+ 0xF5, 0x56, 0x43, 0xDB, 0xDF, 0x48, 0x4F, 0xF9, 0x05, 0x83, 0x7E, 0x1C, 0x2C, 0x43, 0xFD, 0xE1,
+ 0x04, 0x67, 0x36, 0xDF, 0x9C, 0x2D, 0x56, 0x8F, 0xB7, 0xFB, 0xD3, 0x6A, 0x5F, 0x7B, 0xDA, 0xF8,
+ 0xAE, 0x1A, 0xBF, 0x83, 0xFD, 0xD2, 0x2A, 0xBF, 0xF3, 0x03, 0xE5, 0xFC, 0x74, 0x0B, 0x85, 0x18,
+ 0xE5, 0x19, 0x5F, 0x3C, 0xE3, 0x25, 0x27, 0xEF, 0x3C, 0x2D, 0x86, 0x8A, 0x73, 0xEF, 0x72, 0xD9,
+ 0x05, 0x5E, 0xC1, 0xDB, 0x1D, 0x1D, 0xF3, 0x7A, 0x0F, 0xEA, 0xCB, 0xC8, 0x5C, 0xFA, 0xF4, 0x7D,
+ 0xBE, 0xE2, 0xA1, 0x17, 0x7D, 0xA8, 0xEC, 0xBE, 0xEF, 0x99, 0xB0, 0x19, 0xF5, 0xA9, 0xF7, 0x64,
+ 0xBD, 0xEA, 0x37, 0xA4, 0xEF, 0xC6, 0xEC, 0x55, 0x91, 0xFF, 0xF7, 0xE8, 0x4B, 0xFE, 0x1F, 0xD4,
+ 0x22, 0x3D, 0xE9, 0xC5, 0xA6, 0x31, 0x9D, 0x56, 0x6F, 0xE9, 0xDA, 0x0F, 0x52, 0x4B, 0xA8, 0x8A,
+ 0x7D, 0x81, 0x67, 0x6F, 0xF6, 0xD6, 0xFB, 0x29, 0xBA, 0xB8, 0xB7, 0xB7, 0xF2, 0x83, 0x3E, 0x1C,
+ 0xDE, 0xDB, 0x7E, 0x42, 0x1F, 0x3F, 0xA6, 0xF4, 0xC9, 0x38, 0xFC, 0x9A, 0x3B, 0x7F, 0x9C, 0x69,
+ 0xCF, 0xBA, 0xD2, 0x69, 0xBA, 0x79, 0xAC, 0xDD, 0xDD, 0x88, 0xD4, 0xDC, 0xA0, 0x45, 0x95, 0x9F,
+ 0x69, 0x6F, 0x12, 0xFD, 0x91, 0x7C, 0x59, 0x3A, 0xBD, 0x13, 0xAF, 0xF8, 0xEF, 0x4D, 0x13, 0x5B,
+ 0xE7, 0x63, 0x7A, 0xEB, 0xF4, 0x7B, 0x53, 0x15, 0x7C, 0x4C, 0x46, 0x7D, 0xF1, 0x06, 0x65, 0x21,
+ 0x84, 0x7D, 0xD9, 0x97, 0x73, 0xFE, 0x47, 0x25, 0x00, 0x48, 0x66, 0x02, 0x38, 0x80, 0x42, 0x36,
+ 0x1A, 0xE3, 0x67, 0x6D, 0x69, 0x15, 0x70, 0x45, 0xA6, 0x80, 0x27, 0xC5, 0x80, 0xEA, 0xA7, 0x75,
+ 0xF4, 0x77, 0x23, 0xEE, 0x42, 0x3C, 0x13, 0x48, 0x81, 0xFB, 0x22, 0x6E, 0xF5, 0xD7, 0x2C, 0xF7,
+ 0x77, 0x6B, 0x25, 0x78, 0x7A, 0x79, 0x97, 0x7B, 0x52, 0x13, 0x81, 0xCD, 0xA7, 0x5D, 0x83, 0x14,
+ 0x7F, 0x24, 0x18, 0x78, 0x42, 0x57, 0x49, 0x1A, 0xE8, 0x72, 0x2D, 0xA8, 0x73, 0xC7, 0x97, 0x79,
+ 0x21, 0x38, 0x1C, 0x23, 0x38, 0x83, 0x3D, 0xA8, 0x4F, 0x37, 0x88, 0x76, 0xFC, 0xA5, 0x69, 0x2B,
+ 0x98, 0x6D, 0x45, 0x18, 0x82, 0xC7, 0x87, 0x7C, 0x20, 0xF8, 0x80, 0x3F, 0x32, 0x84, 0x44, 0xF8,
+ 0x7D, 0x99, 0x54, 0x81, 0x09, 0xC4, 0x7D, 0x0A, 0xB7, 0x6A, 0x84, 0xC7, 0x83, 0x56, 0x28, 0x6A,
+ 0x05, 0xF7, 0x83, 0x30, 0x38, 0x30, 0xCC, 0x57, 0x85, 0x5F, 0x88, 0x4D, 0xF2, 0x37, 0x63, 0x52,
+ 0xB8, 0x7E, 0x23, 0xD7, 0x85, 0x2C, 0x78, 0x86, 0xB7, 0xF7, 0x82, 0xDA, 0x17, 0x84, 0x89, 0x41,
+ 0x85, 0x61, 0xD7, 0x84, 0x26, 0x76, 0x61, 0x76, 0x36, 0x87, 0x4A, 0x78, 0x7F, 0x3E, 0x07, 0x7F,
+ 0x97, 0x27, 0x87, 0x8A, 0x07, 0x26, 0x76, 0xF8, 0x7E, 0x55, 0x64, 0x82, 0xAB, 0x04, 0x71, 0xFD,
+ 0x07, 0x56, 0x3B, 0xE8, 0x46, 0x5B, 0x51, 0x5D, 0x2E, 0xF1, 0x84, 0x40, 0x88, 0x2B, 0x65, 0x18,
+ 0x76, 0x34, 0x58, 0x83, 0x36, 0x08, 0x4A, 0x94, 0x02, 0x84, 0xA2, 0x05, 0x5E, 0xBA, 0xF5, 0x88,
+ 0x11, 0x16, 0x86, 0x82, 0xB8, 0x76, 0xF5, 0xD2, 0x62, 0x86, 0xD3, 0x7B, 0xD7, 0x13, 0x7E, 0x6E,
+ 0xB5, 0x7D, 0xF8, 0xA6, 0x2B, 0x4F, 0xC6, 0x81, 0x27, 0x45, 0x7F, 0x4F, 0x08, 0x85, 0x37, 0xC5,
+ 0x75, 0x50, 0x04, 0x61, 0xDE, 0x87, 0x87, 0x99, 0xF8, 0x47, 0xF9, 0x92, 0x7C, 0x99, 0x98, 0x66,
+ 0x2B, 0xB6, 0x3F, 0x4E, 0x28, 0x86, 0xF6, 0x96, 0x69, 0x1B, 0xF3, 0x41, 0xA8, 0x88, 0x86, 0x0A,
+ 0x53, 0x2E, 0x51, 0xE8, 0x7B, 0x96, 0xD6, 0x4E, 0x96, 0x07, 0x86, 0x88, 0xF7, 0x81, 0xB5, 0xB8,
+ 0x86, 0x01, 0x73, 0x8C, 0xC8, 0xA8, 0x8B, 0x8D, 0x25, 0x38, 0xC3, 0xD4, 0x8A, 0x04, 0xF8, 0x18,
+ 0x70, 0x27, 0x8C, 0x0F, 0x28, 0x89, 0x99, 0x37, 0x34, 0xCC, 0x02, 0x3C, 0x5A, 0xE1, 0x8D, 0xA7,
+ 0xD2, 0x8D, 0x72, 0xA4, 0x8A, 0x14, 0xB6, 0x30, 0x29, 0xE4, 0x82, 0xD4, 0x88, 0x81, 0xF7, 0xA6,
+ 0x5E, 0x19, 0xD5, 0x79, 0xE9, 0xA3, 0x63, 0x3F, 0x76, 0x44, 0xDA, 0xC6, 0x1A, 0xF0, 0x68, 0x3D,
+ 0x17, 0x57, 0x6F, 0x7C, 0x98, 0x83, 0xE1, 0x92, 0x3F, 0xE9, 0x98, 0x8A, 0xDF, 0x48, 0x83, 0x0D,
+ 0x63, 0x1B, 0xDC, 0x75, 0x90, 0xA1, 0xF8, 0x10, 0xC4, 0x38, 0x86, 0x5F, 0xA5, 0x41, 0x53, 0x04,
+ 0x8A, 0xED, 0x28, 0x29, 0xCA, 0x83, 0x90, 0x0C, 0x09, 0x73, 0x78, 0xE8, 0x81, 0x02, 0xC9, 0x87,
+ 0x0F, 0x18, 0x61, 0xA4, 0x86, 0x8F, 0xF9, 0xB8, 0x8D, 0x93, 0x93, 0x91, 0x17, 0x09, 0x7D, 0xFD,
+ 0x06, 0x87, 0xC6, 0x37, 0x8F, 0xD5, 0xC8, 0x60, 0xD5, 0x45, 0x48, 0xE8, 0xA3, 0x8D, 0xAF, 0xA5,
+ 0x8D, 0x26, 0xD9, 0x8F, 0x97, 0xD6, 0x84, 0x1E, 0x79, 0x5E, 0x49, 0xA8, 0x73, 0x51, 0xA3, 0x82,
+ 0x24, 0x59, 0x92, 0x73, 0xE5, 0x1D, 0x1A, 0xF9, 0x77, 0x75, 0x66, 0x91, 0xFA, 0xE7, 0x92, 0xF4,
+ 0xB8, 0x75, 0x22, 0x64, 0x89, 0xE7, 0x37, 0x33, 0x60, 0xD7, 0x8D, 0x51, 0xC9, 0x7A, 0x41, 0x29,
+ 0x40, 0xE9, 0xA7, 0x5F, 0x3D, 0x19, 0x84, 0x9B, 0x33, 0x83, 0x4F, 0x99, 0x1F, 0x53, 0x29, 0x95,
+ 0x5F, 0x59, 0x3D, 0xC5, 0x57, 0x5E, 0xFC, 0x07, 0x92, 0x9C, 0xF8, 0x5D, 0x8E, 0xF7, 0x95, 0x44,
+ 0x09, 0x7E, 0xC5, 0x21, 0x96, 0x63, 0xC9, 0x5B, 0x65, 0xB9, 0x88, 0x67, 0x89, 0x66, 0x55, 0xA9,
+ 0x96, 0x48, 0x99, 0x87, 0x1B, 0xD9, 0x90, 0x6F, 0x49, 0x53, 0x71, 0xC9, 0x86, 0xF6, 0xD8, 0x94,
+ 0x5C, 0x69, 0x7D, 0x76, 0x19, 0x8D, 0x78, 0x19, 0x77, 0xB3, 0x45, 0x1F, 0x64, 0xF9, 0x91, 0xBE,
+ 0xC8, 0x5B, 0x0B, 0x99, 0x86, 0xB0, 0x38, 0x98, 0x28, 0xB9, 0x8B, 0x2A, 0xC9, 0x91, 0xF9, 0x17,
+ 0x58, 0x7D, 0xD9, 0x8C, 0x17, 0xD5, 0x98, 0x58, 0x28, 0x98, 0x90, 0x99, 0x8C, 0xCE, 0xF8, 0x72,
+ 0x6E, 0x99, 0x19, 0x49, 0x89, 0x71, 0x73, 0x19, 0x3F, 0x9A, 0x99, 0x90, 0x5D, 0xD9, 0x99, 0x91,
+ 0xD9, 0x89, 0x79, 0x19, 0x9A, 0x7B, 0xE9, 0x7F, 0x8A, 0xB9, 0x98, 0x9A, 0x07, 0x98, 0x09, 0xA9,
+ 0x9A, 0x88, 0x69, 0x84, 0x41, 0x81, 0x93, 0x2B, 0x49, 0x98, 0xB2, 0x88, 0x95, 0x39, 0x18, 0x5C,
+ 0x2F, 0x59, 0x98, 0x6A, 0x39, 0x94, 0x5F, 0xC9, 0x96, 0x70, 0x83, 0x6B, 0x7A, 0x29, 0x9A, 0x96,
+ 0xE9, 0x9B, 0x64, 0x77, 0x88, 0x34, 0x46, 0x9B, 0x83, 0xC9, 0x9A, 0x51, 0x69, 0x9C, 0x45, 0xF3,
+ 0x5A, 0x87, 0x79, 0x9B, 0x70, 0xC9, 0x9C, 0xB2, 0x07, 0x88, 0xB2, 0x79, 0x93, 0xC5, 0xE9, 0x9D,
+ 0xD8, 0xF9, 0x99, 0x01, 0x16, 0x37, 0x6E, 0x14, 0x8C, 0x0E, 0x99, 0x9D, 0x3C, 0x69, 0x39, 0xE2,
+ 0x78, 0x9A, 0xB6, 0xF9, 0x99, 0x6B, 0xA9, 0x8F, 0xEE, 0x65, 0x65, 0xE5, 0xF9, 0x87, 0x2C, 0x79,
+ 0x95, 0xE8, 0x45, 0x77, 0xEB, 0x09, 0x9D, 0xD1, 0x29, 0x99, 0xAF, 0xE9, 0x9D, 0xF1, 0xF9, 0x62,
+ 0x33, 0xA9, 0x9C, 0x2D, 0x29, 0x5D, 0x54, 0x17, 0x8F, 0xEC, 0xD9, 0x9E, 0xEE, 0xD9, 0x9F, 0xD2,
+ 0xC9, 0x36, 0x4D, 0xE6, 0x6C, 0x65, 0xF6, 0x9E, 0xBD, 0x99, 0x9E, 0x41, 0xC5, 0x92, 0xFC, 0x88,
+ 0xA0, 0x3A, 0xA6, 0xA0, 0x60, 0x45, 0x22, 0xD6, 0xE9, 0xA0, 0x01, 0x4A, 0x21, 0x9E, 0x19, 0x90,
+ 0x23, 0x36, 0xA1, 0xAB, 0xA9, 0x16, 0x09, 0x3A, 0x9C, 0xB8, 0x89, 0xA1, 0x60, 0xB6, 0x1A, 0xC8,
+ 0xE9, 0x64, 0xF3, 0xF9, 0x46, 0x63, 0x35, 0x9A, 0x04, 0xAA, 0x9E, 0x14, 0x2A, 0x9C, 0xB6, 0x09,
+ 0x88, 0x19, 0x8A, 0xA2, 0xC4, 0x09, 0x9A, 0x5D, 0xD8, 0xA1, 0x2B, 0x52, 0x9F, 0xF6, 0xB9, 0x5F,
+ 0x32, 0x4A, 0x93, 0x35, 0x7A, 0x97, 0x17, 0xCA, 0x9B, 0xFC, 0x39, 0x99, 0x2C, 0x9A, 0x45, 0x67,
+ 0xC7, 0x2D, 0x70, 0xB8, 0x93, 0xB4, 0xB8, 0x3B, 0xE7, 0x79, 0xA4, 0x43, 0x5A, 0x94, 0xC5, 0x81,
+ 0x88, 0x6D, 0x49, 0x74, 0x69, 0x69, 0x1F, 0x3E, 0x5A, 0x14, 0x12, 0x8A, 0x9F, 0x9E, 0x99, 0xA2,
+ 0x43, 0x8A, 0x89, 0x0B, 0x1A, 0xA5, 0x25, 0x8A, 0xA4, 0x14, 0xF6, 0x57, 0xA9, 0x59, 0x82, 0x3B,
+ 0xA9, 0x6A, 0xBC, 0x19, 0xA6, 0x64, 0x9A, 0x92, 0x18, 0xDA, 0x99, 0x8D, 0xA8, 0xA4, 0xE1, 0xC8,
+ 0x9B, 0x6B, 0x8A, 0x68, 0xC5, 0xE7, 0x70, 0x16, 0x0A, 0x3B, 0x55, 0x4A, 0xA3, 0x3A, 0x9A, 0xA4,
+ 0xD9, 0x65, 0x78, 0x44, 0x1A, 0x90, 0x7A, 0xA5, 0x96, 0x80, 0x87, 0xA3, 0xD4, 0x49, 0xA4, 0x63,
+ 0xFA, 0xA7, 0x93, 0x26, 0x63, 0x53, 0x19, 0x87, 0x40, 0x5A, 0xA0, 0x51, 0x49, 0x99, 0x33, 0xAA,
+ 0x8C, 0x71, 0x2A, 0xA7, 0x6E, 0x18, 0xA8, 0x16, 0xB6, 0xA5, 0x91, 0x78, 0x9F, 0x92, 0x6A, 0x94,
+ 0x94, 0x9A, 0x9F, 0x27, 0x5A, 0xA9, 0x39, 0xDA, 0x9A, 0x80, 0xAA, 0xA9, 0x95, 0xF6, 0xA1, 0x0C,
+ 0x78, 0x67, 0x53, 0xE9, 0x9A, 0x06, 0x6A, 0xA9, 0x8A, 0xBA, 0xA8, 0x86, 0x39, 0x68, 0x86, 0x38,
+ 0xA8, 0x82, 0x05, 0x9E, 0x3F, 0xA7, 0x60, 0x9C, 0x69, 0xA3, 0xB0, 0xFA, 0xA5, 0x37, 0x0A, 0xAA,
+ 0xD8, 0xD6, 0xA2, 0x69, 0xDA, 0xA4, 0x63, 0x24, 0xA5, 0xC7, 0xF5, 0x8A, 0xE6, 0xC9, 0xAB, 0xBD,
+ 0x5A, 0x84, 0xB6, 0xB9, 0x84, 0x59, 0xFA, 0xA6, 0xC5, 0x6A, 0xAC, 0x0E, 0x63, 0x7E, 0x75, 0xEA,
+ 0x9C, 0x54, 0x6A, 0xA4, 0x60, 0x7A, 0xA5, 0x99, 0x2A, 0xAC, 0x8F, 0x89, 0x7E, 0xCC, 0x53, 0xA6,
+ 0xDF, 0x58, 0x78, 0xD5, 0xDA, 0x81, 0xBF, 0x14, 0x9E, 0xB2, 0xCA, 0xA8, 0x67, 0x8A, 0xAC, 0xE3,
+ 0x8A, 0x92, 0xD1, 0x3A, 0x73, 0xC9, 0x46, 0x7A, 0x86, 0x27, 0x8D, 0x66, 0x04, 0xA1, 0xBF, 0x0A,
+ 0xAC, 0xA7, 0x8A, 0xAA, 0x82, 0x7A, 0x7D, 0xB7, 0xDA, 0x86, 0xEF, 0x5A, 0x72, 0x84, 0x26, 0xAF,
+ 0xF3, 0xAA, 0xA0, 0x35, 0x3A, 0xA7, 0x74, 0xBA, 0xA4, 0x5F, 0xBA, 0xAF, 0x6D, 0x78, 0x89, 0x41,
+ 0x02, 0xAF, 0x0D, 0xA7, 0x7F, 0xE0, 0x0A, 0x7F, 0x03, 0xBB, 0x82, 0x39, 0xD9, 0x82, 0x08, 0xDB,
+ 0x87, 0xBB, 0x0A, 0x34, 0x0C, 0xBB, 0x8F, 0xFD, 0x85, 0xAB, 0xAE, 0xD7, 0xAC, 0x12, 0x3B, 0xB1,
+ 0xF1, 0x58, 0x53, 0x12, 0x57, 0x69, 0x94, 0xC7, 0xA3, 0x4C, 0x79, 0xAE, 0x46, 0x14, 0xB1, 0x7E,
+ 0x08, 0xB2, 0x56, 0xE9, 0x38, 0xCA, 0x44, 0x9F, 0xB4, 0xDA, 0xA1, 0x27, 0x9B, 0xAD, 0x94, 0x32,
+ 0xA4, 0x73, 0x7A, 0xA8, 0xBE, 0x6A, 0x8D, 0xFC, 0x25, 0x7F, 0x0D, 0x9A, 0xAE, 0x32, 0x06, 0x62,
+ 0x74, 0xC8, 0xB1, 0x9A, 0x13, 0xA6, 0x37, 0x8B, 0xB3, 0x33, 0xFA, 0xAD, 0x1B, 0x96, 0xAC, 0xC8,
+ 0xE2, 0xAF, 0xB0, 0x07, 0x68, 0xD2, 0x2A, 0xB4, 0x0A, 0x8B, 0x9A, 0x3E, 0x8B, 0xAF, 0x06, 0x9B,
+ 0x8F, 0x48, 0x1B, 0x7F, 0x55, 0xCB, 0x36, 0xE2, 0x2A, 0x75, 0x91, 0x27, 0xB4, 0x5E, 0x02, 0x7D,
+ 0xAF, 0x47, 0x73, 0xA1, 0x4A, 0x43, 0x2B, 0xB4, 0x8D, 0x24, 0xB9, 0xAD, 0x20, 0xE7, 0xB4, 0x5E,
+ 0x4B, 0x1A, 0x2A, 0x19, 0xB6, 0x3B, 0x3A, 0xB6, 0xD6, 0x5A, 0xB6, 0x9D, 0x98, 0xA5, 0x99, 0x6A,
+ 0xB4, 0x21, 0x17, 0x39, 0x44, 0xBB, 0xA9, 0xA0, 0xF4, 0xB3, 0x4A, 0x4B, 0xAE, 0x32, 0x37, 0xB7,
+ 0x3C, 0x5A, 0x34, 0x45, 0x17, 0xA0, 0x39, 0x86, 0xB2, 0xB3, 0x9A, 0xAA, 0x51, 0x87, 0xA6, 0xB5,
+ 0xFA, 0x90, 0x82, 0x37, 0x52, 0x83, 0x73, 0xAC, 0x1B, 0xF8, 0xB8, 0x9E, 0x06, 0xA5, 0x1E, 0xBB,
+ 0xA9, 0x6D, 0xF2, 0x61, 0x41, 0x09, 0x89, 0x22, 0x27, 0x69, 0x81, 0x8B, 0x39, 0x83, 0x0B, 0x7A,
+ 0x7F, 0x59, 0xAF, 0xDA, 0x6A, 0x61, 0x0F, 0x73, 0x9D, 0xC2, 0x28, 0xB7, 0x43, 0x27, 0xB9, 0x61,
+ 0xA5, 0xAE, 0x6B, 0x16, 0x58, 0xD2, 0xAA, 0x9B, 0x2B, 0x8A, 0x40, 0xA6, 0xA6, 0xB9, 0x97, 0x65,
+ 0x7F, 0x2C, 0x9B, 0x3A, 0x9F, 0xDB, 0xBA, 0xF7, 0xF4, 0xBA, 0xB1, 0x1B, 0xB5, 0x81, 0x29, 0xBB,
+ 0x51, 0xCB, 0xB8, 0x8D, 0x7B, 0x6A, 0x3D, 0x4B, 0xAD, 0x8C, 0xF6, 0x66, 0x0F, 0xAB, 0x9B, 0xBE,
+ 0x0B, 0xBC, 0x59, 0x4A, 0xBB, 0xC3, 0xBB, 0x4F, 0x05, 0xEB, 0xBB, 0x5A, 0xCB, 0xBA, 0x57, 0xE7,
+ 0x44, 0x45, 0x7A, 0xB8, 0xFF, 0xC9, 0xBC, 0x59, 0xEB, 0xBC, 0xEE, 0x56, 0x6B, 0x86, 0x95, 0xAF,
+ 0xB8, 0xAB, 0xAB, 0x26, 0xC7, 0xA7, 0x61, 0xA9, 0xB4, 0xDA, 0x6B, 0x88, 0xDC, 0xAB, 0x61, 0xDE,
+ 0xDB, 0x73, 0x88, 0x0B, 0x51, 0xA6, 0xAB, 0xAC, 0xC9, 0xFB, 0x73, 0x29, 0x76, 0xBE, 0xEF, 0x97,
+ 0xBE, 0x29, 0x38, 0x79, 0x24, 0x24, 0x3A, 0x19, 0x48, 0x2E, 0x13, 0x4B, 0xBE, 0xF6, 0x4A, 0x68,
+ 0xF4, 0x7B, 0x77, 0x36, 0x85, 0x41, 0x3A, 0xC8, 0xBE, 0xEE, 0x24, 0x93, 0xE8, 0xA3, 0xBA, 0xF0,
+ 0xBB, 0xB6, 0x52, 0xDB, 0x59, 0x01, 0x6C, 0xB0, 0x03, 0x5C, 0xBB, 0x05, 0x0C, 0x8E, 0x07, 0x7C,
+ 0x45, 0x33, 0xF9, 0xA0, 0xEC, 0x58, 0xAA, 0x1B, 0x0A, 0x77, 0x0F, 0x0C, 0x8F, 0x11, 0xDC, 0xB8,
+ 0x9C, 0xDB, 0xA8, 0xE2, 0x47, 0x1C, 0x58, 0xA6, 0xC0, 0xD6, 0x1A, 0xBF, 0x2C, 0xB3, 0x99, 0x0F,
+ 0xFC, 0xAA, 0x74, 0x88, 0x63, 0x90, 0x9B, 0x46, 0xCF, 0xC7, 0x7A, 0x2A, 0xBC, 0x53, 0xFE, 0x6B,
+ 0xA6, 0x86, 0x76, 0xBE, 0x63, 0xC2, 0xC2, 0x2D, 0x3C, 0x6C, 0x06, 0x66, 0x48, 0x35, 0x78, 0x84,
+ 0x8E, 0xA9, 0x6E, 0x35, 0xDC, 0xBB, 0xA2, 0x83, 0xC3, 0xD5, 0x31, 0xA2, 0x06, 0x58, 0x55, 0x1F,
+ 0x47, 0x32, 0x98, 0x18, 0x3B, 0xF2, 0x1B, 0x8C, 0x43, 0xDC, 0xB3, 0x57, 0x72, 0xB9, 0xD1, 0xFB,
+ 0x8F, 0x6F, 0xAA, 0x85, 0x70, 0xE6, 0x8E, 0xB2, 0xF4, 0x4A, 0xE6, 0x6B, 0xC2, 0x35, 0xA4, 0xBC,
+ 0x39, 0x23, 0x9F, 0x54, 0x8B, 0xB8, 0xFB, 0x83, 0xBA, 0xD2, 0x39, 0x93, 0x5C, 0xEC, 0x73, 0x20,
+ 0x17, 0x8B, 0xD7, 0x1B, 0x65, 0x1E, 0xD5, 0x88, 0x9D, 0xEB, 0xAB, 0x22, 0x0B, 0x9E, 0xBD, 0xC3,
+ 0x45, 0x5D, 0xEC, 0xC5, 0x51, 0x1C, 0xC4, 0xC7, 0x44, 0xB0, 0x75, 0xA9, 0xAF, 0x57, 0x4B, 0xC7,
+ 0x7B, 0xD6, 0x15, 0xE8, 0xF6, 0x87, 0x69, 0xFB, 0xC5, 0x4F, 0xBC, 0x2B, 0x4B, 0x58, 0xC5, 0xDD,
+ 0x0A, 0x5A, 0x7F, 0xBC, 0xA8, 0x75, 0x0C, 0x2B, 0x1C, 0x47, 0xC8, 0x78, 0x9C, 0xC7, 0x6E, 0xFC,
+ 0xC6, 0xDB, 0xEA, 0x53, 0xE6, 0x5A, 0xB1, 0x34, 0xFB, 0xC8, 0x87, 0x11, 0x38, 0x77, 0x8C, 0xC1,
+ 0x27, 0x3C, 0xAA, 0x4E, 0xC9, 0x38, 0x89, 0xFC, 0x89, 0x07, 0x52, 0x26, 0x73, 0x0C, 0xC8, 0x9D,
+ 0x66, 0x4E, 0xF1, 0xF2, 0xC9, 0x32, 0xAB, 0x9F, 0x54, 0xD9, 0x3D, 0x97, 0xAC, 0xAE, 0xA9, 0x92,
+ 0x3E, 0x8D, 0xBC, 0xA0, 0xA0, 0xCC, 0xCA, 0xB4, 0x62, 0xC5, 0x85, 0x0C, 0xCB, 0xA3, 0xBC, 0x6F,
+ 0xA5, 0xFC, 0x86, 0x27, 0xE5, 0x4F, 0xB7, 0x7C, 0xA3, 0xA0, 0x3C, 0x50, 0x91, 0x1C, 0x8C, 0x76,
+ 0xBB, 0x95, 0xD0, 0xF3, 0xC2, 0xB4, 0x57, 0xC1, 0xD3, 0x51, 0xCC, 0x29, 0x7A, 0xCC, 0x40, 0x95,
+ 0xCC, 0x1C, 0x0C, 0x79, 0xCE, 0x93, 0xA8, 0x26, 0x2C, 0xB8, 0x56, 0x37, 0x7F, 0xD2, 0x24, 0x3F,
+ 0xF0, 0x59, 0xBD, 0xF1, 0xE1, 0xC9, 0x1F, 0x71, 0xC3, 0xD8, 0x7C, 0x2D, 0x46, 0xB8, 0xCD, 0xD3,
+ 0xFB, 0xCC, 0xD0, 0xFC, 0xCD, 0x04, 0x2C, 0x9E, 0x7E, 0x87, 0x54, 0xD6, 0x1C, 0x4A, 0x76, 0x73,
+ 0xCE, 0xE3, 0x8C, 0x97, 0xEA, 0x3C, 0xCB, 0xB1, 0x24, 0x94, 0x5E, 0xD6, 0xBD, 0xBF, 0x28, 0xC0,
+ 0x54, 0x65, 0xCD, 0x98, 0x6B, 0xCF, 0x6F, 0xA5, 0x4F, 0xEA, 0xBC, 0xB4, 0xD5, 0x37, 0xC2, 0xD1,
+ 0xFC, 0xB7, 0xBB, 0x08, 0xD0, 0xBB, 0xDB, 0xCA, 0xF9, 0x8B, 0x40, 0x8A, 0x78, 0x53, 0x5A, 0x8C,
+ 0x58, 0xFA, 0xAC, 0xC8, 0xAB, 0x08, 0x9C, 0x20, 0xFC, 0xAB, 0xFF, 0xB8, 0x74, 0xF3, 0x2C, 0xBE,
+ 0xAB, 0xCC, 0x52, 0xF8, 0xFC, 0xAC, 0x94, 0x87, 0xD1, 0x59, 0x28, 0x30, 0xE0, 0xEC, 0x9E, 0x1D,
+ 0xED, 0xBA, 0x02, 0x0D, 0xD2, 0x67, 0xC7, 0xD2, 0xBE, 0x77, 0xCC, 0x7C, 0x92, 0xB1, 0x40, 0xAB,
+ 0x3B, 0xFE, 0xAC, 0xCA, 0x88, 0xE5, 0xB0, 0x2D, 0xFD, 0x61, 0xBF, 0xBB, 0x87, 0x93, 0x54, 0xD1,
+ 0x0A, 0x18, 0xB3, 0xE3, 0xEB, 0xCE, 0x1B, 0xBD, 0xC9, 0x39, 0xCD, 0x76, 0x1F, 0xDD, 0x56, 0x32,
+ 0x8C, 0x82, 0xC2, 0xD5, 0xC0, 0x63, 0x76, 0xC9, 0x5C, 0x4B, 0xD4, 0x37, 0xCD, 0xB1, 0x47, 0x1D,
+ 0xBA, 0x0B, 0xB7, 0x59, 0x7D, 0xD2, 0xB4, 0x3E, 0xE9, 0x8C, 0xCF, 0xBA, 0xCE, 0x3F, 0x2B, 0xAA,
+ 0xB6, 0xC8, 0xC3, 0x5A, 0x42, 0xB1, 0x04, 0xE5, 0x74, 0x91, 0x15, 0x32, 0x3E, 0x5D, 0xAE, 0x4E,
+ 0x9D, 0x9A, 0x6E, 0x68, 0xB2, 0x0B, 0xBD, 0xB9, 0xBC, 0x4B, 0xD6, 0x8F, 0x36, 0xD3, 0x67, 0xED,
+ 0xC4, 0x38, 0x88, 0x4A, 0x87, 0x9C, 0xA6, 0x84, 0x27, 0xD3, 0x28, 0x2D, 0xC1, 0x71, 0xCD, 0xA9,
+ 0xD6, 0xC5, 0xCD, 0xDC, 0xBA, 0x8C, 0x77, 0x8D, 0xD7, 0x40, 0xBD, 0xA2, 0x03, 0xBD, 0x5B, 0x52,
+ 0x2D, 0x79, 0x34, 0xFB, 0x9A, 0xB2, 0xF6, 0x5F, 0x63, 0x4C, 0xD8, 0x48, 0x18, 0xD8, 0x41, 0x1C,
+ 0xBB, 0xDC, 0x2A, 0xC4, 0x8B, 0x9D, 0x5E, 0x9D, 0x09, 0xB0, 0xE5, 0x14, 0x56, 0x9D, 0x66, 0x2D,
+ 0x5D, 0xAB, 0xD2, 0x56, 0x5C, 0xC9, 0x10, 0x9C, 0xD9, 0xBF, 0xA9, 0xC1, 0x02, 0xCA, 0x98, 0xB5,
+ 0x94, 0x6B, 0x05, 0xC8, 0x75, 0x45, 0x3A, 0xDA, 0xB1, 0x1C, 0xCF, 0xA6, 0xED, 0x66, 0xAA, 0xA9,
+ 0x7F, 0x9D, 0x5D, 0x68, 0x02, 0x10, 0x10, 0x00
+};
+
+const uint8_t Game::_pwr2_screenTransformData[2152] = {
+ 0x00, 0x01, 0x08, 0x1C, 0x48, 0xB0, 0xA0, 0xC1, 0x83, 0x08, 0x13, 0x2A, 0x5C, 0xC8, 0xB0, 0xA1,
+ 0xC3, 0x87, 0x10, 0x23, 0x4A, 0x9C, 0x48, 0xB1, 0xA2, 0xC5, 0x8B, 0x18, 0x33, 0x6A, 0xDC, 0xC8,
+ 0xB1, 0xA3, 0xC7, 0x8F, 0x20, 0x43, 0x8A, 0x1C, 0x49, 0xB2, 0xA4, 0xC9, 0x93, 0x28, 0x53, 0xAA,
+ 0x5C, 0xC9, 0xB2, 0xA5, 0xCB, 0x97, 0x30, 0x63, 0xCA, 0x9C, 0x49, 0xB3, 0xA6, 0xCD, 0x9B, 0x38,
+ 0x73, 0xEA, 0xDC, 0xC9, 0xB3, 0xA7, 0xCF, 0x9F, 0x40, 0x83, 0x0A, 0x1D, 0x4A, 0xB4, 0xA8, 0xD1,
+ 0xA3, 0x48, 0x93, 0x2A, 0x5D, 0xCA, 0xB4, 0xA9, 0xD3, 0xA7, 0x50, 0xA3, 0x4A, 0x9D, 0x4A, 0xB5,
+ 0xAA, 0xD5, 0xAB, 0x58, 0xB3, 0x6A, 0xDD, 0xCA, 0xB5, 0xAB, 0xD7, 0xAF, 0x60, 0xC3, 0x8A, 0x1D,
+ 0x4B, 0xB6, 0xAC, 0xD9, 0xB3, 0x68, 0xD3, 0xAA, 0x5D, 0xCB, 0xB6, 0xAD, 0xDB, 0xB7, 0x70, 0xE3,
+ 0xCA, 0x9D, 0x4B, 0xB7, 0xAE, 0xDD, 0xBB, 0x78, 0xF3, 0xEA, 0xDD, 0xCB, 0xB7, 0xAF, 0xDF, 0xBF,
+ 0x80, 0x03, 0x0B, 0x1E, 0x4C, 0xB8, 0xB0, 0xE1, 0xC3, 0x88, 0x13, 0x2B, 0x5E, 0xCC, 0xB8, 0xB1,
+ 0xE3, 0xC7, 0x90, 0x23, 0x4B, 0x9E, 0x4C, 0xB9, 0xB2, 0xE5, 0xCB, 0x98, 0x33, 0x6B, 0xDE, 0xCC,
+ 0xB9, 0xB3, 0xE7, 0xCF, 0xA0, 0x43, 0x8B, 0x1E, 0x4D, 0xBA, 0xB4, 0xE9, 0xD3, 0xA8, 0x53, 0xAB,
+ 0x5E, 0xCD, 0xBA, 0xB5, 0xEB, 0xD7, 0xB0, 0x63, 0xCB, 0x9E, 0x4D, 0xBB, 0xB6, 0xED, 0xDB, 0xB8,
+ 0x73, 0xEB, 0xDE, 0xCD, 0xBB, 0xB7, 0xEF, 0xDF, 0xC0, 0x83, 0x0B, 0x1F, 0x4E, 0xBC, 0xB8, 0xF1,
+ 0xE3, 0xC8, 0x93, 0x2B, 0x5F, 0xCE, 0xBC, 0xB9, 0xF3, 0xE7, 0xD0, 0xA3, 0x4B, 0x9F, 0x4E, 0xBD,
+ 0xBA, 0xF5, 0xEB, 0xD8, 0xB3, 0x6B, 0xDF, 0xCE, 0xBD, 0xBB, 0xF7, 0xEF, 0xE0, 0xC3, 0x8B, 0x1F,
+ 0x4F, 0xBE, 0xBC, 0xF9, 0xF3, 0xE8, 0xD3, 0xAB, 0x5F, 0xCF, 0xBE, 0xBD, 0xFB, 0xF7, 0xF0, 0xE3,
+ 0xCB, 0x9F, 0x4F, 0xBF, 0xBE, 0xFD, 0xFB, 0xF8, 0xF3, 0xEB, 0xDF, 0xCF, 0xBF, 0xBF, 0xFF, 0xFF,
+ 0x00, 0x06, 0x28, 0xE0, 0x80, 0x04, 0x16, 0x68, 0xE0, 0x81, 0x08, 0x26, 0xA8, 0xE0, 0x82, 0x0C,
+ 0x36, 0x58, 0x52, 0x00, 0x01, 0xF4, 0x07, 0x21, 0x84, 0xFB, 0x4D, 0x48, 0x61, 0x7D, 0x16, 0x66,
+ 0x78, 0xE1, 0x43, 0x11, 0x3E, 0x68, 0x50, 0x86, 0xAC, 0x69, 0xA8, 0xA1, 0x00, 0x16, 0x72, 0x08,
+ 0x22, 0x48, 0x13, 0x12, 0x24, 0xE2, 0x86, 0xA5, 0xAD, 0x98, 0x21, 0x89, 0x25, 0x32, 0xE4, 0x62,
+ 0x87, 0x1C, 0xC5, 0x38, 0x23, 0x8D, 0x1F, 0xC6, 0x88, 0xD9, 0x8D, 0x01, 0xC0, 0x78, 0x62, 0x42,
+ 0x3C, 0xA6, 0x88, 0x51, 0x90, 0x1A, 0xE6, 0xF8, 0x23, 0x65, 0x44, 0x0A, 0xE0, 0xA3, 0x90, 0x40,
+ 0x06, 0xD9, 0x10, 0x8B, 0x07, 0x11, 0x79, 0xA4, 0x8B, 0x4D, 0xD6, 0x28, 0x10, 0x95, 0x5F, 0x75,
+ 0xC8, 0xA3, 0x92, 0x5C, 0xFA, 0x28, 0x23, 0x8F, 0x0E, 0xE9, 0x18, 0xA5, 0x94, 0x4C, 0x02, 0x80,
+ 0xA5, 0x8A, 0x45, 0x0E, 0xE9, 0xA4, 0x54, 0x50, 0x5E, 0x39, 0xD0, 0x96, 0x5D, 0x72, 0x59, 0x26,
+ 0x42, 0x60, 0x86, 0x29, 0x26, 0x9A, 0x64, 0xB2, 0x88, 0x65, 0x9D, 0x15, 0x91, 0x19, 0xD1, 0x89,
+ 0x6D, 0x5E, 0x84, 0x23, 0x45, 0x69, 0xBE, 0x99, 0x64, 0x9C, 0x72, 0x06, 0x8A, 0xE7, 0x99, 0x4F,
+ 0x1E, 0x79, 0x65, 0x9E, 0x65, 0x42, 0x5A, 0x28, 0x90, 0x66, 0x1A, 0xB9, 0xA6, 0x9D, 0x37, 0xBE,
+ 0xE9, 0xE6, 0xA6, 0x98, 0x0A, 0xE9, 0xA8, 0xA5, 0x3F, 0x96, 0xB8, 0x22, 0xA2, 0x71, 0xCE, 0x59,
+ 0x90, 0x93, 0x11, 0x0E, 0x3A, 0xA6, 0xA3, 0x92, 0x0E, 0xDA, 0xEA, 0xA4, 0x8F, 0x86, 0xEA, 0x27,
+ 0x44, 0xAF, 0x82, 0xA8, 0xE8, 0xA3, 0x66, 0x4A, 0x99, 0x6B, 0xA5, 0x33, 0xE6, 0x0A, 0x27, 0xA9,
+ 0x89, 0xAA, 0x7A, 0x6A, 0x9E, 0x0A, 0x89, 0xB8, 0x68, 0x8F, 0xBA, 0x1E, 0x5B, 0xAB, 0xB2, 0xCB,
+ 0x2E, 0x94, 0xAA, 0xAF, 0xB5, 0x46, 0x2B, 0x6D, 0x9E, 0xC0, 0x0A, 0x30, 0xC0, 0x00, 0x4A, 0x9A,
+ 0x3A, 0x2C, 0xB1, 0x74, 0x1A, 0x0B, 0x2D, 0x84, 0xD9, 0xA2, 0x1A, 0xEB, 0xA8, 0xD3, 0x36, 0xFB,
+ 0xEC, 0xA9, 0xDF, 0x96, 0xAB, 0xEE, 0xBA, 0x23, 0x02, 0x7B, 0xED, 0xB5, 0xE1, 0x6E, 0x1B, 0x2D,
+ 0xA8, 0x37, 0x96, 0x0A, 0xE6, 0xA8, 0x24, 0x22, 0xCA, 0x2E, 0xB1, 0x2B, 0xA6, 0xBB, 0xEF, 0xBF,
+ 0xEA, 0xC6, 0x09, 0xEF, 0xBB, 0xD8, 0x86, 0x0B, 0xB0, 0x96, 0x2F, 0x2E, 0xD9, 0x6E, 0xA9, 0xA4,
+ 0xD6, 0x5B, 0xAD, 0xC2, 0xD3, 0xDA, 0xDB, 0xAF, 0xBF, 0x07, 0x57, 0x5C, 0xAE, 0xC0, 0x04, 0xBF,
+ 0xDB, 0xE5, 0xC5, 0xC1, 0xBA, 0x28, 0x71, 0xC2, 0x0F, 0x6F, 0x2C, 0x62, 0xC8, 0x06, 0x4B, 0x0B,
+ 0xAC, 0xC5, 0x28, 0x5B, 0x58, 0x32, 0xC7, 0xEE, 0x66, 0x0C, 0x6F, 0xC7, 0xAD, 0x36, 0xEC, 0xF1,
+ 0xC7, 0x20, 0x93, 0x0C, 0x71, 0x8F, 0x36, 0xDF, 0x4C, 0xED, 0xC9, 0x29, 0xA3, 0xBC, 0x31, 0xC4,
+ 0x32, 0xFF, 0x9A, 0xB3, 0xBE, 0x92, 0x86, 0x3C, 0x33, 0xD1, 0x2A, 0x87, 0x5C, 0xF0, 0xCA, 0x38,
+ 0x0F, 0x8D, 0xAF, 0xC8, 0x49, 0x3F, 0xBC, 0x2E, 0xCD, 0xD4, 0xE2, 0x0C, 0xAE, 0xD0, 0x4B, 0x66,
+ 0x1B, 0xF4, 0xD1, 0x43, 0xF3, 0x3C, 0xA3, 0x9C, 0x4E, 0x3F, 0x2D, 0x73, 0xD7, 0x1D, 0x93, 0xAD,
+ 0x70, 0xBE, 0x66, 0x23, 0x8D, 0xF0, 0xCE, 0x41, 0xEB, 0xDC, 0xB4, 0xD4, 0x6E, 0x4F, 0x78, 0x32,
+ 0xC9, 0x5C, 0x93, 0xDD, 0xB4, 0xD8, 0x69, 0xC3, 0x2C, 0x77, 0xDE, 0x5C, 0x12, 0x2C, 0x32, 0xDF,
+ 0x80, 0x7B, 0x5D, 0x74, 0xDA, 0xE0, 0xF2, 0x1D, 0xF5, 0xDC, 0x52, 0x2F, 0x9C, 0xF3, 0xC8, 0x52,
+ 0x06, 0x5E, 0xB7, 0xDA, 0xA4, 0xBA, 0xBC, 0xB4, 0xD9, 0x85, 0x3F, 0x8C, 0xAD, 0xC6, 0x8B, 0x17,
+ 0xFD, 0x76, 0xE0, 0x9C, 0x13, 0x5E, 0x73, 0xD8, 0xAF, 0xA6, 0x3D, 0xF9, 0xD3, 0x7E, 0xEB, 0x8D,
+ 0xB1, 0xCB, 0x94, 0xEF, 0x5D, 0x6D, 0xC6, 0x5B, 0x6F, 0xCE, 0xF4, 0xE3, 0x7C, 0xBF, 0xDC, 0xB9,
+ 0x92, 0x93, 0x1B, 0x7D, 0x38, 0xED, 0xA8, 0xAB, 0x7D, 0xBB, 0x8F, 0x9C, 0x63, 0x7E, 0x73, 0xDF,
+ 0xAC, 0x33, 0x0D, 0xFC, 0xB5, 0x04, 0x10, 0x50, 0x3A, 0xE8, 0x95, 0x9F, 0x1E, 0x3C, 0xD4, 0xAA,
+ 0x53, 0xAD, 0x38, 0xE0, 0x92, 0xAF, 0xDE, 0xF5, 0xF1, 0xB6, 0x2B, 0x9F, 0xBB, 0xF3, 0xD6, 0x52,
+ 0x1F, 0xF9, 0xF4, 0x98, 0x8F, 0x9C, 0xFD, 0xF2, 0x59, 0x7F, 0x5F, 0xFC, 0xF8, 0xC6, 0x97, 0x5E,
+ 0x70, 0xED, 0x65, 0xB7, 0x1C, 0xFD, 0xEB, 0x6F, 0xA3, 0x1F, 0xB8, 0xF6, 0xB8, 0x4B, 0x3E, 0xB9,
+ 0xFC, 0xD6, 0xAE, 0x0E, 0x3E, 0xF0, 0x4A, 0xCB, 0xEF, 0xBE, 0xF7, 0xE4, 0x97, 0xAF, 0xBF, 0xEC,
+ 0x24, 0x2B, 0xDD, 0xC2, 0x88, 0x37, 0x3E, 0xDF, 0xA1, 0x2D, 0x7B, 0xFD, 0x2B, 0xE0, 0xFF, 0x0C,
+ 0x68, 0xBF, 0x05, 0xEA, 0xAD, 0x7D, 0xEE, 0xB3, 0x1C, 0x00, 0x87, 0xB7, 0xC0, 0x0A, 0x5A, 0xD0,
+ 0x82, 0x5D, 0xBA, 0x9E, 0xF5, 0xF4, 0x47, 0xB5, 0xEC, 0x15, 0xE0, 0x83, 0x05, 0x50, 0x20, 0xF9,
+ 0x34, 0xD8, 0xC0, 0xEE, 0x25, 0xED, 0x5D, 0x23, 0x9C, 0x20, 0xF0, 0x12, 0xA8, 0xC0, 0x0A, 0x4A,
+ 0x10, 0x83, 0xC2, 0x8B, 0x1F, 0x03, 0xA9, 0xB7, 0x3E, 0x19, 0x56, 0xB0, 0x7F, 0xFA, 0x6B, 0xE1,
+ 0x0D, 0x75, 0x48, 0x3F, 0x44, 0x5D, 0xF0, 0x72, 0x2B, 0xA3, 0x1D, 0x01, 0x40, 0xF8, 0x41, 0x16,
+ 0xEA, 0xD0, 0x66, 0xB9, 0x8B, 0x1A, 0x01, 0x5B, 0xE8, 0x2E, 0x23, 0x16, 0xEF, 0x82, 0x2F, 0x94,
+ 0x9F, 0xFF, 0x4C, 0x67, 0xC3, 0x1F, 0xAE, 0x0F, 0x85, 0x3C, 0xC4, 0x62, 0x02, 0x5D, 0x86, 0x43,
+ 0x0B, 0x76, 0x31, 0x63, 0x4C, 0xA4, 0x60, 0x0A, 0x35, 0x38, 0xA2, 0x01, 0x14, 0xCF, 0x00, 0x05,
+ 0x40, 0x23, 0x08, 0x8D, 0x78, 0x3F, 0x1F, 0x12, 0xB0, 0x60, 0x20, 0x5B, 0xE2, 0x13, 0x55, 0x88,
+ 0x40, 0x36, 0xFE, 0x50, 0x7D, 0x39, 0x9C, 0xE2, 0xD9, 0xAA, 0x68, 0xC5, 0xFF, 0xB1, 0x90, 0x60,
+ 0x4E, 0x9C, 0xA3, 0x19, 0xFF, 0x28, 0xB9, 0x40, 0x72, 0x71, 0x8C, 0x97, 0xD3, 0x22, 0x0F, 0x75,
+ 0xD7, 0x23, 0x33, 0xA6, 0xD1, 0x00, 0x90, 0x24, 0x22, 0x21, 0xE1, 0x97, 0xC1, 0x37, 0xC6, 0x50,
+ 0x8E, 0x73, 0x8C, 0x9C, 0x13, 0x17, 0x78, 0x44, 0x0A, 0x2E, 0xF1, 0x90, 0xE5, 0x4B, 0x5F, 0x1D,
+ 0x05, 0x79, 0x41, 0x52, 0x2A, 0x32, 0x90, 0x44, 0x0C, 0xA1, 0x11, 0xD7, 0xE8, 0x3F, 0x40, 0x26,
+ 0xB0, 0x88, 0xAD, 0xC4, 0x64, 0x2C, 0x65, 0xD9, 0x46, 0xA0, 0x39, 0x12, 0x92, 0x91, 0x4C, 0xA5,
+ 0x2A, 0x11, 0x19, 0x41, 0x57, 0xC2, 0xAB, 0x5D, 0x04, 0x84, 0xA5, 0x0A, 0x97, 0x08, 0x4B, 0x53,
+ 0x82, 0x91, 0x97, 0x52, 0xD4, 0xE1, 0x18, 0x13, 0x35, 0xBC, 0x49, 0xFA, 0xF1, 0x8B, 0x81, 0xEC,
+ 0x5F, 0x2A, 0x57, 0xC9, 0xCA, 0x58, 0x4A, 0x53, 0x92, 0xC6, 0xFC, 0xE2, 0x29, 0x4D, 0xD9, 0x3A,
+ 0x25, 0x15, 0xEF, 0x91, 0xB8, 0x54, 0x63, 0x35, 0xB5, 0x69, 0x40, 0x2D, 0xBE, 0xEC, 0x84, 0xC1,
+ 0x54, 0xE0, 0xE9, 0xBE, 0x59, 0x4C, 0x63, 0xCA, 0x72, 0x96, 0xA0, 0x14, 0xE4, 0x17, 0x23, 0x37,
+ 0xC8, 0x2D, 0xEE, 0x30, 0x9A, 0xA8, 0x9C, 0x26, 0x0B, 0xB1, 0x09, 0x4A, 0x5D, 0xEE, 0x32, 0x9E,
+ 0xA6, 0xB4, 0x23, 0xF5, 0x12, 0x76, 0xCB, 0x70, 0xE2, 0x52, 0x9F, 0xE4, 0xF4, 0xDD, 0x29, 0xDD,
+ 0x47, 0xCC, 0x14, 0x56, 0x92, 0x9D, 0xD8, 0x84, 0x27, 0x3E, 0x11, 0x39, 0xD1, 0x50, 0x62, 0x6C,
+ 0x93, 0xC6, 0x83, 0x67, 0x3D, 0xC7, 0x59, 0x51, 0x7F, 0xBE, 0x32, 0xA2, 0xC7, 0x1C, 0xA2, 0x47,
+ 0x49, 0x39, 0xBE, 0x5D, 0xB6, 0xB2, 0xA4, 0xED, 0x4C, 0xE2, 0xDE, 0x0A, 0x7A, 0x80, 0x96, 0x1A,
+ 0x34, 0xA2, 0x9B, 0x94, 0x1D, 0x16, 0x85, 0x29, 0xD3, 0x8D, 0x56, 0xF3, 0x65, 0x33, 0x45, 0x68,
+ 0x48, 0x2B, 0xCA, 0x53, 0x93, 0x02, 0x50, 0x8B, 0x1C, 0x85, 0x26, 0x4A, 0x83, 0xEA, 0x44, 0x7F,
+ 0x8E, 0x73, 0xA4, 0x21, 0x35, 0xAA, 0x2A, 0x5D, 0x09, 0x52, 0x9B, 0xC2, 0x34, 0x93, 0x06, 0xF3,
+ 0xE0, 0x23, 0x5B, 0x7A, 0x00, 0x83, 0x8A, 0xD3, 0xA4, 0x76, 0x6C, 0xA6, 0x3E, 0x0F, 0xA9, 0x4B,
+ 0x41, 0x8A, 0x4F, 0xA4, 0x3A, 0xAD, 0x27, 0x3B, 0x51, 0x59, 0x54, 0xB0, 0x36, 0xF5, 0xA1, 0x66,
+ 0x6D, 0x27, 0x3E, 0x9F, 0x4A, 0xBE, 0x6A, 0x2A, 0xD5, 0xAD, 0x5D, 0xB5, 0x26, 0x44, 0x3D, 0xCA,
+ 0x54, 0x90, 0x0E, 0x15, 0xA1, 0x4F, 0x64, 0xA6, 0xB5, 0x86, 0x08, 0x49, 0xAA, 0x56, 0xD5, 0xAA,
+ 0x11, 0xE5, 0xA8, 0x42, 0x1D, 0x09, 0x4E, 0x9A, 0xDE, 0x95, 0x9F, 0xC7, 0x1C, 0xE9, 0x3E, 0xF1,
+ 0x8A, 0xD7, 0xC3, 0x86, 0xB5, 0xAE, 0x1F, 0xBC, 0x6A, 0x5B, 0x83, 0xCA, 0xD6, 0xB4, 0xBE, 0xF5,
+ 0xAA, 0x45, 0x04, 0xE1, 0x41, 0x97, 0x9A, 0x58, 0xA5, 0x9E, 0x54, 0xB1, 0x8E, 0xDD, 0x2A, 0xFA,
+ 0x6E, 0xE9, 0xD7, 0xBF, 0x02, 0x16, 0xB4, 0x5E, 0xC5, 0x1D, 0x5F, 0xC3, 0x89, 0x55, 0xB0, 0xB2,
+ 0xB6, 0x88, 0x5C, 0x8D, 0xEB, 0x47, 0xDF, 0x2A, 0x5B, 0xCB, 0x8A, 0xB6, 0xAE, 0x2F, 0x35, 0x29,
+ 0x6D, 0xD9, 0xAA, 0xD9, 0xD7, 0xA6, 0xD2, 0xB7, 0x6F, 0xCD, 0xA6, 0x6D, 0x69, 0x4A, 0xBC, 0xC8,
+ 0x8A, 0xF3, 0x9A, 0xC1, 0x85, 0xAA, 0xF8, 0xD2, 0x58, 0x5A, 0xAB, 0x02, 0x57, 0xAD, 0x7A, 0x44,
+ 0x20, 0x38, 0x73, 0x39, 0x59, 0xE7, 0x12, 0x37, 0x98, 0xB5, 0x9D, 0xEB, 0x6E, 0xF5, 0x49, 0xC4,
+ 0xDC, 0x72, 0x16, 0xBB, 0xA7, 0xD5, 0xED, 0x76, 0x33, 0xDB, 0x5D, 0xEB, 0xFE, 0xF6, 0xB9, 0x8A,
+ 0xED, 0xAC, 0x6C, 0x01, 0x69, 0x5C, 0xCC, 0x8E, 0x97, 0x95, 0x60, 0x9C, 0x6A, 0x73, 0xFB, 0x4A,
+ 0x55, 0xEF, 0x62, 0x55, 0x8A, 0xD3, 0x55, 0x6B, 0x7B, 0x4D, 0xAB, 0x46, 0xB9, 0x82, 0x56, 0x97,
+ 0x9B, 0x05, 0x70, 0x2E, 0x05, 0xEC, 0x5B, 0x92, 0xAE, 0xD6, 0xBB, 0x43, 0x45, 0xAF, 0x51, 0x9D,
+ 0x1B, 0xE0, 0xF6, 0x2A, 0xB8, 0xB2, 0xA1, 0x1D, 0xE3, 0x37, 0xC3, 0xFB, 0xDE, 0x9B, 0x0E, 0x52,
+ 0xBE, 0x7E, 0x35, 0x40, 0x69, 0xEB, 0x3B, 0xE0, 0xFB, 0x72, 0xF5, 0xB5, 0xD2, 0xC4, 0x65, 0x86,
+ 0xFB, 0xEB, 0xBF, 0xD0, 0xAA, 0xD2, 0x9F, 0x0D, 0xEE, 0x6D, 0x8A, 0x1D, 0xBC, 0xD9, 0x12, 0xB3,
+ 0x93, 0xC1, 0x28, 0xB6, 0x6F, 0x8C, 0x19, 0xDC, 0xE1, 0xE9, 0x2A, 0x78, 0xAD, 0x8D, 0x2D, 0x31,
+ 0x8B, 0x57, 0xAC, 0x54, 0x10, 0x97, 0x94, 0xBE, 0xCD, 0xDD, 0xB0, 0x4B, 0x03, 0xCC, 0xC6, 0xEA,
+ 0x0E, 0xD8, 0xC8, 0xF3, 0x25, 0xF1, 0x64, 0xD3, 0x38, 0xDE, 0xE7, 0xC2, 0x58, 0xC5, 0x1A, 0x6E,
+ 0xF1, 0x35, 0x69, 0x4C, 0xE0, 0x07, 0xEF, 0xF8, 0xC9, 0x57, 0xAE, 0xB1, 0x7E, 0x77, 0x5B, 0x54,
+ 0x1B, 0xF3, 0x78, 0xC1, 0x44, 0x9E, 0xB0, 0x86, 0x37, 0x0C, 0xE4, 0x24, 0x43, 0x77, 0xB6, 0x05,
+ 0x46, 0x69, 0x99, 0xF9, 0xDB, 0xE3, 0x2F, 0x43, 0xF9, 0xAA, 0x34, 0x56, 0x63, 0x38, 0x39, 0x7C,
+ 0x5C, 0xB3, 0xC6, 0x59, 0x9C, 0x54, 0x06, 0xB3, 0x41, 0x87, 0xFC, 0x5A, 0x11, 0xF3, 0x17, 0xB8,
+ 0xC8, 0xCD, 0x6F, 0x93, 0xE3, 0x5C, 0xDB, 0x37, 0x17, 0x13, 0xC3, 0x1C, 0x16, 0x32, 0x9F, 0xA1,
+ 0xAB, 0xE7, 0x23, 0xBF, 0x78, 0xCD, 0x14, 0xDE, 0xF1, 0x65, 0x53, 0x3C, 0xE7, 0x3F, 0xEF, 0x99,
+ 0xCF, 0xEE, 0xB5, 0x31, 0x9D, 0xEF, 0xAC, 0xE5, 0x37, 0x8F, 0x19, 0xD3, 0x72, 0x2E, 0x73, 0x9E,
+ 0x77, 0xE9, 0xE9, 0x41, 0x73, 0x1A, 0xA6, 0x2C, 0x5E, 0xAA, 0x98, 0x37, 0xAD, 0x68, 0xD3, 0x1E,
+ 0xBA, 0xCD, 0x80, 0x16, 0xE9, 0x9A, 0xFF, 0x6A, 0xE9, 0x51, 0x83, 0xB9, 0xBC, 0x90, 0x76, 0xEE,
+ 0x88, 0x37, 0x4B, 0xE3, 0x24, 0x9F, 0xBA, 0xD1, 0x9F, 0x66, 0xF3, 0x7E, 0x77, 0x1D, 0xE9, 0x2C,
+ 0xCF, 0x38, 0xCB, 0xE6, 0xC5, 0x6A, 0xAA, 0xE7, 0x78, 0xE0, 0x4B, 0x2B, 0x9A, 0xC8, 0xB4, 0x4D,
+ 0x76, 0x79, 0xC9, 0x4C, 0xE6, 0x5F, 0xEF, 0xB6, 0xD2, 0xAD, 0x6E, 0x35, 0xA7, 0x73, 0xBD, 0x6D,
+ 0x60, 0xFB, 0xBA, 0xB7, 0xD5, 0xC6, 0x32, 0xAE, 0xCD, 0xBB, 0x6D, 0x42, 0x73, 0x56, 0xCC, 0x07,
+ 0x65, 0x76, 0xB3, 0xFD, 0xAC, 0xED, 0x4C, 0x03, 0x7B, 0xC5, 0xB3, 0x0E, 0x36, 0xB1, 0x6D, 0x3D,
+ 0x63, 0x79, 0x67, 0x5B, 0xC8, 0xE5, 0x06, 0xB5, 0xB5, 0xCF, 0xEB, 0x6C, 0xDF, 0xCE, 0xBA, 0xD6,
+ 0xF0, 0x2E, 0xF7, 0xB6, 0xCD, 0x5C, 0x40, 0x74, 0x47, 0x52, 0xDD, 0x3B, 0xCE, 0xB6, 0x9B, 0xF9,
+ 0x8D, 0x65, 0x76, 0xDF, 0x5B, 0xDF, 0xF4, 0x76, 0xF0, 0xC3, 0x1F, 0x2E, 0xF0, 0x8A, 0x77, 0xDA,
+ 0xCB, 0xC9, 0xFE, 0xB7, 0x69, 0x37, 0xCD, 0x70, 0x8B, 0x3B, 0x9B, 0xE0, 0x19, 0x5D, 0xF7, 0x77,
+ 0x65, 0xED, 0xF1, 0xED, 0x3A, 0xFC, 0xCE, 0xF8, 0xB6, 0xB7, 0xC5, 0x09, 0x3C, 0x71, 0x8A, 0x7B,
+ 0x5C, 0xE0, 0xB0, 0x96, 0xB6, 0xC6, 0xC3, 0x5D, 0x63, 0x6C, 0x03, 0xFC, 0xE3, 0xED, 0x56, 0xB5,
+ 0xC8, 0x43, 0x18, 0x4C, 0x69, 0x63, 0x3C, 0x97, 0x01, 0xB6, 0xAA, 0xAF, 0x75, 0x3D, 0xF4, 0x97,
+ 0xD7, 0xDC, 0xE1, 0x2D, 0xA7, 0xB9, 0xD1, 0xEF, 0x1C, 0xED, 0x8C, 0xCF, 0x7C, 0xD3, 0x9D, 0x46,
+ 0x3A, 0xA7, 0x5D, 0x0E, 0xDB, 0xE2, 0xE6, 0xB6, 0x7C, 0x06, 0x97, 0x2C, 0xC9, 0x5F, 0x9E, 0x72,
+ 0x06, 0x17, 0xFD, 0xB4, 0xFB, 0x96, 0x78, 0xD2, 0xBB, 0xBE, 0xF4, 0x6E, 0xC7, 0xDC, 0xBB, 0x4F,
+ 0xFF, 0x73, 0x60, 0xC1, 0x39, 0x6F, 0xA0, 0xDB, 0x3C, 0xDC, 0x3C, 0x47, 0xA1, 0x8D, 0xCF, 0x7D,
+ 0xE0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+const uint8_t Game::_pwr1_spritesData[576] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x16, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x1A, 0x94, 0x65,
+ 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x18, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0xFF, 0x8C, 0x00, 0x49, 0xB0, 0x62,
+ 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+};
+
+const uint8_t Game::_isld_spritesData[368] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x05, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0C, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0D, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0E, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x11, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+};
+
+const uint8_t Game::_lava_spritesData[288] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0xFF, 0xBF, 0xBF, 0x00, 0xFF, 0x42,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xD3, 0xBF, 0xD4, 0x0E, 0xFF, 0xBF,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x3C, 0xC0, 0xBF, 0xC4, 0x00, 0xFF, 0xBF,
+ 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0xBF, 0x00, 0x00, 0x00, 0x00,
+ 0x0B, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x53, 0xFF, 0xBF, 0x28, 0x00, 0x68, 0x39,
+ 0x0C, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x55, 0xD4, 0xBF, 0x00, 0x00, 0xFF, 0x30,
+ 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+};
+
+const uint8_t Game::_lar1_spritesData[400] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0x25,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xBF, 0x00, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0B, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xB4, 0x80, 0x00, 0xA0, 0xB4,
+ 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0x86, 0xBF,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+ 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0xBF,
+};
+
+
+static const int16_t byte_452538[2] = {
+ 1, 0
+};
+
+static const int16_t byte_452540[3] = {
+ 2, 0, 2048
+};
+
+static const int16_t byte_452548[4] = {
+ 3, 0, 1027, 1021
+};
+
+static const int16_t byte_452550[5] = {
+ 4, 0, 1027, 1021, 1027
+};
+
+static const int16_t byte_452560[5] = {
+ 4, 0, -1021, 2048, 1021
+};
+
+static const int16_t byte_452570[7] = {
+ 6, 0, 1021, 6, 1021, 1021, 6
+};
+
+const int16_t *Game::_lar_screenMaskOffsets[] = {
+ byte_452538,
+ byte_452540,
+ byte_452548,
+ byte_452550,
+ byte_452560,
+ byte_452570
+};
+
+uint8_t Game::_lar1_maskData[15 * 6] = {
+ 0x04, 0x00, 0x68, 0x50, 0x08, 0x00, 0x04, 0x00, 0x68, 0x90, 0x08, 0x01, 0x03, 0x00, 0x50, 0x10,
+ 0x0A, 0x01, 0x03, 0x00, 0x80, 0x10, 0x0A, 0x02, 0x04, 0x00, 0xC8, 0x80, 0x0A, 0x03, 0x01, 0x00,
+ 0xC8, 0x10, 0x0C, 0x00, 0x04, 0x00, 0x80, 0x20, 0x0C, 0x01, 0x04, 0x00, 0x38, 0x30, 0x0C, 0x02,
+ 0x04, 0x00, 0xB0, 0x80, 0x0C, 0x03, 0x02, 0x00, 0xB0, 0x40, 0x0C, 0x04, 0x05, 0x00, 0x38, 0x70,
+ 0x0C, 0x05, 0x04, 0x00, 0x38, 0x30, 0x0F, 0x00, 0x04, 0x00, 0x30, 0x70, 0x11, 0x00, 0x03, 0x00,
+ 0x58, 0x60, 0x12, 0x00, 0x04, 0x00, 0xC0, 0x30, 0x17, 0x00
+};
+
+const uint8_t Video::_fontCharactersTable[39 * 2] = {
+ 0x30, 0x00, 0x31, 0x01, 0x32, 0x02, 0x33, 0x03, 0x34, 0x04, 0x35, 0x05, 0x36, 0x06, 0x37, 0x07,
+ 0x38, 0x08, 0x39, 0x09, 0x41, 0x0a, 0x42, 0x0b, 0x43, 0x0c, 0x44, 0x0d, 0x45, 0x0e, 0x46, 0x0f,
+ 0x47, 0x10, 0x48, 0x11, 0x49, 0x12, 0x4a, 0x13, 0x4b, 0x14, 0x4c, 0x15, 0x4d, 0x16, 0x4e, 0x17,
+ 0x4f, 0x18, 0x50, 0x19, 0x51, 0x1a, 0x52, 0x1b, 0x53, 0x1c, 0x54, 0x1d, 0x55, 0x1e, 0x56, 0x1f,
+ 0x57, 0x20, 0x58, 0x21, 0x59, 0x22, 0x5a, 0x23, 0x10, 0x24, 0x11, 0x25, 0x12, 0x26
+};
diff --git a/Src/Vita_&_Switch/system.h b/Src/Vita_&_Switch/system.h
new file mode 100644
index 0000000..d6efecd
--- /dev/null
+++ b/Src/Vita_&_Switch/system.h
@@ -0,0 +1,72 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
+
+#ifndef SYSTEM_H__
+#define SYSTEM_H__
+
+#include "intern.h"
+
+#define SYS_INP_UP (1 << 0)
+#define SYS_INP_RIGHT (1 << 1)
+#define SYS_INP_DOWN (1 << 2)
+#define SYS_INP_LEFT (1 << 3)
+#define SYS_INP_RUN (1 << 4) /* (1 << 0) */
+#define SYS_INP_JUMP (1 << 5) /* (1 << 1) */
+#define SYS_INP_SHOOT (1 << 6) /* (1 << 2) */
+#define SYS_INP_ESC (1 << 7)
+
+struct PlayerInput {
+ uint8_t prevMask, mask;
+ bool skip;
+ bool exit;
+ bool quit;
+ bool screenshot;
+
+ bool keyPressed(int keyMask) const {
+ return (prevMask & keyMask) == 0 && (mask & keyMask) == keyMask;
+ }
+ bool keyReleased(int keyMask) const {
+ return (prevMask & keyMask) == keyMask && (mask & keyMask) == 0;
+ }
+};
+
+struct AudioCallback {
+ void (*proc)(void *param, int16_t *stream, int len); // 22khz
+ void *userdata;
+};
+
+struct System {
+ PlayerInput inp, pad;
+
+ virtual ~System() {}
+
+ virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv) = 0;
+ virtual void destroy() = 0;
+
+ virtual void setScaler(const char *name, int multiplier) = 0;
+ virtual void setGamma(float gamma) = 0;
+
+ virtual void setPalette(const uint8_t *pal, int n, int depth = 8) = 0;
+ virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) = 0;
+ virtual void copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch) = 0;
+ virtual void fillRect(int x, int y, int w, int h, uint8_t color) = 0;
+ virtual void copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal) = 0;
+ virtual void shakeScreen(int dx, int dy) = 0;
+ virtual void updateScreen(bool drawWidescreen) = 0;
+
+ virtual void processEvents() = 0;
+ virtual void sleep(int duration) = 0;
+ virtual uint32_t getTimeStamp() = 0;
+
+ virtual void startAudio(AudioCallback callback) = 0;
+ virtual void stopAudio() = 0;
+ virtual void lockAudio() = 0;
+ virtual void unlockAudio() = 0;
+ virtual AudioCallback setAudioCallback(AudioCallback callback) = 0;
+};
+
+extern System *const g_system;
+
+#endif // SYSTEM_H__
diff --git a/Src/Vita_&_Switch/system_psp.cpp b/Src/Vita_&_Switch/system_psp.cpp
new file mode 100644
index 0000000..d41dded
--- /dev/null
+++ b/Src/Vita_&_Switch/system_psp.cpp
@@ -0,0 +1,417 @@
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "system.h"
+
+struct System_PSP : System {
+
+ uint32_t _buttons;
+ int _shakeDx, _shakeDy;
+ bool _stretchScreen;
+ AudioCallback _audioCb;
+ int _audioChannel;
+ SceUID _audioMutex;
+ uint32_t _vramOffset;
+
+ System_PSP();
+ virtual ~System_PSP();
+ virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv);
+ virtual void destroy();
+ virtual void setScaler(const char *name, int multiplier);
+ virtual void setGamma(float gamma);
+ virtual void setPalette(const uint8_t *pal, int n, int depth);
+ virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch);
+ virtual void copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch);
+ virtual void fillRect(int x, int y, int w, int h, uint8_t color);
+ virtual void copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal);
+ virtual void shakeScreen(int dx, int dy);
+ virtual void updateScreen(bool drawWidescreen);
+ virtual void processEvents();
+ virtual void sleep(int duration);
+ virtual uint32_t getTimeStamp();
+ virtual void startAudio(AudioCallback callback);
+ virtual void stopAudio();
+ virtual void lockAudio();
+ virtual void unlockAudio();
+ virtual AudioCallback setAudioCallback(AudioCallback callback);
+};
+
+static System_PSP system_psp;
+System *const g_system = &system_psp;
+
+// static const int AUDIO_FREQ = 44100;
+static const int AUDIO_SAMPLES_COUNT = 2048;
+
+static const int SCREEN_W = 480;
+static const int SCREEN_H = 272;
+static const int SCREEN_PITCH = 512;
+
+static const int GAME_W = 256;
+static const int GAME_H = 192;
+
+static const int BLUR_TEX_W = 16;
+static const int BLUR_TEX_H = 16;
+
+static uint32_t WHITE_COLOR = GU_RGBA(255, 255, 255, 255);
+
+static uint32_t __attribute__((aligned(16))) _dlist[1024];
+static uint32_t __attribute__((aligned(16))) _clut[256];
+static uint8_t __attribute__((aligned(16))) _texture[256 * 256];
+static uint32_t __attribute__((aligned(16))) _clut2[256];
+static uint8_t __attribute__((aligned(16))) _texture2[256 * 256];
+
+PSP_MODULE_INFO("Heart of Darkness", 0, 2, 8);
+PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER);
+
+struct Vertex {
+ uint16_t u, v;
+ int16_t x, y, z;
+};
+
+static void initVertex2D(Vertex *vertices, int w, int h, int targetW, int targetH) {
+ vertices[0].u = 0; vertices[1].u = w;
+ vertices[0].v = 0; vertices[1].v = h;
+ vertices[0].x = 0; vertices[1].x = targetW;
+ vertices[0].y = 0; vertices[1].y = targetH;
+ vertices[0].z = 0; vertices[1].z = 0;
+}
+
+void System_printLog(FILE *fp, const char *s) {
+ if (fp == stderr) {
+ static bool firstOpen = false;
+ if (!firstOpen) {
+ fp = fopen("stderr.txt", "w");
+ firstOpen = true;
+ } else {
+ fp = fopen("stderr.txt", "a");
+ }
+ } else if (fp == stdout) {
+ static bool firstOpen = false;
+ if (!firstOpen) {
+ fp = fopen("stdout.txt", "w");
+ firstOpen = true;
+ } else {
+ fp = fopen("stdout.txt", "a");
+ }
+ } else {
+ return;
+ }
+ fprintf(fp, "%s\n", s);
+ fclose(fp);
+}
+
+void System_fatalError(const char *s) {
+ sceKernelExitGame();
+}
+
+static int exitCallback(int arg1, int arg2, void *common) {
+ g_system->inp.quit = true;
+ return 0;
+}
+
+static int callbackThread(SceSize args, void *argp) {
+ const int cb = sceKernelCreateCallback("Exit Callback", exitCallback, NULL);
+ sceKernelRegisterExitCallback(cb);
+ sceKernelSleepThreadCB();
+ return 0;
+}
+
+System_PSP::System_PSP() {
+}
+
+System_PSP::~System_PSP() {
+}
+
+void System_PSP::init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv) {
+
+ memset(&inp, 0, sizeof(inp));
+ memset(&pad, 0, sizeof(pad));
+ _buttons = 0;
+ _shakeDx = _shakeDy = 0;
+ _stretchScreen = false; // keep 4:3 AR
+
+ memset(&_audioCb, 0, sizeof(_audioCb));
+ _audioChannel = -1;
+ _audioMutex = 0;
+
+ const int th = sceKernelCreateThread("update_thread", callbackThread, 0x11, 0xFA0, 0, 0);
+ if (th >= 0) {
+ sceKernelStartThread(th, 0, 0);
+ }
+
+ sceKernelDcacheWritebackAll();
+
+ sceCtrlSetSamplingCycle(0);
+ sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);
+
+ sceGuInit();
+ sceGuStart(GU_DIRECT, _dlist);
+
+ const int fbSize = SCREEN_PITCH * SCREEN_H * sizeof(uint32_t); // rgba
+ const int zbSize = SCREEN_PITCH * SCREEN_H * sizeof(uint16_t); // 16 bits
+ _vramOffset = 0;
+ sceGuDrawBuffer(GU_PSM_8888, (void *)_vramOffset, SCREEN_PITCH); _vramOffset += fbSize;
+ sceGuDispBuffer(SCREEN_W, SCREEN_H, (void *)_vramOffset, SCREEN_PITCH); _vramOffset += fbSize;
+ sceGuDepthBuffer((void *)_vramOffset, SCREEN_PITCH); _vramOffset += zbSize;
+
+ sceGuOffset(2048 - (SCREEN_W / 2), 2048 - (SCREEN_H / 2));
+ sceGuViewport(2048, 2048, SCREEN_W, SCREEN_H);
+ sceGuScissor(0, 0, SCREEN_W, SCREEN_H);
+ sceGuEnable(GU_SCISSOR_TEST);
+ sceGuEnable(GU_TEXTURE_2D);
+ sceGuClear(GU_COLOR_BUFFER_BIT | GU_DEPTH_BUFFER_BIT);
+
+ sceGuFinish();
+ sceGuSync(GU_SYNC_WHAT_DONE, GU_SYNC_FINISH);
+
+ sceDisplayWaitVblankStart();
+ sceGuDisplay(GU_TRUE);
+}
+
+void System_PSP::destroy() {
+ sceGuTerm();
+ sceKernelExitGame();
+}
+
+void System_PSP::setScaler(const char *name, int multiplier) {
+}
+
+void System_PSP::setGamma(float gamma) {
+}
+
+void System_PSP::setPalette(const uint8_t *pal, int n, int depth) {
+ const int shift = 8 - depth;
+ for (int i = 0; i < n; ++i) {
+ int r = pal[i * 3];
+ int g = pal[i * 3 + 1];
+ int b = pal[i * 3 + 2];
+ if (shift != 0) {
+ r = (r << shift) | (r >> depth);
+ g = (g << shift) | (g >> depth);
+ b = (b << shift) | (b >> depth);
+ }
+ _clut[i] = GU_RGBA(r, g, b, 255);
+ }
+ sceKernelDcacheWritebackRange(_clut, sizeof(_clut));
+}
+
+void System_PSP::copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) {
+ uint8_t *p = _texture + y * GAME_W + x;
+ if (w == GAME_W && w == pitch) {
+ memcpy(p, buf, w * h);
+ } else {
+ for (int i = 0; i < h; ++i) {
+ memcpy(p, buf, w);
+ p += GAME_W;
+ buf += pitch;
+ }
+ }
+ sceKernelDcacheWritebackRange(_texture, sizeof(_texture));
+}
+
+void System_PSP::copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch) {
+}
+
+void System_PSP::fillRect(int x, int y, int w, int h, uint8_t color) {
+ uint8_t *p = _texture + y * GAME_W + x;
+ if (w == GAME_W) {
+ memset(p, color, w * h);
+ } else {
+ for (int i = 0; i < h; ++i) {
+ memset(p, color, w);
+ p += GAME_W;
+ }
+ }
+ sceKernelDcacheWritebackRange(_texture, sizeof(_texture));
+}
+
+void System_PSP::copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal) {
+ for (int i = 0; i < 256; ++i) {
+ _clut2[i] = GU_RGBA(pal[i * 3], pal[i * 3 + 1], pal[i * 3 + 2], 255);
+ }
+ sceKernelDcacheWritebackRange(_clut2, sizeof(_clut2));
+ memcpy(_texture2, buf, w * h);
+ sceKernelDcacheWritebackRange(_texture2, sizeof(_texture2));
+
+ sceGuStart(GU_DIRECT, _dlist);
+
+ sceGuDrawBufferList(GU_PSM_8888, (void *)_vramOffset, BLUR_TEX_W);
+ sceGuOffset(2048 - (BLUR_TEX_W / 2), 2048 - (BLUR_TEX_H / 2));
+ sceGuViewport(2048, 2048, BLUR_TEX_W, BLUR_TEX_H);
+ sceGuClearColor(WHITE_COLOR);
+ sceGuClear(GU_COLOR_BUFFER_BIT);
+
+ sceGuClutMode(GU_PSM_8888, 0, 0xFF, 0);
+ sceGuClutLoad(256 / 8, _clut2);
+ sceGuTexMode(GU_PSM_T8, 0, 0, 0);
+ sceGuTexImage(0, 256, 256, 256, _texture2);
+ sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB);
+ sceGuTexFilter(GU_LINEAR, GU_LINEAR);
+
+ Vertex *vertices = (Vertex *)sceGuGetMemory(2 * sizeof(struct Vertex));
+ initVertex2D(vertices, GAME_W, GAME_H, BLUR_TEX_W, BLUR_TEX_H);
+ sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, 0, vertices);
+
+ sceGuFinish();
+}
+
+void System_PSP::shakeScreen(int dx, int dy) {
+ _shakeDx = dx;
+ _shakeDy = dy;
+}
+
+void System_PSP::updateScreen(bool drawWidescreen) {
+
+ sceGuStart(GU_DIRECT, _dlist);
+
+ sceGuClearColor(0);
+ sceGuClear(GU_COLOR_BUFFER_BIT);
+
+ if (!_stretchScreen && drawWidescreen) {
+
+ sceGuTexMode(GU_PSM_8888, 0, 0, 0);
+ sceGuTexImage(0, BLUR_TEX_W, BLUR_TEX_H, BLUR_TEX_W, (uint8_t *)sceGeEdramGetAddr() + _vramOffset);
+ sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB);
+ sceGuTexFilter(GU_LINEAR, GU_LINEAR);
+
+ Vertex *vertices = (Vertex *)sceGuGetMemory(2 * sizeof(Vertex));
+ initVertex2D(vertices, BLUR_TEX_W, BLUR_TEX_H, SCREEN_W, SCREEN_H);
+ sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, 0, vertices);
+ }
+
+ sceGuClutMode(GU_PSM_8888, 0, 0xFF, 0);
+ sceGuClutLoad(256 / 8, _clut);
+ sceGuTexMode(GU_PSM_T8, 0, 0, 0);
+ sceGuTexImage(0, 256, 256, 256, _texture);
+ sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB);
+ sceGuTexFilter(GU_LINEAR, GU_LINEAR);
+
+ Vertex *vertices = (Vertex *)sceGuGetMemory(2 * sizeof(Vertex));
+ initVertex2D(vertices, GAME_W, GAME_H, SCREEN_W, SCREEN_H);
+ vertices[0].x += _shakeDx;
+ vertices[1].x += _shakeDx;
+ vertices[0].y += _shakeDy;
+ vertices[1].y += _shakeDy;
+ if (!_stretchScreen) {
+ const int w = (SCREEN_H * GAME_W / GAME_H);
+ const int dx = (SCREEN_W - w) / 2;
+ vertices[0].x += dx;
+ vertices[1].x -= dx;
+ }
+ sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, 0, vertices);
+
+ sceGuFinish();
+ sceGuSync(GU_SYNC_WHAT_DONE, GU_SYNC_FINISH);
+
+ sceDisplayWaitVblankStart();
+ sceGuSwapBuffers();
+}
+
+void System_PSP::processEvents() {
+ inp.prevMask = inp.mask;
+ inp.mask = 0;
+
+ static const struct {
+ int psp;
+ int sys;
+ } mapping[] = {
+ { PSP_CTRL_UP, SYS_INP_UP },
+ { PSP_CTRL_RIGHT, SYS_INP_RIGHT },
+ { PSP_CTRL_DOWN, SYS_INP_DOWN },
+ { PSP_CTRL_LEFT, SYS_INP_LEFT },
+ { PSP_CTRL_CROSS, SYS_INP_JUMP },
+ { PSP_CTRL_SQUARE, SYS_INP_RUN },
+ { PSP_CTRL_CIRCLE, SYS_INP_SHOOT },
+ { PSP_CTRL_TRIANGLE, SYS_INP_SHOOT | SYS_INP_RUN },
+ { PSP_CTRL_START, SYS_INP_ESC },
+ { 0, 0 }
+ };
+ SceCtrlData data;
+ sceCtrlPeekBufferPositive(&data, 1);
+ for (int i = 0; mapping[i].psp != 0; ++i) {
+ if (data.Buttons & mapping[i].psp) {
+ inp.mask |= mapping[i].sys;
+ }
+ }
+ static const int lxMargin = 64;
+ if (data.Lx < 127 - lxMargin) {
+ inp.mask |= SYS_INP_LEFT;
+ } else if (data.Lx > 127 + lxMargin) {
+ inp.mask |= SYS_INP_RIGHT;
+ }
+ static const int lyMargin = 64;
+ if (data.Ly < 127 - lyMargin) {
+ inp.mask |= SYS_INP_UP;
+ } else if (data.Ly > 127 + lyMargin) {
+ inp.mask |= SYS_INP_DOWN;
+ }
+
+ const uint32_t mask = data.Buttons ^ _buttons;
+ if ((data.Buttons & PSP_CTRL_LTRIGGER) & mask) {
+ _stretchScreen = !_stretchScreen;
+ }
+ if ((data.Buttons & PSP_CTRL_RTRIGGER) & mask) {
+ }
+ _buttons = data.Buttons;
+}
+
+void System_PSP::sleep(int duration) {
+ sceKernelDelayThread(duration * 1000);
+}
+
+uint32_t System_PSP::getTimeStamp() {
+ struct timeval tv;
+ sceKernelLibcGettimeofday(&tv, 0);
+ return tv.tv_sec * 1000 + tv.tv_usec / 1000;
+}
+
+static void audioCallback(void *buf, unsigned int samples, void *userdata) { // 44100hz S16 stereo
+ int16_t buf22khz[samples];
+ memset(buf22khz, 0, sizeof(buf22khz));
+ system_psp.lockAudio();
+ (system_psp._audioCb.proc)(system_psp._audioCb.userdata, buf22khz, samples);
+ system_psp.unlockAudio();
+ uint32_t *buf44khz = (uint32_t *)buf;
+ static int16_t prev;
+ for (unsigned int i = 0; i < samples; ++i) {
+ const int16_t current = buf22khz[i];
+ buf44khz[i] = (current << 16) | (((prev + current) >> 1) & 0xFFFF);
+ prev = current;
+ }
+}
+
+void System_PSP::startAudio(AudioCallback callback) {
+ // sceAudioSetFrequency(AUDIO_FREQ);
+ pspAudioInit();
+ _audioCb = callback;
+ _audioMutex = sceKernelCreateSema("audio_lock", 0, 1, 1, 0);
+ _audioChannel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, AUDIO_SAMPLES_COUNT, PSP_AUDIO_FORMAT_STEREO);
+ pspAudioSetChannelCallback(_audioChannel, audioCallback, 0);
+}
+
+void System_PSP::stopAudio() {
+ sceAudioChRelease(_audioChannel);
+ sceKernelDeleteSema(_audioMutex);
+ pspAudioEnd();
+}
+
+void System_PSP::lockAudio() {
+ sceKernelWaitSema(_audioMutex, 1, 0);
+}
+
+void System_PSP::unlockAudio() {
+ sceKernelSignalSema(_audioMutex, 1);
+}
+
+AudioCallback System_PSP::setAudioCallback(AudioCallback callback) {
+ AudioCallback cb = _audioCb;
+ lockAudio();
+ _audioCb = callback;
+ unlockAudio();
+ return cb;
+}
diff --git a/SRC/system_sdl2.cpp b/Src/Vita_&_Switch/system_sdl2.cpp
similarity index 82%
rename from SRC/system_sdl2.cpp
rename to Src/Vita_&_Switch/system_sdl2.cpp
index f016564..548f68c 100644
--- a/SRC/system_sdl2.cpp
+++ b/Src/Vita_&_Switch/system_sdl2.cpp
@@ -1,3 +1,7 @@
+/*
+ * Heart of Darkness engine rewrite
+ * Copyright (C) 2009-2011 Gregory Montoir (cyx@users.sourceforge.net)
+ */
#include
#include
@@ -15,8 +19,6 @@ static bool axis[4]= { false, false, false, false };
static int _scalerMultiplier = 3;
static const Scaler *_scaler = &scaler_xbr;
-static const int _pixelFormat = SDL_PIXELFORMAT_RGB888;
-
static const struct {
const char *name;
const Scaler *scaler;
@@ -43,6 +45,7 @@ struct System_SDL2 : System {
SDL_Window *_window;
SDL_Renderer *_renderer;
SDL_Texture *_texture;
+ SDL_Texture *_backgroundTexture; // YUV (PSX)
int _texW, _texH;
SDL_PixelFormat *_fmt;
uint32_t _pal[256];
@@ -58,12 +61,13 @@ struct System_SDL2 : System {
System_SDL2();
virtual ~System_SDL2() {}
- virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen);
+ virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv);
virtual void destroy();
virtual void setScaler(const char *name, int multiplier);
virtual void setGamma(float gamma);
virtual void setPalette(const uint8_t *pal, int n, int depth);
virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch);
+ virtual void copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch);
virtual void fillRect(int x, int y, int w, int h, uint8_t color);
virtual void copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal);
virtual void shakeScreen(int dx, int dy);
@@ -74,7 +78,6 @@ struct System_SDL2 : System {
virtual void startAudio(AudioCallback callback);
virtual void stopAudio();
- virtual uint32_t getOutputSampleRate();
virtual void lockAudio();
virtual void unlockAudio();
virtual AudioCallback setAudioCallback(AudioCallback callback);
@@ -82,22 +85,27 @@ struct System_SDL2 : System {
void addKeyMapping(int key, uint8_t mask);
void setupDefaultKeyMappings();
void updateKeys(PlayerInput *inp);
- void prepareScaledGfx(const char *caption, bool fullscreen, bool widescreen);
+ void prepareScaledGfx(const char *caption, bool fullscreen, bool widescreen, bool yuv);
};
static System_SDL2 system_sdl2;
System *const g_system = &system_sdl2;
+void System_fatalError(const char *s) {
+ SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Heart of Darkness", s, system_sdl2._window);
+ exit(-1);
+}
+
System_SDL2::System_SDL2() :
_offscreenLut(0), _offscreenRgb(0),
- _window(0), _renderer(0), _texture(0), _fmt(0), _widescreenTexture(0),
+ _window(0), _renderer(0), _texture(0), _backgroundTexture(0), _fmt(0), _widescreenTexture(0),
_controller(0), _joystick(0) {
for (int i = 0; i < 256; ++i) {
_gammaLut[i] = i;
}
}
-void System_SDL2::init(const char *title, int w, int h, bool fullscreen, bool widescreen) {
+void System_SDL2::init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv) {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
SDL_ShowCursor(SDL_DISABLE);
setupDefaultKeyMappings();
@@ -117,7 +125,7 @@ void System_SDL2::init(const char *title, int w, int h, bool fullscreen, bool wi
error("System_SDL2::init() Unable to allocate RGB offscreen buffer");
}
memset(_offscreenLut, 0, offscreenSize);
- prepareScaledGfx(title, fullscreen, widescreen);
+ prepareScaledGfx(title, fullscreen, widescreen, yuv);
_joystick = 0;
_controller = 0;
const int count = SDL_NumJoysticks();
@@ -133,7 +141,7 @@ void System_SDL2::init(const char *title, int w, int h, bool fullscreen, bool wi
}
_joystick = SDL_JoystickOpen(i);
if (_joystick) {
- fprintf(stdout, "Using joystick '%s'", SDL_JoystickName(_joystick));
+ fprintf(stdout, "Using joystick '%s'\n", SDL_JoystickName(_joystick));
break;
}
}
@@ -158,6 +166,10 @@ void System_SDL2::destroy() {
SDL_DestroyTexture(_widescreenTexture);
_widescreenTexture = 0;
}
+ if (_backgroundTexture) {
+ SDL_DestroyTexture(_backgroundTexture);
+ _backgroundTexture = 0;
+ }
if (_renderer) {
SDL_DestroyRenderer(_renderer);
_renderer = 0;
@@ -315,22 +327,39 @@ void System_SDL2::setPalette(const uint8_t *pal, int n, int depth) {
b = _gammaLut[b];
_pal[i] = SDL_MapRGB(_fmt, r, g, b);
}
+ if (_backgroundTexture) {
+ _pal[0] = 0;
+ }
}
void System_SDL2::copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) {
assert(x >= 0 && x + w <= _screenW && y >= 0 && y + h <= _screenH);
- for (int i = 0; i < h; ++i) {
- memcpy(_offscreenLut + y * _screenW + x, buf, w);
- buf += pitch;
- ++y;
+ if (w == pitch && w == _screenW) {
+ memcpy(_offscreenLut + y * _screenW + x, buf, w * h);
+ } else {
+ for (int i = 0; i < h; ++i) {
+ memcpy(_offscreenLut + y * _screenW + x, buf, w);
+ buf += pitch;
+ ++y;
+ }
+ }
+}
+
+void System_SDL2::copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch) {
+ if (_backgroundTexture) {
+ SDL_UpdateYUVTexture(_backgroundTexture, 0, y, ypitch, u, upitch, v, vpitch);
}
}
void System_SDL2::fillRect(int x, int y, int w, int h, uint8_t color) {
assert(x >= 0 && x + w <= _screenW && y >= 0 && y + h <= _screenH);
- for (int i = 0; i < h; ++i) {
- memset(_offscreenLut + y * _screenW + x, color, w);
- ++y;
+ if (w == _screenW) {
+ memset(_offscreenLut + y * _screenW + x, color, w * h);
+ } else {
+ for (int i = 0; i < h; ++i) {
+ memset(_offscreenLut + y * _screenW + x, color, w);
+ ++y;
+ }
}
}
@@ -390,6 +419,7 @@ void System_SDL2::updateScreen(bool drawWidescreen) {
SDL_UnlockTexture(_texture);
SDL_RenderClear(_renderer);
+
if (_widescreenTexture) {
if (drawWidescreen) {
SDL_RenderCopy(_renderer, _widescreenTexture, 0, 0);
@@ -402,8 +432,14 @@ void System_SDL2::updateScreen(bool drawWidescreen) {
r.w = _texW;
r.y += (r.h - _texH) / 2;
r.h = _texH;
+ if (_backgroundTexture) {
+ SDL_RenderCopy(_renderer, _backgroundTexture, 0, &r);
+ }
SDL_RenderCopy(_renderer, _texture, 0, &r);
} else {
+ if (_backgroundTexture) {
+ SDL_RenderCopy(_renderer, _backgroundTexture, 0, 0);
+ }
SDL_RenderCopy(_renderer, _texture, 0, 0);
}
SDL_RenderPresent(_renderer);
@@ -420,6 +456,21 @@ void System_SDL2::processEvents() {
inp.screenshot = true;
}
break;
+ case SDL_JOYDEVICEADDED:
+ if (!_joystick) {
+ _joystick = SDL_JoystickOpen(ev.jdevice.which);
+ if (_joystick) {
+ fprintf(stdout, "Using joystick '%s'\n", SDL_JoystickName(_joystick));
+ }
+ }
+ break;
+ case SDL_JOYDEVICEREMOVED:
+ if (_joystick == SDL_JoystickFromInstanceID(ev.jdevice.which)) {
+ fprintf(stdout, "Removed joystick '%s'\n", SDL_JoystickName(_joystick));
+ SDL_JoystickClose(_joystick);
+ _joystick = 0;
+ }
+ break;
case SDL_JOYHATMOTION:
if (_joystick) {
pad.mask &= ~(SYS_INP_UP | SYS_INP_DOWN | SYS_INP_LEFT | SYS_INP_RIGHT);
@@ -485,9 +536,31 @@ void System_SDL2::processEvents() {
pad.mask &= ~SYS_INP_SHOOT;
}
break;
+ case 3:
+ if (pressed) {
+ pad.mask |= SYS_INP_SHOOT | SYS_INP_RUN;
+ } else {
+ pad.mask &= ~(SYS_INP_SHOOT | SYS_INP_RUN);
+ }
+ break;
+ }
+ }
+ break;
+ case SDL_CONTROLLERDEVICEADDED:
+ if (!_controller) {
+ _controller = SDL_GameControllerOpen(ev.cdevice.which);
+ if (_controller) {
+ fprintf(stdout, "Using controller '%s'\n", SDL_GameControllerName(_controller));
}
}
break;
+ case SDL_CONTROLLERDEVICEREMOVED:
+ if (_controller == SDL_GameControllerFromInstanceID(ev.cdevice.which)) {
+ fprintf(stdout, "Removed controller '%s'\n", SDL_GameControllerName(_controller));
+ SDL_GameControllerClose(_controller);
+ _controller = 0;
+ }
+ break;
case SDL_CONTROLLERAXISMOTION:
if (_controller) {
switch (ev.caxis.axis) {
@@ -580,10 +653,17 @@ void System_SDL2::processEvents() {
}
break;
case SDL_CONTROLLER_BUTTON_Y:
+ if (pressed) {
+ pad.mask |= SYS_INP_SHOOT | SYS_INP_RUN;
+ } else {
+ pad.mask &= ~(SYS_INP_SHOOT | SYS_INP_RUN);
+ }
break;
case SDL_CONTROLLER_BUTTON_BACK:
+ inp.skip = pressed;
+ break;
case SDL_CONTROLLER_BUTTON_START:
- inp.escape = pressed;
+ inp.exit = pressed;
break;
case SDL_CONTROLLER_BUTTON_DPAD_UP:
if (pressed) {
@@ -619,7 +699,6 @@ void System_SDL2::processEvents() {
case SDL_QUIT:
inp.quit = true;
break;
-
}
}
updateKeys(&inp);
@@ -634,9 +713,8 @@ uint32_t System_SDL2::getTimeStamp() {
}
static void mixAudioS16(void *param, uint8_t *buf, int len) {
- System_SDL2 *stub = (System_SDL2 *)param;
memset(buf, 0, len);
- stub->_audioCb.proc(stub->_audioCb.userdata, (int16_t *)buf, len / 2);
+ system_sdl2._audioCb.proc(system_sdl2._audioCb.userdata, (int16_t *)buf, len / 2);
}
void System_SDL2::startAudio(AudioCallback callback) {
@@ -660,10 +738,6 @@ void System_SDL2::stopAudio() {
SDL_CloseAudio();
}
-uint32_t System_SDL2::getOutputSampleRate() {
- return kAudioHz;
-}
-
void System_SDL2::lockAudio() {
SDL_LockAudio();
}
@@ -714,7 +788,7 @@ void System_SDL2::setupDefaultKeyMappings() {
addKeyMapping(SDL_SCANCODE_RETURN, SYS_INP_JUMP);
addKeyMapping(SDL_SCANCODE_LCTRL, SYS_INP_RUN);
addKeyMapping(SDL_SCANCODE_F, SYS_INP_RUN);
-// addKeyMapping(SDL_SCANCODE_LALT, SYS_INP_JUMP);
+ addKeyMapping(SDL_SCANCODE_LALT, SYS_INP_JUMP);
addKeyMapping(SDL_SCANCODE_G, SYS_INP_JUMP);
addKeyMapping(SDL_SCANCODE_LSHIFT, SYS_INP_SHOOT);
addKeyMapping(SDL_SCANCODE_H, SYS_INP_SHOOT);
@@ -736,7 +810,7 @@ void System_SDL2::updateKeys(PlayerInput *inp) {
inp->mask |= pad.mask;
}
-void System_SDL2::prepareScaledGfx(const char *caption, bool fullscreen, bool widescreen) {
+void System_SDL2::prepareScaledGfx(const char *caption, bool fullscreen, bool widescreen, bool yuv) {
int flags = 0;
if (fullscreen) {
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
@@ -755,12 +829,21 @@ void System_SDL2::prepareScaledGfx(const char *caption, bool fullscreen, bool wi
}
_renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
SDL_RenderSetLogicalSize(_renderer, windowW, windowH);
- SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
- _texture = SDL_CreateTexture(_renderer, _pixelFormat, SDL_TEXTUREACCESS_STREAMING, _texW, _texH);
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, (_scaler == &scaler_nearest) ? "0" : "1");
+
+ const int pixelFormat = yuv ? SDL_PIXELFORMAT_RGBA8888 : SDL_PIXELFORMAT_RGB888;
+ _texture = SDL_CreateTexture(_renderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, _texW, _texH);
if (widescreen) {
- _widescreenTexture = SDL_CreateTexture(_renderer, _pixelFormat, SDL_TEXTUREACCESS_STREAMING, _screenW, _screenH);
+ _widescreenTexture = SDL_CreateTexture(_renderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, _screenW, _screenH);
} else {
_widescreenTexture = 0;
}
- _fmt = SDL_AllocFormat(_pixelFormat);
+ if (yuv) {
+ _backgroundTexture = SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, _screenW, _screenH);
+ // the game texture is drawn on top
+ SDL_SetTextureBlendMode(_texture, SDL_BLENDMODE_BLEND);
+ } else {
+ _backgroundTexture = 0;
+ }
+ _fmt = SDL_AllocFormat(pixelFormat);
}
diff --git a/Src/Vita_&_Switch/system_wii.cpp b/Src/Vita_&_Switch/system_wii.cpp
new file mode 100644
index 0000000..c83d09a
--- /dev/null
+++ b/Src/Vita_&_Switch/system_wii.cpp
@@ -0,0 +1,485 @@
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include