diff --git a/README.md b/README.md index 88ada4d..f13e184 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,14 @@ Running setup again verifies existing weights without downloading them again. Complete offline release ZIPs already contain the model and need no download. See [Fortune AI](docs/FORTUNE_AI.md) for details and hardware notes. +## Hell Expedition + +The large hell painting opposite the first-floor bar opens an assembly-native FPS expedition: +Freedoom enemies and weapons, a blue-key gate, two secrets, lava and a final +boss, accompanied by the existing Mick Gordon soundtrack. First clear awards +$6,660; death/retry preserves the casino bank. See +[controls, assets and verification](docs/HELL_EXPEDITION.md). + ## Native Entrance And Character Scenes The graphical startup now fades from Eblan Games into a first-person opening: diff --git a/assets/hell/COPYING.txt b/assets/hell/COPYING.txt new file mode 100644 index 0000000..9d251d3 --- /dev/null +++ b/assets/hell/COPYING.txt @@ -0,0 +1,30 @@ +Copyright © 2001-2024 +Contributors to the Freedoom project. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Freedoom project nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS +IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +For a list of contributors to the Freedoom project, see the file +CREDITS. diff --git a/assets/hell/CREDITS.txt b/assets/hell/CREDITS.txt new file mode 100644 index 0000000..647e27a --- /dev/null +++ b/assets/hell/CREDITS.txt @@ -0,0 +1,205 @@ +--Phase 1-- + +Title: "Fight for Freedom" by Korp, based on "Fountain Dance" by Tyler "Picklehammer" Pantella +Intermission: "YOU LIVE!!!!" by Stratos +Victory: "Distance" by Korp +Bunny: "Impactean Welcome Party" by Goji + +Episode 1 - Outpost Outbreak + E1M1: "Stanky Leg Specialist" by Lola "BlueWorrior" Harvey + E1M2: "Look At Me I'm Underhalls Too" by Mark "TheMisterCat" McGill-Smith + E1M3: "Alone with Colors" by Kevin "Velvetic" Martins & Jonathan "julnen" Martins + E1M9: "Hidden Between Spades" by Korp, remix of dave3d42.mid by jute + E1M4: "Flood the City" by Kevin "Velvetic" Martins & Jonathan "julnen" Martins + E1M5: "The Raging Sun" by Kevin "Velvetic" Martins & Jonathan "julnen" Martins, modified by Goji + E1M6: "Oxidine" by Korp, remix of dave3d35.mid by jute + E1M7: "Totally Surrounded" by Josephus "DH4050" Astartes + E1M8: "On the Next Day" by Kevin "Velvetic" Martins + +Episode 2 - Military Labs + E2M1: "Moonstone" by Korp + E2M2: "Voidgaze" by Korp + E2M3: "Workout" by Korp + E2M4: "Diodine" by Korp + E2M5: "Reminiscence" by Korp + E2M9: "Nightstorm" by Korp + E2M6: "Concept of Technology" by Korp + E2M7: "Ribcage" by Korp + E2M8: "Treble Dissension" by Korp + +Episode 3 - Event Horizon + E3M1: "Intrare In Infernum" by Lola "BlueWorrior" Harvey + E3M2: "The Long Road" by Lola "BlueWorrior" Harvey + E3M3: "Wrought Havoc" by Lola "BlueWorrior" Harvey + E3M4: "Cautious Progress" by Lola "BlueWorrior" Harvey + E3M5: "Scorned by Flesh" by Lola "BlueWorrior" Harvey + E3M6: "Soul Banisher" by Lola "BlueWorrior" Harvey + E3M9: "No-Clip to the End" by Lola "BlueWorrior" Harvey + E3M7: "Ultamatum Infurnus" by Lola "BlueWorrior" Harvey + E3M8: "The Zenith" by Lola "BlueWorrior" Harvey + +Episode 4 - Double Impact + E4M1: "Liberate the Stars" by continuum.mid + E4M2: "Fight Harder... To Obtain a Better Victory!" by Proxy-MIDI + E4M9: "The Deepest Secrets" by continuum.mid + E4M3: "Space Spectre" by Matzu + E4M4: "Deliberate Concealment" by continuum.mid + E4M5: "Main Code" by Korp + E4M6: "Andromeda" by ZeMystic + E4M7: "Ambiguity" by Korp + E4M8: "Vs. The Assault Quindecapod" by continuum.mid + + +--Phase 2-- + +Title: "Horizon's Call" by Korp +Intermission: "FTW You 100" by Lola "BlueWorrior" Harvey +Story: dave3d07.mid by jute + +Cluster 1 + MAP01: "Not My First Rodeo..." by Lola "BlueWorrior" Harvey + MAP02: "Dark Underworld" by Lola "BlueWorrior" Harvey + MAP03: "Demon Dance" by Discoholic + MAP04: "Indigo" by Korp + MAP05: "Under Maintenance" by Lola "BlueWorrior" Harvey + MAP06: "Refrigeration" by Korp, based on "Dark Trail" by Tyler "Picklehammer" Pantella + MAP07: "Fun is Infinite at AGM" by continuum.mid + MAP08: "Syntax Error" by Lola "BlueWorrior" Harvey + MAP09: "Terrible Secrets" by Lola "BlueWorrior" Harvey + MAP10: "Soviet Porno" by Jeremy "Vandalorian" Emerson + MAP11: "Undergrowth" by Korp, remix of dave3d01.mid by jute + +Cluster 2 + MAP12: "Sewers" by Discoholic, remix of "Wasteland Scavengers" by Samuel "Blastfrog" Oliver + MAP13: "Fear" by Discoholic + MAP14: "About the Summer" by Kevin "Velvetic" Martins & Jonathan "julnen" Martins + MAP15: "Backpack of Shells" by Lola "BlueWorrior" Harvey + MAP31: "The Machine" by Lola "BlueWorrior" Harvey + MAP32: "Black Mountain" by Kevin "Velvetic" Martins & Jonathan "julnen" Martins + MAP16: "Reflection" by Scragadelic + MAP17: "Mow Them Down!" by Lola "BlueWorrior" Harvey + MAP18: "Realistic Approach" by Kevin "Velvetic" Martins + MAP19: "Deceit" by Julian "Julian Hope" Aubourg, modified by m + MAP20: "Beasts of Horizon" by Josephus "DH4050" Astartes + +Cluster 3 + MAP21: "Rough Landing" by Korp, remix of dave3d08.mid by jute + MAP22: "Alien Waltz" by Korp + MAP23: "Within the Trilo's Den" by MoonDeLaAxel and Korp + MAP24: "Space's Despair" by Korp, remix of "Balancing Act" by Andrew Bassett + MAP25: "I Sought Wisdom in the Chalice But There Was None" by Jeremy "Vandalorian" Emerson + MAP26: "Stormer" by Korp, originally by Samuel "Blastfrog" Oliver + MAP27: "Dreamcatcher (Black and Blue)" by Kevin "Velvetic" Martins & Jonathan "julnen" Martins + MAP28: "Skull Underfoot" by Lola "BlueWorrior" Harvey + MAP29: "Mists Over the Mire" by Tyler "Picklehammer" Pantella + MAP30: "Devoured by the Jaws of Defeat" by Korp + + +--FreeDM-- + +Title: "My Home, The Colosseum" by Goji +Intermission: "Burn the Bodies" by Tyler "Picklehammer" Pantella +Story: "Geoff's Vinyl Collection" by MoonDeLaAxel, remix of "Why?" by Joshua "wastedjamacan" Masek + + DM01: "Zero Fort" by Ralph "Ralphis" Vickers + DM02: "Descent from Grace" by Lola "BlueWorrior" Harvey + DM03: "Dynamite!" by Korp + DM04: "Pit" by Anthony "Ajanddino" Pierce + DM05: "Music for Freedoom 9" by Jeremy "Vandalorian" Emerson + DM06: "Tunnel" by Anthony "Ajanddino" Pierce + DM07: "I Fell Off The Stairs" by Korp + DM08: "Jump Is Not Allowed!" by Korp + + DM09: "Method to My Madness" by Matias "jupiter_ex" Roldan + DM10: "Paranoia" by Tyler "Picklehammer" Pantella + DM11: "Death's Embrace" by Tyler "Picklehammer" Pantella + DM12: "Parallax" by Korp + DM13: "Shut It" by Korp + DM14: "The Heroic Battle" by Tyler "Picklehammer" Pantella + DM15: "Neat" by Ralph "Ralphis" Vickers + DM16: "Deathmatch Construct" by Korp + + DM17: "Panic" by Anthony "Ajanddino" Pierce + DM18: "Metadata" by Korp + DM19: "Tap Water" by Korp, remix of dave3d27.mid by jute + DM20: "Quirkspitter" by Korp, remix of dave3d16.mid by jute + DM21: "Friendly Fire" by Korp + DM22: "Red Foot" by Kevin "Velvetic" Martins and Simone Alauk + DM23: "Shells" by Ospaggi + DM24: "Constructive Criticism" by Matias "jupiter_ex" Roldan + + DM25: "Shoot 'em All" by rostuhan + DM26: "World of Pain" by Josephus "DH4050" Astartes + DM27: "White Lights" by Kevin "Velvetic" Martins and Jonathan "julnen" Martins + DM28: "Bobby Sent Me" by Ralph "Ralphis" Vickers + DM29: "SevenFour" by Ralph "Ralphis" Vickers + DM30: "Freedom for Your Shooting Skills" by rostuhan + DM31: "The Syntax of Time" by Kevin "Velvetic" Martins + DM32: "Bullet Hell" by rostuhan + + +--Previously Featured in 0.12.1-- + +Phase 1 Title: "Fountain Dance" by Tyler "Picklehammer" Pantella +Phase 1 OPL Title: "I Hate Fredrik Johansson" by Toby "Tobester" Collins Jr. (moved to FreeDM Title, not removed) +Phase 1 Intermission: "Music for Freedoom 10" by Jeremy "Vandalorian" Emerson +Victory: "Ominous Theme" by Jared "BlackJar72" Blackburn +Bunny: "Look At The Bunnies" by RestlessRodent +E1M1: e1m1.mid by Ralph "Ralphis" Vickers +E1M3: "Mortal" by Jeremy "Vandalorian" Emerson +E1M5: "sit on a bench 'n release that tensh'n" by Jazz "jmickle" Mickle +E1M6: dave3d35.mid by jute +E1M7: "Sewers" by Discoholic, remix of "Wasteland Scavengers" by Samuel "Blastfrog" Oliver (moved to MAP12, not removed) +E1M9: "The Raging Sun" by Kevin "Velvetic" Martins and Jonathan "julnen" Martins (moved to E1M5, not removed) +E2M1: dave3d38.mid by jute +E2M2: "Animal Skin" by Kevin "Velvetic" Martins and Jonathan "julnen" Martins +E2M3: "Alone with Colors" by Kevin "Velvetic" Martins and Jonathan "julnen" Martins (moved to E1M3, not removed) +E2M4: "Let 'em Burn" by Kevin "Velvetic" Martins +E2M5: "Music for Freedoom 1" by Jeremy "Vandalorian" Emerson +E2M6: "sit on a bench 'n release that tensh'n" by Jazz "jmickle" Mickle +E2M7: "Music for Freedoom 3" by Jeremy "Vandalorian" Emerson +E2M8: "Pit" by Anthony "Ajanddino" Pierce (moved to DM04, not removed) +E2M9: dave3d34.mid by jute +E3M9: "Lipstick" by Jeremy "Vandalorian" Emerson + +Phase 2 + FreeDM Title: "Warpath" by Tyler "Picklehammer" Pantella +Phase 2 Intermission: "Burn the Bodies" by Tyler "Picklehammer" Pantella +MAP04: "Soviet Porno" by Jeremy "Vandalorian" Emerson (moved to MAP10, not removed) +MAP06: "Dark Trail" by Tyler "Picklehammer" Pantella +MAP07: "Riders of Hell" by Tyler "Picklehammer" Pantella +MAP10: "Death's Embrace" by Tyler "Picklehammer" Pantella (moved to DM11, not removed) +MAP11: dave3d02.mid by jute +MAP12: Doom2Alt.mid by Bradley "BobFromReboot" Lavigne +MAP20: dave3d05.mid by jute +MAP21: "Violence Culture" by Jeremy "Vandalorian" Emerson +MAP22: "Mists Over the Mire" by Tyler "Picklehammer" Pantella (moved to MAP29, not removed) +MAP23: dave3d09.mid by jute +MAP24: "Balancing Act" by Andrew Bassett +MAP26: "Crawl" by Jeremy "Vandalorian" Emerson +MAP29: freedoom.mid by Varis Alpha +MAP30: "Paranoia" by Tyler "Picklehammer" Pantella (moved to DM10, not removed) + +DM03: "Music for Freedoom 2" by Jeremy "Vandalorian" Emerson +DM04: "Rush" by Kevin "Velvetic" Martins and Simone Alauk +DM05: map01.mid by Ralph "Ralphis" Vickers +DM06: "The Heroic Battle" by Tyler "Picklehammer" Pantella (moved to DM14, not removed) +DM07: dave3d30.mid by jute +DM08: Dave3d13.mid by jute +DM10: Doom1.mid by Bradley "BobFromReboot" Lavigne +DM11: "Spamhambled In Space" by Mark "TheMisterCat" McGill-Smith +DM12: dave3d24.mid by jute +DM13: e1m1.mid by Ralph "Ralphis" Vickers +DM14: "Munt Battle" by Mark "TheMisterCat" McGill-Smith +DM16: dave3d02.mid by jute +DM17: d_stlks3.mid by Vincent "Vicious" Fong +DM18: dave3d06.mid by jute +DM19: dave3d27.mid by jute +DM20: dave3d16.mid by jute +DM21: dave3d12.mid by jute +DM23: dave3d17.mid by jute +DM25: dave3d20.mid by jute +DM26: dave3d09.mid by jute +DM27: "Music for Freedoom 7" by Jeremy "Vandalorian" Emerson +DM30: dave3d01.mid by jute +DM32: "Munt Battle" by Mark "TheMisterCat" McGill-Smith + diff --git a/assets/hell/boss.bin b/assets/hell/boss.bin new file mode 100644 index 0000000..ab15759 Binary files /dev/null and b/assets/hell/boss.bin differ diff --git a/assets/hell/demon.bin b/assets/hell/demon.bin new file mode 100644 index 0000000..70b92c1 Binary files /dev/null and b/assets/hell/demon.bin differ diff --git a/assets/hell/digits.bin b/assets/hell/digits.bin new file mode 100644 index 0000000..9a5a4a4 Binary files /dev/null and b/assets/hell/digits.bin differ diff --git a/assets/hell/dsdoropn.wav b/assets/hell/dsdoropn.wav new file mode 100644 index 0000000..fd824e6 Binary files /dev/null and b/assets/hell/dsdoropn.wav differ diff --git a/assets/hell/dsfirsht.wav b/assets/hell/dsfirsht.wav new file mode 100644 index 0000000..c47c3e8 Binary files /dev/null and b/assets/hell/dsfirsht.wav differ diff --git a/assets/hell/dsitemup.wav b/assets/hell/dsitemup.wav new file mode 100644 index 0000000..c0a4417 Binary files /dev/null and b/assets/hell/dsitemup.wav differ diff --git a/assets/hell/dspistol.wav b/assets/hell/dspistol.wav new file mode 100644 index 0000000..3e22823 Binary files /dev/null and b/assets/hell/dspistol.wav differ diff --git a/assets/hell/dsplpain.wav b/assets/hell/dsplpain.wav new file mode 100644 index 0000000..86ff57d Binary files /dev/null and b/assets/hell/dsplpain.wav differ diff --git a/assets/hell/dspodth1.wav b/assets/hell/dspodth1.wav new file mode 100644 index 0000000..a3b41a5 Binary files /dev/null and b/assets/hell/dspodth1.wav differ diff --git a/assets/hell/dspopain.wav b/assets/hell/dspopain.wav new file mode 100644 index 0000000..70611f0 Binary files /dev/null and b/assets/hell/dspopain.wav differ diff --git a/assets/hell/dssgcock.wav b/assets/hell/dssgcock.wav new file mode 100644 index 0000000..1719d3d Binary files /dev/null and b/assets/hell/dssgcock.wav differ diff --git a/assets/hell/dsshotgn.wav b/assets/hell/dsshotgn.wav new file mode 100644 index 0000000..ff5dd4f Binary files /dev/null and b/assets/hell/dsshotgn.wav differ diff --git a/assets/hell/dstelept.wav b/assets/hell/dstelept.wav new file mode 100644 index 0000000..f43008b Binary files /dev/null and b/assets/hell/dstelept.wav differ diff --git a/assets/hell/faces.bin b/assets/hell/faces.bin new file mode 100644 index 0000000..d02a785 Binary files /dev/null and b/assets/hell/faces.bin differ diff --git a/assets/hell/fire.bin b/assets/hell/fire.bin new file mode 100644 index 0000000..36c0073 Binary files /dev/null and b/assets/hell/fire.bin differ diff --git a/assets/hell/hud.bin b/assets/hell/hud.bin new file mode 100644 index 0000000..a61d0e0 Binary files /dev/null and b/assets/hell/hud.bin differ diff --git a/assets/hell/imp.bin b/assets/hell/imp.bin new file mode 100644 index 0000000..086d6cf Binary files /dev/null and b/assets/hell/imp.bin differ diff --git a/assets/hell/impacts.bin b/assets/hell/impacts.bin new file mode 100644 index 0000000..05b1780 Binary files /dev/null and b/assets/hell/impacts.bin differ diff --git a/assets/hell/items.bin b/assets/hell/items.bin new file mode 100644 index 0000000..ad10ec9 Binary files /dev/null and b/assets/hell/items.bin differ diff --git a/assets/hell/manifest.json b/assets/hell/manifest.json new file mode 100644 index 0000000..ff3cff3 --- /dev/null +++ b/assets/hell/manifest.json @@ -0,0 +1,169 @@ +{ + "version": "0.13.0", + "url": "https://github.com/freedoom/freedoom/releases/download/v0.13.0/freedoom-0.13.0.zip", + "archive_sha256": "3f9b264f3e3ce503b4fb7f6bdcb1f419d93c7b546f4df3e874dd878db9688f59", + "packs": { + "soldier": [ + "POSSA1", + "POSSB1", + "POSSC1", + "POSSD1", + "POSSE1", + "POSSF1", + "POSSG1", + "POSSH0", + "POSSI0", + "POSSJ0", + "POSSK0", + "POSSL0" + ], + "demon": [ + "SARGA1", + "SARGB1", + "SARGC1", + "SARGD1", + "SARGE1", + "SARGF1", + "SARGH1", + "SARGI0", + "SARGJ0", + "SARGK0", + "SARGL0", + "SARGN0" + ], + "imp": [ + "TROOA1", + "TROOB1", + "TROOC1", + "TROOD1", + "TROOE1", + "TROOF1", + "TROOH1", + "TROOI0", + "TROOJ0", + "TROOK0", + "TROOL0", + "TROOM0" + ], + "boss": [ + "BOSSA1", + "BOSSB1", + "BOSSC1", + "BOSSD1", + "BOSSE1", + "BOSSF1", + "BOSSH1", + "BOSSI0", + "BOSSJ0", + "BOSSK0", + "BOSSL0", + "BOSSO0" + ], + "pistol": [ + "PISGA0", + "PISGB0", + "PISGC0", + "PISGD0", + "PISGE0" + ], + "shotgun": [ + "SHTGA0", + "SHTGB0", + "SHTGC0", + "SHTGD0" + ], + "fire": [ + "BAL1A0", + "BAL1B0", + "BAL1C0", + "BAL1D0", + "BAL1E0" + ], + "portal": [ + "TFOGA0", + "TFOGB0", + "TFOGC0", + "TFOGD0", + "TFOGE0", + "TFOGF0", + "TFOGG0", + "TFOGH0" + ], + "items": [ + "MEDIA0", + "ARM1A0", + "CLIPA0", + "SHELA0", + "SHOTA0", + "BKEYA0", + "BON2A0" + ], + "materials": [ + "MARBLE1", + "GSTONE1", + "SKIN2", + "BIGDOOR2", + "SW1GARG", + "SP_HOT1", + "FLOOR7_2", + "CEIL5_2", + "LAVA1", + "LAVA2", + "LAVA3", + "LAVA4" + ], + "hud": [ + "STBAR" + ], + "digits": [ + "STTNUM0", + "STTNUM1", + "STTNUM2", + "STTNUM3", + "STTNUM4", + "STTNUM5", + "STTNUM6", + "STTNUM7", + "STTNUM8", + "STTNUM9", + "STTPRCNT" + ], + "faces": [ + "STFST00", + "STFST01", + "STFST02", + "STFOUCH0", + "STFST10", + "STFST11", + "STFST12", + "STFOUCH1", + "STFST20", + "STFST21", + "STFST22", + "STFOUCH2", + "STFST30", + "STFST31", + "STFST32", + "STFOUCH3", + "STFST40", + "STFST41", + "STFST42", + "STFOUCH4", + "STFDEAD0" + ], + "impacts": [ + "PUFFA0", + "PUFFB0", + "PUFFC0", + "PUFFD0" + ], + "painting": [ + "SKY3", + "LAVA1", + "GSTONE1", + "BOSSA1", + "TROOA1", + "SARGA1" + ] + } +} diff --git a/assets/hell/materials.bin b/assets/hell/materials.bin new file mode 100644 index 0000000..d402325 Binary files /dev/null and b/assets/hell/materials.bin differ diff --git a/assets/hell/painting-preview.png b/assets/hell/painting-preview.png new file mode 100644 index 0000000..2749d9e Binary files /dev/null and b/assets/hell/painting-preview.png differ diff --git a/assets/hell/painting.bin b/assets/hell/painting.bin new file mode 100644 index 0000000..a373921 Binary files /dev/null and b/assets/hell/painting.bin differ diff --git a/assets/hell/pistol.bin b/assets/hell/pistol.bin new file mode 100644 index 0000000..8f95040 Binary files /dev/null and b/assets/hell/pistol.bin differ diff --git a/assets/hell/portal.bin b/assets/hell/portal.bin new file mode 100644 index 0000000..50bed00 Binary files /dev/null and b/assets/hell/portal.bin differ diff --git a/assets/hell/preview.png b/assets/hell/preview.png new file mode 100644 index 0000000..79f641b Binary files /dev/null and b/assets/hell/preview.png differ diff --git a/assets/hell/shotgun.bin b/assets/hell/shotgun.bin new file mode 100644 index 0000000..0e9f95c Binary files /dev/null and b/assets/hell/shotgun.bin differ diff --git a/assets/hell/soldier.bin b/assets/hell/soldier.bin new file mode 100644 index 0000000..9a1bc1e Binary files /dev/null and b/assets/hell/soldier.bin differ diff --git a/bin/poker.exe b/bin/poker.exe index acc5ab5..abd39a1 100644 Binary files a/bin/poker.exe and b/bin/poker.exe differ diff --git a/build/AssemblerCasinoLauncher.cs b/build/AssemblerCasinoLauncher.cs index 79658a3..1462d69 100644 --- a/build/AssemblerCasinoLauncher.cs +++ b/build/AssemblerCasinoLauncher.cs @@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher { static class Program { - const string ExpectedPokerSha256 = "B0F5CE31D9A2467CFE8AF66CA90CC545F75318F11D149473A4377A2A278C8B23"; + const string ExpectedPokerSha256 = "EC24778AE95D57B80247180AB0121279C8224A2B7E36801A168AEEF391E6B34B"; [STAThread] static void Main() diff --git a/build/graphics/poker.exe b/build/graphics/poker.exe index acc5ab5..abd39a1 100644 Binary files a/build/graphics/poker.exe and b/build/graphics/poker.exe differ diff --git a/casino3d.inc b/casino3d.inc index 65d28e4..c70ad8c 100644 --- a/casino3d.inc +++ b/casino3d.inc @@ -175,6 +175,7 @@ include src_graphics\backrooms_data.inc include src_graphics\observer_data.inc include src_graphics\darts_data.inc include src_graphics\jackbox_data.inc +include src_graphics\hell_fps_data.inc .data ALIGN 4 @@ -472,7 +473,7 @@ screenBuffer dd SCREEN_SIZE dup(?) ALIGN 4 zBuffer dd SCREEN_W dup(?) ALIGN 4 -texturesBuffer dd (185 * 128 * 128) dup(?) +texturesBuffer dd (197 * 128 * 128) dup(?) hudNumBuffer db 32 dup(?) @@ -4496,6 +4497,8 @@ unlock_done: UnlockMouse3D ENDP UpdateMouseLockState PROC + cmp engineMode,MODE_HELL_FPS + je lock_interior_check cmp engineMode,MODE_STAIRS je lock_interior_check cmp engineMode, MODE_WALK @@ -4567,6 +4570,10 @@ skip_mouse_look: mov eax,50 @@: imul eax, 200 + cmp engineMode,MODE_HELL_FPS + jne @F + imul eax,2 +@@: mov speed, eax cmp keys3D[10h], 0 je @F @@ -4667,19 +4674,33 @@ check_total_movement: add eax, deltaMoveY mov nextY, eax + cmp engineMode,MODE_HELL_FPS + jne hf_move_regular_x + invoke HFPlayerCanWalk,nextX,playerY + jmp hf_move_check_x +hf_move_regular_x: invoke CanWalk3D, nextX, playerY +hf_move_check_x: test eax, eax jz @F mov eax, nextX mov playerX, eax @@: + cmp engineMode,MODE_HELL_FPS + jne hf_move_regular_y + invoke HFPlayerCanWalk,playerX,nextY + jmp hf_move_check_y +hf_move_regular_y: invoke CanWalk3D, playerX, nextY +hf_move_check_y: test eax, eax jz input_action mov eax, nextY mov playerY, eax input_action: + cmp engineMode,MODE_HELL_FPS + je player_input_complete cmp brActive3D,0 jne player_input_complete cmp engineMode,MODE_STAIRS @@ -5254,6 +5275,9 @@ include src_graphics\observer.inc include src_graphics\darts.inc include src_graphics\jackbox.inc include src_graphics\backrooms.inc +include src_graphics\hell_fps.inc +include src_graphics\hell_fps_combat.inc +include src_graphics\hell_fps_render.inc ; --- Main Loop of the 3D Casino Engine --- StartHellTower3D PROC uses ebx esi edi @@ -5317,6 +5341,11 @@ engine_observer_skip: invoke Pressed3D, 1Bh test eax, eax jz engine_tick + cmp engineMode,MODE_HELL_FPS + jne @F + call LeaveHellFPS3D + jmp engine_tick +@@: cmp engineMode, MODE_INTRO jne @F call FinishStartup3D @@ -5397,6 +5426,14 @@ check_arrival_mode: call RenderArrival3D jmp present_frame check_walk_mode: + cmp engineMode,MODE_HELL_FPS + jne @F + call HandleHellFPS3D + cmp engineMode,MODE_WALK + je check_walk_mode + call RenderHellFPS3D + jmp present_frame +@@: cmp engineMode, MODE_WALK jne check_slots_mode cmp brActive3D,0 @@ -5405,6 +5442,8 @@ check_walk_mode: call UpdateCamera3D call CheckInteractions call HandlePlayerInput + cmp engineMode,MODE_HELL_FPS + je check_walk_mode call BackroomsEntry3D cmp brActive3D,0 jne engine_backrooms @@ -5741,6 +5780,7 @@ engine_idle: invoke Sleep, 50 jmp engine_loop engine_shutdown: + call FreeHellFPS3D call StopFortuneWorker3D call SettleScratchOnQuit3D call UnlockMouse3D diff --git a/dist/Assembler Casino.exe b/dist/Assembler Casino.exe index 95dfdd2..746c604 100644 Binary files a/dist/Assembler Casino.exe and b/dist/Assembler Casino.exe differ diff --git a/docs/HELL_ASSET_PIPELINE.md b/docs/HELL_ASSET_PIPELINE.md new file mode 100644 index 0000000..c29f9fc --- /dev/null +++ b/docs/HELL_ASSET_PIPELINE.md @@ -0,0 +1,25 @@ +# Hell presentation asset contract + +Imported offline from the pinned Freedoom 0.13.0 archive using +`tools/generators/import_freedoom.py`. Runtime remains MASM32. + +All packs: 20-byte `ANM1` header (five little-endian DWORDs: magic, width, height, +frame count, payload bytes), then row-major BGRA frames. Nearest-neighbour only. + +| Pack | Frame size | Layout / alpha / origin | +|---|---|---| +| `hud.bin` | 320×32 | One `STBAR` frame, rendered 2× at (0,416) | +| `digits.bin` | 16×16 | Ten digits and percent, right-aligned; transparent | +| `faces.bin` | 24×32 | Five health bands × four expressions, then dead; bottom-centred | +| `impacts.bin` | 32×32 | Four `PUFF` frames, shared sprite origin; transparent | +| `painting.bin` | 512×256 | One opaque, power-of-two wall texture; full-frame UVs | + +Painting composes Freedoom sky, lava, masonry and monsters with an authored gilt +border. Its preview uses intended landscape aspect. Engine maps the texture onto +a 6×2-world-unit wall panel; it is not a camera-facing sprite. Power-of-two +dimensions are required by `DrawCityVertical3D`'s masked texture coordinates. +No generated AI image or proprietary Doom artwork is used. + +HUD glyphs and portrait retain original pixel-art palette. Frame padding avoids +per-expression scaling jitter. See `assets/hell/manifest.json` for source lumps +and `assets/hell/COPYING.txt` / `CREDITS.txt` for licence and attribution. diff --git a/docs/HELL_EXPEDITION.md b/docs/HELL_EXPEDITION.md new file mode 100644 index 0000000..ae1c8a9 --- /dev/null +++ b/docs/HELL_EXPEDITION.md @@ -0,0 +1,94 @@ +# Hell Expedition + +## Play + +Launch `poker.exe` or `dist/Assembler Casino.exe`. On the casino's first floor, +approach the large gilt-framed hell painting on the wall opposite the bar +(map position 6, 30.9), face it and press **E**. The upstairs Hell Tower remains +a separate game. The old freestanding portal and its collision frame are gone. + +| Input | Action | +|---|---| +| WASD | Move / strafe | +| Mouse | Turn | +| Left mouse / Space | Fire; hold for repeated fire | +| 1 / 2 | Pistol / shotgun, after collecting it | +| E | Open nearby gates/secrets; activate final exit | +| Enter after death | Restart expedition | +| Esc | Return to casino | + +Collect the shotgun near the starting point. Cross the barracks and lava court, +find the blue key in the northern crypt, open the eastern gate and defeat the +warden in the southern sanctum. Use the exit portal to finish. Two concealed +side rooms contain extra supplies. Lava causes damage; armour absorbs half of +incoming damage while it lasts. Health and ammunition are expedition-local. + +Death and retry do not deduct casino money. The first successful clear credits +**$6,660**, subject to the existing bank cap. Later clears show statistics without +paying again. Leaving or retrying starts a fresh expedition; mid-level progress +is not saved. Completion belongs to the current casino profile. + +## Implementation + +- MASM32 runtime: `src_graphics/hell_fps*.inc`; integrated with the existing + raycaster, collision, window/input and signed profile code. +- Authored 32×32 map: five encounter regions, 28 enemies across four types, + 24 pickups, a blue-key gate, two secrets and a boss-controlled exit. +- Combat simulation: fixed 35 Hz; existing frame-time-scaled player movement. +- Hitscan pistol/shotgun, wall occlusion, enemy attack windups, BFS navigation, + projectile pool, health/armour, weapon and monster animation, HUD/statistics. +- Shotgun: seven independent pellets per shell, approximately 12.7-degree + horizontal cone, individual damage/occlusion and short-lived impact puffs. + A single shot can hit several enemies; the pistol remains a single ray. +- Doom-layout Freedoom status bar: large red ammunition/health/armour numbers, + damage-dependent animated face, weapon slots, key and ammunition reserves. +- Completion flag: bit 18 in native and preview signed saves; file sizes stay + unchanged. Native profiles remain the normal game save path. +- Music: existing `assets/music/tower/Mick_Gordon_-_The_Only_Thing_They_Fear_Is_You_OST_DOOM_Eternal_72020713.mp3`. + Dedicated MCI music alias at 35% of the previous music level (still follows + the volume setting, including mute), applied before playback starts. + Six independent waveaudio effect channels retain their existing loudness. + +## Assets and regeneration + +Graphics and effects originate from **Freedoom 0.13.0**, imported offline into +ANM1 BGRA packs and WAV files. Licence and credits ship in +`assets/hell/COPYING.txt` and `assets/hell/CREDITS.txt`. Exact source lumps, +download URL and archive SHA-256 are in `assets/hell/manifest.json`. +The casino level is original; no Doom WAD is loaded at runtime. + +```powershell +python tools/generators/import_freedoom.py path/to/freedoom-0.13.0.zip +python tools/generators/make_hell_level.py +cmd /c build.bat +powershell -ExecutionPolicy Bypass -File tools/tests/publish_graphics.ps1 +``` + +The final command updates local root/bin game copies and recompiles the existing +C# launcher with the new integrity hash. It does not upload a release. +Python/Pillow are offline development dependencies only. Include all of +`assets/hell/` and the existing soundtrack when copying the game distribution. +Freedoom's licence applies to its assets, not to the separate soundtrack. + +## Verification + +```powershell +python tools/tests/check_hell_fps.py +python tools/tests/check_hell_fps.py --window +python tools/tests/check_artifacts.py +``` + +Native checks execute shipping ASM procedures: portal targeting, wall/body +collisions, single-ray pistol versus multi-target pellet spread, wall occlusion, +one-shell consumption/empty weapon, impact pool, pickups, armour/death/retry, key gate, +secrets, 1,050 AI ticks, a finite-ammo boss fight with strafing and normal health, +boss-locked exit, one-time reward, both signed save +formats and return to the hall. Framebuffers are saved in `build/scratch/`. +The window check presents 120 frames through Win32, sends mouse press/release +events, checks shots, all six SFX opens and music's MCI playing status, then +returns and closes. It also exercises asset-path resolution from a non-root +working directory. Tests compare all `runtime/` file hashes before/after. + +These are automated mechanics/render/audio checks, not a complete human +playthrough. The requested 10–15-minute first-clear duration and final difficulty +still need playtesting; no measured duration is claimed. diff --git a/poker.asm b/poker.asm index cf7ce96..d9ffc29 100644 --- a/poker.asm +++ b/poker.asm @@ -827,6 +827,10 @@ save_stats_played_loop: shl ecx,17 or eax,ecx or eax,2 + mov ecx,hfCompleted + and ecx,1 + shl ecx,18 + or eax,ecx mov DWORD PTR [saveBuffer+304],eax invoke ArtifactSaveSignature3D,OFFSET saveBuffer mov DWORD PTR [saveBuffer+308],eax @@ -902,6 +906,10 @@ load_file_opened: shr eax,17 and eax,1 mov brEnabled3D,eax + mov eax,DWORD PTR [saveBuffer+304] + shr eax,18 + and eax,1 + mov hfCompleted,eax load_signature_ok: mov eax, DWORD PTR [saveBuffer+4] mov bankroll, eax @@ -2061,7 +2069,18 @@ ENDIF IFDEF BACKROOMS_TEST include tools/tests/backrooms_test.inc ENDIF +IFDEF HELL_FPS_TEST +include tools/tests/hell_fps_test.inc +ENDIF main PROC +IFDEF HELL_FPS_TEST +IFDEF HELL_FPS_WINDOW_TEST + call RunHellFPSWindowTest +ELSE + call RunHellFPSTest +ENDIF + invoke ExitProcess,eax +ENDIF IFDEF BACKROOMS_TEST call RunBackroomsTest3D invoke ExitProcess,eax diff --git a/poker.exe b/poker.exe index acc5ab5..abd39a1 100644 Binary files a/poker.exe and b/poker.exe differ diff --git a/poker.obj b/poker.obj index 41ce6b8..dbcf28a 100644 Binary files a/poker.obj and b/poker.obj differ diff --git a/src_graphics/artifact_system.inc b/src_graphics/artifact_system.inc index 34bcd6d..2b6b564 100644 --- a/src_graphics/artifact_system.inc +++ b/src_graphics/artifact_system.inc @@ -33,7 +33,7 @@ ValidateArtifactSave3D PROC uses esi edi source:DWORD cmp DWORD PTR [esi+280],32465241h jne artifact_save_invalid mov eax,[esi+304] - and eax,0FFFCFFFFh + and eax,0FFF8FFFFh cmp eax,2 jne artifact_save_invalid test DWORD PTR [esi+304],20000h @@ -72,6 +72,7 @@ ArtifactCredit3D PROC amount:DWORD ArtifactCredit3D ENDP ResetArtifacts3D PROC + mov hfCompleted,0 mov brOwned3D,0 mov brEnabled3D,1 mov charmCloverUses,0 diff --git a/src_graphics/city_bins.inc b/src_graphics/city_bins.inc index 1291832..20d2f67 100644 --- a/src_graphics/city_bins.inc +++ b/src_graphics/city_bins.inc @@ -5,6 +5,23 @@ cityRayY3D dd SCREEN_W dup(?) ; Exterior-only materials do not consume or overwrite the hall's texture slots. ResolveCitySurface3D PROC material:DWORD mov eax,material + cmp eax,HF_PAINTING_MATERIAL + jne hf_not_painting + mov eax,hfPaintingArt.pixels + mov ecx,512 + mov edx,256 + ret +hf_not_painting: + cmp eax,HF_WALL + jb hf_surface_other + cmp eax,HF_LAVA+3 + ja hf_surface_other + shl eax,16 + add eax,OFFSET texturesBuffer + mov ecx,128 + mov edx,128 + ret +hf_surface_other: cmp hallLevel3D,HALL_BACKROOMS jne br_surface_other_world cmp eax,BR_WALL diff --git a/src_graphics/hall_scene.inc b/src_graphics/hall_scene.inc index a4e2a16..66f7a7b 100644 --- a/src_graphics/hall_scene.inc +++ b/src_graphics/hall_scene.inc @@ -20,6 +20,10 @@ hall_plane_row: mov eax, viewCeiling3D sub eax, viewEye3D mov material, 101 + cmp engineMode,MODE_HELL_FPS + jne @F + mov material,HF_CEILING +@@: cmp brActive3D,0 je @F cmp hallLevel3D,HALL_BACKROOMS @@ -756,6 +760,7 @@ hall_render_lounge: invoke DrawCityObject3D,OFFSET scrStation3D,0 call RenderHallDressing3D call RenderHallPortals3D + call RenderHellPortal3D call RenderFortuneEntrance3D call RenderLift3D call ApplyHallLighting3D @@ -865,6 +870,7 @@ hall_check_other: call CheckNpcInteractions3D call CheckFortuneInteractions3D call CheckRoofInteractions3D + invoke HallTarget3D,HF_PORTAL_X,HF_PORTAL_Y,INTERACT_HELL_FPS,OFFSET hfPortalPrompt ret hall_check_bunker: mov interactTargetId, 0 @@ -874,6 +880,12 @@ hall_check_bunker: CheckHallInteractions3D ENDP TriggerHallInteraction3D PROC + cmp interactTargetId,INTERACT_HELL_FPS + jne @F + call BeginHellFPS3D + mov eax,1 + ret +@@: call TryBowlingInteraction3D test eax,eax jnz hall_trigger_handled diff --git a/src_graphics/hell_fps.inc b/src_graphics/hell_fps.inc new file mode 100644 index 0000000..41f8db0 --- /dev/null +++ b/src_graphics/hell_fps.inc @@ -0,0 +1,277 @@ +; Expedition lifecycle and fixed 35 Hz simulation. No external game runtime. +HFPlaySound PROC channel:DWORD + cmp hfSoundReady,0 + je @F + mov eax,channel + cmp eax,6 + jae @F + invoke mciSendStringA,hfSndPlays[eax*4],0,0,0 +@@: + ret +HFPlaySound ENDP + +HFLoad PROC uses esi edi + cmp hfLoaded,1 + je hf_load_ok + xor esi,esi +hf_load_next: + invoke LoadAnimPack3D,hfPacks[esi*4],hfPaths[esi*4] + test eax,eax + jz hf_load_failed + inc esi + cmp esi,HF_PACK_COUNT + jb hf_load_next + ; Dedicated texture slots, never overwrite casino materials. + mov esi,hfMaterials.pixels + mov edi,OFFSET texturesBuffer+HF_WALL*65536 + mov ecx,12*128*128 + cld + rep movsd + mov hfLoaded,1 +hf_load_ok: + mov eax,1 + ret +hf_load_failed: + call FreeHellFPS3D + xor eax,eax + ret +HFLoad ENDP + +FreeHellFPS3D PROC uses esi + xor esi,esi +@@: + invoke FreeAnimPack3D,hfPacks[esi*4] + inc esi + cmp esi,HF_PACK_COUNT + jb @B + mov hfLoaded,0 + invoke mciSendStringA,OFFSET hfMusicClose,0,0,0 + xor esi,esi +@@: + invoke mciSendStringA,hfSndCloses[esi*4],0,0,0 + inc esi + cmp esi,6 + jb @B + mov hfSoundReady,0 + ret +FreeHellFPS3D ENDP + +HFReset PROC uses esi edi ebx + mov esi,OFFSET hfMapTemplate + mov edi,OFFSET hfMap + mov ecx,256 + cld + rep movsd + xor eax,eax + mov edi,OFFSET hfBolts + mov ecx,SIZEOF hfBolts/4 + rep stosd + mov edi,OFFSET hfImpacts + mov ecx,SIZEOF hfImpacts/4 + rep stosd + mov edi,OFFSET hfItemTaken + mov ecx,HF_ITEMS + rep stosd + mov esi,OFFSET hfEnemySeeds + mov edi,OFFSET hfEnemies + mov ebx,HF_ENEMIES +hf_reset_enemy: + lodsd + mov [edi].HFEnemy.x,eax + lodsd + mov [edi].HFEnemy.y,eax + lodsd + mov [edi].HFEnemy.kind,eax + mov eax,hfMonsterHP[eax*4] + mov [edi].HFEnemy.hp,eax + mov [edi].HFEnemy.cooldown,35 + mov [edi].HFEnemy.pose,0 + mov [edi].HFEnemy.timer,0 + mov [edi].HFEnemy.awake,0 + add edi,SIZEOF HFEnemy + dec ebx + jnz hf_reset_enemy + mov hfState,0 + mov hfHealth,100 + mov hfArmor,0 + mov hfBullets,80 + mov hfShells,0 + mov hfWeapon,0 + mov hfHasShotgun,0 + mov hfKey,0 + mov hfSecrets,0 + mov hfKills,0 + mov hfTicks,0 + mov hfAccumulator,0 + mov hfCooldown,0 + mov hfFlash,0 + mov hfHurt,0 + mov hfDoorMask,0 + mov hfAwarded,0 + mov hfFireHeld,0 + mov hfFirePending,0 + mov hfUsePending,0 + mov playerX,3*FP_1+FP_HALF + mov playerY,26*FP_1+FP_HALF + mov playerAngle,0 + mov worldMap3D,OFFSET hfMap + mov hallLevel3D,99 + mov floorTexture3D,HF_FLOOR + mov viewEye3D,FP_1 + mov viewCeiling3D,FP_1*5/2 + mov outdoorRender3D,0 + mov viewHorizon3D,HALF_H + mov engineMode,MODE_HELL_FPS + mov hfMessage,OFFSET hfWelcomeText + mov hfMessageTime,210 + call ClearKeys3D + call UpdateCamera3D + call HFBuildFlow + ret +HFReset ENDP + +BeginHellFPS3D PROC uses esi + call HFLoad + test eax,eax + jnz @F + invoke CopyString3D,OFFSET hfMissingText,OFFSET flashMessage + mov flashTimer,180 + ret +@@: + mov eax,playerX + mov hfOldX,eax + mov eax,playerY + mov hfOldY,eax + mov eax,playerAngle + mov hfOldAngle,eax + mov eax,worldMap3D + mov hfOldMap,eax + mov eax,floorTexture3D + mov hfOldFloor,eax + mov eax,hallLevel3D + mov hfOldLevel,eax + mov eax,viewEye3D + mov hfOldEye,eax + mov eax,viewCeiling3D + mov hfOldCeiling,eax + call StopTowerMusic + call StopJukeboxTrack3D + invoke PlayCasinoSound3D,0,0,0 + invoke mciSendStringA,OFFSET hfMusicOpen,0,0,0 + mov hfMusicError,eax + mov hfLastVolume,-1 + call HFUpdateMusicVolume + invoke mciSendStringA,OFFSET hfMusicPlay,0,0,0 + or hfMusicError,eax + mov hfSoundFailures,0 + xor esi,esi +hf_open_sound: + invoke mciSendStringA,hfSndOpens[esi*4],0,0,0 + test eax,eax + jz @F + inc hfSoundFailures +@@: + inc esi + cmp esi,6 + jb hf_open_sound + mov hfSoundReady,1 + call HFReset + ret +BeginHellFPS3D ENDP + +LeaveHellFPS3D PROC uses esi + mov eax,hfOldX + mov playerX,eax + mov eax,hfOldY + mov playerY,eax + mov eax,hfOldAngle + mov playerAngle,eax + mov eax,hfOldMap + mov worldMap3D,eax + mov eax,hfOldFloor + mov floorTexture3D,eax + mov eax,hfOldLevel + mov hallLevel3D,eax + mov eax,hfOldEye + mov viewEye3D,eax + mov eax,hfOldCeiling + mov viewCeiling3D,eax + mov hfFireHeld,0 + mov hfFirePending,0 + mov engineMode,MODE_WALK + mov paused3D,0 + invoke mciSendStringA,OFFSET hfMusicClose,0,0,0 + xor esi,esi +@@: + invoke mciSendStringA,hfSndCloses[esi*4],0,0,0 + inc esi + cmp esi,6 + jb @B + mov hfSoundReady,0 + call ClearKeys3D + call UpdateCamera3D + ret +LeaveHellFPS3D ENDP + +HFUpdateMusicVolume PROC + mov eax,settingsVolume + cmp eax,hfLastVolume + je @F + mov hfLastVolume,eax + imul eax,HF_MUSIC_SCALE + invoke FormatNumber,eax,OFFSET hfNumber + invoke CopyCString,OFFSET hfVolumeCommand,OFFSET hfVolumePrefix + invoke AppendCString,OFFSET hfVolumeCommand,OFFSET hfNumber + invoke mciSendStringA,OFFSET hfVolumeCommand,0,0,0 + mov hfVolumeError,eax +@@: + ret +HFUpdateMusicVolume ENDP + +HandleHellFPS3D PROC uses ebx + call HFUpdateMusicVolume + cmp hfState,0 + je hf_handle_play + invoke Pressed3D,0Dh + test eax,eax + jz hf_handle_done + cmp hfState,1 + jne hf_handle_exit + call HFReset + ret +hf_handle_exit: + call LeaveHellFPS3D + ret +hf_handle_play: + call HandlePlayerInput + invoke Pressed3D,31h + test eax,eax + jz @F + mov hfWeapon,0 +@@: + invoke Pressed3D,32h + test eax,eax + jz @F + cmp hfHasShotgun,0 + je @F + mov hfWeapon,1 +@@: + invoke Pressed3D,45h + or hfUsePending,eax + mov eax,mouseClick3D + or hfFirePending,eax + mov mouseClick3D,0 + mov eax,deltaTime3D + imul eax,35 + add hfAccumulator,eax +hf_handle_tick: + cmp hfAccumulator,1000 + jb hf_handle_done + sub hfAccumulator,1000 + call HFTick + cmp hfState,0 + je hf_handle_tick + mov hfAccumulator,0 +hf_handle_done: + ret +HandleHellFPS3D ENDP diff --git a/src_graphics/hell_fps_combat.inc b/src_graphics/hell_fps_combat.inc new file mode 100644 index 0000000..a10dc45 --- /dev/null +++ b/src_graphics/hell_fps_combat.inc @@ -0,0 +1,764 @@ +; All positions/velocities are signed 16.16; bounded pools and fixed ticks. +HFDistance PROC x0:DWORD,y0:DWORD,x1:DWORD,y1:DWORD + mov eax,x0 + sub eax,x1 + cdq + xor eax,edx + sub eax,edx + mov ecx,eax + mov eax,y0 + sub eax,y1 + cdq + xor eax,edx + sub eax,edx + cmp eax,ecx + jae @F + mov eax,ecx +@@: + ret +HFDistance ENDP + +HFPlayerCanWalk PROC uses esi edi px:DWORD,py:DWORD + invoke CanWalk3D,px,py + test eax,eax + jz hf_player_walk_done + mov esi,OFFSET hfEnemies + mov edi,HF_ENEMIES +hf_player_body: + cmp [esi].HFEnemy.hp,0 + jle hf_player_body_next + invoke HFDistance,px,py,[esi].HFEnemy.x,[esi].HFEnemy.y + cmp eax,FP_1*9/20 + jb hf_player_body_block +hf_player_body_next: + add esi,SIZEOF HFEnemy + dec edi + jnz hf_player_body + mov eax,1 + ret +hf_player_body_block: + xor eax,eax +hf_player_walk_done: + ret +HFPlayerCanWalk ENDP + +HFLineClear PROC uses ebx esi edi x0:DWORD,y0:DWORD,x1:DWORD,y1:DWORD + local stepX:DWORD,stepY:DWORD,rx:DWORD,ry:DWORD + invoke HFDistance,x0,y0,x1,y1 + shr eax,12 ; quarter of a collision radius per sample, <= 1/16 tile + inc eax + mov ebx,eax + mov eax,x1 + sub eax,x0 + cdq + idiv ebx + mov stepX,eax + mov eax,y1 + sub eax,y0 + cdq + idiv ebx + mov stepY,eax + mov esi,x0 + mov edi,y0 +hf_los_step: + add esi,stepX + add edi,stepY + mov eax,esi + sar eax,16 + cmp eax,32 + jae hf_los_blocked + mov ecx,edi + sar ecx,16 + cmp ecx,32 + jae hf_los_blocked + shl ecx,5 + add eax,ecx + cmp hfMap[eax],0 + jne hf_los_blocked + dec ebx + jnz hf_los_step + mov eax,1 + ret +hf_los_blocked: + xor eax,eax + ret +HFLineClear ENDP + +HFDamagePlayer PROC uses ebx amount:DWORD + cmp hfState,0 + jne hf_damage_done + mov ebx,amount + mov eax,ebx + shr eax,1 + cmp eax,hfArmor + jbe @F + mov eax,hfArmor +@@: + sub hfArmor,eax + sub ebx,eax + sub hfHealth,ebx + mov hfHurt,10 + invoke HFPlaySound,2 + cmp hfHealth,0 + jg hf_damage_done + mov hfHealth,0 + mov hfState,1 + mov hfFireHeld,0 +hf_damage_done: + ret +HFDamagePlayer ENDP + +HFDamageEnemy PROC uses esi enemy:DWORD,amount:DWORD + mov esi,enemy + cmp [esi].HFEnemy.hp,0 + jle hf_enemy_damage_done + mov eax,amount + sub [esi].HFEnemy.hp,eax + mov [esi].HFEnemy.awake,1 + mov [esi].HFEnemy.pose,6 + mov [esi].HFEnemy.timer,4 + cmp [esi].HFEnemy.hp,0 + jg hf_enemy_damage_done + mov [esi].HFEnemy.hp,0 + mov [esi].HFEnemy.pose,7 + mov [esi].HFEnemy.timer,0 + inc hfKills +hf_enemy_damage_done: + ret +HFDamageEnemy ENDP + +HFFire PROC uses esi edi ebx + local pellet:DWORD,angle:DWORD,rx:DWORD,ry:DWORD,stepX:DWORD,stepY:DWORD,steps:DWORD + cmp hfCooldown,0 + jne hf_fire_done + cmp hfWeapon,1 + je hf_fire_shotgun + cmp hfBullets,0 + je hf_fire_done + dec hfBullets + mov hfCooldown,9 + mov pellet,1 + invoke HFPlaySound,4 + jmp hf_fire_begin +hf_fire_shotgun: + cmp hfShells,0 + je hf_fire_done + dec hfShells + mov hfCooldown,29 + mov pellet,HF_SHOT_PELLETS + invoke HFPlaySound,0 +hf_fire_begin: + mov hfFlash,4 +hf_fire_pellet: + mov eax,playerAngle + cmp hfWeapon,0 + je @F + mov ecx,pellet + add eax,hfPelletSpread[ecx*4-4] +@@: + and eax,511 + mov angle,eax + mov ecx,tableCos[eax*4] + sar ecx,3 + mov stepX,ecx + mov ecx,tableSin[eax*4] + sar ecx,3 + mov stepY,ecx + mov eax,playerX + mov rx,eax + mov eax,playerY + mov ry,eax + mov steps,192 +hf_ray_step: + mov eax,stepX + add rx,eax + mov eax,stepY + add ry,eax + mov eax,rx + sar eax,16 + cmp eax,32 + jae hf_ray_end + mov ecx,ry + sar ecx,16 + cmp ecx,32 + jae hf_ray_end + shl ecx,5 + add eax,ecx + cmp hfMap[eax],0 + jne hf_ray_end + mov esi,OFFSET hfEnemies + mov ebx,HF_ENEMIES +hf_ray_enemy: + cmp [esi].HFEnemy.hp,0 + jle hf_ray_next_enemy + invoke HFDistance,rx,ry,[esi].HFEnemy.x,[esi].HFEnemy.y + cmp eax,FP_1*7/20 + ja hf_ray_next_enemy + invoke RandomRange3D,9 + add eax,8 + invoke HFDamageEnemy,esi,eax + jmp hf_ray_end +hf_ray_next_enemy: + add esi,SIZEOF HFEnemy + dec ebx + jnz hf_ray_enemy + dec steps + jnz hf_ray_step +hf_ray_end: + ; Each pellet owns its own impact; stay just in front of a wall's depth. + mov ecx,pellet + dec ecx + imul ecx,SIZEOF HFImpact + mov eax,rx + sub eax,stepX + sub eax,stepX + mov hfImpacts[ecx].x,eax + mov eax,ry + sub eax,stepY + sub eax,stepY + mov hfImpacts[ecx].y,eax + mov hfImpacts[ecx].life,8 + dec pellet + jnz hf_fire_pellet +hf_fire_done: + ret +HFFire ENDP + +HFSpawnBolt PROC uses esi ebx x:DWORD,y:DWORD,damage:DWORD + local distance:DWORD + mov esi,OFFSET hfBolts + mov ebx,32 +@@: + cmp [esi].HFBolt.life,0 + je hf_bolt_slot + add esi,SIZEOF HFBolt + dec ebx + jnz @B + ret +hf_bolt_slot: + invoke HFDistance,x,y,playerX,playerY + cmp eax,FP_1/2 + jae @F + mov eax,FP_1/2 +@@: + mov distance,eax + mov eax,x + mov [esi].HFBolt.x,eax + mov eax,y + mov [esi].HFBolt.y,eax + mov eax,damage + mov [esi].HFBolt.damage,eax + mov [esi].HFBolt.life,210 + mov eax,playerX + sub eax,x + mov ecx,FP_1/7 + imul ecx + idiv distance + mov [esi].HFBolt.vx,eax + mov eax,playerY + sub eax,y + mov ecx,FP_1/7 + imul ecx + idiv distance + mov [esi].HFBolt.vy,eax + invoke HFPlaySound,1 + ret +HFSpawnBolt ENDP + +; Breadth-first distance field permits monsters to navigate around room pillars. +HFBuildFlow PROC uses esi edi ebx + local head:DWORD,tail:DWORD,current:DWORD,neighbor:DWORD + cld + mov edi,OFFSET hfFlow + mov eax,0FFFFFFFFh + mov ecx,512 + rep stosd + mov eax,playerY + sar eax,16 + shl eax,5 + mov ecx,playerX + sar ecx,16 + add eax,ecx + mov hfFlow[eax*2],0 + mov hfQueue,eax + mov head,0 + mov tail,1 +hf_flow_pop: + mov eax,head + cmp eax,tail + jae hf_flow_done + mov eax,hfQueue[eax*4] + mov current,eax + movzx ebx,hfFlow[eax*2] + inc ebx + xor esi,esi +hf_flow_neighbor: + mov eax,current + cmp esi,0 + jne @F + dec eax +@@: + cmp esi,1 + jne @F + inc eax +@@: + cmp esi,2 + jne @F + sub eax,32 +@@: + cmp esi,3 + jne @F + add eax,32 +@@: + cmp eax,1024 + jae hf_flow_next + cmp hfMap[eax],0 + jne hf_flow_next + cmp hfFlow[eax*2],0FFFFh + jne hf_flow_next + mov hfFlow[eax*2],bx + mov ecx,tail + mov hfQueue[ecx*4],eax + inc tail +hf_flow_next: + inc esi + cmp esi,4 + jb hf_flow_neighbor + inc head + jmp hf_flow_pop +hf_flow_done: + ret +HFBuildFlow ENDP + +HFMoveEnemy PROC uses esi edi ebx enemy:DWORD + local tx:DWORD,ty:DWORD,distance:DWORD,speed:DWORD,best:DWORD,cell:DWORD,nx:DWORD,ny:DWORD + mov esi,enemy + mov eax,playerX + mov tx,eax + mov eax,playerY + mov ty,eax + invoke HFLineClear,[esi].HFEnemy.x,[esi].HFEnemy.y,playerX,playerY + test eax,eax + jnz hf_move_target + mov eax,[esi].HFEnemy.y + sar eax,16 + shl eax,5 + mov ecx,[esi].HFEnemy.x + sar ecx,16 + add eax,ecx + mov cell,eax + movzx eax,hfFlow[eax*2] + mov best,eax + mov edi,-1 + xor ebx,ebx +hf_move_flow: + mov eax,cell + cmp ebx,0 + jne @F + dec eax +@@: + cmp ebx,1 + jne @F + inc eax +@@: + cmp ebx,2 + jne @F + sub eax,32 +@@: + cmp ebx,3 + jne @F + add eax,32 +@@: + cmp eax,1024 + jae hf_move_flow_next + movzx ecx,hfFlow[eax*2] + cmp ecx,best + jae hf_move_flow_next + mov best,ecx + mov edi,eax +hf_move_flow_next: + inc ebx + cmp ebx,4 + jb hf_move_flow + cmp edi,-1 + je hf_move_done + mov eax,edi + and eax,31 + shl eax,16 + add eax,FP_HALF + mov tx,eax + shr edi,5 + shl edi,16 + add edi,FP_HALF + mov ty,edi +hf_move_target: + invoke HFDistance,[esi].HFEnemy.x,[esi].HFEnemy.y,tx,ty + cmp eax,FP_1/8 + jb hf_move_done + mov distance,eax + mov eax,[esi].HFEnemy.kind + mov eax,hfMonsterSpeed[eax*4] + mov speed,eax + mov eax,tx + sub eax,[esi].HFEnemy.x + imul speed + idiv distance + add eax,[esi].HFEnemy.x + mov nx,eax + invoke CanWalk3D,eax,[esi].HFEnemy.y + test eax,eax + jz @F + mov eax,nx + mov [esi].HFEnemy.x,eax +@@: + mov eax,ty + sub eax,[esi].HFEnemy.y + imul speed + idiv distance + add eax,[esi].HFEnemy.y + mov ny,eax + invoke CanWalk3D,[esi].HFEnemy.x,eax + test eax,eax + jz hf_move_done + mov eax,ny + mov [esi].HFEnemy.y,eax +hf_move_done: + ret +HFMoveEnemy ENDP + +HFEnemiesTick PROC uses esi edi ebx + local distance:DWORD,visible:DWORD + mov esi,OFFSET hfEnemies + mov edi,HF_ENEMIES +hf_ai_enemy: + cmp [esi].HFEnemy.hp,0 + jg hf_ai_alive + cmp [esi].HFEnemy.pose,11 + jae hf_ai_next + inc [esi].HFEnemy.timer + cmp [esi].HFEnemy.timer,6 + jb hf_ai_next + mov [esi].HFEnemy.timer,0 + inc [esi].HFEnemy.pose + jmp hf_ai_next +hf_ai_alive: + cmp [esi].HFEnemy.cooldown,0 + je @F + dec [esi].HFEnemy.cooldown +@@: + cmp [esi].HFEnemy.timer,0 + je hf_ai_perceive + dec [esi].HFEnemy.timer + jnz hf_ai_next + cmp [esi].HFEnemy.pose,4 + jne hf_ai_next + invoke HFLineClear,[esi].HFEnemy.x,[esi].HFEnemy.y,playerX,playerY + test eax,eax + jz hf_ai_next + invoke HFDistance,[esi].HFEnemy.x,[esi].HFEnemy.y,playerX,playerY + mov distance,eax + mov ebx,[esi].HFEnemy.kind + cmp eax,FP_1*7/10 + jbe hf_ai_melee + cmp ebx,1 + je hf_ai_next + cmp ebx,0 + jne hf_ai_projectile + invoke RandomRange3D,4 + test eax,eax + jz hf_ai_attack_pose + invoke HFDamagePlayer,6 + jmp hf_ai_attack_pose +hf_ai_perceive: + invoke HFDistance,[esi].HFEnemy.x,[esi].HFEnemy.y,playerX,playerY + mov distance,eax + mov visible,0 + cmp eax,16*FP_1 + ja hf_ai_awake + invoke HFLineClear,[esi].HFEnemy.x,[esi].HFEnemy.y,playerX,playerY + mov visible,eax + test eax,eax + jz hf_ai_awake + mov [esi].HFEnemy.awake,1 +hf_ai_awake: + cmp [esi].HFEnemy.awake,0 + je hf_ai_next + mov eax,hfTicks + shr eax,3 + and eax,3 + mov [esi].HFEnemy.pose,eax + cmp visible,0 + je hf_ai_move + mov ebx,[esi].HFEnemy.kind + cmp [esi].HFEnemy.cooldown,0 + jne hf_ai_spacing + cmp distance,FP_1*7/10 + jbe hf_ai_windup + cmp ebx,1 + je hf_ai_move +hf_ai_windup: + mov [esi].HFEnemy.pose,4 + mov [esi].HFEnemy.timer,12 + jmp hf_ai_next +hf_ai_projectile: + invoke HFSpawnBolt,[esi].HFEnemy.x,[esi].HFEnemy.y,hfMonsterDamage[ebx*4] + jmp hf_ai_attack_pose +hf_ai_melee: + invoke HFDamagePlayer,hfMonsterDamage[ebx*4] +hf_ai_attack_pose: + mov [esi].HFEnemy.pose,5 + mov [esi].HFEnemy.timer,10 + mov [esi].HFEnemy.cooldown,55 + cmp ebx,3 + jne hf_ai_next + mov [esi].HFEnemy.cooldown,18 + jmp hf_ai_next +hf_ai_spacing: + cmp distance,FP_1*7/10 + jb hf_ai_next + cmp ebx,1 + je hf_ai_move + cmp distance,4*FP_1 + jb hf_ai_next +hf_ai_move: + invoke HFMoveEnemy,esi +hf_ai_next: + add esi,SIZEOF HFEnemy + dec edi + jnz hf_ai_enemy + ret +HFEnemiesTick ENDP + +HFBoltsTick PROC uses esi edi + mov esi,OFFSET hfBolts + mov edi,32 +hf_projectile: + cmp [esi].HFBolt.life,0 + je hf_projectile_next + dec [esi].HFBolt.life + mov eax,[esi].HFBolt.vx + add [esi].HFBolt.x,eax + mov eax,[esi].HFBolt.vy + add [esi].HFBolt.y,eax + invoke CanWalk3D,[esi].HFBolt.x,[esi].HFBolt.y + test eax,eax + jz hf_projectile_destroy + invoke HFDistance,[esi].HFBolt.x,[esi].HFBolt.y,playerX,playerY + cmp eax,FP_1*3/10 + ja hf_projectile_next + invoke HFDamagePlayer,[esi].HFBolt.damage +hf_projectile_destroy: + mov [esi].HFBolt.life,0 +hf_projectile_next: + add esi,SIZEOF HFBolt + dec edi + jnz hf_projectile + ret +HFBoltsTick ENDP + +HFPickups PROC uses esi edi ebx + xor ebx,ebx + mov esi,OFFSET hfItemSeeds +hf_pickup: + cmp hfItemTaken[ebx*4],0 + jne hf_pickup_next + invoke HFDistance,playerX,playerY,[esi],[esi+4] + cmp eax,FP_1*3/5 + ja hf_pickup_next + mov edi,[esi+8] + cmp edi,0 + jne hf_pick_armor + cmp hfHealth,100 + jae hf_pickup_next + add hfHealth,25 + cmp hfHealth,100 + jbe hf_pick_commit + mov hfHealth,100 + jmp hf_pick_commit +hf_pick_armor: + cmp edi,1 + jne hf_pick_bullets + cmp hfArmor,100 + jae hf_pickup_next + mov hfArmor,100 + jmp hf_pick_commit +hf_pick_bullets: + cmp edi,2 + jne hf_pick_shells + add hfBullets,40 + jmp hf_pick_commit +hf_pick_shells: + cmp edi,3 + jne hf_pick_shotgun + add hfShells,12 + jmp hf_pick_commit +hf_pick_shotgun: + cmp edi,4 + jne hf_pick_key + mov hfHasShotgun,1 + mov hfWeapon,1 + add hfShells,16 + jmp hf_pick_commit +hf_pick_key: + mov hfKey,1 +hf_pick_commit: + mov hfItemTaken[ebx*4],1 + mov hfMessage,OFFSET hfPickupText + cmp edi,5 + jne @F + mov hfMessage,OFFSET hfKeyFoundText +@@: + mov hfMessageTime,105 + invoke HFPlaySound,3 +hf_pickup_next: + add esi,12 + inc ebx + cmp ebx,HF_ITEMS + jb hf_pickup + ret +HFPickups ENDP + +HFUse PROC uses ebx esi + invoke HFDistance,playerX,playerY,FP_1*11/2,FP_1*25/2 + cmp eax,FP_1*8/5 + ja hf_use_secret_two + test hfDoorMask,1 + jnz hf_use_secret_two + or hfDoorMask,1 + mov hfMap[12*32+5],0 + mov hfMap[12*32+6],0 + jmp hf_use_secret +hf_use_secret_two: + invoke HFDistance,playerX,playerY,FP_1*45/2,27*FP_1 + cmp eax,FP_1*8/5 + ja hf_use_gate + test hfDoorMask,2 + jnz hf_use_gate + or hfDoorMask,2 + mov hfMap[26*32+22],0 + mov hfMap[27*32+22],0 +hf_use_secret: + inc hfSecrets + mov hfMessage,OFFSET hfSecretText + mov hfMessageTime,105 + invoke HFPlaySound,5 + ret +hf_use_gate: + invoke HFDistance,playerX,playerY,FP_1*45/2,7*FP_1 + cmp eax,2*FP_1 + ja hf_use_exit + cmp hfKey,0 + jne hf_use_unlock + mov hfMessage,OFFSET hfNeedKeyText + mov hfMessageTime,105 + ret +hf_use_unlock: + mov hfMap[6*32+22],0 + mov hfMap[7*32+22],0 + or hfDoorMask,4 + mov hfMessage,OFFSET hfDoorText + mov hfMessageTime,105 + invoke HFPlaySound,5 + ret +hf_use_exit: + invoke HFDistance,playerX,playerY,28*FP_1,29*FP_1 + cmp eax,2*FP_1 + ja hf_use_done + mov esi,OFFSET hfEnemies + mov ebx,HF_ENEMIES +@@: + cmp [esi].HFEnemy.kind,3 + jne hf_exit_next + cmp [esi].HFEnemy.hp,0 + jg hf_exit_locked +hf_exit_next: + add esi,SIZEOF HFEnemy + dec ebx + jnz @B + mov hfState,2 + mov hfFireHeld,0 + mov hfMessageTime,0 + cmp hfCompleted,0 + jne hf_use_done + mov hfCompleted,1 + mov hfAwarded,1 + invoke ArtifactCredit3D,6660 + call SaveProgress3D + ret +hf_exit_locked: + mov hfMessage,OFFSET hfExitText + mov hfMessageTime,105 +hf_use_done: + ret +HFUse ENDP + +HFTick PROC uses esi ecx + mov esi,OFFSET hfImpacts + mov ecx,HF_SHOT_PELLETS +hf_impact_tick: + cmp [esi].HFImpact.life,0 + je @F + dec [esi].HFImpact.life +@@: + add esi,SIZEOF HFImpact + loop hf_impact_tick + inc hfTicks + cmp hfCooldown,0 + je @F + dec hfCooldown +@@: + cmp hfFlash,0 + je @F + dec hfFlash +@@: + cmp hfHurt,0 + je @F + dec hfHurt +@@: + cmp hfMessageTime,0 + je @F + dec hfMessageTime +@@: + mov eax,hfFireHeld + or eax,hfFirePending + or al,keys3D[20h] + jz @F + call HFFire +@@: + mov hfFirePending,0 + cmp hfUsePending,0 + je @F + mov hfUsePending,0 + call HFUse +@@: + cmp hfState,0 + jne hf_tick_done + mov eax,hfTicks + and eax,7 + jnz @F + call HFBuildFlow +@@: + call HFEnemiesTick + call HFBoltsTick + cmp hfState,0 + jne hf_tick_done + call HFPickups + ; Central lava channel: visible floor patch exactly matches damage bounds. + mov eax,hfTicks + xor edx,edx + mov ecx,18 + div ecx + test edx,edx + jnz hf_tick_done + cmp playerX,16*FP_1 + jb hf_tick_done + cmp playerX,18*FP_1 + jae hf_tick_done + cmp playerY,17*FP_1 + jb hf_tick_done + cmp playerY,20*FP_1 + jae hf_tick_done + invoke HFDamagePlayer,8 +hf_tick_done: + ret +HFTick ENDP diff --git a/src_graphics/hell_fps_data.inc b/src_graphics/hell_fps_data.inc new file mode 100644 index 0000000..2c6b29d --- /dev/null +++ b/src_graphics/hell_fps_data.inc @@ -0,0 +1,203 @@ +; Native MASM32 hell expedition. Assets: Freedoom 0.13.0 (assets/hell/COPYING.txt). +MODE_HELL_FPS equ 29 +INTERACT_HELL_FPS equ 55 +HF_WALL equ 185 +HF_FLOOR equ 191 +HF_CEILING equ 192 +HF_LAVA equ 193 +; South wall faces the customer side of the bar at x=3..9, y=23..24. +HF_PORTAL_X equ 6*FP_1 +HF_PORTAL_Y equ FP_1*309/10 +HF_PAINTING_MATERIAL equ 500 +HF_PACK_COUNT equ 15 +HF_SHOT_PELLETS equ 7 +HF_MUSIC_SCALE equ 35 ; settings 0..10 -> MCI 0..350 (35% of previous level) +BeginHellFPS3D PROTO +LeaveHellFPS3D PROTO +HandleHellFPS3D PROTO +RenderHellFPS3D PROTO +RenderHellPortal3D PROTO +FreeHellFPS3D PROTO +HFReset PROTO +HFBuildFlow PROTO +HFTick PROTO +HFFire PROTO +HFUse PROTO +HFLineClear PROTO :DWORD,:DWORD,:DWORD,:DWORD +HFDamagePlayer PROTO :DWORD +HFDamageEnemy PROTO :DWORD,:DWORD +HFSpawnBolt PROTO :DWORD,:DWORD,:DWORD +HFDistance PROTO :DWORD,:DWORD,:DWORD,:DWORD +HFPlayerCanWalk PROTO :DWORD,:DWORD +HFPlaySound PROTO :DWORD +HFUpdateMusicVolume PROTO +HFEnemy STRUCT + x dd ? + y dd ? + kind dd ? + hp dd ? + cooldown dd ? + pose dd ? + timer dd ? + awake dd ? +HFEnemy ENDS +HFBolt STRUCT + x dd ? + y dd ? + vx dd ? + vy dd ? + life dd ? + damage dd ? +HFBolt ENDS +HFImpact STRUCT + x dd ? + y dd ? + life dd ? +HFImpact ENDS +include src_graphics\hell_fps_map.inc +.data +hfCompleted dd 0 +hfState dd 0 ; 0 playing, 1 dead, 2 victory +hfHealth dd 100 +hfArmor dd 0 +hfBullets dd 80 +hfShells dd 0 +hfWeapon dd 0 +hfHasShotgun dd 0 +hfKey dd 0 +hfSecrets dd 0 +hfKills dd 0 +hfTicks dd 0 +hfAccumulator dd 0 +hfCooldown dd 0 +hfFlash dd 0 +hfHurt dd 0 +hfFireHeld dd 0 +hfFirePending dd 0 +hfUsePending dd 0 +hfOldX dd 0 +hfOldY dd 0 +hfOldAngle dd 0 +hfOldMap dd 0 +hfOldFloor dd 0 +hfOldLevel dd 0 +hfOldEye dd 0 +hfOldCeiling dd 0 +hfDoorMask dd 0 +hfMessage dd 0 +hfMessageTime dd 0 +hfLoaded dd 0 +hfSoundReady dd 0 +hfMusicError dd 0 +hfSoundFailures dd 0 +hfVolumeError dd 0 +hfNumber db 32 dup(0) +hfTitle db 'HELL: THE HOUSE ALWAYS BLEEDS',0 +hfHelp db 'WASD MOVE MOUSE AIM LMB FIRE 1/2 WEAPON E USE ESC RETURN',0 +hfHealthText db 'HEALTH',0 +hfArmorText db 'ARMOR',0 +hfAmmoText db 'AMMO',0 +hfKillsText db 'KILLS',0 +hfKeyText db 'BLUE KEY',0 +hfPistolText db 'PISTOL',0 +hfShotgunText db 'SHOTGUN',0 +hfDeadText db 'YOU DIED - ENTER: RETRY / ESC: CASINO',0 +hfWinText db 'HELL CLEARED - ENTER / ESC: CASINO',0 +hfRewardText db 'FIRST CLEAR: +$6,660',0 +hfRepeatText db 'EXPEDITION COMPLETE',0 +hfAwarded dd 0 +hfStatsText db 'TIME (SEC) KILLS / 28 SECRETS / 2',0 +hfPortalPrompt db '[E] HELL EXPEDITION',0 +hfMissingText db 'HELL ASSETS MISSING - RESTORE assets/hell',0 +hfNeedKeyText db 'THE SANCTUM NEEDS THE BLUE KEY',0 +hfDoorText db 'THE GATE OPENS',0 +hfSecretText db 'SECRET FOUND',0 +hfPickupText db 'SUPPLIES COLLECTED',0 +hfKeyFoundText db 'BLUE KEY ACQUIRED - FIND THE EASTERN GATE',0 +hfExitText db 'DEFEAT THE WARDEN, THEN USE THE EXIT PORTAL',0 +hfWelcomeText db 'FIND THE SHOTGUN. RECOVER THE BLUE KEY. KILL THE WARDEN.',0 +hfMaterials AnimPack3D <> +hfSoldier AnimPack3D <> +hfDemon AnimPack3D <> +hfImp AnimPack3D <> +hfBoss AnimPack3D <> +hfPistol AnimPack3D <> +hfShotgun AnimPack3D <> +hfItemsArt AnimPack3D <> +hfFireArt AnimPack3D <> +hfPortalArt AnimPack3D <> +hfHUDArt AnimPack3D <> +hfDigitsArt AnimPack3D <> +hfFacesArt AnimPack3D <> +hfPaintingArt AnimPack3D <> +hfImpactsArt AnimPack3D <> +hfPacks dd OFFSET hfMaterials,OFFSET hfSoldier,OFFSET hfDemon,OFFSET hfImp,OFFSET hfBoss,OFFSET hfPistol,OFFSET hfShotgun,OFFSET hfItemsArt,OFFSET hfFireArt,OFFSET hfPortalArt,OFFSET hfHUDArt,OFFSET hfDigitsArt,OFFSET hfFacesArt,OFFSET hfPaintingArt,OFFSET hfImpactsArt +hfMatPath db 'assets\hell\materials.bin',0 +hfSoldierPath db 'assets\hell\soldier.bin',0 +hfDemonPath db 'assets\hell\demon.bin',0 +hfImpPath db 'assets\hell\imp.bin',0 +hfBossPath db 'assets\hell\boss.bin',0 +hfPistolPath db 'assets\hell\pistol.bin',0 +hfShotgunPath db 'assets\hell\shotgun.bin',0 +hfItemsPath db 'assets\hell\items.bin',0 +hfFirePath db 'assets\hell\fire.bin',0 +hfPortalPath db 'assets\hell\portal.bin',0 +hfHUDPath db 'assets\hell\hud.bin',0 +hfDigitsPath db 'assets\hell\digits.bin',0 +hfFacesPath db 'assets\hell\faces.bin',0 +hfPaintingPath db 'assets\hell\painting.bin',0 +hfImpactsPath db 'assets\hell\impacts.bin',0 +hfPaths dd OFFSET hfMatPath,OFFSET hfSoldierPath,OFFSET hfDemonPath,OFFSET hfImpPath,OFFSET hfBossPath,OFFSET hfPistolPath,OFFSET hfShotgunPath,OFFSET hfItemsPath,OFFSET hfFirePath,OFFSET hfPortalPath,OFFSET hfHUDPath,OFFSET hfDigitsPath,OFFSET hfFacesPath,OFFSET hfPaintingPath,OFFSET hfImpactsPath +hfMonsterPacks dd OFFSET hfSoldier,OFFSET hfDemon,OFFSET hfImp,OFFSET hfBoss +hfMonsterHP dd 55,105,85,1100 +hfMonsterSpeed dd 1900,3300,1800,1350 +hfMonsterDamage dd 6,15,10,18 +hfObject CityProp3D <0,0,FP_1,FP_1*3/2,0,OFFSET hfSoldier,0> +hfPelletSpread dd -9,-6,-3,0,3,6,9 ; independent rays, ~12.7-degree full cone +hfPercentText db '%',0 +hfArmsText db 'ARMS',0 +hfSlot1Text db '1',0 +hfSlot2Text db '2',0 +hfAmmoTypesText db 'BULL',0 +hfShellTypeText db 'SHEL',0 +hfEmptyAmmoText db 'RCKT 0 50',0 +hfEmptyCellText db 'CELL 0 300',0 +hfMaxBulletsText db '/ 280',0 +hfMaxShellsText db '/ 160',0 +hfMusicOpen db 'open "assets\music\tower\Mick_Gordon_-_The_Only_Thing_They_Fear_Is_You_OST_DOOM_Eternal_72020713.mp3" type mpegvideo alias hellmusic',0 +hfMusicPlay db 'play hellmusic repeat',0 +hfMusicClose db 'close hellmusic',0 +hfVolumePrefix db 'setaudio hellmusic volume to ',0 +hfVolumeCommand db 80 dup(0) +hfLastVolume dd -1 +; Six independent waveaudio channels let combat, pain and pickups overlap. +hfSndOpen0 db 'open "assets\hell\dsshotgn.wav" type waveaudio alias hfshot',0 +hfSndOpen1 db 'open "assets\hell\dsfirsht.wav" type waveaudio alias hfmonster',0 +hfSndOpen2 db 'open "assets\hell\dsplpain.wav" type waveaudio alias hfhurt',0 +hfSndOpen3 db 'open "assets\hell\dsitemup.wav" type waveaudio alias hfpick',0 +hfSndOpen4 db 'open "assets\hell\dspistol.wav" type waveaudio alias hfpistol',0 +hfSndOpen5 db 'open "assets\hell\dsdoropn.wav" type waveaudio alias hfdoor',0 +hfSndPlay0 db 'play hfshot from 0',0 +hfSndPlay1 db 'play hfmonster from 0',0 +hfSndPlay2 db 'play hfhurt from 0',0 +hfSndPlay3 db 'play hfpick from 0',0 +hfSndPlay4 db 'play hfpistol from 0',0 +hfSndPlay5 db 'play hfdoor from 0',0 +hfSndClose0 db 'close hfshot',0 +hfSndClose1 db 'close hfmonster',0 +hfSndClose2 db 'close hfhurt',0 +hfSndClose3 db 'close hfpick',0 +hfSndClose4 db 'close hfpistol',0 +hfSndClose5 db 'close hfdoor',0 +hfSndOpens dd OFFSET hfSndOpen0,OFFSET hfSndOpen1,OFFSET hfSndOpen2,OFFSET hfSndOpen3,OFFSET hfSndOpen4,OFFSET hfSndOpen5 +hfSndPlays dd OFFSET hfSndPlay0,OFFSET hfSndPlay1,OFFSET hfSndPlay2,OFFSET hfSndPlay3,OFFSET hfSndPlay4,OFFSET hfSndPlay5 +hfSndCloses dd OFFSET hfSndClose0,OFFSET hfSndClose1,OFFSET hfSndClose2,OFFSET hfSndClose3,OFFSET hfSndClose4,OFFSET hfSndClose5 +.data? +hfMap db 1024 dup(?) +hfEnemies HFEnemy HF_ENEMIES dup(<>) +hfItemTaken dd HF_ITEMS dup(?) +hfBolts HFBolt 32 dup(<>) +hfFlow dw 1024 dup(?) +hfQueue dd 1024 dup(?) +hfImpacts HFImpact HF_SHOT_PELLETS dup(<>) +.code diff --git a/src_graphics/hell_fps_map.inc b/src_graphics/hell_fps_map.inc new file mode 100644 index 0000000..5d7e226 --- /dev/null +++ b/src_graphics/hell_fps_map.inc @@ -0,0 +1,92 @@ +; Generated by tools/generators/make_hell_level.py. +.data +hfMapTemplate label byte + db 185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185 + db 185,185,185,185,185,185,185,185,185,185,185,185,186,186,186,186,186,186,186,186,186,186,186,187,187,187,187,187,187,187,187,185 + db 185,185,185,185,185,185,185,185,185,185,185,185,186,186,186,186,186,186,186,186,186,186,186,187,187,187,187,187,187,187,187,185 + db 185,185,185,185,185,185,185,185,185,185,185,185,186,0,0,0,0,0,0,0,0,0,186,187,0,0,0,0,0,0,187,185 + db 185,185,185,185,185,185,185,185,185,185,185,185,186,0,0,0,0,0,0,0,0,0,186,187,0,0,0,0,0,0,187,185 + db 185,185,185,185,185,185,185,185,185,185,185,185,186,0,0,0,0,0,0,0,0,0,186,187,0,0,0,0,0,0,187,185 + db 185,185,185,185,185,185,185,185,185,185,185,185,186,0,0,0,186,0,0,0,0,0,188,0,0,0,0,0,0,0,187,185 + db 185,185,0,0,0,0,0,0,0,185,185,185,186,0,0,0,0,0,0,186,0,0,188,0,0,0,0,0,0,0,187,185 + db 185,185,0,0,0,0,0,0,0,185,185,185,186,0,0,0,0,0,0,0,0,0,186,187,0,0,0,0,0,0,187,185 + db 185,185,0,0,0,0,0,0,0,185,185,185,186,0,0,0,0,0,0,0,0,0,186,187,0,0,0,0,0,0,187,185 + db 185,185,0,0,0,0,0,0,0,185,185,185,186,0,0,0,0,0,0,0,0,0,186,187,0,0,186,0,0,0,187,185 + db 185,185,0,0,0,0,0,0,0,185,185,185,186,186,186,186,186,0,0,186,186,186,186,187,0,0,0,0,0,0,187,185 + db 185,185,185,185,185,188,188,185,185,185,185,185,185,185,185,185,185,0,0,185,185,185,185,187,0,0,0,0,0,0,187,185 + db 185,185,185,185,185,0,0,185,185,185,185,185,0,0,0,0,0,0,0,0,0,0,185,187,0,0,0,0,0,0,187,185 + db 185,185,185,0,0,0,0,0,0,0,0,185,0,0,0,0,0,0,0,0,0,0,185,187,0,0,0,0,0,0,187,185 + db 185,185,185,0,0,0,0,0,0,0,0,185,0,0,0,0,0,0,0,0,0,0,185,187,0,0,0,0,0,0,187,185 + db 185,185,185,0,0,0,0,0,0,0,0,0,0,0,0,186,0,0,0,0,0,0,185,187,0,0,0,186,0,0,187,185 + db 185,185,185,0,0,186,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,185,187,0,0,0,0,0,0,187,185 + db 185,185,185,0,0,0,0,0,186,0,0,185,0,0,0,0,0,0,0,0,0,0,185,187,0,0,0,0,0,0,187,185 + db 185,185,185,0,0,0,0,0,0,0,0,185,0,0,0,0,0,0,0,186,0,0,185,187,0,0,0,0,0,0,187,185 + db 185,185,185,0,0,0,0,0,0,0,0,185,0,0,0,0,0,0,0,0,0,0,185,187,0,0,0,0,0,0,187,185 + db 185,185,185,0,0,0,0,0,0,0,0,185,0,0,0,0,0,0,0,0,0,0,185,187,187,187,0,0,187,187,187,185 + db 185,185,185,185,185,0,0,185,185,185,185,185,0,0,0,0,0,0,0,0,0,0,185,190,190,190,0,0,190,190,190,185 + db 185,185,0,0,0,0,0,0,0,0,0,185,185,185,185,185,185,185,185,185,185,185,185,0,0,0,0,0,0,0,190,185 + db 185,185,0,0,0,0,0,0,0,0,0,185,185,185,185,185,185,185,185,185,185,185,185,0,0,0,0,0,0,0,190,185 + db 185,185,0,0,0,0,0,0,0,0,0,185,185,185,185,185,185,185,0,0,0,0,185,0,0,0,0,0,0,0,190,185 + db 185,185,0,0,0,0,0,0,0,0,0,185,185,185,185,185,185,185,0,0,0,0,188,0,0,186,0,0,0,0,190,185 + db 185,185,0,0,0,0,0,0,0,0,0,185,185,185,185,185,185,185,0,0,0,0,188,0,0,0,0,0,0,0,190,185 + db 185,185,0,0,0,0,0,0,0,0,0,185,185,185,185,185,185,185,0,0,0,0,185,0,0,0,0,0,0,0,190,185 + db 185,185,0,0,0,0,0,0,0,0,0,185,185,185,185,185,185,185,185,185,185,185,185,0,0,0,0,0,0,0,190,185 + db 185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,190,190,190,190,190,190,190,190,185 + db 185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185 +hfEnemySeeds label dword + dd 7*FP_1+FP_HALF,25*FP_1+FP_HALF,0 + dd 8*FP_1+FP_HALF,28*FP_1+FP_HALF,0 + dd 4*FP_1+FP_HALF,20*FP_1+FP_HALF,0 + dd 8*FP_1+FP_HALF,16*FP_1+FP_HALF,1 + dd 9*FP_1+FP_HALF,20*FP_1+FP_HALF,2 + dd 4*FP_1+FP_HALF,15*FP_1+FP_HALF,0 + dd 14*FP_1+FP_HALF,20*FP_1+FP_HALF,2 + dd 20*FP_1+FP_HALF,21*FP_1+FP_HALF,0 + dd 20*FP_1+FP_HALF,14*FP_1+FP_HALF,2 + dd 13*FP_1+FP_HALF,14*FP_1+FP_HALF,1 + dd 18*FP_1+FP_HALF,17*FP_1+FP_HALF,1 + dd 15*FP_1+FP_HALF,18*FP_1+FP_HALF,0 + dd 14*FP_1+FP_HALF,8*FP_1+FP_HALF,2 + dd 20*FP_1+FP_HALF,4*FP_1+FP_HALF,2 + dd 15*FP_1+FP_HALF,4*FP_1+FP_HALF,0 + dd 20*FP_1+FP_HALF,9*FP_1+FP_HALF,1 + dd 18*FP_1+FP_HALF,5*FP_1+FP_HALF,0 + dd 25*FP_1+FP_HALF,5*FP_1+FP_HALF,0 + dd 28*FP_1+FP_HALF,8*FP_1+FP_HALF,2 + dd 25*FP_1+FP_HALF,12*FP_1+FP_HALF,1 + dd 28*FP_1+FP_HALF,15*FP_1+FP_HALF,0 + dd 25*FP_1+FP_HALF,19*FP_1+FP_HALF,2 + dd 28*FP_1+FP_HALF,19*FP_1+FP_HALF,1 + dd 24*FP_1+FP_HALF,25*FP_1+FP_HALF,2 + dd 28*FP_1+FP_HALF,25*FP_1+FP_HALF,2 + dd 27*FP_1+FP_HALF,28*FP_1+FP_HALF,3 + dd 4*FP_1+FP_HALF,9*FP_1+FP_HALF,1 + dd 7*FP_1+FP_HALF,8*FP_1+FP_HALF,0 +HF_ENEMIES equ 28 +hfItemSeeds label dword + dd 3*FP_1+FP_HALF,27*FP_1+FP_HALF,4 + dd 4*FP_1+FP_HALF,28*FP_1+FP_HALF,3 + dd 9*FP_1+FP_HALF,24*FP_1+FP_HALF,2 + dd 3*FP_1+FP_HALF,19*FP_1+FP_HALF,0 + dd 8*FP_1+FP_HALF,14*FP_1+FP_HALF,3 + dd 9*FP_1+FP_HALF,15*FP_1+FP_HALF,2 + dd 13*FP_1+FP_HALF,21*FP_1+FP_HALF,1 + dd 14*FP_1+FP_HALF,14*FP_1+FP_HALF,3 + dd 20*FP_1+FP_HALF,20*FP_1+FP_HALF,0 + dd 19*FP_1+FP_HALF,14*FP_1+FP_HALF,2 + dd 14*FP_1+FP_HALF,4*FP_1+FP_HALF,0 + dd 20*FP_1+FP_HALF,3*FP_1+FP_HALF,5 + dd 17*FP_1+FP_HALF,9*FP_1+FP_HALF,3 + dd 25*FP_1+FP_HALF,4*FP_1+FP_HALF,2 + dd 28*FP_1+FP_HALF,11*FP_1+FP_HALF,0 + dd 25*FP_1+FP_HALF,17*FP_1+FP_HALF,3 + dd 28*FP_1+FP_HALF,20*FP_1+FP_HALF,3 + dd 24*FP_1+FP_HALF,24*FP_1+FP_HALF,0 + dd 29*FP_1+FP_HALF,28*FP_1+FP_HALF,3 + dd 24*FP_1+FP_HALF,28*FP_1+FP_HALF,2 + dd 3*FP_1+FP_HALF,8*FP_1+FP_HALF,1 + dd 7*FP_1+FP_HALF,10*FP_1+FP_HALF,3 + dd 19*FP_1+FP_HALF,26*FP_1+FP_HALF,0 + dd 20*FP_1+FP_HALF,27*FP_1+FP_HALF,3 +HF_ITEMS equ 24 +.code diff --git a/src_graphics/hell_fps_render.inc b/src_graphics/hell_fps_render.inc new file mode 100644 index 0000000..be0cf64 --- /dev/null +++ b/src_graphics/hell_fps_render.inc @@ -0,0 +1,348 @@ +; World sprites share casino depth testing. HUD uses native 640x480 pixels. +HFText PROC uses esi edi ebx x:DWORD,y:DWORD,text:DWORD,color:DWORD + local row:DWORD,column:DWORD,cursor:DWORD,bits:DWORD + mov esi,text + mov eax,x + mov cursor,eax +hf_text_char: + movzx eax,BYTE PTR [esi] + test eax,eax + jz hf_text_done + lea edi,font8x8[eax*8] + mov row,0 +hf_text_row: + mov eax,row + movzx eax,BYTE PTR [edi+eax] + mov bits,eax + mov column,0 +hf_text_pixel: + test bits,80h + jz hf_text_next_pixel + mov eax,y + add eax,row + cmp eax,480 + jae hf_text_next_pixel + mov ebx,cursor + add ebx,column + cmp ebx,640 + jae hf_text_next_pixel + imul eax,640 + add eax,ebx + mov ebx,color + mov screenBuffer[eax*4],ebx +hf_text_next_pixel: + shl bits,1 + inc column + cmp column,8 + jb hf_text_pixel + inc row + cmp row,8 + jb hf_text_row + add cursor,8 + inc esi + jmp hf_text_char +hf_text_done: + ret +HFText ENDP + +HFNumber PROC x:DWORD,y:DWORD,value:DWORD + invoke FormatNumber,value,OFFSET hfNumber + invoke HFText,x,y,OFFSET hfNumber,00FFE0A0h + ret +HFNumber ENDP + +HFLargeNumber PROC uses ebx edi rightEdge:DWORD,y:DWORD,value:DWORD,percent:DWORD + mov ebx,value + mov edi,rightEdge + cmp percent,0 + je hf_large_digit + sub edi,28 + invoke DrawAnimSprite3D,OFFSET hfDigitsArt,10,edi,y,32,32 +hf_large_digit: + mov eax,ebx + xor edx,edx + mov ecx,10 + div ecx + mov ebx,eax + sub edi,28 + invoke DrawAnimSprite3D,OFFSET hfDigitsArt,edx,edi,y,32,32 + test ebx,ebx + jnz hf_large_digit + ret +HFLargeNumber ENDP + +HFRenderStatus PROC uses ebx + invoke DrawAnimSprite3D,OFFSET hfHUDArt,0,0,416,640,64 + mov eax,hfBullets + cmp hfWeapon,0 + je @F + mov eax,hfShells +@@: + invoke HFLargeNumber,86,422,eax,0 + invoke HFLargeNumber,208,422,hfHealth,1 + invoke HFLargeNumber,466,422,hfArmor,1 + ; Replace multiplayer FRAGS field with the owned weapon slots. + invoke DrawBox,108,209,35,31,003C3B36h + invoke HFText,228,460,OFFSET hfArmsText,00B0ACA0h + mov eax,00777770h + cmp hfWeapon,0 + jne @F + mov eax,00FFE070h +@@: + invoke HFText,230,434,OFFSET hfSlot1Text,eax + cmp hfHasShotgun,0 + je hf_status_face + mov eax,00777770h + cmp hfWeapon,1 + jne @F + mov eax,00FFE070h +@@: + invoke HFText,258,434,OFFSET hfSlot2Text,eax +hf_status_face: + mov eax,100 + sub eax,hfHealth + xor edx,edx + mov ecx,20 + div ecx + cmp eax,4 + jbe @F + mov eax,4 +@@: + shl eax,2 + mov ebx,eax + cmp hfHurt,0 + je hf_face_idle + add ebx,3 + jmp hf_face_draw +hf_face_idle: + mov eax,hfTicks + shr eax,5 + xor edx,edx + mov ecx,3 + div ecx + add ebx,edx +hf_face_draw: + cmp hfState,1 + jne @F + mov ebx,20 +@@: + invoke DrawAnimSprite3D,OFFSET hfFacesArt,ebx,296,416,48,64 + cmp hfKey,0 + je @F + invoke DrawAnimSprite3D,OFFSET hfItemsArt,5,476,420,28,28 +@@: + ; Clear original ammo glyphs, then draw live reserve counts. + invoke DrawBox,257,209,62,30,002E302Ch + invoke HFText,518,420,OFFSET hfAmmoTypesText,00B0ACA0h + invoke HFNumber,558,420,hfBullets + invoke HFText,590,420,OFFSET hfMaxBulletsText,008E8C80h + invoke HFText,518,434,OFFSET hfShellTypeText,00B0ACA0h + invoke HFNumber,558,434,hfShells + invoke HFText,590,434,OFFSET hfMaxShellsText,008E8C80h + invoke HFText,518,448,OFFSET hfEmptyAmmoText,008E8C80h + invoke HFText,518,462,OFFSET hfEmptyCellText,008E8C80h + ret +HFRenderStatus ENDP + +RenderHellPortal3D PROC + cmp hallLevel3D,0 + jne hf_portal_done + invoke LoadAnimPack3D,OFFSET hfPaintingArt,OFFSET hfPaintingPath + cmp hfPaintingArt.pixels,0 + je hf_portal_done + ; Gilt landscape hung flat on the south wall opposite the bar. + invoke DrawCityVertical3D,1,HF_PORTAL_Y,3*FP_1,9*FP_1,FP_1*35/100,FP_1*235/100,HF_PAINTING_MATERIAL +hf_portal_done: + ret +RenderHellPortal3D ENDP + +RenderHellFPS3D PROC uses esi edi ebx + call UpdateCamera3D + call RenderCeilingAndFloor + call RenderWalls + call PrepareCityDepth3D + ; Animated lava patch, same bounds as HFTick damage. + mov eax,hfTicks + shr eax,3 + and eax,3 + add eax,HF_LAVA + invoke DrawCityHorizontal3D,16*FP_1,18*FP_1,17*FP_1,20*FP_1,128,eax + mov esi,OFFSET hfEnemies + mov edi,HF_ENEMIES +hf_render_enemy: + mov eax,[esi].HFEnemy.x + mov hfObject.posX,eax + mov eax,[esi].HFEnemy.y + mov hfObject.posY,eax + mov eax,[esi].HFEnemy.kind + mov eax,hfMonsterPacks[eax*4] + mov hfObject.asset,eax + mov eax,[esi].HFEnemy.pose + mov hfObject.frame,eax + mov hfObject.worldW,FP_1*6/5 + mov hfObject.worldH,FP_1*3/2 + mov hfObject.groundZ,0 + cmp [esi].HFEnemy.kind,3 + jne @F + mov hfObject.worldW,FP_1*9/5 + mov hfObject.worldH,FP_1*22/10 +@@: + invoke DrawCityObject3D,OFFSET hfObject,0 + add esi,SIZEOF HFEnemy + dec edi + jnz hf_render_enemy + mov hfObject.asset,OFFSET hfItemsArt + mov hfObject.worldW,FP_1/2 + mov hfObject.worldH,FP_1/2 + mov hfObject.groundZ,0 + xor edi,edi + mov esi,OFFSET hfItemSeeds +hf_render_item: + cmp hfItemTaken[edi*4],0 + jne hf_render_item_next + mov eax,[esi] + mov hfObject.posX,eax + mov eax,[esi+4] + mov hfObject.posY,eax + mov eax,[esi+8] + mov hfObject.frame,eax + invoke DrawCityObject3D,OFFSET hfObject,0 +hf_render_item_next: + add esi,12 + inc edi + cmp edi,HF_ITEMS + jb hf_render_item + mov hfObject.asset,OFFSET hfFireArt + mov hfObject.worldW,FP_1/3 + mov hfObject.worldH,FP_1/3 + mov hfObject.groundZ,FP_1*3/4 + mov eax,hfTicks + shr eax,2 + and eax,1 + mov hfObject.frame,eax + mov esi,OFFSET hfBolts + mov edi,32 +hf_render_bolt: + cmp [esi].HFBolt.life,0 + je hf_render_bolt_next + mov eax,[esi].HFBolt.x + mov hfObject.posX,eax + mov eax,[esi].HFBolt.y + mov hfObject.posY,eax + invoke DrawCityObject3D,OFFSET hfObject,0 +hf_render_bolt_next: + add esi,SIZEOF HFBolt + dec edi + jnz hf_render_bolt + mov hfObject.asset,OFFSET hfImpactsArt + mov hfObject.worldW,FP_1/3 + mov hfObject.worldH,FP_1/3 + mov hfObject.groundZ,FP_1*5/6 + mov esi,OFFSET hfImpacts + mov edi,HF_SHOT_PELLETS +hf_render_impact: + cmp [esi].HFImpact.life,0 + je hf_render_impact_next + mov eax,[esi].HFImpact.x + mov hfObject.posX,eax + mov eax,[esi].HFImpact.y + mov hfObject.posY,eax + mov eax,8 + sub eax,[esi].HFImpact.life + shr eax,1 + mov hfObject.frame,eax + invoke DrawCityObject3D,OFFSET hfObject,0 +hf_render_impact_next: + add esi,SIZEOF HFImpact + dec edi + jnz hf_render_impact + mov hfObject.asset,OFFSET hfPortalArt + mov hfObject.posX,28*FP_1 + mov hfObject.posY,29*FP_1 + mov hfObject.worldW,FP_1*3/2 + mov hfObject.worldH,2*FP_1 + mov hfObject.groundZ,0 + mov eax,hfTicks + shr eax,2 + and eax,7 + mov hfObject.frame,eax + invoke DrawCityObject3D,OFFSET hfObject,0 + cmp hfState,0 + jne hf_render_hud + mov esi,OFFSET hfPistol + mov edi,0 + cmp hfWeapon,1 + jne hf_weapon_pose + mov esi,OFFSET hfShotgun +hf_weapon_pose: + cmp hfCooldown,0 + je hf_weapon_draw + cmp hfWeapon,0 + jne hf_shotgun_pose + ; Four recovery poses over the nine-tick pistol cycle. + mov eax,9 + sub eax,hfCooldown + shr eax,1 + inc eax + cmp eax,4 + jbe @F + mov eax,4 +@@: + mov edi,eax + jmp hf_weapon_draw +hf_shotgun_pose: + mov edi,3 + cmp hfCooldown,10 + jbe hf_weapon_draw + mov edi,2 + cmp hfCooldown,18 + jbe hf_weapon_draw + mov edi,1 +hf_weapon_draw: + invoke DrawAnimSprite3D,esi,edi,176,176,288,240 + cmp hfFlash,0 + je @F + invoke DrawAnimSprite3D,OFFSET hfFireArt,0,296,202,48,48 +@@: + invoke DrawBox,159,117,2,7,00D0C0A0h + invoke DrawBox,156,120,8,1,00D0C0A0h +hf_render_hud: + cmp hfHurt,0 + je @F + invoke DrawBox,0,0,320,3,00902010h + invoke DrawBox,0,0,3,210,00902010h + invoke DrawBox,317,0,3,210,00902010h +@@: + call HFRenderStatus + cmp hfMessageTime,0 + je @F + invoke HFText,12,34,hfMessage,00FFD090h +@@: + cmp hfState,0 + je hf_render_done + invoke DrawBox,24,80,272,65,00181010h + mov eax,OFFSET hfDeadText + cmp hfState,1 + je @F + mov eax,OFFSET hfWinText +@@: + invoke HFText,64,176,eax,00FFB070h + invoke HFText,64,208,OFFSET hfStatsText,00C0A080h + mov eax,hfTicks + xor edx,edx + mov ecx,35 + div ecx + invoke HFNumber,80,226,eax + invoke HFNumber,232,226,hfKills + invoke HFNumber,392,226,hfSecrets + cmp hfState,2 + jne hf_render_done + mov eax,OFFSET hfRepeatText + cmp hfAwarded,0 + je @F + mov eax,OFFSET hfRewardText +@@: + invoke HFText,144,258,eax,00FFE0A0h +hf_render_done: + ret +RenderHellFPS3D ENDP diff --git a/src_graphics/platform_code.inc b/src_graphics/platform_code.inc index 8b5c8e3..93e325e 100644 --- a/src_graphics/platform_code.inc +++ b/src_graphics/platform_code.inc @@ -14,6 +14,19 @@ WindowEvents3D PROC uses ebx esi edi hwnd:DWORD, uMsg:DWORD, wp:DWORD, lp:DWORD xor eax, eax ret @@: + cmp engineMode,MODE_HELL_FPS + jne hf_window_not_mouse + cmp uMsg,201h + jne @F + mov hfFireHeld,1 + mov hfFirePending,1 + jmp event_done +@@: + cmp uMsg,202h + jne hf_window_not_mouse + mov hfFireHeld,0 + jmp event_done +hf_window_not_mouse: cmp uMsg,202h jne @F cmp engineMode,MODE_DARTS @@ -150,6 +163,8 @@ event_fullscreen: call ToggleFullscreen3D jmp event_done event_focus_lost: + mov hfFireHeld,0 + mov hfFirePending,0 mov brEntryHold3D,0 mov dtHolding3D,0 mov dtMouseReleased3D,0 @@ -512,6 +527,8 @@ UpdateCamera3D ENDP ; Four corners of a 0.3-tile-wide body; bounds checked before every map read. CanWalk3D PROC uses ebx esi edi px:DWORD, py:DWORD + cmp engineMode,MODE_HELL_FPS + je br_generic_collision cmp brActive3D,0 jne br_generic_collision invoke CanWalkHall3D, px, py @@ -560,6 +577,7 @@ LoadProgress3D PROC uses ebx mov charmSkullUses, 0 mov charmDevilUses, 0 mov charmEnabledMask3D,7 + mov hfCompleted,0 mov brOwned3D,0 mov brEnabled3D,1 invoke CreateFileA, OFFSET savePath3D, 80000000h, 1, 0, 3, 80h, 0 @@ -578,7 +596,7 @@ LoadProgress3D PROC uses ebx cmp eax,saveData3D[36] jne progress_loaded mov eax,saveData3D[32] - test eax,0FFFCFFF0h + test eax,0FFF8FFF0h jnz progress_loaded test eax,20000h jz @F @@ -595,6 +613,10 @@ LoadProgress3D PROC uses ebx shr eax,17 and eax,1 mov brEnabled3D,eax + mov eax,saveData3D[32] + shr eax,18 + and eax,1 + mov hfCompleted,eax jmp progress_version_ok progress_legacy_version: cmp saveData3D, 33443343h @@ -690,6 +712,10 @@ SaveProgress3D PROC uses ebx shl ecx,17 or eax,ecx or eax,charmEnabledMask3D + mov ecx,hfCompleted + and ecx,1 + shl ecx,18 + or eax,ecx mov saveData3D[32],eax invoke PreviewArtifactSignature3D,OFFSET saveData3D mov saveData3D[36],eax diff --git a/tools/generators/import_freedoom.py b/tools/generators/import_freedoom.py new file mode 100644 index 0000000..9b0d892 --- /dev/null +++ b/tools/generators/import_freedoom.py @@ -0,0 +1,237 @@ +"""Import pinned Freedoom assets into native ANM1 packs (offline build tool).""" +import argparse +import hashlib +import io +import json +import struct +import urllib.request +import wave +import zipfile +from pathlib import Path + +from PIL import Image, ImageDraw + +ROOT = Path(__file__).resolve().parents[2] +SHA256 = "3f9b264f3e3ce503b4fb7f6bdcb1f419d93c7b546f4df3e874dd878db9688f59" +URL = "https://github.com/freedoom/freedoom/releases/download/v0.13.0/freedoom-0.13.0.zip" + + +class Wad: + def __init__(self, data): + magic, count, offset = struct.unpack_from('<4sII', data) + assert magic == b'IWAD' + self.lumps = {} + for i in range(count): + pos, size, name = struct.unpack_from('= 23 and y >= 22: + grid[y][x] = 190 # infernal sanctum + elif x >= 23: + grid[y][x] = 187 # flesh-lined approach + elif x >= 12 and y <= 11: + grid[y][x] = 186 # crypt stone + +# kind: 0 soldier, 1 melee, 2 imp, 3 boss. Coordinates at tile centres. +enemies = [(7,25,0),(8,28,0),(4,20,0),(8,16,1),(9,20,2),(4,15,0), + (14,20,2),(20,21,0),(20,14,2),(13,14,1),(18,17,1),(15,18,0), + (14,8,2),(20,4,2),(15,4,0),(20,9,1),(18,5,0), + (25,5,0),(28,8,2),(25,12,1),(28,15,0),(25,19,2),(28,19,1), + (24,25,2),(28,25,2),(27,28,3),(4,9,1),(7,8,0)] +# item kinds correspond to items.bin: health, armour, bullets, shells, shotgun, key, secret. +items = [(3,27,4),(4,28,3),(9,24,2),(3,19,0),(8,14,3),(9,15,2), + (13,21,1),(14,14,3),(20,20,0),(19,14,2),(14,4,0),(20,3,5), + (17,9,3),(25,4,2),(28,11,0),(25,17,3),(28,20,3), + (24,24,0),(29,28,3),(24,28,2),(3,8,1),(7,10,3),(19,26,0),(20,27,3)] +for x,y,_ in enemies+items: + assert grid[y][x] == 0, (x,y) + + +def reachable(open_groups=()): + seen = {(3,26)} + todo = deque(seen) + while todo: + x,y = todo.popleft() + for nx,ny in [(x-1,y),(x+1,y),(x,y-1),(x,y+1)]: + if (nx,ny) in seen or not (0<=nx<32 and 0<=ny<32): + continue + if grid[ny][nx] and (nx,ny) not in [(x,y) for x,y,group in doors if group in open_groups]: + continue + seen.add((nx,ny)) + todo.append((nx,ny)) + return seen + + +assert (20,3) in reachable() and (27,28) not in reachable() +assert (27,28) not in reachable((0,1)), 'Secrets must not bypass the key gate' +assert (27,28) in reachable((2,)), 'Secrets must remain optional' +assert all((x,y) in reachable((0,1,2)) for x,y,_ in enemies+items) +# Conservative ammunition budget: half the pellets land at minimum damage. +bullets = 80 + 40*sum(kind == 2 for _,_,kind in items) +shells = 16 + 12*sum(kind == 3 for _,_,kind in items) +enemy_hp = sum((55,105,85,1100)[kind] for _,_,kind in enemies) +assert (bullets + shells*7)*8//2 > enemy_hp +lines = ['; Generated by tools/generators/make_hell_level.py.','.data','hfMapTemplate label byte'] +lines += [' db '+','.join(map(str,row)) for row in grid] +lines += ['hfEnemySeeds label dword'] +lines += [f' dd {x}*FP_1+FP_HALF,{y}*FP_1+FP_HALF,{kind}' for x,y,kind in enemies] +lines += [f'HF_ENEMIES equ {len(enemies)}','hfItemSeeds label dword'] +lines += [f' dd {x}*FP_1+FP_HALF,{y}*FP_1+FP_HALF,{kind}' for x,y,kind in items] +lines += [f'HF_ITEMS equ {len(items)}','.code'] +(ROOT/'src_graphics/hell_fps_map.inc').write_text('\n'.join(lines)+'\n',encoding='ascii') +print(f'Hell map: {len(enemies)} enemies, {len(items)} pickups, key gate and two secrets validated') diff --git a/tools/tests/check_hell_fps.py b/tools/tests/check_hell_fps.py new file mode 100644 index 0000000..f1069bc --- /dev/null +++ b/tools/tests/check_hell_fps.py @@ -0,0 +1,53 @@ +"""Build and execute native MASM combat, renderer, progression and save checks.""" +from pathlib import Path +import argparse +import hashlib +import subprocess +from PIL import Image +from check_artifacts import toolchain + +ROOT = Path(__file__).resolve().parents[2] +OUT = ROOT / 'build/scratch' + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--window',action='store_true') + options = parser.parse_args() + OUT.mkdir(parents=True, exist_ok=True) + before = {p: hashlib.sha256(p.read_bytes()).digest() for p in (ROOT/'runtime').rglob('*') if p.is_file()} + ml, link = toolchain() + sdk = Path('C:/Program Files (x86)/Windows Kits/10/Lib') + lib = next(p/'um/x86' for p in sorted(sdk.iterdir(), reverse=True) + if (p/'um/x86/kernel32.lib').exists()) + commands = [[ml,'/c','/coff','/Cp','/nologo','/I.','/DHELL_FPS_TEST=1', + '/Fobuild/scratch/hell-fps.obj','poker.asm'], + [link,'/nologo','/SUBSYSTEM:CONSOLE','/LIBPATH:'+str(lib), + '/OUT:build/scratch/hell-fps-test.exe','build/scratch/hell-fps.obj', + 'kernel32.lib','winmm.lib','user32.lib','ws2_32.lib','gdi32.lib']] + if options.window: + commands[0].insert(1,'/DHELL_FPS_WINDOW_TEST=1') + for command in commands: + subprocess.run(list(map(str,command)),cwd=ROOT,check=True,timeout=120) + try: + result = subprocess.run([str(OUT/'hell-fps-test.exe')],cwd=OUT if options.window else ROOT,timeout=90) + finally: + assert before == {p: hashlib.sha256(p.read_bytes()).digest() for p in (ROOT/'runtime').rglob('*') if p.is_file()}, 'Test changed player saves' + assert result.returncode == 0, f'Native test failed at stage/exception {result.returncode:#x}' + for name in (('window',) if options.window else ('entry','court','portal','victory')): + raw = (OUT/f'hell-{name}.bgra').read_bytes() + assert len(raw) == 640*480*4 + image = Image.frombytes('RGBA',(640,480),raw,'raw','BGRA').convert('RGB') + image.save(OUT/f'hell-{name}.png') + assert len(image.getcolors(640*480)) > 100, f'{name}: blank renderer' + if options.window: + print('PASS: real Win32 window, 120 presented frames, mouse-down/up events, firing, Mick Gordon playback, six SFX channels, clean return') + return + print('PASS: painting entrance, collision/LOS, multi-target seven-pellet shotgun, ' + 'single-ray pistol, armour/death/retry, gates/secrets, ' + '1050 AI ticks, finite-ammo boss fight with dodging, first-clear reward, ' + 'native/preview save roundtrips, return, 4 native render captures') + + +if __name__ == '__main__': + main() diff --git a/tools/tests/hell_fps_test.inc b/tools/tests/hell_fps_test.inc new file mode 100644 index 0000000..c47f758 --- /dev/null +++ b/tools/tests/hell_fps_test.inc @@ -0,0 +1,509 @@ +.data +hfTestStage dd 1 +hfTestIo dd 0 +hfTestFrame db 'build\scratch\hell-entry.bgra',0 +hfTestCourt db 'build\scratch\hell-court.bgra',0 +hfTestHall db 'build\scratch\hell-portal.bgra',0 +hfTestWin db 'build\scratch\hell-victory.bgra',0 +hfTestPreview db 'build\scratch\hf-prev.dat',0 +hfTestProfileDir db 'build\scratch',0 +hfTestProfileName db 'hell-profile.dat',0 +hfTestBoss dd 0 +hfTestStrafe dd 5000 +.code +HFTestAimBoss PROC uses esi edi ebx + local vx:DWORD,vy:DWORD,best:DWORD + mov esi,hfTestBoss + mov eax,[esi].HFEnemy.x + sub eax,playerX + sar eax,8 + mov vx,eax + mov eax,[esi].HFEnemy.y + sub eax,playerY + sar eax,8 + mov vy,eax + mov best,80000000h + xor edi,edi +hf_test_aim_angle: + mov eax,tableCos[edi*4] + sar eax,8 + imul eax,vx + mov ebx,eax + mov eax,tableSin[edi*4] + sar eax,8 + imul eax,vy + add eax,ebx + cmp eax,best + jle @F + mov best,eax + mov playerAngle,edi +@@: + inc edi + cmp edi,512 + jb hf_test_aim_angle + ret +HFTestAimBoss ENDP + +HFTestCapture PROC uses ebx path:DWORD + invoke CreateFileA,path,40000000h,0,0,2,80h,0 + mov ebx,eax + invoke WriteFile,ebx,OFFSET screenBuffer,SCREEN_SIZE*4,OFFSET hfTestIo,0 + invoke CloseHandle,ebx + ret +HFTestCapture ENDP + +RunHellFPSTest PROC uses esi edi ebx + call InitHall3D + call LoadTextures3D + call LoadHallAssets3D + call InitializeCasinoSprites3D + mov hfTestStage,2 + call HFLoad + test eax,eax + jz hf_test_fail + mov playerX,HF_PORTAL_X + mov playerY,29*FP_1 + mov playerAngle,128 + mov engineMode,MODE_WALK + call UpdateCamera3D + call CheckHallInteractions3D + ; Approach must be within the standard interaction distance. + mov playerY,FP_1*294/10 + call CheckHallInteractions3D + cmp interactTargetId,INTERACT_HELL_FPS + jne hf_test_fail + invoke CanWalk3D,HF_PORTAL_X,FP_1*294/10 + cmp eax,1 + jne hf_test_fail + ; Old standing frame no longer blocks the west aisle. + invoke CanWalk3D,FP_1*25/10,11*FP_1 + cmp eax,1 + jne hf_test_fail + mov playerX,4*FP_1 + mov playerY,12*FP_1 + mov playerAngle,256 + call UpdateCamera3D + mov interactTargetId,0 + call CheckHallInteractions3D + cmp interactTargetId,INTERACT_HELL_FPS + je hf_test_fail + mov playerX,HF_PORTAL_X + mov playerY,FP_1*255/10 + mov playerAngle,128 + call UpdateCamera3D + call RenderCeilingAndFloor + call RenderWalls + call PrepareCityDepth3D + call RenderHall3D + invoke HFTestCapture,OFFSET hfTestHall + mov hfOldX,HF_PORTAL_X + mov hfOldY,FP_1*294/10 + mov hfOldAngle,128 + mov hfOldMap,OFFSET casinoMap + mov hfOldFloor,100 + mov hfOldLevel,0 + mov hfOldEye,HALL_STANDING_EYE + mov hfOldCeiling,FP_1*5/2 + call HFReset + mov hfTestStage,3 + call RenderHellFPS3D + invoke HFTestCapture,OFFSET hfTestFrame + invoke HFLineClear,3*FP_1+FP_HALF,26*FP_1+FP_HALF,7*FP_1+FP_HALF,26*FP_1+FP_HALF + cmp eax,1 + jne hf_test_fail + invoke HFLineClear,3*FP_1+FP_HALF,26*FP_1+FP_HALF,13*FP_1,26*FP_1+FP_HALF + test eax,eax + jnz hf_test_fail + invoke CanWalk3D,0,0 + test eax,eax + jnz hf_test_fail + mov hfTestStage,4 + ; Same hitscan must stop at wall, then damage the nearest target in open space. + mov hfEnemies.x,12*FP_1 + mov hfEnemies.y,26*FP_1+FP_HALF + mov hfEnemies.hp,100 + mov hfEnemies[SIZEOF HFEnemy].hp,0 + call HFFire + cmp hfEnemies.hp,100 + jne hf_test_fail + mov hfEnemies.x,7*FP_1 + mov hfCooldown,0 + call HFFire + cmp hfEnemies.hp,100 + jae hf_test_fail + cmp hfBullets,78 + jne hf_test_fail + mov hfTestStage,5 + mov bankroll,12345 + invoke HFPlayerCanWalk,hfEnemies.x,hfEnemies.y + test eax,eax + jnz hf_test_fail + mov hfArmor,20 + invoke HFDamagePlayer,10 + cmp hfHealth,95 + jne hf_test_fail + cmp hfArmor,15 + jne hf_test_fail + mov hfArmor,0 + invoke HFDamagePlayer,1000 + cmp hfState,1 + jne hf_test_fail + call HFReset + cmp hfState,0 + jne hf_test_fail + cmp hfHealth,100 + jne hf_test_fail + cmp bankroll,12345 + jne hf_test_fail + ; Natural shotgun pickup and consumption; no injected weapon ownership. + mov playerX,3*FP_1+FP_HALF + mov playerY,27*FP_1+FP_HALF + call HFPickups + cmp hfHasShotgun,1 + jne hf_test_fail + cmp hfShells,16 + jne hf_test_fail + call HFFire + cmp hfShells,15 + jne hf_test_fail + mov hfTestStage,12 + ; Three separated targets: pistol hits centre only; one shell hits all three. + call HFReset + mov esi,OFFSET hfEnemies + mov ecx,HF_ENEMIES +@@: + mov [esi].HFEnemy.hp,0 + add esi,SIZEOF HFEnemy + loop @B + mov hfEnemies.x,8*FP_1 + mov hfEnemies.y,FP_1*103/4 + mov hfEnemies.hp,1000 + mov hfEnemies[SIZEOF HFEnemy].x,8*FP_1 + mov hfEnemies[SIZEOF HFEnemy].y,FP_1*106/4 + mov hfEnemies[SIZEOF HFEnemy].hp,1000 + mov hfEnemies[2*SIZEOF HFEnemy].x,8*FP_1 + mov hfEnemies[2*SIZEOF HFEnemy].y,FP_1*109/4 + mov hfEnemies[2*SIZEOF HFEnemy].hp,1000 + call HFFire + cmp hfEnemies.hp,1000 + jne hf_test_fail + cmp hfEnemies[SIZEOF HFEnemy].hp,1000 + jae hf_test_fail + cmp hfEnemies[2*SIZEOF HFEnemy].hp,1000 + jne hf_test_fail + mov hfCooldown,0 + mov hfWeapon,1 + mov hfShells,2 + call HFFire + cmp hfShells,1 + jne hf_test_fail + cmp hfEnemies.hp,1000 + jae hf_test_fail + cmp hfEnemies[2*SIZEOF HFEnemy].hp,1000 + jae hf_test_fail + mov esi,OFFSET hfImpacts + mov ecx,HF_SHOT_PELLETS +@@: + cmp [esi].HFImpact.life,8 + jne hf_test_fail + add esi,SIZEOF HFImpact + loop @B + ; The whole pellet cone must stop at the separating wall. + mov hfEnemies.x,12*FP_1 + mov hfEnemies.hp,1000 + mov hfEnemies[SIZEOF HFEnemy].x,12*FP_1 + mov hfEnemies[SIZEOF HFEnemy].hp,1000 + mov hfEnemies[2*SIZEOF HFEnemy].x,12*FP_1 + mov hfEnemies[2*SIZEOF HFEnemy].hp,1000 + mov hfCooldown,0 + call HFFire + cmp hfEnemies.hp,1000 + jne hf_test_fail + cmp hfEnemies[SIZEOF HFEnemy].hp,1000 + jne hf_test_fail + cmp hfEnemies[2*SIZEOF HFEnemy].hp,1000 + jne hf_test_fail + cmp hfShells,0 + jne hf_test_fail + mov hfCooldown,0 + call HFFire + cmp hfCooldown,0 + jne hf_test_fail + call HFReset + mov hfTestStage,6 + mov playerX,21*FP_1+FP_HALF + mov playerY,6*FP_1+FP_HALF + call HFUse + cmp hfMap[6*32+22],188 + jne hf_test_fail + mov playerX,20*FP_1+FP_HALF + mov playerY,3*FP_1+FP_HALF + call HFPickups + cmp hfKey,1 + jne hf_test_fail + mov playerX,21*FP_1+FP_HALF + mov playerY,6*FP_1+FP_HALF + call HFUse + cmp hfMap[6*32+22],0 + jne hf_test_fail + mov playerX,5*FP_1+FP_HALF + mov playerY,13*FP_1+FP_HALF + call HFUse + call HFUse + cmp hfSecrets,1 + jne hf_test_fail + mov playerX,23*FP_1 + mov playerY,27*FP_1 + call HFUse + cmp hfSecrets,2 + jne hf_test_fail + mov hfTestStage,7 + ; Real AI/projectile/pickup loop stress; no alternate Python simulation. + call HFReset + mov playerX,14*FP_1 + mov playerY,21*FP_1 + mov playerAngle,430 + mov hfWeapon,1 + mov hfHasShotgun,1 + mov hfShells,80 + mov hfFireHeld,1 + mov ebx,1050 +hf_test_combat: + mov hfHealth,100 + mov hfState,0 + call HFTick + dec ebx + jnz hf_test_combat + mov hfFireHeld,0 + call RenderHellFPS3D + invoke HFTestCapture,OFFSET hfTestCourt + mov hfTestStage,11 + ; Isolated boss encounter: real pickups, finite ammo, normal damage and ticks. + call HFReset + mov esi,OFFSET hfEnemies + mov ecx,HF_ENEMIES +hf_test_isolate_boss: + cmp [esi].HFEnemy.kind,3 + jne hf_test_disable_enemy + mov hfTestBoss,esi + jmp @F +hf_test_disable_enemy: + mov [esi].HFEnemy.hp,0 +@@: + add esi,SIZEOF HFEnemy + loop hf_test_isolate_boss + mov playerX,3*FP_1+FP_HALF + mov playerY,27*FP_1+FP_HALF + call HFPickups + mov playerX,4*FP_1+FP_HALF + mov playerY,28*FP_1+FP_HALF + call HFPickups + mov playerX,8*FP_1+FP_HALF + mov playerY,14*FP_1+FP_HALF + call HFPickups + mov playerX,27*FP_1+FP_HALF + mov playerY,24*FP_1+FP_HALF + mov playerAngle,128 + mov hfFireHeld,1 + mov ebx,1400 +hf_test_boss_fight: + cmp playerX,28*FP_1+FP_HALF + jl @F + mov hfTestStrafe,-5000 +@@: + cmp playerX,26*FP_1+FP_HALF + jg @F + mov hfTestStrafe,5000 +@@: + mov eax,playerX + add eax,hfTestStrafe + push eax + invoke HFPlayerCanWalk,eax,playerY + pop ecx + test eax,eax + jz @F + mov playerX,ecx +@@: + call HFTestAimBoss + call HFTick + cmp hfState,0 + jne hf_test_fail + cmp hfKills,1 + je hf_test_boss_killed + dec ebx + jnz hf_test_boss_fight + jmp hf_test_fail +hf_test_boss_killed: + mov hfFireHeld,0 + mov hfTestStage,8 + ; Locked exit, then one-time reward, then repeat use cannot pay twice. + call HFReset + mov bankroll,10000 + mov hfCompleted,0 + mov playerX,28*FP_1 + mov playerY,28*FP_1 + call HFUse + cmp hfState,0 + jne hf_test_fail + mov esi,OFFSET hfEnemies + mov ecx,HF_ENEMIES +@@: + mov [esi].HFEnemy.hp,0 + add esi,SIZEOF HFEnemy + loop @B + call HFUse + cmp hfState,2 + jne hf_test_fail + cmp bankroll,16660 + jne hf_test_fail + call HFUse + cmp bankroll,16660 + jne hf_test_fail + mov playerX,26*FP_1 + mov playerY,24*FP_1 + mov playerAngle,96 + mov hfTicks,18000 + mov hfKills,28 + mov hfSecrets,2 + call RenderHellFPS3D + invoke HFTestCapture,OFFSET hfTestWin + mov hfTestStage,9 + ; Save extension survives native signed profile round trip. + mov nativeProfilesDirPtr3D,OFFSET hfTestProfileDir + mov nativeProfileActive3D,1 + invoke CopyCString,OFFSET saveFileName,OFFSET hfTestProfileName + call BuildNativeProfileDir3D + call SaveGame + mov hfCompleted,0 + call LoadSave + cmp hfCompleted,1 + jne hf_test_fail + cmp bankroll,16660 + jne hf_test_fail + mov hfTestStage,10 + ; Preview save uses the same new bit without changing its 40-byte format. + mov nativeProfileActive3D,0 + mov arrivalConfirmed3D,1 + invoke CopyCString,OFFSET savePath3D,OFFSET hfTestPreview + call SaveProgress3D + mov hfCompleted,0 + call LoadProgress3D + cmp hfCompleted,1 + jne hf_test_fail + cmp bankroll,16660 + jne hf_test_fail + mov arrivalConfirmed3D,0 + call LeaveHellFPS3D + cmp engineMode,MODE_WALK + jne hf_test_fail + cmp playerX,HF_PORTAL_X + jne hf_test_fail + cmp hallLevel3D,0 + jne hf_test_fail + call FreeHellFPS3D + xor eax,eax + ret +hf_test_fail: + mov eax,hfTestStage + ret +RunHellFPSTest ENDP + +IFDEF HELL_FPS_WINDOW_TEST +.data +hfWindowShot db 'build\scratch\hell-window.bgra',0 +hfMusicStatus db 'status hellmusic mode',0 +hfAudioMode db 32 dup(0) +.code +RunHellFPSWindowTest PROC uses esi ebx + call InitPaths3D + call Create3DWindow + test eax,eax + jz hf_window_failed + ; Keep test window modest and avoid taking keyboard/mouse focus. + cmp fullscreen3D,0 + je @F + call ToggleFullscreen3D +@@: + invoke SetWindowPos,h3DWnd,0,80,80,800,640,14h + call InitHall3D + call LoadTextures3D + call LoadHallAssets3D + call InitializeCasinoSprites3D + mov playerX,HF_PORTAL_X + mov playerY,FP_1*294/10 + mov playerAngle,128 + mov engineMode,MODE_WALK + call BeginHellFPS3D + cmp engineMode,MODE_HELL_FPS + jne hf_window_failed + cmp hfMusicError,0 + jne hf_window_failed + cmp hfSoundFailures,0 + jne hf_window_failed + cmp hfVolumeError,0 + jne hf_window_failed + mov settingsVolume,10 + call HFUpdateMusicVolume + cmp hfVolumeError,0 + jne hf_window_failed + mov eax,SIZEOF hfVolumePrefix-1 + cmp DWORD PTR hfVolumeCommand[eax],00303533h ; "350", not full volume 1000 + jne hf_window_failed + mov settingsVolume,0 + call HFUpdateMusicVolume + cmp hfVolumeError,0 + jne hf_window_failed + mov eax,SIZEOF hfVolumePrefix-1 + cmp WORD PTR hfVolumeCommand[eax],0030h + jne hf_window_failed + mov settingsVolume,5 + call HFUpdateMusicVolume + cmp hfVolumeError,0 + jne hf_window_failed + invoke ShowWindow,h3DWnd,4 + mov esi,120 +hf_window_frame: + invoke PeekMessageA,OFFSET msg3D,0,0,0,PM_REMOVE + test eax,eax + jz hf_window_simulate + invoke TranslateMessage,OFFSET msg3D + invoke DispatchMessageA,OFFSET msg3D + jmp hf_window_frame +hf_window_simulate: + mov deltaTime3D,17 + add animationClock3D,17 + cmp esi,110 + jne @F + invoke WindowEvents3D,h3DWnd,201h,1,0 +@@: + cmp esi,35 + jne @F + invoke WindowEvents3D,h3DWnd,202h,0,0 +@@: + call HandleHellFPS3D + call RenderHellFPS3D + call Present3D + invoke Sleep,17 + dec esi + jnz hf_window_frame + invoke HFTestCapture,OFFSET hfWindowShot + cmp hfBullets,80 + jae hf_window_failed + cmp hfFireHeld,0 + jne hf_window_failed + invoke mciSendStringA,OFFSET hfMusicStatus,OFFSET hfAudioMode,32,0 + test eax,eax + jnz hf_window_failed + cmp DWORD PTR hfAudioMode,'yalp' + jne hf_window_failed + call LeaveHellFPS3D + call FreeHellFPS3D + invoke DestroyWindow,h3DWnd + xor eax,eax + ret +hf_window_failed: + call FreeHellFPS3D + mov eax,111 + ret +RunHellFPSWindowTest ENDP +ENDIF