diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index ad3a1c11..00000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,101 +0,0 @@ -Thanks for contributing to Chocolate Doom! Whatever your contribution, -whether it's code or just a bug report, it's greatly appreciated. - -The project is governed by the -[Contributor Covenant](http://contributor-covenant.org/version/1/4/) -version 1.4. By contributing to the project you agree to abide by its -terms. To report violations, please send an email to fraggle@gmail.com. - -### Reporting bugs - -Before reporting a bug, it's worth checking if this really is a bug. -Chocolate Doom's mission is to reproduce the Vanilla (DOS) versions of -the Doom engine games, bugs and all. Check out the -[NOT-BUGS](../NOT-BUGS.md) file for a list of common issues which aren't -really bugs at all. You might also try searching [the GitHub issues -list](https://github.com/chocolate-doom/chocolate-doom/issues) to see -if your bug has already been reported. - -If you're confident that you've found a real bug (or even if you're -not sure!) please go ahead and [file an issue on -GitHub](https://github.com/chocolate-doom/chocolate-doom/issues/new). -You'll need a GitHub account, but it's pretty easy to sign up. - -Please try to give as much information as possible: - -* What version of Chocolate Doom are you using? Check the title bar of - the window for the version number. - -* Chocolate Doom runs on many different operating systems (not just - Windows!). Please say which operating system and what version of it - you're using. - -* Please say which game you're playing (Doom 1, Doom 2, Heretic, - Hexen, Strife, etc.) and if you're using any fan-made WADs or mods, - please say which mods (and where they can be downloaded!). It helps - to give the full command line you're using to start the game. - -* Please mention if you have any special configuration you think may be - relevant, too. - -### Feature requests - -Chocolate Doom is always open to new feature requests; however, please -be aware that the project is designed around a deliberately limited -[philosophy](../PHILOSOPHY.md), and many features common in other source -ports will not be accepted. Here are a few common requests which are -often rejected: - -* "High resolution" rendering (greater than 320x200 display). - -* An option to disable Vanilla limits, such as the visplane rendering - limit. - -* Ability to play "No Rest For The Living", the expansion pack which - comes with the XBLA / BFG Edition of Doom. - -If you're not sure whether your feature is in line with the project -philosophy, don't worry - just ask anyway! -To make a feature request, [file an issue on -GitHub](https://github.com/chocolate-doom/chocolate-doom/issues/new). - -### Bug fixes / code submission - -Thank you for contributing code to Chocolate Doom! Please check the -following guidelines before opening a pull request: - -* All code must be licensed under [the GNU General Public License, - version 2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). - Please don't reuse code that isn't GPL, or that is GPLv3 licensed. - Be aware that by submitting your code to the project, you're agreeing - to license it under the GPL. - -* Please follow the coding style guidelines described in the - [HACKING](../HACKING.md) file. - -* Please don't make unnecessary changes which just change formatting - without any actual change to program logic. While being consistent - is nice, such changes destroy the ability to use the `git blame` - command to see when code was last changed. - -* The guidelines given above in the "feature requests" section also - apply here. New features which aren't in line with the project - philosophy are likely to be rejected. If you're not sure, open a - feature request first and ask before you start implementing your - feature. - -* Follow the guidelines for [how to write a Git commit - message](http://chris.beams.io/posts/git-commit/). In short: the - first line should be a short summary; keep to an 80 column limit; - use the imperative mood ("fix bug X", rather than "fixed bug X" or - "fixing bug X"). If your change fixes a particular subsystem, - prefix the summary with that subsystem: eg. "doom: Fix bug X" or - "textscreen: Change size of X". - -* If you're making a change related to a bug, reference the GitHub - issue number in the commit message, eg. "This is a partial fix - for #646". This will link your commit into the issue comments. If - your change is a fix for the bug, put the word "fixes" before the - issue number to automatically close the issue once your change - is merged. - diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 101a5b33..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,28 +0,0 @@ - - -### Background - -Version of Chocolate Doom: - -Operating System and version: - -Game: (Doom/Heretic/Hexen/Strife/other) - -Any loaded WADs and mods (please include full command line): - -### Bug description - -Observed behavior: - -Expected behavior: - diff --git a/.gitignore b/.gitignore index fcd9394a..45dc1afb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ aclocal.m4 autom4te.cache autotools bin -config.h +/config.h config.hin config.log config.status @@ -52,3 +52,8 @@ GPATH GRTAGS GTAGS /HTML/ +*.midx +*.midx.z +*.wad +*.whd +/cmake-*/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..51fa36d7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "3rdparty/tinyusb"] + path = 3rdparty/tinyusb + url = git@github.com:liamfraser/tinyusb.git diff --git a/3rdparty/tinyusb b/3rdparty/tinyusb new file mode 160000 index 00000000..900f9372 --- /dev/null +++ b/3rdparty/tinyusb @@ -0,0 +1 @@ +Subproject commit 900f9372c35be9bdb5374af66b0fe3d9d32aae2b diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f5b7a7a..4bc7212b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,30 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -cmake_minimum_required(VERSION 3.7.2) -project("Chocolate Doom" VERSION 3.0.0 LANGUAGES C) +cmake_minimum_required(VERSION 3.13) + +# We use PICO_SDK_PATH being set as an indicator that we're doing a Pico BUILD +if (PICO_SDK_PATH) + include(pico_sdk_import.cmake) + include(pico_extras_import.cmake) +endif() + +project("Chocolate Doom" VERSION 3.0.0 LANGUAGES C CXX) +enable_language(CXX) +set(CMAKE_CXX_STANDARD 14) + +if (PICO_SDK_PATH) + # we are using git@github.com:liamfraser/tinyusb.git as it has some RP2040 fixes that aren't upstreamed yet + set(PICO_TINYUSB_PATH ${CMAKE_CURRENT_LIST_DIR}/3rdparty/tinyusb) + # this only affects device builds device, but we want to use zone for malloc in this case so we don't have two separate elastic spaces and can fit more in + set(SKIP_PICO_MALLOC 1) + pico_sdk_init() + if (PICO_ON_DEVICE AND NOT CMAKE_BUILD_TYPE STREQUAL "MinSizeRel") + message(WARNING "You should do a MinSizeRel build when targeting the RP2040 + (with -DCMAKE_BUILD_TYPE=MinSizeRel)") + endif() +endif() + +set(CMAKE_C_STANDARD 11) # Autotools variables set(top_srcdir ${CMAKE_CURRENT_SOURCE_DIR}) @@ -24,26 +47,30 @@ if(MSVC) add_definitions("/D_CRT_SECURE_NO_WARNINGS" "/D_CRT_SECURE_NO_DEPRECATE" "/D_CRT_NONSTDC_NO_DEPRECATE") else() - add_compile_options("-Wall" "-Wdeclaration-after-statement" - "-Wredundant-decls") + #add_compile_options("-Wall" "-Wdeclaration-after-statement" "-Wredundant-decls") + add_compile_options("-Wall" "-Wredundant-decls") endif() -find_package(SDL2 2.0.1) -find_package(SDL2_mixer 2.0.0) -find_package(SDL2_net 2.0.0) +# Note PICO_SDK path is set by the SDK initialization if it occurs above +if (NOT PICO_SDK) + find_package(SDL2 2.0.1 REQUIRED) + find_package(SDL2_mixer 2.0.0 REQUIRED) + find_package(SDL2_net 2.0.0 REQUIRED) -# Check for libsamplerate. -find_package(samplerate) -if(SAMPLERATE_FOUND) - set(HAVE_LIBSAMPLERATE TRUE) -endif() - -# Check for libpng. -find_package(PNG) -if(PNG_FOUND) - set(HAVE_LIBPNG TRUE) + # Check for libsamplerate. + find_package(samplerate) + if(SAMPLERATE_FOUND) + set(HAVE_LIBSAMPLERATE TRUE) + endif() + + # Check for libpng. + find_package(PNG) + if(PNG_FOUND) + set(HAVE_LIBPNG TRUE) + endif() endif() +set(HAVE_MMAP 1) find_package(m) include(CheckSymbolExists) diff --git a/README-chocolate.md b/README-chocolate.md new file mode 100644 index 00000000..f39b1b60 --- /dev/null +++ b/README-chocolate.md @@ -0,0 +1,104 @@ +# Chocolate Doom + +Chocolate Doom aims to accurately reproduce the original DOS version of +Doom and other games based on the Doom engine in a form that can be +run on modern computers. + +Originally, Chocolate Doom was only a Doom source port. The project +now includes ports of Heretic and Hexen, and Strife. + +Chocolate Doom’s aims are: + + * To always be 100% Free and Open Source software. + * Portability to as many different operating systems as possible. + * Accurate reproduction of the original DOS versions of the games, + including bugs. + * Compatibility with the DOS demo, configuration and savegame files. + * To provide an accurate retro “feel” (display and input should + behave the same). + +More information about the philosophy and design behind Chocolate Doom +can be found in the PHILOSOPHY file distributed with the source code. + +## Setting up gameplay + +For instructions on how to set up Chocolate Doom for play, see the +INSTALL file. + +## Configuration File + +Chocolate Doom is compatible with the DOS Doom configuration file +(normally named `default.cfg`). Existing configuration files for DOS +Doom should therefore simply work out of the box. However, Chocolate +Doom also provides some extra settings. These are stored in a +separate file named `chocolate-doom.cfg`. + +The configuration can be edited using the chocolate-setup tool. + +## Command line options + +Chocolate Doom supports a number of command line parameters, including +some extras that were not originally suported by the DOS versions. For +binary distributions, see the CMDLINE file included with your +download; more information is also available on the Chocolate Doom +website. + +## Playing TCs + +With Vanilla Doom there is no way to include sprites in PWAD files. +Chocolate Doom’s ‘-file’ command line option behaves exactly the same +as Vanilla Doom, and trying to play TCs by adding the WAD files using +‘-file’ will not work. + +Many Total Conversions (TCs) are distributed as a PWAD file which must +be merged into the main IWAD. Typically a copy of DEUSF.EXE is +included which performs this merge. Chocolate Doom includes a new +option, ‘-merge’, which will simulate this merge. Essentially, the +WAD directory is merged in memory, removing the need to modify the +IWAD on disk. + +To play TCs using Chocolate Doom, run like this: + +``` +chocolate-doom -merge thetc.wad +``` + +Here are some examples: + +``` +chocolate-doom -merge batman.wad -deh batman.deh vbatman.deh (Batman Doom) +chocolate-doom -merge aoddoom1.wad -deh aoddoom1.deh (Army of Darkness Doom) +``` + +## Other information + + * Chocolate Doom includes a number of different options for music + playback. See the README.Music file for more details. + + * More information, including information about how to play various + classic TCs, is available on the Chocolate Doom website: + + https://www.chocolate-doom.org/ + + You are encouraged to sign up and contribute any useful information + you may have regarding the port! + + * Chocolate Doom is not perfect. Although it aims to accurately + emulate and reproduce the DOS executables, some behavior can be very + difficult to reproduce. Because of the nature of the project, you + may also encounter Vanilla Doom bugs; these are intentionally + present; see the NOT-BUGS file for more information. + + New bug reports can be submitted to the issue tracker on Github: + + https://github.com/chocolate-doom/chocolate-doom/issues + + * Source code patches are welcome, but please follow the style + guidelines - see the file named HACKING included with the source + distribution. + + * Chocolate Doom is distributed under the GNU GPL. See the COPYING + file for more information. + + * Please send any feedback, questions or suggestions to + chocolate-doom-dev-list@chocolate-doom.org. Thanks! diff --git a/README.md b/README.md index f39b1b60..9a95b0fb 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,219 @@ -# Chocolate Doom +# RP2040 Doom -Chocolate Doom aims to accurately reproduce the original DOS version of -Doom and other games based on the Doom engine in a form that can be -run on modern computers. +This is a port of Doom for RP2040 devices, derived from [Chocolate Doom](https://github.com/chocolate-doom/chocolate-doom). -Originally, Chocolate Doom was only a Doom source port. The project -now includes ports of Heretic and Hexen, and Strife. +Significant changes have been made to support running on the RP2040 device, but particularly to support running the +entire shareware `DOOM1.WAD` which is 4M big on a Raspberry Pi Pico with only 2M flash! -Chocolate Doom’s aims are: +You can read many details on this port in the blog post [here](https://kilograham.github.io/rp2040-doom/). - * To always be 100% Free and Open Source software. - * Portability to as many different operating systems as possible. - * Accurate reproduction of the original DOS versions of the games, - including bugs. - * Compatibility with the DOS demo, configuration and savegame files. - * To provide an accurate retro “feel” (display and input should - behave the same). +Note that a hopefully-fully-functional `chocolate-doom` executable is buildable from this RP2040 code base as a +means of +verification that everything still works, but whilst they can still be built, Hexen, Strife and Heretic are almost +certainly broken, so are not built by default. -More information about the philosophy and design behind Chocolate Doom -can be found in the PHILOSOPHY file distributed with the source code. +This chocolate-doom commit that the code is branched off can be found in the `upstream` branch. -## Setting up gameplay +The original Chocolate Doom README is [here](README-chocolate.md). -For instructions on how to set up Chocolate Doom for play, see the -INSTALL file. +## Code State -## Configuration File +Thus far, the focus has been entirely on getting RP2040 Doom running. Not a lot of time has been +spent +cleaning +the code up. There are a bunch of defunct `#ifdefs` and other code that was useful at some point, +but no longer are, and indeed changing them may result in non-functional code. This is particularly +true of +the +`whd_gen` tool +used to +convert/compress WADs +who's code is +likely completely incomprehensible! -Chocolate Doom is compatible with the DOS Doom configuration file -(normally named `default.cfg`). Existing configuration files for DOS -Doom should therefore simply work out of the box. However, Chocolate -Doom also provides some extra settings. These are stored in a -separate file named `chocolate-doom.cfg`. +## Artifacts -The configuration can be edited using the chocolate-setup tool. +You can find a RP2040 Doom UF2s based on the standard VGA/I2S pins in the +releases of this repository. There are also versions with the shareware DOOM1.WAD already embedded. -## Command line options +Note you can always use `picotool info -a ` to see the pins used by a particular build. -Chocolate Doom supports a number of command line parameters, including -some extras that were not originally suported by the DOS versions. For -binary distributions, see the CMDLINE file included with your -download; more information is also available on the Chocolate Doom -website. +## Goals -## Playing TCs +The main goals for this port were: -With Vanilla Doom there is no way to include sprites in PWAD files. -Chocolate Doom’s ‘-file’ command line option behaves exactly the same -as Vanilla Doom, and trying to play TCs by adding the WAD files using -‘-file’ will not work. +1. Everything should match the original game experience, i.e. all the graphics at classic 320x200 resolution, stereo + sound, + OPL2 music, save/load, demo playback, cheats, network multiplayer... basically it should feel like the original game. +2. `DOOM1.WAD` should run on a Raspberry Pi Pico. There was also to be no sneaky discarding of splash screens, altering of levels, down-sampling of + textures or whatever. RP2040 boards with 8M should be able to play at least the full *Ultimate Doom* and *DOOM II* + WADs. +3. The RP2040 should output directly to VGA (16 color pins for RGB565 along with HSync/VSync) along with stereo sound. -Many Total Conversions (TCs) are distributed as a PWAD file which must -be merged into the main IWAD. Typically a copy of DEUSF.EXE is -included which performs this merge. Chocolate Doom includes a new -option, ‘-merge’, which will simulate this merge. Essentially, the -WAD directory is merged in memory, removing the need to modify the -IWAD on disk. +## Results -To play TCs using Chocolate Doom, run like this: +[![RP2040 Doom on a Raspberry Pi Pico](https://img.youtube.com/vi/eDVazQVycP4/maxresdefault.jpg)](https://youtu.be/eDVazQVycP4) -``` -chocolate-doom -merge thetc.wad +Features: + +* Full `DOOM1.WAD` playable on Raspberry Pi Pico with 2M flash. +* *Ultimate Doom* and *Doom II* are playable on 8M devices. +* 320x200x60 VGA output (really 1280x1024x60). +* 9 Channel OPL2 Sound at 49716Hz. +* 9 Channel Stereo Sound Effects. +* I2C networking for up to 4 players. +* Save/Load of games. +* All cheats supported. +* Demos from original WADs run correctly. +* USB Keyboard Input support. +* All end scenes, intermissions, help screens etc. supported. +* Good frame rate; generally 30-35+ FPS. +* Uses 270Mhz overclock (requires flash chip that will run at 135Mhz) + +# Building + +RP2040 Doom should build fine on Linux and macOS. The RP2040 targeting builds should also work on Windows, though I +haven't tried. + +The build uses `CMake`. + +## Regular chocolate-doom/native builds + +To build everything, assuming you have SDL2 dependencies installed, you can create a build directory: + +```bash +mkdir build +cd build +cmake .. ``` -Here are some examples: +And then run `make` or `make -j` from that directory. To build a particular target e.g. `chocolate-doom`, +do `make chocolate-doom` -``` -chocolate-doom -merge batman.wad -deh batman.deh vbatman.deh (Batman Doom) -chocolate-doom -merge aoddoom1.wad -deh aoddoom1.deh (Army of Darkness Doom) +Note this is the way you build the `whd_gen` tool too. + +## RP2040 Doom builds + +You must have [pico-sdk](https://github.com/raspberrypi/pico-sdk) and +**the latest version of** [pico-extras](https://github.com/raspberrypi/pico-extras) installed, along with the regular +pico-sdk requisites (e.g. +`arm-none-eabi-gcc`). If in doubt, see the Raspberry Pi +[documentation](https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf). I have been building against +the `develop` branch of `pico-sdk`, so I recommend that.. + +For USB keyboard input support, RP2040 Doom currently uses a modified version of TinyUSB included as a submodule. +Make sure you have initialized this submodule via `git submodule update --init` + +You can create a build directly like this: + +```bash +mkdir rp2040-build +cd rp2040-build +cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DPICO_BOARD=vgaboard -DPICO_SDK_PATH=/path/to/pico-sdk -DPICO_EXTRAS_PATH=/path/to/pico-extras .. ``` -## Other information +Note that the `PICO_BOARD` setting is for the standard VGA demo board which has RGB on pins 0->15, sync pins on 16,17 +and +I2S on 26,27,28. - * Chocolate Doom includes a number of different options for music - playback. See the README.Music file for more details. +As before, use `make` or `make ` to build. - * More information, including information about how to play various - classic TCs, is available on the Chocolate Doom website: +The RP2040 version has four targets, each of which create a similarly named `UF2` file (e.g. `doom_tiny_nh.uf2`). +These UF2 files contain the executable code/data, but they do not contain the WAD data which is converted into a +RP2040 Domom +specific WHD/WHX format by `whd_gen` (for more see below). The WHD/WHX file must also be loaded onto the device at a +specific address which varies by binary. - https://www.chocolate-doom.org/ +"super-tiny" refers to RP2040 Doom builds that use the more compressed WHX format, and +required for`DOOM1. +WAD` to +run +on a 2M Raspberry Pi Pico. "Non super-tiny" refers to RP2040 Doom builds that use the WHD format which is larger, but +also is +required for *Ultimate Doom* and *Doom II* WADs. These binaries are distinct as supporting both formats in the same +binary would just have made things bigger and slower. - You are encouraged to sign up and contribute any useful information - you may have regarding the port! - * Chocolate Doom is not perfect. Although it aims to accurately - emulate and reproduce the DOS executables, some behavior can be very - difficult to reproduce. Because of the nature of the project, you - may also encounter Vanilla Doom bugs; these are intentionally - present; see the NOT-BUGS file for more information. +* **doom_tiny_nh** This is a "super tiny" version with no USB keyboard support. You can use +[SDL Event Forwarder](https://github.com/kilograham/sdl_event_forwarder) to tunnel keyboard input from your host + computer over UART. The WHX file must be loaded at `0x10040000`. +* **doom_tiny_nh_cd** This is a "super tiny" version with additional USB keyboard support. Because of the extra USB + code, the WHX file must be loaded at `0x10042000`. As you can see USB support via TinyUSB causes the binary to + grow by 2K (hence the move of the WHX file address) leaving less space for saved games (which are also stored in + flash). +* **doom_tiny_nh_nost** This is a "non super tiny" version of `doom_tiny_nh` supporting larger WADs stored as WHD. The WHD + file must be loaded at `0x10048000` +* **doom_tiny_nh_nost_cd** This is a "non super tiny" version of `doom_tiny_nh_cd` supporting larger WADs stored as + WHD. The WHD + file must be loaded at `0x10048000` - New bug reports can be submitted to the issue tracker on Github: +You can load you WHD/WHX file using [picotool](https://github.com/raspberrypi/picotool). e.g. - https://github.com/chocolate-doom/chocolate-doom/issues +```bash +picotool load -v -t bin doom1.whx -o 0x10042000. +``` - * Source code patches are welcome, but please follow the style - guidelines - see the file named HACKING included with the source - distribution. +See `whd_gen` further below for generating `WHX` or `WHD` files. - * Chocolate Doom is distributed under the GNU GPL. See the COPYING - file for more information. +#### USB keyboard support + +Note that TinyUSB host mode support for keyboard may not work with all keyboards especially since the RP2040 Doom +has been built with small limits for number/sizes of hubs etc. I know that Raspberry Pi keyboards work fine, as +did my ancient +Dell keyboard. Your keyboard may just do nothing, or may cause a crash. If so, for now, you are stuck forwarding +keys from another PC via sdl_event_forwarder. + +### RP2040 Doom builds not targeting an RP2040 device + +You can also build the RP2040 Doom to run on your host computer (Linux or macOS) by using +[pico_host_sdl](https://github.com/raspberrypi/pico-host-sdl) which simulates RP2040 based video/audio output using SDL. + +This version currently embeds the WHD/WHX in `src/tiny.whd.h` so you must generate this file. + +You can do this via `./cup.sh ` + +```bash +mkdir host-build +cd host-build +cmake -DPICO_PLATFORM=host -DPICO_SDK_PATH=/path/to/pico-sdk -DPICO_EXTRAS_PATH=/path/to/pico-extras -DPICO_SDK_PRE_LIST_DIRS=/path/to/pico_host_sdl .. +``` + +... and then `make` as usual. + +## whd_gen + +`doom1.whx` is includd in this repository, otherwise you need to build `whd_gen` using the regular native build +instructions above. + +To generate a WHX file (you must use this to convert DOOM1.WAD to run on a 2M Raspberry Pi Pico) + +```bash +whd_gen +``` + +The larger WADs (e.g. *Ultimate Doom* or *Doom II* have levels which are too complex to convert into a super tiny +WHX file. These larger WADs are not going to fit in a 2M flash anywy, so the less compressed WHD format can be used +given that the device now probably has 8M of flash. + +```bash +whd_gen -no-super-tiny +``` + +Note that `whd_gen` has not been tested with a wide variety of WADs, so whilst it is possible that non Id WADs may +work, it is by no means guaranteed! + +NOTE: You should use a release build of `whd_gen` for the best sound effect fidelity, as the debug build +deliberately lowers the encoding quality for the sake of speed. + +# Future + +*Evilution* and *Plutonia* are not yet supported. There is an issue tracking it +[here](https://github.com/kilograham/rp2040-doom/issues/1). + +# RP2040 Doom Licenses + +* Any code derived from chocolate-doom matinains its existing license (generally GPLv2). +* New RP2040 Doom specific code not implementing existing chocolate-doom interfaces is licensed BSD-3. +* ADPCM-XA is unmodified and is licensed BSD-3. +* Modified emu8950 derived code retains its MIT license. - * Please send any feedback, questions or suggestions to - chocolate-doom-dev-list@chocolate-doom.org. Thanks! diff --git a/cmake/config.h.cin b/cmake/config.h.cin index 818317dd..1293b309 100644 --- a/cmake/config.h.cin +++ b/cmake/config.h.cin @@ -7,5 +7,6 @@ #cmakedefine HAVE_LIBSAMPLERATE #cmakedefine HAVE_LIBPNG #cmakedefine HAVE_DIRENT_H +#cmakedefine HAVE_MMAP #cmakedefine01 HAVE_DECL_STRCASECMP #cmakedefine01 HAVE_DECL_STRNCASECMP diff --git a/cup.sh b/cup.sh new file mode 100755 index 00000000..0536bab6 --- /dev/null +++ b/cup.sh @@ -0,0 +1 @@ +xxd -i $1 | sed "s/unsigned/const unsigned/g" | sed "s/$1/tiny_whd/g" >src/tiny.whd.h diff --git a/doom1.whx b/doom1.whx new file mode 100644 index 00000000..0fa85d55 Binary files /dev/null and b/doom1.whx differ diff --git a/m_deathmch.png b/m_deathmch.png new file mode 100644 index 00000000..448582a4 Binary files /dev/null and b/m_deathmch.png differ diff --git a/m_game.png b/m_game.png new file mode 100644 index 00000000..cb29f0cf Binary files /dev/null and b/m_game.png differ diff --git a/m_host.png b/m_host.png new file mode 100644 index 00000000..8f5f3ed1 Binary files /dev/null and b/m_host.png differ diff --git a/m_join.png b/m_join.png new file mode 100644 index 00000000..6da28256 Binary files /dev/null and b/m_join.png differ diff --git a/m_name.png b/m_name.png new file mode 100644 index 00000000..32f19672 Binary files /dev/null and b/m_name.png differ diff --git a/m_network.png b/m_network.png new file mode 100644 index 00000000..bd30b896 Binary files /dev/null and b/m_network.png differ diff --git a/m_two.png b/m_two.png new file mode 100644 index 00000000..2fd4ed56 Binary files /dev/null and b/m_two.png differ diff --git a/opl/CMakeLists.txt b/opl/CMakeLists.txt index f88a12a1..f89ca2cf 100644 --- a/opl/CMakeLists.txt +++ b/opl/CMakeLists.txt @@ -1,15 +1,41 @@ -add_library(opl STATIC - opl_internal.h - opl.c opl.h - opl_linux.c - opl_obsd.c - opl_queue.c opl_queue.h - opl_sdl.c - opl_timer.c opl_timer.h - opl_win32.c - ioperm_sys.c ioperm_sys.h - opl3.c opl3.h) -target_include_directories(opl - INTERFACE "." - PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") -target_link_libraries(opl SDL2::mixer) +if (PICO_SDK) + add_library(opl INTERFACE) + target_sources(opl INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/opl_api.c + ${CMAKE_CURRENT_LIST_DIR}/emu8950.c + ${CMAKE_CURRENT_LIST_DIR}/slot_render.cpp + ${CMAKE_CURRENT_LIST_DIR}/opl_pico.c) + target_compile_options(opl INTERFACE -fms-extensions) # want OPL_SLOT_RENDER to be unnamed within OPL_SLOT + if (APPLE) + target_compile_options(opl INTERFACE -Wno-microsoft-anon-tag) + endif() + target_compile_definitions(opl INTERFACE + EMU8950_NO_RATECONV + ) + target_include_directories(opl INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + target_link_libraries(opl INTERFACE pico_audio_i2s hardware_gpio) + if (PICO_ON_DEVICE) + target_sources(opl INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/slot_render_pico.S + ) + target_link_libraries(opl INTERFACE hardware_interp) + endif() +else() + add_library(opl INTERFACE) + target_sources(opl INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/opl_api.c + ${CMAKE_CURRENT_LIST_DIR}/opl.c + ${CMAKE_CURRENT_LIST_DIR}/opl_linux.c + ${CMAKE_CURRENT_LIST_DIR}/opl_obsd.c + ${CMAKE_CURRENT_LIST_DIR}/opl_queue.c + ${CMAKE_CURRENT_LIST_DIR}/opl_sdl.c + ${CMAKE_CURRENT_LIST_DIR}/opl_timer.c + ${CMAKE_CURRENT_LIST_DIR}/opl_win32.c + ${CMAKE_CURRENT_LIST_DIR}/ioperm_sys.c + ${CMAKE_CURRENT_LIST_DIR}/emu8950.c + ${CMAKE_CURRENT_LIST_DIR}/opl3.c) + target_include_directories(opl + INTERFACE "." + "${CMAKE_CURRENT_BINARY_DIR}/../") + target_link_libraries(opl INTERFACE SDL2::mixer) +endif() diff --git a/opl/emu8950.c b/opl/emu8950.c new file mode 100644 index 00000000..de7fcc15 --- /dev/null +++ b/opl/emu8950.c @@ -0,0 +1,2028 @@ +/** + * emu8950 v1.1.0 + * https://github.com/digital-sound-antiques/emu8950 + * Copyright (C) 2001-2020 Mitsutaka Okazaki + * Copyright (C) 2021-2022 Graham Sanderson + * + * SPDX-License-Identifier: MIT + */ +#if USE_EMU8950_OPL +#include "emu8950.h" +#include +#include +#include +#include +#include + +#define SAMPLE_BUF_SIZE 1024 + +#ifndef INLINE +#if defined(_MSC_VER) +#define INLINE __inline +#elif defined(__GNUC__) +#define INLINE __inline__ +#else +#define INLINE inline +#endif +#endif + +#define _PI_ 3.14159265358979323846264338327950288 + +/* dynamic range of envelope output */ +#if !EMU8950_NO_FLOAT +#define EG_STEP 0.1875 +#else +#define EG_STEPx16 3 +#endif + +/* dynamic range of total level */ +#define TL_STEP 0.75 +#define TL_BITS 6 + +/* dynamic range of sustine level */ +#define SL_STEP 3.0 +#define SL_BITS 4 + +/* damper speed before key-on. key-scale affects. */ +#define DAMPER_RATE 12 + +#define TL2EG(tl) ((tl) << 2) + + +/* clang-format off */ +/* exp_table[255-x] = round((exp2((double)x / 256.0) - 1) * 1024) */ +static uint16_t exp_table[256] = { + 1018, 1013, 1007, 1002, 996, 991, 986, 980, 975, 969, 964, 959, 953, 948, 942, 937, + 932, 927, 921, 916, 911, 906, 900, 895, 890, 885, 880, 874, 869, 864, 859, 854, + 849, 844, 839, 834, 829, 824, 819, 814, 809, 804, 799, 794, 789, 784, 779, 774, + 770, 765, 760, 755, 750, 745, 741, 736, 731, 726, 722, 717, 712, 708, 703, 698, + 693, 689, 684, 680, 675, 670, 666, 661, 657, 652, 648, 643, 639, 634, 630, 625, + 621, 616, 612, 607, 603, 599, 594, 590, 585, 581, 577, 572, 568, 564, 560, 555, + 551, 547, 542, 538, 534, 530, 526, 521, 517, 513, 509, 505, 501, 496, 492, 488, + 484, 480, 476, 472, 468, 464, 460, 456, 452, 448, 444, 440, 436, 432, 428, 424, + 420, 416, 412, 409, 405, 401, 397, 393, 389, 385, 382, 378, 374, 370, 367, 363, + 359, 355, 352, 348, 344, 340, 337, 333, 329, 326, 322, 318, 315, 311, 308, 304, + 300, 297, 293, 290, 286, 283, 279, 276, 272, 268, 265, 262, 258, 255, 251, 248, + 244, 241, 237, 234, 231, 227, 224, 220, 217, 214, 210, 207, 204, 200, 197, 194, + 190, 187, 184, 181, 177, 174, 171, 168, 164, 161, 158, 155, 152, 148, 145, 142, + 139, 136, 133, 130, 126, 123, 120, 117, 114, 111, 108, 105, 102, 99, 96, 93, + 90, 87, 84, 81, 78, 75, 72, 69, 66, 63, 60, 57, 54, 51, 48, 45, + 42, 40, 37, 34, 31, 28, 25, 22, 20, 17, 14, 11, 8, 6, 3, 0, +}; +/* logsin_table[x] = round(-log2(sin((x + 0.5) * PI / (PG_WIDTH / 4) / 2)) * 256) */ + +#if !EMU8950_NO_WAVE_TABLE_MAP +#define LOGSIN_TABLE_SIZE PG_WIDTH / 4 +#else +#define LOGSIN_TABLE_SIZE PG_WIDTH / 2 +#endif +static uint16_t logsin_table[LOGSIN_TABLE_SIZE] = { + 2137, 1731, 1543, 1419, 1326, 1252, 1190, 1137, 1091, 1050, 1013, 979, 949, 920, 894, 869, + 846, 825, 804, 785, 767, 749, 732, 717, 701, 687, 672, 659, 646, 633, 621, 609, + 598, 587, 576, 566, 556, 546, 536, 527, 518, 509, 501, 492, 484, 476, 468, 461, + 453, 446, 439, 432, 425, 418, 411, 405, 399, 392, 386, 380, 375, 369, 363, 358, + 352, 347, 341, 336, 331, 326, 321, 316, 311, 307, 302, 297, 293, 289, 284, 280, + 276, 271, 267, 263, 259, 255, 251, 248, 244, 240, 236, 233, 229, 226, 222, 219, + 215, 212, 209, 205, 202, 199, 196, 193, 190, 187, 184, 181, 178, 175, 172, 169, + 167, 164, 161, 159, 156, 153, 151, 148, 146, 143, 141, 138, 136, 134, 131, 129, + 127, 125, 122, 120, 118, 116, 114, 112, 110, 108, 106, 104, 102, 100, 98, 96, + 94, 92, 91, 89, 87, 85, 83, 82, 80, 78, 77, 75, 74, 72, 70, 69, + 67, 66, 64, 63, 62, 60, 59, 57, 56, 55, 53, 52, 51, 49, 48, 47, + 46, 45, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, + 29, 28, 27, 26, 25, 24, 23, 23, 22, 21, 20, 20, 19, 18, 17, 17, + 16, 15, 15, 14, 13, 13, 12, 12, 11, 10, 10, 9, 9, 8, 8, 7, + 7, 7, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, + 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +#if EMU8950_NO_WAVE_TABLE_MAP + // double the table size to include second 1/4 cycle + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, + 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, + 7, 8, 8, 9, 9, 10, 10, 11, 12, 12, 13, 13, 14, 15, 15, 16, + 17, 17, 18, 19, 20, 20, 21, 22, 23, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45, 46, + 47, 48, 49, 51, 52, 53, 55, 56, 57, 59, 60, 62, 63, 64, 66, 67, + 69, 70, 72, 74, 75, 77, 78, 80, 82, 83, 85, 87, 89, 91, 92, 94, + 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 125, 127, + 129, 131, 134, 136, 138, 141, 143, 146, 148, 151, 153, 156, 159, 161, 164, 167, + 169, 172, 175, 178, 181, 184, 187, 190, 193, 196, 199, 202, 205, 209, 212, 215, + 219, 222, 226, 229, 233, 236, 240, 244, 248, 251, 255, 259, 263, 267, 271, 276, + 280, 284, 289, 293, 297, 302, 307, 311, 316, 321, 326, 331, 336, 341, 347, 352, + 358, 363, 369, 375, 380, 386, 392, 399, 405, 411, 418, 425, 432, 439, 446, 453, + 461, 468, 476, 484, 492, 501, 509, 518, 527, 536, 546, 556, 566, 576, 587, 598, + 609, 621, 633, 646, 659, 672, 687, 701, 717, 732, 749, 767, 785, 804, 825, 846, + 869, 894, 920, 949, 979, 1013, 1050, 1091, 1137, 1190, 1252, 1326, 1419, 1543, 1731, 2137, +#endif +}; +/* clang-format on */ + +/* amplitude lfo table */ +/* The following envelop pattern is verified on real YM2413. */ +/* each element repeates 64 cycles */ +static uint8_t am_table[210] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, // + 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, // + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, // + 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, // + 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, // + 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, // + 12, 12, 12, 12, 12, 12, 12, 12, // + 13, 13, 13, // + 12, 12, 12, 12, 12, 12, 12, 12, // + 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 10, 10, 10, // + 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, // + 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, // + 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, // + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, // + 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}; + +#if !EMU8950_SLOT_RENDER +#if !EMU8950_NO_WAVE_TABLE_MAP +static uint16_t wave_table_map[4][PG_WIDTH]; +#else +// we start with +// _ _ +// / \/ \ which is abs(sine) wave +// +static uint16_t wav_or_table_lookup[4][4] = { + {0, 0, 0x8000, 0x8000}, // .. negate second half + {0, 0, 0x0fff, 0x0fff}, // .. attenuate second half + {0, 0, 0x0000, 0x0000}, // .. leave second half alone + {0, 0xfff, 0, 0xfff}, // .. attenuate 1 and 3 +}; +#endif + + +/* offset to fnum, rough approximation of 14 cents depth. */ +static int8_t pm_table[8][PM_PG_WIDTH] = { + {0, 0, 0, 0, 0, 0, 0, 0}, // fnum = 000xxxxx + {0, 0, 1, 0, 0, 0, -1, 0}, // fnum = 001xxxxx + {0, 1, 2, 1, 0, -1, -2, -1}, // fnum = 010xxxxx + {0, 1, 3, 1, 0, -1, -3, -1}, // fnum = 011xxxxx + {0, 2, 4, 2, 0, -2, -4, -2}, // fnum = 100xxxxx + {0, 2, 5, 2, 0, -2, -5, -2}, // fnum = 101xxxxx + {0, 3, 6, 3, 0, -3, -6, -3}, // fnum = 110xxxxx + {0, 3, 7, 3, 0, -3, -7, -3}, // fnum = 111xxxxx +}; + + +/* envelope decay increment step table */ +static uint8_t eg_step_tables[4][8] = { + {0, 1, 0, 1, 0, 1, 0, 1}, + {0, 1, 0, 1, 1, 1, 0, 1}, + {0, 1, 1, 1, 0, 1, 1, 1}, + {0, 1, 1, 1, 1, 1, 1, 1}, +}; +static uint8_t eg_step_tables_fast[4][8] = { + {1, 1, 1, 1, 1, 1, 1, 1}, + {1, 1, 1, 2, 1, 1, 1, 2}, + {1, 2, 1, 2, 1, 2, 1, 2}, + {1, 2, 2, 2, 1, 2, 2, 2}, +}; + +static uint32_t ml_table[16] = {1, 1 * 2, 2 * 2, 3 * 2, 4 * 2, 5 * 2, 6 * 2, 7 * 2, + 8 * 2, 9 * 2, 10 * 2, 10 * 2, 12 * 2, 12 * 2, 15 * 2, 15 * 2}; + +#endif + +#if !EMU8950_NO_TLL +#if !EMU8950_NO_FLOAT +#define dB2(x) ((x)*2) +static double kl_table[16] = {dB2(0.000), dB2(9.000), dB2(12.000), dB2(13.875), dB2(15.000), dB2(16.125), + dB2(16.875), dB2(17.625), dB2(18.000), dB2(18.750), dB2(19.125), dB2(19.500), + dB2(19.875), dB2(20.250), dB2(20.625), dB2(21.000)}; +#else +#define dB2x16(x) ((uint16_t)((x)*32)) +static int16_t kl_tablex16[16] = {dB2x16(0.000), dB2x16(9.000), dB2x16(12.000), dB2x16(13.875), dB2x16(15.000), + dB2x16(16.125), + dB2x16(16.875), dB2x16(17.625), dB2x16(18.000), dB2x16(18.750), dB2x16(19.125), + dB2x16(19.500), + dB2x16(19.875), dB2x16(20.250), dB2x16(20.625), dB2x16(21.000)}; +#endif +#endif + +#if !EMU8950_NO_TLL +static uint32_t tll_table[8 * 16][1 << TL_BITS][4]; +#endif +static int32_t rks_table[2][32][2]; + +#define min(i, j) (((i) < (j)) ? (i) : (j)) +#define max(i, j) (((i) > (j)) ? (i) : (j)) + +/*************************************************** + + Internal Sample Rate Converter + +****************************************************/ +/* Note: to disable internal rate converter, set clock/72 to output sampling rate. */ + +/* + * LW is truncate length of sinc(x) calculation. + * Lower LW is faster, higher LW results better quality. + * LW must be a non-zero positive even number, no upper limit. + * LW=16 or greater is recommended when upsampling. + * LW=8 is practically okay for downsampling. + */ +#define LW 16 + +#if !EMU8950_NO_RATECONV +/* resolution of sinc(x) table. sinc(x) where 0.0<=x<1.0 corresponds to sinc_table[0...SINC_RESO-1] */ +#define SINC_RESO 256 +#define SINC_AMP_BITS 12 + +// double hamming(double x) { return 0.54 - 0.46 * cos(2 * PI * x); } +static double blackman(double x) { return 0.42 - 0.5 * cos(2 * _PI_ * x) + 0.08 * cos(4 * _PI_ * x); } + +static double sinc(double x) { return (x == 0.0 ? 1.0 : sin(_PI_ * x) / (_PI_ * x)); } + +static double windowed_sinc(double x) { return blackman(0.5 + 0.5 * x / (LW / 2)) * sinc(x); } + +/* f_inp: input frequency. f_out: output frequencey, ch: number of channels */ +OPL_RateConv *OPL_RateConv_new(double f_inp, double f_out, int ch) { + OPL_RateConv *conv = malloc(sizeof(OPL_RateConv)); + int i; + + conv->ch = ch; + conv->f_ratio = f_inp / f_out; + conv->buf = malloc(sizeof(void *) * ch); + for (i = 0; i < ch; i++) { + conv->buf[i] = malloc(sizeof(conv->buf[0][0]) * LW); + } + + /* create sinc_table for positive 0 <= x < LW/2 */ + conv->sinc_table = malloc(sizeof(conv->sinc_table[0]) * SINC_RESO * LW / 2); + for (i = 0; i < SINC_RESO * LW / 2; i++) { + const double x = (double) i / SINC_RESO; + if (f_out < f_inp) { + /* for downsampling */ + conv->sinc_table[i] = (int16_t) ((1 << SINC_AMP_BITS) * windowed_sinc(x / conv->f_ratio) / conv->f_ratio); + } else { + /* for upsampling */ + conv->sinc_table[i] = (int16_t) ((1 << SINC_AMP_BITS) * windowed_sinc(x)); + } + } + + return conv; +} + +static INLINE int16_t lookup_sinc_table(int16_t *table, double x) { + int16_t index = (int16_t) (x * SINC_RESO); + if (index < 0) + index = -index; + return table[min(SINC_RESO * LW / 2 - 1, index)]; +} + +void OPL_RateConv_reset(OPL_RateConv *conv) { + int i; + conv->timer = 0; + for (i = 0; i < conv->ch; i++) { + memset(conv->buf[i], 0, sizeof(conv->buf[i][0]) * LW); + } +} + +/* put original data to this converter at f_inp. */ +void OPL_RateConv_putData(OPL_RateConv *conv, int ch, int16_t data) { + int16_t *buf = conv->buf[ch]; + int i; + for (i = 0; i < LW - 1; i++) { + buf[i] = buf[i + 1]; + } + buf[LW - 1] = data; +} + +/* get resampled data from this converter at f_out. */ +/* this function must be called f_out / f_inp times per one putData call. */ +int16_t OPL_RateConv_getData(OPL_RateConv *conv, int ch) { + int16_t *buf = conv->buf[ch]; + int32_t sum = 0; + int k; + double dn; + conv->timer += conv->f_ratio; + dn = conv->timer - floor(conv->timer); + conv->timer = dn; + + for (k = 0; k < LW; k++) { + double x = ((double) k - (LW / 2 - 1)) - dn; + sum += buf[k] * lookup_sinc_table(conv->sinc_table, x); + } + return sum >> SINC_AMP_BITS; +} + +void OPL_RateConv_delete(OPL_RateConv *conv) { + int i; + for (i = 0; i < conv->ch; i++) { + free(conv->buf[i]); + } + free(conv->buf); + free(conv->sinc_table); + free(conv); +} + +#endif + +/*************************************************** + + Create tables + +****************************************************/ +static void makeSinTable(void) { +#if !EMU8950_NO_WAVE_TABLE_MAP + int x; + + for (x = 0; x < PG_WIDTH; x++) { + if (x < PG_WIDTH / 4) { + wave_table_map[0][x] = logsin_table[x]; + } else if (x < PG_WIDTH / 2) { + wave_table_map[0][x] = logsin_table[PG_WIDTH / 2 - x - 1]; + } else { + wave_table_map[0][x] = 0x8000 | wave_table_map[0][PG_WIDTH - x - 1]; + } + } + + for (x = 0; x < PG_WIDTH; x++) { + if (x < PG_WIDTH / 2) { + wave_table_map[1][x] = wave_table_map[0][x]; + } else { + wave_table_map[1][x] = 0xfff; + } + } + + for (x = 0; x < PG_WIDTH; x++) { + if (x < PG_WIDTH / 2) { + wave_table_map[2][x] = wave_table_map[0][x]; + } else { + wave_table_map[2][x] = wave_table_map[0][x - PG_WIDTH / 2]; + } + } + + for (x = 0; x < PG_WIDTH; x++) { + if (x < PG_WIDTH / 4) { + wave_table_map[3][x] = wave_table_map[0][x]; + } else if (x < PG_WIDTH / 2) { + wave_table_map[3][x] = 0xfff; + } else if (x < PG_WIDTH * 3 / 4) { + wave_table_map[3][x] = wave_table_map[0][x - PG_WIDTH / 2]; + } else { + wave_table_map[3][x] = 0xfff; + } + } +#endif +} + +static void makeTllTable(void) { +#if !EMU8950_NO_TLL + int32_t tmp; + int32_t fnum, block, TL, KL, kx; + + for (fnum = 0; fnum < 16; fnum++) { + for (block = 0; block < 8; block++) { + for (TL = 0; TL < 64; TL++) { + for (KL = 0; KL < 4; KL++) { + kx = ((KL & 1) << 1) | ((KL >> 1) & 1); + if (KL == 0) { + tll_table[(block << 4) | fnum][TL][KL] = TL2EG(TL); + } else { +#if !EMU8950_NO_FLOAT + tmp = (int32_t)(kl_table[fnum] - dB2(3.000) * (7 - block)); + if (tmp <= 0) + tll_table[(block << 4) | fnum][TL][KL] = TL2EG(TL); + else + tll_table[(block << 4) | fnum][TL][KL] = (uint32_t)((tmp >> (3 - kx)) / EG_STEP) + TL2EG(TL); +#else + tmp = (int32_t)(kl_tablex16[fnum] - dB2x16(3.000) * (7 - block)); + if (tmp <= 0) + tll_table[(block << 4) | fnum][TL][KL] = TL2EG(TL); + else + tll_table[(block << 4) | fnum][TL][KL] = (uint32_t)((tmp >> (3 - kx)) / EG_STEPx16) + TL2EG(TL); +#endif + } + } + } + } + } +#endif +} + +static void makeRksTable(void) { + int fnum8, fnum9, blk; + int blk_fnum98; + for (fnum8 = 0; fnum8 < 2; fnum8++) + for (fnum9 = 0; fnum9 < 2; fnum9++) + for (blk = 0; blk < 8; blk++) { + blk_fnum98 = (blk << 2) | (fnum9 << 1) | fnum8; + rks_table[0][blk_fnum98][1] = (blk << 1) + fnum9; + rks_table[0][blk_fnum98][0] = blk >> 1; + rks_table[1][blk_fnum98][1] = (blk << 1) + (fnum9 & fnum8); + rks_table[1][blk_fnum98][0] = blk >> 1; + } +} + +static uint8_t table_initialized = 0; + +static void initializeTables() { + makeTllTable(); + makeRksTable(); + makeSinTable(); + table_initialized = 1; +} + +/********************************************************* + + Synthesizing + +*********************************************************/ +#define SLOT_BD1 12 +#define SLOT_BD2 13 +#define SLOT_HH 14 +#define SLOT_SD 15 +#define SLOT_TOM 16 +#define SLOT_CYM 17 + +/* utility macros */ +#define MOD(o, x) (&(o)->slot[(x) << 1]) +#define CAR(o, x) (&(o)->slot[((x) << 1) | 1]) +#define BIT(s, b) (((s) >> (b)) & 1) + +#if OPL_DEBUG +static void _debug_print_patch(OPL_SLOT *slot) { + OPL_PATCH *p = slot->patch; + printf("[slot#%d am:%d pm:%d eg:%d kr:%d ml:%d kl:%d tl:%d ws:%d fb:%d A:%d D:%d S:%d R:%d]\n", slot->number, // + p->AM, p->PM, p->EG, p->KR, p->ML, // + p->KL, p->TL, p->WS, p->FB, // + p->AR, p->DR, p->SL, p->RR); +} + +static char *_debug_eg_state_name(OPL_SLOT *slot) { + switch (slot->eg_state) { + case ATTACK: + return "attack"; + case DECAY: + return "decay"; + case SUSTAIN: + return "sustain"; + case RELEASE: + return "release"; + case DAMP: + return "damp"; + default: + return "unknown"; + } +} + +static INLINE void _debug_print_slot_info(OPL_SLOT *slot) { + char *name = _debug_eg_state_name(slot); + _debug_print_patch(slot); + printf("[slot#%d state:%s fnum:%03x rate:%d-%d]\n", slot->number, name, slot->blk_fnum, slot->eg_rate_h, + slot->eg_rate_l); + fflush(stdout); +} +#endif + +enum SLOT_UPDATE_FLAG +{ + UPDATE_WS = 1, + UPDATE_TLL = 2, + UPDATE_RKS = 4, + UPDATE_EG = 8, + UPDATE_ALL = 255, +}; + +static INLINE void request_update(OPL_SLOT *slot, int flag) { + slot->update_requests |= flag; +} + +static INLINE int get_parameter_rate(OPL_SLOT *slot) { + switch (slot->eg_state) { + case ATTACK: + return slot->patch->AR; + case DECAY: + return slot->patch->DR; + case SUSTAIN: + return slot->patch->EG ? 0 : slot->patch->RR; + case RELEASE: + return slot->patch->RR; + default: + return 0; + } +} + +static void commit_slot_update(OPL_SLOT *slot, uint8_t notesel) { + + if (slot->update_requests & UPDATE_WS) { +#if !EMU8950_NO_WAVE_TABLE_MAP + slot->wave_table = wave_table_map[slot->patch->WS & 3]; +#else +#if !EMU8950_SLOT_RENDER + slot->wav_or_table = wav_or_table_lookup[slot->patch->WS & 3]; +#endif +#endif + } + + if (slot->update_requests & UPDATE_TLL) { +#if !EMU8950_NO_TLL + if ((slot->type & 1) == 0) { + slot->tll = tll_table[slot->blk_fnum >> 6][slot->patch->TL][slot->patch->KL]; + } else { + slot->tll = tll_table[slot->blk_fnum >> 6][slot->patch->TL][slot->patch->KL]; + } +#else + static const uint8_t kslrom4[16] = { + 0 * 4, 32 * 4, 40 * 4, 45 * 4, 48 * 4, 51 * 4, 53 * 4, 55 * 4, 56 * 4, 58 * 4, 59 * 4, 60 * 4, 61 * 4, + 62 * 4, 63 * 4, 255 + }; + + int fnum = (slot->blk_fnum >> 6) & 15; + int block = (slot->blk_fnum >> 10); + int16_t ksl = kslrom4[fnum] - ((0x08 - block) << 5); + if (ksl < 0) { + slot->tll = slot->patch->TL4; + } else { + slot->tll = slot->patch->TL4 + (ksl >> slot->patch->KL_SHIFT); + } +#endif + } + + if (slot->update_requests & UPDATE_RKS) { + slot->rks = rks_table[notesel][slot->blk_fnum >> 8][slot->patch->KR]; + } + + if (slot->update_requests & (UPDATE_RKS | UPDATE_EG)) { + int p_rate = get_parameter_rate(slot); + + if (p_rate == 0) { + slot->eg_shift = 0; + slot->eg_rate_h = 0; + slot->eg_rate_l = 0; + } else { + slot->eg_rate_h = min(15, p_rate + (slot->rks >> 2)); + slot->eg_rate_l = slot->rks & 3; + if (slot->eg_state == ATTACK) { + slot->eg_shift = (0 < slot->eg_rate_h && slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0; + } else { + slot->eg_shift = (slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0; + } + } + } + +#if OPL_DEBUG + if (slot->last_eg_state != slot->eg_state) { + _debug_print_slot_info(slot); + slot->last_eg_state = slot->eg_state; + } +#endif + + slot->update_requests = 0; +} + +#if !EMU8950_SLOT_RENDER +static void commit_slot_update_eg_only(OPL_SLOT *slot, uint8_t notesel) { + assert(slot->update_requests == UPDATE_EG); + int p_rate = get_parameter_rate(slot); + + if (p_rate == 0) { + slot->eg_shift = 0; + slot->eg_rate_h = 0; + slot->eg_rate_l = 0; + } else { + slot->eg_rate_h = min(15, p_rate + (slot->rks >> 2)); + slot->eg_rate_l = slot->rks & 3; + if (slot->eg_state == ATTACK) { + slot->eg_shift = (0 < slot->eg_rate_h && slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0; + } else { + slot->eg_shift = (slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0; + } + } + slot->update_requests = 0; +} +#endif + +static void reset_slot(OPL_SLOT *slot, int number) { + slot->patch = &(slot->__patch); + memset(slot->patch, 0, sizeof(OPL_PATCH)); + slot->number = number; +#if !EMU8950_NO_PERCUSSION_MODE + slot->type = number % 2; +#endif +// slot->pg_keep = 0; +#if !EMU8950_NO_WAVE_TABLE_MAP + slot->wave_table = wave_table_map[0]; +#else +#if !EMU8950_SLOT_RENDER + slot->wav_or_table = wav_or_table_lookup[0]; +#endif +#endif +// slot->pg_phase = 0; +// slot->output[0] = 0; +// slot->output[1] = 0; + slot->eg_state = RELEASE; +// slot->eg_shift = 0; +// slot->rks = 0; +// slot->tll = 0; +// slot->blk_fnum = 0; +// slot->blk = 0; +// slot->fnum = 0; +// slot->pg_out = 0; + slot->eg_out = EG_MUTE; +} + +#if !EMU8950_NO_PERCUSSION_MODE +#define slot_pg_keep(slot) slot->pg_keep +#define opl_perc_mode(opl) opl->perc_mode +#else +#define slot_pg_keep(slot) 0 +#define opl_perc_mode(opl) 0 +#endif +static INLINE void slotOn(OPL *opl, int i) { + OPL_SLOT *slot = &opl->slot[i]; + if (min(15, slot->patch->AR + (slot->rks >> 2)) == 15) { + slot->eg_state = DECAY; + slot->eg_out = 0; + } else { + slot->eg_state = ATTACK; + } + if (!slot_pg_keep(slot)) { + slot->pg_phase = 0; + } + request_update(slot, UPDATE_EG); +} + +static INLINE void slotOff(OPL *opl, int i) { + OPL_SLOT *slot = &opl->slot[i]; + slot->eg_state = RELEASE; + request_update(slot, UPDATE_EG); +} + +static INLINE void update_key_status(OPL *opl) { + const uint8_t r14 = opl->reg[0xbd]; + const uint8_t perc_mode = BIT(r14, 5); + uint32_t new_slot_key_status = 0; + uint32_t updated_status; + int ch; + +#if !EMU8950_NO_TIMER + if (opl->csm_mode && opl->csm_key_count) { + new_slot_key_status = 0x3ffff; + } +#endif + + for (ch = 0; ch < 9; ch++) + if (opl->reg[0xB0 + ch] & 0x20) + new_slot_key_status |= 3 << (ch * 2); + + if (perc_mode) { + if (r14 & 0x10) + new_slot_key_status |= 3 << SLOT_BD1; + + if (r14 & 0x01) + new_slot_key_status |= 1 << SLOT_HH; + + if (r14 & 0x08) + new_slot_key_status |= 1 << SLOT_SD; + + if (r14 & 0x04) + new_slot_key_status |= 1 << SLOT_TOM; + + if (r14 & 0x02) + new_slot_key_status |= 1 << SLOT_CYM; + } + + updated_status = opl->slot_key_status ^ new_slot_key_status; + + if (updated_status) { + int i; + for (i = 0; i < 18; i++) + if (BIT(updated_status, i)) { + if (BIT(new_slot_key_status, i)) { + slotOn(opl, i); + } else { + slotOff(opl, i); + } + } + } + + opl->slot_key_status = new_slot_key_status; +} + +/* set f-Nnmber ( fnum : 10bit ) */ +static INLINE void set_fnumber(OPL *opl, int ch, int fnum) { + OPL_SLOT *car = CAR(opl, ch); + OPL_SLOT *mod = MOD(opl, ch); + car->fnum = fnum; + car->blk_fnum = (car->blk_fnum & 0x1c00) | (fnum & 0x3ff); + mod->fnum = fnum; + mod->blk_fnum = (mod->blk_fnum & 0x1c00) | (fnum & 0x3ff); + request_update(car, UPDATE_EG | UPDATE_RKS | UPDATE_TLL); + request_update(mod, UPDATE_EG | UPDATE_RKS | UPDATE_TLL); +} + +/* set block data (blk : 3bit ) */ +static INLINE void set_block(OPL *opl, int ch, int blk) { + OPL_SLOT *car = CAR(opl, ch); + OPL_SLOT *mod = MOD(opl, ch); + car->blk = blk; + car->blk_fnum = ((blk & 7) << 10) | (car->blk_fnum & 0x3ff); + mod->blk = blk; + mod->blk_fnum = ((blk & 7) << 10) | (mod->blk_fnum & 0x3ff); + request_update(car, UPDATE_EG | UPDATE_RKS | UPDATE_TLL); + request_update(mod, UPDATE_EG | UPDATE_RKS | UPDATE_TLL); +} + +static INLINE void update_perc_mode(OPL *opl) { +#if !EMU8950_NO_PERCUSSION_MODE + const uint8_t new_perc_mode = (opl->reg[0xbd] >> 5) & 1; + + if (opl->perc_mode != new_perc_mode) { + if (new_perc_mode) { + opl->slot[SLOT_HH].type = 3; + opl->slot[SLOT_HH].pg_keep = 1; + opl->slot[SLOT_SD].type = 3; + opl->slot[SLOT_TOM].type = 3; + opl->slot[SLOT_CYM].type = 3; + opl->slot[SLOT_CYM].pg_keep = 1; + } else { + opl->slot[SLOT_HH].type = 0; + opl->slot[SLOT_HH].pg_keep = 0; + opl->slot[SLOT_SD].type = 1; + opl->slot[SLOT_TOM].type = 0; + opl->slot[SLOT_CYM].type = 1; + opl->slot[SLOT_CYM].pg_keep = 0; + } + } + opl->perc_mode = new_perc_mode; +#else + assert(!((opl->reg[0xbd] >> 5) & 1)); // new_perc_mode should be 0 +#endif +} + +#if !EMU8950_LINEAR +static INLINE void update_ampm(OPL *opl) { +#if !EMU8950_NO_TEST_FLAG + const uint32_t pm_inc = (opl_test_flag(opl) & 8) ? opl->pm_dphase << 10 : opl->pm_dphase; + const uint32_t am_inc = opl_test_flag(opl) ? 64 : 1; + if (opl_test_flag(opl) & 2) { + opl->pm_phase = 0; + opl->am_phase = 0; + } else { + opl->pm_phase = (opl->pm_phase + pm_inc) & (PM_DP_WIDTH - 1); + opl->am_phase += am_inc; + } + opl->lfo_am = am_table[(opl->am_phase >> 6) % sizeof(am_table)] >> (opl->am_mode ? 0 : 2); +#else + opl->pm_phase = (opl->pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); + opl->am_phase_index++; + if (opl->am_phase_index == sizeof(am_table)) opl->am_phase_index = 0; + opl->lfo_am = am_table[opl->am_phase_index] >> (opl->am_mode ? 0 : 2); +#endif +} +static void update_noise(OPL *opl, int cycle) { +#if !EMU8950_SIMPLER_NOISE + int i; + for (i = 0; i < cycle; i++) { + if (opl->noise & 1) { + opl->noise ^= 0x800200; + } + opl->noise >>= 1; + } +#endif +} + +static int noise_bit(OPL *opl) { +#if !EMU8950_SIMPLER_NOISE + return opl->noise & 1; +#else + if (opl->noise & 1) { + opl->noise ^= 0x800200; + opl->noise >>= 1; + return 1; + } + opl->noise >>= 1; + return 0; +#endif +} + +static void update_short_noise(OPL *opl) { + const uint32_t pg_hh = opl->slot[SLOT_HH].pg_out; + const uint32_t pg_cym = opl->slot[SLOT_CYM].pg_out; + + const uint8_t h_bit2 = BIT(pg_hh, PG_BITS - 8); + const uint8_t h_bit7 = BIT(pg_hh, PG_BITS - 3); + const uint8_t h_bit3 = BIT(pg_hh, PG_BITS - 7); + + const uint8_t c_bit3 = BIT(pg_cym, PG_BITS - 7); + const uint8_t c_bit5 = BIT(pg_cym, PG_BITS - 5); + + opl->short_noise = (h_bit2 ^ h_bit7) | (h_bit3 ^ c_bit5) | (c_bit3 ^ c_bit5); +} +#endif + +#if !EMU8950_SLOT_RENDER +static INLINE void calc_phase(OPL_SLOT *slot, int32_t pm_phase, uint8_t pm_mode, uint8_t reset) { + int8_t pm = 0; + if (slot->patch->PM) { + pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; + pm >>= (pm_mode ? 0 : 1); + } + + if (reset) { + slot->pg_phase = 0; + } + slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; + slot->pg_phase &= (DP_WIDTH - 1); + slot->pg_out = slot->pg_phase >> DP_BASE_BITS; +} + +static INLINE uint8_t lookup_attack_step(OPL_SLOT *slot, uint32_t counter) { + int index = (counter >> slot->eg_shift) & 7; + switch (slot->eg_rate_h) { + case 13: + return eg_step_tables_fast[slot->eg_rate_l][index]; + case 14: + return eg_step_tables_fast[slot->eg_rate_l][index] << 1; + case 0: + case 15: + return 0; + default: + return eg_step_tables[slot->eg_rate_l][index]; + } +} + +static INLINE uint8_t lookup_decay_step(OPL_SLOT *slot, uint32_t counter) { + int index = (counter >> slot->eg_shift) & 7; + switch (slot->eg_rate_h) { + case 0: + return 0; + case 13: + return eg_step_tables_fast[slot->eg_rate_l][index]; + case 14: + return eg_step_tables_fast[slot->eg_rate_l][index] << 1; + case 15: + return 4; + default: + return eg_step_tables[slot->eg_rate_l][index]; + } +} + +static INLINE void calc_envelope(OPL_SLOT *slot, uint16_t eg_counter, uint8_t test) { + + uint16_t mask = (1 << slot->eg_shift) - 1; + uint8_t step; + + if (slot->eg_state == ATTACK) { + if (0 < slot->eg_out && slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { + step = lookup_attack_step(slot, eg_counter); + slot->eg_out += (~slot->eg_out * step) >> 3; + } + } else { + if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { + slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); + } + } + + switch (slot->eg_state) { + case ATTACK: + if (slot->eg_out == 0) { + slot->eg_state = DECAY; + request_update(slot, UPDATE_EG); + } + break; + + case DECAY: + if ((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL) { + slot->eg_state = SUSTAIN; + request_update(slot, UPDATE_EG); + } + break; + + case SUSTAIN: + case RELEASE: + default: + break; + } + + if (test) { + slot->eg_out = 0; + } +} +#endif + +#if !EMU8950_LINEAR +static void update_slots(OPL *opl) { + int i; + opl->eg_counter++; + + for (i = 0; i < 18; i++) { + OPL_SLOT *slot = &opl->slot[i]; + if (slot->update_requests) { + commit_slot_update(slot, opl->notesel); + } + calc_envelope(slot, opl->eg_counter, opl_test_flag(opl) & 1); + calc_phase(slot, opl->pm_phase, opl->pm_mode, opl_test_flag(opl) & 4); + } +} + +#endif +/* input: 0..8191 output: -4095..4095 */ +static int16_t lookup_exp_table(int16_t i) { + /* from andete's expressoin */ + int16_t t = (exp_table[(i & 0xffu)] + 1024); + int16_t res = t >> ((i & 0x7f00) >> 8); +#if EMU8950_LINEAR_NEG_NOT_NOT + return ((i & 0x8000) ? -res : res) << 1; +#else + return ((i & 0x8000) ? ~res : res) << 1; +#endif +} + +static INLINE int16_t to_linear(uint16_t h, OPL_SLOT *slot, int16_t am) { + uint16_t att; + if (slot->eg_out >= EG_MAX) { + return 0; + } + + att = min(EG_MUTE, (slot->eg_out + slot->tll + am)) << 3; + return lookup_exp_table(h + att); +} + +#define LOGSIN_MASK (PG_WIDTH/4 - 1) +#define LOGSIN_MASK2 (PG_WIDTH/2 - 1) + +//static INLINE uint16_t get_wave_table(OPL_SLOT *slot, uint32_t index) { +static uint16_t get_wave_table(OPL_SLOT *slot, uint32_t index) { +#if !EMU8950_NO_WAVE_TABLE_MAP + return slot->wave_table[index]; +#else +#if !EMU8950_SLOT_RENDER + return slot->wav_or_table[(index >> (PG_BITS - 2))&3] | logsin_table[(index & LOGSIN_MASK2)]; +#else + assert(0); + return 0; +#endif +#if 0 + switch (((index >> (PG_BITS - 4))&0xc) | (slot->patch->WS & 3)) { + case 0b0000: + case 0b0001: + case 0b0010: + case 0b0011: + case 0b1010: + case 0b1011: + return logsin_table[index & LOGSIN_MASK]; + case 0b0100: + case 0b0101: + case 0b0110: + case 0b1110: + return logsin_table[LOGSIN_MASK - (index & LOGSIN_MASK)]; + case 0b1000: + return 0x8000 | logsin_table[index & LOGSIN_MASK]; + case 0b1100: + return 0x8000 | logsin_table[LOGSIN_MASK - (index & LOGSIN_MASK)]; + default: + return 0xfff; + } +#endif +#endif +} + +static INLINE uint16_t get_wave_table_wrap(OPL_SLOT *slot, uint32_t index) { +#if !EMU8950_NO_WAVE_TABLE_MAP + return get_wave_table(slot, index & (PG_WIDTH - 1)); +#else + return get_wave_table(slot, index); +#endif +} + +static INLINE int16_t calc_slot_car(OPL *opl, int ch, int16_t fm) { + OPL_SLOT *slot = CAR(opl, ch); + + uint8_t am = slot->patch->AM ? opl->lfo_am : 0; + + slot->output[1] = slot->output[0]; + slot->output[0] = to_linear(get_wave_table_wrap(slot, slot->pg_out + 2 * (fm >> 1)), slot, am); + + return slot->output[0]; +} + +static INLINE int16_t calc_slot_mod(OPL *opl, int ch) { + OPL_SLOT *slot = MOD(opl, ch); + + int16_t fm = slot->patch->FB > 0 ? (slot->output[1] + slot->output[0]) >> (9 - slot->patch->FB) : 0; + uint8_t am = slot->patch->AM ? opl->lfo_am : 0; + + slot->output[1] = slot->output[0]; + slot->output[0] = to_linear(get_wave_table_wrap(slot, slot->pg_out + fm), slot, am); + + return slot->output[0]; +} + +/* Specify phase offset directly based on 10-bit (1024-length) sine table */ +#define _PD(phase) ((PG_BITS < 10) ? (phase >> (10 - PG_BITS)) : (phase << (PG_BITS - 10))) + +#if !EMU8950_NO_PERCUSSION_MODE +static INLINE int16_t calc_slot_tom(OPL *opl) { + OPL_SLOT *slot = &(opl->slot[SLOT_TOM]); + + return to_linear(get_wave_table(slot, slot->pg_out), slot, 0); +} + + +static INLINE int16_t calc_slot_snare(OPL *opl) { + OPL_SLOT *slot = &(opl->slot[SLOT_SD]); + + uint32_t phase; + + if (BIT(opl->slot[SLOT_HH].pg_out, PG_BITS - 2)) + phase = noise_bit(opl) ? _PD(0x300) : _PD(0x200); + else + phase = noise_bit(opl) ? _PD(0x0) : _PD(0x100); + + return to_linear(get_wave_table(slot, phase), slot, 0); +} + +static INLINE int16_t calc_slot_cym(OPL *opl) { + OPL_SLOT *slot = &(opl->slot[SLOT_CYM]); + + uint32_t phase = opl->short_noise ? _PD(0x300) : _PD(0x100); + + return to_linear(get_wave_table(slot, phase), slot, 0); +} + +static INLINE int16_t calc_slot_hat(OPL *opl) { + OPL_SLOT *slot = &(opl->slot[SLOT_HH]); + + uint32_t phase; + + if (opl->short_noise) + phase = noise_bit(opl) ? _PD(0x2d0) : _PD(0x234); + else + phase = noise_bit(opl) ? _PD(0x34) : _PD(0xd0); + + return to_linear(get_wave_table(slot, phase), slot, 0); +} +#endif + +#define _MO(x) (-(x) >> 1) +#define _RO(x) (x) + +static INLINE int16_t calc_fm(OPL *opl, int ch) { + if (opl->ch_alg[ch]) { + return calc_slot_car(opl, ch, 0) + calc_slot_mod(opl, ch); + } + return calc_slot_car(opl, ch, calc_slot_mod(opl, ch)); +} + +#if !EMU8950_NO_TIMER +static void latch_timer1(OPL *opl) { opl->timer1_counter = opl->reg[0x02] << 2; } + +static void latch_timer2(OPL *opl) { opl->timer2_counter = opl->reg[0x03] << 4; } + +static void csm_key_on(OPL *opl) { + opl->csm_key_count = 1; + update_key_status(opl); +} + +static void csm_key_off(OPL *opl) { + opl->csm_key_count = 0; + update_key_status(opl); +} + +static void update_timer(OPL *opl) { + if (opl->csm_mode && 0 < opl->csm_key_count) { + csm_key_off(opl); + } + + if (opl->reg[0x04] & 0x01) { + opl->timer1_counter++; + if (opl->timer1_counter >> 10) { + opl->status |= 0x40; // timer1 overflow + if (opl->csm_mode) { + csm_key_on(opl); + } + if (opl->timer1_func) { + opl->timer1_func(opl->timer1_user_data); + } + latch_timer1(opl); + } + } + + if (opl->reg[0x04] & 0x02) { + opl->timer2_counter++; + if (opl->timer2_counter >> 12) { + opl->status |= 0x20; // timer2 overflow + if (opl->timer2_func) { + opl->timer2_func(opl->timer2_user_data); + } + latch_timer2(opl); + } + } +} +#endif + +#if !EMU8950_LINEAR +static void update_output(OPL *opl) { + int16_t *out; + int i; + +#if !EMU8950_NO_TIMER + update_timer(opl); +#endif + // generate amplitude modulation same for all channels + // need am_phase and lfo_am + update_ampm(opl); +#if EMU8950_SHORT_NOISE_UPDATE_CHECK + if (opl->mask & (OPL_MASK_CYM | OPL_MASK_HH)) + update_short_noise(opl); +#else + update_short_noise(opl); +#endif + update_slots(opl); + + out = opl->ch_out; + + /* CH1-6 */ + for (i = 0; i < 6; i++) { + if (!(opl->mask & OPL_MASK_CH(i))) { + out[i] = _MO(calc_fm(opl, i)); + } + } + + /* CH7 */ + if (!opl_perc_mode(opl)) { + if (!(opl->mask & OPL_MASK_CH(6))) { + out[6] = _MO(calc_fm(opl, 6)); + } + } else { + if (!(opl->mask & OPL_MASK_BD)) { + out[9] = _RO(calc_fm(opl, 6)); + } + } + update_noise(opl, 14); + + /* CH8 */ + if (!opl_perc_mode(opl)) { + if (!(opl->mask & OPL_MASK_CH(7))) { + out[7] = _MO(calc_fm(opl, 7)); + } + } else { + if (!(opl->mask & OPL_MASK_HH)) { + out[10] = _RO(calc_slot_hat(opl)); + } + if (!(opl->mask & OPL_MASK_SD)) { + out[11] = _RO(calc_slot_snare(opl)); + } + } + update_noise(opl, 2); + + /* CH9 */ + if (!opl_perc_mode(opl)) { + if (!(opl->mask & OPL_MASK_CH(8))) { + out[8] = _MO(calc_fm(opl, 8)); + } + } else { + if (!(opl->mask & OPL_MASK_TOM)) { + out[12] = _RO(calc_slot_tom(opl)); + } + if (!(opl->mask & OPL_MASK_CYM)) { + out[13] = _RO(calc_slot_cym(opl)); + } + } + update_noise(opl, 2); + +} + +INLINE static void mix_output(OPL *opl) { + int16_t out = 0; + int i; + for (i = 0; i < 15; i++) { + out += opl->ch_out[i]; + } +#if !EMU8950_NO_RATECONV + if (opl->conv) { + OPL_RateConv_putData(opl->conv, 0, out); + } else { + opl->mix_out[0] = out; + } +#else + opl->mix_out[0] = out; +#endif +} + +INLINE static int16_t mix_output_raw(OPL *opl) { + int32_t out = 0; + +#if !EMU8950_NO_PERCUSSION_MODE + for (int i = 0; i < 15; i++) { + out += opl->ch_out[i]; + } +#else + for (int i = 0; i < 9; i++) { + out += opl->ch_out[i]; + } +#endif + + return out; +} +#endif + +/*********************************************************** + + External Interfaces + +***********************************************************/ + +OPL *OPL_new(uint32_t clk, uint32_t rate) { + OPL *opl; + + if (!table_initialized) { + initializeTables(); + } + + opl = (OPL *) calloc(sizeof(OPL), 1); + if (opl == NULL) + return NULL; + + opl->clk = clk; + opl->rate = rate; +// opl->mask = 0; +#if !EMU8950_NO_RATECONV +// opl->conv = NULL; +#endif +// opl->mix_out[0] = 0; +// opl->mix_out[1] = 0; +#if !EMU8950_NO_TIMER +// opl->timer1_func = NULL; +// opl->timer1_user_data = NULL; +// opl->timer2_func = NULL; +// opl->timer2_user_data = NULL; +#endif + + OPL_reset(opl); + + return opl; +} + +void OPL_delete(OPL *opl) { +#if !EMU8950_NO_RATECONV + if (opl->conv) { + OPL_RateConv_delete(opl->conv); + opl->conv = NULL; + } +#endif + free(opl); +} + +static void reset_rate_conversion_params(OPL *opl) { +#if !EMU8950_NO_RATECONV + const double f_out = opl->rate; + const double f_inp = opl->clk / 72; + + opl->out_time = 0; + opl->out_step = ((uint32_t) f_inp) << 8; + opl->inp_step = ((uint32_t) f_out) << 8; + + if (opl->conv) { + OPL_RateConv_delete(opl->conv); + opl->conv = NULL; + } + + if (floor(f_inp) != f_out && floor(f_inp + 0.5) != f_out) { + opl->conv = OPL_RateConv_new(f_inp, f_out, 2); + } + + if (opl->conv) { + OPL_RateConv_reset(opl->conv); + } +#endif +} + +void OPL_reset(OPL *opl) { + int i; + + if (!opl) + return; + +#if EMU8950_NO_RATECONV + // no useful fields to preserve + memset(opl, 0, sizeof(*opl)); +#else + // some fields are not reset + opl->adr = 0; + opl->notesel = 0; + + opl->status = 0; + + opl->csm_mode = 0; + opl->csm_key_count = 0; + opl->timer1_counter = 0; + opl->timer2_counter = 0; + + opl->pm_phase = 0; + opl->am_phase = 0; + + opl->mask = 0; + + opl->perc_mode = 0; + opl->slot_key_status = 0; + opl->eg_counter = 0; +#endif + +#if !EMU8950_NO_PERCUSSION_MODE + opl->noise = 1; +#endif + + reset_rate_conversion_params(opl); + + for (i = 0; i < 18; i++) { + reset_slot(&opl->slot[i], i); + } + +// for (i = 0; i < 9; i++) { +// opl->ch_alg[i] = 0; +// } + +// for (i = 0; i < 0x100; i++) { +// opl->reg[i] = 0; +// } + opl->reg[0x04] = 0x18; // MASK_EOS | MASK_BUF_RDY + opl->pm_dphase = PM_DPHASE; + +// for (i = 0; i < 15; i++) { +// opl->ch_out[i] = 0; +// } + +} + +void OPL_setRate(OPL *opl, uint32_t rate) { + opl->rate = rate; + reset_rate_conversion_params(opl); +} + +void OPL_setQuality(OPL *opl, uint8_t q) {} + +void OPL_setPan(OPL *opl, uint32_t ch, uint8_t pan) { opl->pan[ch & 15] = pan; } + +#if !EMU8950_LINEAR +int16_t OPL_calc(OPL *opl) { + while (opl->out_step > opl->out_time) { + opl->out_time += opl->inp_step; + update_output(opl); + mix_output(opl); + } + opl->out_time -= opl->out_step; +#if !EMU8950_NO_RATECONV + if (opl->conv) { + opl->mix_out[0] = OPL_RateConv_getData(opl->conv, 0); + } +#endif + return opl->mix_out[0]; +} + +void OPL_calc_buffer(OPL *opl, int16_t *buffer, uint32_t nsamples) { + assert(opl->out_step == opl->inp_step); + for (unsigned i = 0; i < nsamples; i++) { + update_output(opl); + buffer[i] = mix_output_raw(opl); + } +} +#endif + +#if PICO_ON_DEVICE +#include "hardware/gpio.h" +#endif +#if !LIB_PICO_PLATFORM +#define __not_in_flash_func(x) x +#endif + +#if EMU8950_LINEAR + +// these return number of samples rendered (can early out due to silence on the slot - not necessarily the channel) +uint32_t slot_mod_linear(OPL *opl, OPL_SLOT *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase); +uint32_t slot_car_linear_alg0(OPL *opl, OPL_SLOT *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase); +uint32_t slot_car_linear_alg1(OPL *opl, OPL_SLOT *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase); + +#if DUMPO +int hack_ch; +#endif +#if !EMU8950_SLOT_RENDER +uint32_t __not_in_flash_func(slot_mod_linear)(OPL *opl, OPL_SLOT *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { + uint32_t s = 0; + uint32_t nsamples_bak = nsamples; + if (slot->eg_state == ATTACK) { + for (; s < nsamples; s++) { + eg_counter++; + pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); + + uint16_t mask = (1 << slot->eg_shift) - 1; + if (0 < slot->eg_out && slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { + uint8_t step = lookup_attack_step(slot, eg_counter); + slot->eg_out += (~slot->eg_out * step) >> 3; + } + if (slot->eg_out == 0) { + slot->eg_state = DECAY; + // todo collapse + request_update(slot, UPDATE_EG); + commit_slot_update_eg_only(slot, opl->notesel); + nsamples = s; + } + + int8_t pm = 0; + if (slot->patch->PM) { + pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; + pm >>= (opl->pm_mode ? 0 : 1); + } + slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; + slot->pg_phase &= (DP_WIDTH - 1); + uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; + + int16_t fm = slot->patch->FB > 0 ? (slot->output[1] + slot->output[0]) >> (9 - slot->patch->FB) : 0; + slot->output[1] = slot->output[0]; + + uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; + opl->mod_buffer[s] = slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out + fm), slot, am); + } + } + if (slot->eg_state == DECAY) { + nsamples = nsamples_bak; + // todo if note ends we can stop early + for (; s < nsamples; s++) { + eg_counter++; + pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); + uint16_t mask = (1 << slot->eg_shift) - 1; + + if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { + slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); + // todo check for decay to zero + } + + if ((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL) { + slot->eg_state = SUSTAIN; + request_update(slot, UPDATE_EG); + commit_slot_update_eg_only(slot, opl->notesel); + nsamples = s; + } + + int8_t pm = 0; + if (slot->patch->PM) { + pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; + pm >>= (opl->pm_mode ? 0 : 1); + } + slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; + slot->pg_phase &= (DP_WIDTH - 1); + uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; + + int16_t fm = slot->patch->FB > 0 ? (slot->output[1] + slot->output[0]) >> (9 - slot->patch->FB) : 0; + slot->output[1] = slot->output[0]; + + uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; + opl->mod_buffer[s] = slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out + fm), slot, am); + } + } + if (slot->eg_state == SUSTAIN || slot->eg_state == RELEASE) { + nsamples = nsamples_bak; + for (; s < nsamples; s++) { + eg_counter++; + pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); + uint16_t mask = (1 << slot->eg_shift) - 1; + + if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { + slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); + } + int8_t pm = 0; + if (slot->patch->PM) { + pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; + pm >>= (opl->pm_mode ? 0 : 1); + } + slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; + slot->pg_phase &= (DP_WIDTH - 1); + uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; + + int16_t fm = slot->patch->FB > 0 ? (slot->output[1] + slot->output[0]) >> (9 - slot->patch->FB) : 0; + slot->output[1] = slot->output[0]; + + uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; + opl->mod_buffer[s] = slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out + fm), slot, am); + } + } + return nsamples; +} + +uint32_t __not_in_flash_func(slot_car_linear_alg1)(OPL *opl, OPL_SLOT *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { + uint32_t s = 0; + uint32_t nsamples_bak = nsamples; + if (slot->eg_state == ATTACK) { + for (; s < nsamples; s++) { + eg_counter++; + pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); + + uint16_t mask = (1 << slot->eg_shift) - 1; + if (0 < slot->eg_out && slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { + uint8_t step = lookup_attack_step(slot, eg_counter); + slot->eg_out += (~slot->eg_out * step) >> 3; + } + if (slot->eg_out == 0) { + slot->eg_state = DECAY; + // todo collapse + request_update(slot, UPDATE_EG); + commit_slot_update_eg_only(slot, opl->notesel); + nsamples = s; + } + + int8_t pm = 0; + if (slot->patch->PM) { + pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; + pm >>= (opl->pm_mode ? 0 : 1); + } + slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; + slot->pg_phase &= (DP_WIDTH - 1); + uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; + + uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; + slot->output[1] = slot->output[0]; + slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out), slot, am); + opl->buffer[s] += slot->output[0] + opl->mod_buffer[s]; + } + } + if (slot->eg_state == DECAY) { + nsamples = nsamples_bak; + // todo if note ends we can stop early + for (; s < nsamples; s++) { + eg_counter++; + pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); + uint16_t mask = (1 << slot->eg_shift) - 1; + + if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { + slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); + // todo check for decay to zero + } + + if ((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL) { + slot->eg_state = SUSTAIN; + request_update(slot, UPDATE_EG); + commit_slot_update_eg_only(slot, opl->notesel); + nsamples = s; + } + + int8_t pm = 0; + if (slot->patch->PM) { + pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; + pm >>= (opl->pm_mode ? 0 : 1); + } + slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; + slot->pg_phase &= (DP_WIDTH - 1); + uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; + + uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; + slot->output[1] = slot->output[0]; + slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out), slot, am); + opl->buffer[s] += slot->output[0] + opl->mod_buffer[s]; + } + } + if (slot->eg_state == SUSTAIN || slot->eg_state == RELEASE) { + nsamples = nsamples_bak; + for (; s < nsamples; s++) { + eg_counter++; + pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); + uint16_t mask = (1 << slot->eg_shift) - 1; + + if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { + slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); + } + int8_t pm = 0; + if (slot->patch->PM) { + pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; + pm >>= (opl->pm_mode ? 0 : 1); + } + slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; + slot->pg_phase &= (DP_WIDTH - 1); + uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; + + uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; + slot->output[1] = slot->output[0]; + slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out), slot, am); + opl->buffer[s] += slot->output[0] + opl->mod_buffer[s]; + } + } + return nsamples; +} + +uint32_t __not_in_flash_func(slot_car_linear_alg0)(OPL *opl, OPL_SLOT *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { + uint32_t s = 0; + uint32_t nsamples_bak = nsamples; + if (slot->eg_state == ATTACK) { + for (; s < nsamples; s++) { + eg_counter++; + pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); + + uint16_t mask = (1 << slot->eg_shift) - 1; + if (0 < slot->eg_out && slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { + uint8_t step = lookup_attack_step(slot, eg_counter); + slot->eg_out += (~slot->eg_out * step) >> 3; + } + if (slot->eg_out == 0) { + slot->eg_state = DECAY; + // todo collapse + request_update(slot, UPDATE_EG); + commit_slot_update_eg_only(slot, opl->notesel); + nsamples = s; + } + + int8_t pm = 0; + if (slot->patch->PM) { + pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; + pm >>= (opl->pm_mode ? 0 : 1); + } + slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; + slot->pg_phase &= (DP_WIDTH - 1); + uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; + + uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; + slot->output[1] = slot->output[0]; + + // todo is this masking realy necessary; i doubt it. .. seems to be always even anyway +// int32_t fm = 2 * (opl->mod_buffer[s] >> 1); + int32_t fm = opl->mod_buffer[s]; + + slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out + fm), slot, am); + opl->buffer[s] += slot->output[0]; + } + } + if (slot->eg_state == DECAY) { + nsamples = nsamples_bak; + // todo if note ends we can stop early + for (; s < nsamples; s++) { + if (hack_ch == 0 && s == 12) { + breako(); + } + eg_counter++; + pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); + uint16_t mask = (1 << slot->eg_shift) - 1; + + if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { + slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); + // todo check for decay to zero + } + + if ((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL) { + slot->eg_state = SUSTAIN; + request_update(slot, UPDATE_EG); + commit_slot_update_eg_only(slot, opl->notesel); + nsamples = s; + } + + int8_t pm = 0; + if (slot->patch->PM) { + pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; + pm >>= (opl->pm_mode ? 0 : 1); + } + slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; + slot->pg_phase &= (DP_WIDTH - 1); + uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; + + uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; + slot->output[1] = slot->output[0]; + + // todo is this masking realy necessary; i doubt it. .. seems to be always even anyway +// int32_t fm = 2 * (opl->mod_buffer[s] >> 1); + int32_t fm = opl->mod_buffer[s]; + + slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out + fm), slot, am); + opl->buffer[s] += slot->output[0]; + } + } + if (slot->eg_state == SUSTAIN || slot->eg_state == RELEASE) { + nsamples = nsamples_bak; + for (; s < nsamples; s++) { + if (hack_ch == 0 && s == 12) { + breako(); + } + eg_counter++; + pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); + uint16_t mask = (1 << slot->eg_shift) - 1; + + if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { + slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); + } + int8_t pm = 0; + if (slot->patch->PM) { + pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; + pm >>= (opl->pm_mode ? 0 : 1); + } + slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; + slot->pg_phase &= (DP_WIDTH - 1); + uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; + + uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; + slot->output[1] = slot->output[0]; + + // todo is this masking realy necessary; i doubt it. .. seems to be always even anyway +// int32_t fm = 2 * (opl->mod_buffer[s] >> 1); + int32_t fm = opl->mod_buffer[s]; + + slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out + fm), slot, am); + opl->buffer[s] += slot->output[0]; + } + } + return nsamples; +} +#endif + +static_assert(EMU8950_NO_PERCUSSION_MODE, ""); +// this produces stereo +void OPL_calc_buffer_linear(OPL *opl, int32_t *buffer, uint32_t nsamples) { + int i; +#if EMU8950_SLOT_RENDER + // kind of a nit pick, but so cheap - saves a bug every 24 hours due to an optimization + // (we require that incrementing eg_counter is never zero during the rendering loop) + opl->eg_counter = (opl->eg_counter & 0x3fffffffu) | 0x80000000u; + static uint8_t lfo_am_buffer_lsl3[SAMPLE_BUF_SIZE]; + assert(nsamples <= sizeof(lfo_am_buffer_lsl3)); + opl->lfo_am_buffer_lsl3 = lfo_am_buffer_lsl3; +#else + static uint8_t lfo_am_buffer[SAMPLE_BUF_SIZE]; + assert(nsamples <= sizeof(lfo_am_buffer)); + opl->lfo_am_buffer = lfo_am_buffer; +#endif + static int16_t mod_buffer[SAMPLE_BUF_SIZE]; + + opl->mod_buffer = mod_buffer; + opl->buffer = buffer; + + // todo achievable by memcpy + for(uint32_t s = 0; sam_phase_index++; + if (opl->am_phase_index == sizeof(am_table)) opl->am_phase_index = 0; + // todo this is a candidate for remove simply because it is not super noticeable without + +#if EMU8950_SLOT_RENDER + // note <<3 still fits within 8 bits + lfo_am_buffer_lsl3[s] = (am_table[opl->am_phase_index] >> (opl->am_mode ? 0 : 2)) << 3; +#else + lfo_am_buffer[s] = (am_table[opl->am_phase_index] >> (opl->am_mode ? 0 : 2)); +#endif + buffer[s]=0; + } + + for (i = 0; i < 18; i++) { + OPL_SLOT *slot = &opl->slot[i]; + int ch = i >> 1; +#if DUMPO + hack_ch = ch; + if (ch == 8 && bc == 1) { + printf("HRMPH\n"); + } +#endif + if (slot->update_requests) { + commit_slot_update(slot, opl->notesel); + } +#if EMU8950_SLOT_RENDER + slot->lfo_am_buffer_lsl3 = opl->lfo_am_buffer_lsl3; + slot->pm_mode = opl->pm_mode; + slot->mod_buffer = opl->mod_buffer; +#endif + if (!(i & 1)) { + // ---- MOD SLOT ---- + if ((slot+1)->eg_out >= EG_MUTE && (slot+1)->eg_state != ATTACK && !opl->ch_alg[ch]) { +#if DUMPO + memset(slot_output[i], 0, nsamples*2); + memset(slot_output[i+1], 0, nsamples*2); +#endif + i++; + continue; + } + + uint32_t s_mod; +#if EMU8950_LINEAR_SKIP // todo consider disabling as almost unnecessary with EMU8950_LINEAR_END_OF_NOTE_OPTIMIZATION + if (slot->eg_out >= EG_MUTE && slot->eg_state != ATTACK) { + s_mod = 0; + } else +#endif + { +// printf("MOD %d\n", i); +// if (bc == 7 && i == 14) { +// printf("WAM\n"); +// } + s_mod = slot_mod_linear(opl, slot, nsamples, opl->eg_counter, opl->pm_phase); + } + if (s_mod != nsamples) { + memset(opl->mod_buffer + s_mod, 0, (nsamples - s_mod) * 2); + } +#if DUMPO + memcpy(slot_output[i], opl->mod_buffer, nsamples * 2); +#endif + } else { +#if EMU8950_LINEAR_SKIP +#if EMU8950_SLOT_RENDER + slot->buffer = opl->buffer; +#endif + uint32_t s_alg; +#if EMU8950_LINEAR_SKIP // todo consider disabling as almost unnecessary with EMU8950_LINEAR_END_OF_NOTE_OPTIMIZATION + if (slot->eg_out >= EG_MUTE && slot->eg_state != ATTACK) { + s_alg = 0; + } else +#endif +#if DUMPO + memcpy(opl_buffer_bak, opl->buffer, nsamples * 4); + memset(opl->buffer, 0, nsamples * 4); +#endif + { + if (opl->ch_alg[ch]) { + s_alg = slot_car_linear_alg1(opl, slot, nsamples, opl->eg_counter, opl->pm_phase); + } else { + s_alg = slot_car_linear_alg0(opl, slot, nsamples, opl->eg_counter, opl->pm_phase); + } + } + if (s_alg != nsamples) { + if (opl->ch_alg[ch]) { + for (uint32_t s = s_alg; s < nsamples; s++) { + opl->buffer[s] += opl->mod_buffer[s]; + } + } + } +#if DUMPO + for(uint s = 0; s < nsamples; s++) { + slot_output[i][s] = opl->buffer[s]; + opl->buffer[s] += opl_buffer_bak[s]; + } +#endif +#endif + } + } + opl->pm_phase = (opl->pm_phase + opl->pm_dphase * nsamples) & (PM_DP_WIDTH - 1); + opl->eg_counter += nsamples; + +} +#endif + +void OPL_calc_buffer_stereo(OPL *opl, int32_t *buffer, uint32_t nsamples) { + assert(opl->out_step == opl->inp_step); +#if DUMPO + bc++; +#endif +#if !EMU8950_LINEAR + for (unsigned i = 0; i < nsamples; i++) { + update_output(opl); + uint16_t raw = mix_output_raw(opl); +#if DUMPO + printf("SND %d %d %08x : ", bc, i, raw); + for (int i = 0; i < 9; i++) { + printf("%04x ", (uint16_t) opl->ch_out[i]); + } + printf("\n"); +#endif + + buffer[i] = (raw << 16u) | raw; + } +#else + OPL_calc_buffer_linear(opl, buffer, nsamples); +// if (0) + for (unsigned i = 0; i < nsamples; i++) { +#if DUMPO + uint16_t raw = _MO(buffer[i]); + printf("SND %d %d %08x : ", bc, i, raw); + for (int j = 0; j < 18; j++) { + printf("%04x ", (uint16_t) slot_output[j][i]); + } + printf("\n"); +#else + uint16_t raw = buffer[i] >> 1; // _MO() +#endif + // todo clamp? + buffer[i] = (raw << 16u) | raw; + } +#endif +} + +void OPL_writeReg(OPL *opl, uint32_t reg, uint8_t data) { + +// printf("WR %04x %2x\n", reg, data); + int32_t s, c; + + static int32_t stbl[32] = {0, 2, 4, 1, 3, 5, -1, -1, 6, 8, 10, 7, 9, 11, -1, -1, + 12, 14, 16, 13, 15, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; + + reg = reg & 0xff; + + if ((reg == 0x04) && (data & 0x80)) { + // IRQ RESET + opl->status = 0; + opl->reg[0x04] &= 0x7f; + return; + } + + opl->reg[reg] = data; + + if (reg == 0x01) { +#if !EMU8950_NO_TEST_FLAG + opl->test_flag = data; +#endif + } else if (reg == 0x04) { + + if (data & 0x01) { +#if !EMU8950_NO_TIMER + latch_timer1(opl); +#else + printf("WARNING TIMER1 LATCH\n"); +#endif + } + if (data & 0x02) { +#if !EMU8950_NO_TIMER + latch_timer2(opl); +#else + printf("WARNING TIMER2 LATCH\n"); +#endif + } + + } else if (0x07 <= reg && reg <= 0x12) { + + if (reg == 0x08) { +#if !EMU8950_NO_TIMER + opl->csm_mode = (data >> 7) & 1; +#else + if ((data >> 7) & 1) { + printf("WARNING SET CSM MODE\n"); + } +#endif + opl->notesel = (data >> 6) & 1; + } + + } else if (0x20 <= reg && reg < 0x40) { + + s = stbl[reg - 0x20]; + if (s >= 0) { + opl->slot[s].patch->AM = (data >> 7) & 1; + opl->slot[s].patch->PM = (data >> 6) & 1; + opl->slot[s].patch->EG = (data >> 5) & 1; + opl->slot[s].patch->KR = (data >> 4) & 1; + opl->slot[s].patch->ML = (data) & 15; + request_update(&(opl->slot[s]), UPDATE_ALL); + } + + } else if (0x40 <= reg && reg < 0x60) { + + s = stbl[reg - 0x40]; + if (s >= 0) { +#if !EMU8950_NO_TLL + opl->slot[s].patch->TL = (data)&63; + opl->slot[s].patch->KL = (data >> 6) & 3; +#else + opl->slot[s].patch->TL4 = data << 2; + static const uint8_t kslshift[4] = { + 8, 1, 2, 0 + }; + opl->slot[s].patch->KL_SHIFT = kslshift[(data >> 6) & 3]; +#endif + request_update(&(opl->slot[s]), UPDATE_ALL); + } + + } else if (0x60 <= reg && reg < 0x80) { + + s = stbl[reg - 0x60]; + if (s >= 0) { + opl->slot[s].patch->AR = (data >> 4) & 15; + opl->slot[s].patch->DR = (data) & 15; + request_update(&(opl->slot[s]), UPDATE_EG); + } + + } else if (0x80 <= reg && reg < 0xa0) { + + s = stbl[reg - 0x80]; + if (s >= 0) { + opl->slot[s].patch->SL = (data >> 4) & 15; + opl->slot[s].patch->RR = (data) & 15; + request_update(&(opl->slot[s]), UPDATE_EG); + } + + } else if (0xa0 <= reg && reg < 0xa9) { + + c = reg - 0xa0; + set_fnumber(opl, c, data + ((opl->reg[reg + 0x10] & 3) << 8)); + + } else if (0xb0 <= reg && reg < 0xb9) { + + c = reg - 0xb0; + set_fnumber(opl, c, ((data & 3) << 8) + opl->reg[reg - 0x10]); + set_block(opl, c, (data >> 2) & 7); + update_key_status(opl); + + } else if (0xc0 <= reg && reg < 0xc9) { + + c = reg - 0xc0; + opl->slot[c * 2].patch->FB = (data >> 1) & 7; + opl->ch_alg[c] = data & 1; + + } else if (reg == 0xbd) { + + update_perc_mode(opl); + update_key_status(opl); + opl->am_mode = (data >> 7) & 1; + opl->pm_mode = (data >> 6) & 1; + + } else if (0xe0 <= reg && reg < 0x100) { + if (opl->reg[0x01] & 0x20) { + s = stbl[reg - 0xe0]; + if (s >= 0) { + opl->slot[s].patch->WS = data & 3; + request_update(&(opl->slot[s]), UPDATE_WS); + } + } + } +} +#endif \ No newline at end of file diff --git a/opl/emu8950.h b/opl/emu8950.h new file mode 100644 index 00000000..62c911f9 --- /dev/null +++ b/opl/emu8950.h @@ -0,0 +1,229 @@ +#ifndef _EMU8950_H_ +#define _EMU8950_H_ + +#include +#include "slot_render.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define OPL_DEBUG 0 + +/* mask */ +#define OPL_MASK_CH(x) (1 << (x)) +#define OPL_MASK_HH (1 << 9) +#define OPL_MASK_CYM (1 << 10) +#define OPL_MASK_TOM (1 << 11) +#define OPL_MASK_SD (1 << 12) +#define OPL_MASK_BD (1 << 13) +#define OPL_MASK_ADPCM (1 << 14) +#define OPL_MASK_RHYTHM (OPL_MASK_HH | OPL_MASK_CYM | OPL_MASK_TOM | OPL_MASK_SD | OPL_MASK_BD) + +#if !EMU8950_NO_RATECONV +/* rate conveter */ +typedef struct __OPL_RateConv { + int ch; + double timer; + double f_ratio; + int16_t *sinc_table; + int16_t **buf; +} OPL_RateConv; + +OPL_RateConv *OPL_RateConv_new(double f_inp, double f_out, int ch); +void OPL_RateConv_reset(OPL_RateConv *conv); +void OPL_RateConv_putData(OPL_RateConv *conv, int ch, int16_t data); +int16_t OPL_RateConv_getData(OPL_RateConv *conv, int ch); +void OPL_RateConv_delete(OPL_RateConv *conv); +#endif +/* slot */ +typedef struct __OPL_SLOT { + struct SLOT_RENDER; + uint8_t number; + +#if !EMU8950_NO_PERCUSSION_MODE // only use was to set based on percussion mode + /* type flags: + * 000000SM + * |+-- M: 0:modulator 1:carrier + * +--- S: 0:normal 1:single slot mode (sd, tom, hh or cym) + */ + uint8_t type; +#endif + + OPL_PATCH __patch; + + /* phase generator (pg) */ + uint32_t pg_out; /* pg output, as index of wave table */ +#if !EMU8950_NO_PERCUSSION_MODE + uint8_t pg_keep; /* if 1, pg_phase is preserved when key-on */ +#endif + uint16_t blk_fnum; /* (block << 9) | f-number */ + + uint32_t update_requests; /* flags to debounce update */ + +#if OPL_DEBUG + uint8_t last_eg_state; +#endif +} OPL_SLOT; + +typedef struct __OPL { + uint32_t clk; + uint32_t rate; + +#if !EMU8950_NO_TIMER + uint8_t csm_mode; + uint8_t csm_key_count; +#endif + uint8_t notesel; + + uint32_t inp_step; + uint32_t out_step; + uint32_t out_time; + +#if EMU8950_LINEAR +#if EMU8950_SLOT_RENDER + uint8_t *lfo_am_buffer_lsl3; +#else + uint8_t *lfo_am_buffer; +#endif + int16_t *mod_buffer; + int32_t *buffer; +#endif +#if !EMU8950_NO_TEST_FLAG + uint8_t test_flag; +#endif + uint32_t slot_key_status; +#if !EMU8950_NO_PERCUSSION_MODE + uint8_t perc_mode; +#endif + + uint32_t eg_counter; + + uint32_t pm_phase; + uint32_t pm_dphase; + +#if !EMU8950_NO_TEST_FLAG + int32_t am_phase; +#else + uint8_t am_phase_index; +#endif + uint8_t lfo_am; + +#if !EMU8950_NO_PERCUSSION_MODE + uint32_t noise; + uint8_t short_noise; +#endif + + uint8_t reg[0x100]; + uint8_t ch_alg[9]; // alg for each channels + + uint8_t pan[16]; + + uint32_t mask; + uint8_t am_mode; + uint8_t pm_mode; + + /* channel output */ + /* 0..8:tone 9:bd 10:hh 11:sd 12:tom 13:cym 14:adpcm */ + int16_t ch_out[15]; + + int16_t mix_out[2]; + + OPL_SLOT slot[18]; + +#if !EMU8950_NO_RATECONV + OPL_RateConv *conv; +#endif + +#if !EMU8950_NO_TIMER + uint32_t timer1_counter; // 80us counter + uint32_t timer2_counter; // 320us counter + void *timer1_user_data; + void *timer2_user_data; + void (*timer1_func)(void *user); + void (*timer2_func)(void *user); +#endif + uint8_t status; + +} OPL; + +#if !EMU8950_NO_TEST_FLAG +#define opl_test_flag(opl) opl->test_flag +#else +// waveform enable only +#define opl_test_flag(opl) 0x20 +#endif + +OPL *OPL_new(uint32_t clk, uint32_t rate); +void OPL_delete(OPL *); + +void OPL_reset(OPL *); + +/** + * Set output wave sampling rate. + * @param rate sampling rate. If clock / 72 (typically 49716 or 49715 at 3.58MHz) is set, the internal rate converter is disabled. + */ +void OPL_setRate(OPL *opl, uint32_t rate); + +/** + * Set internal calcuration quality. Currently no effects, just for compatibility. + * >= v1.0.0 always synthesizes internal output at clock/72 Hz. + */ +void OPL_setQuality(OPL *opl, uint8_t q); + +/** + * Set fine-grained panning + * @param ch 0..8:tone 9:bd 10:hh 11:sd 12:tom 13:cym 14,15:reserved + * @param pan output strength of left/right channel. + * pan[0]: left, pan[1]: right. pan[0]=pan[1]=1.0f for center. + */ +void OPL_setPanFine(OPL *opl, uint32_t ch, float pan[2]); + +void OPL_writeIO(OPL *opl, uint32_t reg, uint8_t val); +void OPL_writeReg(OPL *opl, uint32_t reg, uint8_t val); + +/** + * Calculate sample + */ +int16_t OPL_calc(OPL *opl); + +void OPL_calc_buffer(OPL *opl, int16_t *buffer, uint32_t nsamples); +// LE left/right channels int16:int16 +void OPL_calc_buffer_stereo(OPL *opl, int32_t *buffer, uint32_t nsamples); + +/** + * Set channel mask + * @param mask mask flag: OPL_MASK_* can be used. + * - bit 0..8: mask for ch 1 to 9 (OPL_MASK_CH(i)) + * - bit 9: mask for Hi-Hat (OPL_MASK_HH) + * - bit 10: mask for Top-Cym (OPL_MASK_CYM) + * - bit 11: mask for Tom (OPL_MASK_TOM) + * - bit 12: mask for Snare Drum (OPL_MASK_SD) + * - bit 13: mask for Bass Drum (OPL_MASK_BD) + */ +uint32_t OPL_setMask(OPL *, uint32_t mask); + +/** + * Read OPL status register + * @returns + * 76543210 + * ||||| +- D0: PCM/BSY + * ||||+---- D3: BUF/RDY + * |||+----- D4: EOS + * ||+------ D5: TIMER2 + * |+------- D6: TIMER1 + * +-------- D7: IRQ + */ +uint8_t OPL_status(OPL *opl); + +/* for compatibility */ +#define OPL_set_rate OPL_setRate +#define OPL_set_quality OPL_setQuality +#define OPL_set_pan OPL_setPan +#define OPL_set_pan_fine OPL_setPanFine + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/opl/opl.c b/opl/opl.c index 0d256891..d39bec67 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -20,435 +21,12 @@ #include #include -#include "SDL.h" - #include "opl.h" #include "opl_internal.h" -//#define OPL_DEBUG_TRACE +#include "SDL.h" -#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM) -extern opl_driver_t opl_linux_driver; -#endif -#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64) -extern opl_driver_t opl_openbsd_driver; -#endif -#ifdef _WIN32 -extern opl_driver_t opl_win32_driver; -#endif -extern opl_driver_t opl_sdl_driver; - -static opl_driver_t *drivers[] = -{ -#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM) - &opl_linux_driver, -#endif -#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64) - &opl_openbsd_driver, -#endif -#ifdef _WIN32 - &opl_win32_driver, -#endif - &opl_sdl_driver, - NULL -}; - -static opl_driver_t *driver = NULL; -static int init_stage_reg_writes = 1; - -unsigned int opl_sample_rate = 22050; - -// -// Init/shutdown code. -// - -// Initialize the specified driver and detect an OPL chip. Returns -// true if an OPL is detected. - -static opl_init_result_t InitDriver(opl_driver_t *_driver, - unsigned int port_base) -{ - opl_init_result_t result1, result2; - - // Initialize the driver. - - if (!_driver->init_func(port_base)) - { - return OPL_INIT_NONE; - } - - // The driver was initialized okay, so we now have somewhere - // to write to. It doesn't mean there's an OPL chip there, - // though. Perform the detection sequence to make sure. - // (it's done twice, like how Doom does it). - - driver = _driver; - init_stage_reg_writes = 1; - - result1 = OPL_Detect(); - result2 = OPL_Detect(); - if (result1 == OPL_INIT_NONE || result2 == OPL_INIT_NONE) - { - printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name); - _driver->shutdown_func(); - driver = NULL; - return OPL_INIT_NONE; - } - - init_stage_reg_writes = 0; - - printf("OPL_Init: Using driver '%s'.\n", driver->name); - - return result2; -} - -// Find a driver automatically by trying each in the list. - -static opl_init_result_t AutoSelectDriver(unsigned int port_base) -{ - int i; - opl_init_result_t result; - - for (i=0; drivers[i] != NULL; ++i) - { - result = InitDriver(drivers[i], port_base); - if (result != OPL_INIT_NONE) - { - return result; - } - } - - printf("OPL_Init: Failed to find a working driver.\n"); - - return OPL_INIT_NONE; -} - -// Initialize the OPL library. Return value indicates type of OPL chip -// detected, if any. - -opl_init_result_t OPL_Init(unsigned int port_base) -{ - char *driver_name; - int i; - int result; - - driver_name = getenv("OPL_DRIVER"); - - if (driver_name != NULL) - { - // Search the list until we find the driver with this name. - - for (i=0; drivers[i] != NULL; ++i) - { - if (!strcmp(driver_name, drivers[i]->name)) - { - result = InitDriver(drivers[i], port_base); - if (result) - { - return result; - } - else - { - printf("OPL_Init: Failed to initialize " - "driver: '%s'.\n", driver_name); - return OPL_INIT_NONE; - } - } - } - - printf("OPL_Init: unknown driver: '%s'.\n", driver_name); - - return OPL_INIT_NONE; - } - else - { - return AutoSelectDriver(port_base); - } -} - -// Shut down the OPL library. - -void OPL_Shutdown(void) -{ - if (driver != NULL) - { - driver->shutdown_func(); - driver = NULL; - } -} - -// Set the sample rate used for software OPL emulation. - -void OPL_SetSampleRate(unsigned int rate) -{ - opl_sample_rate = rate; -} - -void OPL_WritePort(opl_port_t port, unsigned int value) -{ - if (driver != NULL) - { -#ifdef OPL_DEBUG_TRACE - printf("OPL_write: %i, %x\n", port, value); - fflush(stdout); -#endif - driver->write_port_func(port, value); - } -} - -unsigned int OPL_ReadPort(opl_port_t port) -{ - if (driver != NULL) - { - unsigned int result; - -#ifdef OPL_DEBUG_TRACE - printf("OPL_read: %i...\n", port); - fflush(stdout); -#endif - - result = driver->read_port_func(port); - -#ifdef OPL_DEBUG_TRACE - printf("OPL_read: %i -> %x\n", port, result); - fflush(stdout); -#endif - - return result; - } - else - { - return 0; - } -} - -// -// Higher-level functions, based on the lower-level functions above -// (register write, etc). -// - -unsigned int OPL_ReadStatus(void) -{ - return OPL_ReadPort(OPL_REGISTER_PORT); -} - -// Write an OPL register value - -void OPL_WriteRegister(int reg, int value) -{ - int i; - - if (reg & 0x100) - { - OPL_WritePort(OPL_REGISTER_PORT_OPL3, reg); - } - else - { - OPL_WritePort(OPL_REGISTER_PORT, reg); - } - - // For timing, read the register port six times after writing the - // register number to cause the appropriate delay - - for (i=0; i<6; ++i) - { - // An oddity of the Doom OPL code: at startup initialization, - // the spacing here is performed by reading from the register - // port; after initialization, the data port is read, instead. - - if (init_stage_reg_writes) - { - OPL_ReadPort(OPL_REGISTER_PORT); - } - else - { - OPL_ReadPort(OPL_DATA_PORT); - } - } - - OPL_WritePort(OPL_DATA_PORT, value); - - // Read the register port 24 times after writing the value to - // cause the appropriate delay - - for (i=0; i<24; ++i) - { - OPL_ReadStatus(); - } -} - -// Detect the presence of an OPL chip - -opl_init_result_t OPL_Detect(void) -{ - int result1, result2; - int i; - - // Reset both timers: - OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60); - - // Enable interrupts: - OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); - - // Read status - result1 = OPL_ReadStatus(); - - // Set timer: - OPL_WriteRegister(OPL_REG_TIMER1, 0xff); - - // Start timer 1: - OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x21); - - // Wait for 80 microseconds - // This is how Doom does it: - - for (i=0; i<200; ++i) - { - OPL_ReadStatus(); - } - - OPL_Delay(1 * OPL_MS); - - // Read status - result2 = OPL_ReadStatus(); - - // Reset both timers: - OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60); - - // Enable interrupts: - OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); - - if ((result1 & 0xe0) == 0x00 && (result2 & 0xe0) == 0xc0) - { - result1 = OPL_ReadPort(OPL_REGISTER_PORT); - result2 = OPL_ReadPort(OPL_REGISTER_PORT_OPL3); - if (result1 == 0x00) - { - return OPL_INIT_OPL3; - } - else - { - return OPL_INIT_OPL2; - } - } - else - { - return OPL_INIT_NONE; - } -} - -// Initialize registers on startup - -void OPL_InitRegisters(int opl3) -{ - int r; - - // Initialize level registers - - for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) - { - OPL_WriteRegister(r, 0x3f); - } - - // Initialize other registers - // These two loops write to registers that actually don't exist, - // but this is what Doom does ... - // Similarly, the <= is also intenational. - - for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r) - { - OPL_WriteRegister(r, 0x00); - } - - // More registers ... - - for (r=1; r < OPL_REGS_LEVEL; ++r) - { - OPL_WriteRegister(r, 0x00); - } - - // Re-initialize the low registers: - - // Reset both timers and enable interrupts: - OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60); - OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); - - // "Allow FM chips to control the waveform of each operator": - OPL_WriteRegister(OPL_REG_WAVEFORM_ENABLE, 0x20); - - if (opl3) - { - OPL_WriteRegister(OPL_REG_NEW, 0x01); - - // Initialize level registers - - for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) - { - OPL_WriteRegister(r | 0x100, 0x3f); - } - - // Initialize other registers - // These two loops write to registers that actually don't exist, - // but this is what Doom does ... - // Similarly, the <= is also intenational. - - for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r) - { - OPL_WriteRegister(r | 0x100, 0x00); - } - - // More registers ... - - for (r=1; r < OPL_REGS_LEVEL; ++r) - { - OPL_WriteRegister(r | 0x100, 0x00); - } - } - - // Keyboard split point on (?) - OPL_WriteRegister(OPL_REG_FM_MODE, 0x40); - - if (opl3) - { - OPL_WriteRegister(OPL_REG_NEW, 0x01); - } -} - -// -// Timer functions. -// - -void OPL_SetCallback(uint64_t us, opl_callback_t callback, void *data) -{ - if (driver != NULL) - { - driver->set_callback_func(us, callback, data); - } -} - -void OPL_ClearCallbacks(void) -{ - if (driver != NULL) - { - driver->clear_callbacks_func(); - } -} - -void OPL_Lock(void) -{ - if (driver != NULL) - { - driver->lock_func(); - } -} - -void OPL_Unlock(void) -{ - if (driver != NULL) - { - driver->unlock_func(); - } -} +extern opl_driver_t *driver; typedef struct { @@ -504,20 +82,3 @@ void OPL_Delay(uint64_t us) SDL_DestroyMutex(delay_data.mutex); SDL_DestroyCond(delay_data.cond); } - -void OPL_SetPaused(int paused) -{ - if (driver != NULL) - { - driver->set_paused_func(paused); - } -} - -void OPL_AdjustCallbacks(float value) -{ - if (driver != NULL) - { - driver->adjust_callbacks_func(value); - } -} - diff --git a/opl/opl.h b/opl/opl.h index deaa442d..4bb447a9 100644 --- a/opl/opl.h +++ b/opl/opl.h @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -126,7 +127,7 @@ void OPL_SetCallback(uint64_t us, opl_callback_t callback, void *data); // Adjust callback times by the specified factor. For example, a value of // 0.5 will halve all remaining times. -void OPL_AdjustCallbacks(float factor); +void OPL_AdjustCallbacks(unsigned int old_tempo, unsigned int new_tempo); // Clear all OPL callbacks that have been set. diff --git a/opl/opl3.h b/opl/opl3.h index 2a64037a..33fb598a 100644 --- a/opl/opl3.h +++ b/opl/opl3.h @@ -1,5 +1,6 @@ // // Copyright (C) 2013-2018 Alexey Khokholov (Nuke.YKT) +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -49,6 +50,7 @@ typedef struct _opl3_slot opl3_slot; typedef struct _opl3_channel opl3_channel; typedef struct _opl3_chip opl3_chip; + struct _opl3_slot { opl3_channel *channel; opl3_chip *chip; diff --git a/opl/opl_api.c b/opl/opl_api.c new file mode 100644 index 00000000..aeb9f450 --- /dev/null +++ b/opl/opl_api.c @@ -0,0 +1,483 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// OPL interface. +// + +#include "config.h" + +#include +#include +#include + +#include "opl.h" +#include "opl_internal.h" + +//#define OPL_DEBUG_TRACE + +#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM) +extern opl_driver_t opl_linux_driver; +#endif +#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64) +extern opl_driver_t opl_openbsd_driver; +#endif +#ifdef _WIN32 +extern opl_driver_t opl_win32_driver; +#endif +#if PICO_BUILD +extern opl_driver_t opl_pico_driver; +#else +extern opl_driver_t opl_sdl_driver; +#endif + +static const opl_driver_t *drivers[] = + { +#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM) + &opl_linux_driver, +#endif +#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64) + &opl_openbsd_driver, +#endif +#ifdef _WIN32 + &opl_win32_driver, +#endif +#if PICO_BUILD + &opl_pico_driver, +#else + &opl_sdl_driver, +#endif + NULL + }; + +const opl_driver_t *driver = NULL; +static int init_stage_reg_writes = 1; + +unsigned int opl_sample_rate = 22050; + +// +// Init/shutdown code. +// + +// Initialize the specified driver and detect an OPL chip. Returns +// true if an OPL is detected. + +static opl_init_result_t InitDriver(const opl_driver_t *_driver, + unsigned int port_base) +{ + opl_init_result_t result1, result2; + + // Initialize the driver. + + if (!_driver->init_func(port_base)) + { + return OPL_INIT_NONE; + } + + // The driver was initialized okay, so we now have somewhere + // to write to. It doesn't mean there's an OPL chip there, + // though. Perform the detection sequence to make sure. + // (it's done twice, like how Doom does it). + + driver = _driver; + init_stage_reg_writes = 1; + + result1 = OPL_Detect(); + result2 = OPL_Detect(); + if (result1 == OPL_INIT_NONE || result2 == OPL_INIT_NONE) + { + printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name); + _driver->shutdown_func(); + driver = NULL; + return OPL_INIT_NONE; + } + + init_stage_reg_writes = 0; + + printf("OPL_Init: Using driver '%s'.\n", driver->name); + + return result2; +} + +// Find a driver automatically by trying each in the list. + +static opl_init_result_t AutoSelectDriver(unsigned int port_base) +{ + int i; + opl_init_result_t result; + + for (i=0; drivers[i] != NULL; ++i) + { + result = InitDriver(drivers[i], port_base); + if (result != OPL_INIT_NONE) + { + return result; + } + } + + printf("OPL_Init: Failed to find a working driver.\n"); + + return OPL_INIT_NONE; +} + +// Initialize the OPL library. Return value indicates type of OPL chip +// detected, if any. + +opl_init_result_t OPL_Init(unsigned int port_base) +{ +#if PICO_BUILD + driver = drivers[0]; + driver->init_func(0); + return OPL_INIT_OPL2; +#else + char *driver_name; + int i; + int result; + + driver_name = getenv("OPL_DRIVER"); + + if (driver_name != NULL) + { + // Search the list until we find the driver with this name. + + for (i=0; drivers[i] != NULL; ++i) + { + if (!strcmp(driver_name, drivers[i]->name)) + { + result = InitDriver(drivers[i], port_base); + if (result) + { + return result; + } + else + { + printf("OPL_Init: Failed to initialize " + "driver: '%s'.\n", driver_name); + return OPL_INIT_NONE; + } + } + } + + printf("OPL_Init: unknown driver: '%s'.\n", driver_name); + + return OPL_INIT_NONE; + } + else + { + return AutoSelectDriver(port_base); + } +#endif +} + +// Shut down the OPL library. + +void OPL_Shutdown(void) +{ + if (driver != NULL) + { + driver->shutdown_func(); + driver = NULL; + } +} + +// Set the sample rate used for software OPL emulation. + +void OPL_SetSampleRate(unsigned int rate) +{ + opl_sample_rate = rate; +} + +void OPL_WritePort(opl_port_t port, unsigned int value) +{ + if (driver != NULL) + { +#ifdef OPL_DEBUG_TRACE + printf("OPL_write: %i, %x\n", port, value); + fflush(stdout); +#endif + driver->write_port_func(port, value); + } +} + +unsigned int OPL_ReadPort(opl_port_t port) +{ + if (driver != NULL) + { + unsigned int result; + +#ifdef OPL_DEBUG_TRACE + printf("OPL_read: %i...\n", port); + fflush(stdout); +#endif + + result = driver->read_port_func(port); + +#ifdef OPL_DEBUG_TRACE + printf("OPL_read: %i -> %x\n", port, result); + fflush(stdout); +#endif + + return result; + } + else + { + return 0; + } +} + +// +// Higher-level functions, based on the lower-level functions above +// (register write, etc). +// + +unsigned int OPL_ReadStatus(void) +{ + return OPL_ReadPort(OPL_REGISTER_PORT); +} + +// Write an OPL register value + +void OPL_WriteRegister(int reg, int value) +{ + int i; + + if (reg & 0x100) + { + OPL_WritePort(OPL_REGISTER_PORT_OPL3, reg); + } + else + { + OPL_WritePort(OPL_REGISTER_PORT, reg); + } + + // For timing, read the register port six times after writing the + // register number to cause the appropriate delay + + for (i=0; i<6; ++i) + { + // An oddity of the Doom OPL code: at startup initialization, + // the spacing here is performed by reading from the register + // port; after initialization, the data port is read, instead. + + if (init_stage_reg_writes) + { + OPL_ReadPort(OPL_REGISTER_PORT); + } + else + { + OPL_ReadPort(OPL_DATA_PORT); + } + } + + OPL_WritePort(OPL_DATA_PORT, value); + + // Read the register port 24 times after writing the value to + // cause the appropriate delay + + for (i=0; i<24; ++i) + { + OPL_ReadStatus(); + } +} + +// Detect the presence of an OPL chip + +opl_init_result_t OPL_Detect(void) +{ + int result1, result2; + int i; + + // Reset both timers: + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60); + + // Enable interrupts: + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); + + // Read status + result1 = OPL_ReadStatus(); + + // Set timer: + OPL_WriteRegister(OPL_REG_TIMER1, 0xff); + + // Start timer 1: + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x21); + + // Wait for 80 microseconds + // This is how Doom does it: + + for (i=0; i<200; ++i) + { + OPL_ReadStatus(); + } + + OPL_Delay(1 * OPL_MS); + + // Read status + result2 = OPL_ReadStatus(); + + // Reset both timers: + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60); + + // Enable interrupts: + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); + + if ((result1 & 0xe0) == 0x00 && (result2 & 0xe0) == 0xc0) + { + result1 = OPL_ReadPort(OPL_REGISTER_PORT); + result2 = OPL_ReadPort(OPL_REGISTER_PORT_OPL3); + if (result1 == 0x00) + { + return OPL_INIT_OPL3; + } + else + { + return OPL_INIT_OPL2; + } + } + else + { + return OPL_INIT_NONE; + } +} + +// Initialize registers on startup + +void OPL_InitRegisters(int opl3) +{ + int r; + + // Initialize level registers + + for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) + { + OPL_WriteRegister(r, 0x3f); + } + + // Initialize other registers + // These two loops write to registers that actually don't exist, + // but this is what Doom does ... + // Similarly, the <= is also intenational. + + for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r) + { + OPL_WriteRegister(r, 0x00); + } + + // More registers ... + + for (r=1; r < OPL_REGS_LEVEL; ++r) + { + OPL_WriteRegister(r, 0x00); + } + + // Re-initialize the low registers: + + // Reset both timers and enable interrupts: + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60); + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); + + // "Allow FM chips to control the waveform of each operator": + OPL_WriteRegister(OPL_REG_WAVEFORM_ENABLE, 0x20); + + if (opl3) + { + OPL_WriteRegister(OPL_REG_NEW, 0x01); + + // Initialize level registers + + for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) + { + OPL_WriteRegister(r | 0x100, 0x3f); + } + + // Initialize other registers + // These two loops write to registers that actually don't exist, + // but this is what Doom does ... + // Similarly, the <= is also intenational. + + for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r) + { + OPL_WriteRegister(r | 0x100, 0x00); + } + + // More registers ... + + for (r=1; r < OPL_REGS_LEVEL; ++r) + { + OPL_WriteRegister(r | 0x100, 0x00); + } + } + + // Keyboard split point on (?) + OPL_WriteRegister(OPL_REG_FM_MODE, 0x40); + + if (opl3) + { + OPL_WriteRegister(OPL_REG_NEW, 0x01); + } +} + +// +// Timer functions. +// + +void OPL_SetCallback(uint64_t us, opl_callback_t callback, void *data) +{ + if (driver != NULL) + { + driver->set_callback_func(us, callback, data); + } +} + +void OPL_ClearCallbacks(void) +{ + if (driver != NULL) + { + driver->clear_callbacks_func(); + } +} + +void OPL_Lock(void) +{ + if (driver != NULL) + { + driver->lock_func(); + } +} + +void OPL_Unlock(void) +{ + if (driver != NULL) + { + driver->unlock_func(); + } +} + + +void OPL_SetPaused(int paused) +{ + if (driver != NULL) + { + driver->set_paused_func(paused); + } +} + +void OPL_AdjustCallbacks(unsigned int old_tempo, unsigned int new_tempo) +{ + if (driver != NULL) + { + driver->adjust_callbacks_func(old_tempo, new_tempo); + } +} + diff --git a/opl/opl_internal.h b/opl/opl_internal.h index c67dbff4..10aacd09 100644 --- a/opl/opl_internal.h +++ b/opl/opl_internal.h @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -32,7 +33,7 @@ typedef void (*opl_clear_callbacks_func)(void); typedef void (*opl_lock_func)(void); typedef void (*opl_unlock_func)(void); typedef void (*opl_set_paused_func)(int paused); -typedef void (*opl_adjust_callbacks_func)(float value); +typedef void (*opl_adjust_callbacks_func)(unsigned int old_tempo, unsigned int new_tempo); typedef struct { diff --git a/opl/opl_linux.c b/opl/opl_linux.c index 19e4c3e3..68e5cf53 100644 --- a/opl/opl_linux.c +++ b/opl/opl_linux.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -37,12 +38,12 @@ static int OPL_Linux_Init(unsigned int port_base) if (ioperm(port_base, 2, 1) < 0) { - fprintf(stderr, "Failed to get I/O port permissions for 0x%x: %s\n", + stderr_print( "Failed to get I/O port permissions for 0x%x: %s\n", port_base, strerror(errno)); if (errno == EPERM) { - fprintf(stderr, + stderr_print( "\tYou may need to run the program as root in order\n" "\tto acquire I/O port permissions for OPL MIDI playback.\n"); } diff --git a/opl/opl_obsd.c b/opl/opl_obsd.c index 39e0c156..a6ef4633 100644 --- a/opl/opl_obsd.c +++ b/opl/opl_obsd.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -60,7 +61,7 @@ static int OPL_OpenBSD_Init(unsigned int port_base) if (set_iopl(3) < 0) { - fprintf(stderr, "Failed to get raise I/O privilege level: " + stderr_print( "Failed to get raise I/O privilege level: " "check that you are running as root.\n"); return 0; } diff --git a/opl/opl_pico.c b/opl/opl_pico.c new file mode 100644 index 00000000..eb3ee0e6 --- /dev/null +++ b/opl/opl_pico.c @@ -0,0 +1,605 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// OPL SDL interface. +// + +// todo replace opl_queue with pheap +#include "config.h" + +#include +#include +#include +#include + +#include "pico/mutex.h" +#include "pico/audio_i2s.h" +#include "pico/util/pheap.h" +#include "hardware/gpio.h" +#include "i_picosound.h" + +#if USE_WOODY_OPL +#include "woody_opl.h" +#elif USE_EMU8950_OPL +#include "emu8950.h" +#else +#include "opl3.h" +#endif + +#include "opl.h" +#include "opl_internal.h" + +#include "opl_queue.h" + +#define MAX_SOUND_SLICE_TIME 100 /* ms */ + +typedef struct +{ + unsigned int rate; // Number of times the timer is advanced per sec. + unsigned int enabled; // Non-zero if timer is enabled. + unsigned int value; // Last value that was set. + uint64_t expire_time; // Calculated time that timer will expire. +} opl_timer_t; + +// When the callback mutex is locked using OPL_Lock, callback functions +// are not invoked. + +static mutex_t callback_mutex; + +// Queue of callbacks waiting to be invoked. + +static opl_callback_queue_t *callback_queue; + +// Mutex used to control access to the callback queue. + +static mutex_t callback_queue_mutex; + +// Current time, in us since startup: + +static uint64_t current_time; + +// If non-zero, playback is currently paused. + +static int opl_pico_paused; + +// Time offset (in us) due to the fact that callbacks +// were previously paused. + +static uint64_t pause_offset; + +// OPL software emulator structure. + +#if USE_WOODY_OPL +// todo configure this based on woody build flag +#define opl_op3mode 0 +#elif USE_EMU8950_OPL +#define opl_op3mode 0 +static OPL *emu8950_opl; +#else +static opl3_chip opl_chip; +static int opl_opl3mode; +#endif + +// Register number that was written. + +static int register_num = 0; + +#if !EMU8950_NO_TIMER +// Timers; DBOPL does not do timer stuff itself. + +static opl_timer_t timer1 = { 12500, 0, 0, 0 }; +static opl_timer_t timer2 = { 3125, 0, 0, 0 }; +#endif + +// SDL parameters. + +static bool audio_was_initialized = 0; + +static inline void Pico_LockMutex(mutex_t *mutex) { + +} + +static inline void Pico_UnlockMutex(mutex_t *mutex) { + +} + +// Advance time by the specified number of samples, invoking any +// callback functions as appropriate. + +static void AdvanceTime(unsigned int nsamples) +{ + opl_callback_t callback; + void *callback_data; + uint64_t us; + + Pico_LockMutex(&callback_queue_mutex); + + // Advance time. + + us = ((uint64_t) nsamples * OPL_SECOND) / PICO_SOUND_SAMPLE_FREQ; + current_time += us; + + if (opl_pico_paused) + { + pause_offset += us; + } + + // Are there callbacks to invoke now? Keep invoking them + // until there are no more left. + + while (!OPL_Queue_IsEmpty(callback_queue) + && current_time >= OPL_Queue_Peek(callback_queue) + pause_offset) + { + // Pop the callback from the queue to invoke it. + + if (!OPL_Queue_Pop(callback_queue, &callback, &callback_data)) + { + break; + } + + // The mutex stuff here is a bit complicated. We must + // hold callback_mutex when we invoke the callback (so that + // the control thread can use OPL_Lock() to prevent callbacks + // from being invoked), but we must not be holding + // callback_queue_mutex, as the callback must be able to + // call OPL_SetCallback to schedule new callbacks. + + Pico_UnlockMutex(&callback_queue_mutex); + + Pico_LockMutex(&callback_mutex); + callback(callback_data); + Pico_UnlockMutex(&callback_mutex); + + Pico_LockMutex(&callback_queue_mutex); + } + + Pico_UnlockMutex(&callback_queue_mutex); +} + +// Call the OPL emulator code to fill the specified buffer. + +// Callback function to fill a new sound buffer: + +#define LIMITED_CALLBACK_TYPES 1 + +#if LIMITED_CALLBACK_TYPES +extern void RestartSong(void *unused); +extern void TrackTimerCallback(void *track); +#endif + +#if DOOM_TINY +extern uint8_t restart_song_state; +#endif + +void OPL_Pico_Mix_callback(audio_buffer_t *audio_buffer) +{ + unsigned int filled, buffer_samples; +#if DOOM_TINY + if (restart_song_state == 2) { + RestartSong(0); + } +#endif + + // Repeatedly call the OPL emulator update function until the buffer is + // full. + filled = 0; + buffer_samples = audio_buffer->max_sample_count; + +//#if PICO_ON_DEVICE +// absolute_time_t t0 = get_absolute_time(); +// gpio_set_mask(1); +//#endif + while (filled < buffer_samples) { +//#if PICO_ON_DEVICE +// gpio_set_mask(32); +//#endif + uint64_t next_callback_time; + uint64_t nsamples; + + Pico_LockMutex(&callback_queue_mutex); + + // Work out the time until the next callback waiting in + // the callback queue must be invoked. We can then fill the + // buffer with this many samples. + + if (opl_pico_paused || OPL_Queue_IsEmpty(callback_queue)) { + nsamples = buffer_samples - filled; + } else { + next_callback_time = OPL_Queue_Peek(callback_queue) + pause_offset; + + nsamples = (next_callback_time - current_time) * PICO_SOUND_SAMPLE_FREQ; + nsamples = (nsamples + OPL_SECOND - 1) / OPL_SECOND; + + if (nsamples > buffer_samples - filled) { + nsamples = buffer_samples - filled; + } + } + + Pico_UnlockMutex(&callback_queue_mutex); + + // Add emulator output to buffer. + + //OPL3_GenerateStream(&opl_chip, (Bit16s *) (audio_buffer->buffer->bytes + filled * 4), nsamples); +#if USE_WOODY_OPL + int16_t *sndptr = (int16_t *) (audio_buffer->buffer->bytes + filled * 4); + // todo store in stereo? + adlib_getsample(sndptr, nsamples); + for(int i=nsamples-1; i>=0; i--) { + sndptr[i*2] = sndptr[i*2 + 1] = sndptr[i]; + } +#elif USE_EMU8950_OPL + if (nsamples) { + int32_t *sndptr32 = (int32_t *) (audio_buffer->buffer->bytes + filled * 4); + OPL_calc_buffer_stereo(emu8950_opl, sndptr32, nsamples); + } +#else + int16_t *sndptr = (int16_t *) (audio_buffer->buffer->bytes + filled * 4); + for(int i = 0; i < nsamples; i++) + { + OPL3_GenerateResampled(&opl_chip, sndptr); + sndptr += 2; + } +#endif + filled += nsamples; + + // Invoke callbacks for this point in time. + +//#if PICO_ON_DEVICE +// gpio_clr_mask(32); +//#endif + AdvanceTime(nsamples); + } + audio_buffer->sample_count = audio_buffer->max_sample_count; +#if !USE_WOODY_OPL + int16_t *samples = (int16_t *)audio_buffer->buffer->bytes; + for(uint i=0;isample_count * 2; i++) { + samples[i] <<= 3; + } +#endif +//#if PICO_ON_DEVICE +// gpio_clr_mask(1); +// int32_t t = (int32_t)absolute_time_diff_us(t0, get_absolute_time()); +// static int max_t; +// static int ii; +// static int total; +// total += t; +// if (t > max_t) { +// max_t = t; +// } +// ii++; +// if (!(ii &127)) { +// printf("AVG %d MAX %d\n", total / 128, max_t); +// max_t = 0; +// total = 0; +// } +//#endif +} + +static void OPL_Pico_Shutdown(void) +{ + if (audio_was_initialized) + { + I_PicoSoundSetMusicGenerator(NULL); + OPL_Queue_Destroy(callback_queue); + audio_was_initialized = 0; + } +} + +static int OPL_Pico_Init(unsigned int port_base) +{ + if (I_PicoSoundIsInitialized()) { + opl_pico_paused = 0; + pause_offset = 0; + + // Queue structure of callbacks to invoke. + + callback_queue = OPL_Queue_Create(); + current_time = 0; + + +#if USE_WOODY_OPL + adlib_init(mixing_freq); +#elif USE_EMU8950_OPL + emu8950_opl = OPL_new(3579552, PICO_SOUND_SAMPLE_FREQ); // todo check rate +#else + OPL3_Reset(&opl_chip, PICO_SOUND_SAMPLE_FREQ); + opl_opl3mode = 0; +#endif + + // // Set postmix that adds the OPL music. This is deliberately done + // // as a postmix and not using Mix_HookMusic() as the latter disables + // // normal Pico_mixer music mixing. + // Mix_SetPostMix(OPL_Mix_Callback, NULL); + I_PicoSoundSetMusicGenerator(OPL_Pico_Mix_callback); + audio_was_initialized = 1; + } else { + audio_was_initialized = 0; + } + return 1; +} + +static unsigned int OPL_Pico_PortRead(opl_port_t port) +{ + unsigned int result = 0; + + if (port == OPL_REGISTER_PORT_OPL3) + { + return 0xff; + } + +#if !EMU8950_NO_TIMER + if (timer1.enabled && current_time > timer1.expire_time) + { + result |= 0x80; // Either have expired + result |= 0x40; // Timer 1 has expired + } + + if (timer2.enabled && current_time > timer2.expire_time) + { + result |= 0x80; // Either have expired + result |= 0x20; // Timer 2 has expired + } +#endif + + return result; +} + +static void OPLTimer_CalculateEndTime(opl_timer_t *timer) +{ + int tics; + + // If the timer is enabled, calculate the time when the timer + // will expire. + + if (timer->enabled) + { + tics = 0x100 - timer->value; + timer->expire_time = current_time + + ((uint64_t) tics * OPL_SECOND) / timer->rate; + } +} + +static void WriteRegister(unsigned int reg_num, unsigned int value) +{ + switch (reg_num) + { +#if !EMU8950_NO_TIMER + case OPL_REG_TIMER1: + timer1.value = value; + OPLTimer_CalculateEndTime(&timer1); + break; + + case OPL_REG_TIMER2: + timer2.value = value; + OPLTimer_CalculateEndTime(&timer2); + break; + + case OPL_REG_TIMER_CTRL: + if (value & 0x80) + { + timer1.enabled = 0; + timer2.enabled = 0; + } + else + { + if ((value & 0x40) == 0) + { + timer1.enabled = (value & 0x01) != 0; + OPLTimer_CalculateEndTime(&timer1); + } + + if ((value & 0x20) == 0) + { + timer1.enabled = (value & 0x02) != 0; + OPLTimer_CalculateEndTime(&timer2); + } + } + + break; +#endif + case OPL_REG_NEW: +#if !USE_WOODY_OPL && !USE_EMU8950_OPL + opl_opl3mode = value & 0x01; +#endif + default: +#if USE_WOODY_OPL + adlib_write(reg_num, value); +#elif USE_EMU8950_OPL + OPL_writeReg(emu8950_opl, reg_num, value); +#else + OPL3_WriteRegBuffered(&opl_chip, reg_num, value); +#endif + break; + } +} + +static void OPL_Pico_PortWrite(opl_port_t port, unsigned int value) +{ + if (port == OPL_REGISTER_PORT) + { + register_num = value; + } + else if (port == OPL_REGISTER_PORT_OPL3) + { + register_num = value | 0x100; + } + else if (port == OPL_DATA_PORT) + { + WriteRegister(register_num, value); + } +} + +static void OPL_Pico_SetCallback(uint64_t us, opl_callback_t callback, + void *data) +{ + Pico_LockMutex(&callback_queue_mutex); + OPL_Queue_Push(callback_queue, callback, data, + current_time - pause_offset + us); + Pico_UnlockMutex(&callback_queue_mutex); +} + +static void OPL_Pico_ClearCallbacks(void) +{ + Pico_LockMutex(&callback_queue_mutex); + OPL_Queue_Clear(callback_queue); + Pico_UnlockMutex(&callback_queue_mutex); +} + +static void OPL_Pico_Lock(void) +{ + Pico_LockMutex(&callback_mutex); +} + +static void OPL_Pico_Unlock(void) +{ + Pico_UnlockMutex(&callback_mutex); +} + +static void OPL_Pico_SetPaused(int paused) +{ + opl_pico_paused = paused; +} + +static void OPL_Pico_AdjustCallbacks(unsigned int old_tempo, unsigned int new_tempo) +{ + Pico_LockMutex(&callback_queue_mutex); + OPL_Queue_AdjustCallbacks(callback_queue, current_time, old_tempo, new_tempo); + Pico_UnlockMutex(&callback_queue_mutex); +} + +const opl_driver_t opl_pico_driver = +{ + "Pico", + OPL_Pico_Init, + OPL_Pico_Shutdown, + OPL_Pico_PortRead, + OPL_Pico_PortWrite, + OPL_Pico_SetCallback, + OPL_Pico_ClearCallbacks, + OPL_Pico_Lock, + OPL_Pico_Unlock, + OPL_Pico_SetPaused, + OPL_Pico_AdjustCallbacks, +}; + +void OPL_Delay(uint64_t us) { + sleep_us(us); // todo not sure we want to block +} + +// todo really limited to 1 event per track i think, so could go smaller +#define MAX_OPL_QUEUE 10 +PHEAP_DEFINE_STATIC(opl_heap, MAX_OPL_QUEUE + 1); + +// todo note also the callback is likely only one of a couple of functions +typedef struct queue_entry { + uint64_t time; // todo graham can likely be 32 bit, too much work atm +#if !LIMITED_CALLBACK_TYPES + opl_callback_t callback; +#endif + void *data; +} queue_entry_t; + +struct opl_callback_queue_s { + queue_entry_t entries[MAX_OPL_QUEUE]; +}; + +static struct opl_callback_queue_s queue; + +static inline queue_entry_t *get_entry(opl_callback_queue_t *queue, pheap_node_id_t id) { + assert(id && id <= opl_heap.max_nodes); + return queue->entries + id - 1; +} + +bool opl_queue_comparator(void *user_data, pheap_node_id_t a, pheap_node_id_t b) { + opl_callback_queue_t *q = (opl_callback_queue_t *)user_data; + return get_entry(q, a)->time < get_entry(q, b)->time; +} + +opl_callback_queue_t *OPL_Queue_Create(void) { + ph_post_alloc_init(&opl_heap, MAX_OPL_QUEUE, opl_queue_comparator, &queue); + return &queue; +} + +int OPL_Queue_IsEmpty(opl_callback_queue_t *queue) { + return ph_peek_head(&opl_heap) == 0; +} + +void OPL_Queue_Clear(opl_callback_queue_t *queue) { + ph_clear(&opl_heap); +} + +void OPL_Queue_Destroy(opl_callback_queue_t *queue) { + +} + +void OPL_Queue_Push(opl_callback_queue_t *queue, + opl_callback_t callback, void *data, + uint64_t time) { + pheap_node_id_t id = ph_new_node(&opl_heap); + assert(id); // check not full + queue_entry_t *qe = get_entry(queue, id); + qe->time = time; +#if !LIMITED_CALLBACK_TYPES + qe->callback = callback +#else + assert(data || callback == RestartSong); + assert(!data || callback == TrackTimerCallback); +#endif + qe->data = data; + ph_insert_node(&opl_heap, id); +} + +int OPL_Queue_Pop(opl_callback_queue_t *queue, + opl_callback_t *callback, void **data) { + if (!ph_peek_head(&opl_heap)) return 0; + pheap_node_id_t id = ph_remove_head(&opl_heap, true); + queue_entry_t *qe = get_entry(queue, id); +#if !LIMITED_CALLBACK_TYPES + *callback = qe->callback; +#else + *callback = qe->data ? TrackTimerCallback : RestartSong; +#endif + *data = qe->data; + return 1; +} + +uint64_t OPL_Queue_Peek(opl_callback_queue_t *queue) { + pheap_node_id_t head = ph_peek_head(&opl_heap); + if (head) { + return get_entry(queue, head)->time; + } else { + return 0; + } +} + +static uint AdjustCallbacks(pheap_node_id_t id, uint64_t time, unsigned int old_tempo, unsigned int new_tempo) { + uint count = 0; + if (id) { + pheap_node_t *node = ph_get_node(&opl_heap, id); + queue_entry_t *entry = get_entry(&queue, id); + uint64_t offset = entry->time - time; + entry->time = time + (offset * new_tempo) / old_tempo; + AdjustCallbacks(node->child, time, old_tempo, new_tempo); + AdjustCallbacks(node->sibling, time, old_tempo, new_tempo); + } + return count; +} + +void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *_queue, + uint64_t time, unsigned int old_tempo, unsigned int new_tempo) +{ + assert(_queue == &queue); + AdjustCallbacks(ph_peek_head(&opl_heap), time, old_tempo, new_tempo); +} diff --git a/opl/opl_queue.c b/opl/opl_queue.c index e948b641..a61ba3ce 100644 --- a/opl/opl_queue.c +++ b/opl/opl_queue.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -20,6 +21,7 @@ #include #include +#include "doomtype.h" #include "opl_queue.h" #define MAX_OPL_QUEUE 64 @@ -71,7 +73,7 @@ void OPL_Queue_Push(opl_callback_queue_t *queue, if (queue->num_entries >= MAX_OPL_QUEUE) { - fprintf(stderr, "OPL_Queue_Push: Exceeded maximum callbacks\n"); + stderr_print( "OPL_Queue_Push: Exceeded maximum callbacks\n"); return; } @@ -202,11 +204,12 @@ uint64_t OPL_Queue_Peek(opl_callback_queue_t *queue) } void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue, - uint64_t time, float factor) + uint64_t time, unsigned int old_tempo, unsigned int new_tempo) { int64_t offset; int i; + float factor = old_tempo / new_tempo; for (i = 0; i < queue->num_entries; ++i) { offset = queue->entries[i].time - time; diff --git a/opl/opl_queue.h b/opl/opl_queue.h index 8b4f0dbe..556324d7 100644 --- a/opl/opl_queue.h +++ b/opl/opl_queue.h @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -32,8 +33,8 @@ void OPL_Queue_Push(opl_callback_queue_t *queue, int OPL_Queue_Pop(opl_callback_queue_t *queue, opl_callback_t *callback, void **data); uint64_t OPL_Queue_Peek(opl_callback_queue_t *queue); -void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue, - uint64_t time, float factor); +void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue, uint64_t time, + unsigned int old_tempo, unsigned int new_tempo); #endif /* #ifndef OPL_QUEUE_H */ diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index b1ccb70a..cc924190 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -24,8 +25,15 @@ #include "SDL.h" #include "SDL_mixer.h" +#include "doomtype.h" +#if USE_WOODY_OPL +#include "woody_opl.h" +#elif USE_EMU8950_OPL +#include "emu8950.h" +#else #include "opl3.h" +#endif #include "opl.h" #include "opl_internal.h" @@ -70,8 +78,16 @@ static uint64_t pause_offset; // OPL software emulator structure. +#if USE_WOODY_OPL +// todo configure this based on woody build flag +#define opl_op3mode 0 +#elif USE_EMU8950_OPL +#define opl_op3mode 0 +static OPL *emu8950_opl; +#else static opl3_chip opl_chip; static int opl_opl3mode; +#endif // Temporary mixing buffer used by the mixing callback. @@ -164,7 +180,20 @@ static void FillBuffer(uint8_t *buffer, unsigned int nsamples) // OPL output is generated into temporary buffer and then mixed // (to avoid overflows etc.) +#if USE_WOODY_OPL + Bit16s *mb16 = (Bit16s *) mix_buffer; + adlib_getsample(mb16, nsamples); + for(int i=nsamples-1; i>=0; i--) { + mb16[i*2] = mb16[i*2 + 1] = mb16[i]; + } +#elif USE_EMU8950_OPL + int16_t *buf = (int16_t *) mix_buffer; + for(int i=0;i +#include + +#if PICO_ON_DEVICE +#include "hardware/interp.h" +#define SLOT_RENDER_DATA __scratch_y("slot_render_cpp") +#else +#define SLOT_RENDER_DATA +#endif + +static_assert(PM_DPHASE > 0, ""); + +#define unlikely(x) __builtin_expect((x),0) + +#if EMU8950_SLOT_RENDER +#include + +#define INLINE inline +// todo combine all these tablles into 1 +static uint8_t eg_step_tables_fast[4][8] = { + {1, 1, 1, 1, 1, 1, 1, 1}, + {1, 1, 1, 2, 1, 1, 1, 2}, + {1, 2, 1, 2, 1, 2, 1, 2}, + {1, 2, 2, 2, 1, 2, 2, 2}, +}; +static uint8_t eg_step_tables_fast2[4][8] = { + {2, 2, 2, 2, 2, 2, 2, 2}, + {2, 2, 2, 4, 2, 2, 2, 4}, + {2, 4, 2, 4, 2, 4, 2, 4}, + {2, 4, 4, 4, 2, 4, 4, 4}, +}; +static uint8_t eg_step_tables[4][8] = { + {0, 1, 0, 1, 0, 1, 0, 1}, + {0, 1, 0, 1, 1, 1, 0, 1}, + {0, 1, 1, 1, 0, 1, 1, 1}, + {0, 1, 1, 1, 1, 1, 1, 1}, +}; +static uint8_t eg_step_table4[8] = { + 4,4,4,4,4,4,4,4 +}; +// too can find this elesewhere +static uint8_t eg_step_table0[8] = { + 0,0,0,0,0,0,0,0 +}; + +// todo combine all these tablles into 1 - seems slower for now probably not in asm tho +//static uint8_t eg_step[14][8] = { +//#define ST_NORMAL 0 +// {0, 1, 0, 1, 0, 1, 0, 1}, +// {0, 1, 0, 1, 1, 1, 0, 1}, +// {0, 1, 1, 1, 0, 1, 1, 1}, +// {0, 1, 1, 1, 1, 1, 1, 1}, +//#define ST_FAST 4 +// {1, 1, 1, 1, 1, 1, 1, 1}, +// {1, 1, 1, 2, 1, 1, 1, 2}, +// {1, 2, 1, 2, 1, 2, 1, 2}, +// {1, 2, 2, 2, 1, 2, 2, 2}, +//#define ST_FAST2 8 +// {2, 2, 2, 2, 2, 2, 2, 2}, +// {2, 2, 2, 4, 2, 2, 2, 4}, +// {2, 4, 2, 4, 2, 4, 2, 4}, +// {2, 4, 4, 4, 2, 4, 4, 4}, +//#define ST_FOUR 12 +// {4, 4, 4, 4, 4, 4, 4, 4}, +//#define ST_ZERO 13 +// {0, 0, 0, 0, 0, 0, 0, 0}, +//}; + +/* offset to fnum, rough approximation of 14 cents depth. */ +static int8_t pm_table[8][PM_PG_WIDTH] = { + {0, 0, 0, 0, 0, 0, 0, 0}, // fnum = 000xxxxx + {0, 0, 1, 0, 0, 0, -1, 0}, // fnum = 001xxxxx + {0, 1, 2, 1, 0, -1, -2, -1}, // fnum = 010xxxxx + {0, 1, 3, 1, 0, -1, -3, -1}, // fnum = 011xxxxx + {0, 2, 4, 2, 0, -2, -4, -2}, // fnum = 100xxxxx + {0, 2, 5, 2, 0, -2, -5, -2}, // fnum = 101xxxxx + {0, 3, 6, 3, 0, -3, -6, -3}, // fnum = 110xxxxx + {0, 3, 7, 3, 0, -3, -7, -3}, // fnum = 111xxxxx +}; +// todo we are probably fine with pm_table_half[x] = pm_table[x/2] >> 1 as an approcimation, but keeping separate for diff for now +static int8_t pm_table_half[8][PM_PG_WIDTH] = { + {0 >> 1, 0 >> 1, 0 >> 1, 0 >> 1, 0 >> 1, 0 >> 1, 0 >> 1, 0 >> 1}, // fnum = 000xxxxx + {0 >> 1, 0 >> 1, 1 >> 1, 0 >> 1, 0 >> 1, 0 >> 1, -1 >> 1, 0 >> 1}, // fnum = 001xxxxx + {0 >> 1, 1 >> 1, 2 >> 1, 1 >> 1, 0 >> 1, -1 >> 1, -2 >> 1, -1 >> 1}, // fnum = 010xxxxx + {0 >> 1, 1 >> 1, 3 >> 1, 1 >> 1, 0 >> 1, -1 >> 1, -3 >> 1, -1 >> 1}, // fnum = 011xxxxx + {0 >> 1, 2 >> 1, 4 >> 1, 2 >> 1, 0 >> 1, -2 >> 1, -4 >> 1, -2 >> 1}, // fnum = 100xxxxx + {0 >> 1, 2 >> 1, 5 >> 1, 2 >> 1, 0 >> 1, -2 >> 1, -5 >> 1, -2 >> 1}, // fnum = 101xxxxx + {0 >> 1, 3 >> 1, 6 >> 1, 3 >> 1, 0 >> 1, -3 >> 1, -6 >> 1, -3 >> 1}, // fnum = 110xxxxx + {0 >> 1, 3 >> 1, 7 >> 1, 3 >> 1, 0 >> 1, -3 >> 1, -7 >> 1, -3 >> 1}, // fnum = 111xxxxx +}; + +/* clang-format off */ +/* exp_table[255-x] = round((exp2((double)x / 256.0) - 1) * 1024) */ +static uint16_t exp_table[256] = { + 1024+1018, 1024+1013, 1024+1007, 1024+1002, 1024+996, 1024+991, 1024+986, 1024+980, 1024+975, 1024+969, 1024+964, 1024+959, 1024+953, 1024+948, 1024+942, 1024+937, + 1024+932, 1024+927, 1024+921, 1024+916, 1024+911, 1024+906, 1024+900, 1024+895, 1024+890, 1024+885, 1024+880, 1024+874, 1024+869, 1024+864, 1024+859, 1024+854, + 1024+849, 1024+844, 1024+839, 1024+834, 1024+829, 1024+824, 1024+819, 1024+814, 1024+809, 1024+804, 1024+799, 1024+794, 1024+789, 1024+784, 1024+779, 1024+774, + 1024+770, 1024+765, 1024+760, 1024+755, 1024+750, 1024+745, 1024+741, 1024+736, 1024+731, 1024+726, 1024+722, 1024+717, 1024+712, 1024+708, 1024+703, 1024+698, + 1024+693, 1024+689, 1024+684, 1024+680, 1024+675, 1024+670, 1024+666, 1024+661, 1024+657, 1024+652, 1024+648, 1024+643, 1024+639, 1024+634, 1024+630, 1024+625, + 1024+621, 1024+616, 1024+612, 1024+607, 1024+603, 1024+599, 1024+594, 1024+590, 1024+585, 1024+581, 1024+577, 1024+572, 1024+568, 1024+564, 1024+560, 1024+555, + 1024+551, 1024+547, 1024+542, 1024+538, 1024+534, 1024+530, 1024+526, 1024+521, 1024+517, 1024+513, 1024+509, 1024+505, 1024+501, 1024+496, 1024+492, 1024+488, + 1024+484, 1024+480, 1024+476, 1024+472, 1024+468, 1024+464, 1024+460, 1024+456, 1024+452, 1024+448, 1024+444, 1024+440, 1024+436, 1024+432, 1024+428, 1024+424, + 1024+420, 1024+416, 1024+412, 1024+409, 1024+405, 1024+401, 1024+397, 1024+393, 1024+389, 1024+385, 1024+382, 1024+378, 1024+374, 1024+370, 1024+367, 1024+363, + 1024+359, 1024+355, 1024+352, 1024+348, 1024+344, 1024+340, 1024+337, 1024+333, 1024+329, 1024+326, 1024+322, 1024+318, 1024+315, 1024+311, 1024+308, 1024+304, + 1024+300, 1024+297, 1024+293, 1024+290, 1024+286, 1024+283, 1024+279, 1024+276, 1024+272, 1024+268, 1024+265, 1024+262, 1024+258, 1024+255, 1024+251, 1024+248, + 1024+244, 1024+241, 1024+237, 1024+234, 1024+231, 1024+227, 1024+224, 1024+220, 1024+217, 1024+214, 1024+210, 1024+207, 1024+204, 1024+200, 1024+197, 1024+194, + 1024+190, 1024+187, 1024+184, 1024+181, 1024+177, 1024+174, 1024+171, 1024+168, 1024+164, 1024+161, 1024+158, 1024+155, 1024+152, 1024+148, 1024+145, 1024+142, + 1024+139, 1024+136, 1024+133, 1024+130, 1024+126, 1024+123, 1024+120, 1024+117, 1024+114, 1024+111, 1024+108, 1024+105, 1024+102, 1024+99, 1024+96, 1024+93, + 1024+90, 1024+87, 1024+84, 1024+81, 1024+78, 1024+75, 1024+72, 1024+69, 1024+66, 1024+63, 1024+60, 1024+57, 1024+54, 1024+51, 1024+48, 1024+45, + 1024+42, 1024+40, 1024+37, 1024+34, 1024+31, 1024+28, 1024+25, 1024+22, 1024+20, 1024+17, 1024+14, 1024+11, 1024+8, 1024+6, 1024+3, 1024+0, +}; + +/* logsin_table[x] = round(-log2(sin((x + 0.5) * PI / (PG_WIDTH / 4) / 2)) * 256) */ + +static uint32_t ml_table[16] = {1, 1 * 2, 2 * 2, 3 * 2, 4 * 2, 5 * 2, 6 * 2, 7 * 2, + 8 * 2, 9 * 2, 10 * 2, 10 * 2, 12 * 2, 12 * 2, 15 * 2, 15 * 2}; + +#if !EMU8950_NO_WAVE_TABLE_MAP +#define LOGSIN_TABLE_SIZE PG_WIDTH / 4 +#else +#define LOGSIN_TABLE_SIZE PG_WIDTH / 2 +#endif +static uint16_t SLOT_RENDER_DATA logsin_table[LOGSIN_TABLE_SIZE] = { + 2137, 1731, 1543, 1419, 1326, 1252, 1190, 1137, 1091, 1050, 1013, 979, 949, 920, 894, 869, + 846, 825, 804, 785, 767, 749, 732, 717, 701, 687, 672, 659, 646, 633, 621, 609, + 598, 587, 576, 566, 556, 546, 536, 527, 518, 509, 501, 492, 484, 476, 468, 461, + 453, 446, 439, 432, 425, 418, 411, 405, 399, 392, 386, 380, 375, 369, 363, 358, + 352, 347, 341, 336, 331, 326, 321, 316, 311, 307, 302, 297, 293, 289, 284, 280, + 276, 271, 267, 263, 259, 255, 251, 248, 244, 240, 236, 233, 229, 226, 222, 219, + 215, 212, 209, 205, 202, 199, 196, 193, 190, 187, 184, 181, 178, 175, 172, 169, + 167, 164, 161, 159, 156, 153, 151, 148, 146, 143, 141, 138, 136, 134, 131, 129, + 127, 125, 122, 120, 118, 116, 114, 112, 110, 108, 106, 104, 102, 100, 98, 96, + 94, 92, 91, 89, 87, 85, 83, 82, 80, 78, 77, 75, 74, 72, 70, 69, + 67, 66, 64, 63, 62, 60, 59, 57, 56, 55, 53, 52, 51, 49, 48, 47, + 46, 45, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, + 29, 28, 27, 26, 25, 24, 23, 23, 22, 21, 20, 20, 19, 18, 17, 17, + 16, 15, 15, 14, 13, 13, 12, 12, 11, 10, 10, 9, 9, 8, 8, 7, + 7, 7, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, + 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +#if EMU8950_NO_WAVE_TABLE_MAP + // double the table size to include second 1/4 cycle + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, + 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, + 7, 8, 8, 9, 9, 10, 10, 11, 12, 12, 13, 13, 14, 15, 15, 16, + 17, 17, 18, 19, 20, 20, 21, 22, 23, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45, 46, + 47, 48, 49, 51, 52, 53, 55, 56, 57, 59, 60, 62, 63, 64, 66, 67, + 69, 70, 72, 74, 75, 77, 78, 80, 82, 83, 85, 87, 89, 91, 92, 94, + 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 125, 127, + 129, 131, 134, 136, 138, 141, 143, 146, 148, 151, 153, 156, 159, 161, 164, 167, + 169, 172, 175, 178, 181, 184, 187, 190, 193, 196, 199, 202, 205, 209, 212, 215, + 219, 222, 226, 229, 233, 236, 240, 244, 248, 251, 255, 259, 263, 267, 271, 276, + 280, 284, 289, 293, 297, 302, 307, 311, 316, 321, 326, 331, 336, 341, 347, 352, + 358, 363, 369, 375, 380, 386, 392, 399, 405, 411, 418, 425, 432, 439, 446, 453, + 461, 468, 476, 484, 492, 501, 509, 518, 527, 536, 546, 556, 566, 576, 587, 598, + 609, 621, 633, 646, 659, 672, 687, 701, 717, 732, 749, 767, 785, 804, 825, 846, + 869, 894, 920, 949, 979, 1013, 1050, 1091, 1137, 1190, 1252, 1326, 1419, 1543, 1731, 2137, +#endif +}; + +#if EMU8950_NO_WAVE_TABLE_MAP +// we start with +// _ _ +// / \/ \ which is abs(sine) wave +// +static uint16_t wav_or_table_lookup[4][4] = { + {0x0000, 0x0000, 0x8000, 0x8000}, // .. negate second half + {0x0000, 0x0000, 0x0fff, 0x0fff}, // .. attenuate second half + {0x0000, 0x0000, 0x0000, 0x0000}, // .. leave second half alone + {0x0000, 0x0fff, 0x0000, 0x0fff}, // .. attenuate 1 and 3 +}; +#endif + +static INLINE int16_t calc_sample(const SLOT_RENDER *slot, uint32_t index, int16_t am) { + +#if !EMU8950_NO_WAVE_TABLE_MAP + uint16_t h = slot->wave_table[index & (PG_WIDTH - 1)]; +#else +#if PICO_ON_DEVICE + interp0->accum[0] = index << 1; + uint16_t h = *(uint16_t *)(interp0->peek[0]) | *(uint16_t *)(interp0->peek[1]); +#else + uint16_t h = slot->wav_or_table[(index >> (PG_BITS - 2)) & 3] | slot->logsin_table[(index & (PG_WIDTH / 2 - 1))]; +#endif +#endif + uint16_t att = h + slot->eg_out_tll_lsl3 + am; + int16_t t = exp_table[att&0xff]; + // note we're really just bit clearing the original top bit 15 .. + // todo presumably the & is ignored on ARM? + int16_t res = t >> ((att>>8)&127); + if (!res) return res; // maybe make things more compatible +#if EMU8950_LINEAR_NEG_NOT_NOT + return ((att & 0x8000) ? -res : res) << 1; +#else + return ((att & 0x8000) ? ~res : res) << 1; +#endif +} + +#if 0 +static uint8_t *get_attack_step_table(SLOT_RENDER *slot) { + int index = slot->eg_rate_l; + uint32_t hm1 = (slot->eg_rate_h - 1); + if (hm1 < 12) { + index += ST_NORMAL; + } else if (hm1 >= 14) { + // 0 and 15 + index = ST_ZERO; + } else if (hm1 == 12) { + index += ST_FAST; + } else { + index += ST_FAST2; + } + return eg_step[index]; +} + +// note this is the same as attack except for 15 => ST_FOUR +static uint8_t *get_decay_step_table(SLOT_RENDER *slot) { + uint32_t hm1 = (slot->eg_rate_h - 1); + int index = slot->eg_rate_l; + if (hm1 < 12) { + index += ST_NORMAL; + } else if (hm1 >= 14) { + index = hm1 == 14 ? ST_FOUR : ST_ZERO; + } else if (hm1 == 12) { + index += ST_FAST; + } else { + index += ST_FAST2; + } + return eg_step[index]; +} +#else +static uint8_t *get_attack_step_table(SLOT_RENDER *slot) { + switch (slot->eg_rate_h) { + case 13: + return eg_step_tables_fast[slot->eg_rate_l]; + case 14: + return eg_step_tables_fast2[slot->eg_rate_l]; + case 0: + case 15: + return eg_step_table0; + default: + return eg_step_tables[slot->eg_rate_l]; + } +} + +static uint8_t *get_decay_step_table(SLOT_RENDER *slot) { + switch (slot->eg_rate_h) { + case 0: + return eg_step_table0; + case 13: + return eg_step_tables_fast[slot->eg_rate_l]; + case 14: + return eg_step_tables_fast2[slot->eg_rate_l]; + case 15: + return eg_step_table4; + default: + return eg_step_tables[slot->eg_rate_l]; + } +} +#endif + +template int get_parameter_rate(SLOT_RENDER *slot) { + switch (EG_STATE) { + case ATTACK: + return slot->patch->AR; + case DECAY: + return slot->patch->DR; + case SUSTAIN: + return slot->patch->EG ? 0 : slot->patch->RR; + case RELEASE: + return slot->patch->RR; + default: + return 0; + } +} + +template void commit_slot_update_eg_only(SLOT_RENDER *slot) { + int p_rate = get_parameter_rate(slot); + + if (p_rate == 0) { + slot->eg_shift = 0; + slot->eg_rate_h = 0; + slot->eg_rate_l = 0; + } else { + slot->eg_rate_h = std::min(15, p_rate + (slot->rks >> 2)); + slot->eg_rate_l = slot->rks & 3; + if (EG_STATE == ATTACK) { + slot->eg_shift = (0 < slot->eg_rate_h && slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0; + } else { + slot->eg_shift = (slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0; + } + } +} + +template uint32_t advance_phase(SLOT_RENDER *slot, uint32_t &pm_phase) { + int8_t pm = 0; + if (PM) { +#if !PICO_ON_DEVICE + // todo if we do this with interpolator, then we can just skip the if + // todo PM_DPHASE == 512 + pm_phase = (pm_phase + PM_DPHASE) & (PM_DP_WIDTH - 1); + pm = slot->efix_pm_table[pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; +#else + interp1->add_raw[1] = 1; +// printf("%08x %08x\n", interp1->accum[1], interp1->peek[1]); + pm = *(int8_t *)interp1->peek[1]; +#endif + } +#if !PICO_ON_DEVICE + slot->pg_phase += slot->efix_pg_pm_x_fnum3ff + pm * slot->efix_pg_phase_multiplier; +#if EMU8950_NIT_PICKS + slot->pg_phase &= (DP_WIDTH - 1) * 2; // note the clear bottom bit is just for exact compatability for comparison with original code +#else + slot->pg_phase &= (DP_WIDTH * 2 - 1); // should be quicker on ARM +#endif + return slot->pg_phase >> (DP_BASE_BITS + 1); +#else + static_assert(((DP_WIDTH * 2 - 1) >> (DP_BASE_BITS + 1)) == (1 << PG_BITS) -1, ""); + // pg_phase += pm * slot->efix_pg_phase_multiplier + if (pm) { + interp1->add_raw[0] = pm * slot->efix_pg_phase_multiplier; + } + interp1->add_raw[0] = interp1->base[0]; + // tmp = slot->pg_phase >> (DP_BASE_BITS + 1) & ((1 << PG_BITS) - 1); + // slot->pg_phase += slot->efix_pg_pm_x_fnum3ff + // return tmp; + return interp1->add_raw[0]; +#endif +} + +template void mod_am1_fb1_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) { + int16_t fm = (slot->output[1] + slot->output[0]) >> slot->nine_minus_FB; + slot->output[1] = slot->output[0]; + uint32_t pg_out = advance_phase(slot, pm_phase); + slot->mod_buffer[s] = slot->output[0] = calc_sample(slot, pg_out + fm, slot->lfo_am_buffer_lsl3[s]); +} + +template void mod_am1_fb0_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) { + uint32_t pg_out = advance_phase(slot, pm_phase); + slot->mod_buffer[s] = calc_sample(slot, pg_out, slot->lfo_am_buffer_lsl3[s]); +} + +template void mod_am0_fb1_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) { + int16_t fm = (slot->output[1] + slot->output[0]) >> slot->nine_minus_FB; + slot->output[1] = slot->output[0]; + uint32_t pg_out = advance_phase(slot, pm_phase); + + slot->mod_buffer[s] = slot->output[0] = calc_sample(slot, pg_out + fm, 0); +} + +template void mod_am0_fb0_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) { + uint32_t pg_out = advance_phase(slot, pm_phase); + slot->mod_buffer[s] = calc_sample(slot, pg_out, 0); + +} + +template void alg0_am1_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) { + // todo is this masking realy necessary; i doubt it. .. seems to be always even anyway +// int32_t fm = 2 * (opl->mod_buffer[s] >> 1); + int32_t fm = slot->mod_buffer[s]; + uint32_t pg_out = advance_phase(slot, pm_phase); + int32_t val = calc_sample(slot, pg_out + fm, slot->lfo_am_buffer_lsl3[s]); + slot->buffer[s] += val; +} + +template void alg0_am0_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) { + // todo could reset these when we start a new note +// slot->output[1] = slot->output[0]; + + // todo is this masking realy necessary; i doubt it. .. seems to be always even anyway +// int32_t fm = 2 * (opl->mod_buffer[s] >> 1); + int32_t fm = slot->mod_buffer[s]; + + uint32_t pg_out = advance_phase(slot, pm_phase); + int32_t val = calc_sample(slot, pg_out + fm, 0); + slot->buffer[s] += val; +} + + +template void alg1_am1_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) { + uint32_t pg_out = advance_phase(slot, pm_phase); + int16_t val = calc_sample(slot, pg_out, slot->lfo_am_buffer_lsl3[s]); + slot->buffer[s] += val + slot->mod_buffer[s]; +} + +template void alg1_am0_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) { + uint32_t pg_out = advance_phase(slot, pm_phase); + int16_t val = calc_sample(slot, pg_out, 0); + slot->buffer[s] += val + slot->mod_buffer[s]; +} + +#if PICO_ON_DEVICE +extern "C" uint32_t test_slot_asm(SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint fn); +#endif + +template uint32_t slot_envelope_loop(F&& fn, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { + // factored out as it is constant per call + slot->efix_pg_phase_multiplier = ml_table[slot->patch->ML] << slot->blk; + uint32_t efix_pg_pm_x_fnum3ff = (slot->fnum & 0x3ff) * slot->efix_pg_phase_multiplier; + // pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; + // pm >>= (slot->pm_mode ? 0 : 1); + int8_t *efix_pm_table = slot->pm_mode ? pm_table[(slot->fnum >> 7) & 7] : + pm_table_half[(slot->fnum >> 7) & 7]; +#if !PICO_ON_DEVICE + slot->wav_or_table = wav_or_table_lookup[slot->patch->WS & 3]; + slot->logsin_table = logsin_table; + slot->efix_pg_pm_x_fnum3ff = efix_pg_pm_x_fnum3ff; + slot->efix_pm_table = efix_pm_table; +#else + // note that index is pre-doubled + // wave_table[(index >> (PG_BITS - 2)) & 3] + interp_config c = interp_default_config(); + interp_config_set_shift(&c, PG_BITS - 2); + interp_config_set_mask(&c, 1, 2); + interp_set_config(interp0, 0, &c); + interp0->base[0] = (uintptr_t)wav_or_table_lookup[slot->patch->WS & 3]; + c = interp_default_config(); + interp_config_set_cross_input(&c, true); + interp_config_set_mask(&c, 1, PG_BITS - 1); + interp_set_config(interp0, 1, &c); + // logsin_table[(index & (PG_WIDTH / 2 - 1))]; + interp0->base[1] = (uintptr_t)logsin_table; + + // return slot->pg_phase >> (DP_BASE_BITS + 1) & ((1 << PG_BITS) - 1); + // post increment with + c = interp_default_config(); + interp_config_set_add_raw(&c, true); + interp_config_set_shift(&c, DP_BASE_BITS + 1); + interp_config_set_mask(&c, 0, PG_BITS - 1); + interp_set_config(interp1, 0, &c); + slot->efix_pg_pm_x_fnum3ff = efix_pg_pm_x_fnum3ff; + interp1->base[0] = efix_pg_pm_x_fnum3ff; + interp1->accum[0] = slot->pg_phase; + + c = interp_default_config(); + static_assert(PM_DPHASE == 1, ""); // better for everyone! + // pm_phase = (pm_phase + PM_DPHASE) & (PM_DP_WIDTH - 1); + // pm = slot->efix_pm_table[pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; + interp_config_set_shift(&c, PM_DP_BITS - PM_PG_BITS); + interp_config_set_mask(&c, 0, PM_PG_BITS - 1); + interp_set_config(interp1, 1, &c); + interp1->base[1] = (uintptr_t)efix_pm_table; + interp1->accum[1] = pm_phase; + // we lookup in the lfo_am_buffer_lsl3 at inter0->base[2] + sample_ptr_in_mod_buffer / 2 for mod + // or at inter0->base[2] + sample_ptr_in_buffer / 4 + if (F_NUM <8) { + interp0->base[2] = (uintptr_t)slot->lfo_am_buffer_lsl3 - ((uintptr_t)slot->mod_buffer) / 2; + } else { + interp0->base[2] = (uintptr_t)slot->lfo_am_buffer_lsl3 - ((uintptr_t)slot->buffer) / 4; + } + interp1->base[2] = slot->efix_pg_phase_multiplier; +#endif + uint32_t s = 0; + uint32_t nsamples_bak = nsamples; + slot->eg_out_tll_lsl3 = std::min(EG_MAX/*EG_MUTE*/, slot->eg_out + slot->tll) << 3; // note EG_MAX not EG_MUTE to avoid overflow check later + +#if PICO_ON_DEVICE && EMU8950_ASM + // we lookup in mod_buffer at buffer_mod_buffer_offset + sample_ptr_in_buffer / 2 + slot->buffer_mod_buffer_offset = (uintptr_t)slot->mod_buffer - ((uintptr_t)slot->buffer) / 2; + s = test_slot_asm(slot, nsamples, eg_counter, F_NUM); + slot->pg_phase = interp1->accum[0]; + return s; +#endif + // todo eg_rate_h == 0 ... + // todo prate == 0 ? + if (slot->eg_state == ATTACK) { + // note we roll eg_rate_h > 0 check which is (was) always paired with (eg_counter++ && eg_shift_mask) == 0 + // and guarantee that eg_counter is never zero because bit 31-30 area always set to 10 on entry + uint32_t eg_shift_mask = slot->eg_rate_h > 0 ? (1 << slot->eg_shift) - 1 : 0xffffffff; + uint8_t *eg_step_table = get_attack_step_table(slot); + + // this original code did something like this every loop + // + // if ((++eg_counter & eg_shift_mask) == 0 && 0 < slot->eg_out)) { + // // update eg_out etc. + // } + // if (!eg_out) { + // // enter decay next time + // } + // + // since eg_out can only change as a result of the if, we do + // + // a) before the loop + // + // if (!eg_out) { + // eg_counter++; + // // enter decay next time + // } + // + // b) inside the loop + // + // if ((++eg_counter & eg_shift_mask) == 0) { + // // update eg_out etc. + // if (!eg_out) { + // // enter decay next time + // } + // } + if (slot->eg_out == 0) { + // straight into decay + eg_counter++; + slot->eg_state = DECAY; + commit_slot_update_eg_only(slot); + fn(slot, pm_phase, s++); + } else { + for (; s < nsamples; s++) { +#if DUMPO + if (hack_ch == 17 && s == 12) breako(); +#endif + if (unlikely((++eg_counter & eg_shift_mask) == 0)) { + uint8_t step = eg_step_table[(eg_counter >> slot->eg_shift) & 7]; + slot->eg_out += (~slot->eg_out * step) >> 3; + slot->eg_out_tll_lsl3 = std::min(EG_MAX/*EG_MUTE*/, slot->eg_out + slot->tll) << 3; // note EG_MAX not EG_MUTE to avoid overflow check later + if (unlikely(slot->eg_out == 0)) { + slot->eg_state = DECAY; + commit_slot_update_eg_only(slot); + nsamples = s; + } + } + fn(slot, pm_phase, s); + } + } + } + if (slot->eg_state == DECAY) { + nsamples = nsamples_bak; + // todo if note ends we can stop early + uint32_t eg_shift_mask = slot->eg_rate_h > 0 ? (1 << slot->eg_shift) - 1 : 0xffffffff; + uint8_t *eg_step_table = get_decay_step_table(slot); + // the original code checked for sustain every loop, however we have move it inside the once per 1<patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL)) { + if (s < nsamples) { + // because we have moved the second line of the test above into the per !(++eg_coiunter & eg_shift_maask)) + // test below, we have to half unroll the first loop + slot->eg_state = SUSTAIN; + eg_counter++; + commit_slot_update_eg_only(slot); + fn(slot, pm_phase, s++); + } + } else { + for (; s < nsamples; s++) { + if (unlikely((++eg_counter & eg_shift_mask) == 0)) { + slot->eg_out = static_cast(std::min(EG_MUTE, slot->eg_out + (int) eg_step_table[ + (eg_counter >> slot->eg_shift) & 7])); + slot->eg_out_tll_lsl3 = std::min(EG_MAX/*EG_MUTE*/, slot->eg_out + slot->tll) << 3; // note EG_MAX not EG_MUTE to avoid overflow check later + // todo check for decay to zero + if (unlikely((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL)) { + slot->eg_state = SUSTAIN; + commit_slot_update_eg_only(slot); + nsamples = s; +#if EMU8950_LINEAR_END_OF_NOTE_OPTIMIZATION + } else if (slot->eg_out == EG_MUTE) { + // we have decayed to zero, so give up + nsamples = s; +#endif + } + } + fn(slot, pm_phase, s); + } + } + } + if (slot->eg_state == SUSTAIN || slot->eg_state == RELEASE) { + nsamples = nsamples_bak; + // todo if note ends we can stop early + uint32_t eg_shift_mask = slot->eg_rate_h > 0 ? (1 << slot->eg_shift) - 1 : 0xffffffff; + uint8_t *eg_step_table = get_decay_step_table(slot); + for (; s < nsamples; s++) { + if (unlikely((++eg_counter & eg_shift_mask) == 0)) { + slot->eg_out = static_cast(std::min(EG_MUTE, slot->eg_out + (int)eg_step_table[(eg_counter >> slot->eg_shift)&7])); +#if EMU8950_LINEAR_END_OF_NOTE_OPTIMIZATION + if (slot->eg_out == EG_MUTE) { + // we have decayed to zero, so give up + nsamples = s; + } +#endif + slot->eg_out_tll_lsl3 = std::min(EG_MAX/*EG_MUTE*/, slot->eg_out + slot->tll) << 3; // note EG_MAX not EG_MUTE to avoid overflow check later + } + fn(slot, pm_phase, s); + } + } +#if PICO_ON_DEVICE + slot->pg_phase = interp1->accum[0]; +#endif + // not necessary for correctness + //#if EMU8950_NIT_PICKS + // slot->pg_phase &= (DP_WIDTH - 1) * 2; + //#endif + return s; +} + +typedef void OPL; + +template uint32_t slot_mod_linear(OPL *opl, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { + if (slot->patch->AM) { + if (slot->patch->FB) { + slot->nine_minus_FB = 9 - slot->patch->FB; + return slot_envelope_loop<6+PM>(mod_am1_fb1_fn, slot, nsamples, eg_counter, pm_phase); + } else { + return slot_envelope_loop<2+PM>(mod_am1_fb0_fn, slot, nsamples, eg_counter, pm_phase); + } + } else { + if (slot->patch->FB) { + slot->nine_minus_FB = 9 - slot->patch->FB; + return slot_envelope_loop<4+PM>(mod_am0_fb1_fn, slot, nsamples, eg_counter, pm_phase); + } else { + return slot_envelope_loop<0+PM>(mod_am0_fb0_fn, slot, nsamples, eg_counter, pm_phase); + } + } +} + +extern "C" uint32_t slot_mod_linear(OPL *opl, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { + if (slot->patch->PM) + return slot_mod_linear(opl, slot, nsamples, eg_counter, pm_phase); + else + return slot_mod_linear(opl, slot, nsamples, eg_counter, pm_phase); +} + +template uint32_t slot_car_linear_alg0(OPL *opl, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { + if (slot->patch->AM) { + return slot_envelope_loop<10+PM>(alg0_am1_fn, slot, nsamples, eg_counter, pm_phase); + } else { + return slot_envelope_loop<8+PM>(alg0_am0_fn, slot, nsamples, eg_counter, pm_phase); + } +} + +extern "C" uint32_t slot_car_linear_alg0(OPL *opl, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { + if (slot->patch->PM) + return slot_car_linear_alg0(opl, slot, nsamples, eg_counter, pm_phase); + else + return slot_car_linear_alg0(opl, slot, nsamples, eg_counter, pm_phase); +} + +template uint32_t slot_car_linear_alg1(OPL *opl, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { + + if (slot->patch->AM) { + return slot_envelope_loop<14+PM>(alg1_am1_fn, slot, nsamples, eg_counter, pm_phase); + } else { + return slot_envelope_loop<12+PM>(alg1_am0_fn, slot, nsamples, eg_counter, pm_phase); + } +} + + +extern "C" uint32_t slot_car_linear_alg1(OPL *opl, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { + if (slot->patch->PM) + return slot_car_linear_alg1(opl, slot, nsamples, eg_counter, pm_phase); + else + return slot_car_linear_alg1(opl, slot, nsamples, eg_counter, pm_phase); +} +#endif \ No newline at end of file diff --git a/opl/slot_render.h b/opl/slot_render.h new file mode 100644 index 00000000..714964a4 --- /dev/null +++ b/opl/slot_render.h @@ -0,0 +1,141 @@ +/** + * Copyright (C) 2001-2020 Mitsutaka Okazaki + * Copyright (C) 2021-2022 Graham Sanderson + */ + #ifndef _SLOT_RENDER_H_ +#define _SLOT_RENDER_H_ +#ifdef __cplusplus +extern "C" { +#endif +#include +#define EG_BITS 9 +#define EG_MUTE ((1 << EG_BITS) - 1) +#define EG_MAX (0x1f0) // 93dB + +/* sine table */ +#define PG_BITS 10 /* 2^10 = 1024 length sine table */ +#define PG_WIDTH (1 << PG_BITS) + +/* phase increment counter */ +#define DP_BITS 20 +#define DP_WIDTH (1 << DP_BITS) +#define DP_BASE_BITS (DP_BITS - PG_BITS) + +/* pitch modulator */ +#define PM_PG_BITS 3 +#define PM_PG_WIDTH (1 << PM_PG_BITS) + +// no benefit that I can see to this being 22 - it just makes PM_DPHASE unruly at 512 +#if 0 +#define PM_DP_BITS 22 +#else +#define PM_DP_BITS 13 +#endif +#define PM_DP_WIDTH (1 << PM_DP_BITS) +#define PM_DPHASE (PM_DP_WIDTH / (1024 * 8)) + +/* voice data */ +typedef struct __OPL_PATCH { +#if !EMU8950_NO_TLL + /* 0 */ uint8_t TL; + /* 1 */ uint8_t KL; +#else + /* 0 */ uint8_t TL4; + /* 1 */ uint8_t KL_SHIFT; +#endif + /* 2 */ uint8_t FB; + /* 3 */ uint8_t EG; + /* 4 */ uint8_t ML; + /* 5 */ uint8_t AR; + /* 6 */ uint8_t DR; + /* 7 */ uint8_t SL; + /* 8 */ uint8_t RR; + /* 9 */ uint8_t KR; + /* a */ uint8_t AM; + /* b */ uint8_t PM; + uint8_t WS; +} OPL_PATCH; + +enum __OPL_EG_STATE +{ + ATTACK, DECAY, SUSTAIN, RELEASE, UNKNOWN +}; + +struct SLOT_RENDER { +// stutff the assembly cares about is duplicate at the top so we can be sure of its order witout #ifs +#if EMU8950_ASM + // note there is code that writes zero to all for at once + /* 0x00 */ uint8_t eg_state; /* current state */ + /* 0x01 */ uint8_t eg_rate_h; /* eg speed rate high 4bits */ + /* 0x02 */ uint8_t eg_rate_l; /* eg speed rate low 2bits */ + /* 0x03 */ uint8_t eg_shift; /* shift for eg global counter, controls envelope speed */ + + /* 0x04 */ uint8_t nine_minus_FB; + /* 0x05 */ uint8_t rks; /* key scale offset (rks) for eg speed */ + uint16_t pad2; + /* 0x08 */ int16_t eg_out; /* eg output */ + /* 0x0a */ uint16_t tll; /* total level + key scale level*/ + /* 0x0c */ int32_t eg_out_tll_lsl3; /* (eg_out + tll) << 3 */ + // only used for mod slot + /* 0x10 */ int32_t output[2]; /* output value, latest and previous. */ + // these are actually opl local not slot local + /* 0x18 */ int16_t *mod_buffer; + /* 0x1c */ int32_t *buffer; + /* 0x20 */ uintptr_t buffer_mod_buffer_offset; + /* 0x24 */ OPL_PATCH *patch; +#else + uint8_t eg_state; /* current state */ + uint8_t eg_rate_h; /* eg speed rate high 4bits */ + uint8_t eg_rate_l; /* eg speed rate low 2bits */ +#if EMU8950_SLOT_RENDER + uint8_t nine_minus_FB; + int32_t eg_out_tll_lsl3; /* (eg_out + tll) << 3 */ +#endif + uint8_t rks; /* key scale offset (rks) for eg speed */ + int16_t eg_out; /* eg output */ + uint16_t tll; /* total level + key scale level*/ + // only used for mod slot + int32_t output[2]; /* output value, latest and previous. */ + // these are actually opl local not slot local + int16_t *mod_buffer; + int32_t *buffer; + uint32_t eg_shift; /* shift for eg global counter, controls envelope speed */ + OPL_PATCH *patch; +#endif + uint16_t fnum; /* f-number (9 bits) */ + uint16_t efix_pg_phase_multiplier; /* ml_table[slot->patch->ML]) << slot->blk >> 1 */ + uint8_t blk; /* block (3 bits) */ + uint32_t pg_phase; /* pg phase */ // note this moves twice as fast in slot_render version +#if EMU8950_SLOT_RENDER +//#if !PICO_ON_DEVICE + uint32_t efix_pg_pm_x_fnum3ff; /* efix_pg_phase_multiplier * (fnum & 0x3ff) */ +//#endif +#endif + +#if EMU8950_SLOT_RENDER + uint8_t *lfo_am_buffer_lsl3; +#else + uint8_t *lfo_am_buffer; +#endif + uint8_t pm_mode; + +#if !EMU8950_NO_WAVE_TABLE_MAP + uint16_t *wave_table; /* wave table */ +#else +#if EMU8950_SLOT_RENDER +#if !PICO_ON_DEVICE + uint16_t *logsin_table; + int8_t *efix_pm_table; +#endif +#endif +#if !PICO_ON_DEVICE + uint16_t *wav_or_table; +#endif +#endif +}; + + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/opl/slot_render_pico.S b/opl/slot_render_pico.S new file mode 100644 index 00000000..7fecf35d --- /dev/null +++ b/opl/slot_render_pico.S @@ -0,0 +1,789 @@ +// +// Copyright (C) 2001-2020 Mitsutaka Okazaki +// Copyright (C) 2021-2022 Graham Sanderson +// + .syntax unified + +#define ATTACK 0 +#define DECAY 1 +#define SUSTAIN 2 +#define RELEASE 3 + +//.section .data.slot_render_asm +.section .scratch_y.slot_render_S, "a" +exp_table: +.hword 1024+1018, 1024+1013, 1024+1007, 1024+1002, 1024+996, 1024+991, 1024+986, 1024+980, 1024+975, 1024+969, 1024+964, 1024+959, 1024+953, 1024+948, 1024+942, 1024+937 +.hword 1024+932, 1024+927, 1024+921, 1024+916, 1024+911, 1024+906, 1024+900, 1024+895, 1024+890, 1024+885, 1024+880, 1024+874, 1024+869, 1024+864, 1024+859, 1024+854 +.hword 1024+849, 1024+844, 1024+839, 1024+834, 1024+829, 1024+824, 1024+819, 1024+814, 1024+809, 1024+804, 1024+799, 1024+794, 1024+789, 1024+784, 1024+779, 1024+774 +.hword 1024+770, 1024+765, 1024+760, 1024+755, 1024+750, 1024+745, 1024+741, 1024+736, 1024+731, 1024+726, 1024+722, 1024+717, 1024+712, 1024+708, 1024+703, 1024+698 +.hword 1024+693, 1024+689, 1024+684, 1024+680, 1024+675, 1024+670, 1024+666, 1024+661, 1024+657, 1024+652, 1024+648, 1024+643, 1024+639, 1024+634, 1024+630, 1024+625 +.hword 1024+621, 1024+616, 1024+612, 1024+607, 1024+603, 1024+599, 1024+594, 1024+590, 1024+585, 1024+581, 1024+577, 1024+572, 1024+568, 1024+564, 1024+560, 1024+555 +.hword 1024+551, 1024+547, 1024+542, 1024+538, 1024+534, 1024+530, 1024+526, 1024+521, 1024+517, 1024+513, 1024+509, 1024+505, 1024+501, 1024+496, 1024+492, 1024+488 +.hword 1024+484, 1024+480, 1024+476, 1024+472, 1024+468, 1024+464, 1024+460, 1024+456, 1024+452, 1024+448, 1024+444, 1024+440, 1024+436, 1024+432, 1024+428, 1024+424 +.hword 1024+420, 1024+416, 1024+412, 1024+409, 1024+405, 1024+401, 1024+397, 1024+393, 1024+389, 1024+385, 1024+382, 1024+378, 1024+374, 1024+370, 1024+367, 1024+363 +.hword 1024+359, 1024+355, 1024+352, 1024+348, 1024+344, 1024+340, 1024+337, 1024+333, 1024+329, 1024+326, 1024+322, 1024+318, 1024+315, 1024+311, 1024+308, 1024+304 +.hword 1024+300, 1024+297, 1024+293, 1024+290, 1024+286, 1024+283, 1024+279, 1024+276, 1024+272, 1024+268, 1024+265, 1024+262, 1024+258, 1024+255, 1024+251, 1024+248 +.hword 1024+244, 1024+241, 1024+237, 1024+234, 1024+231, 1024+227, 1024+224, 1024+220, 1024+217, 1024+214, 1024+210, 1024+207, 1024+204, 1024+200, 1024+197, 1024+194 +.hword 1024+190, 1024+187, 1024+184, 1024+181, 1024+177, 1024+174, 1024+171, 1024+168, 1024+164, 1024+161, 1024+158, 1024+155, 1024+152, 1024+148, 1024+145, 1024+142 +.hword 1024+139, 1024+136, 1024+133, 1024+130, 1024+126, 1024+123, 1024+120, 1024+117, 1024+114, 1024+111, 1024+108, 1024+105, 1024+102, 1024+99, 1024+96, 1024+93 +.hword 1024+90, 1024+87, 1024+84, 1024+81, 1024+78, 1024+75, 1024+72, 1024+69, 1024+66, 1024+63, 1024+60, 1024+57, 1024+54, 1024+51, 1024+48, 1024+45 +.hword 1024+42, 1024+40, 1024+37, 1024+34, 1024+31, 1024+28, 1024+25, 1024+22, 1024+20, 1024+17, 1024+14, 1024+11, 1024+8, 1024+6, 1024+3, 1024+0 + +eg_step_tables: // note the defines have 0x80 to indicate that there are 4 x 8 entries based for each of eg_rate_l +#define ST_NORMAL ((0 * 8) | 0x80) +.byte 0, 1, 0, 1, 0, 1, 0, 1 +.byte 0, 1, 0, 1, 1, 1, 0, 1 +.byte 0, 1, 1, 1, 0, 1, 1, 1 +.byte 0, 1, 1, 1, 1, 1, 1, 1 +#define ST_FAST ((4 * 8) | 0x80) +.byte 1, 1, 1, 1, 1, 1, 1, 1 +.byte 1, 1, 1, 2, 1, 1, 1, 2 +.byte 1, 2, 1, 2, 1, 2, 1, 2 +.byte 1, 2, 2, 2, 1, 2, 2, 2 +#define ST_FAST2 ((8 * 8) | 0x80) +.byte 2, 2, 2, 2, 2, 2, 2, 2 +.byte 2, 2, 2, 4, 2, 2, 2, 4 +.byte 2, 4, 2, 4, 2, 4, 2, 4 +.byte 2, 4, 4, 4, 2, 4, 4, 4 +#define ST_FOUR (12 * 8) +.byte 4, 4, 4, 4, 4, 4, 4, 4 +#define ST_ZERO (13 * 8) +.byte 0, 0, 0, 0, 0, 0, 0, 0 + +attack_rate_def_table: + .byte ST_ZERO, ST_NORMAL, ST_NORMAL, ST_NORMAL + .byte ST_NORMAL, ST_NORMAL, ST_NORMAL, ST_NORMAL + .byte ST_NORMAL, ST_NORMAL, ST_NORMAL, ST_NORMAL + .byte ST_NORMAL, ST_FAST, ST_FAST2, ST_ZERO + +decay_rate_def_table: + .byte ST_ZERO, ST_NORMAL, ST_NORMAL, ST_NORMAL + .byte ST_NORMAL, ST_NORMAL, ST_NORMAL, ST_NORMAL + .byte ST_NORMAL, ST_NORMAL, ST_NORMAL, ST_NORMAL + .byte ST_NORMAL, ST_FAST, ST_FAST2, ST_FOUR + +.section .text.slot_render_asm +#include "pico/asm_helper.S" +#include "hardware/regs/sio.h" + +#define rl_slot r0 +#define rl_sample_out r1 +#define rl_eg_counter r2 +#define rl_eg_shift_mask r3 +#define rl_tmp0 r4 +#define rl_tmp1 r5 +#define rl_tmp2 r6 +#define rl_interp r7 + +#define rh_begin_loop r8 +#define rh_end_loop r9 +#define rh_sample_out_end r10 +#define rh_fn r11 +#define rh_exp_table r12 +// only used for alg0 and alg1; hmm. don't care +#define rh_buffer_mod_buffer_offset lr + +#define INTERP0_ACCUM0 (SIO_INTERP0_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP0_PEEK0 (SIO_INTERP0_PEEK_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP0_PEEK1 (SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP0_BASE2_AKA_LFO_AM_BUFFER_LSL3_OFFSET (SIO_INTERP0_BASE2_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP1_PEEK0 (SIO_INTERP1_PEEK_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP1_PEEK1 (SIO_INTERP1_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP1_POP0 (SIO_INTERP1_POP_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP1_ADD_RAW0 (SIO_INTERP1_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP1_ADD_RAW1 (SIO_INTERP1_ACCUM1_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP1_BASE0 (SIO_INTERP1_BASE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP1_BASE2_AKA_PG_PHASE_MULTIPLIER (SIO_INTERP1_BASE2_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) + +#define SLOTB_EG_STATE 0x00 +#define SLOTB_EG_RATE_H 0x01 +#define SLOTB_EG_RATE_L 0x02 +#define SLOTB_EG_SHIFT 0x03 +#define SLOTB_NINE_MINUS_FB 0x04 +#define SLOTB_RKS 0x05 +#define SLOTH_EG_OUT 0x08 +#define SLOTH_TLL 0x0a +#define SLOTW_EG_OUT_TLL_LSL3 0x0c +#define SLOTW_OUTPUT0 0x10 +#define SLOTW_OUTPUT1 0x14 +#define SLOTW_MOD_BUFFER 0x18 +#define SLOTW_BUFFER 0x1c +#define SLOTW_BUFFER_MOD_BUFFER_OFFSET 0x20 +#define SLOTW_PATCH 0x24 + +#define PATCH_EG 0x3 +#define PATCH_DR 0x6 +#define PATCH_SL 0x7 +#define PATCH_RR 0x8 + +.macro advance_phase_pm0 + ldr rl_tmp0, [rl_interp, #INTERP1_BASE0] + str rl_tmp0, [rl_interp, #INTERP1_ADD_RAW0] + ldr rl_tmp0, [rl_interp, #INTERP1_ADD_RAW0] +.endm + +.macro advance_phase_pm1 + movs rl_tmp1, #1 + str rl_tmp1, [rl_interp, #INTERP1_ADD_RAW1] + ldr rl_tmp0, [rl_interp, #INTERP1_PEEK1] + ldrb rl_tmp0, [rl_tmp0] // pm + sxtb rl_tmp0, rl_tmp0 + ldr rl_tmp1, [rl_interp, #INTERP1_BASE2_AKA_PG_PHASE_MULTIPLIER] + muls rl_tmp0, rl_tmp1 + ldr rl_tmp1, [rl_interp, #INTERP1_BASE0] + adds rl_tmp0, rl_tmp1 + str rl_tmp0, [rl_interp, #INTERP1_ADD_RAW0] + ldr rl_tmp0, [rl_interp, #INTERP1_ADD_RAW0] +.endm + +.macro calc_sample is_am + // interp0->accum[0] = index << 1; + lsls rl_tmp0, #1 // todo can we avoid this + str rl_tmp0, [rl_interp, #INTERP0_ACCUM0] + + // uint16_t h = *(uint16_t *)(interp0->peek[0]) | *(uint16_t *)(interp0->peek[1]); + ldr rl_tmp1, [rl_interp, #INTERP0_PEEK0] + ldr rl_tmp0, [rl_interp, #INTERP0_PEEK1] + ldrh rl_tmp0, [rl_tmp0] + ldrh rl_tmp1, [rl_tmp1] + orrs rl_tmp1, rl_tmp0 // rl_tmp1 = att + + // uint16_t att = h + slot->eg_out_tll_lsl3 + ldr rl_tmp0, [rl_slot, #SLOTW_EG_OUT_TLL_LSL3] + adds rl_tmp1, rl_tmp0 + // if (am) { h += am } +.if \is_am + adds rl_tmp1, rl_tmp2 +.endif + + // int16_t t = exp_table[att&0xff]; + lsls rl_tmp0, rl_tmp1, #24 + lsrs rl_tmp0, rl_tmp0, #23 // rl_tmp0 = (att&0xff) *2 + add rl_tmp0, rh_exp_table // or load + ldrh rl_tmp0, [rl_tmp0] + + // int16_t res = t >> ((att>>8)&127); + // todo this is currently res : ~res but we can inverted our OR table + // return ((att & 0x8000) ? ~res : res); + lsls rl_tmp1, #17 + sbcs rl_tmp2, rl_tmp2 + + mvns rl_tmp2, rl_tmp2 // todo remove this too, but we need to fix up fm + + lsrs rl_tmp1, #25 + lsrs rl_tmp0, rl_tmp1 + // todo: inaudible todo force zeros for now + beq 9f + # note this matches the original ~res << 1 code, however + # we don't have an early cutoff for full attenuation, so get -2 not 0 in some + # cases, however this was true of the original code too; the annoying thing + # is that it isn't trivial to fix up the - otherwise I think unimportant - differences for the sake of diff + eors rl_tmp0, rl_tmp2 + lsls rl_tmp0, #1 // todo remove +9: +.endm + +.macro calc_sample_am0 + calc_sample 0 +.endm +.macro calc_sample_am1_lsr1 + // must preserve rl_tmp0... set am << 3 in rl_tmp2 + ldr rl_tmp1, [rl_interp, #INTERP0_BASE2_AKA_LFO_AM_BUFFER_LSL3_OFFSET] + lsrs rl_tmp2, rl_sample_out, #1 + ldrb rl_tmp2, [rl_tmp1, rl_tmp2] + calc_sample 1 +.endm + +.macro calc_sample_am1_lsr2 + // must preserve rl_tmp0... set am << 3 in rl_tmp2 + ldr rl_tmp1, [rl_interp, #INTERP0_BASE2_AKA_LFO_AM_BUFFER_LSL3_OFFSET] + lsrs rl_tmp2, rl_sample_out, #2 + ldrb rl_tmp2, [rl_tmp1, rl_tmp2] + calc_sample 1 +.endm + +.macro mod_sample_done + strh rl_tmp0, [rl_sample_out] + adds rl_sample_out, #2 + cmp rl_sample_out, rh_sample_out_end + bge 9f + bx rh_begin_loop +9: bx rh_end_loop +.endm + +.macro add_fb_fm + ldr rl_tmp1, [rl_slot, #SLOTW_OUTPUT0] + ldr rl_tmp2, [rl_slot, #SLOTW_OUTPUT1] + str rl_tmp1, [rl_slot, #SLOTW_OUTPUT1] + adds rl_tmp1, rl_tmp2 + ldrb rl_tmp2, [rl_slot, #SLOTB_NINE_MINUS_FB] + lsrs rl_tmp1, rl_tmp2 + adds rl_tmp0, rl_tmp1 +.endm + +.thumb_func +mod_am0_fb0_pm0_fn: + advance_phase_pm0 // -> tmp0 = pg_out + calc_sample_am0 // tmp0 = pg_out -> tmp0 = sample + mod_sample_done + +.thumb_func +mod_am0_fb0_pm1_fn: + advance_phase_pm1 // -> tmp0 = pg_out + calc_sample_am0 // tmp0 = pg_out -> tmp0 = sample + mod_sample_done + +.thumb_func +mod_am1_fb0_pm0_fn: + advance_phase_pm0 // -> tmp0 = pg_out + calc_sample_am1_lsr1 // tmp0 = pg_out -> tmp0 = sample + mod_sample_done + +.thumb_func +mod_am1_fb0_pm1_fn: + advance_phase_pm1 // -> tmp0 = pg_out + + calc_sample_am1_lsr1 // tmp0 = pg_out -> tmp0 = sample + mod_sample_done + +.thumb_func +mod_am0_fb1_pm0_fn: + advance_phase_pm0 // -> tmp0 = pg_out + add_fb_fm // tmp0 = pg_out -> tmp0 = pg_out + self_fm() + calc_sample_am0 // tmp0 = pg_out + fm -> tmp0 = sample + str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0] + mod_sample_done + +.thumb_func +mod_am0_fb1_pm1_fn: + advance_phase_pm1 // -> tmp0 = pg_out + add_fb_fm // tmp0 = pg_out -> tmp0 = pg_out + self_fm() + calc_sample_am0 // tmp0 = pg_out + fm -> tmp0 = sample + str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0] + mod_sample_done + +.thumb_func +mod_am1_fb1_pm0_fn: + advance_phase_pm0 // -> tmp0 = pg_out + add_fb_fm // tmp0 = pg_out -> tmp0 = pg_out + self_fm() + calc_sample_am1_lsr1 // tmp0 = pg_out + fm -> tmp0 = sample + str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0] + mod_sample_done + +.thumb_func +mod_am1_fb1_pm1_fn: + advance_phase_pm1 // -> tmp0 = pg_out + add_fb_fm // tmp0 = pg_out -> tmp0 = pg_out + self_fm() + calc_sample_am1_lsr1 // tmp0 = pg_out + fm -> tmp0 = sample + str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0] + mod_sample_done + +.macro alg_sample_done + ldr rl_tmp1, [rl_sample_out] + add rl_tmp1, rl_tmp0 + str rl_tmp1, [rl_sample_out] + adds rl_sample_out, #4 + cmp rl_sample_out, rh_sample_out_end + bge 9f + bx rh_begin_loop +9: bx rh_end_loop +.endm + +// ah these two are the same, just called in differnt places +.macro alg0_f_mod + lsrs rl_tmp1, rl_sample_out, #1 + add rl_tmp1, rh_buffer_mod_buffer_offset + ldrh rl_tmp2, [rl_tmp1] + add rl_tmp0, rl_tmp2 +.endm + +.macro alg1_a_mod + lsrs rl_tmp1, rl_sample_out, #1 + add rl_tmp1, rh_buffer_mod_buffer_offset + ldrh rl_tmp2, [rl_tmp1] + add rl_tmp0, rl_tmp2 +.endm + +// alg0 is FM by the mod slot +.thumb_func +alg0_am0_pm0_fn: + advance_phase_pm0 // -> tmp0 = pg_out + alg0_f_mod // tmp0 = pg_out -> tmp0 = pg_out + mod_slot[s] + calc_sample_am0 // tmp0 = pg_out + mod_fm -> tmp0 = sample + str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0] + alg_sample_done + +.thumb_func +alg0_am0_pm1_fn: + advance_phase_pm1 // -> tmp0 = pg_out + alg0_f_mod // tmp0 = pg_out -> tmp0 = pg_out + mod_slot[s] + calc_sample_am0 // tmp0 = pg_out + mod_fm -> tmp0 = sample + str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0] + alg_sample_done + +.thumb_func +alg0_am1_pm0_fn: + advance_phase_pm0 // -> tmp0 = pg_out + alg0_f_mod // tmp0 = pg_out -> tmp0 = pg_out + mod_slot[s] + calc_sample_am1_lsr2 // tmp0 = pg_out + mod_fm -> tmp0 = sample + str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0] + alg_sample_done + +.thumb_func +alg0_am1_pm1_fn: + advance_phase_pm1 // -> tmp0 = pg_out + alg0_f_mod // tmp0 = pg_out -> tmp0 = pg_out + mod_slot[s] + calc_sample_am1_lsr2 // tmp0 = pg_out + mod_fm -> tmp0 = sample + str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0] + alg_sample_done + +// alg1 is AM by the mod slot +.thumb_func +alg1_am0_pm0_fn: + advance_phase_pm0 // -> tmp0 = pg_out + calc_sample_am0 // tmp0 = pg_out + mod_fm -> tmp0 = sample + alg1_a_mod // tmp0 = sample-> tmp- = sample + mod_slot[s] + alg_sample_done + +.thumb_func +alg1_am0_pm1_fn: + advance_phase_pm1 // -> tmp0 = pg_out + calc_sample_am0 // tmp0 = pg_out + mod_fm -> tmp0 = sample + alg1_a_mod // tmp0 = sample-> tmp- = sample + mod_slot[s] + alg_sample_done + +.thumb_func +alg1_am1_pm0_fn: + advance_phase_pm0 // -> tmp0 = pg_out + calc_sample_am1_lsr2 // tmp0 = pg_out + mod_fm -> tmp0 = sample + alg1_a_mod // tmp0 = sample-> tmp- = sample + mod_slot[s] + alg_sample_done + +.thumb_func +alg1_am1_pm1_fn: + advance_phase_pm1 // -> tmp0 = pg_out + calc_sample_am1_lsr2 // tmp0 = pg_out + mod_fm -> tmp0 = sample + alg1_a_mod // tmp0 = sample-> tmp- = sample + mod_slot[s] + alg_sample_done + +.global slot_render_fn_table +.align 4 +slot_render_fn_table: + .word mod_am0_fb0_pm0_fn + .word mod_am0_fb0_pm1_fn + .word mod_am1_fb0_pm0_fn + .word mod_am1_fb0_pm1_fn + .word mod_am0_fb1_pm0_fn + .word mod_am0_fb1_pm1_fn + .word mod_am1_fb1_pm0_fn + .word mod_am1_fb1_pm1_fn + .word alg0_am0_pm0_fn + .word alg0_am0_pm1_fn + .word alg0_am1_pm0_fn + .word alg0_am1_pm1_fn + .word alg1_am0_pm0_fn + .word alg1_am0_pm1_fn + .word alg1_am1_pm0_fn + .word alg1_am1_pm1_fn + +.macro set_eg_shift_mask + ldrb rl_eg_shift_mask, [rl_slot, #SLOTB_EG_RATE_H] + cmp rl_eg_shift_mask, #0 + beq 9f + ldrb rl_tmp0, [rl_slot, #SLOTB_EG_SHIFT] + movs rl_eg_shift_mask, #1 + lsls rl_eg_shift_mask, rl_tmp0 +9: + subs rl_eg_shift_mask, #1 +.endm + +// rl_tmp0 = eg_out +.macro update_eg_out_tll_lsl3 + //slot->eg_out_tll_lsl3 = std::min(EG_MAX/*EG_MUTE*/, slot->eg_out + slot->tll) << 3; // note EG_MAX not EG_MUTE to avoid overflow check later + ldrh rl_tmp1, [rl_slot, #SLOTH_TLL] + adds rl_tmp0, rl_tmp1 + movs rl_tmp2, #0x1f + lsls rl_tmp2, #4 + cmp rl_tmp0, rl_tmp2 + ble 9f + movs rl_tmp0, rl_tmp2 +9: + lsls rl_tmp0, #3 + str rl_tmp0, [rl_slot, #SLOTW_EG_OUT_TLL_LSL3] +.endm + +#define FRAMEW_EG_STEP_TABLE 0 +#define FRAMEW_SAMPLE_OUT_END_BACK 4 +#define FRAME_SIZE 8 + +.macro set_step_table table_def + // get_attack_step_table + ldr rl_tmp0, =eg_step_tables + ldr rl_tmp1, =\table_def + ldrb rl_tmp2, [rl_slot, SLOTB_EG_RATE_H] + ldrb rl_tmp2, [rl_tmp1, rl_tmp2] + lsls rl_tmp1, rl_tmp2, #25 + bcc 1f + lsrs rl_tmp2, rl_tmp1, #25 + ldrb rl_tmp1, [rl_slot, SLOTB_EG_RATE_L] + lsls rl_tmp1, #3 + adds rl_tmp2, rl_tmp1 +1: + adds rl_tmp0, rl_tmp2 + str rl_tmp0, [sp, FRAMEW_EG_STEP_TABLE] +.endm + +.macro update_eg_vars_for_non_zero_rate + ldrb rl_tmp1, [rl_slot, #SLOTB_RKS] + // slot->eg_rate_l = slot->rks & 3; + lsls rl_tmp2, rl_tmp1, #30 + lsrs rl_tmp2, rl_tmp2, #30 + strb rl_tmp2, [rl_slot, #SLOTB_EG_RATE_L] + // slot->eg_rate_h = std::min(15, p_rate + (slot->rks >> 2)); + lsrs rl_tmp1, #2 + adds rl_tmp0, rl_tmp1 + cmp rl_tmp0, #15 + ble 9f + movs rl_tmp0, #15 +9: + strb rl_tmp0, [rl_slot, #SLOTB_EG_RATE_H] + // slot->eg_shift = (slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0; + // note eg_shift was already zeroed above + movs rl_tmp1, #12 + subs rl_tmp1, rl_tmp0 + bcc 1f + strb rl_tmp1, [rl_slot, #SLOTB_EG_SHIFT] +.endm + +// (slot, nsamples, eg_counter, fn); +.global test_slot_asm +.thumb_func +test_slot_asm: +#if 0 +#define rl_slot r0 i +#define rl_sample_out r1 x +#define rl_eg_counter r2 i +#define rl_eg_shift_mask r3 x +#define rl_tmp0 r4 - +#define rl_tmp1 r5 - +#define rl_tmp2 r6 - +#define rl_interp r7 +#endif + +// rh_begin_loop r8 x +// rh_end_loop r9 x +// rh_sample_out_end r10 x +// rh_fn r11 x +// rh_exp_table r12 x +// rh_mod_buffer_offset lr - + + push {r4-r7, lr} + mov r4, r8 + mov r5, r9 + mov r6, r10 + mov r7, r11 + push {r4-r7} + mov r4, r12 + push {r3, r4} // we want to save r3 as well + sub sp, #FRAME_SIZE + + // note r1 == rl_sample_out + lsrs r5, r3, #3 + bne 1f + // is a mod fn + lsls r4, r1, #1 + ldr rl_sample_out, [rl_slot, #SLOTW_MOD_BUFFER] + add r4, rl_sample_out + b 2f +1: + // is an arg fn + lsls r4, r1, #2 + ldr rl_sample_out, [rl_slot, #SLOTW_BUFFER] + add r4, rl_sample_out +2: + mov rh_sample_out_end, r4 + str r4, [sp, #FRAMEW_SAMPLE_OUT_END_BACK] + + ldr r4, =slot_render_fn_table + lsls r3, #2 + ldr r4, [r4, r3] + mov rh_fn, r4 + + ldr rl_tmp0, =exp_table + mov rh_exp_table, rl_tmp0 + + ldr rl_tmp0, [rl_slot, #SLOTW_BUFFER_MOD_BUFFER_OFFSET] + mov rh_buffer_mod_buffer_offset, rl_tmp0 + + ldr rl_interp, =0xd0000080 + + ldrb rl_tmp0, [rl_slot, #SLOTB_EG_STATE] + cmp rl_tmp0, #ATTACK + bne check_decay +attack: + ldr rl_tmp0, =check_decay + mov rh_end_loop, rl_tmp0 + + set_eg_shift_mask + set_step_table attack_rate_def_table + + // if (slot->eg_out == 0) { + ldrh rl_tmp1, [rl_slot, #SLOTH_EG_OUT] + cmp rl_tmp1, #0 + bne attack_loop_enter + + // early enter decay state + adds rl_eg_counter, #1 + b enter_decay_state + +attack_loop_enter: + ldr rl_tmp0, =attack_loop + mov rh_begin_loop, rl_tmp0 +.thumb_func +attack_loop: + adds rl_eg_counter, #1 + tst rl_eg_counter, rl_eg_shift_mask + beq 1f + bx rh_fn +1: + ldrh rl_tmp0, [rl_slot, #SLOTH_EG_OUT] +// cmp rl_tmp0, #0 +// bhi 1f +// bx rh_fn +//1: + + // uint8_t step = eg_step_table[(eg_counter >> slot->eg_shift) & 7]; + + ldrb rl_tmp1, [rl_slot, #SLOTB_EG_SHIFT] + movs rl_tmp2, rl_eg_counter + lsrs rl_tmp2, rl_tmp1 + lsls rl_tmp2, #29 + lsrs rl_tmp2, #29 + ldr rl_tmp1, [sp, #FRAMEW_EG_STEP_TABLE] + ldrb rl_tmp1, [rl_tmp1, rl_tmp2] + + // slot->eg_out += (~slot->eg_out * step) >> 3; + mvns rl_tmp2, rl_tmp0 + muls rl_tmp2, rl_tmp1 + asrs rl_tmp2, #3 + adds rl_tmp0, rl_tmp2 + strh rl_tmp0, [rl_slot, #SLOTH_EG_OUT] + + update_eg_out_tll_lsl3 + + ldrh rl_tmp0, [rl_slot, #SLOTH_EG_OUT] + cmp rl_tmp0, #0 + beq enter_decay_state + bx rh_fn + +enter_decay_state: + // int p_rate = slot->patch->DR; + ldr rl_tmp0, [rl_slot, #SLOTW_PATCH] + ldrb rl_tmp0, [rl_tmp0, #PATCH_DR] + cmp rl_tmp0, #0 + // set all eg_state, shift, rate_h, rate_l all to zero (if rl_tmp0 is zero) + str rl_tmp0, [rl_slot, #SLOTB_EG_STATE] + beq 1f + update_eg_vars_for_non_zero_rate +1: + movs rl_tmp0, #DECAY + strb rl_tmp0, [rl_slot, #SLOTB_EG_STATE] + // do last sample + mov rh_sample_out_end, rl_sample_out + + ldr rl_tmp0, =check_decay + mov rh_begin_loop, rl_tmp0 + bx rh_fn + +.thumb_func +check_decay: + ldr rl_tmp0, [sp, #FRAMEW_SAMPLE_OUT_END_BACK] + cmp rl_sample_out, rl_tmp0 + beq check_sustain_release // done is too far away + mov rh_sample_out_end, rl_tmp0 + ldrb rl_tmp0, [rl_slot, #SLOTB_EG_STATE] + cmp rl_tmp0, #DECAY + bne check_sustain_release +decay: + ldr rl_tmp0, =check_sustain_release + mov rh_end_loop, rl_tmp0 + + set_eg_shift_mask + set_step_table decay_rate_def_table + + adds rl_tmp0, rl_eg_counter, #1 + tst rl_tmp0, rl_eg_shift_mask + beq decay_loop_enter + + // check ((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL)) + ldrh rl_tmp0, [rl_slot, #SLOTH_EG_OUT] + ldr rl_tmp1, [rl_slot, #SLOTW_PATCH] + ldrb rl_tmp1, [rl_tmp1, #PATCH_SL] + cmp rl_tmp1, #15 + beq decay_loop_enter + + lsrs rl_tmp2, rl_tmp0, #4 + cmp rl_tmp2, rl_tmp1 + bne decay_loop_enter + // early enter sustain state + mov rl_eg_counter, rl_tmp0 + b enter_sustain_state + +decay_loop_enter: + ldr rl_tmp0, =decay_loop + mov rh_begin_loop, rl_tmp0 +.thumb_func +decay_loop: + adds rl_eg_counter, #1 + tst rl_eg_counter, rl_eg_shift_mask + beq 1f + bx rh_fn +1: + //slot->eg_out = static_cast(std::min(EG_MUTE, slot->eg_out + (int) eg_step_table[(eg_counter >> slot->eg_shift) & 7])); + ldr rl_tmp0, [sp, #FRAMEW_EG_STEP_TABLE] + ldrb rl_tmp1, [rl_slot, #SLOTB_EG_SHIFT] + movs rl_tmp2, rl_eg_counter + lsrs rl_tmp2, rl_tmp1 + lsls rl_tmp2, #29 + lsrs rl_tmp2, #29 + ldrb rl_tmp1, [rl_tmp0, rl_tmp2] + ldrh rl_tmp0, [rl_slot, #SLOTH_EG_OUT] + adds rl_tmp0, rl_tmp1 + // todo do we actually need this min check + lsrs rl_tmp1, rl_tmp0, #9 + beq 1f + ldr rl_tmp0, =0x1ff +1: + strh rl_tmp0, [rl_slot, #SLOTH_EG_OUT] + + // uses rl_tmp0 + update_eg_out_tll_lsl3 + + ldrh rl_tmp0, [rl_slot, #SLOTH_EG_OUT] + ldr rl_tmp1, [rl_slot, #SLOTW_PATCH] + ldrb rl_tmp1, [rl_tmp1, #PATCH_SL] + cmp rl_tmp1, #15 + beq 1f + + lsrs rl_tmp2, rl_tmp0, #4 + cmp rl_tmp2, rl_tmp1 + beq enter_sustain_state + +1: + // check for EG_MUTE (0x1ff) + adds rl_tmp1, rl_tmp0, #1 + lsrs rl_tmp1, #9 + bne 1f + bx rh_fn + +1: + // we decayed to zero, but still need to run loop contents once more + mov rh_sample_out_end, rl_sample_out + bx rh_fn + +enter_sustain_state: + // int p_rate = slot->patch->EG ? 0 : slot->patch->RR + ldr rl_tmp2, [rl_slot, #SLOTW_PATCH] + ldrb rl_tmp1, [rl_tmp2, #PATCH_EG] + movs rl_tmp0, #0 + cmp rl_tmp1, #0 + bne 1f + ldrb rl_tmp0, [rl_tmp2, #PATCH_RR] +1: + // set all eg_state, shift, rate_h, rate_l all to zero (if rl_tmp0 is zero) + str rl_tmp0, [rl_slot, #SLOTB_EG_STATE] + cmp rl_tmp0, #0 + beq 1f + update_eg_vars_for_non_zero_rate +1: + movs rl_tmp0, #SUSTAIN + strb rl_tmp0, [rl_slot, #SLOTB_EG_STATE] + // do last sample + mov rh_sample_out_end, rl_sample_out + + ldr rl_tmp0, =check_sustain_release + mov rh_begin_loop, rl_tmp0 + bx rh_fn + +.thumb_func +check_sustain_release: + ldr rl_tmp0, [sp, #FRAMEW_SAMPLE_OUT_END_BACK] + cmp rl_sample_out, rl_tmp0 + beq done + mov rh_sample_out_end, rl_tmp0 + ldrb rl_tmp0, [rl_slot, #SLOTB_EG_STATE] + cmp rl_tmp0, #SUSTAIN + blt done + ldr rl_tmp0, =done + mov rh_end_loop, rl_tmp0 + set_eg_shift_mask + set_step_table decay_rate_def_table + +sustain_release_loop_enter: + ldr rl_tmp0, =sustain_release_loop + mov rh_begin_loop, rl_tmp0 +.thumb_func +sustain_release_loop: + adds rl_eg_counter, #1 + tst rl_eg_counter, rl_eg_shift_mask + beq 1f + bx rh_fn +1: + //slot->eg_out = static_cast(std::min(EG_MUTE, slot->eg_out + (int) eg_step_table[(eg_counter >> slot->eg_shift) & 7])); + ldr rl_tmp0, [sp, #FRAMEW_EG_STEP_TABLE] + ldrb rl_tmp1, [rl_slot, #SLOTB_EG_SHIFT] + movs rl_tmp2, rl_eg_counter + lsrs rl_tmp2, rl_tmp1 + lsls rl_tmp2, #29 + lsrs rl_tmp2, #29 + ldrb rl_tmp1, [rl_tmp0, rl_tmp2] + ldrh rl_tmp0, [rl_slot, #SLOTH_EG_OUT] + adds rl_tmp0, rl_tmp1 + + strh rl_tmp0, [rl_slot, #SLOTH_EG_OUT] + adds rl_tmp2, rl_tmp0, #1 + lsrs rl_tmp1, rl_tmp2, #9 + bne 1f + update_eg_out_tll_lsl3 + bx rh_fn +1: + // we decayed to zero, but still need to run loop contents + mov rh_sample_out_end, rl_sample_out + bx rh_fn +.thumb_func +done: + add sp, #FRAME_SIZE + pop {r2-r7} + mov r12, r3 + mov r8, r4 + mov r9, r5 + mov r10, r6 + mov r11, r7 + + // figure out how many samples we did + lsrs r2, #3 + bne 1f + // is a mod fn + ldr r0, [rl_slot, #SLOTW_MOD_BUFFER] + subs r1, r0 + lsrs r0, r1, #1 + pop {r4-r7, pc} +1: + // is an arg fn + ldr r0, [rl_slot, #SLOTW_BUFFER] + subs r1, r1 + lsrs r1, r0, #2 + pop {r4-r7, pc} + + +#if 0 + movs rl_tmp0, #1 + lsls rl_tmp0, #12 + subs rl_tmp0, #2 + lsls rl_tmp1, rl_eg_counter, #1 + cmp rl_tmp0, rl_tmp1 + bne 1f + bkpt #0 +1: + +#endif \ No newline at end of file diff --git a/pcsound/CMakeLists.txt b/pcsound/CMakeLists.txt index 7988b8bd..1cc62d5d 100644 --- a/pcsound/CMakeLists.txt +++ b/pcsound/CMakeLists.txt @@ -1,11 +1,15 @@ -add_library(pcsound STATIC - pcsound.c pcsound.h - pcsound_bsd.c - pcsound_sdl.c - pcsound_linux.c - pcsound_win32.c - pcsound_internal.h) -target_include_directories(pcsound - INTERFACE "." - PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") -target_link_libraries(pcsound SDL2::mixer) +if (PICO_SDK) + add_library(pcsound INTERFACE) +else() + add_library(pcsound STATIC + pcsound.c pcsound.h + pcsound_bsd.c + pcsound_sdl.c + pcsound_linux.c + pcsound_win32.c + pcsound_internal.h) + target_include_directories(pcsound + INTERFACE "." + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") + target_link_libraries(pcsound SDL2::mixer) +endif() \ No newline at end of file diff --git a/pico_extras_import.cmake b/pico_extras_import.cmake new file mode 100644 index 00000000..706add02 --- /dev/null +++ b/pico_extras_import.cmake @@ -0,0 +1,62 @@ +# This is a copy of /external/pico_extras_import.cmake + +# This can be dropped into an external project to help locate pico-extras +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH)) + set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH}) + message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT)) + set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH)) + set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')") +endif () + +if (NOT PICO_EXTRAS_PATH) + if (PICO_EXTRAS_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_EXTRAS_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + PICO_EXTRAS + GIT_REPOSITORY https://github.com/raspberrypi/pico-extras + GIT_TAG master + ) + if (NOT PICO_EXTRAS) + message("Downloading PICO EXTRAS") + FetchContent_Populate(PICO_EXTRAS) + set(PICO_EXTRAS_PATH ${PICO_EXTRAS_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras") + set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras) + message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}") + else() + message(FATAL_ERROR + "PICO EXTRAS location was not specified. Please set PICO_EXTRAS_PATH or set PICO_EXTRAS_FETCH_FROM_GIT to on to fetch from git." + ) + endif() + endif () +endif () + +set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS") +set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable") +set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS") + +get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_EXTRAS_PATH}) + message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found") +endif () + +set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE) + +add_subdirectory(${PICO_EXTRAS_PATH} pico_extras) \ No newline at end of file diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 00000000..2548382a --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,64 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# todo document + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY git@asic-git.pitowers.org:projectmu/pico_sdk.git + GIT_TAG master + ) + if (NOT pico_sdk) + message("Downloading PICO SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/src/.gitignore b/src/.gitignore index 556dfeaf..34aa6bbe 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -17,6 +17,7 @@ chocolate-setup *.desktop *.txt !CMakeLists.txt +!license.txt *.appdata.xml tags TAGS diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 261683cc..a7529243 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,39 +1,78 @@ -foreach(SUBDIR doom heretic hexen strife setup) +if (PICO_SDK) + set(BINARIES doom) +else() + # comment out heretic/hexen/strife for now, as whilst they may build, how well they work is unclear +# set(BINARIES doom heretic hexen strife setup) + set(BINARIES doom setup) +endif() + +add_subdirectory(adpcm-xq) + +foreach(SUBDIR ${BINARIES}) add_subdirectory("${SUBDIR}") endforeach() # Common source files used by absolutely everything: +cmake_policy(SET CMP0076 NEW) -set(COMMON_SOURCE_FILES +if (NOT PICO_SDK) + set(I_PLATFORM sdl) +else() + set(I_PLATFORM pico) +endif() + +add_library(common INTERFACE) +target_sources(common INTERFACE i_main.c - i_system.c i_system.h + i_system.h m_argv.c m_argv.h m_misc.c m_misc.h) -# Dedicated server (chocolate-server): +target_compile_definitions(common INTERFACE + #HACK_FINALE_E1M1=1 + #HACK_FINALE_SHAREWARE=1 +# DEBUG_MOBJ=1 +) -set(DEDSERV_FILES - d_dedicated.c - d_mode.c d_mode.h - i_timer.c i_timer.h - net_common.c net_common.h - net_dedicated.c net_dedicated.h - net_io.c net_io.h - net_packet.c net_packet.h - net_sdl.c net_sdl.h - net_query.c net_query.h - net_server.c net_server.h - net_structrw.c net_structrw.h - z_native.c z_zone.h) +if (NOT PICO_SDK) + add_library(common_sdl INTERFACE) + target_sources(common_sdl INTERFACE + i_system.c + ) +else() + add_subdirectory(pico) +endif() -add_executable("${PROGRAM_PREFIX}server" WIN32 ${COMMON_SOURCE_FILES} ${DEDSERV_FILES}) -target_include_directories("${PROGRAM_PREFIX}server" - PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") -target_link_libraries("${PROGRAM_PREFIX}server" SDL2::SDL2main SDL2::net) +target_link_libraries(common INTERFACE common_${I_PLATFORM}) -# Source files used by the game binaries (chocolate-doom, etc.) +# todo chocolate-server also doesn't currently build +if (NOT PICO_SDK) + # Dedicated server (chocolate-server): -set(GAME_SOURCE_FILES + set(DEDSERV_FILES + d_dedicated.c + d_mode.c d_mode.h + i_timer.c i_timer.h + net_common.c net_common.h + net_dedicated.c net_dedicated.h + net_io.c net_io.h + net_packet.c net_packet.h + net_sdl.c net_sdl.h + net_query.c net_query.h + net_server.c net_server.h + net_structrw.c net_structrw.h + z_native.c z_zone.h) + + add_executable("${PROGRAM_PREFIX}server" WIN32 ${COMMON_SOURCE_FILES} ${DEDSERV_FILES}) + target_include_directories("${PROGRAM_PREFIX}server" + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") + target_link_libraries("${PROGRAM_PREFIX}server" common SDL2::SDL2main SDL2::net) +endif() + +# Source files used by the game BINARIES (chocolate-doom, etc.) + +add_library(game INTERFACE) +target_sources(game INTERFACE aes_prng.c aes_prng.h d_event.c d_event.h doomkeys.h @@ -44,22 +83,17 @@ set(GAME_SOURCE_FILES d_ticcmd.h deh_str.c deh_str.h gusconf.c gusconf.h - i_cdmus.c i_cdmus.h - i_endoom.c i_endoom.h - i_glob.c i_glob.h - i_input.c i_input.h - i_joystick.c i_joystick.h - i_swap.h - i_midipipe.c i_midipipe.h - i_musicpack.c - i_oplmusic.c - i_pcsound.c - i_sdlmusic.c - i_sdlsound.c - i_sound.c i_sound.h - i_timer.c i_timer.h - i_video.c i_video.h - i_videohr.c i_videohr.h + i_cdmus.h + i_endoom.h + i_glob.h + i_input.h + i_joystick.h + i_swap.h + i_midipipe.h + i_sound.h + i_timer.h + i_video.h + i_videohr.h midifile.c midifile.h mus2mid.c mus2mid.h m_bbox.c m_bbox.h @@ -67,18 +101,8 @@ set(GAME_SOURCE_FILES m_config.c m_config.h m_controls.c m_controls.h m_fixed.c m_fixed.h + net_client.c net_client.h - net_common.c net_common.h - net_dedicated.c net_dedicated.h - net_defs.h - net_gui.c net_gui.h - net_io.c net_io.h - net_loop.c net_loop.h - net_packet.c net_packet.h - net_query.c net_query.h - net_sdl.c net_sdl.h - net_server.c net_server.h - net_structrw.c net_structrw.h sha1.c sha1.h memio.c memio.h tables.c tables.h @@ -89,134 +113,553 @@ set(GAME_SOURCE_FILES w_main.c w_main.h w_wad.c w_wad.h w_file.c w_file.h - w_file_stdc.c - w_file_posix.c - w_file_win32.c - w_merge.c w_merge.h - z_zone.c z_zone.h) + w_file_posix.c + w_file_memory.c + w_merge.c w_merge.h + z_zone.c z_zone.h + + i_oplmusic.c + i_sound.c + +) + +if (NOT PICO_SDK) + add_library(game_sdl INTERFACE) + target_sources(game_sdl INTERFACE + # todo maybe a separate network library + net_common.c net_common.h + net_dedicated.c net_dedicated.h + net_defs.h + net_gui.c net_gui.h + net_io.c net_io.h + net_loop.c net_loop.h + net_packet.c net_packet.h + net_query.c net_query.h + net_sdl.c net_sdl.h + net_server.c net_server.h + net_structrw.c net_structrw.h + + w_file_stdc.c + w_file_win32.c + + i_cdmus.c + i_endoom.c + i_glob.c + i_input.c + i_joystick.c + + i_midipipe.c + i_musicpack.c + i_pcsound.c + i_sdlmusic.c + i_sdlsound.c + i_timer.c + i_video.c + i_videohr.c + ) +else() + add_library(game_pico INTERFACE) +endif() + +target_link_libraries(game INTERFACE game_${I_PLATFORM}) if(MSVC) - list(APPEND GAME_SOURCE_FILES + target_sources(game INTERFACE "../win32/win_opendir.c" "../win32/win_opendir.h") endif() -set(DEHACKED_SOURCE_FILES +target_link_libraries(game INTERFACE common opl) + +add_library(game_deh INTERFACE) +target_sources(game_deh INTERFACE deh_defs.h deh_io.c deh_io.h deh_main.c deh_main.h deh_mapping.c deh_mapping.h deh_text.c) -# Some games support dehacked patches, some don't: +target_link_libraries(game_deh INTERFACE game) -set(SOURCE_FILES ${COMMON_SOURCE_FILES} ${GAME_SOURCE_FILES}) -set(SOURCE_FILES_WITH_DEH ${SOURCE_FILES} ${DEHACKED_SOURCE_FILES}) +if (NOT PICO_SDK) + set(EXTRA_LIBS SDL2::SDL2main SDL2::SDL2 SDL2::mixer SDL2::net textscreen pcsound) + if(SAMPLERATE_FOUND) + list(APPEND EXTRA_LIBS samplerate::samplerate) + endif() + if(PNG_FOUND) + list(APPEND EXTRA_LIBS PNG::PNG) + endif() -set(EXTRA_LIBS SDL2::SDL2main SDL2::SDL2 SDL2::mixer SDL2::net textscreen pcsound opl) -if(SAMPLERATE_FOUND) - list(APPEND EXTRA_LIBS samplerate::samplerate) -endif() -if(PNG_FOUND) - list(APPEND EXTRA_LIBS PNG::PNG) + if ("doom" IN_LIST BINARIES) + if(WIN32) + add_executable("${PROGRAM_PREFIX}doom" WIN32 "${CMAKE_CURRENT_BINARY_DIR}/resource.rc") + else() + add_executable("${PROGRAM_PREFIX}doom") + endif() + target_link_libraries("${PROGRAM_PREFIX}doom" PRIVATE game_deh) + + target_include_directories("${PROGRAM_PREFIX}doom" + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") + target_link_libraries("${PROGRAM_PREFIX}doom" PRIVATE doom ${EXTRA_LIBS}) + + if(MSVC) + set_target_properties("${PROGRAM_PREFIX}doom" PROPERTIES + LINK_FLAGS "/MANIFEST:NO") + endif() + endif() + + if ("heretic" IN_LIST BINARIES) + if(WIN32) + add_executable("${PROGRAM_PREFIX}heretic" WIN32 "${CMAKE_CURRENT_BINARY_DIR}/resource.rc") + else() + add_executable("${PROGRAM_PREFIX}heretic" ) + endif() + target_link_libraries("${PROGRAM_PREFIX}heretic" PRIVATE game_deh) + + target_include_directories("${PROGRAM_PREFIX}heretic" + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") + target_link_libraries("${PROGRAM_PREFIX}heretic" PRIVATE heretic ${EXTRA_LIBS}) + + if(MSVC) + set_target_properties("${PROGRAM_PREFIX}heretic" PROPERTIES + LINK_FLAGS "/MANIFEST:NO") + endif() + endif() + + if ("hexen" IN_LIST BINARIES) + if(WIN32) + add_executable("${PROGRAM_PREFIX}hexen" WIN32 "${CMAKE_CURRENT_BINARY_DIR}/resource.rc") + else() + add_executable("${PROGRAM_PREFIX}hexen") + endif() + target_link_libraries("${PROGRAM_PREFIX}hexen" PRIVATE game) + + target_include_directories("${PROGRAM_PREFIX}hexen" + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") + target_link_libraries("${PROGRAM_PREFIX}hexen" PRIVATE hexen ${EXTRA_LIBS}) + + if(MSVC) + set_target_properties("${PROGRAM_PREFIX}hexen" PROPERTIES + LINK_FLAGS "/MANIFEST:NO") + endif() + endif() + + if ("strife" IN_LIST BINARIES) + if(WIN32) + add_executable("${PROGRAM_PREFIX}strife" WIN32 ${SOURCE_FILES_WITH_DEH} "${CMAKE_CURRENT_BINARY_DIR}/resource.rc") + else() + add_executable("${PROGRAM_PREFIX}strife" ${SOURCE_FILES_WITH_DEH}) + endif() + target_link_libraries("${PROGRAM_PREFIX}strife" PRIVATE game_deh) + + target_include_directories("${PROGRAM_PREFIX}strife" + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") + target_link_libraries("${PROGRAM_PREFIX}strife" PRIVATE strife ${EXTRA_LIBS}) + + if(MSVC) + set_target_properties("${PROGRAM_PREFIX}strife" PROPERTIES + LINK_FLAGS "/MANIFEST:NO") + endif() + endif() + + # Source files needed for chocolate-setup: + + if ("setup" IN_LIST BINARIES) + set(SETUP_FILES + deh_str.c deh_str.h + d_mode.c d_mode.h + d_iwad.c d_iwad.h + i_timer.c i_timer.h + m_config.c m_config.h + m_controls.c m_controls.h + net_io.c net_io.h + net_packet.c net_packet.h + net_sdl.c net_sdl.h + net_query.c net_query.h + net_structrw.c net_structrw.h + z_native.c z_zone.h) + + if(WIN32) + add_executable("${PROGRAM_PREFIX}setup" WIN32 ${SETUP_FILES} "${CMAKE_CURRENT_BINARY_DIR}/setup-res.rc") + else() + add_executable("${PROGRAM_PREFIX}setup" ${SETUP_FILES} ) + endif() + target_link_libraries("${PROGRAM_PREFIX}setup" PRIVATE setup common) + + target_include_directories("${PROGRAM_PREFIX}setup" + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") + if (NOT PICO_SDK) + target_link_libraries("${PROGRAM_PREFIX}setup" PRIVATE SDL2::SDL2main SDL2::SDL2 SDL2::mixer SDL2::net) + endif() + target_link_libraries("${PROGRAM_PREFIX}setup" PRIVATE textscreen) + + if(MSVC) + set_target_properties("${PROGRAM_PREFIX}setup" PROPERTIES + LINK_FLAGS "/MANIFEST:NO") + endif() + endif() + + add_executable(midiread midifile.c z_native.c i_system.c m_argv.c m_misc.c) + target_compile_definitions(midiread PRIVATE "-DTEST") + target_include_directories(midiread PRIVATE "." "${CMAKE_CURRENT_BINARY_DIR}/../") + + if (NOT PICO_SDK) + target_link_libraries(midiread SDL2::SDL2main SDL2::SDL2) + endif() + + add_executable(mus2mid mus2mid.c memio.c z_native.c i_system.c m_argv.c m_misc.c) + target_compile_definitions(mus2mid PRIVATE "-DSTANDALONE") + target_include_directories(mus2mid PRIVATE "." "${CMAKE_CURRENT_BINARY_DIR}/../") + if (NOT PICO_SDK) + target_link_libraries(mus2mid SDL2::SDL2main SDL2::SDL2) + endif() +endif() # NOT PICO_SDK +add_library(small_doom_common INTERFACE) + +target_link_libraries(small_doom_common INTERFACE adpcm-lib) # handy to have about + +target_compile_definitions(small_doom_common INTERFACE + SHRINK_MOBJ=1 # may break saved games + + DOOM_ONLY=1 # don't support hexxen etc. + DOOM_SMALL=1 + DOOM_CONST=1 # thread const thru lots of places + + SOUND_LOW_PASS=1 + NUM_SOUND_CHANNELS=8 # sounds ok (actually that is how many are used by default) + + # functionality + NO_USE_CHECKSUM=1 + NO_USE_RELOAD=1 + USE_SINGLE_IWAD=1 + NO_USE_WIPE=1 #memory if nothing else - we can fix this (you can do it) + NO_USE_JOYSTICK=1 + NO_USE_DEH=1 + NO_USE_MUSIC_PACKS=1 + + USE_FLAT_MAX_256=1 + USE_MEMMAP_ONLY=1 + + USE_LIGHTMAP_INDEXES=1 # saves about 9K + + #FRACBITS=15 # ha ha doesn't seem to be passed thru everywhere! + #NO_USE_ENDDOOM=1 + #NO_DRAW_VISPLANES=1 + + USE_ERASE_FRAME=1 # + NO_DRAW_MID=1 + NO_DRAW_TOP=1 + NO_DRAW_BOTTOM=1 + NO_DRAW_MASKED=1 + NO_DRAW_SKY=1 + #NO_DRAW_SPANS=1 + NO_DRAW_SPRITES=1 + NO_DRAW_PSPRITES=1 + +# NO_VISPLANES=1 + NO_VISPLANE_GUTS=1 + NO_VISPLANE_CACHES=1 # todo these might be fine as they can be temporary + NO_DRAWSEGS=1 + NO_VISSPRITES=1 + + + NO_MASKED_FLOOR_CLIP=1 # not needed with PD_ rendering and with floor clip it we have occasional visual glitches with sprites split across visplanes + + PD_DRAW_COLUMNS=1 + PD_DRAW_MARKERS=1 + PD_DRAW_PLANES=1 + + PD_SCALE_SORT=1 + + PD_CLIP_WALLS=1 + PD_QUANTIZE=1 + + PD_SANITY=1 + + #MU_STATS=1 + PD_COLUMNS=1 + PICO_DOOM=1 + NO_USE_DS_COLORMAP=1 + NO_USE_DC_COLORMAP=1 + + #PRINT_COLORMAPS=1 + #PRINT_PALETTE=1 + USE_READONLY_MMAP=1 + +# ----------------------------------------------------------------- +# MUSIC RELATED +# ----------------------------------------------------------------- + NO_USE_TIMIDITY=1 + NO_USE_GUS=1 + NO_USE_LIBSAMPLERATE=1 + + # slightly slower but only uses 1K of sin tables vs 9K + EMU8950_NO_WAVE_TABLE_MAP=1 + + EMU8950_NO_TLL=1 # don't use lookup table for total level + EMU8950_NO_FLOAT=1 # double check there is no float + EMU8950_NO_TIMER=1 # disable timer which isn't used + EMU8950_NO_TEST_FLAG=1 # disable test flags (which aren't used) + EMU8950_SIMPLER_NOISE=1 # only generate noise bit when needed + EMU8950_SHORT_NOISE_UPDATE_CHECK=1 # only update short noise if it is used + # actually this doesn't make things faster + #EMU8950_LINEAR_NEG_NOT_NOT=1 # negative values rounded towrds -infinity not 0 without this; does it matter? + + EMU8950_LINEAR_SKIP=1 # skip silent slots + EMU8950_LINEAR_END_OF_NOTE_OPTIMIZATION # early out envelope when DECAY/SUSTAIN/RELEASE envelope reaches mute + EMU8950_NO_PERCUSSION_MODE=1 # rhythm only mode (doom doesn't use percussion; whhhaaaah!?) + EMU8950_LINEAR=1 # reorganize to do linear runs of channels + + # things we really don't care about but need for diff-ing with non linear version + # BEWARE - need to turn this off for comparison with on device version which doesn't do nitpicks (though could i guess) + + EMU8950_ASM=1 +# EMU8950_NIT_PICKS=1 +# DUMPO=1 + +# ----------------------------------------------------------------- +# FLASH SIZE +# ----------------------------------------------------------------- + NO_USE_STATE_MISC #doesn't appear to be used anyway - perhaps in doom only + +# ----------------------------------------------------------------- +# RAM SIZE +# ----------------------------------------------------------------- + USE_RAW_MAPNODE=1 + USE_RAW_MAPVERTEX=1 + USE_RAW_MAPSEG=1 + USE_RAW_MAPLINEDEF=1 + # USE_RAW_MAPSUBSECTOR=1 # obsolete... now WHD + # USE_RAW_MAPSIDEDEF=1 obsolete + USE_RAW_MAPTHING=1 + + USE_INDEX_LINEBUFFER=1 + NO_USE_ZLIGHT=1 + NO_Z_ZONE_ID=1 # seems unused + + Z_MALOOC_EXTRA_DATA=1 + USE_THINKER_POOL=1 + NO_INTERCEPTS_OVERRUN=1 +# INCLUDE_SOUND_C_IN_S_SOUND=1 # avoid issues with non static const array +# ----------------------------------------------------------------- +# IMMUTABLE +# ----------------------------------------------------------------- + + TEMP_IMMUTABLE_DISABLED=1 + USE_CONST_SFX=1 + USE_CONST_MUSIC=1 + + NO_DEMO_RECORDING=1 + PICO_NO_TIMING_DEMO=1 + NO_USE_EXIT=1 # not sure whether we have an exit... if so what does it need to do? + + PICO_DEBUG_PIN_BASE=18 + PICO_DEBUG_PIN_COUNT=2 +) + +target_compile_options(small_doom_common INTERFACE + -Wall + -Wno-unused-function + -Wno-unused-but-set-variable + -Wno-unused-variable + ) + +if (TARGET chocolate-doom) + target_compile_definitions(chocolate-doom PRIVATE + USE_FLAT_MAX_256=1 + ) endif() -if(WIN32) - add_executable("${PROGRAM_PREFIX}doom" WIN32 ${SOURCE_FILES_WITH_DEH} "${CMAKE_CURRENT_BINARY_DIR}/resource.rc") +function(add_doom_tiny SUFFIX RENDER_LIB) + add_executable(doom_tiny${SUFFIX}) + target_link_libraries(doom_tiny${SUFFIX} PRIVATE game) + + target_sources(doom_tiny${SUFFIX} PRIVATE + tiny_huff.c + musx_decoder.c + image_decoder.c + ) + target_include_directories(doom_tiny${SUFFIX} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") + target_link_libraries(doom_tiny${SUFFIX} PRIVATE doom ${EXTRA_LIBS}) + + target_link_libraries(doom_tiny${SUFFIX} PRIVATE small_doom_common) + + if (PICO_SDK) + pico_set_float_implementation(doom_tiny${SUFFIX} none) + pico_set_double_implementation(doom_tiny${SUFFIX} none) + pico_add_extra_outputs(doom_tiny${SUFFIX}) + endif() + + target_compile_options(doom_tiny${SUFFIX} PRIVATE + -Wno-format-truncation) + target_compile_definitions(doom_tiny${SUFFIX} PRIVATE + DOOM_TINY=1 + EMU8950_SLOT_RENDER=1 + + SHRINK_MOBJ=1 # may break saved games + NO_RDRAW=1 + + #NO_DRAW_MENU=1 + # SDK minimilization + PICO_TIME_DEFAULT_ALARM_POOL_DISABLED=1 + PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS=1 + PICO_DISABLE_SHARED_IRQ_HANDLERS=1 + PICO_USE_OPTIMISTIC_SBRK=1 + + # MUSIC + USE_EMU8950_OPL=1 + USE_DIRECT_MIDI_LUMP=1 + + NO_USE_NET=1 # standard networking + USE_PICO_NET=1 + # for vgaboard + PICO_DEFAULT_I2C=1 + PICO_DEFAULT_I2C_SDA_PIN=18 + PICO_DEFAULT_I2C_SCL_PIN=19 + + #NO_USE_LOAD=1 + #NO_USE_SAVE=1 + $<$:NO_FILE_ACCESS=1> + + SAVE_COMPRESSED=1 + LOAD_COMPRESSED=1 + + NO_USE_ARGS=1 + NO_USE_SAVE_CONFIG=1 + NO_USE_FLOAT=1 + + USE_VANILLA_KEYBOARD_MAPPING_ONLY=1 + + #FORCE_NODRAW=1 + + NO_USE_LOADING_DISK=1 + + # -- these aren't used any more, we adapt based on DOOM_TINY + #NO_USE_ST=1 # for now skip all the status crap + #NO_USE_DEFAULT_ST_LIB=1 + + #NO_PAGE_DRAWER=1 # splash screens + #NO_HU_DRAWER=1 + + USE_WHD=1 + + + NO_Z_MALLOC_USER_PTR=1 # onyl needed for freeing textures i think + + FIXED_SCREENWIDTH=1 + FLOOR_CEILING_CLIP_8BIT=1 + +# NO_USE_WI=1 + USE_MUSX=1 + MUSX_COMPRESSED=1 + + NO_SCREENSHOT=1 + + NO_USE_BOUND_CONFIG=1 + + USE_FPS=1 + +# PICO_DOOM_INFO=1 + ) + target_compile_definitions(doom_tiny${SUFFIX} PRIVATE + #FORCE_NODRAW=1 + USE_MEMORY_WAD=1 + PICO_DEBUG_MALLOC=1 + EMU8950_NO_RATECONV=1 + + PICO_CORE1_STACK_SIZE=0x4f8 + + NO_IERROR=1 + ) + if (PICO_ON_DEVICE) + target_compile_options(doom_tiny${SUFFIX} PRIVATE -fno-common -fdata-sections -Wl,--sort-section=alignment) + target_compile_definitions(doom_tiny${SUFFIX} PRIVATE + NO_ZONE_DEBUG=1 + ) + #target_link_libraries(doom_tiny${SUFFIX} PRIVATE hardware_flash) + endif() + if (FORCE_DEBUG) + target_compile_options(doom_tiny${SUFFIX} PRIVATE -g) + endif() + target_link_libraries(doom_tiny${SUFFIX} PRIVATE ${RENDER_LIB}) + set(PICO_HACK 0) + set(STAMP_HACK 0) + if (PICO_HACK) + target_compile_definitions(doom_tiny${SUFFIX} PRIVATE + PICO_AUDIO_I2S_DATA_PIN=20 + PICO_AUDIO_I2S_CLOCK_PIN_BASE=21 + NO_USE_UART=1 + ) + pico_enable_stdio_uart(doom_tiny${SUFFIX} 0) + elseif(STAMP_HACK) + target_compile_definitions(doom_tiny${SUFFIX} PRIVATE + PICO_AUDIO_I2S_DATA_PIN=22 + PICO_AUDIO_I2S_CLOCK_PIN_BASE=23 + NO_USE_UART=1 + INCREASE_I2S_DRIVE_STRENGTH=1 + ) + pico_enable_stdio_uart(doom_tiny${SUFFIX} 0) + endif() +endfunction() + +add_subdirectory(whd_gen) + +add_library(render_newhope INTERFACE) +target_sources(render_newhope INTERFACE + pd_render.cpp + ) +target_compile_definitions(render_newhope INTERFACE + PICODOOM_RENDER_NEWHOPE=1 + MERGE_DISTSCALE0_INTO_VIEWCOSSINANGLE=1 + NO_USE_ZLIGHT=1 + PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT=4 + PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS=1 + PICO_SCANVIDEO_SCANLINE_RELEASE_FUNCTION=1 + ) +if (PICO_ON_DEVICE) + target_compile_definitions(render_newhope INTERFACE + PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS=164 + ) else() - add_executable("${PROGRAM_PREFIX}doom" ${SOURCE_FILES_WITH_DEH}) + # we cannot rewrite the buffers in host mode so we need them to be big enough for text mode + target_compile_definitions(render_newhope INTERFACE + PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS=324 + ) endif() -target_include_directories("${PROGRAM_PREFIX}doom" - PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") -target_link_libraries("${PROGRAM_PREFIX}doom" doom ${EXTRA_LIBS}) +if (PICO_SDK) + add_doom_tiny("" render_newhope) + add_library(tiny_settings INTERFACE) + target_compile_definitions(tiny_settings INTERFACE + WHD_SUPER_TINY=1 + DEMO1_ONLY=1 + NO_USE_FINALE_CAST=1 + NO_USE_FINALE_BUNNY=1 + ) + target_compile_definitions(doom_tiny PRIVATE + TINY_WAD_ADDR=0x10040000 + ) + target_link_libraries(doom_tiny PRIVATE tiny_settings) + add_doom_tiny(_nost render_newhope) -if(MSVC) - set_target_properties("${PROGRAM_PREFIX}doom" PROPERTIES - LINK_FLAGS "/MANIFEST:NO") + target_compile_definitions(doom_tiny_nost PRIVATE + # NO_USE_FINALE_CAST=1 # note this is only used by doom II but doesnt take that much more space + TINY_WAD_ADDR=0x10048000 + ) + if (PICO_ON_DEVICE) + suppress_tinyusb_warnings() + add_doom_tiny(_usb render_newhope) + target_link_libraries(doom_tiny_usb PRIVATE tiny_settings pico_cd) + target_compile_definitions(doom_tiny_usb PRIVATE + USB_SUPPORT=1 + TINY_WAD_ADDR=0x10042000 # ugh tinyusb is >8K currently!! + ) + add_doom_tiny(_nost_usb render_newhope) + target_link_libraries(doom_tiny_nost_usb PRIVATE pico_cd) + target_compile_definitions(doom_tiny_nost_usb PRIVATE + USB_SUPPORT=1 + TINY_WAD_ADDR=0x10048000 + ) + endif() endif() -if(WIN32) - add_executable("${PROGRAM_PREFIX}heretic" WIN32 ${SOURCE_FILES_WITH_DEH} "${CMAKE_CURRENT_BINARY_DIR}/resource.rc") -else() - add_executable("${PROGRAM_PREFIX}heretic" ${SOURCE_FILES_WITH_DEH}) -endif() -target_include_directories("${PROGRAM_PREFIX}heretic" - PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") -target_link_libraries("${PROGRAM_PREFIX}heretic" heretic ${EXTRA_LIBS}) - -if(MSVC) - set_target_properties("${PROGRAM_PREFIX}heretic" PROPERTIES - LINK_FLAGS "/MANIFEST:NO") -endif() - -if(WIN32) - add_executable("${PROGRAM_PREFIX}hexen" WIN32 ${SOURCE_FILES} "${CMAKE_CURRENT_BINARY_DIR}/resource.rc") -else() - add_executable("${PROGRAM_PREFIX}hexen" ${SOURCE_FILES}) -endif() - -target_include_directories("${PROGRAM_PREFIX}hexen" - PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") -target_link_libraries("${PROGRAM_PREFIX}hexen" hexen ${EXTRA_LIBS}) - -if(MSVC) - set_target_properties("${PROGRAM_PREFIX}hexen" PROPERTIES - LINK_FLAGS "/MANIFEST:NO") -endif() - -if(WIN32) - add_executable("${PROGRAM_PREFIX}strife" WIN32 ${SOURCE_FILES_WITH_DEH} "${CMAKE_CURRENT_BINARY_DIR}/resource.rc") -else() - add_executable("${PROGRAM_PREFIX}strife" ${SOURCE_FILES_WITH_DEH}) -endif() - -target_include_directories("${PROGRAM_PREFIX}strife" - PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") -target_link_libraries("${PROGRAM_PREFIX}strife" strife ${EXTRA_LIBS}) - -if(MSVC) - set_target_properties("${PROGRAM_PREFIX}strife" PROPERTIES - LINK_FLAGS "/MANIFEST:NO") -endif() - -# Source files needed for chocolate-setup: - -set(SETUP_FILES - deh_str.c deh_str.h - d_mode.c d_mode.h - d_iwad.c d_iwad.h - i_timer.c i_timer.h - m_config.c m_config.h - m_controls.c m_controls.h - net_io.c net_io.h - net_packet.c net_packet.h - net_sdl.c net_sdl.h - net_query.c net_query.h - net_structrw.c net_structrw.h - z_native.c z_zone.h) - -if(WIN32) - add_executable("${PROGRAM_PREFIX}setup" WIN32 ${SETUP_FILES} ${COMMON_SOURCE_FILES} "${CMAKE_CURRENT_BINARY_DIR}/setup-res.rc") -else() - add_executable("${PROGRAM_PREFIX}setup" ${SETUP_FILES} ${COMMON_SOURCE_FILES}) -endif() - -target_include_directories("${PROGRAM_PREFIX}setup" - PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") -target_link_libraries("${PROGRAM_PREFIX}setup" SDL2::SDL2main SDL2::SDL2 SDL2::mixer SDL2::net setup textscreen) - -if(MSVC) - set_target_properties("${PROGRAM_PREFIX}setup" PROPERTIES - LINK_FLAGS "/MANIFEST:NO") -endif() - -add_executable(midiread midifile.c z_native.c i_system.c m_argv.c m_misc.c) -target_compile_definitions(midiread PRIVATE "-DTEST") -target_include_directories(midiread PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") -target_link_libraries(midiread SDL2::SDL2main SDL2::SDL2) - -add_executable(mus2mid mus2mid.c memio.c z_native.c i_system.c m_argv.c m_misc.c) -target_compile_definitions(mus2mid PRIVATE "-DSTANDALONE") -target_include_directories(mus2mid PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") -target_link_libraries(mus2mid SDL2::SDL2main SDL2::SDL2) diff --git a/src/adpcm-xq/CMakeLists.txt b/src/adpcm-xq/CMakeLists.txt new file mode 100644 index 00000000..770fc177 --- /dev/null +++ b/src/adpcm-xq/CMakeLists.txt @@ -0,0 +1,10 @@ +add_library(adpcm-lib INTERFACE) +target_sources(adpcm-lib INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/adpcm-lib.c) + +target_include_directories(adpcm-lib INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +#if (NOT PICO_ON_DEVICE) +# add_executable(adpcm-xq adpcm-xq.c) +# target_link_libraries(adpcm-xq adpcm-lib) +#endif() diff --git a/src/adpcm-xq/adpcm-lib.c b/src/adpcm-xq/adpcm-lib.c new file mode 100644 index 00000000..5c6c1ef4 --- /dev/null +++ b/src/adpcm-xq/adpcm-lib.c @@ -0,0 +1,400 @@ +//////////////////////////////////////////////////////////////////////////// +// **** ADPCM-XQ **** // +// Xtreme Quality ADPCM Encoder/Decoder // +// Copyright (c) 2015 David Bryant. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "adpcm-lib.h" + +/* This module encodes and decodes 4-bit ADPCM (DVI/IMA varient). ADPCM data is divided + * into independently decodable blocks that can be relatively small. The most common + * configuration is to store 505 samples into a 256 byte block, although other sizes are + * permitted as long as the number of samples is one greater than a multiple of 8. When + * multiple channels are present, they are interleaved in the data with an 8-sample + * interval. + */ + +/********************************* 4-bit ADPCM encoder ********************************/ + +#define CLIP(data, min, max) \ +if ((data) > (max)) data = max; \ +else if ((data) < (min)) data = min; + +/* step table */ +static const uint16_t step_table[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, + 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, + 32767 +}; + +/* step index tables */ +static const int index_table[] = { + /* adpcm data size is 4 */ + -1, -1, -1, -1, 2, 4, 6, 8 +}; + +struct adpcm_channel { + int32_t pcmdata; // current PCM value + int32_t error, weight, history [2]; // for noise shaping + int8_t index; // current index into step size table +}; + +struct adpcm_context { + struct adpcm_channel channels [2]; + int num_channels, lookahead, noise_shaping; +}; + +/* Create ADPCM encoder context with given number of channels. + * The returned pointer is used for subsequent calls. Note that + * even though an ADPCM encoder could be set up to encode frames + * independently, we use a context so that we can use previous + * data to improve quality (this encoder might not be optimal + * for encoding independent frames). + */ + +void *adpcm_create_context (int num_channels, int lookahead, int noise_shaping, int32_t initial_deltas [2]) +{ + struct adpcm_context *pcnxt = malloc (sizeof (struct adpcm_context)); + int ch, i; + + memset (pcnxt, 0, sizeof (struct adpcm_context)); + pcnxt->noise_shaping = noise_shaping; + pcnxt->num_channels = num_channels; + pcnxt->lookahead = lookahead; + + // given the supplied initial deltas, search for and store the closest index + + for (ch = 0; ch < num_channels; ++ch) + for (i = 0; i <= 88; i++) + if (i == 88 || initial_deltas [ch] < ((int32_t) step_table [i] + step_table [i+1]) / 2) { + pcnxt->channels [ch].index = i; + break; + } + + return pcnxt; +} + +/* Free the ADPCM encoder context. + */ + +void adpcm_free_context (void *p) +{ + struct adpcm_context *pcnxt = (struct adpcm_context *) p; + + free (pcnxt); +} + +static void set_decode_parameters (struct adpcm_context *pcnxt, int32_t *init_pcmdata, int8_t *init_index) +{ + int ch; + + for (ch = 0; ch < pcnxt->num_channels; ch++) { + pcnxt->channels[ch].pcmdata = init_pcmdata[ch]; + pcnxt->channels[ch].index = init_index[ch]; + } +} + +static void get_decode_parameters (struct adpcm_context *pcnxt, int32_t *init_pcmdata, int8_t *init_index) +{ + int ch; + + for (ch = 0; ch < pcnxt->num_channels; ch++) { + init_pcmdata[ch] = pcnxt->channels[ch].pcmdata; + init_index[ch] = pcnxt->channels[ch].index; + } +} + +static double minimum_error (const struct adpcm_channel *pchan, int nch, int32_t csample, const int16_t *sample, int depth, int *best_nibble) +{ + int32_t delta = csample - pchan->pcmdata; + struct adpcm_channel chan = *pchan; + int step = step_table[chan.index]; + int trial_delta = (step >> 3); + int nibble, nibble2; + double min_error; + + if (delta < 0) { + int mag = (-delta << 2) / step; + nibble = 0x8 | (mag > 7 ? 7 : mag); + } + else { + int mag = (delta << 2) / step; + nibble = mag > 7 ? 7 : mag; + } + + if (nibble & 1) trial_delta += (step >> 2); + if (nibble & 2) trial_delta += (step >> 1); + if (nibble & 4) trial_delta += step; + if (nibble & 8) trial_delta = -trial_delta; + + chan.pcmdata += trial_delta; + CLIP(chan.pcmdata, -32768, 32767); + if (best_nibble) *best_nibble = nibble; + min_error = (double) (chan.pcmdata - csample) * (chan.pcmdata - csample); + + if (depth) { + chan.index += index_table[nibble & 0x07]; + CLIP(chan.index, 0, 88); + min_error += minimum_error (&chan, nch, sample [nch], sample + nch, depth - 1, NULL); + } + else + return min_error; + + for (nibble2 = 0; nibble2 <= 0xF; ++nibble2) { + double error; + + if (nibble2 == nibble) + continue; + + chan = *pchan; + trial_delta = (step >> 3); + + if (nibble2 & 1) trial_delta += (step >> 2); + if (nibble2 & 2) trial_delta += (step >> 1); + if (nibble2 & 4) trial_delta += step; + if (nibble2 & 8) trial_delta = -trial_delta; + + chan.pcmdata += trial_delta; + CLIP(chan.pcmdata, -32768, 32767); + + error = (double) (chan.pcmdata - csample) * (chan.pcmdata - csample); + + if (error < min_error) { + chan.index += index_table[nibble2 & 0x07]; + CLIP(chan.index, 0, 88); + error += minimum_error (&chan, nch, sample [nch], sample + nch, depth - 1, NULL); + + if (error < min_error) { + if (best_nibble) *best_nibble = nibble2; + min_error = error; + } + } + } + + return min_error; +} + +static uint8_t encode_sample (struct adpcm_context *pcnxt, int ch, const int16_t *sample, int num_samples) +{ + struct adpcm_channel *pchan = pcnxt->channels + ch; + int32_t csample = *sample; + int depth = num_samples - 1, nibble; + int step = step_table[pchan->index]; + int trial_delta = (step >> 3); + + if (pcnxt->noise_shaping == NOISE_SHAPING_DYNAMIC) { + int32_t sam = (3 * pchan->history [0] - pchan->history [1]) >> 1; + int32_t temp = csample - (((pchan->weight * sam) + 512) >> 10); + int32_t shaping_weight; + + if (sam && temp) pchan->weight -= (((sam ^ temp) >> 29) & 4) - 2; + pchan->history [1] = pchan->history [0]; + pchan->history [0] = csample; + + shaping_weight = (pchan->weight < 256) ? 1024 : 1536 - (pchan->weight * 2); + temp = -((shaping_weight * pchan->error + 512) >> 10); + + if (shaping_weight < 0 && temp) { + if (temp == pchan->error) + temp = (temp < 0) ? temp + 1 : temp - 1; + + pchan->error = -csample; + csample += temp; + } + else + pchan->error = -(csample += temp); + } + else if (pcnxt->noise_shaping == NOISE_SHAPING_STATIC) + pchan->error = -(csample -= pchan->error); + + if (depth > pcnxt->lookahead) + depth = pcnxt->lookahead; + + minimum_error (pchan, pcnxt->num_channels, csample, sample, depth, &nibble); + + if (nibble & 1) trial_delta += (step >> 2); + if (nibble & 2) trial_delta += (step >> 1); + if (nibble & 4) trial_delta += step; + if (nibble & 8) trial_delta = -trial_delta; + + pchan->pcmdata += trial_delta; + pchan->index += index_table[nibble & 0x07]; + CLIP(pchan->index, 0, 88); + CLIP(pchan->pcmdata, -32768, 32767); + + if (pcnxt->noise_shaping) + pchan->error += pchan->pcmdata; + + return nibble; +} + +static void encode_chunks (struct adpcm_context *pcnxt, uint8_t **outbuf, size_t *outbufsize, const int16_t **inbuf, int inbufcount) +{ + const int16_t *pcmbuf; + int chunks, ch, i; + + chunks = (inbufcount - 1) / 8; + *outbufsize += (chunks * 4) * pcnxt->num_channels; + + while (chunks--) + { + for (ch = 0; ch < pcnxt->num_channels; ch++) + { + pcmbuf = *inbuf + ch; + + for (i = 0; i < 4; i++) { + **outbuf = encode_sample (pcnxt, ch, pcmbuf, chunks * 8 + (3 - i) * 2 + 2); + pcmbuf += pcnxt->num_channels; + **outbuf |= encode_sample (pcnxt, ch, pcmbuf, chunks * 8 + (3 - i) * 2 + 1) << 4; + pcmbuf += pcnxt->num_channels; + (*outbuf)++; + } + } + + *inbuf += 8 * pcnxt->num_channels; + } +} + +/* Encode a block of 16-bit PCM data into 4-bit ADPCM. + * + * Parameters: + * p the context returned by adpcm_begin() + * outbuf destination buffer + * outbufsize pointer to variable where the number of bytes written + * will be stored + * inbuf source PCM samples + * inbufcount number of composite PCM samples provided (note: this is + * the total number of 16-bit samples divided by the number + * of channels) + * + * Returns 1 (for success as there is no error checking) + */ + +int adpcm_encode_block (void *p, uint8_t *outbuf, size_t *outbufsize, const int16_t *inbuf, int inbufcount) +{ + struct adpcm_context *pcnxt = (struct adpcm_context *) p; + int32_t init_pcmdata[2]; + int8_t init_index[2]; + int ch; + + *outbufsize = 0; + + if (!inbufcount) + return 1; + + get_decode_parameters(pcnxt, init_pcmdata, init_index); + + for (ch = 0; ch < pcnxt->num_channels; ch++) { + init_pcmdata[ch] = *inbuf++; + outbuf[0] = init_pcmdata[ch]; + outbuf[1] = init_pcmdata[ch] >> 8; + outbuf[2] = init_index[ch]; + outbuf[3] = 0; + + outbuf += 4; + *outbufsize += 4; + } + + set_decode_parameters(pcnxt, init_pcmdata, init_index); + encode_chunks (pcnxt, &outbuf, outbufsize, &inbuf, inbufcount); + + return 1; +} + +/********************************* 4-bit ADPCM decoder ********************************/ + +/* Decode the block of ADPCM data into PCM. This requires no context because ADPCM blocks + * are indeppendently decodable. This assumes that a single entire block is always decoded; + * it must be called multiple times for multiple blocks and cannot resume in the middle of a + * block. + * + * Parameters: + * outbuf destination for interleaved PCM samples + * inbuf source ADPCM block + * inbufsize size of source ADPCM block + * channels number of channels in block (must be determined from other context) + * + * Returns number of converted composite samples (total samples divided by number of channels) + */ + +int adpcm_decode_block (int16_t *outbuf, const uint8_t *inbuf, size_t inbufsize, int channels) +{ + int ch, samples = 1, chunks; + int32_t pcmdata[2]; + int8_t index[2]; + + if (inbufsize < (uint32_t) channels * 4) + return 0; + + for (ch = 0; ch < channels; ch++) { + *outbuf++ = pcmdata[ch] = (int16_t) (inbuf [0] | (inbuf [1] << 8)); + index[ch] = inbuf [2]; + + if (index [ch] < 0 || index [ch] > 88 || inbuf [3]) // sanitize the input a little... + return 0; + + inbufsize -= 4; + inbuf += 4; + } + + chunks = inbufsize / (channels * 4); + samples += chunks * 8; + + while (chunks--) { + int ch, i; + + for (ch = 0; ch < channels; ++ch) { + + for (i = 0; i < 4; ++i) { + int step = step_table [index [ch]], delta = step >> 3; + + if (*inbuf & 1) delta += (step >> 2); + if (*inbuf & 2) delta += (step >> 1); + if (*inbuf & 4) delta += step; + if (*inbuf & 8) delta = -delta; + + pcmdata[ch] += delta; + index[ch] += index_table [*inbuf & 0x7]; + CLIP(index[ch], 0, 88); + CLIP(pcmdata[ch], -32768, 32767); + outbuf [i * 2 * channels] = pcmdata[ch]; + + step = step_table [index [ch]], delta = step >> 3; + + if (*inbuf & 0x10) delta += (step >> 2); + if (*inbuf & 0x20) delta += (step >> 1); + if (*inbuf & 0x40) delta += step; + if (*inbuf & 0x80) delta = -delta; + + pcmdata[ch] += delta; + index[ch] += index_table [(*inbuf >> 4) & 0x7]; + CLIP(index[ch], 0, 88); + CLIP(pcmdata[ch], -32768, 32767); + outbuf [(i * 2 + 1) * channels] = pcmdata[ch]; + + inbuf++; + } + + outbuf++; + } + + outbuf += channels * 7; + } + + return samples; +} + diff --git a/src/adpcm-xq/adpcm-lib.h b/src/adpcm-xq/adpcm-lib.h new file mode 100644 index 00000000..b312b5a1 --- /dev/null +++ b/src/adpcm-xq/adpcm-lib.h @@ -0,0 +1,35 @@ +//////////////////////////////////////////////////////////////////////////// +// **** ADPCM-XQ **** // +// Xtreme Quality ADPCM Encoder/Decoder // +// Copyright (c) 2015 David Bryant. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +#ifndef ADPCMLIB_H_ +#define ADPCMLIB_H_ + +#if defined(_MSC_VER) && _MSC_VER < 1600 +typedef unsigned __int64 uint64_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int8 uint8_t; +typedef __int64 int64_t; +typedef __int32 int32_t; +typedef __int16 int16_t; +typedef __int8 int8_t; +#else +#include +#endif + +void *adpcm_create_context (int num_channels, int lookahead, int noise_shaping, int32_t initial_deltas [2]); +int adpcm_encode_block (void *p, uint8_t *outbuf, size_t *outbufsize, const int16_t *inbuf, int inbufcount); +int adpcm_decode_block (int16_t *outbuf, const uint8_t *inbuf, size_t inbufsize, int channels); +void adpcm_free_context (void *p); + +#define NOISE_SHAPING_OFF 0 // flat noise (no shaping) +#define NOISE_SHAPING_STATIC 1 // first-order highpass shaping +#define NOISE_SHAPING_DYNAMIC 2 // dynamically tilted noise based on signal + +#endif /* ADPCMLIB_H_ */ + diff --git a/src/adpcm-xq/adpcm-xq.c b/src/adpcm-xq/adpcm-xq.c new file mode 100644 index 00000000..2f227095 --- /dev/null +++ b/src/adpcm-xq/adpcm-xq.c @@ -0,0 +1,789 @@ +//////////////////////////////////////////////////////////////////////////// +// **** ADPCM-XQ **** // +// Xtreme Quality ADPCM Encoder/Decoder // +// Copyright (c) 2015 David Bryant. // +// All Rights Reserved. // +// Distributed under the BSD Software License (see license.txt) // +//////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "adpcm-lib.h" + +static const char *sign_on = "\n" +" ADPCM-XQ Xtreme Quality IMA-ADPCM WAV Encoder / Decoder Version 0.3\n" +" Copyright (c) 2018 David Bryant. All Rights Reserved.\n\n"; + +static const char *usage = +" Usage: ADPCM-XQ [-options] infile.wav outfile.wav\n\n" +" Operation: conversion is performed based on the type of the infile\n" +" (either encode 16-bit PCM to 4-bit IMA-ADPCM or decode back)\n\n" +" Options: -[0-8] = encode lookahead samples (default = 3)\n" +" -bn = override auto block size, 2^n bytes (n = 8-15)\n" +" -d = decode only (fail on WAV file already PCM)\n" +" -e = encode only (fail on WAV file already ADPCM)\n" +" -f = encode flat noise (no dynamic noise shaping)\n" +" -h = display this help message\n" +" -q = quiet mode (display errors only)\n" +" -r = raw output (no WAV header written)\n" +" -v = verbose (display lots of info)\n" +" -y = overwrite outfile if it exists\n\n" +" Web: Visit www.github.com/dbry/adpcm-xq for latest version and info\n\n"; + +#define ADPCM_FLAG_NOISE_SHAPING 0x1 +#define ADPCM_FLAG_RAW_OUTPUT 0x2 + +static int adpcm_converter (char *infilename, char *outfilename, int flags, int blocksize_pow2, int lookahead); +static int verbosity = 0, decode_only = 0, encode_only = 0; + +int main (argc, argv) int argc; char **argv; +{ + int lookahead = 3, flags = ADPCM_FLAG_NOISE_SHAPING, blocksize_pow2 = 0, overwrite = 0, asked_help = 0; + char *infilename = NULL, *outfilename = NULL; + FILE *outfile; + + // if the name of the executable ends in "encoder" or "decoder", just do that function + encode_only = argc && strstr (argv [0], "encoder") && strlen (strstr (argv [0], "encoder")) == strlen ("encoder"); + decode_only = argc && strstr (argv [0], "decoder") && strlen (strstr (argv [0], "decoder")) == strlen ("decoder"); + + // loop through command-line arguments + + while (--argc) { +#if defined (_WIN32) + if ((**++argv == '-' || **argv == '/') && (*argv)[1]) +#else + if ((**++argv == '-') && (*argv)[1]) +#endif + while (*++*argv) + switch (**argv) { + + case '0': case '1': case '2': + case '3': case '4': case '5': + case '6': case '7': case '8': + lookahead = **argv - '0'; + break; + + case 'B': case 'b': + blocksize_pow2 = strtol (++*argv, argv, 10); + + if (blocksize_pow2 < 8 || blocksize_pow2 > 15) { + fprintf (stderr, "\nblock size power must be 8 to 15!\n"); + return -1; + } + + --*argv; + break; + + case 'D': case 'd': + decode_only = 1; + break; + + case 'E': case 'e': + encode_only = 1; + break; + + case 'F': case 'f': + flags &= ~ADPCM_FLAG_NOISE_SHAPING; + break; + + case 'H': case 'h': + asked_help = 0; + break; + + case 'Q': case 'q': + verbosity = -1; + break; + + case 'R': case 'r': + flags |= ADPCM_FLAG_RAW_OUTPUT; + break; + + case 'V': case 'v': + verbosity = 1; + break; + + case 'Y': case 'y': + overwrite = 1; + break; + + default: + fprintf (stderr, "\nillegal option: %c !\n", **argv); + return 1; + } + else if (!infilename) { + infilename = malloc (strlen (*argv) + 10); + strcpy (infilename, *argv); + } + else if (!outfilename) { + outfilename = malloc (strlen (*argv) + 10); + strcpy (outfilename, *argv); + } + else { + fprintf (stderr, "\nextra unknown argument: %s !\n", *argv); + return 1; + } + } + + if (verbosity >= 0) + fprintf (stderr, "%s", sign_on); + + if (!outfilename || asked_help) { + printf ("%s", usage); + return 0; + } + + if (!strcmp (infilename, outfilename)) { + fprintf (stderr, "can't overwrite input file (specify different/new output file name)\n"); + return -1; + } + + if (!overwrite && (outfile = fopen (outfilename, "r"))) { + fclose (outfile); + fprintf (stderr, "output file \"%s\" exists (use -y to overwrite)\n", outfilename); + return -1; + } + + return adpcm_converter (infilename, outfilename, flags, blocksize_pow2, lookahead); +} + +typedef struct { + char ckID [4]; + uint32_t ckSize; + char formType [4]; +} RiffChunkHeader; + +typedef struct { + char ckID [4]; + uint32_t ckSize; +} ChunkHeader; + +#define ChunkHeaderFormat "4L" + +typedef struct { + uint16_t FormatTag, NumChannels; + uint32_t SampleRate, BytesPerSecond; + uint16_t BlockAlign, BitsPerSample; + uint16_t cbSize; + union { + uint16_t ValidBitsPerSample; + uint16_t SamplesPerBlock; + uint16_t Reserved; + } Samples; + int32_t ChannelMask; + uint16_t SubFormat; + char GUID [14]; +} WaveHeader; + +#define WaveHeaderFormat "SSLLSSSSLS" + +typedef struct { + char ckID [4]; + uint32_t ckSize; + uint32_t TotalSamples; +} FactHeader; + +#define FactHeaderFormat "4LL" + +#define WAVE_FORMAT_PCM 0x1 +#define WAVE_FORMAT_IMA_ADPCM 0x11 +#define WAVE_FORMAT_EXTENSIBLE 0xfffe + +static int write_pcm_wav_header (FILE *outfile, int num_channels, size_t num_samples, int sample_rate); +static int write_adpcm_wav_header (FILE *outfile, int num_channels, size_t num_samples, int sample_rate, int samples_per_block); +static int adpcm_decode_data (FILE *infile, FILE *outfile, int num_channels, size_t num_samples, int block_size); +static int adpcm_encode_data (FILE *infile, FILE *outfile, int num_channels, size_t num_samples, int samples_per_block, int lookahead, int noise_shaping); +static void little_endian_to_native (void *data, char *format); +static void native_to_little_endian (void *data, char *format); + +static int adpcm_converter (char *infilename, char *outfilename, int flags, int blocksize_pow2, int lookahead) +{ + int format = 0, res = 0, bits_per_sample, sample_rate, num_channels; + uint32_t fact_samples = 0; + size_t num_samples = 0; + FILE *infile, *outfile; + RiffChunkHeader riff_chunk_header; + ChunkHeader chunk_header; + WaveHeader WaveHeader; + + if (!(infile = fopen (infilename, "rb"))) { + fprintf (stderr, "can't open file \"%s\" for reading!\n", infilename); + return -1; + } + + // read initial RIFF form header + + if (!fread (&riff_chunk_header, sizeof (RiffChunkHeader), 1, infile) || + strncmp (riff_chunk_header.ckID, "RIFF", 4) || + strncmp (riff_chunk_header.formType, "WAVE", 4)) { + fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename); + return -1; + } + + // loop through all elements of the RIFF wav header (until the data chuck) + + while (1) { + + if (!fread (&chunk_header, sizeof (ChunkHeader), 1, infile)) { + fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename); + return -1; + } + + little_endian_to_native (&chunk_header, ChunkHeaderFormat); + + // if it's the format chunk, we want to get some info out of there and + // make sure it's a .wav file we can handle + + if (!strncmp (chunk_header.ckID, "fmt ", 4)) { + int supported = 1; + + if (chunk_header.ckSize < 16 || chunk_header.ckSize > sizeof (WaveHeader) || + !fread (&WaveHeader, chunk_header.ckSize, 1, infile)) { + fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename); + return -1; + } + + little_endian_to_native (&WaveHeader, WaveHeaderFormat); + + format = (WaveHeader.FormatTag == WAVE_FORMAT_EXTENSIBLE && chunk_header.ckSize == 40) ? + WaveHeader.SubFormat : WaveHeader.FormatTag; + + bits_per_sample = (chunk_header.ckSize == 40 && WaveHeader.Samples.ValidBitsPerSample) ? + WaveHeader.Samples.ValidBitsPerSample : WaveHeader.BitsPerSample; + + if (WaveHeader.NumChannels < 1 || WaveHeader.NumChannels > 2) + supported = 0; + else if (format == WAVE_FORMAT_PCM) { + if (decode_only) { + fprintf (stderr, "\"%s\" is PCM .WAV file, invalid in decode-only mode!\n", infilename); + return -1; + } + + if (bits_per_sample < 9 || bits_per_sample > 16) + supported = 0; + + if (WaveHeader.BlockAlign != WaveHeader.NumChannels * 2) + supported = 0; + } + else if (format == WAVE_FORMAT_IMA_ADPCM) { + if (encode_only) { + fprintf (stderr, "\"%s\" is ADPCM .WAV file, invalid in encode-only mode!\n", infilename); + return -1; + } + + if (bits_per_sample != 4) + supported = 0; + + if (WaveHeader.Samples.SamplesPerBlock != (WaveHeader.BlockAlign - WaveHeader.NumChannels * 4) * (WaveHeader.NumChannels ^ 3) + 1) { + fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename); + return -1; + } + } + else + supported = 0; + + if (!supported) { + fprintf (stderr, "\"%s\" is an unsupported .WAV format!\n", infilename); + return -1; + } + + if (verbosity > 0) { + fprintf (stderr, "format tag size = %d\n", chunk_header.ckSize); + fprintf (stderr, "FormatTag = 0x%x, NumChannels = %d, BitsPerSample = %d\n", + WaveHeader.FormatTag, WaveHeader.NumChannels, WaveHeader.BitsPerSample); + fprintf (stderr, "BlockAlign = %d, SampleRate = %d, BytesPerSecond = %d\n", + WaveHeader.BlockAlign, WaveHeader.SampleRate, WaveHeader.BytesPerSecond); + + if (chunk_header.ckSize > 16) { + if (format == WAVE_FORMAT_PCM) + fprintf (stderr, "cbSize = %d, ValidBitsPerSample = %d\n", WaveHeader.cbSize, + WaveHeader.Samples.ValidBitsPerSample); + else if (format == WAVE_FORMAT_IMA_ADPCM) + fprintf (stderr, "cbSize = %d, SamplesPerBlock = %d\n", WaveHeader.cbSize, + WaveHeader.Samples.SamplesPerBlock); + } + + if (chunk_header.ckSize > 20) + fprintf (stderr, "ChannelMask = %x, SubFormat = %d\n", + WaveHeader.ChannelMask, WaveHeader.SubFormat); + } + } + else if (!strncmp (chunk_header.ckID, "fact", 4)) { + + if (chunk_header.ckSize < 4 || !fread (&fact_samples, sizeof (fact_samples), 1, infile)) { + fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename); + return -1; + } + + if (chunk_header.ckSize > 4) { + int bytes_to_skip = chunk_header.ckSize - 4; + char dummy; + + while (bytes_to_skip--) + if (!fread (&dummy, 1, 1, infile)) { + fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename); + return -1; + } + } + } + else if (!strncmp (chunk_header.ckID, "data", 4)) { + + // on the data chunk, get size and exit parsing loop + + if (!WaveHeader.NumChannels) { // make sure we saw a "fmt" chunk... + fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename); + return -1; + } + + if (!chunk_header.ckSize) { + fprintf (stderr, "this .WAV file has no audio samples, probably is corrupt!\n"); + return -1; + } + + if (format == WAVE_FORMAT_PCM) { + if (chunk_header.ckSize % WaveHeader.BlockAlign) { + fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename); + return -1; + } + + num_samples = chunk_header.ckSize / WaveHeader.BlockAlign; + } + else { + int complete_blocks = chunk_header.ckSize / WaveHeader.BlockAlign; + int leftover_bytes = chunk_header.ckSize % WaveHeader.BlockAlign; + int samples_last_block; + + num_samples = complete_blocks * WaveHeader.Samples.SamplesPerBlock; + + if (leftover_bytes) { + if (leftover_bytes % (WaveHeader.NumChannels * 4)) { + fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename); + return -1; + } + if (verbosity > 0) fprintf (stderr, "data chunk has %d bytes left over for final ADPCM block\n", leftover_bytes); + samples_last_block = (leftover_bytes - (WaveHeader.NumChannels * 4)) * (WaveHeader.NumChannels ^ 3) + 1; + num_samples += samples_last_block; + } + else + samples_last_block = WaveHeader.Samples.SamplesPerBlock; + + if (fact_samples) { + if (fact_samples < num_samples && fact_samples > num_samples - samples_last_block) { + if (verbosity > 0) fprintf (stderr, "total samples reduced %lu by FACT chunk\n", (unsigned long) (num_samples - fact_samples)); + num_samples = fact_samples; + } + else if (WaveHeader.NumChannels == 2 && (fact_samples >>= 1) < num_samples && fact_samples > num_samples - samples_last_block) { + if (verbosity > 0) fprintf (stderr, "num samples reduced %lu by [incorrect] FACT chunk\n", (unsigned long) (num_samples - fact_samples)); + num_samples = fact_samples; + } + } + } + + if (!num_samples) { + fprintf (stderr, "this .WAV file has no audio samples, probably is corrupt!\n"); + return -1; + } + + if (verbosity > 0) + fprintf (stderr, "num samples = %lu\n", (unsigned long) num_samples); + + num_channels = WaveHeader.NumChannels; + sample_rate = WaveHeader.SampleRate; + break; + } + else { // just ignore unknown chunks + int bytes_to_eat = (chunk_header.ckSize + 1) & ~1L; + char dummy; + + if (verbosity > 0) + fprintf (stderr, "extra unknown chunk \"%c%c%c%c\" of %d bytes\n", + chunk_header.ckID [0], chunk_header.ckID [1], chunk_header.ckID [2], + chunk_header.ckID [3], chunk_header.ckSize); + + while (bytes_to_eat--) + if (!fread (&dummy, 1, 1, infile)) { + fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename); + return -1; + } + } + } + + if (!(outfile = fopen (outfilename, "wb"))) { + fprintf (stderr, "can't open file \"%s\" for writing!\n", outfilename); + return -1; + } + + if (format == WAVE_FORMAT_PCM) { + int block_size, samples_per_block; + + if (blocksize_pow2) + block_size = 1 << blocksize_pow2; + else + block_size = 256 * num_channels * (sample_rate < 11000 ? 1 : sample_rate / 11000); + + samples_per_block = (block_size - num_channels * 4) * (num_channels ^ 3) + 1; + + if (verbosity > 0) + fprintf (stderr, "each %d byte ADPCM block will contain %d samples * %d channels\n", + block_size, samples_per_block, num_channels); + + if (!(flags & ADPCM_FLAG_RAW_OUTPUT) && !write_adpcm_wav_header (outfile, num_channels, num_samples, sample_rate, samples_per_block)) { + fprintf (stderr, "can't write header to file \"%s\" !\n", outfilename); + return -1; + } + + if (verbosity >= 0) fprintf (stderr, "encoding PCM file \"%s\" to%sADPCM file \"%s\"...\n", + infilename, (flags & ADPCM_FLAG_RAW_OUTPUT) ? " raw " : " ", outfilename); + + res = adpcm_encode_data (infile, outfile, num_channels, num_samples, samples_per_block, lookahead, + (flags & ADPCM_FLAG_NOISE_SHAPING) ? (sample_rate > 64000 ? NOISE_SHAPING_STATIC : NOISE_SHAPING_DYNAMIC) : NOISE_SHAPING_OFF); + } + else if (format == WAVE_FORMAT_IMA_ADPCM) { + if (!(flags & ADPCM_FLAG_RAW_OUTPUT) && !write_pcm_wav_header (outfile, num_channels, num_samples, sample_rate)) { + fprintf (stderr, "can't write header to file \"%s\" !\n", outfilename); + return -1; + } + + if (verbosity >= 0) fprintf (stderr, "decoding ADPCM file \"%s\" to%sPCM file \"%s\"...\n", + infilename, (flags & ADPCM_FLAG_RAW_OUTPUT) ? " raw " : " ", outfilename); + + res = adpcm_decode_data (infile, outfile, num_channels, num_samples, WaveHeader.BlockAlign); + } + + fclose (outfile); + fclose (infile); + return res; +} + +static int write_pcm_wav_header (FILE *outfile, int num_channels, size_t num_samples, int sample_rate) +{ + RiffChunkHeader riffhdr; + ChunkHeader datahdr, fmthdr; + WaveHeader wavhdr; + + int wavhdrsize = 16; + int bytes_per_sample = 2; + size_t total_data_bytes = num_samples * bytes_per_sample * num_channels; + + memset (&wavhdr, 0, sizeof (wavhdr)); + + wavhdr.FormatTag = WAVE_FORMAT_PCM; + wavhdr.NumChannels = num_channels; + wavhdr.SampleRate = sample_rate; + wavhdr.BytesPerSecond = sample_rate * num_channels * bytes_per_sample; + wavhdr.BlockAlign = bytes_per_sample * num_channels; + wavhdr.BitsPerSample = 16; + + strncpy (riffhdr.ckID, "RIFF", sizeof (riffhdr.ckID)); + strncpy (riffhdr.formType, "WAVE", sizeof (riffhdr.formType)); + riffhdr.ckSize = sizeof (riffhdr) + wavhdrsize + sizeof (datahdr) + total_data_bytes; + strncpy (fmthdr.ckID, "fmt ", sizeof (fmthdr.ckID)); + fmthdr.ckSize = wavhdrsize; + + strncpy (datahdr.ckID, "data", sizeof (datahdr.ckID)); + datahdr.ckSize = total_data_bytes; + + // write the RIFF chunks up to just before the data starts + + native_to_little_endian (&riffhdr, ChunkHeaderFormat); + native_to_little_endian (&fmthdr, ChunkHeaderFormat); + native_to_little_endian (&wavhdr, WaveHeaderFormat); + native_to_little_endian (&datahdr, ChunkHeaderFormat); + + return fwrite (&riffhdr, sizeof (riffhdr), 1, outfile) && + fwrite (&fmthdr, sizeof (fmthdr), 1, outfile) && + fwrite (&wavhdr, wavhdrsize, 1, outfile) && + fwrite (&datahdr, sizeof (datahdr), 1, outfile); +} + +static int write_adpcm_wav_header (FILE *outfile, int num_channels, size_t num_samples, int sample_rate, int samples_per_block) +{ + RiffChunkHeader riffhdr; + ChunkHeader datahdr, fmthdr; + WaveHeader wavhdr; + FactHeader facthdr; + + int wavhdrsize = 20; + int block_size = (samples_per_block - 1) / (num_channels ^ 3) + (num_channels * 4); + size_t num_blocks = num_samples / samples_per_block; + int leftover_samples = num_samples % samples_per_block; + size_t total_data_bytes = num_blocks * block_size; + + if (leftover_samples) { + int last_block_samples = ((leftover_samples + 6) & ~7) + 1; + int last_block_size = (last_block_samples - 1) / (num_channels ^ 3) + (num_channels * 4); + total_data_bytes += last_block_size; + } + + memset (&wavhdr, 0, sizeof (wavhdr)); + + wavhdr.FormatTag = WAVE_FORMAT_IMA_ADPCM; + wavhdr.NumChannels = num_channels; + wavhdr.SampleRate = sample_rate; + wavhdr.BytesPerSecond = sample_rate * block_size / samples_per_block; + wavhdr.BlockAlign = block_size; + wavhdr.BitsPerSample = 4; + wavhdr.cbSize = 2; + wavhdr.Samples.SamplesPerBlock = samples_per_block; + + strncpy (riffhdr.ckID, "RIFF", sizeof (riffhdr.ckID)); + strncpy (riffhdr.formType, "WAVE", sizeof (riffhdr.formType)); + riffhdr.ckSize = sizeof (riffhdr) + wavhdrsize + sizeof (facthdr) + sizeof (datahdr) + total_data_bytes; + strncpy (fmthdr.ckID, "fmt ", sizeof (fmthdr.ckID)); + fmthdr.ckSize = wavhdrsize; + strncpy (facthdr.ckID, "fact", sizeof (facthdr.ckID)); + facthdr.TotalSamples = num_samples; + facthdr.ckSize = 4; + + strncpy (datahdr.ckID, "data", sizeof (datahdr.ckID)); + datahdr.ckSize = total_data_bytes; + + // write the RIFF chunks up to just before the data starts + + native_to_little_endian (&riffhdr, ChunkHeaderFormat); + native_to_little_endian (&fmthdr, ChunkHeaderFormat); + native_to_little_endian (&wavhdr, WaveHeaderFormat); + native_to_little_endian (&facthdr, FactHeaderFormat); + native_to_little_endian (&datahdr, ChunkHeaderFormat); + + return fwrite (&riffhdr, sizeof (riffhdr), 1, outfile) && + fwrite (&fmthdr, sizeof (fmthdr), 1, outfile) && + fwrite (&wavhdr, wavhdrsize, 1, outfile) && + fwrite (&facthdr, sizeof (facthdr), 1, outfile) && + fwrite (&datahdr, sizeof (datahdr), 1, outfile); +} + +static int adpcm_decode_data (FILE *infile, FILE *outfile, int num_channels, size_t num_samples, int block_size) +{ + int samples_per_block = (block_size - num_channels * 4) * (num_channels ^ 3) + 1, percent; + void *pcm_block = malloc (samples_per_block * num_channels * 2); + void *adpcm_block = malloc (block_size); + size_t progress_divider = 0; + + if (!pcm_block || !adpcm_block) { + fprintf (stderr, "could not allocate memory for buffers!\n"); + return -1; + } + + if (verbosity >= 0 && num_samples > 1000) { + progress_divider = (num_samples + 50) / 100; + fprintf (stderr, "\rprogress: %d%% ", percent = 0); + fflush (stderr); + } + + while (num_samples) { + int this_block_adpcm_samples = samples_per_block; + int this_block_pcm_samples = samples_per_block; + + if (this_block_adpcm_samples > num_samples) { + this_block_adpcm_samples = ((num_samples + 6) & ~7) + 1; + block_size = (this_block_adpcm_samples - 1) / (num_channels ^ 3) + (num_channels * 4); + this_block_pcm_samples = num_samples; + } + + if (!fread (adpcm_block, block_size, 1, infile)) { + fprintf (stderr, "could not read all audio data from input file!\n"); + return -1; + } + + if (adpcm_decode_block (pcm_block, adpcm_block, block_size, num_channels) != this_block_adpcm_samples) { + fprintf (stderr, "adpcm_decode_block() did not return expected value!\n"); + return -1; + } + + if (!fwrite (pcm_block, this_block_pcm_samples * num_channels * 2, 1, outfile)) { + fprintf (stderr, "could not write all audio data to output file!\n"); + return -1; + } + + num_samples -= this_block_pcm_samples; + + if (progress_divider) { + int new_percent = 100 - num_samples / progress_divider; + + if (new_percent != percent) { + fprintf (stderr, "\rprogress: %d%% ", percent = new_percent); + fflush (stderr); + } + } + } + + if (verbosity >= 0) + fprintf (stderr, "\r...completed successfully\n"); + + free (adpcm_block); + free (pcm_block); + return 0; +} + +static int adpcm_encode_data (FILE *infile, FILE *outfile, int num_channels, size_t num_samples, int samples_per_block, int lookahead, int noise_shaping) +{ + int block_size = (samples_per_block - 1) / (num_channels ^ 3) + (num_channels * 4), percent; + int16_t *pcm_block = malloc (samples_per_block * num_channels * 2); + void *adpcm_block = malloc (block_size); + size_t progress_divider = 0; + void *adpcm_cnxt = NULL; + + if (!pcm_block || !adpcm_block) { + fprintf (stderr, "could not allocate memory for buffers!\n"); + return -1; + } + + if (verbosity >= 0 && num_samples > 1000) { + progress_divider = (num_samples + 50) / 100; + fprintf (stderr, "\rprogress: %d%% ", percent = 0); + fflush (stderr); + } + + while (num_samples) { + int this_block_adpcm_samples = samples_per_block; + int this_block_pcm_samples = samples_per_block; + size_t num_bytes; + + if (this_block_pcm_samples > num_samples) { + this_block_adpcm_samples = ((num_samples + 6) & ~7) + 1; + block_size = (this_block_adpcm_samples - 1) / (num_channels ^ 3) + (num_channels * 4); + this_block_pcm_samples = num_samples; + } + + if (!fread (pcm_block, this_block_pcm_samples * num_channels * 2, 1, infile)) { + fprintf (stderr, "\rcould not read all audio data from input file!\n"); + return -1; + } + + // if this is the last block and it's not full, duplicate the last sample(s) so we don't + // create problems for the lookahead + + if (this_block_adpcm_samples > this_block_pcm_samples) { + int16_t *dst = pcm_block + this_block_pcm_samples * num_channels, *src = dst - num_channels; + int dups = (this_block_adpcm_samples - this_block_pcm_samples) * num_channels; + + while (dups--) + *dst++ = *src++; + } + + // if this is the first block, compute a decaying average (in reverse) so that we can let the + // encoder know what kind of initial deltas to expect (helps initializing index) + + if (!adpcm_cnxt) { + int32_t average_deltas [2]; + int i; + + average_deltas [0] = average_deltas [1] = 0; + + for (i = this_block_adpcm_samples * num_channels; i -= num_channels;) { + average_deltas [0] -= average_deltas [0] >> 3; + average_deltas [0] += abs ((int32_t) pcm_block [i] - pcm_block [i - num_channels]); + + if (num_channels == 2) { + average_deltas [1] -= average_deltas [1] >> 3; + average_deltas [1] += abs ((int32_t) pcm_block [i-1] - pcm_block [i+1]); + } + } + + average_deltas [0] >>= 3; + average_deltas [1] >>= 3; + + adpcm_cnxt = adpcm_create_context (num_channels, lookahead, noise_shaping, average_deltas); + } + + adpcm_encode_block (adpcm_cnxt, adpcm_block, &num_bytes, pcm_block, this_block_adpcm_samples); + + if (num_bytes != block_size) { + fprintf (stderr, "\radpcm_encode_block() did not return expected value (expected %d, got %d)!\n", block_size, (int) num_bytes); + return -1; + } + + if (!fwrite (adpcm_block, block_size, 1, outfile)) { + fprintf (stderr, "\rcould not write all audio data to output file!\n"); + return -1; + } + + num_samples -= this_block_pcm_samples; + + if (progress_divider) { + int new_percent = 100 - num_samples / progress_divider; + + if (new_percent != percent) { + fprintf (stderr, "\rprogress: %d%% ", percent = new_percent); + fflush (stderr); + } + } + } + + if (verbosity >= 0) + fprintf (stderr, "\r...completed successfully\n"); + + if (adpcm_cnxt) + adpcm_free_context (adpcm_cnxt); + + free (adpcm_block); + free (pcm_block); + return 0; +} + +static void little_endian_to_native (void *data, char *format) +{ + unsigned char *cp = (unsigned char *) data; + int32_t temp; + + while (*format) { + switch (*format) { + case 'L': + temp = cp [0] + ((int32_t) cp [1] << 8) + ((int32_t) cp [2] << 16) + ((int32_t) cp [3] << 24); + * (int32_t *) cp = temp; + cp += 4; + break; + + case 'S': + temp = cp [0] + (cp [1] << 8); + * (short *) cp = (short) temp; + cp += 2; + break; + + default: + if (isdigit ((unsigned char) *format)) + cp += *format - '0'; + + break; + } + + format++; + } +} + +static void native_to_little_endian (void *data, char *format) +{ + unsigned char *cp = (unsigned char *) data; + int32_t temp; + + while (*format) { + switch (*format) { + case 'L': + temp = * (int32_t *) cp; + *cp++ = (unsigned char) temp; + *cp++ = (unsigned char) (temp >> 8); + *cp++ = (unsigned char) (temp >> 16); + *cp++ = (unsigned char) (temp >> 24); + break; + + case 'S': + temp = * (short *) cp; + *cp++ = (unsigned char) temp; + *cp++ = (unsigned char) (temp >> 8); + break; + + default: + if (isdigit ((unsigned char) *format)) + cp += *format - '0'; + + break; + } + + format++; + } +} + + diff --git a/src/adpcm-xq/license.txt b/src/adpcm-xq/license.txt new file mode 100644 index 00000000..d263b71b --- /dev/null +++ b/src/adpcm-xq/license.txt @@ -0,0 +1,26 @@ + Copyright (c) David Bryant + 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 Conifer Software 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 REGENTS 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. + diff --git a/src/d_event.c b/src/d_event.c index 3eef5728..2f70023f 100644 --- a/src/d_event.c +++ b/src/d_event.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -22,7 +23,11 @@ #include #include "d_event.h" +#if !DOOM_SMALL #define MAXEVENTS 64 +#else +#define MAXEVENTS 8 +#endif static event_t events[MAXEVENTS]; static int eventhead; diff --git a/src/d_iwad.c b/src/d_iwad.c index 75ae0bab..38ed0986 100644 --- a/src/d_iwad.c +++ b/src/d_iwad.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -38,16 +39,20 @@ static const iwad_t iwads[] = { "tnt.wad", pack_tnt, commercial, "Final Doom: TNT: Evilution" }, { "doom.wad", doom, retail, "Doom" }, { "doom1.wad", doom, shareware, "Doom Shareware" }, +#if !DOOM_ONLY { "chex.wad", pack_chex, retail, "Chex Quest" }, { "hacx.wad", pack_hacx, commercial, "Hacx" }, +#endif { "freedm.wad", doom2, commercial, "FreeDM" }, { "freedoom2.wad", doom2, commercial, "Freedoom: Phase 2" }, { "freedoom1.wad", doom, retail, "Freedoom: Phase 1" }, +#if !DOOM_ONLY { "heretic.wad", heretic, retail, "Heretic" }, { "heretic1.wad", heretic, shareware, "Heretic Shareware" }, { "hexen.wad", hexen, commercial, "Hexen" }, //{ "strife0.wad", strife, commercial, "Strife" }, // haleyjd: STRIFE-FIXME { "strife1.wad", strife, commercial, "Strife" }, +#endif }; // Array of locations to search for IWAD files @@ -442,6 +447,7 @@ static void CheckDOSDefaults(void) #endif +#if !DOOM_TINY // Returns true if the specified path is a path to a file // of the specified name. @@ -529,7 +535,7 @@ static GameMission_t IdentifyIWADByName(const char *name, int mask) GameMission_t mission; name = M_BaseName(name); - mission = none; + mission = mission_none; for (i=0; i // - +#if USE_MEMORY_WAD + return ".wad"; +#else +#if !NO_USE_ARGS iwadparm = M_CheckParmWithArgs("-iwad", 1); if (iwadparm) @@ -854,6 +865,7 @@ char *D_FindIWAD(int mask, GameMission_t *mission) *mission = IdentifyIWADByName(result, mask); } else +#endif { // Search through the list and look for an IWAD @@ -866,6 +878,7 @@ char *D_FindIWAD(int mask, GameMission_t *mission) result = SearchDirectoryForIWAD(iwad_dirs[i], mask, mission); } } +#endif return result; } @@ -950,6 +963,9 @@ const char *D_SuggestIWADName(GameMission_t mission, GameMode_t mode) return "unknown.wad"; } + +#endif + const char *D_SuggestGameName(GameMission_t mission, GameMode_t mode) { int i; @@ -957,7 +973,7 @@ const char *D_SuggestGameName(GameMission_t mission, GameMode_t mode) for (i = 0; i < arrlen(iwads); ++i) { if (iwads[i].mission == mission - && (mode == indetermined || iwads[i].mode == mode)) + && (mode == indetermined || iwads[i].mode == mode)) { return iwads[i].description; } @@ -966,3 +982,21 @@ const char *D_SuggestGameName(GameMission_t mission, GameMode_t mode) return "Unknown game?"; } +#if DOOM_TINY +GameMission_t IdentifyIWADByName(const char *name) { + size_t i; + GameMission_t mission; + mission = mission_none; + + for (i=0; i 17 hours + uint64_t v = 150323855ull * (uint32_t)time_ms; + return (int)(v >> 32); +#else + return time_ms * TICRATE / 1000; +#endif } -static boolean BuildNewTic(void) -{ - int gameticdiv; +static boolean BuildNewTic(void) { + int gameticdiv; ticcmd_t cmd; - gameticdiv = gametic/ticdup; + gameticdiv = gametic / ticdup; - I_StartTic (); + I_StartTic(); loop_interface->ProcessEvents(); // Always run the menu loop_interface->RunMenu(); - if (drone) - { + if (drone) { // In drone mode, do not generate any ticcmds. return false; } - if (new_sync) - { - // If playing single player, do not allow tics to buffer - // up very far + if (new_sync) { + // If playing single player, do not allow tics to buffer + // up very far - if (!net_client_connected && maketic - gameticdiv > 2) - return false; + if (!net_client_connected && maketic - gameticdiv > 2) + return false; - // Never go more than ~200ms ahead + // Never go more than ~200ms ahead - if (maketic - gameticdiv > 8) - return false; - } - else - { - if (maketic - gameticdiv >= 5) - return false; + if (maketic - gameticdiv >= MIN(8, BACKUPTICS)) + return false; + } else { + if (maketic - gameticdiv >= 5) + return false; } //printf ("mk:%i ",maketic); memset(&cmd, 0, sizeof(ticcmd_t)); loop_interface->BuildTiccmd(&cmd, maketic); +#if !NO_USE_NET if (net_client_connected) { NET_CL_SendTiccmd(&cmd, maketic); } +#endif ticdata[maketic % BACKUPTICS].cmds[localplayer] = cmd; +#if !DOOM_TINY ticdata[maketic % BACKUPTICS].ingame[localplayer] = true; +#else + ticdata[maketic % BACKUPTICS].cmds[localplayer].ingame = true; +#endif +#if USE_PICO_NET + if (net_client_connected) { + // piconet uses ticdata directly, so we need it to be initialized above + piconet_new_local_tic(maketic); + } +#endif ++maketic; @@ -199,13 +231,12 @@ static boolean BuildNewTic(void) // Builds ticcmds for console player, // sends out a packet // -int lasttime; +int lasttime; -void NetUpdate (void) -{ +void NetUpdate(void) { int nowtime; int newtics; - int i; + int i; // If we are running with singletics (timing a demo), this // is all done separately. @@ -215,8 +246,10 @@ void NetUpdate (void) // Run network subsystems +#if !NO_USE_NET NET_CL_Run(); NET_SV_Run(); +#endif // check time nowtime = GetAdjustedTime() / ticdup; @@ -224,40 +257,35 @@ void NetUpdate (void) lasttime = nowtime; - if (skiptics <= newtics) - { + if (skiptics <= newtics) { newtics -= skiptics; skiptics = 0; - } - else - { + } else { skiptics -= newtics; newtics = 0; } // build new ticcmds for console player - for (i=0 ; iconsoleplayer = 0; - settings->num_players = 1; - settings->player_classes[0] = player_class; - - //! - // @category net - // - // Use original network client sync code rather than the improved - // sync code. - // - settings->new_sync = !M_ParmExists("-oldsync"); - - //! - // @category net - // @arg - // - // Send n extra tics in every packet as insurance against dropped - // packets. - // - - i = M_CheckParmWithArgs("-extratics", 1); - - if (i > 0) - settings->extratics = atoi(myargv[i+1]); - else - settings->extratics = 1; - - //! - // @category net - // @arg - // - // Reduce the resolution of the game by a factor of n, reducing - // the amount of network bandwidth needed. - // - - i = M_CheckParmWithArgs("-dup", 1); - - if (i > 0) - settings->ticdup = atoi(myargv[i+1]); - else - settings->ticdup = 1; - - if (net_client_connected) - { - // Send our game settings and block until game start is received - // from the server. - - NET_CL_StartGame(settings); - BlockUntilStart(settings, callback); - - // Read the game settings that were received. - - NET_CL_GetSettings(settings); - } - - if (drone) - { - settings->consoleplayer = 0; - } - - // Set the local player and playeringame[] values. - - localplayer = settings->consoleplayer; - - for (i = 0; i < NET_MAXPLAYERS; ++i) - { - local_playeringame[i] = i < settings->num_players; - } - - // Copy settings to global variables. - - ticdup = settings->ticdup; - new_sync = settings->new_sync; - - // TODO: Message disabled until we fix new_sync. - //if (!new_sync) - //{ - // printf("Syncing netgames like Vanilla Doom.\n"); - //} -} boolean D_InitNetGame(net_connect_data_t *connect_data) { @@ -528,17 +470,19 @@ void D_QuitNetGame (void) NET_SV_Shutdown(); NET_CL_Disconnect(); } +#endif -static int GetLowTic(void) -{ +static int GetLowTic(void) { int lowtic; lowtic = maketic; - if (net_client_connected) - { - if (drone || recvtic < lowtic) - { + if (net_client_connected) { +#if USE_PICO_NET + recvtic = piconet_maybe_recv_tic(recvtic); + if (!net_client_connected) piconet_stop(); +#endif + if (drone || recvtic < lowtic) { lowtic = recvtic; } } @@ -550,8 +494,7 @@ static int frameon; static int frameskip[4]; static int oldnettics; -static void OldNetSync(void) -{ +static void OldNetSync(void) { unsigned int i; int keyplayer = -1; @@ -560,30 +503,23 @@ static void OldNetSync(void) // ideally maketic should be 1 - 3 tics above lowtic // if we are consistantly slower, speed up time - for (i=0 ; i recvtic; oldnettics = maketic; - if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3]) - { + if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3]) { skiptics = 1; // printf ("+"); } @@ -601,18 +536,15 @@ static void OldNetSync(void) // Returns true if there are players in the game: -static boolean PlayersInGame(void) -{ +static boolean PlayersInGame(void) { boolean result = false; unsigned int i; // If we are connected to a server, check if there are any players // in the game. - if (net_client_connected) - { - for (i = 0; i < NET_MAXPLAYERS; ++i) - { + if (net_client_connected) { + for (i = 0; i < NET_MAXPLAYERS; ++i) { result = result || local_playeringame[i]; } } @@ -620,8 +552,7 @@ static boolean PlayersInGame(void) // Whether single or multi-player, unless we are running as a drone, // we are in the game. - if (!drone) - { + if (!drone) { result = true; } @@ -631,13 +562,11 @@ static boolean PlayersInGame(void) // When using ticdup, certain values must be cleared out when running // the duplicate ticcmds. -static void TicdupSquash(ticcmd_set_t *set) -{ +static void TicdupSquash(ticcmd_set_t *set) { ticcmd_t *cmd; unsigned int i; - for (i = 0; i < NET_MAXPLAYERS ; ++i) - { + for (i = 0; i < NET_MAXPLAYERS; ++i) { cmd = &set->cmds[i]; cmd->chatchar = 0; if (cmd->buttons & BT_SPECIAL) @@ -648,15 +577,16 @@ static void TicdupSquash(ticcmd_set_t *set) // When running in single player mode, clear all the ingame[] array // except the local player. -static void SinglePlayerClear(ticcmd_set_t *set) -{ +static void SinglePlayerClear(ticcmd_set_t *set) { unsigned int i; - for (i = 0; i < NET_MAXPLAYERS; ++i) - { - if (i != localplayer) - { + for (i = 0; i < NET_MAXPLAYERS; ++i) { + if (i != localplayer) { +#if !DOOM_TINY set->ingame[i] = false; +#else + set->cmds[i].ingame = false; +#endif } } } @@ -665,16 +595,18 @@ static void SinglePlayerClear(ticcmd_set_t *set) // TryRunTics // -void TryRunTics (void) -{ - int i; - int lowtic; - int entertic; +void TryRunTics(void) { + int i; + int lowtic; + int entertic; static int oldentertics; int realtics; - int availabletics; - int counts; + int availabletics; + int counts; +#if PICO_BUILD + DEBUG_PINS_SET(tics, 2); +#endif // get real tics entertic = I_GetTime() / ticdup; realtics = entertic - oldentertics; @@ -683,30 +615,24 @@ void TryRunTics (void) // in singletics mode, run a single tic every time this function // is called. - if (singletics) - { + if (singletics) { BuildNewTic(); - } - else - { - NetUpdate (); + } else { + NetUpdate(); } lowtic = GetLowTic(); - availabletics = lowtic - gametic/ticdup; + availabletics = lowtic - gametic / ticdup; // decide how many tics to run - if (new_sync) - { - counts = availabletics; - } - else - { + if (new_sync) { + counts = availabletics; + } else { // decide how many tics to run - if (realtics < availabletics-1) - counts = realtics+1; + if (realtics < availabletics - 1) + counts = realtics + 1; else if (realtics < availabletics) counts = realtics; else @@ -715,33 +641,29 @@ void TryRunTics (void) if (counts < 1) counts = 1; - if (net_client_connected) - { + if (net_client_connected) { OldNetSync(); } } if (counts < 1) - counts = 1; + counts = 1; // wait for new tics if needed - while (!PlayersInGame() || lowtic < gametic/ticdup + counts) - { - NetUpdate (); + while (!PlayersInGame() || lowtic < gametic / ticdup + counts) { + NetUpdate(); lowtic = GetLowTic(); - if (lowtic < gametic/ticdup) - I_Error ("TryRunTics: lowtic < gametic"); + if (lowtic < gametic / ticdup) + I_Error("TryRunTics: lowtic < gametic"); // Still no tics to run? Sleep until some are available. - if (lowtic < gametic/ticdup + counts) - { + if (lowtic < gametic / ticdup + counts) { // If we're in a netgame, we might spin forever waiting for // new network data to be received. So don't stay in here // forever - give the menu a chance to work. - if (I_GetTime() / ticdup - entertic >= MAX_NETGAME_STALL_TICS) - { + if (I_GetTime() / ticdup - entertic >= MAX_NETGAME_STALL_TICS) { return; } @@ -750,43 +672,86 @@ void TryRunTics (void) } // run the count * ticdup dics - while (counts--) - { + while (counts--) { +#if PICO_BUILD + DEBUG_PINS_SET(tics, 1); +#endif ticcmd_set_t *set; - if (!PlayersInGame()) - { + if (!PlayersInGame()) { return; } set = &ticdata[(gametic / ticdup) % BACKUPTICS]; - - if (!net_client_connected) - { +#if DEBUG_CONSISTENCY + if (netgame) printf("apply tic %d\n", gametic); +#endif + if (!net_client_connected) { SinglePlayerClear(set); } - for (i=0 ; i lowtic) - I_Error ("gametic>lowtic"); + for (i = 0; i < ticdup; i++) { + if (gametic / ticdup > lowtic) + I_Error("gametic>lowtic"); +#if !DOOM_TINY memcpy(local_playeringame, set->ingame, sizeof(local_playeringame)); +#else + int lplayer_count = 0; + for(int j=0;jcmds[j].ingame; + lplayer_count += local_playeringame[j]; + } + if (net_client_connected && lplayer_count < 2) { + net_client_connected = false; + piconet_stop(); + } +#endif - loop_interface->RunTic(set->cmds, set->ingame); - gametic++; +//#define DUMP_TICS PICO_BUILD +#if DUMP_TICS +#define MAX_TIMES 64 + static uint16_t times[MAX_TIMES]; + uint32_t us = time_us_32(); +#endif + loop_interface->RunTic(set->cmds, local_playeringame); +#if DUMP_TICS + static int ticks; + static uint32_t t0; + times[ticks & (MAX_TIMES - 1)] = time_us_32() - us; + ticks++; + if (0 == (ticks & (MAX_TIMES - 1))) { + int min = 100000000; + int max = 0; + int total = 0; + for (int t = 0; t < MAX_TIMES; t++) { + total += times[t]; + if (times[t] > max) max = times[t]; + if (times[t] < min) min = times[t]; + } + printf("TICKS %d %d/block min/max/avg %d/%d/%d\n", ticks, (int) (time_us_32() - t0), min, max, + total / MAX_TIMES); + t0 = time_us_32(); + } +#endif + gametic++; - // modify command for duplicated tics + // modify command for duplicated tics TicdupSquash(set); - } + } - NetUpdate (); // check for new console commands + NetUpdate(); // check for new console commands +#if PICO_BUILD + DEBUG_PINS_CLR(tics, 1); +#endif } +#if PICO_BUILD + DEBUG_PINS_CLR(tics, 2); +#endif } -void D_RegisterLoopCallbacks(loop_interface_t *i) -{ +void D_RegisterLoopCallbacks(loop_interface_t *i) { loop_interface = i; } @@ -794,8 +759,7 @@ void D_RegisterLoopCallbacks(loop_interface_t *i) #include "m_misc.h" #include "w_wad.h" -static boolean StrictDemos(void) -{ +static boolean StrictDemos(void) { //! // @category demo // @@ -812,10 +776,8 @@ static boolean StrictDemos(void) // this extension (no extensions are allowed if -strictdemos is given // on the command line). A warning is shown on the console using the // provided string describing the non-vanilla expansion. -boolean D_NonVanillaRecord(boolean conditional, const char *feature) -{ - if (!conditional || StrictDemos()) - { +boolean D_NonVanillaRecord(boolean conditional, const char *feature) { + if (!conditional || StrictDemos()) { return false; } @@ -828,8 +790,8 @@ boolean D_NonVanillaRecord(boolean conditional, const char *feature) // Returns true if the given lump number corresponds to data from a .lmp // file, as opposed to a WAD. -static boolean IsDemoFile(int lumpnum) -{ +static boolean IsDemoFile(int lumpnum) { +#if !USE_MEMMAP_ONLY char *lower; boolean result; @@ -839,6 +801,9 @@ static boolean IsDemoFile(int lumpnum) free(lower); return result; +#else + return false; +#endif } // If the provided conditional value is true, we're trying to play back @@ -850,15 +815,12 @@ static boolean IsDemoFile(int lumpnum) // demo that comes from a .lmp file, not a .wad file. // - Before proceeding, a warning is shown to the user on the console. boolean D_NonVanillaPlayback(boolean conditional, int lumpnum, - const char *feature) -{ - if (!conditional || StrictDemos()) - { + const char *feature) { + if (!conditional || StrictDemos()) { return false; } - if (!IsDemoFile(lumpnum)) - { + if (!IsDemoFile(lumpnum)) { printf("Warning: WAD contains demo with a non-vanilla extension " "(%s)\n", feature); return false; @@ -871,3 +833,105 @@ boolean D_NonVanillaPlayback(boolean conditional, int lumpnum, return true; } +void D_StartNetGame(net_gamesettings_t *settings, + netgame_startup_callback_t callback) { + int i; + + offsetms = 0; + recvtic = 0; + + settings->consoleplayer = 0; + settings->num_players = 1; + settings->player_classes[0] = player_class; + +#if !NO_USE_NET + //! + // @category net + // + // Use original network client sync code rather than the improved + // sync code. + // + settings->new_sync = !M_ParmExists("-oldsync"); +#endif + + //! + // @category net + // @arg + // + // Send n extra tics in every packet as insurance against dropped + // packets. + // + +#if !NO_USE_ARGS + i = M_CheckParmWithArgs("-extratics", 1); + + if (i > 0) + settings->extratics = atoi(myargv[i+1]); + else +#endif + settings->extratics = 1; + + //! + // @category net + // @arg + // + // Reduce the resolution of the game by a factor of n, reducing + // the amount of network bandwidth needed. + // + +#if !NO_USE_ARGS + i = M_CheckParmWithArgs("-dup", 1); + + if (i > 0) + settings->ticdup = atoi(myargv[i+1]); + else +#endif + settings->ticdup = 1; + +#if !NO_USE_NET + if (net_client_connected) + { + // Send our game settings and block until game start is received + // from the server. + + NET_CL_StartGame(settings); + BlockUntilStart(settings, callback); + + // Read the game settings that were received. + + NET_CL_GetSettings(settings); + } +#endif + + if (drone) { + settings->consoleplayer = 0; + } + + // Set the local player and playeringame[] values. + + localplayer = settings->consoleplayer; + + for (i = 0; i < NET_MAXPLAYERS; ++i) { + local_playeringame[i] = i < settings->num_players; + } + + // Copy settings to global variables. + + ticdup = settings->ticdup; +#if !NO_USE_NET + new_sync = settings->new_sync; +#endif + + // TODO: Message disabled until we fix new_sync. + //if (!new_sync) + //{ + // printf("Syncing netgames like Vanilla Doom.\n"); + //} +} + +#if USE_PICO_NET +void D_StartPicoNetGame() { + gametic = maketic = recvtic = 0; + net_client_connected = true; +} +#endif \ No newline at end of file diff --git a/src/d_loop.h b/src/d_loop.h index f8538f0b..aac6d30c 100644 --- a/src/d_loop.h +++ b/src/d_loop.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -75,7 +76,8 @@ void D_StartNetGame(net_gamesettings_t *settings, netgame_startup_callback_t callback); extern boolean singletics; -extern int gametic, ticdup; +extern int gametic; +extern isb_int8_t ticdup; // Check if it is permitted to record a demo with a non-vanilla feature. boolean D_NonVanillaRecord(boolean conditional, const char *feature); @@ -84,5 +86,18 @@ boolean D_NonVanillaRecord(boolean conditional, const char *feature); boolean D_NonVanillaPlayback(boolean conditional, int lumpnum, const char *feature); +// The complete set of data for a particular tic. +typedef struct { + ticcmd_t cmds[NET_MAXPLAYERS]; +#if !DOOM_TINY + boolean ingame[NET_MAXPLAYERS]; +#endif +} ticcmd_set_t; + +#if USE_PICO_NET +// setup game loop for piconet play +void D_StartPicoNetGame(); +extern ticcmd_set_t ticdata[BACKUPTICS]; // we share the ticdata with piconet +#endif #endif diff --git a/src/d_mode.c b/src/d_mode.c index 26625ec4..d1444f13 100644 --- a/src/d_mode.c +++ b/src/d_mode.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -23,26 +24,30 @@ // Valid game mode/mission combinations, with the number of // episodes/maps for each. -static struct +static const struct { GameMission_t mission; GameMode_t mode; - int episode; - int map; + isb_int8_t episode; + isb_int8_t map; } valid_modes[] = { +#if !DOOM_ONLY { pack_chex, retail, 1, 5 }, +#endif { doom, shareware, 1, 9 }, { doom, registered, 3, 9 }, { doom, retail, 4, 9 }, { doom2, commercial, 1, 32 }, { pack_tnt, commercial, 1, 32 }, { pack_plut, commercial, 1, 32 }, +#if !DOOM_ONLY { pack_hacx, commercial, 1, 32 }, { heretic, shareware, 1, 9 }, { heretic, registered, 3, 9 }, { heretic, retail, 5, 9 }, { hexen, commercial, 1, 60 }, { strife, commercial, 1, 34 }, +#endif }; // Check that a gamemode+gamemission received over the network is valid. @@ -69,7 +74,7 @@ boolean D_ValidEpisodeMap(GameMission_t mission, GameMode_t mode, // Hacks for Heretic secret episodes - if (mission == heretic) + if (gamemission_is_heretic(mission)) { if (mode == retail && episode == 6) { @@ -120,20 +125,26 @@ static struct { GameMission_t mission; GameVersion_t version; } valid_versions[] = { +#if !DOOM_TINY { doom, exe_doom_1_2 }, { doom, exe_doom_1_666 }, { doom, exe_doom_1_7 }, { doom, exe_doom_1_8 }, +#endif { doom, exe_doom_1_9 }, +#if !DOOM_ONLY { doom, exe_hacx }, +#endif { doom, exe_ultimate }, { doom, exe_final }, { doom, exe_final2 }, +#if !DOOM_ONLY { doom, exe_chex }, { heretic, exe_heretic_1_3 }, { hexen, exe_hexen_1_1 }, { strife, exe_strife_1_2 }, { strife, exe_strife_1_31 }, +#endif }; boolean D_ValidGameVersion(GameMission_t mission, GameVersion_t version) @@ -143,7 +154,7 @@ boolean D_ValidGameVersion(GameMission_t mission, GameVersion_t version) // All Doom variants can use the Doom versions. if (mission == doom2 || mission == pack_plut || mission == pack_tnt - || mission == pack_hacx || mission == pack_chex) + || gamemission_is_hacx(mission) || gamemission_is_chex(mission)) { mission = doom; } @@ -167,17 +178,21 @@ boolean D_IsEpisodeMap(GameMission_t mission) switch (mission) { case doom: +#if !DOOM_ONLY case heretic: case pack_chex: +#endif return true; - case none: - case hexen: + case mission_none: case doom2: - case pack_hacx: case pack_tnt: case pack_plut: +#if !DOOM_ONLY + case hexen: + case pack_hacx: case strife: +#endif default: return false; } @@ -187,7 +202,7 @@ const char *D_GameMissionString(GameMission_t mission) { switch (mission) { - case none: + case mission_none: default: return "none"; case doom: @@ -198,6 +213,7 @@ const char *D_GameMissionString(GameMission_t mission) return "tnt"; case pack_plut: return "plutonia"; +#if !DOOM_ONLY case pack_hacx: return "hacx"; case pack_chex: @@ -208,6 +224,7 @@ const char *D_GameMissionString(GameMission_t mission) return "hexen"; case strife: return "strife"; +#endif } } diff --git a/src/d_mode.h b/src/d_mode.h index 7822a44a..8e6ec778 100644 --- a/src/d_mode.h +++ b/src/d_mode.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -30,14 +31,25 @@ typedef enum doom2, // Doom 2 pack_tnt, // Final Doom: TNT: Evilution pack_plut, // Final Doom: The Plutonia Experiment +#if !DOOM_ONLY pack_chex, // Chex Quest (modded doom) pack_hacx, // Hacx (modded doom2) heretic, // Heretic hexen, // Hexen strife, // Strife +#endif - none + mission_none } GameMission_t; +#if !DOOM_ONLY +#define gamemission_is_chex(m) ((m) == pack_chex) +#define gamemission_is_hacx(m) ((m) == pack_hacx) +#define gamemission_is_heretic(m) ((m) == heretic) +#else +#define gamemission_is_chex(m) 0 +#define gamemission_is_hacx(m) 0 +#define gamemission_is_heretic(m) 0 +#endif // The "mode" allows more accurate specification of the game mode we are // in: eg. shareware vs. registered. So doom1.wad and doom.wad are the @@ -61,10 +73,13 @@ typedef enum exe_doom_1_7, // Doom 1.7/1.7a: " exe_doom_1_8, // Doom 1.8: " exe_doom_1_9, // Doom 1.9: " +#if !DOOM_ONLY exe_hacx, // Hacx +#endif exe_ultimate, // Ultimate Doom (retail) exe_final, // Final Doom exe_final2, // Final Doom (alternate exe) +#if !DOOM_ONLY exe_chex, // Chex Quest executable (based on Final Doom) exe_heretic_1_3, // Heretic 1.3 @@ -72,7 +87,13 @@ typedef enum exe_hexen_1_1, // Hexen 1.1 exe_strife_1_2, // Strife v1.2 exe_strife_1_31 // Strife v1.31 +#endif } GameVersion_t; +#if !DOOM_ONLY +#define gameversion_is_chex(v) ((v) == exe_chex) +#else +#define gameversion_is_chex(v) 0 +#endif // What IWAD variant are we using? diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h index daf0da31..7bae844c 100644 --- a/src/d_ticcmd.h +++ b/src/d_ticcmd.h @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -39,18 +40,24 @@ typedef struct // villsa [STRIFE] according to the asm, // consistancy is a short, not a byte byte consistancy; // checks for net game +#if !DOOM_ONLY // villsa - Strife specific: byte buttons2; int inventory; - + // Heretic/Hexen specific: byte lookfly; // look/fly up/down/centering byte arti; // artitype_t to use -} ticcmd_t; - - - +#endif +#if DOOM_TINY + boolean ingame; // saves space elsewhere by moving from separate array (and the ^ is 7 bytes) +#endif +} ticcmd_t; +#if DOOM_TINY +#include +static_assert(sizeof(ticcmd_t) == 8, ""); +#endif #endif diff --git a/src/deh_io.c b/src/deh_io.c index 4a25941c..adab7c1b 100644 --- a/src/deh_io.c +++ b/src/deh_io.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -28,6 +29,7 @@ #include "deh_defs.h" #include "deh_io.h" +#if !NO_USE_DEH typedef enum { DEH_INPUT_FILE, @@ -66,12 +68,12 @@ static deh_context_t *DEH_NewContext(void) { deh_context_t *context; - context = Z_Malloc(sizeof(*context), PU_STATIC, NULL); + context = Z_Malloc(sizeof(*context), PU_STATIC, 0); // Initial read buffer size of 128 bytes context->readbuffer_size = 128; - context->readbuffer = Z_Malloc(context->readbuffer_size, PU_STATIC, NULL); + context->readbuffer = Z_Malloc(context->readbuffer_size, PU_STATIC, 0); context->linenum = 0; context->last_was_newline = true; @@ -120,7 +122,11 @@ deh_context_t *DEH_OpenLump(int lumpnum) context->input_buffer_pos = 0; context->filename = malloc(9); +#if !USE_MEMMAP_ONLY M_StringCopy(context->filename, lumpinfo[lumpnum]->name, 9); +#else + M_StringCopy(context->filename, "", 9); +#endif return context; } @@ -213,7 +219,7 @@ static void IncreaseReadBuffer(deh_context_t *context) int newbuffer_size; newbuffer_size = context->readbuffer_size * 2; - newbuffer = Z_Malloc(newbuffer_size, PU_STATIC, NULL); + newbuffer = Z_Malloc(newbuffer_size, PU_STATIC, 0); memcpy(newbuffer, context->readbuffer, context->readbuffer_size); @@ -335,3 +341,4 @@ boolean DEH_HadError(deh_context_t *context) return context->had_error; } +#endif \ No newline at end of file diff --git a/src/deh_main.c b/src/deh_main.c index d2721969..2df4db94 100644 --- a/src/deh_main.c +++ b/src/deh_main.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -31,6 +32,7 @@ #include "deh_io.h" #include "deh_main.h" +#if !NO_USE_DEH extern deh_section_t *deh_section_types[]; extern const char *deh_signatures[]; @@ -487,6 +489,7 @@ int DEH_LoadLumpByName(const char *name, boolean allow_long, boolean allow_error // Check the command line for -deh argument, and others. void DEH_ParseCommandLine(void) { +#if !NO_USE_ARGS char *filename; int p; @@ -511,5 +514,7 @@ void DEH_ParseCommandLine(void) ++p; } } +#endif } +#endif \ No newline at end of file diff --git a/src/deh_mapping.c b/src/deh_mapping.c index f96508b9..1d87e02d 100644 --- a/src/deh_mapping.c +++ b/src/deh_mapping.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -27,6 +28,7 @@ #include "deh_mapping.h" +#if !NO_USE_DEH static deh_mapping_entry_t *GetMappingEntryByName(deh_context_t *context, deh_mapping_t *mapping, char *name) @@ -198,3 +200,4 @@ void DEH_StructSHA1Sum(sha1_context_t *context, deh_mapping_t *mapping, } } +#endif \ No newline at end of file diff --git a/src/deh_str.c b/src/deh_str.c index 76af188a..acc7908a 100644 --- a/src/deh_str.c +++ b/src/deh_str.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -26,6 +27,7 @@ #include "z_zone.h" +#if !NO_USE_DEH typedef struct { char *from_text; @@ -103,7 +105,7 @@ static void InitHashTable(void) hash_table_entries = 0; hash_table_length = 16; hash_table = Z_Malloc(sizeof(deh_substitution_t *) * hash_table_length, - PU_STATIC, NULL); + PU_STATIC, 0); memset(hash_table, 0, sizeof(deh_substitution_t *) * hash_table_length); } @@ -124,7 +126,7 @@ static void IncreaseHashtable(void) hash_table_length *= 2; hash_table = Z_Malloc(sizeof(deh_substitution_t *) * hash_table_length, - PU_STATIC, NULL); + PU_STATIC, 0); memset(hash_table, 0, sizeof(deh_substitution_t *) * hash_table_length); // go through the old table and insert all the old entries @@ -184,7 +186,7 @@ void DEH_AddStringReplacement(const char *from_text, const char *to_text) Z_Free(sub->to_text); len = strlen(to_text) + 1; - sub->to_text = Z_Malloc(len, PU_STATIC, NULL); + sub->to_text = Z_Malloc(len, PU_STATIC, 0); memcpy(sub->to_text, to_text, len); } else @@ -194,11 +196,11 @@ void DEH_AddStringReplacement(const char *from_text, const char *to_text) // We need to create our own duplicates of the provided strings. len = strlen(from_text) + 1; - sub->from_text = Z_Malloc(len, PU_STATIC, NULL); + sub->from_text = Z_Malloc(len, PU_STATIC, 0); memcpy(sub->from_text, from_text, len); len = strlen(to_text) + 1; - sub->to_text = Z_Malloc(len, PU_STATIC, NULL); + sub->to_text = Z_Malloc(len, PU_STATIC, 0); memcpy(sub->to_text, to_text, len); DEH_AddToHashtable(sub); @@ -431,3 +433,4 @@ void DEH_snprintf(char *buffer, size_t len, const char *fmt, ...) va_end(args); } +#endif \ No newline at end of file diff --git a/src/deh_str.h b/src/deh_str.h index d547de6b..fed61979 100644 --- a/src/deh_str.h +++ b/src/deh_str.h @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -24,19 +25,18 @@ // Used to do dehacked text substitutions throughout the program +#if !NO_USE_DEH const char *DEH_String(const char *s) PRINTF_ARG_ATTR(1); void DEH_printf(const char *fmt, ...) PRINTF_ATTR(1, 2); void DEH_fprintf(FILE *fstream, const char *fmt, ...) PRINTF_ATTR(2, 3); void DEH_snprintf(char *buffer, size_t len, const char *fmt, ...) PRINTF_ATTR(3, 4); void DEH_AddStringReplacement(const char *from_text, const char *to_text); - - -#if 0 +#else // Static macro versions of the functions above #define DEH_String(x) (x) #define DEH_printf printf -#define DEH_fprintf fprintf +#define DEH_fprintf(x,...) stderr_print(__VA_ARGS__) #define DEH_snprintf snprintf #endif diff --git a/src/deh_text.c b/src/deh_text.c index 9e621b43..0adc0c94 100644 --- a/src/deh_text.c +++ b/src/deh_text.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -27,6 +28,7 @@ #include "deh_io.h" #include "deh_main.h" +#if !NO_USE_DEH // Given a string length, find the maximum length of a // string that can replace it. @@ -111,3 +113,4 @@ deh_section_t deh_section_text = NULL, }; +#endif \ No newline at end of file diff --git a/src/doom/CMakeLists.txt b/src/doom/CMakeLists.txt index 0310910a..2b2c2094 100644 --- a/src/doom/CMakeLists.txt +++ b/src/doom/CMakeLists.txt @@ -1,4 +1,6 @@ -add_library(doom STATIC +add_library(doom INTERFACE) +cmake_policy(SET CMP0076 NEW) +target_sources(doom INTERFACE am_map.c am_map.h deh_ammo.c deh_bexstr.c @@ -21,8 +23,10 @@ add_library(doom STATIC dstrings.c dstrings.h d_textur.h d_think.h - f_finale.c f_finale.h - f_wipe.c f_wipe.h + f_finale.h + f_finale.c + f_wipe.c + f_wipe.h g_game.c g_game.h hu_lib.c hu_lib.h hu_stuff.c hu_stuff.h @@ -50,6 +54,7 @@ add_library(doom STATIC p_tick.c p_tick.h p_user.c r_bsp.c r_bsp.h + r_data_whd.c r_data.c r_data.h r_defs.h r_draw.c r_draw.h @@ -67,5 +72,9 @@ add_library(doom STATIC st_stuff.c st_stuff.h wi_stuff.c wi_stuff.h) -target_include_directories(doom PRIVATE "../" "${CMAKE_CURRENT_BINARY_DIR}/../../") -target_link_libraries(doom SDL2::SDL2 SDL2::mixer SDL2::net) +target_include_directories(doom INTERFACE "../" "${CMAKE_CURRENT_BINARY_DIR}/../../") +if (NOT PICO_SDK) + target_link_libraries(doom INTERFACE SDL2::SDL2 SDL2::mixer SDL2::net) +else() + target_link_libraries(doom INTERFACE pico_stdlib pico_multicore) +endif() diff --git a/src/doom/am_map.c b/src/doom/am_map.c index 2ea9f62d..3a4dee55 100644 --- a/src/doom/am_map.c +++ b/src/doom/am_map.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -143,7 +144,7 @@ typedef struct // starting from the middle. // #define R ((8*PLAYERRADIUS)/7) -mline_t player_arrow[] = { +static const mline_t player_arrow[] = { { { -R+R/8, 0 }, { R, 0 } }, // ----- { { R, 0 }, { R-R/2, R/4 } }, // -----> { { R, 0 }, { R-R/2, -R/4 } }, @@ -155,7 +156,7 @@ mline_t player_arrow[] = { #undef R #define R ((8*PLAYERRADIUS)/7) -mline_t cheat_player_arrow[] = { +static const mline_t cheat_player_arrow[] = { { { -R+R/8, 0 }, { R, 0 } }, // ----- { { R, 0 }, { R-R/2, R/6 } }, // -----> { { R, 0 }, { R-R/2, -R/6 } }, @@ -175,16 +176,16 @@ mline_t cheat_player_arrow[] = { }; #undef R -#define R (FRACUNIT) -mline_t triangle_guy[] = { - { { (fixed_t)(-.867*R), (fixed_t)(-.5*R) }, { (fixed_t)(.867*R ), (fixed_t)(-.5*R) } }, - { { (fixed_t)(.867*R ), (fixed_t)(-.5*R) }, { (fixed_t)(0 ), (fixed_t)(R ) } }, - { { (fixed_t)(0 ), (fixed_t)(R ) }, { (fixed_t)(-.867*R), (fixed_t)(-.5*R) } } -}; -#undef R +//#define R (FRACUNIT) +//static const mline_t triangle_guy[] = { +// { { (fixed_t)(-.867*R), (fixed_t)(-.5*R) }, { (fixed_t)(.867*R ), (fixed_t)(-.5*R) } }, +// { { (fixed_t)(.867*R ), (fixed_t)(-.5*R) }, { (fixed_t)(0 ), (fixed_t)(R ) } }, +// { { (fixed_t)(0 ), (fixed_t)(R ) }, { (fixed_t)(-.867*R), (fixed_t)(-.5*R) } } +//}; +//#undef R #define R (FRACUNIT) -mline_t thintriangle_guy[] = { +static const mline_t thintriangle_guy[] = { { { (fixed_t)(-.5*R), (fixed_t)(-.7*R) }, { (fixed_t)(R ), (fixed_t)(0 ) } }, { { (fixed_t)(R ), (fixed_t)(0 ) }, { (fixed_t)(-.5*R), (fixed_t)(.7*R ) } }, { { (fixed_t)(-.5*R), (fixed_t)(.7*R ) }, { (fixed_t)(-.5*R), (fixed_t)(-.7*R) } } @@ -194,14 +195,14 @@ mline_t thintriangle_guy[] = { -static int cheating = 0; -static int grid = 0; +static isb_int8_t cheating = 0; +static isb_int8_t grid = 0; static int leveljuststarted = 1; // kluge until AM_LevelInit() is called boolean automapactive = false; -static int finit_width = SCREENWIDTH; -static int finit_height = SCREENHEIGHT - ST_HEIGHT; +#define finit_width SCREENWIDTH +#define finit_height (SCREENHEIGHT - ST_HEIGHT) // location of window on screen static int f_x; @@ -211,7 +212,11 @@ static int f_y; static int f_w; static int f_h; +#if DOOM_TINY +#define lightlev 0 // unused anyway +#else static int lightlev; // used for funky strobing effect +#endif static pixel_t* fb; // pseudo-frame buffer static int amclock; @@ -259,11 +264,13 @@ static fixed_t scale_ftom; static player_t *plr; // the player represented by an arrow -static patch_t *marknums[10]; // numbers used for marking by the automap +#if !USE_WHD +static vpatch_handle_large_t marknums[10]; // numbers used for marking by the automap +#endif static mpoint_t markpoints[AM_NUMMARKPOINTS]; // where the points are static int markpointnum = 0; // next point to be assigned -static int followplayer = 1; // specifies whether to follow the player around +static isb_int8_t followplayer = 1; // specifies whether to follow the player around cheatseq_t cheat_amap = CHEAT("iddt", 0); @@ -328,8 +335,8 @@ void AM_restoreScaleAndLoc(void) m_x = old_m_x; m_y = old_m_y; } else { - m_x = plr->mo->x - m_w/2; - m_y = plr->mo->y - m_h/2; + m_x = plr->mo->xy.x - m_w/2; + m_y = plr->mo->xy.y - m_h/2; } m_x2 = m_x + m_w; m_y2 = m_y + m_h; @@ -365,17 +372,22 @@ void AM_findMinMaxBoundaries(void) for (i=0;i max_x) - max_x = vertexes[i].x; + if (vertex_x_raw(&vertexes[i]) < min_x) + min_x = vertex_x_raw(&vertexes[i]); + else if (vertex_x_raw(&vertexes[i]) > max_x) + max_x = vertex_x_raw(&vertexes[i]); - if (vertexes[i].y < min_y) - min_y = vertexes[i].y; - else if (vertexes[i].y > max_y) - max_y = vertexes[i].y; + if (vertex_y_raw(&vertexes[i]) < min_y) + min_y = vertex_y_raw(&vertexes[i]); + else if (vertex_y_raw(&vertexes[i]) > max_y) + max_y = vertex_y_raw(&vertexes[i]); } - + + min_x = vertex_raw_to_fixed(min_x); + min_y = vertex_raw_to_fixed(min_y); + max_x = vertex_raw_to_fixed(max_x); + max_y = vertex_raw_to_fixed(max_y); + max_w = max_x - min_x; max_h = max_y - min_y; @@ -429,11 +441,12 @@ void AM_initVariables(void) static event_t st_notify = { ev_keyup, AM_MSGENTERED, 0, 0 }; automapactive = true; - fb = I_VideoBuffer; f_oldloc.x = INT_MAX; amclock = 0; +#if !DOOM_TINY lightlev = 0; +#endif m_paninc.x = m_paninc.y = 0; ftom_zoommul = FRACUNIT; @@ -461,8 +474,8 @@ void AM_initVariables(void) } } - m_x = plr->mo->x - m_w/2; - m_y = plr->mo->y - m_h/2; + m_x = plr->mo->xy.x - m_w/2; + m_y = plr->mo->xy.y - m_h/2; AM_changeWindowLoc(); // for saving & restoring @@ -486,14 +499,17 @@ void AM_loadPics(void) for (i=0;i<10;i++) { +#if !USE_WHD DEH_snprintf(namebuf, 9, "AMMNUM%d", i); marknums[i] = W_CacheLumpName(namebuf, PU_STATIC); +#endif } } void AM_unloadPics(void) { +#if !DOOM_TINY int i; char namebuf[9]; @@ -502,6 +518,7 @@ void AM_unloadPics(void) DEH_snprintf(namebuf, 9, "AMMNUM%d", i); W_ReleaseLumpName(namebuf); } +#endif } void AM_clearMarks(void) @@ -555,7 +572,7 @@ void AM_Stop (void) // void AM_Start (void) { - static int lastlevel = -1, lastepisode = -1; + static isb_int8_t lastlevel = -1, lastepisode = -1; if (!stopped) AM_Stop(); stopped = false; @@ -605,6 +622,7 @@ AM_Responder rc = false; +#if !NO_USE_JOYSTICK if (ev->type == ev_joystick && joybautomap >= 0 && (ev->data1 & (1 << joybautomap)) != 0) { @@ -624,6 +642,7 @@ AM_Responder return true; } +#endif if (!automapactive) { @@ -784,14 +803,14 @@ void AM_changeWindowScale(void) void AM_doFollowPlayer(void) { - if (f_oldloc.x != plr->mo->x || f_oldloc.y != plr->mo->y) + if (f_oldloc.x != plr->mo->xy.x || f_oldloc.y != plr->mo->xy.y) { - m_x = FTOM(MTOF(plr->mo->x)) - m_w/2; - m_y = FTOM(MTOF(plr->mo->y)) - m_h/2; + m_x = FTOM(MTOF(plr->mo->xy.x)) - m_w/2; + m_y = FTOM(MTOF(plr->mo->xy.y)) - m_h/2; m_x2 = m_x + m_w; m_y2 = m_y + m_h; - f_oldloc.x = plr->mo->x; - f_oldloc.y = plr->mo->y; + f_oldloc.x = plr->mo->xy.x; + f_oldloc.y = plr->mo->xy.y; // m_x = FTOM(MTOF(plr->mo->x - m_w/2)); // m_y = FTOM(MTOF(plr->mo->y - m_h/2)); @@ -805,6 +824,7 @@ void AM_doFollowPlayer(void) // // // +#if !DOOM_TINY void AM_updateLightLev(void) { static int nexttic = 0; @@ -821,7 +841,7 @@ void AM_updateLightLev(void) } } - +#endif // // Updates on Game Tick @@ -856,6 +876,9 @@ void AM_Ticker (void) // void AM_clearFB(int color) { +#if PICODOOM_RENDER_NEWHOPE + assert(f_h <= MAIN_VIEWHEIGHT); +#endif memset(fb, color, f_w*f_h*sizeof(*fb)); } @@ -1017,7 +1040,8 @@ AM_drawFline register int ax; register int ay; register int d; - + +#if !DOOM_TINY static int fuck = 0; // For debugging only @@ -1029,6 +1053,7 @@ AM_drawFline DEH_fprintf(stderr, "fuck %d \r", fuck++); return; } +#endif #define PUTDOT(xx,yy,cc) fb[(yy)*f_w+(xx)]=(cc) @@ -1148,37 +1173,38 @@ void AM_drawWalls(void) int i; static mline_t l; - for (i=0;ix; - l.a.y = lines[i].v1->y; - l.b.x = lines[i].v2->x; - l.b.y = lines[i].v2->y; - if (cheating || (lines[i].flags & ML_MAPPED)) + l.a.x = vertex_x(line_v1(li)); + l.a.y = vertex_y(line_v1(li)); + l.b.x = vertex_x(line_v2(li)); + l.b.y = vertex_y(line_v2(li)); + if (cheating || line_is_mapped(li)) { - if ((lines[i].flags & LINE_NEVERSEE) && !cheating) + if ((line_flags(li) & LINE_NEVERSEE) && !cheating) continue; - if (!lines[i].backsector) + if (!line_backsector(li)) { AM_drawMline(&l, WALLCOLORS+lightlev); } else { - if (lines[i].special == 39) + if (line_special(li) == 39) { // teleporters AM_drawMline(&l, WALLCOLORS+WALLRANGE/2); } - else if (lines[i].flags & ML_SECRET) // secret door + else if (line_flags(li) & ML_SECRET) // secret door { if (cheating) AM_drawMline(&l, SECRETWALLCOLORS + lightlev); else AM_drawMline(&l, WALLCOLORS+lightlev); } - else if (lines[i].backsector->floorheight - != lines[i].frontsector->floorheight) { + else if (line_backsector(li)->rawfloorheight + != line_frontsector(li)->rawfloorheight) { AM_drawMline(&l, FDWALLCOLORS + lightlev); // floor level change } - else if (lines[i].backsector->ceilingheight - != lines[i].frontsector->ceilingheight) { + else if (line_backsector(li)->rawceilingheight + != line_frontsector(li)->rawceilingheight) { AM_drawMline(&l, CDWALLCOLORS+lightlev); // ceiling level change } else if (cheating) { @@ -1188,7 +1214,7 @@ void AM_drawWalls(void) } else if (plr->powers[pw_allmap]) { - if (!(lines[i].flags & LINE_NEVERSEE)) AM_drawMline(&l, GRAYS+3); + if (!(line_flags(li) & LINE_NEVERSEE)) AM_drawMline(&l, GRAYS+3); } } } @@ -1207,19 +1233,19 @@ AM_rotate fixed_t tmpx; tmpx = - FixedMul(*x,finecosine[a>>ANGLETOFINESHIFT]) - - FixedMul(*y,finesine[a>>ANGLETOFINESHIFT]); + FixedMul(*x,finecosine(a>>ANGLETOFINESHIFT)) + - FixedMul(*y,finesine(a>>ANGLETOFINESHIFT)); *y = - FixedMul(*x,finesine[a>>ANGLETOFINESHIFT]) - + FixedMul(*y,finecosine[a>>ANGLETOFINESHIFT]); + FixedMul(*x,finesine(a>>ANGLETOFINESHIFT)) + + FixedMul(*y,finecosine(a>>ANGLETOFINESHIFT)); *x = tmpx; } void AM_drawLineCharacter -( mline_t* lineguy, +( const mline_t* lineguy, int lineguylines, fixed_t scale, angle_t angle, @@ -1279,11 +1305,11 @@ void AM_drawPlayers(void) if (cheating) AM_drawLineCharacter (cheat_player_arrow, arrlen(cheat_player_arrow), 0, - plr->mo->angle, WHITE, plr->mo->x, plr->mo->y); + mobj_angle(plr->mo), WHITE, plr->mo->xy.x, plr->mo->xy.y); else AM_drawLineCharacter - (player_arrow, arrlen(player_arrow), 0, plr->mo->angle, - WHITE, plr->mo->x, plr->mo->y); + (player_arrow, arrlen(player_arrow), 0, mobj_angle(plr->mo), + WHITE, plr->mo->xy.x, plr->mo->xy.y); return; } @@ -1304,8 +1330,8 @@ void AM_drawPlayers(void) color = their_colors[their_color]; AM_drawLineCharacter - (player_arrow, arrlen(player_arrow), 0, p->mo->angle, - color, p->mo->x, p->mo->y); + (player_arrow, arrlen(player_arrow), 0, mobj_angle(p->mo), + color, p->mo->xy.x, p->mo->xy.y); } } @@ -1320,13 +1346,13 @@ AM_drawThings for (i=0;iangle, colors+lightlev, t->x, t->y); - t = t->snext; + 16<xy.x, t->xy.y); + t = mobj_snext(t); } } } @@ -1345,8 +1371,14 @@ void AM_drawMarks(void) h = 6; // because something's wrong with the wad, i guess fx = CXMTOF(markpoints[i].x); fy = CYMTOF(markpoints[i].y); + vpatch_handle_large_t patch; +#if USE_WHD + patch = VPATCH_AMMNUM0 + i; +#else + patch = marknums[i]; +#endif if (fx >= f_x && fx <= f_w - w && fy >= f_y && fy <= f_h - h) - V_DrawPatch(fx, fy, marknums[i]); + V_DrawPatch(fx, fy, patch); } } @@ -1361,6 +1393,7 @@ void AM_drawCrosshair(int color) void AM_Drawer (void) { if (!automapactive) return; + fb = I_VideoBuffer; AM_clearFB(BACKGROUND); if (grid) diff --git a/src/doom/d_items.c b/src/doom/d_items.c index 33f310c6..602db16a 100644 --- a/src/doom/d_items.c +++ b/src/doom/d_items.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -34,7 +35,7 @@ // atkstate, i.e. attack/fire/hit frame // flashstate, muzzle flash // -weaponinfo_t weaponinfo[NUMWEAPONS] = +should_be_const weaponinfo_t weaponinfo[NUMWEAPONS] = { { // fist diff --git a/src/doom/d_items.h b/src/doom/d_items.h index 3d22a063..964c6156 100644 --- a/src/doom/d_items.h +++ b/src/doom/d_items.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -28,14 +29,14 @@ typedef struct { ammotype_t ammo; - int upstate; - int downstate; - int readystate; - int atkstate; - int flashstate; + isb_int16_t upstate; + isb_int16_t downstate; + isb_int16_t readystate; + isb_int16_t atkstate; + isb_int16_t flashstate; } weaponinfo_t; -extern weaponinfo_t weaponinfo[NUMWEAPONS]; +extern should_be_const weaponinfo_t weaponinfo[NUMWEAPONS]; #endif diff --git a/src/doom/d_main.c b/src/doom/d_main.c index 49a2c7f5..969e49a4 100644 --- a/src/doom/d_main.c +++ b/src/doom/d_main.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -73,9 +74,14 @@ #include "r_local.h" #include "statdump.h" +#if PICO_DOOM +#include "picodoom.h" +#endif #include "d_main.h" - +#if PICO_BUILD +#include "i_picosound.h" +#endif // // D-DoomLoop() // Not a globally visible function, @@ -89,11 +95,13 @@ void D_DoomLoop (void); // Location where savegames are stored +#if !NO_FILE_ACCESS char * savegamedir; // location of IWAD and WAD files char * iwadfile; +#endif boolean devparm; // started game with -devparm @@ -105,13 +113,11 @@ boolean fastparm; // checkparm of -fast //extern int sfxVolume; //extern int musicVolume; -extern boolean inhelpscreens; - skill_t startskill; -int startepisode; -int startmap; +isb_int8_t startepisode; +isb_int8_t startmap; boolean autostart; -int startloadgame; +isb_int8_t startloadgame; boolean advancedemo; @@ -121,11 +127,17 @@ boolean storedemo; // If true, the main game loop has started. boolean main_loop_started = false; +#if !USE_MEMMAP_ONLY char wadfile[1024]; // primary wad file +#endif char mapdir[1024]; // directory of development maps -int show_endoom = 1; +isb_int8_t show_endoom = 1; +#if !DOOM_TINY int show_diskicon = 1; +#else +#define show_diskicon 0 +#endif void D_ConnectNetGame(void); @@ -163,23 +175,29 @@ void D_ProcessEvents (void) // wipegamestate can be set to -1 to force a wipe on the next draw gamestate_t wipegamestate = GS_DEMOSCREEN; extern boolean setsizeneeded; -extern int showMessages; +extern isb_int8_t showMessages; void R_ExecuteSetViewSize (void); boolean D_Display (void) { +#if PICO_DOOM + pd_begin_frame(); +#endif + +#if !DOOM_TINY static boolean viewactivestate = false; static boolean menuactivestate = false; static boolean inhelpscreensstate = false; static boolean fullscreen = false; +#endif static gamestate_t oldgamestate = -1; - static int borderdrawcount; - int y; boolean wipe; +#if !DOOM_TINY + static int borderdrawcount; boolean redrawsbar; - + redrawsbar = false; - + // change the view size if needed if (setsizeneeded) { @@ -187,19 +205,27 @@ boolean D_Display (void) oldgamestate = -1; // force background redraw borderdrawcount = 3; } +#endif // save the current screen if about to wipe if (gamestate != wipegamestate) { wipe = true; +#if !DOOM_TINY wipe_StartScreen(0, 0, SCREENWIDTH, SCREENHEIGHT); +#endif } else wipe = false; +#if DOOM_TINY + // we only redraw briefly during wipe + if (!wipestate || wipestate == WIPESTATE_REDRAW1) { +#endif if (gamestate == GS_LEVEL && gametic) HU_Erase(); - + +#if !PD_COLUMNS // do buffered drawing switch (gamestate) { @@ -217,7 +243,9 @@ boolean D_Display (void) break; case GS_INTERMISSION: +#if !NO_USE_WI WI_Drawer (); +#endif break; case GS_FINALE: @@ -228,28 +256,48 @@ boolean D_Display (void) D_PageDrawer (); break; } +#endif // draw buffered stuff to screen I_UpdateNoBlit (); // draw the view directly +#if !DOOM_TINY if (gamestate == GS_LEVEL && !automapactive && gametic) - R_RenderPlayerView (&players[displayplayer]); +#else + // not sure what the reason for not drawing when gametic == 0 (i.e. the first frame of the game), however it means we + // don't draw anything in a network game until the sync is done, which screws up what we see during the wipe. I haven't + // seen any downside to doing the drawing. + if (gamestate == GS_LEVEL && !automapactive) +#endif + R_RenderPlayerView (&players[displayplayer]); +#if !DOOM_TINY if (gamestate == GS_LEVEL && gametic) HU_Drawer (); - +#endif + // clean up border stuff - if (gamestate != oldgamestate && gamestate != GS_LEVEL) - I_SetPalette (W_CacheLumpName (DEH_String("PLAYPAL"),PU_CACHE)); + if (gamestate != oldgamestate && gamestate != GS_LEVEL) { +#if !USE_WHD + I_SetPalette (W_CacheLumpName (DEH_String("PLAYPAL"),PU_CACHE)); +#else + I_SetPaletteNum(0); +#endif + } // see if the border needs to be initially drawn if (gamestate == GS_LEVEL && oldgamestate != GS_LEVEL) { +#if !DOOM_TINY viewactivestate = false; // view was not active +#endif +#if !NO_RDRAW R_FillBackScreen (); // draw the pattern into the back screen +#endif } +#if !DOOM_TINY // see if the border needs to be updated to the screen if (gamestate == GS_LEVEL && !automapactive && scaledviewwidth != SCREENWIDTH) { @@ -257,38 +305,50 @@ boolean D_Display (void) borderdrawcount = 3; if (borderdrawcount) { +#if !NO_RDRAW R_DrawViewBorder (); // erase old menu stuff +#endif borderdrawcount--; } - } +#endif if (testcontrols) { // Box showing current mouse speed - +#if !NO_USE_MOUSE V_DrawMouseSpeedBox(testcontrols_mousespeed); +#endif } +#if !DOOM_TINY menuactivestate = menuactive; viewactivestate = viewactive; inhelpscreensstate = inhelpscreens; +#endif oldgamestate = wipegamestate = gamestate; // draw pause pic if (paused) { + int y; if (automapactive) y = 4; else y = viewwindowy+4; - V_DrawPatchDirect(viewwindowx + (scaledviewwidth - 68) / 2, y, - W_CacheLumpName (DEH_String("M_PAUSE"), PU_CACHE)); + V_DrawPatchDirect(viewwindowx + (scaledviewwidth - 68) / 2, y, VPATCH_HANDLE(VPATCH_NAME(M_PAUSE))); } +#if DOOM_TINY + } // end of check of wipe state +#endif +#if PICO_DOOM + pd_end_frame(wipe); +#else // menus go directly to the screen M_Drawer (); // menu is drawn even on top of everything +#endif NetUpdate (); // send out any new accumulation return wipe; @@ -327,7 +387,9 @@ void D_BindVariables(void) I_BindInputVariables(); I_BindVideoVariables(); +#if !NO_USE_JOYSTICK I_BindJoystickVariables(); +#endif I_BindSoundVariables(); M_BindBaseControls(); @@ -341,18 +403,21 @@ void D_BindVariables(void) key_multi_msgplayer[2] = HUSTR_KEYBROWN; key_multi_msgplayer[3] = HUSTR_KEYRED; +#if !NO_USE_NET NET_BindVariables(); +#endif - M_BindIntVariable("mouse_sensitivity", &mouseSensitivity); + M_BindIntISB8Variable("mouse_sensitivity", &mouseSensitivity); M_BindIntVariable("sfx_volume", &sfxVolume); M_BindIntVariable("music_volume", &musicVolume); - M_BindIntVariable("show_messages", &showMessages); - M_BindIntVariable("screenblocks", &screenblocks); - M_BindIntVariable("detaillevel", &detailLevel); + M_BindIntISB8Variable("show_messages", &showMessages); + M_BindIntISB8Variable("screenblocks", &screenblocks); + M_BindIntISB8Variable("detaillevel", &detailLevel); M_BindIntVariable("snd_channels", &snd_channels); - M_BindIntVariable("vanilla_savegame_limit", &vanilla_savegame_limit); - M_BindIntVariable("vanilla_demo_limit", &vanilla_demo_limit); - M_BindIntVariable("show_endoom", &show_endoom); + M_BindIntISB8Variable("vanilla_savegame_limit", &vanilla_savegame_limit); + M_BindIntISB8Variable("vanilla_demo_limit", &vanilla_demo_limit); + M_BindIntISB8Variable("show_endoom", &show_endoom); +#if !DOOM_TINY M_BindIntVariable("show_diskicon", &show_diskicon); // Multiplayer chat macros @@ -364,6 +429,7 @@ void D_BindVariables(void) M_snprintf(buf, sizeof(buf), "chatmacro%i", i); M_BindStringVariable(buf, &chat_macros[i]); } +#endif } // @@ -394,6 +460,7 @@ boolean D_GrabMouseCallback(void) // void D_RunFrame() { +#if !DOOM_TINY int nowtime; int tics; static int wipestart; @@ -416,6 +483,7 @@ void D_RunFrame() I_FinishUpdate (); // page flip or blit buffer return; } +#endif // frame syncronous IO operations I_StartFrame (); @@ -427,6 +495,7 @@ void D_RunFrame() // Update display, next frame, with current state if no profiling is on if (screenvisible && !nodrawers) { +#if !DOOM_TINY if ((wipe = D_Display ())) { // start wipe on this frame @@ -437,6 +506,11 @@ void D_RunFrame() // normal update I_FinishUpdate (); // page flip or blit buffer } +#else + do { + D_Display(); + } while (wipestate); +#endif } } @@ -454,15 +528,26 @@ void D_DoomLoop (void) " may cause demos and network games to get out of sync.\n"); } +#if !NO_DEMO_RECORDING if (demorecording) G_BeginRecording (); +#endif main_loop_started = true; I_SetWindowTitle(gamedescription); I_GraphicsCheckCommandLine(); +#if !NO_USE_MOUSE I_SetGrabMouseCallback(D_GrabMouseCallback); +#endif I_InitGraphics(); +#if USB_SUPPORT + printf("Sleeping 2s for USB devices\n"); // TinyUSB still grinds to a halt during connect/disconnect + absolute_time_t end_time = make_timeout_time_ms(2000); + do { + tuh_task(); + } while (!time_reached(end_time)); +#endif EnableLoadingDisk(); TryRunTics(); @@ -480,6 +565,12 @@ void D_DoomLoop (void) while (1) { D_RunFrame(); +#if PICO_DOOM_INFO + static uint8_t x = 0; + if (!++x) { + printf ("free zone memory: 0x%x\n", Z_FreeMemory()); + } +#endif } } @@ -510,7 +601,9 @@ void D_PageTicker (void) // void D_PageDrawer (void) { +#if !NO_PAGE_DRAWER && !DOOM_TINY V_DrawPatch (0, 0, W_CacheLumpName(pagename, PU_CACHE)); +#endif } @@ -536,6 +629,7 @@ void D_DoAdvanceDemo (void) paused = false; gameaction = ga_nothing; +#if !DOOM_TINY // The Ultimate Doom executable changed the demo sequence to add // a DEMO4 demo. Final Doom was based on Ultimate, so also // includes this change; however, the Final Doom IWADs do not @@ -546,6 +640,10 @@ void D_DoAdvanceDemo (void) // includes a fixed executable. if (gameversion == exe_ultimate || gameversion == exe_final) +#else + // dont want to preserver above^ + if (gameversion == exe_ultimate) +#endif demosequence = (demosequence+1)%7; else demosequence = (demosequence+1)%6; @@ -675,6 +773,7 @@ static const char *banners[] = static char *GetGameName(char *gamename) { +#if !NO_USE_DEH size_t i; const char *deh_sub; @@ -683,7 +782,7 @@ static char *GetGameName(char *gamename) // Has the banner been replaced? deh_sub = DEH_String(banners[i]); - + if (deh_sub != banners[i]) { size_t gamename_size; @@ -712,7 +811,7 @@ static char *GetGameName(char *gamename) return gamename; } } - +#endif return gamename; } @@ -760,32 +859,22 @@ void D_IdentifyVersion(void) // any known IWAD name, we may have a dilemma. Try to // identify by its contents. - if (gamemission == none) + if (gamemission == mission_none) { - unsigned int i; - - for (i=0; iname, "MAP01", 8)) - { - gamemission = doom2; - break; - } - else if (!strncasecmp(lumpinfo[i]->name, "E1M1", 8)) - { - gamemission = doom; - break; - } + if (W_CheckNumForName("MAP01") >= 0) { + gamemission = doom2; + } else if (W_CheckNumForName("E1M1") >= 0) { + gamemission = doom; } - if (gamemission == none) + if (gamemission == mission_none) { // Still no idea. I don't think this is going to work. I_Error("Unknown or invalid IWAD file."); } } - +#if !DEMO1_ONLY // Make sure gamemode is set up correctly if (logical_gamemission == doom) @@ -826,20 +915,24 @@ void D_IdentifyVersion(void) // detecting it based on the filename. Valid values are: "doom2", // "tnt" and "plutonia". // +#if !NO_USE_ARGS p = M_CheckParmWithArgs("-pack", 1); if (p > 0) { SetMissionForPackName(myargv[p + 1]); } +#endif } +#endif } // Set the gamedescription string void D_SetGameDescription(void) { - gamedescription = "Unknown"; +#if !DEMO1_ONLY + gamedescription = "Unknown"; if (logical_gamemission == doom) { // Doom 1. But which version? @@ -888,16 +981,18 @@ void D_SetGameDescription(void) gamedescription = GetGameName("DOOM 2: TNT - Evilution"); } } +#else + gamedescription = GetGameName("DOOM Shareware"); +#endif } // print title for every printed line char title[128]; -static boolean D_AddFile(char *filename) +static boolean D_AddFile(const char *filename) { wad_file_t *handle; - printf(" adding %s\n", filename); handle = W_AddFile(filename); return handle != NULL; @@ -930,6 +1025,7 @@ static const char *copyright_banners[] = void PrintDehackedBanners(void) { +#if !NO_USE_DEH size_t i; for (i=0; iname); +#endif // Now that we've loaded the IWAD, we can figure out what gamemission // we're playing and which version of Vanilla Doom we need to emulate. @@ -1437,6 +1589,7 @@ void D_DoomMain (void) // Check which IWAD variant we are using. +#if !DEMO1_ONLY if (W_CheckNumForName("FREEDOOM") >= 0) { if (W_CheckNumForName("FREEDM") >= 0) @@ -1452,7 +1605,9 @@ void D_DoomMain (void) { gamevariant = bfgedition; } +#endif +#if !NO_USE_DEH //! // @category mod // @@ -1465,6 +1620,7 @@ void D_DoomMain (void) // them to be played properly. LoadIwadDeh(); } +#endif // Doom 3: BFG Edition includes modified versions of the classic // IWADs which can be identified by an additional DMENUPIC lump. @@ -1474,6 +1630,7 @@ void D_DoomMain (void) // We specifically check for DMENUPIC here, before PWADs have been // loaded which could probably include a lump of that name. +#if !NO_USE_DEH if (gamevariant == bfgedition) { printf("BFG Edition: Using workarounds as needed.\n"); @@ -1505,7 +1662,9 @@ void D_DoomMain (void) // can swap this in instead, and it kind of makes sense. DEH_AddStringReplacement("M_SCRNSZ", "M_DISP"); } +#endif +#if !USE_SINGLE_IWAD //! // @category mod // @@ -1519,7 +1678,10 @@ void D_DoomMain (void) W_AutoLoadWADs(autoload_dir); free(autoload_dir); } +#endif + +#if !NO_USE_DEH // Load Dehacked patches specified on the command line with -deh. // Note that there's a very careful and deliberate ordering to how // Dehacked patches are loaded. The order we use is: @@ -1527,9 +1689,12 @@ void D_DoomMain (void) // 2. Command line dehacked patches specified with -deh. // 3. PWAD dehacked patches in DEHACKED lumps. DEH_ParseCommandLine(); +#endif +#if !DOOM_TINY // Load PWAD files. modifiedgame = W_ParseCommandLine(); +#endif // Debug: // W_PrintDirectory(); @@ -1542,6 +1707,7 @@ void D_DoomMain (void) // Play back the demo named demo.lmp. // +#if !NO_USE_ARGS p = M_CheckParmWithArgs ("-playdemo", 1); if (!p) @@ -1592,15 +1758,21 @@ void D_DoomMain (void) printf("Playing demo %s.\n", file); } +#endif +#if !NO_DEMO_RECORDING || !PICO_NO_TIMING_DEMO I_AtExit(G_CheckDemoStatusAtExit, true); +#endif +#if !USE_WHD // Generate the WAD hash table. Speed things up a bit. W_GenerateHashTable(); +#endif // Load DEHACKED lumps from WAD files - but only if we give the right // command line parameter. +#if !NO_USE_DEH //! // @category mod // @@ -1622,13 +1794,19 @@ void D_DoomMain (void) printf(" loaded %i DEHACKED lumps from PWAD files.\n", loaded); } +#endif // Set the gamedescription string. This is only possible now that // we've finished loading Dehacked patches. D_SetGameDescription(); +#if !NO_USE_SAVE && !NO_FILE_ACCESS +#if DOOM_TINY && !PICO_ON_DEVICE + savegamedir = "./"; +#else savegamedir = M_GetSaveGameDir(D_SaveGameIWADName(gamemission)); - +#endif +#endif // Check for -file in shareware if (modifiedgame && (gamevariant != freedoom)) { @@ -1641,19 +1819,20 @@ void D_DoomMain (void) "dphoof","bfgga0","heada1","cybra1","spida1d1" }; int i; - + if ( gamemode == shareware) I_Error(DEH_String("\nYou cannot -file with the shareware " "version. Register!")); // Check for fake IWAD with right name, - // but w/o all the lumps of the registered version. + // but w/o all the lumps of the registered version. if (gamemode == registered) for (i = 0;i < 23; i++) if (W_CheckNumForName(name[i])<0) I_Error(DEH_String("\nThis is not the registered version.")); } +#if !USE_WHD if (W_CheckNumForName("SS_START") >= 0 || W_CheckNumForName("FF_END") >= 0) { @@ -1662,10 +1841,12 @@ void D_DoomMain (void) " floor textures. You may want to use the '-merge' command\n" " line option instead of '-file'.\n"); } +#endif I_PrintStartupBanner(gamedescription); PrintDehackedBanners(); +#if !DEMO1_ONLY // Freedoom's IWADs are Boom-compatible, which means they usually // don't work in Vanilla (though FreeDM is okay). Show a warning // message and give a link to the website. @@ -1677,19 +1858,26 @@ void D_DoomMain (void) " https://www.chocolate-doom.org/wiki/index.php/Freedoom\n"); I_PrintDivider(); } +#endif DEH_printf("I_Init: Setting up machine state.\n"); I_CheckIsScreensaver(); I_InitTimer(); +#if !NO_USE_JOYSTICK I_InitJoystick(); +#endif I_InitSound(true); I_InitMusic(); +#if !NO_USE_NET printf ("NET_Init: Init network subsystem.\n"); NET_Init (); +#endif +#if !NO_USE_NET // Initial netgame startup. Connect to server etc. D_ConnectNetGame(); +#endif // get skill / episode / map from parms startskill = sk_medium; @@ -1706,6 +1894,7 @@ void D_DoomMain (void) // 0 disables all monsters. // +#if !NO_USE_ARGS p = M_CheckParmWithArgs("-skill", 1); if (p) @@ -1713,6 +1902,7 @@ void D_DoomMain (void) startskill = myargv[p+1][0]-'1'; autostart = true; } +#endif //! // @category game @@ -1722,6 +1912,7 @@ void D_DoomMain (void) // Start playing on episode n (1-4) // +#if !NO_USE_ARGS p = M_CheckParmWithArgs("-episode", 1); if (p) @@ -1730,10 +1921,11 @@ void D_DoomMain (void) startmap = 1; autostart = true; } - +#endif + timelimit = 0; - //! + //! // @arg // @category net // @vanilla @@ -1741,12 +1933,14 @@ void D_DoomMain (void) // For multiplayer games: exit each level after n minutes. // +#if !NO_USE_ARGS p = M_CheckParmWithArgs("-timer", 1); if (p) { timelimit = atoi(myargv[p+1]); } +#endif //! // @category net @@ -1771,6 +1965,7 @@ void D_DoomMain (void) // (Doom 2) // +#if !NO_USE_ARGS p = M_CheckParmWithArgs("-warp", 1); if (p) @@ -1792,7 +1987,9 @@ void D_DoomMain (void) } autostart = true; } +#endif +#if !NO_USE_ARGS // Undocumented: // Invoked by setup to test the controls. @@ -1805,6 +2002,7 @@ void D_DoomMain (void) autostart = true; testcontrols = true; } +#endif // Check for load game parameter // We do this here and save the slot number, so that the network code @@ -1818,16 +2016,20 @@ void D_DoomMain (void) // Load the game in slot s. // +#if !NO_USE_LOAD && !NO_USE_ARGS p = M_CheckParmWithArgs("-loadgame", 1); - + if (p) { startloadgame = atoi(myargv[p+1]); } else +#endif { +#if !DOOM_TINY // Not loading a game startloadgame = -1; +#endif } DEH_printf("M_Init: Init miscellaneous info.\n"); @@ -1842,7 +2044,9 @@ void D_DoomMain (void) DEH_printf("S_Init: Setting up sound.\n"); S_Init (sfxVolume * 8, musicVolume * 8); +#if !USE_PICO_NET DEH_printf("D_CheckNetGame: Checking network game status.\n"); +#endif D_CheckNetGame (); PrintGameVersion(); @@ -1874,6 +2078,8 @@ void D_DoomMain (void) // Record a demo named x.lmp. // +#if !NO_USE_ARGS +#if !NO_DEMO_RECORDING p = M_CheckParmWithArgs("-record", 1); if (p) @@ -1881,7 +2087,7 @@ void D_DoomMain (void) G_RecordDemo (myargv[p+1]); autostart = true; } - +#endif p = M_CheckParmWithArgs("-playdemo", 1); if (p) { @@ -1889,20 +2095,23 @@ void D_DoomMain (void) G_DeferedPlayDemo (demolumpname); D_DoomLoop (); // never returns } - + p = M_CheckParmWithArgs("-timedemo", 1); if (p) { G_TimeDemo (demolumpname); D_DoomLoop (); // never returns } - +#endif + +#if !DOOM_TINY if (startloadgame >= 0) { M_StringCopy(file, P_SaveGameFile(startloadgame), sizeof(file)); G_LoadGame(file); } - +#endif + if (gameaction != ga_loadgame ) { if (autostart || netgame) diff --git a/src/doom/d_main.h b/src/doom/d_main.h index 0fe9547b..e7e2a412 100644 --- a/src/doom/d_main.h +++ b/src/doom/d_main.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -44,7 +45,8 @@ void D_StartTitle (void); // extern gameaction_t gameaction; - +extern const char* pagename; +extern boolean advancedemo; #endif diff --git a/src/doom/d_net.c b/src/doom/d_net.c index de931fb0..c5b20400 100644 --- a/src/doom/d_net.c +++ b/src/doom/d_net.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -68,7 +69,6 @@ static void PlayerQuitGame(player_t *player) static void RunTic(ticcmd_t *cmds, boolean *ingame) { - extern boolean advancedemo; unsigned int i; // Check for player quits. @@ -143,7 +143,11 @@ static void SaveGameSettings(net_gamesettings_t *settings) settings->episode = startepisode; settings->map = startmap; settings->skill = startskill; +#if !DOOM_TINY settings->loadgame = startloadgame; +#else + settings->loadgame = -1; +#endif settings->gameversion = gameversion; settings->nomonsters = nomonsters; settings->fast_monsters = fastparm; @@ -166,6 +170,7 @@ static void InitConnectData(net_connect_data_t *connect_data) // Run as the left screen in three screen mode. // +#if !DOOM_TINY if (M_CheckParm("-left") > 0) { viewangleoffset = ANG90; @@ -183,16 +188,17 @@ static void InitConnectData(net_connect_data_t *connect_data) viewangleoffset = ANG270; connect_data->drone = true; } - +#endif // // Connect data // // Game type fields: - connect_data->gamemode = gamemode; + connect_data->_gamemode = gamemode; connect_data->gamemission = gamemission; +#if !DOOM_TINY // Are we recording a demo? Possibly set lowres turn mode connect_data->lowres_turn = (M_ParmExists("-record") @@ -200,9 +206,14 @@ static void InitConnectData(net_connect_data_t *connect_data) || M_ParmExists("-shorttics"); // Read checksums of our WAD directory and dehacked information +#endif +#if !NO_USE_CHECKSUM W_Checksum(connect_data->wad_sha1sum); DEH_Checksum(connect_data->deh_sha1sum); +#else + // leave as garbage for now +#endif // Are we playing with the Freedoom IWAD? @@ -214,6 +225,7 @@ void D_ConnectNetGame(void) net_connect_data_t connect_data; InitConnectData(&connect_data); +#if !NO_USE_NET netgame = D_InitNetGame(&connect_data); //! @@ -224,10 +236,13 @@ void D_ConnectNetGame(void) // demos. // +#if !NO_USE_ARGS if (M_CheckParm("-solo-net") > 0) { netgame = true; } +#endif +#endif } // @@ -249,6 +264,7 @@ void D_CheckNetGame (void) D_StartNetGame(&settings, NULL); LoadGameSettings(&settings); +#if !DOOM_TINY DEH_printf("startskill %i deathmatch: %i startmap: %i startepisode: %i\n", startskill, deathmatch, startmap, startepisode); @@ -274,5 +290,6 @@ void D_CheckNetGame (void) printf(".\n"); } } +#endif } diff --git a/src/doom/d_player.h b/src/doom/d_player.h index d78482d0..5e1a7709 100644 --- a/src/doom/d_player.h +++ b/src/doom/d_player.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -111,13 +112,13 @@ typedef struct player_s // Is wp_nochange if not changing. weapontype_t pendingweapon; - int weaponowned[NUMWEAPONS]; - int ammo[NUMAMMO]; - int maxammo[NUMAMMO]; + isb_int8_t weaponowned[NUMWEAPONS]; + int ammo[NUMAMMO]; // int because pointed to by st_ stuff + int maxammo[NUMAMMO]; // int because pointed to by st_ stuff // True if button down last tic. - int attackdown; - int usedown; + isb_int8_t attackdown; + isb_int8_t usedown; // Bit flags, for cheats and debug. // See cheat_t, above. @@ -142,21 +143,21 @@ typedef struct player_s mobj_t* attacker; // So gun flashes light up areas. - int extralight; + isb_int8_t extralight; // Current PLAYPAL, ??? // can be set to REDCOLORMAP for pain, etc. - int fixedcolormap; + isb_int8_t fixedcolormap; // Player skin colorshift, // 0-3 for which color to draw player. - int colormap; - - // Overlay view sprites (gun, etc). - pspdef_t psprites[NUMPSPRITES]; + isb_int8_t colormap; // True if secret level has been done. - boolean didsecret; + boolean didsecret; + + // Overlay view sprites (gun, etc). + pspdef_t psprites[NUMPSPRITES]; } player_t; diff --git a/src/doom/d_think.h b/src/doom/d_think.h index 0966ad96..8d8a9c8f 100644 --- a/src/doom/d_think.h +++ b/src/doom/d_think.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -51,17 +52,64 @@ typedef union // Historically, "think_t" is yet another // function pointer to a routine to handle // an actor. -typedef actionf_t think_t; +typedef enum { + ThinkF_NULL = 0, + ThinkF_T_MoveCeiling, + ThinkF_T_VerticalDoor, + ThinkF_T_PlatRaise, + ThinkF_T_FireFlicker, + ThinkF_T_LightFlash, + ThinkF_T_StrobeFlash, + ThinkF_T_MoveFloor, + ThinkF_T_Glow, + ThinkF_P_MobjThinker, + ThinkF_INVALID, // only set during save game load, should be overwritten + ThinkF_REMOVED, + NUM_THINKF +} think_t_orig; +#include +static_assert(NUM_THINKF < 256, ""); +typedef uint8_t think_t; - -// Doubly linked list of actors. +// linked list of actors. typedef struct thinker_s { - struct thinker_s* prev; - struct thinker_s* next; + // todo graham this can be an array index into an active thinker array + shortptr_t /*struct thinker_s*/ sp_next; think_t function; - -} thinker_t; +#if DOOM_SMALL + uint8_t pool_info; // 0xff if not in memory pool +#endif +} __attribute__((aligned(4))) thinker_t; // must be aligned for shortptr + +#if DOOM_SMALL +#if PICO_ON_DEVICE +static_assert(sizeof(thinker_t) == 4, ""); // note z_zone requires this too to zero out +#endif +#endif +#define thinker_next(t) ((thinker_t *)shortptr_to_ptr((t)->sp_next)) +static inline shortptr_t thinker_to_shortptr(thinker_t *thinker) { + return ptr_to_shortptr(thinker); +} + +#if !USE_THINKER_POOL +#include "z_zone.h" +static inline void *Z_ThinkMalloc(int size, int tag, void *user) { + thinker_t *t = Z_Malloc(size, tag, user); + memset(t, 0, size); + return t; +} +#define Z_ThinkFree Z_Free +#else +#include "z_zone.h" +thinker_t *Z_ThinkMallocImpl(int size); +void Z_ThinkFree(thinker_t *thinker); +static inline void *Z_ThinkMalloc(int size, int tag, void *user) { + assert(!user); + assert(tag == PU_LEVEL); + return Z_ThinkMallocImpl(size); +} +#endif diff --git a/src/doom/deh_ammo.c b/src/doom/deh_ammo.c index dd8bee54..125ca75b 100644 --- a/src/doom/deh_ammo.c +++ b/src/doom/deh_ammo.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -26,6 +27,7 @@ #include "deh_main.h" #include "p_local.h" +#if !NO_USE_DEH static void *DEH_AmmoStart(deh_context_t *context, char *line) { int ammo_number = 0; @@ -101,3 +103,4 @@ deh_section_t deh_section_ammo = DEH_AmmoSHA1Hash, }; +#endif \ No newline at end of file diff --git a/src/doom/deh_bexstr.c b/src/doom/deh_bexstr.c index f9235f20..c3ad848e 100644 --- a/src/doom/deh_bexstr.c +++ b/src/doom/deh_bexstr.c @@ -1,6 +1,7 @@ // // Copyright(C) 2005-2014 Simon Howard // Copyright(C) 2014 Fabian Greffrath +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -25,6 +26,7 @@ #include "dstrings.h" +#if !NO_USE_DEH typedef struct { const char *macro; const char *string; @@ -374,3 +376,4 @@ deh_section_t deh_section_bexstr = NULL, NULL, }; +#endif \ No newline at end of file diff --git a/src/doom/deh_cheat.c b/src/doom/deh_cheat.c index 8e269c04..58b6397d 100644 --- a/src/doom/deh_cheat.c +++ b/src/doom/deh_cheat.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -26,6 +27,7 @@ #include "am_map.h" #include "st_stuff.h" +#if !NO_USE_DEH typedef struct { const char *name; @@ -144,3 +146,4 @@ deh_section_t deh_section_cheat = NULL, }; +#endif \ No newline at end of file diff --git a/src/doom/deh_doom.c b/src/doom/deh_doom.c index b13cc03f..fe81a70d 100644 --- a/src/doom/deh_doom.c +++ b/src/doom/deh_doom.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -19,6 +20,8 @@ #include "deh_defs.h" #include "deh_main.h" +#if !NO_USE_DEH + const char *deh_signatures[] = { "Patch File for DeHackEd v2.3", @@ -66,3 +69,4 @@ deh_section_t *deh_section_types[] = NULL }; +#endif \ No newline at end of file diff --git a/src/doom/deh_frame.c b/src/doom/deh_frame.c index e1cd59da..92265a0e 100644 --- a/src/doom/deh_frame.c +++ b/src/doom/deh_frame.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -27,6 +28,7 @@ #include "deh_main.h" #include "deh_mapping.h" +#if !NO_USE_DEH DEH_BEGIN_MAPPING(state_mapping, state_t) DEH_MAPPING("Sprite number", sprite) DEH_MAPPING("Sprite subnumber", frame) @@ -159,3 +161,4 @@ deh_section_t deh_section_frame = DEH_FrameSHA1Sum, }; +#endif \ No newline at end of file diff --git a/src/doom/deh_misc.c b/src/doom/deh_misc.c index 9b76a429..d60cd4d4 100644 --- a/src/doom/deh_misc.c +++ b/src/doom/deh_misc.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -24,6 +25,7 @@ #include "deh_main.h" #include "deh_misc.h" +#if !NO_USE_DEH // Dehacked: "Initial Health" // This is the initial health a player has when starting anew. // See G_PlayerReborn in g_game.c @@ -226,3 +228,4 @@ deh_section_t deh_section_misc = DEH_MiscSHA1Sum, }; +#endif \ No newline at end of file diff --git a/src/doom/deh_misc.h b/src/doom/deh_misc.h index f29428f5..a6857221 100644 --- a/src/doom/deh_misc.h +++ b/src/doom/deh_misc.h @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -35,6 +36,8 @@ #define DEH_DEFAULT_BFG_CELLS_PER_SHOT 40 #define DEH_DEFAULT_SPECIES_INFIGHTING 0 +#if !NO_USE_DEH + extern int deh_initial_health; extern int deh_initial_bullets; extern int deh_max_health; @@ -52,7 +55,7 @@ extern int deh_idkfa_armor_class; extern int deh_bfg_cells_per_shot; extern int deh_species_infighting; -#if 0 +#else // To compile without dehacked, it's possible to use these: diff --git a/src/doom/deh_ptr.c b/src/doom/deh_ptr.c index 5ca3116f..e35df50e 100644 --- a/src/doom/deh_ptr.c +++ b/src/doom/deh_ptr.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -26,6 +27,7 @@ #include "deh_io.h" #include "deh_main.h" +#if !NO_USE_DEH static actionf_t codeptrs[NUMSTATES]; static int CodePointerIndex(actionf_t *ptr) @@ -140,3 +142,4 @@ deh_section_t deh_section_pointer = DEH_PointerSHA1Sum, }; +#endif \ No newline at end of file diff --git a/src/doom/deh_sound.c b/src/doom/deh_sound.c index 8d178c78..62f9105e 100644 --- a/src/doom/deh_sound.c +++ b/src/doom/deh_sound.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -24,6 +25,7 @@ #include "deh_mapping.h" #include "sounds.h" +#if !NO_USE_DEH DEH_BEGIN_MAPPING(sound_mapping, sfxinfo_t) DEH_UNSUPPORTED_MAPPING("Offset") DEH_UNSUPPORTED_MAPPING("Zero/One") @@ -100,3 +102,4 @@ deh_section_t deh_section_sound = NULL, }; +#endif \ No newline at end of file diff --git a/src/doom/deh_thing.c b/src/doom/deh_thing.c index a6baff1d..27848708 100644 --- a/src/doom/deh_thing.c +++ b/src/doom/deh_thing.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -26,6 +27,7 @@ #include "info.h" +#if !NO_USE_DEH DEH_BEGIN_MAPPING(thing_mapping, mobjinfo_t) DEH_MAPPING("ID #", doomednum) DEH_MAPPING("Initial frame", spawnstate) @@ -129,3 +131,4 @@ deh_section_t deh_section_thing = DEH_ThingSHA1Sum, }; +#endif \ No newline at end of file diff --git a/src/doom/deh_weapon.c b/src/doom/deh_weapon.c index 2c180475..88d30eda 100644 --- a/src/doom/deh_weapon.c +++ b/src/doom/deh_weapon.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -27,6 +28,7 @@ #include "deh_main.h" #include "deh_mapping.h" +#if !NO_USE_DEH DEH_BEGIN_MAPPING(weapon_mapping, weaponinfo_t) DEH_MAPPING("Ammo type", ammo) DEH_MAPPING("Deselect frame", upstate) @@ -99,3 +101,4 @@ deh_section_t deh_section_weapon = DEH_WeaponSHA1Sum, }; +#endif \ No newline at end of file diff --git a/src/doom/doomdata.h b/src/doom/doomdata.h index 6e42be6f..2561ae81 100644 --- a/src/doom/doomdata.h +++ b/src/doom/doomdata.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -131,9 +132,14 @@ typedef PACKED_STRUCT ( // Set if already seen, thus drawn in automap. #define ML_MAPPED 256 - - - +#if USE_WHD && WHD_SUPER_TINY +#define ML_NO_PREDICT_SIDE 256 // can share with ML_MAPPED +#define ML_NO_PREDICT_V1 512 +#define ML_NO_PREDICT_V2 1024 +#define ML_HAS_SPECIAL 2048 +#define ML_HAS_TAG 4096 +#define ML_SIDE_MASK 0xe000u +#endif // Sector definition, from editing. typedef PACKED_STRUCT ( { @@ -206,8 +212,14 @@ typedef PACKED_STRUCT ( short options; }) mapthing_t; - - +#if !WHD_SUPER_TINY +#define TAG_666 666 +#define TAG_667 667 +#else +// we only have one byte tags +#define TAG_666 254 +#define TAG_667 255 +#endif #endif // __DOOMDATA__ diff --git a/src/doom/doomdef.h b/src/doom/doomdef.h index 62d729dd..c0262a67 100644 --- a/src/doom/doomdef.h +++ b/src/doom/doomdef.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -39,7 +40,9 @@ // If rangecheck is undefined, // most parameter validation debugging code will not be compiled +#ifndef NDEBUG #define RANGECHECK +#endif // The maximum number of players, multiplayer/networking. #define MAXPLAYERS 4 @@ -66,7 +69,15 @@ typedef enum ga_completed, ga_victory, ga_worlddone, - ga_screenshot +#if !NO_SCREENSHOT + ga_screenshot, +#endif +#if USE_PICO_NET + ga_newgamenet, +#endif +#if DOOM_TINY + ga_deferredquit, +#endif } gameaction_t; // diff --git a/src/doom/doomstat.c b/src/doom/doomstat.c index e330197a..f8ec31e6 100644 --- a/src/doom/doomstat.c +++ b/src/doom/doomstat.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -22,14 +23,18 @@ // Game Mode - identify IWAD as shareware, retail etc. +#if !DEMO1_ONLY GameMode_t gamemode = indetermined; +#endif GameMission_t gamemission = doom; GameVersion_t gameversion = exe_final2; GameVariant_t gamevariant = vanilla; const char *gamedescription; +#if !DOOM_TINY // Set if homebrew PWAD stuff has been added. boolean modifiedgame; +#endif diff --git a/src/doom/doomstat.h b/src/doom/doomstat.h index 10a418e0..788fdbb4 100644 --- a/src/doom/doomstat.h +++ b/src/doom/doomstat.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -53,7 +54,11 @@ extern boolean devparm; // DEBUG: launched with -devparm // ----------------------------------------------------- // Game Mode - identify IWAD as shareware, retail etc. // +#if !DEMO1_ONLY extern GameMode_t gamemode; +#else +#define gamemode shareware +#endif extern GameMission_t gamemission; extern GameVersion_t gameversion; extern GameVariant_t gamevariant; @@ -65,11 +70,15 @@ extern const char *gamedescription; // as the same most of the time. #define logical_gamemission \ - (gamemission == pack_chex ? doom : \ - gamemission == pack_hacx ? doom2 : gamemission) + (gamemission_is_chex(gamemission) ? doom : \ + gamemission_is_hacx(gamemission) ? doom2 : gamemission) // Set if homebrew PWAD stuff has been added. +#if !DOOM_TINY extern boolean modifiedgame; +#else +#define modifiedgame 0 +#endif // ------------------------------------------- @@ -78,20 +87,20 @@ extern boolean modifiedgame; // Defaults for menu, methinks. extern skill_t startskill; -extern int startepisode; -extern int startmap; +extern isb_int8_t startepisode; +extern isb_int8_t startmap; // Savegame slot to load on startup. This is the value provided to // the -loadgame option. If this has not been provided, this is -1. -extern int startloadgame; +extern isb_int8_t startloadgame; extern boolean autostart; // Selected by user. extern skill_t gameskill; -extern int gameepisode; -extern int gamemap; +extern isb_int8_t gameepisode; +extern isb_int8_t gamemap; // If non-zero, exit the level after this number of minutes extern int timelimit; @@ -100,10 +109,14 @@ extern int timelimit; extern boolean respawnmonsters; // Netgame? Only true if >1 player. +#if !NO_USE_NET || USE_PICO_NET extern boolean netgame; +#else +#define netgame false +#endif // 0=Cooperative; 1=Deathmatch; 2=Altdeath -extern int deathmatch; +extern isb_int8_t deathmatch; // ------------------------- // Internal parameters for sound rendering. @@ -145,11 +158,20 @@ extern boolean paused; // Game Pause? extern boolean viewactive; +#if !FORCE_NODRAW extern boolean nodrawers; +#else +#define nodrawers true +#endif +#if !DOOM_TINY extern boolean testcontrols; extern int testcontrols_mousespeed; +#else +#define testcontrols false +#define testcontrols_mousespeed false +#endif @@ -159,8 +181,8 @@ extern int testcontrols_mousespeed; extern int viewangleoffset; // Player taking events, and displaying. -extern int consoleplayer; -extern int displayplayer; +extern isb_int8_t consoleplayer; +extern isb_int8_t displayplayer; // ------------------------------------- @@ -185,7 +207,11 @@ extern boolean usergame; //? extern boolean demoplayback; +#if !NO_DEMO_RECORDING extern boolean demorecording; +#else +#define demorecording 0 +#endif // Round angleturn in ticcmds to the nearest 256. This is used when // recording Vanilla demos in netgames. @@ -243,8 +269,10 @@ extern wbstartstruct_t wminfo; // Internal parameters, used for engine. // +#if !NO_FILE_ACCESS // File handling stuff. extern char *savegamedir; +#endif // if true, load all graphics at level load extern boolean precache; @@ -254,16 +282,16 @@ extern boolean precache; // to force a wipe on the next draw extern gamestate_t wipegamestate; -extern int mouseSensitivity; +extern isb_int8_t mouseSensitivity; -extern int bodyqueslot; +extern isb_uint8_t bodyqueslot; // Needed to store the number of the dummy sky flat. // Used for rendering, // as well as tracking projectiles etc. -extern int skyflatnum; +extern flatnum_t skyflatnum; diff --git a/src/doom/dstrings.c b/src/doom/dstrings.c index 86433ab2..5839c9c6 100644 --- a/src/doom/dstrings.c +++ b/src/doom/dstrings.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -20,7 +21,7 @@ #include "dstrings.h" -const char *doom1_endmsg[] = +const constcharstar doom1_endmsg[NUM_QUITMESSAGES] = { "are you sure you want to\nquit this great game?", "please don't leave, there's more\ndemons to toast!", @@ -32,7 +33,7 @@ const char *doom1_endmsg[] = "go ahead and leave. see if i care.", }; -const char *doom2_endmsg[] = +const constcharstar doom2_endmsg[NUM_QUITMESSAGES] = { // QuitDOOM II messages "are you sure you want to\nquit this great game?", diff --git a/src/doom/dstrings.h b/src/doom/dstrings.h index 81115792..3c9c173a 100644 --- a/src/doom/dstrings.h +++ b/src/doom/dstrings.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -34,8 +35,9 @@ // 8 per each game type #define NUM_QUITMESSAGES 8 -extern const char *doom1_endmsg[]; -extern const char *doom2_endmsg[]; +#include "doomtype.h" +extern const constcharstar doom1_endmsg[NUM_QUITMESSAGES]; +extern const constcharstar doom2_endmsg[NUM_QUITMESSAGES]; #endif diff --git a/src/doom/f_finale.c b/src/doom/f_finale.c index 862b9e18..f437a4a8 100644 --- a/src/doom/f_finale.c +++ b/src/doom/f_finale.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -35,19 +36,13 @@ #include "sounds.h" #include "doomstat.h" +#include "r_data.h" #include "r_state.h" -typedef enum -{ - F_STAGE_TEXT, - F_STAGE_ARTSCREEN, - F_STAGE_CAST, -} finalestage_t; - // ? //#include "doomstat.h" //#include "r_local.h" -//#include "f_finale.h" +#include "f_finale.h" // Stage of animation: finalestage_t finalestage; @@ -67,7 +62,16 @@ typedef struct static textscreen_t textscreens[] = { +#if !HACK_FINALE_E1M1 { doom, 1, 8, "FLOOR4_8", E1TEXT}, +#else +#if HACK_FINALE_SHAREWARE + { doom, 3, 1, "MFLR8_4", E3TEXT}, // for shareware +#else + { doom, 1, 1, "FLOOR4_8", E1TEXT}, // for ultimate +#endif +#endif +#if !DEMO1_ONLY { doom, 2, 8, "SFLR6_1", E2TEXT}, { doom, 3, 8, "MFLR8_4", E3TEXT}, { doom, 4, 8, "MFLR8_3", E4TEXT}, @@ -92,6 +96,7 @@ static textscreen_t textscreens[] = { pack_plut, 1, 30, "RROCK17", P4TEXT}, { pack_plut, 1, 15, "RROCK13", P5TEXT}, { pack_plut, 1, 31, "RROCK19", P6TEXT}, +#endif }; const char *finaletext; @@ -100,8 +105,10 @@ const char *finaleflat; void F_StartCast (void); void F_CastTicker (void); boolean F_CastResponder (event_t *ev); -void F_CastDrawer (void); +#if DOOM_TINY +#include "doom/m_menu.h" +#endif // // F_StartFinale // @@ -114,6 +121,9 @@ void F_StartFinale (void) viewactive = false; automapactive = false; +#if DOOM_TINY + M_ClearMenus(); // don't want menus in the finale... this way we can prevent help appearing which we don't draw correctly +#endif if (logical_gamemission == doom) { S_ChangeMusic(mus_victor, true); @@ -131,7 +141,7 @@ void F_StartFinale (void) // Hack for Chex Quest - if (gameversion == exe_chex && screen->mission == doom) + if (gameversion_is_chex(gameversion) && screen->mission == doom) { screen->level = 5; } @@ -141,6 +151,9 @@ void F_StartFinale (void) && gamemap == screen->level) { finaletext = screen->text; +#if 0 && HACK_FINALE_E1M1 + finaletext = "MUCH SHORTER!"; +#endif finaleflat = screen->background; } } @@ -159,9 +172,10 @@ void F_StartFinale (void) boolean F_Responder (event_t *event) { +#if !NO_USE_FINALE_CAST if (finalestage == F_STAGE_CAST) return F_CastResponder (event); - +#endif return false; } @@ -171,47 +185,50 @@ boolean F_Responder (event_t *event) // void F_Ticker (void) { - size_t i; - + size_t i; + // check for skipping - if ( (gamemode == commercial) - && ( finalecount > 50) ) - { - // go on to the next level - for (i=0 ; i 50)) { + // go on to the next level + for (i = 0; i < MAXPLAYERS; i++) + if (players[i].cmd.buttons) + break; + + if (i < MAXPLAYERS) { +#if !NO_USE_FINALE_CAST + if (gamemap == 30) + F_StartCast(); + else +#endif + gameaction = ga_worlddone; + } } - + // advance animation finalecount++; - - if (finalestage == F_STAGE_CAST) - { - F_CastTicker (); - return; + +#if !NO_USE_FINALE_CAST + if (finalestage == F_STAGE_CAST) { + F_CastTicker(); + return; } - - if ( gamemode == commercial) - return; - +#endif + + if (gamemode == commercial) + return; + if (finalestage == F_STAGE_TEXT - && finalecount>strlen (finaletext)*TEXTSPEED + TEXTWAIT) - { - finalecount = 0; - finalestage = F_STAGE_ARTSCREEN; - wipegamestate = -1; // force a wipe - if (gameepisode == 3) - S_StartMusic (mus_bunny); + && finalecount > strlen(finaletext) * TEXTSPEED + TEXTWAIT) { + finalecount = 0; + finalestage = F_STAGE_ARTSCREEN; + wipegamestate = -1; // force a wipe + if (gameepisode == 3) + S_StartMusic(mus_bunny); } +#if PICO_ON_DEVICE + I_UpdateSound(); +#endif } @@ -221,12 +238,12 @@ void F_Ticker (void) // #include "hu_stuff.h" -extern patch_t *hu_font[HU_FONTSIZE]; +extern vpatch_sequence_t hu_font; void F_TextWrite (void) { - byte* src; + should_be_const byte* src; pixel_t* dest; int x,y,w; @@ -235,7 +252,8 @@ void F_TextWrite (void) int c; int cx; int cy; - + +#if !DOOM_TINY // erase the entire screen to a tiled background src = W_CacheLumpName ( finaleflat , PU_CACHE); dest = I_VideoBuffer; @@ -255,6 +273,7 @@ void F_TextWrite (void) } V_MarkRect (0, 0, SCREENWIDTH, SCREENHEIGHT); +#endif // draw some of the text onto the screen cx = 10; @@ -275,7 +294,7 @@ void F_TextWrite (void) cy += 11; continue; } - + c = toupper(c) - HU_FONTSTART; if (c < 0 || c> HU_FONTSIZE) { @@ -283,15 +302,22 @@ void F_TextWrite (void) continue; } - w = SHORT (hu_font[c]->width); + w = vpatch_width(resolve_vpatch_handle(vpatch_n(hu_font,c))); if (cx+w > SCREENWIDTH) break; - V_DrawPatch(cx, cy, hu_font[c]); +#if DOOM_TINY + // since we don't clear the screen, only draw the last few character (otherwise we have too many patches) + // note we should only need to draw one, but we seem to skip some and I can't be bothered to figure out why + if (count < 3) V_DrawPatch(cx, cy, vpatch_n(hu_font,c)); +#else + V_DrawPatch(cx, cy, vpatch_n(hu_font,c)); +#endif cx+=w; } } +#if !NO_USE_FINALE_CAST // // Final DOOM 2 animation // Casting by id Software. @@ -327,7 +353,7 @@ castinfo_t castorder[] = { int castnum; int casttics; -state_t* caststate; +should_be_const state_t* caststate; boolean castdeath; int castframes; int castonmelee; @@ -339,10 +365,11 @@ boolean castattacking; // void F_StartCast (void) { + if (finalestage == F_STAGE_CAST) return; // seems to be a bug in general, but seems easier for us to start the cast wipe multiple times wipegamestate = -1; // force a screen wipe castnum = 0; caststate = &states[mobjinfo[castorder[castnum].type].seestate]; - casttics = caststate->tics; + casttics = state_tics(caststate); castdeath = false; finalestage = F_STAGE_CAST; castframes = 0; @@ -363,7 +390,7 @@ void F_CastTicker (void) if (--casttics > 0) return; // not time to change state yet - if (caststate->tics == -1 || caststate->nextstate == S_NULL) + if (state_tics(caststate) == -1 || caststate->nextstate == S_NULL) { // switch from deathstate to next monster castnum++; @@ -452,7 +479,7 @@ void F_CastTicker (void) } } - casttics = caststate->tics; + casttics = state_tics(caststate); if (casttics == -1) casttics = 15; } @@ -473,7 +500,7 @@ boolean F_CastResponder (event_t* ev) // go into death frame castdeath = true; caststate = &states[mobjinfo[castorder[castnum].type].deathstate]; - casttics = caststate->tics; + casttics = state_tics(caststate); castframes = 0; castattacking = false; if (mobjinfo[castorder[castnum].type].deathsound) @@ -507,7 +534,7 @@ void F_CastPrint (const char *text) continue; } - w = SHORT (hu_font[c]->width); + w = vpatch_width(resolve_vpatch_handle(vpatch_n(hu_font,c))); width += w; } @@ -526,8 +553,8 @@ void F_CastPrint (const char *text) continue; } - w = SHORT (hu_font[c]->width); - V_DrawPatch(cx, 180, hu_font[c]); + w = vpatch_width(resolve_vpatch_handle(vpatch_n(hu_font,c))); + V_DrawPatch(cx, 180, vpatch_n(hu_font,c)); cx+=w; } @@ -538,32 +565,44 @@ void F_CastPrint (const char *text) // F_CastDrawer // +int F_CastSprite(void) { + spriteframeref_t sprframe = sprite_frame(caststate->sprite, caststate->frame & FF_FRAMEMASK); + int lump; + boolean flip; + if (spriteframe_rotates(sprframe)) { + lump = spriteframe_rotated_pic(sprframe, 0); + flip = spriteframe_rotated_flipped(sprframe, 0); + } else { + // use single rotation for all views + lump = spriteframe_unrotated_pic(sprframe); + flip = spriteframe_unrotated_flipped(sprframe); + } + return flip ? -1 - lump : lump; +} + void F_CastDrawer (void) { - spritedef_t* sprdef; - spriteframe_t* sprframe; - int lump; - boolean flip; - patch_t* patch; - +#if !DOOM_TINY + int lump = F_CastSprite(); + boolean flip = lump < 0; + if (flip) lump = -1 - lump; // erase the entire screen to a background V_DrawPatch (0, 0, W_CacheLumpName (DEH_String("BOSSBACK"), PU_CACHE)); F_CastPrint (DEH_String(castorder[castnum].name)); // draw the current frame in the middle of the screen - sprdef = &sprites[caststate->sprite]; - sprframe = &sprdef->spriteframes[ caststate->frame & FF_FRAMEMASK]; - lump = sprframe->lump[0]; - flip = (boolean)sprframe->flip[0]; - - patch = W_CacheLumpNum (lump+firstspritelump, PU_CACHE); - if (flip) - V_DrawPatchFlipped(SCREENWIDTH/2, 170, patch); - else - V_DrawPatch(SCREENWIDTH/2, 170, patch); -} + should_be_const patch_t *patch = W_CacheLumpNum (lump + firstspritelump, PU_CACHE); + if (flip) + V_DrawPatchFlipped(SCREENWIDTH/2, 170, patch); + else + V_DrawPatch(SCREENWIDTH/2, 170, patch); +#else + F_CastPrint (DEH_String(castorder[castnum].name)); +#endif +} +#endif // // F_DrawPatchCol @@ -571,7 +610,7 @@ void F_CastDrawer (void) void F_DrawPatchCol ( int x, - patch_t* patch, + should_be_const patch_t* patch, int col ) { column_t* column; @@ -580,7 +619,7 @@ F_DrawPatchCol pixel_t* desttop; int count; - column = (column_t *)((byte *)patch + LONG(patch->columnofs[col])); + column = (column_t *)((byte *)patch + patch_columnofs(patch, col)); desttop = I_VideoBuffer + x; // step through the posts in a column @@ -599,31 +638,73 @@ F_DrawPatchCol } } +#if !NO_USE_FINALE_BUNNY +int F_BunnyScrollPos(void) { + int scrolled = (SCREENWIDTH - ((signed int) finalecount-230)/2); + if (scrolled > SCREENWIDTH) + scrolled = SCREENWIDTH; + if (scrolled < 0) + scrolled = 0; + return scrolled; +} +void F_BunnyDrawPatches(void) { + char name[10]; + int stage; + static int laststage; + if (finalecount < 1130) + return; + if (finalecount < 1180) + { +#if !DOOM_TINY + V_DrawPatch((SCREENWIDTH - 13 * 8) / 2, + (SCREENHEIGHT - 8 * 8) / 2, + W_CacheLumpName ("END0",PU_CACHE)); +#else + V_DrawPatch((SCREENWIDTH - 13 * 8) / 2, + (SCREENHEIGHT - 8 * 8) / 2, + VPATCH_NAME(END0)); +#endif + laststage = 0; + return; + } + + stage = (finalecount-1180) / 5; + if (stage > 6) + stage = 6; + if (stage > laststage) + { + S_StartSound (NULL, sfx_pistol); + laststage = stage; + } + +#if !DOOM_TINY + DEH_snprintf(name, 10, "END%i", stage); + V_DrawPatch((SCREENWIDTH - 13 * 8) / 2, + (SCREENHEIGHT - 8 * 8) / 2, + W_CacheLumpName (name,PU_CACHE)); +#else + V_DrawPatch((SCREENWIDTH - 13 * 8) / 2, + (SCREENHEIGHT - 8 * 8) / 2, + VPATCH_END0 + stage); +#endif +} +#if !DOOM_TINY // // F_BunnyScroll // void F_BunnyScroll (void) { - signed int scrolled; + signed int scrolled = F_BunnyScrollPos(); int x; - patch_t* p1; - patch_t* p2; - char name[10]; - int stage; - static int laststage; - + should_be_const patch_t* p1; + should_be_const patch_t* p2; + p1 = W_CacheLumpName (DEH_String("PFUB2"), PU_LEVEL); p2 = W_CacheLumpName (DEH_String("PFUB1"), PU_LEVEL); V_MarkRect (0, 0, SCREENWIDTH, SCREENHEIGHT); - - scrolled = (SCREENWIDTH - ((signed int) finalecount-230)/2); - if (scrolled > SCREENWIDTH) - scrolled = SCREENWIDTH; - if (scrolled < 0) - scrolled = 0; - + for ( x=0 ; x= exe_ultimate) + { + lumpname = "CREDIT"; + } + else + { + lumpname = "HELP2"; + } + break; + case 2: + lumpname = "VICTORY2"; + break; + case 3: + return NULL; // bunny + case 4: + lumpname = "ENDPIC"; + break; + default: + assert(false); + lumpname = "CREDIT"; } - - stage = (finalecount-1180) / 5; - if (stage > 6) - stage = 6; - if (stage > laststage) - { - S_StartSound (NULL, sfx_pistol); - laststage = stage; - } - - DEH_snprintf(name, 10, "END%i", stage); - V_DrawPatch((SCREENWIDTH - 13 * 8) / 2, - (SCREENHEIGHT - 8 * 8) / 2, - W_CacheLumpName (name,PU_CACHE)); + + return DEH_String(lumpname); } static void F_ArtScreenDrawer(void) { - const char *lumpname; - - if (gameepisode == 3) +#if !DOOM_TINY + const char *lumpname = F_ArtScreenLumpName(); + + if (!lumpname) { +#if !NO_USE_FINALE_BUNNY F_BunnyScroll(); - } - else +#endif + } else { - switch (gameepisode) - { - case 1: - if (gameversion >= exe_ultimate) - { - lumpname = "CREDIT"; - } - else - { - lumpname = "HELP2"; - } - break; - case 2: - lumpname = "VICTORY2"; - break; - case 4: - lumpname = "ENDPIC"; - break; - default: - return; - } - - lumpname = DEH_String(lumpname); - V_DrawPatch (0, 0, W_CacheLumpName(lumpname, PU_CACHE)); } +#endif } // @@ -704,8 +772,10 @@ void F_Drawer (void) switch (finalestage) { case F_STAGE_CAST: +#if !DOOM_TINY && !NO_USE_FINALE_CAST F_CastDrawer(); break; +#endif case F_STAGE_TEXT: F_TextWrite(); break; diff --git a/src/doom/f_finale.h b/src/doom/f_finale.h index daa71c32..e13d9729 100644 --- a/src/doom/f_finale.h +++ b/src/doom/f_finale.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -39,7 +40,25 @@ void F_Drawer (void); void F_StartFinale (void); +const char *F_ArtScreenLumpName(void); +typedef enum +{ + F_STAGE_TEXT, + F_STAGE_ARTSCREEN, + F_STAGE_CAST, +} finalestage_t; +// Stage of animation: +extern finalestage_t finalestage; +extern const char *finaleflat; + +#if DOOM_TINY +int F_BunnyScrollPos(void); +void F_BunnyDrawPatches(void); +#endif +// return the sprite to to draw or -1-x to draw x flipped +int F_CastSprite(void); +void F_CastDrawer (void); #endif diff --git a/src/doom/f_wipe.c b/src/doom/f_wipe.c index 0fa7754d..9d6515f7 100644 --- a/src/doom/f_wipe.c +++ b/src/doom/f_wipe.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -234,8 +235,10 @@ wipe_StartScreen int width, int height ) { - wipe_scr_start = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*wipe_scr_start), PU_STATIC, NULL); +#if !NO_USE_WIPE + wipe_scr_start = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*wipe_scr_start), PU_STATIC, 0); I_ReadScreen(wipe_scr_start); +#endif return 0; } @@ -246,9 +249,11 @@ wipe_EndScreen int width, int height ) { - wipe_scr_end = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*wipe_scr_end), PU_STATIC, NULL); +#if !NO_USE_WIPE + wipe_scr_end = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*wipe_scr_end), PU_STATIC, 0); I_ReadScreen(wipe_scr_end); V_DrawBlock(x, y, width, height, wipe_scr_start); // restore start scr. +#endif return 0; } @@ -261,6 +266,7 @@ wipe_ScreenWipe int height, int ticks ) { +#if !NO_USE_WIPE int rc; static int (*wipes[])(int, int, int) = { @@ -290,5 +296,8 @@ wipe_ScreenWipe } return !go; +#else + return 1; +#endif } diff --git a/src/doom/f_wipe.h b/src/doom/f_wipe.h index f48a9ca6..2e2783af 100644 --- a/src/doom/f_wipe.h +++ b/src/doom/f_wipe.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -60,4 +61,16 @@ wipe_ScreenWipe int height, int ticks ); +#if DOOM_TINY +typedef enum { + WIPESTATE_NONE, + WIPESTATE_SKIP1, + WIPESTATE_REDRAW1, + WIPESTATE_SKIP2, + WIPESTATE_REDRAW2, + WIPESTATE_SKIP3, +} wipestate_t; +extern wipestate_t wipestate; +extern volatile uint8_t wipe_min; +#endif #endif diff --git a/src/doom/g_game.c b/src/doom/g_game.c index 7da1576f..f38b5e57 100644 --- a/src/doom/g_game.c +++ b/src/doom/g_game.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -15,7 +16,7 @@ // DESCRIPTION: none // - +#define xprintf(x, ...) ((void)0) #include #include @@ -51,7 +52,7 @@ #include "st_stuff.h" #include "am_map.h" #include "statdump.h" - +#include "m_menu.h" // Needs access to LFB. #include "v_video.h" @@ -73,6 +74,10 @@ #include "g_game.h" +#if USE_WHD +#include "tiny_huff.h" +#include "picodoom.h" +#endif #define SAVEGAMESIZE 0x2c000 @@ -83,7 +88,7 @@ void G_PlayerReborn (int player); void G_DoReborn (int playernum); void G_DoLoadLevel (void); -void G_DoNewGame (void); +void G_DoNewGame (boolean net); void G_DoPlayDemo (void); void G_DoCompleted (void); void G_DoVictory (void); @@ -98,8 +103,8 @@ gameaction_t gameaction; gamestate_t gamestate; skill_t gameskill; boolean respawnmonsters; -int gameepisode; -int gamemap; +isb_int8_t gameepisode; +isb_int8_t gamemap; // If non-zero, exit the level after this number of minutes. @@ -110,40 +115,61 @@ boolean sendpause; // send a pause event next tic boolean sendsave; // send a save event next tic boolean usergame; // ok to save / end game -boolean timingdemo; // if true, exit with report on completion -boolean nodrawers; // for comparative timing purposes +boolean timingdemo; // if true, exit with report on completion +#if !FORCE_NODRAW +boolean nodrawers; // for comparative timing purposes +#endif int starttime; // for comparative timing purposes boolean viewactive; -int deathmatch; // only if started as net death -boolean netgame; // only true if packets are broadcast +isb_int8_t deathmatch; // only if started as net death +#if !NO_USE_NET || USE_PICO_NET +boolean netgame; // only true if packets are broadcast +#endif boolean playeringame[MAXPLAYERS]; player_t players[MAXPLAYERS]; boolean turbodetected[MAXPLAYERS]; -int consoleplayer; // player taking events and displaying -int displayplayer; // view being displayed +isb_int8_t consoleplayer; // player taking events and displaying +isb_int8_t displayplayer; // view being displayed int levelstarttic; // gametic at level start -int totalkills, totalitems, totalsecret; // for intermission +int totalkills, totalitems, totalsecret; // for intermission +#if !NO_DEMO_RECORDING char *demoname; -boolean demorecording; +boolean demorecording; +#endif boolean longtics; // cph's doom 1.91 longtics hack boolean lowres_turn; // low resolution turning for longtics boolean demoplayback; -boolean netdemo; +boolean netdemo; byte* demobuffer; +#if !USE_WHD byte* demo_p; -byte* demoend; +#else +struct { + th_bit_input bi; + uint8_t changes_offset; + uint8_t fb_delta_offset; + uint8_t strafe_offset; + uint8_t turn_delta_offset; + uint8_t buttons_offset; + int8_t last_fb; + int8_t last_turn; + uint16_t *decoders; +} demo_decode; +#endif +byte* demoend; boolean singledemo; // quit after playing a demo from cmdline boolean precache = true; // if true, load all graphics at start +#if !DOOM_TINY boolean testcontrols = false; // Invoked by setup to test controls int testcontrols_mousespeed; - +#endif wbstartstruct_t wminfo; // parms for world map / intermission @@ -154,11 +180,18 @@ byte consistancy[MAXPLAYERS][BACKUPTICS]; #define TURBOTHRESHOLD 0x32 -fixed_t forwardmove[2] = {0x19, 0x32}; -fixed_t sidemove[2] = {0x18, 0x28}; -fixed_t angleturn[3] = {640, 1280, 320}; // + slow turn +#if !NO_USE_ARGS +// there is --turbo option that hackily changes these +fixed_t forwardmove[2] = {0x19, 0x32}; +fixed_t sidemove[2] = {0x18, 0x28}; +fixed_t angleturn[3] = {640, 1280, 320}; // + slow turn +#else +static const int16_t forwardmove[2] = {0x19, 0x32}; +static const int16_t sidemove[2] = {0x18, 0x28}; +static const int16_t angleturn[3] = {640, 1280, 320}; // + slow turn +#endif -static int *weapon_keys[] = { +static const key_type_t * const weapon_keys[] = { &key_weapon1, &key_weapon2, &key_weapon3, @@ -196,8 +229,22 @@ static const struct #define NUMKEYS 256 #define MAX_JOY_BUTTONS 20 -static boolean gamekeydown[NUMKEYS]; -static int turnheld; // for accelerative turning +#if !DOOM_SMALL +static boolean gamekeydown[NUMKEYS]; +#define is_gamekeydown(k) gamekeydown[k] +#define set_gamekeydown(k,d) gamekeydown[k] = (d) +#else +static uint8_t gamekeydown[(NUMKEYS + 7) >> 3]; +#define is_gamekeydown(k) (gamekeydown[(k)>>3] & (1u << ((k)&7u))) +static inline void set_gamekeydown(unsigned k, boolean down) { + if (down) + gamekeydown[(k)>>3] |= (1u << ((k)&7u)); + else + gamekeydown[(k)>>3] &= ~(1u << ((k)&7u)); +} +#endif + +static int turnheld; // for accelerative turning static boolean mousearray[MAX_MOUSE_BUTTONS + 1]; static boolean *mousebuttons = &mousearray[1]; // allow [-1] @@ -208,28 +255,30 @@ int mousey; static int dclicktime; static boolean dclickstate; -static int dclicks; +static isb_int8_t dclicks; static int dclicktime2; static boolean dclickstate2; -static int dclicks2; +static isb_int8_t dclicks2; +#if !NO_USE_JOYSTICK // joystick values are repeated static int joyxmove; static int joyymove; static int joystrafemove; static boolean joyarray[MAX_JOY_BUTTONS + 1]; static boolean *joybuttons = &joyarray[1]; // allow [-1] - -static int savegameslot; +#endif + +static isb_int8_t savegameslot; static char savedescription[32]; #define BODYQUESIZE 32 -mobj_t* bodyque[BODYQUESIZE]; -int bodyqueslot; +shortptr_t /*mobj_t* */ bodyque[BODYQUESIZE]; +isb_uint8_t bodyqueslot; -int vanilla_savegame_limit = 1; -int vanilla_demo_limit = 1; +isb_int8_t vanilla_savegame_limit = 1; +isb_int8_t vanilla_demo_limit = 1; int G_CmdChecksum (ticcmd_t* cmd) { @@ -332,29 +381,42 @@ void G_BuildTiccmd (ticcmd_t* cmd, int maketic) memset(cmd, 0, sizeof(ticcmd_t)); +#if DEBUG_CONSISTENCY + if (netgame) printf("MAKETIC READ CONSISTENCY %d (slot %d) %d %02x\n", maketic, maketic%BACKUPTICS, consoleplayer, consistancy[consoleplayer][maketic%BACKUPTICS]); +#endif cmd->consistancy = consistancy[consoleplayer][maketic%BACKUPTICS]; - strafe = gamekeydown[key_strafe] || mousebuttons[mousebstrafe] - || joybuttons[joybstrafe]; + strafe = is_gamekeydown(key_strafe) + || mousebuttons[mousebstrafe] +#if !NO_USE_JOYSTICK + || joybuttons[joybstrafe] +#endif + ; // fraggle: support the old "joyb_speed = 31" hack which // allowed an autorun effect speed = key_speed >= NUMKEYS + || is_gamekeydown(key_speed) +#if !NO_USE_JOYSTICK || joybspeed >= MAX_JOY_BUTTONS - || gamekeydown[key_speed] - || joybuttons[joybspeed]; - + || joybuttons[joybspeed] +#endif + ; + forward = side = 0; // use two stage accelerative turning // on the keyboard and joystick - if (joyxmove < 0 - || joyxmove > 0 - || gamekeydown[key_right] - || gamekeydown[key_left]) - turnheld += ticdup; + if (is_gamekeydown(key_right) + || is_gamekeydown(key_left) +#if !NO_USE_JOYSTICK + || joyxmove < 0 + || joyxmove > 0 +#endif + ) + turnheld += ticdup; else turnheld = 0; @@ -366,76 +428,98 @@ void G_BuildTiccmd (ticcmd_t* cmd, int maketic) // let movement keys cancel each other out if (strafe) { - if (gamekeydown[key_right]) + if (is_gamekeydown(key_right)) { // fprintf(stderr, "strafe right\n"); side += sidemove[speed]; } - if (gamekeydown[key_left]) + if (is_gamekeydown(key_left)) { // fprintf(stderr, "strafe left\n"); side -= sidemove[speed]; } +#if !NO_USE_JOYSTICK if (joyxmove > 0) side += sidemove[speed]; if (joyxmove < 0) - side -= sidemove[speed]; + side -= sidemove[speed]; +#endif } else { - if (gamekeydown[key_right]) + if (is_gamekeydown(key_right)) cmd->angleturn -= angleturn[tspeed]; - if (gamekeydown[key_left]) - cmd->angleturn += angleturn[tspeed]; - if (joyxmove > 0) + if (is_gamekeydown(key_left)) + cmd->angleturn += angleturn[tspeed]; +#if !NO_USE_JOYSTICK + if (joyxmove > 0) cmd->angleturn -= angleturn[tspeed]; if (joyxmove < 0) cmd->angleturn += angleturn[tspeed]; - } - - if (gamekeydown[key_up]) +#endif + } + + if (is_gamekeydown(key_up)) { // fprintf(stderr, "up\n"); forward += forwardmove[speed]; } - if (gamekeydown[key_down]) + if (is_gamekeydown(key_down)) { // fprintf(stderr, "down\n"); forward -= forwardmove[speed]; } +#if !NO_USE_JOYSTICK if (joyymove < 0) forward += forwardmove[speed]; if (joyymove > 0) - forward -= forwardmove[speed]; + forward -= forwardmove[speed]; +#endif + + if (is_gamekeydown(key_strafeleft) - if (gamekeydown[key_strafeleft] - || joybuttons[joybstrafeleft] || mousebuttons[mousebstrafeleft] - || joystrafemove < 0) +#if !NO_USE_JOYSTICK + || joybuttons[joybstrafeleft] + || joystrafemove < 0 +#endif + ) { side -= sidemove[speed]; } - if (gamekeydown[key_straferight] - || joybuttons[joybstraferight] + if (is_gamekeydown(key_straferight) || mousebuttons[mousebstraferight] - || joystrafemove > 0) +#if !NO_USE_JOYSTICK + || joybuttons[joybstraferight] + || joystrafemove > 0 +#endif + ) { side += sidemove[speed]; } +#if !NO_USE_NET // buttons - cmd->chatchar = HU_dequeueChatChar(); + cmd->chatchar = HU_dequeueChatChar(); +#endif - if (gamekeydown[key_fire] || mousebuttons[mousebfire] - || joybuttons[joybfire]) + if (is_gamekeydown(key_fire) + || mousebuttons[mousebfire] +#if !NO_USE_JOYSTICK + || joybuttons[joybfire] +#endif + ) cmd->buttons |= BT_ATTACK; - if (gamekeydown[key_use] + if (is_gamekeydown(key_use) +#if !NO_USE_JOYSTICK || joybuttons[joybuse] - || mousebuttons[mousebuse]) +#endif + || mousebuttons[mousebuse] + ) { cmd->buttons |= BT_USE; // clear double clicks if hit use button @@ -460,7 +544,7 @@ void G_BuildTiccmd (ticcmd_t* cmd, int maketic) { int key = *weapon_keys[i]; - if (gamekeydown[key]) + if (is_gamekeydown(key)) { cmd->buttons |= BT_CHANGE; cmd->buttons |= i< 1 ) { dclickstate2 = bstrafe; @@ -540,14 +627,16 @@ void G_BuildTiccmd (ticcmd_t* cmd, int maketic) if (strafe) side += mousex*2; else - cmd->angleturn -= mousex*0x8; + cmd->angleturn -= mousex*0x8; +#if !DOOM_TINY if (mousex == 0) { // No movement in the previous frame testcontrols_mousespeed = 0; } +#endif mousex = mousey = 0; @@ -595,6 +684,9 @@ void G_BuildTiccmd (ticcmd_t* cmd, int maketic) carry = desired_angleturn - cmd->angleturn; } +#if DOOM_TINY + cmd->ingame = true; +#endif } @@ -611,37 +703,41 @@ void G_DoLoadLevel (void) // we look for an actual index, instead of simply // setting one. +#if !USE_WHD skyflatnum = R_FlatNumForName(DEH_String(SKYFLATNAME)); +#else + skyflatnum = W_GetNumForName(SKYFLATNAME) - firstflat; +#endif // The "Sky never changes in Doom II" bug was fixed in // the id Anthology version of doom2.exe for Final Doom. if ((gamemode == commercial) - && (gameversion == exe_final2 || gameversion == exe_chex)) + && (gameversion == exe_final2 || gameversion_is_chex(gameversion))) { - const char *skytexturename; + texturename_t skytexturename; if (gamemap < 12) { - skytexturename = "SKY1"; + skytexturename = TEXTURE_NAME(SKY1); } else if (gamemap < 21) { - skytexturename = "SKY2"; + skytexturename = TEXTURE_NAME(SKY2); } else { - skytexturename = "SKY3"; + skytexturename = TEXTURE_NAME(SKY3); } - skytexturename = DEH_String(skytexturename); + skytexturename = DEH_TextureName(skytexturename); skytexture = R_TextureNumForName(skytexturename); } levelstarttic = gametic; // for time calculation - if (wipegamestate == GS_LEVEL) - wipegamestate = -1; // force a wipe + if (wipegamestate == GS_LEVEL) + wipegamestate = -1; // force a wipe gamestate = GS_LEVEL; @@ -653,26 +749,30 @@ void G_DoLoadLevel (void) memset (players[i].frags,0,sizeof(players[i].frags)); } - P_SetupLevel (gameepisode, gamemap, 0, gameskill); - displayplayer = consoleplayer; // view the guy you are playing - gameaction = ga_nothing; + P_SetupLevel (gameepisode, gamemap, 0, gameskill); + displayplayer = consoleplayer; // view the guy you are playing + gameaction = ga_nothing; Z_CheckHeap (); // clear cmd building stuff memset (gamekeydown, 0, sizeof(gamekeydown)); +#if !NO_USE_JOYSTICK joyxmove = joyymove = joystrafemove = 0; +#endif mousex = mousey = 0; sendpause = sendsave = paused = false; memset(mousearray, 0, sizeof(mousearray)); +#if !NO_USE_JOYSTICK memset(joyarray, 0, sizeof(joyarray)); - +#endif if (testcontrols) { players[consoleplayer].message = "Press escape to quit."; } } +#if !NO_USE_JOYSTICK static void SetJoyButtons(unsigned int buttons_mask) { int i; @@ -700,6 +800,7 @@ static void SetJoyButtons(unsigned int buttons_mask) joybuttons[i] = button_on; } } +#endif static void SetMouseButtons(unsigned int buttons_mask) { @@ -732,7 +833,14 @@ static void SetMouseButtons(unsigned int buttons_mask) // Get info needed to make ticcmd_ts for the players. // boolean G_Responder (event_t* ev) -{ +{ +#if USE_FPS + if (ev->type == ev_keydown && ev->data2 =='\\') { + show_fps ^= 1; + return true; + } +#endif + // allow spy mode changes even during the demo if (gamestate == GS_LEVEL && ev->type == ev_keydown && ev->data1 == key_spy && (singledemo || !deathmatch) ) @@ -770,10 +878,10 @@ boolean G_Responder (event_t* ev) G_DeathMatchSpawnPlayer (0); return true; } -#endif - if (HU_Responder (ev)) + if (HU_Responder (ev)) return true; // chat ate the event - if (ST_Responder (ev)) +#endif + if (ST_Responder (ev)) return true; // status window ate it if (AM_Responder (ev)) return true; // automap ate it @@ -783,8 +891,9 @@ boolean G_Responder (event_t* ev) { if (F_Responder (ev)) return true; // finale ate the event - } + } +#if !DOOM_TINY if (testcontrols && ev->type == ev_mouse) { // If we are invoked by setup to test the controls, save the @@ -794,6 +903,7 @@ boolean G_Responder (event_t* ev) testcontrols_mousespeed = abs(ev->data2); } +#endif // If the next/previous weapon keys are pressed, set the next_weapon // variable to change weapons when the next ticcmd is generated. @@ -816,14 +926,14 @@ boolean G_Responder (event_t* ev) } else if (ev->data1 data1] = true; + set_gamekeydown(ev->data1, true); } return true; // eat key down events case ev_keyup: if (ev->data1 data1] = false; + set_gamekeydown(ev->data1, false); return false; // always let key up events filter down case ev_mouse: @@ -831,14 +941,15 @@ boolean G_Responder (event_t* ev) mousex = ev->data2*(mouseSensitivity+5)/10; mousey = ev->data3*(mouseSensitivity+5)/10; return true; // eat events - - case ev_joystick: + +#if !NO_USE_JOYSTICK + case ev_joystick: SetJoyButtons(ev->data1); joyxmove = ev->data2; joyymove = ev->data3; joystrafemove = ev->data4; - return true; // eat events - + return true; // eat events +#endif default: break; } @@ -857,49 +968,73 @@ void G_Ticker (void) int i; int buf; ticcmd_t* cmd; - + // do player reborns if needed for (i=0 ; i BACKUPTICS && consistancy[i][buf] != cmd->consistancy) - { - I_Error ("consistency failure (%i should be %i)", - cmd->consistancy, consistancy[i][buf]); + { +//#warning removed consistency failure +//#if !USE_PICO_NET +#if DEBUG_CONSISTENCY + printf("consistency error tic %d (slot %d) %d (%02x should be %02x)\n", gametic, gametic % BACKUPTICS, i, + cmd->consistancy, consistancy[i][buf]); +#else + I_Error("consistency error tic %d (slot %d) %d (%02x should be %02x)\n", gametic, gametic % BACKUPTICS, i, + cmd->consistancy, consistancy[i][buf]); +#endif +//#endif } - if (players[i].mo) - consistancy[i][buf] = players[i].mo->x; - else + if (players[i].mo) { +#if DEBUG_CONSISTENCY + printf("calc consistency %d (slot %d) %d %02x\n", gametic, gametic % BACKUPTICS, i, players[i].mo->xy.x&0xff); +#endif + consistancy[i][buf] = players[i].mo->xy.x; + } else consistancy[i][buf] = rndindex; } } @@ -997,7 +1143,9 @@ void G_Ticker (void) if (oldgamestate == GS_INTERMISSION && gamestate != GS_INTERMISSION) { +#if !NO_USE_WI WI_End(); +#endif } oldgamestate = gamestate; @@ -1005,15 +1153,22 @@ void G_Ticker (void) // do main actions switch (gamestate) { - case GS_LEVEL: - P_Ticker (); - ST_Ticker (); + case GS_LEVEL: + P_Ticker (); +#if DOOM_TINY + if (!pre_wipe_state) +#endif + ST_Ticker (); AM_Ticker (); HU_Ticker (); break; - case GS_INTERMISSION: - WI_Ticker (); + case GS_INTERMISSION: +#if !NO_USE_WI + WI_Ticker (); +#else + G_WorldDone(); +#endif break; case GS_FINALE: @@ -1128,8 +1283,8 @@ G_CheckSpot { // first spawn of level, before corpses for (i=0 ; ix == mthing->x << FRACBITS - && players[i].mo->y == mthing->y << FRACBITS) + if (players[i].mo->xy.x == mthing->x << FRACBITS + && players[i].mo->xy.y == mthing->y << FRACBITS) return false; return true; } @@ -1142,8 +1297,8 @@ G_CheckSpot // flush an old corpse if needed if (bodyqueslot >= BODYQUESIZE) - P_RemoveMobj (bodyque[bodyqueslot%BODYQUESIZE]); - bodyque[bodyqueslot%BODYQUESIZE] = players[playernum].mo; + P_RemoveMobj (shortptr_to_mobj(bodyque[bodyqueslot%BODYQUESIZE])); + bodyque[bodyqueslot%BODYQUESIZE] = mobj_to_shortptr(players[playernum].mo); bodyqueslot++; // spawn a teleport fog @@ -1154,13 +1309,13 @@ G_CheckSpot // // an = ( ANG45 * (((unsigned int) mthing->angle)/45) ) // >> ANGLETOFINESHIFT; - // mo = P_SpawnMobj (x+20*finecosine[an], y+20*finesine[an] + // mo = P_SpawnMobj (x+20*finecosine(an), y+20*finesine(an) // , ss->sector->floorheight // , MT_TFOG); // // But 'an' can be a signed value in the DOS version. This means that // we get a negative index and the lookups into finecosine/finesine - // end up dereferencing values in finetangent[]. + // end up dereferencing values in finetangent(). // A player spawning on a deathmatch start facing directly west spawns // "silently" with no spawn fog. Emulate this. // @@ -1178,27 +1333,27 @@ G_CheckSpot switch (an) { case 4096: // -4096: - xa = finetangent[2048]; // finecosine[-4096] - ya = finetangent[0]; // finesine[-4096] + xa = finetangent(2048); // finecosine(-4096) + ya = finetangent(0); // finesine(-4096) break; case 5120: // -3072: - xa = finetangent[3072]; // finecosine[-3072] - ya = finetangent[1024]; // finesine[-3072] + xa = finetangent(3072); // finecosine(-3072) + ya = finetangent(1024); // finesine(-3072) break; case 6144: // -2048: - xa = finesine[0]; // finecosine[-2048] - ya = finetangent[2048]; // finesine[-2048] + xa = finesine(0); // finecosine(-2048) + ya = finetangent(2048); // finesine(-2048) break; case 7168: // -1024: - xa = finesine[1024]; // finecosine[-1024] - ya = finetangent[3072]; // finesine[-1024] + xa = finesine(1024); // finecosine(-1024) + ya = finetangent(3072); // finesine(-1024) break; case 0: case 1024: case 2048: case 3072: - xa = finecosine[an]; - ya = finesine[an]; + xa = finecosine(an); + ya = finesine(an); break; default: I_Error("G_CheckSpot: unexpected angle %d\n", an); @@ -1206,11 +1361,11 @@ G_CheckSpot break; } mo = P_SpawnMobj(x + 20 * xa, y + 20 * ya, - ss->sector->floorheight, MT_TFOG); + sector_floorheight(subsector_sector(ss)), MT_TFOG); } if (players[consoleplayer].viewz != 1) - S_StartSound (mo, sfx_telept); // don't start sound on first frame + S_StartObjSound (mo, sfx_telept); // don't start sound on first frame return true; } @@ -1262,7 +1417,7 @@ void G_DoReborn (int playernum) // respawn at the start // first dissasociate the corpse - players[playernum].mo->player = NULL; + mobj_full(players[playernum].mo)->sp_player =0; // spawn at random spot if in death match if (deathmatch) @@ -1293,16 +1448,18 @@ void G_DoReborn (int playernum) } } - + +#if !NO_SCREENSHOT void G_ScreenShot (void) { gameaction = ga_screenshot; -} +} +#endif // DOOM Par Times -int pars[4][10] = +static const isb_int16_t pars[4][10] = { {0}, {0,30,75,120,90,165,180,180,30,165}, @@ -1311,7 +1468,7 @@ int pars[4][10] = }; // DOOM II Par Times -int cpars[32] = +static const isb_int16_t cpars[32] = { 30,90,120,120,90,150,120,120,270,90, // 1-10 210,150,150,150,210,150,420,150,210,150, // 11-20 @@ -1324,8 +1481,7 @@ int cpars[32] = // G_DoCompleted // boolean secretexit; -extern char* pagename; - + void G_ExitLevel (void) { secretexit = false; @@ -1361,7 +1517,7 @@ void G_DoCompleted (void) { // Chex Quest ends after 5 levels, rather than 8. - if (gameversion == exe_chex) + if (gameversion_is_chex(gameversion)) { if (gamemap == 5) { @@ -1373,7 +1529,14 @@ void G_DoCompleted (void) { switch(gamemap) { +#if !HACK_FINALE_E1M1 case 8: +#else + case 1: +#if !HACK_FINALE_SHAREWARE + gameepisode = 3; // for ultimate +#endif +#endif gameaction = ga_victory; return; case 9: @@ -1499,8 +1662,10 @@ void G_DoCompleted (void) automapactive = false; StatCopy(&wminfo); - - WI_Start (&wminfo); + +#if !NO_USE_WI + WI_Start (&wminfo); +#endif } @@ -1512,7 +1677,7 @@ void G_WorldDone (void) gameaction = ga_worlddone; if (secretexit) - players[consoleplayer].didsecret = true; + players[consoleplayer].didsecret = true; if ( gamemode == commercial ) { @@ -1522,6 +1687,11 @@ void G_WorldDone (void) case 31: if (!secretexit) break; +#if HACK_FINALE_E1M1 + case 1: + gamemap=30; + // fall thru +#endif case 6: case 11: case 20: @@ -1553,29 +1723,58 @@ void R_ExecuteSetViewSize (void); char savename[256]; void G_LoadGame (char* name) -{ +{ +#if !NO_FILE_ACCESS M_StringCopy(savename, name, sizeof(savename)); - gameaction = ga_loadgame; -} +#endif + gameaction = ga_loadgame; +} -void G_DoLoadGame (void) -{ +void G_DoLoadGame (void) { +#if !NO_USE_LOAD int savedleveltime; - - gameaction = ga_nothing; - + + gameaction = ga_nothing; + +#if !NO_FILE_ACCESS save_stream = fopen(savename, "rb"); - if (save_stream == NULL) - { + if (save_stream == NULL) { I_Error("Could not load savegame %s", savename); } +#endif +#if LOAD_COMPRESSED + th_bit_input bi; + uint32_t size; +#if !NO_FILE_ACCESS + uint8_t *load_buffer = pd_get_work_area(&size); + fseek(save_stream, 0, SEEK_END); + uint fsize = ftell(save_stream); + if (fsize > size) { + return; + } + fseek(save_stream, 0, SEEK_SET); + fread(load_buffer, 1, fsize, save_stream); +#else + flash_slot_info_t slots[g_load_slot+1]; + P_SaveGameGetExistingFlashSlotAddresses(slots, g_load_slot+1); + if (!slots[g_load_slot].data) return; + const uint8_t *load_buffer = slots[g_load_slot].data; +#endif + sg_bi = &bi; + th_bit_input_init(sg_bi, load_buffer); +#endif savegame_error = false; if (!P_ReadSaveGameHeader()) { +#if !NO_FILE_ACCESS fclose(save_stream); +#endif +#if DOOM_TINY + players[consoleplayer].message = "Load failed: invalid game or WAD mismatch."; +#endif return; } @@ -1587,24 +1786,45 @@ void G_DoLoadGame (void) leveltime = savedleveltime; // dearchive all the modifications - P_UnArchivePlayers (); - P_UnArchiveWorld (); - P_UnArchiveThinkers (); - P_UnArchiveSpecials (); + xprintf("PL AT %08x\n", (sg_bi->cur - load_buffer) * 8 - sg_bi->bits); + P_UnArchivePlayers (); + xprintf("WO AT %08x\n", (sg_bi->cur - load_buffer) * 8 - sg_bi->bits); + P_UnArchiveWorld (); + xprintf("TH %08x\n", (sg_bi->cur - load_buffer) * 8 - sg_bi->bits); + P_UnArchiveThinkers (); + xprintf("SP %08x\n", (sg_bi->cur - load_buffer) * 8 - sg_bi->bits); + P_UnArchiveSpecials (); if (!P_ReadSaveGameEOF()) I_Error ("Bad savegame"); +#if !NO_FILE_ACCESS fclose(save_stream); - +#endif + if (setsizeneeded) R_ExecuteSetViewSize (); - + +#if !NO_RDRAW // draw the pattern into the back screen - R_FillBackScreen (); -} + R_FillBackScreen (); +#endif +#endif +} +#if PICO_ON_DEVICE +static boolean save_game_clear(int key) { + if (key == key_menu_confirm) { + uint32_t size; + uint8_t *tmp_buffer = pd_get_work_area(&size); + P_SaveGameWriteFlashSlot(savegameslot, NULL, 0, tmp_buffer); + M_SaveGame(0); + return true; + } + return false; +} +#endif // // G_SaveGame // Called by the menu task. @@ -1621,7 +1841,9 @@ G_SaveGame } void G_DoSaveGame (void) -{ +{ +#if !NO_USE_SAVE +#if !NO_FILE_ACCESS char *savegame_file; char *temp_savegame_file; char *recovery_savegame_file; @@ -1648,30 +1870,63 @@ void G_DoSaveGame (void) temp_savegame_file, recovery_savegame_file); } } +#endif +#if SAVE_COMPRESSED + th_bit_output bo; + uint32_t size; + uint8_t *save_buffer = pd_get_work_area(&size); + save_buffer += 4096; size -= 4096; // we need 4k for flash writing + sg_bo = &bo; + th_bit_output_init(sg_bo, save_buffer, size); +#endif savegame_error = false; P_WriteSaveGameHeader(savedescription); + xprintf("PL AT %08x\n", (sg_bo->cur - save_buffer) * 8 + sg_bo->bits); P_ArchivePlayers (); + xprintf("WO AT %08x\n", (sg_bo->cur - save_buffer) * 8 + sg_bo->bits); P_ArchiveWorld (); + xprintf("TH AT %08x\n", (sg_bo->cur - save_buffer) * 8 + sg_bo->bits); P_ArchiveThinkers (); + xprintf("SP AT %08x\n", (sg_bo->cur - save_buffer) * 8 + sg_bo->bits); P_ArchiveSpecials (); P_WriteSaveGameEOF(); + boolean resume = true; +#if SAVE_COMPRESSED + if (bo.bits) th_write_bits(&bo, 0, 8-bo.bits); +#if !NO_FILE_ACCESS + fwrite(save_buffer, 1, bo.cur - save_buffer, save_stream); +#endif + printf("SAVE GAME SIZE %d\n", (int)(bo.cur - save_buffer)); +#if PICO_ON_DEVICE + if (!P_SaveGameWriteFlashSlot(savegameslot, save_buffer, (int)(bo.cur - save_buffer), save_buffer - 4096)) { + M_StartMessage("There was not enough space to save the game.\nWould you like to clear this slot and\n try saving again in a different slot?\n\npress y or n.",save_game_clear,true); + resume = false; + } +#endif +#endif // Enforce the same savegame size limit as in Vanilla Doom, // except if the vanilla_savegame_limit setting is turned off. +#if !NO_FILE_ACCESS if (vanilla_savegame_limit && ftell(save_stream) > SAVEGAMESIZE) { + I_Error("Savegame buffer overrun"); } +#endif // Finish up, close the savegame file. +#if !NO_FILE_ACCESS fclose(save_stream); +#endif +#if !DOOM_TINY if (recovery_savegame_file != NULL) { // We failed to save to the normal location, but we wrote a @@ -1681,20 +1936,27 @@ void G_DoSaveGame (void) "But your game has been saved to '%s' for recovery.", temp_savegame_file, recovery_savegame_file); } +#endif +#if !NO_FILE_ACCESS // Now rename the temporary savegame file to the actual savegame // file, overwriting the old savegame if there was one there. remove(savegame_file); rename(temp_savegame_file, savegame_file); +#endif - gameaction = ga_nothing; M_StringCopy(savedescription, "", sizeof(savedescription)); + gameaction = ga_nothing; + if (resume) { + players[consoleplayer].message = DEH_String(GGSAVED); + } - players[consoleplayer].message = DEH_String(GGSAVED); - +#if !NO_RDRAW // draw the pattern into the back screen R_FillBackScreen (); +#endif +#endif } @@ -1704,34 +1966,45 @@ void G_DoSaveGame (void) // consoleplayer, displayplayer, playeringame[] should be set. // skill_t d_skill; -int d_episode; -int d_map; +static isb_int8_t d_episode; +static isb_int8_t d_map; void G_DeferedInitNew ( skill_t skill, int episode, - int map) + int map, + boolean net) { - d_skill = skill; - d_episode = episode; - d_map = map; - gameaction = ga_newgame; -} + d_skill = skill; + d_episode = (isb_int8_t)episode; + d_map = (isb_int8_t)map; +#if USE_PICO_NET + netgame = net; + gameaction = net ? ga_newgamenet : ga_newgame; +#else + gameaction = ga_newgame; +#endif +} -void G_DoNewGame (void) +void G_DoNewGame(boolean net) { - demoplayback = false; + demoplayback = false; netdemo = false; - netgame = false; - deathmatch = false; - playeringame[1] = playeringame[2] = playeringame[3] = 0; + if (!net) { +#if !NO_USE_NET || USE_PICO_NET // var doesn't exist otherwise + netgame = false; +#endif + deathmatch = false; + playeringame[0] = 1; + playeringame[1] = playeringame[2] = playeringame[3] = 0; + consoleplayer = 0; + } respawnparm = false; fastparm = false; nomonsters = false; - consoleplayer = 0; - G_InitNew (d_skill, d_episode, d_map); + G_InitNew (d_skill, d_episode, d_map); gameaction = ga_nothing; } @@ -1742,7 +2015,7 @@ G_InitNew int episode, int map ) { - const char *skytexturename; + texturename_t skytexturename; int i; if (paused) @@ -1822,6 +2095,7 @@ G_InitNew else respawnmonsters = false; +#if !DOOM_CONST if (fastparm || (skill == sk_nightmare && gameskill != sk_nightmare) ) { for (i=S_SARG_RUN1 ; i<=S_SARG_PAIN2 ; i++) @@ -1838,6 +2112,9 @@ G_InitNew mobjinfo[MT_HEADSHOT].speed = 10*FRACUNIT; mobjinfo[MT_TROOPSHOT].speed = 10*FRACUNIT; } +#else + nightmare_speeds = fastparm || skill == sk_nightmare; +#endif // force players to be initialized upon first level load for (i=0 ; iangleturn = ((unsigned char) *demo_p++)<<8; } - cmd->buttons = (unsigned char)*demo_p++; + cmd->buttons = (unsigned char)*demo_p++; +#else + uint changes = th_decode(demo_decode.decoders + demo_decode.changes_offset, &demo_decode.bi); + assert(changes <= 16); + if (changes == 16) { + // end of demo data stream + G_CheckDemoStatus(); + return; + } + if (changes & 1) { + demo_decode.last_fb += from_zig(th_decode(demo_decode.decoders + demo_decode.fb_delta_offset, &demo_decode.bi)); + } + cmd->forwardmove = demo_decode.last_fb; + if (changes & 2) { + cmd->sidemove = from_zig(th_decode(demo_decode.decoders + demo_decode.strafe_offset, &demo_decode.bi)); + } else { + cmd->sidemove = 0; + } + assert(!longtics); + if (changes & 4) { + demo_decode.last_turn += from_zig(th_decode(demo_decode.decoders + demo_decode.turn_delta_offset, &demo_decode.bi)); + } + cmd->angleturn = demo_decode.last_turn << 8; + if (changes & 8) { + cmd->buttons = th_decode(demo_decode.decoders + demo_decode.buttons_offset, &demo_decode.bi); + } else { + cmd->buttons = 0; + } +#endif } // Increase the size of the demo buffer to allow unlimited demos +#if !NO_DEMO_RECORDING static void IncreaseDemoBuffer(void) { int current_length; @@ -1967,10 +2273,10 @@ static void IncreaseDemoBuffer(void) } void G_WriteDemoTiccmd (ticcmd_t* cmd) -{ +{ byte *demo_start; - if (gamekeydown[key_demo_quit]) // press q to end demo recording + if (is_gamekeydown(key_demo_quit)) // press q to end demo recording G_CheckDemoStatus (); demo_start = demo_p; @@ -2013,10 +2319,9 @@ void G_WriteDemoTiccmd (ticcmd_t* cmd) } G_ReadDemoTiccmd (cmd); // make SURE it is exactly the same -} - - - +} + + // // G_RecordDemo // @@ -2028,7 +2333,7 @@ void G_RecordDemo (char *name) usergame = false; demoname_size = strlen(name) + 5; - demoname = Z_Malloc(demoname_size, PU_STATIC, NULL); + demoname = Z_Malloc(demoname_size, PU_STATIC, 0); M_snprintf(demoname, demoname_size, "%s.lmp", name); maxsize = 0x20000; @@ -2040,14 +2345,17 @@ void G_RecordDemo (char *name) // Specify the demo buffer size (KiB) // +#if !NO_USE_ARGS i = M_CheckParmWithArgs("-maxdemo", 1); if (i) maxsize = atoi(myargv[i+1])*1024; - demobuffer = Z_Malloc (maxsize,PU_STATIC,NULL); +#endif + demobuffer = Z_Malloc (maxsize,PU_STATIC, 0); demoend = demobuffer + maxsize; demorecording = true; -} +} +#endif // Get the demo version code appropriate for the version set in gameversion. int G_VanillaVersionCode(void) @@ -2068,6 +2376,7 @@ int G_VanillaVersionCode(void) } } +#if !NO_DEMO_RECORDING void G_BeginRecording (void) { int i; @@ -2107,7 +2416,7 @@ void G_BeginRecording (void) for (i=0 ; i 0 || M_CheckParm("-netdemo") > 0) { netgame = true; netdemo = true; } +#endif - // don't spend a lot of time in loadlevel + // don't spend a lot of time in loadlevel precache = false; G_InitNew (skill, episode, map); - precache = true; +#if USE_WHD + uint8_t decode_words = *demo_p++; + memset(&demo_decode, 0, sizeof(demo_decode)); + th_bit_input_init(&demo_decode.bi, demo_p); + demo_decode.decoders = Z_Malloc(decode_words * 2, PU_LEVEL, 0); + int idx=0; + uint8_t tmp_buffer[512]; +#define READ_DECODER(name, func) demo_decode.name##_offset = idx, idx = func(&demo_decode.bi, demo_decode.decoders + idx, decode_words - idx, tmp_buffer, sizeof(tmp_buffer)) - demo_decode.decoders + READ_DECODER(changes, th_read_simple_decoder); + READ_DECODER(fb_delta, th_read_simple_decoder); + READ_DECODER(strafe, th_read_simple_decoder); + READ_DECODER(turn_delta, th_read_simple_decoder); + READ_DECODER(buttons, th_read_simple_decoder); + assert(decode_words == idx); +#endif + precache = true; starttime = I_GetTime (); usergame = false; @@ -2240,7 +2569,9 @@ void G_TimeDemo (char* name) // Disable rendering the screen entirely. // +#if !FORCE_NODRAW nodrawers = M_CheckParm ("-nodraw"); +#endif timingdemo = true; singletics = true; @@ -2253,7 +2584,7 @@ void G_TimeDemo (char* name) /* =================== = -= G_CheckDemoStatus += G_CheckDemoStatus = = Called after a death or level completion to allow demos to be cleaned up = Returns true if a new demo loop action will take place @@ -2262,31 +2593,30 @@ void G_TimeDemo (char* name) boolean G_CheckDemoStatus (void) { - int endtime; - - if (timingdemo) + + if (timingdemo) { - float fps; - int realtics; - - endtime = I_GetTime (); - realtics = endtime - starttime; - fps = ((float) gametic * TICRATE) / realtics; - // Prevent recursive calls timingdemo = false; demoplayback = false; +#if !NO_USE_FLOAT + int endtime = I_GetTime (); + int realtics = endtime - starttime; + float fps = ((float) gametic * TICRATE) / realtics; I_Error ("timed %i gametics in %i realtics (%f fps)", gametic, realtics, fps); - } - - if (demoplayback) +#endif + } + + if (demoplayback) { W_ReleaseLumpName(defdemoname); demoplayback = false; netdemo = false; +#if !NO_USE_NET netgame = false; +#endif deathmatch = false; playeringame[1] = playeringame[2] = playeringame[3] = 0; respawnparm = false; @@ -2300,8 +2630,9 @@ boolean G_CheckDemoStatus (void) D_AdvanceDemo (); return true; - } - + } + +#if !NO_DEMO_RECORDING if (demorecording) { *demo_p++ = DEMOMARKER; @@ -2309,7 +2640,8 @@ boolean G_CheckDemoStatus (void) Z_Free (demobuffer); demorecording = false; I_Error ("Demo %s recorded",demoname); - } + } +#endif return false; } diff --git a/src/doom/g_game.h b/src/doom/g_game.h index 519ef0bf..150668bb 100644 --- a/src/doom/g_game.h +++ b/src/doom/g_game.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -35,7 +36,7 @@ void G_InitNew (skill_t skill, int episode, int map); // Can be called by the startup code or M_Responder. // A normal game starts at map 1, // but a warp test can start elsewhere -void G_DeferedInitNew (skill_t skill, int episode, int map); +void G_DeferedInitNew (skill_t skill, int episode, int map, boolean net); void G_DeferedPlayDemo (const char* demo); @@ -48,10 +49,12 @@ void G_DoLoadGame (void); // Called by M_Responder. void G_SaveGame (int slot, char* description); +#if !NO_DEMO_RECORDING // Only called by startup code. void G_RecordDemo (char* name); void G_BeginRecording (void); +#endif void G_PlayDemo (char* name); void G_TimeDemo (char* name); @@ -74,7 +77,11 @@ void G_ScreenShot (void); void G_DrawMouseSpeedBox(void); int G_VanillaVersionCode(void); -extern int vanilla_savegame_limit; -extern int vanilla_demo_limit; +extern isb_int8_t vanilla_savegame_limit; +extern isb_int8_t vanilla_demo_limit; + +#if PICO_ON_DEVICE +extern uint8_t g_load_slot; +#endif #endif diff --git a/src/doom/hu_lib.c b/src/doom/hu_lib.c index 6a42b04b..dc6c229c 100644 --- a/src/doom/hu_lib.c +++ b/src/doom/hu_lib.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -24,6 +25,7 @@ #include "v_video.h" #include "i_swap.h" +#include "w_wad.h" #include "hu_lib.h" #include "r_local.h" #include "r_draw.h" @@ -49,7 +51,7 @@ HUlib_initTextLine ( hu_textline_t* t, int x, int y, - patch_t** f, + vpatch_sequence_t f, int sc ) { t->x = x; @@ -110,10 +112,10 @@ HUlib_drawTextLine && c >= l->sc && c <= '_') { - w = SHORT(l->f[c - l->sc]->width); + w = vpatch_width(resolve_vpatch_handle(vpatch_n(l->f,c - l->sc))); if (x+w > SCREENWIDTH) break; - V_DrawPatchDirect(x, l->y, l->f[c - l->sc]); + V_DrawPatchDirect(x, l->y, vpatch_n(l->f, c - l->sc)); x += w; } else @@ -126,9 +128,9 @@ HUlib_drawTextLine // draw the cursor if requested if (drawcursor - && x + SHORT(l->f['_' - l->sc]->width) <= SCREENWIDTH) + && x + vpatch_width(resolve_vpatch_handle(vpatch_n(l->f,'_' - l->sc))) <= SCREENWIDTH) { - V_DrawPatchDirect(x, l->y, l->f['_' - l->sc]); + V_DrawPatchDirect(x, l->y, vpatch_n(l->f, '_' - l->sc)); } } @@ -144,10 +146,11 @@ void HUlib_eraseTextLine(hu_textline_t* l) // and the text must either need updating or refreshing // (because of a recent change back from the automap) +#if !NO_RDRAW if (!automapactive && viewwindowx && l->needsupdate) { - lh = SHORT(l->f[0]->height) + 1; + lh = patch_height(l->f[0]) + 1; for (y=l->y,yoffset=y*SCREENWIDTH ; yy+lh ; y++,yoffset+=SCREENWIDTH) { if (y < viewwindowy || y >= viewwindowy + viewheight) @@ -160,6 +163,7 @@ void HUlib_eraseTextLine(hu_textline_t* l) } } } +#endif if (l->needsupdate) l->needsupdate--; @@ -171,7 +175,7 @@ HUlib_initSText int x, int y, int h, - patch_t** font, + vpatch_sequence_t font, int startchar, boolean* on ) { @@ -182,9 +186,10 @@ HUlib_initSText s->on = on; s->laston = true; s->cl = 0; + int pheight = vpatch_height(resolve_vpatch_handle(vpatch_n(font, 0))); for (i=0;il[i], - x, y - i*(SHORT(font[0]->height)+1), + x, y - i*(pheight+1), font, startchar); } @@ -263,7 +268,7 @@ HUlib_initIText ( hu_itext_t* it, int x, int y, - patch_t** font, + vpatch_sequence_t font, int startchar, boolean* on ) { diff --git a/src/doom/hu_lib.h b/src/doom/hu_lib.h index ca5eb8d8..00c0db9a 100644 --- a/src/doom/hu_lib.h +++ b/src/doom/hu_lib.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -39,7 +40,7 @@ typedef struct int x; int y; - patch_t** f; // font + vpatch_sequence_t f; // font int sc; // start character char l[HU_MAXLINELENGTH+1]; // line of text int len; // current line length @@ -97,7 +98,7 @@ void HUlib_init(void); // clear a line of text void HUlib_clearTextLine(hu_textline_t *t); -void HUlib_initTextLine(hu_textline_t *t, int x, int y, patch_t **f, int sc); +void HUlib_initTextLine(hu_textline_t *t, int x, int y, vpatch_sequence_t f, int sc); // returns success boolean HUlib_addCharToTextLine(hu_textline_t *t, char ch); @@ -123,7 +124,7 @@ HUlib_initSText int x, int y, int h, - patch_t** font, + vpatch_sequence_t font, int startchar, boolean* on ); @@ -149,7 +150,7 @@ HUlib_initIText ( hu_itext_t* it, int x, int y, - patch_t** font, + vpatch_sequence_t font, int startchar, boolean* on ); diff --git a/src/doom/hu_stuff.c b/src/doom/hu_stuff.c index fd14c837..a9877cec 100644 --- a/src/doom/hu_stuff.c +++ b/src/doom/hu_stuff.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -41,6 +42,7 @@ // Data. #include "dstrings.h" #include "sounds.h" +#include "r_data.h" // // Locally used constants, shortcuts. @@ -52,17 +54,19 @@ #define HU_TITLE_CHEX (mapnames_chex[(gameepisode-1)*9+gamemap-1]) #define HU_TITLEHEIGHT 1 #define HU_TITLEX 0 -#define HU_TITLEY (167 - SHORT(hu_font[0]->height)) +#define HU_TITLEY (167 - vpatch_height(resolve_vpatch_handle(vpatch_n(hu_font,0)))) #define HU_INPUTTOGGLE 't' #define HU_INPUTX HU_MSGX -#define HU_INPUTY (HU_MSGY + HU_MSGHEIGHT*(SHORT(hu_font[0]->height) +1)) +#define HU_INPUTY (HU_MSGY + HU_MSGHEIGHT*(vpatch_height(resolve_vpatch_handle(vpatch_n(hu_font,0))) +1)) #define HU_INPUTWIDTH 64 #define HU_INPUTHEIGHT 1 - - -char *chat_macros[10] = +#if !DOOM_TINY +const char *chat_macros[10] = +#else +const constcharstar chat_macros[10] = +#endif { HUSTR_CHATMACRO0, HUSTR_CHATMACRO1, @@ -76,7 +80,7 @@ char *chat_macros[10] = HUSTR_CHATMACRO9 }; -const char *player_names[] = +const constcharstar player_names[] = { HUSTR_PLRGREEN, HUSTR_PLRINDIGO, @@ -86,7 +90,12 @@ const char *player_names[] = char chat_char; // remove later. static player_t* plr; -patch_t* hu_font[HU_FONTSIZE]; +#if !USE_WHD +vpatch_handle_large_t hu_fontx[HU_FONTSIZE]; +vpatch_sequence_t hu_font = hu_fontx; +#else +vpatch_sequence_t hu_font; +#endif static hu_textline_t w_title; boolean chat_on; static hu_itext_t w_chat; @@ -101,7 +110,7 @@ static boolean message_nottobefuckedwith; static hu_stext_t w_message; static int message_counter; -extern int showMessages; +extern isb_int8_t showMessages; static boolean headsupactive = false; @@ -110,7 +119,7 @@ static boolean headsupactive = false; // The actual names can be found in DStrings.h. // -const char *mapnames[] = // DOOM shareware/registered/retail (Ultimate) names. +const constcharstar mapnames[] = // DOOM shareware/registered/retail (Ultimate) names. { HUSTR_E1M1, @@ -123,6 +132,7 @@ const char *mapnames[] = // DOOM shareware/registered/retail (Ultimate) names. HUSTR_E1M8, HUSTR_E1M9, +#if !DEMO1_ONLY HUSTR_E2M1, HUSTR_E2M2, HUSTR_E2M3, @@ -162,6 +172,7 @@ const char *mapnames[] = // DOOM shareware/registered/retail (Ultimate) names. "NEWLEVEL", "NEWLEVEL", "NEWLEVEL" +#endif }; const char *mapnames_chex[] = // Chex Quest names. @@ -224,7 +235,7 @@ const char *mapnames_chex[] = // Chex Quest names. // the layout in the Vanilla executable, where it is possible to // overflow the end of one array into the next. -const char *mapnames_commercial[] = +const char * const mapnames_commercial[] = { // DOOM 2 map names. @@ -352,12 +363,19 @@ void HU_Init(void) char buffer[9]; // load the heads-up font +#if !USE_WHD j = HU_FONTSTART; for (i=0;ixtics - 1; + if (nightmare_speeds) { + int st = s - states; + if (st >= S_SARG_RUN1 && st <= S_SARG_PAIN2) { + tics >>= 1; + } + } + return tics; +} +#endif + + + +should_be_const state_t states[NUMSTATES] = { + STATE(SPR_TROO,0,-1,NULL,S_NULL,0,0), // S_NULL + STATE(SPR_SHTG,4,0,A_Light0,S_NULL,0,0), // S_LIGHTDONE + STATE(SPR_PUNG,0,1,A_WeaponReady,S_PUNCH,0,0), // S_PUNCH + STATE(SPR_PUNG,0,1,A_Lower,S_PUNCHDOWN,0,0), // S_PUNCHDOWN + STATE(SPR_PUNG,0,1,A_Raise,S_PUNCHUP,0,0), // S_PUNCHUP + STATE(SPR_PUNG,1,4,NULL,S_PUNCH2,0,0), // S_PUNCH1 + STATE(SPR_PUNG,2,4,A_Punch,S_PUNCH3,0,0), // S_PUNCH2 + STATE(SPR_PUNG,3,5,NULL,S_PUNCH4,0,0), // S_PUNCH3 + STATE(SPR_PUNG,2,4,NULL,S_PUNCH5,0,0), // S_PUNCH4 + STATE(SPR_PUNG,1,5,A_ReFire,S_PUNCH,0,0), // S_PUNCH5 + STATE(SPR_PISG,0,1,A_WeaponReady,S_PISTOL,0,0),// S_PISTOL + STATE(SPR_PISG,0,1,A_Lower,S_PISTOLDOWN,0,0), // S_PISTOLDOWN + STATE(SPR_PISG,0,1,A_Raise,S_PISTOLUP,0,0), // S_PISTOLUP + STATE(SPR_PISG,0,4,NULL,S_PISTOL2,0,0), // S_PISTOL1 + STATE(SPR_PISG,1,6,A_FirePistol,S_PISTOL3,0,0),// S_PISTOL2 + STATE(SPR_PISG,2,4,NULL,S_PISTOL4,0,0), // S_PISTOL3 + STATE(SPR_PISG,1,5,A_ReFire,S_PISTOL,0,0), // S_PISTOL4 + STATE(SPR_PISF,0 | FF_FULLBRIGHT,7,A_Light1,S_LIGHTDONE,0,0), // S_PISTOLFLASH + STATE(SPR_SHTG,0,1,A_WeaponReady,S_SGUN,0,0), // S_SGUN + STATE(SPR_SHTG,0,1,A_Lower,S_SGUNDOWN,0,0), // S_SGUNDOWN + STATE(SPR_SHTG,0,1,A_Raise,S_SGUNUP,0,0), // S_SGUNUP + STATE(SPR_SHTG,0,3,NULL,S_SGUN2,0,0), // S_SGUN1 + STATE(SPR_SHTG,0,7,A_FireShotgun,S_SGUN3,0,0), // S_SGUN2 + STATE(SPR_SHTG,1,5,NULL,S_SGUN4,0,0), // S_SGUN3 + STATE(SPR_SHTG,2,5,NULL,S_SGUN5,0,0), // S_SGUN4 + STATE(SPR_SHTG,3,4,NULL,S_SGUN6,0,0), // S_SGUN5 + STATE(SPR_SHTG,2,5,NULL,S_SGUN7,0,0), // S_SGUN6 + STATE(SPR_SHTG,1,5,NULL,S_SGUN8,0,0), // S_SGUN7 + STATE(SPR_SHTG,0,3,NULL,S_SGUN9,0,0), // S_SGUN8 + STATE(SPR_SHTG,0,7,A_ReFire,S_SGUN,0,0), // S_SGUN9 + STATE(SPR_SHTF,0 | FF_FULLBRIGHT,4,A_Light1,S_SGUNFLASH2,0,0), // S_SGUNFLASH1 + STATE(SPR_SHTF,1 | FF_FULLBRIGHT,3,A_Light2,S_LIGHTDONE,0,0), // S_SGUNFLASH2 + STATE(SPR_SHT2,0,1,A_WeaponReady,S_DSGUN,0,0), // S_DSGUN + STATE(SPR_SHT2,0,1,A_Lower,S_DSGUNDOWN,0,0), // S_DSGUNDOWN + STATE(SPR_SHT2,0,1,A_Raise,S_DSGUNUP,0,0), // S_DSGUNUP + STATE(SPR_SHT2,0,3,NULL,S_DSGUN2,0,0), // S_DSGUN1 + STATE(SPR_SHT2,0,7,A_FireShotgun2,S_DSGUN3,0,0), // S_DSGUN2 + STATE(SPR_SHT2,1,7,NULL,S_DSGUN4,0,0), // S_DSGUN3 + STATE(SPR_SHT2,2,7,A_CheckReload,S_DSGUN5,0,0), // S_DSGUN4 + STATE(SPR_SHT2,3,7,A_OpenShotgun2,S_DSGUN6,0,0), // S_DSGUN5 + STATE(SPR_SHT2,4,7,NULL,S_DSGUN7,0,0), // S_DSGUN6 + STATE(SPR_SHT2,5,7,A_LoadShotgun2,S_DSGUN8,0,0), // S_DSGUN7 + STATE(SPR_SHT2,6,6,NULL,S_DSGUN9,0,0), // S_DSGUN8 + STATE(SPR_SHT2,7,6,A_CloseShotgun2,S_DSGUN10,0,0), // S_DSGUN9 + STATE(SPR_SHT2,0,5,A_ReFire,S_DSGUN,0,0), // S_DSGUN10 + STATE(SPR_SHT2,1,7,NULL,S_DSNR2,0,0), // S_DSNR1 + STATE(SPR_SHT2,0,3,NULL,S_DSGUNDOWN,0,0), // S_DSNR2 + STATE(SPR_SHT2,8 | FF_FULLBRIGHT,5,A_Light1,S_DSGUNFLASH2,0,0), // S_DSGUNFLASH1 + STATE(SPR_SHT2,9 | FF_FULLBRIGHT,4,A_Light2,S_LIGHTDONE,0,0), // S_DSGUNFLASH2 + STATE(SPR_CHGG,0,1,A_WeaponReady,S_CHAIN,0,0), // S_CHAIN + STATE(SPR_CHGG,0,1,A_Lower,S_CHAINDOWN,0,0), // S_CHAINDOWN + STATE(SPR_CHGG,0,1,A_Raise,S_CHAINUP,0,0), // S_CHAINUP + STATE(SPR_CHGG,0,4,A_FireCGun,S_CHAIN2,0,0), // S_CHAIN1 + STATE(SPR_CHGG,1,4,A_FireCGun,S_CHAIN3,0,0), // S_CHAIN2 + STATE(SPR_CHGG,1,0,A_ReFire,S_CHAIN,0,0), // S_CHAIN3 + STATE(SPR_CHGF,0 | FF_FULLBRIGHT,5,A_Light1,S_LIGHTDONE,0,0), // S_CHAINFLASH1 + STATE(SPR_CHGF,1 | FF_FULLBRIGHT,5,A_Light2,S_LIGHTDONE,0,0), // S_CHAINFLASH2 + STATE(SPR_MISG,0,1,A_WeaponReady,S_MISSILE,0,0), // S_MISSILE + STATE(SPR_MISG,0,1,A_Lower,S_MISSILEDOWN,0,0), // S_MISSILEDOWN + STATE(SPR_MISG,0,1,A_Raise,S_MISSILEUP,0,0), // S_MISSILEUP + STATE(SPR_MISG,1,8,A_GunFlash,S_MISSILE2,0,0), // S_MISSILE1 + STATE(SPR_MISG,1,12,A_FireMissile,S_MISSILE3,0,0), // S_MISSILE2 + STATE(SPR_MISG,1,0,A_ReFire,S_MISSILE,0,0), // S_MISSILE3 + STATE(SPR_MISF,0 | FF_FULLBRIGHT,3,A_Light1,S_MISSILEFLASH2,0,0), // S_MISSILEFLASH1 + STATE(SPR_MISF,1 | FF_FULLBRIGHT,4,NULL,S_MISSILEFLASH3,0,0), // S_MISSILEFLASH2 + STATE(SPR_MISF,2 | FF_FULLBRIGHT,4,A_Light2,S_MISSILEFLASH4,0,0), // S_MISSILEFLASH3 + STATE(SPR_MISF,3 | FF_FULLBRIGHT,4,A_Light2,S_LIGHTDONE,0,0), // S_MISSILEFLASH4 + STATE(SPR_SAWG,2,4,A_WeaponReady,S_SAWB,0,0), // S_SAW + STATE(SPR_SAWG,3,4,A_WeaponReady,S_SAW,0,0), // S_SAWB + STATE(SPR_SAWG,2,1,A_Lower,S_SAWDOWN,0,0), // S_SAWDOWN + STATE(SPR_SAWG,2,1,A_Raise,S_SAWUP,0,0), // S_SAWUP + STATE(SPR_SAWG,0,4,A_Saw,S_SAW2,0,0), // S_SAW1 + STATE(SPR_SAWG,1,4,A_Saw,S_SAW3,0,0), // S_SAW2 + STATE(SPR_SAWG,1,0,A_ReFire,S_SAW,0,0), // S_SAW3 + STATE(SPR_PLSG,0,1,A_WeaponReady,S_PLASMA,0,0), // S_PLASMA + STATE(SPR_PLSG,0,1,A_Lower,S_PLASMADOWN,0,0), // S_PLASMADOWN + STATE(SPR_PLSG,0,1,A_Raise,S_PLASMAUP,0,0), // S_PLASMAUP + STATE(SPR_PLSG,0,3,A_FirePlasma,S_PLASMA2,0,0), // S_PLASMA1 + STATE(SPR_PLSG,1,20,A_ReFire,S_PLASMA,0,0), // S_PLASMA2 + STATE(SPR_PLSF,0 | FF_FULLBRIGHT,4,A_Light1,S_LIGHTDONE,0,0), // S_PLASMAFLASH1 + STATE(SPR_PLSF,1 | FF_FULLBRIGHT,4,A_Light1,S_LIGHTDONE,0,0), // S_PLASMAFLASH2 + STATE(SPR_BFGG,0,1,A_WeaponReady,S_BFG,0,0), // S_BFG + STATE(SPR_BFGG,0,1,A_Lower,S_BFGDOWN,0,0), // S_BFGDOWN + STATE(SPR_BFGG,0,1,A_Raise,S_BFGUP,0,0), // S_BFGUP + STATE(SPR_BFGG,0,20,A_BFGsound,S_BFG2,0,0), // S_BFG1 + STATE(SPR_BFGG,1,10,A_GunFlash,S_BFG3,0,0), // S_BFG2 + STATE(SPR_BFGG,1,10,A_FireBFG,S_BFG4,0,0), // S_BFG3 + STATE(SPR_BFGG,1,20,A_ReFire,S_BFG,0,0), // S_BFG4 + STATE(SPR_BFGF,0 | FF_FULLBRIGHT,11,A_Light1,S_BFGFLASH2,0,0), // S_BFGFLASH1 + STATE(SPR_BFGF,1 | FF_FULLBRIGHT,6,A_Light2,S_LIGHTDONE,0,0), // S_BFGFLASH2 + STATE(SPR_BLUD,2,8,NULL,S_BLOOD2,0,0), // S_BLOOD1 + STATE(SPR_BLUD,1,8,NULL,S_BLOOD3,0,0), // S_BLOOD2 + STATE(SPR_BLUD,0,8,NULL,S_NULL,0,0), // S_BLOOD3 + STATE(SPR_PUFF,0 | FF_FULLBRIGHT,4,NULL,S_PUFF2,0,0), // S_PUFF1 + STATE(SPR_PUFF,1,4,NULL,S_PUFF3,0,0), // S_PUFF2 + STATE(SPR_PUFF,2,4,NULL,S_PUFF4,0,0), // S_PUFF3 + STATE(SPR_PUFF,3,4,NULL,S_NULL,0,0), // S_PUFF4 + STATE(SPR_BAL1,0 | FF_FULLBRIGHT,4,NULL,S_TBALL2,0,0), // S_TBALL1 + STATE(SPR_BAL1,1 | FF_FULLBRIGHT,4,NULL,S_TBALL1,0,0), // S_TBALL2 + STATE(SPR_BAL1,2 | FF_FULLBRIGHT,6,NULL,S_TBALLX2,0,0), // S_TBALLX1 + STATE(SPR_BAL1,3 | FF_FULLBRIGHT,6,NULL,S_TBALLX3,0,0), // S_TBALLX2 + STATE(SPR_BAL1,4 | FF_FULLBRIGHT,6,NULL,S_NULL,0,0), // S_TBALLX3 + STATE(SPR_BAL2,0 | FF_FULLBRIGHT,4,NULL,S_RBALL2,0,0), // S_RBALL1 + STATE(SPR_BAL2,1 | FF_FULLBRIGHT,4,NULL,S_RBALL1,0,0), // S_RBALL2 + STATE(SPR_BAL2,2 | FF_FULLBRIGHT,6,NULL,S_RBALLX2,0,0), // S_RBALLX1 + STATE(SPR_BAL2,3 | FF_FULLBRIGHT,6,NULL,S_RBALLX3,0,0), // S_RBALLX2 + STATE(SPR_BAL2,4 | FF_FULLBRIGHT,6,NULL,S_NULL,0,0), // S_RBALLX3 + STATE(SPR_PLSS,0 | FF_FULLBRIGHT,6,NULL,S_PLASBALL2,0,0), // S_PLASBALL + STATE(SPR_PLSS,1 | FF_FULLBRIGHT,6,NULL,S_PLASBALL,0,0), // S_PLASBALL2 + STATE(SPR_PLSE,0 | FF_FULLBRIGHT,4,NULL,S_PLASEXP2,0,0), // S_PLASEXP + STATE(SPR_PLSE,1 | FF_FULLBRIGHT,4,NULL,S_PLASEXP3,0,0), // S_PLASEXP2 + STATE(SPR_PLSE,2 | FF_FULLBRIGHT,4,NULL,S_PLASEXP4,0,0), // S_PLASEXP3 + STATE(SPR_PLSE,3 | FF_FULLBRIGHT,4,NULL,S_PLASEXP5,0,0), // S_PLASEXP4 + STATE(SPR_PLSE,4 | FF_FULLBRIGHT,4,NULL,S_NULL,0,0), // S_PLASEXP5 + STATE(SPR_MISL,0 | FF_FULLBRIGHT,1,NULL,S_ROCKET,0,0), // S_ROCKET + STATE(SPR_BFS1,0 | FF_FULLBRIGHT,4,NULL,S_BFGSHOT2,0,0), // S_BFGSHOT + STATE(SPR_BFS1,1 | FF_FULLBRIGHT,4,NULL,S_BFGSHOT,0,0), // S_BFGSHOT2 + STATE(SPR_BFE1,0 | FF_FULLBRIGHT,8,NULL,S_BFGLAND2,0,0), // S_BFGLAND + STATE(SPR_BFE1,1 | FF_FULLBRIGHT,8,NULL,S_BFGLAND3,0,0), // S_BFGLAND2 + STATE(SPR_BFE1,2 | FF_FULLBRIGHT,8,A_BFGSpray,S_BFGLAND4,0,0), // S_BFGLAND3 + STATE(SPR_BFE1,3 | FF_FULLBRIGHT,8,NULL,S_BFGLAND5,0,0), // S_BFGLAND4 + STATE(SPR_BFE1,4 | FF_FULLBRIGHT,8,NULL,S_BFGLAND6,0,0), // S_BFGLAND5 + STATE(SPR_BFE1,5 | FF_FULLBRIGHT,8,NULL,S_NULL,0,0), // S_BFGLAND6 + STATE(SPR_BFE2,0 | FF_FULLBRIGHT,8,NULL,S_BFGEXP2,0,0), // S_BFGEXP + STATE(SPR_BFE2,1 | FF_FULLBRIGHT,8,NULL,S_BFGEXP3,0,0), // S_BFGEXP2 + STATE(SPR_BFE2,2 | FF_FULLBRIGHT,8,NULL,S_BFGEXP4,0,0), // S_BFGEXP3 + STATE(SPR_BFE2,3 | FF_FULLBRIGHT,8,NULL,S_NULL,0,0), // S_BFGEXP4 + STATE(SPR_MISL,1 | FF_FULLBRIGHT,8,A_Explode,S_EXPLODE2,0,0), // S_EXPLODE1 + STATE(SPR_MISL,2 | FF_FULLBRIGHT,6,NULL,S_EXPLODE3,0,0), // S_EXPLODE2 + STATE(SPR_MISL,3 | FF_FULLBRIGHT,4,NULL,S_NULL,0,0), // S_EXPLODE3 + STATE(SPR_TFOG,0 | FF_FULLBRIGHT,6,NULL,S_TFOG01,0,0), // S_TFOG + STATE(SPR_TFOG,1 | FF_FULLBRIGHT,6,NULL,S_TFOG02,0,0), // S_TFOG01 + STATE(SPR_TFOG,0 | FF_FULLBRIGHT,6,NULL,S_TFOG2,0,0), // S_TFOG02 + STATE(SPR_TFOG,1 | FF_FULLBRIGHT,6,NULL,S_TFOG3,0,0), // S_TFOG2 + STATE(SPR_TFOG,2 | FF_FULLBRIGHT,6,NULL,S_TFOG4,0,0), // S_TFOG3 + STATE(SPR_TFOG,3 | FF_FULLBRIGHT,6,NULL,S_TFOG5,0,0), // S_TFOG4 + STATE(SPR_TFOG,4 | FF_FULLBRIGHT,6,NULL,S_TFOG6,0,0), // S_TFOG5 + STATE(SPR_TFOG,5 | FF_FULLBRIGHT,6,NULL,S_TFOG7,0,0), // S_TFOG6 + STATE(SPR_TFOG,6 | FF_FULLBRIGHT,6,NULL,S_TFOG8,0,0), // S_TFOG7 + STATE(SPR_TFOG,7 | FF_FULLBRIGHT,6,NULL,S_TFOG9,0,0), // S_TFOG8 + STATE(SPR_TFOG,8 | FF_FULLBRIGHT,6,NULL,S_TFOG10,0,0), // S_TFOG9 + STATE(SPR_TFOG,9 | FF_FULLBRIGHT,6,NULL,S_NULL,0,0), // S_TFOG10 + STATE(SPR_IFOG,0 | FF_FULLBRIGHT,6,NULL,S_IFOG01,0,0), // S_IFOG + STATE(SPR_IFOG,1 | FF_FULLBRIGHT,6,NULL,S_IFOG02,0,0), // S_IFOG01 + STATE(SPR_IFOG,0 | FF_FULLBRIGHT,6,NULL,S_IFOG2,0,0), // S_IFOG02 + STATE(SPR_IFOG,1 | FF_FULLBRIGHT,6,NULL,S_IFOG3,0,0), // S_IFOG2 + STATE(SPR_IFOG,2 | FF_FULLBRIGHT,6,NULL,S_IFOG4,0,0), // S_IFOG3 + STATE(SPR_IFOG,3 | FF_FULLBRIGHT,6,NULL,S_IFOG5,0,0), // S_IFOG4 + STATE(SPR_IFOG,4 | FF_FULLBRIGHT,6,NULL,S_NULL,0,0), // S_IFOG5 + STATE(SPR_PLAY,0,-1,NULL,S_NULL,0,0), // S_PLAY + STATE(SPR_PLAY,0,4,NULL,S_PLAY_RUN2,0,0), // S_PLAY_RUN1 + STATE(SPR_PLAY,1,4,NULL,S_PLAY_RUN3,0,0), // S_PLAY_RUN2 + STATE(SPR_PLAY,2,4,NULL,S_PLAY_RUN4,0,0), // S_PLAY_RUN3 + STATE(SPR_PLAY,3,4,NULL,S_PLAY_RUN1,0,0), // S_PLAY_RUN4 + STATE(SPR_PLAY,4,12,NULL,S_PLAY,0,0), // S_PLAY_ATK1 + STATE(SPR_PLAY,5 | FF_FULLBRIGHT,6,NULL,S_PLAY_ATK1,0,0), // S_PLAY_ATK2 + STATE(SPR_PLAY,6,4,NULL,S_PLAY_PAIN2,0,0), // S_PLAY_PAIN + STATE(SPR_PLAY,6,4,A_Pain,S_PLAY,0,0), // S_PLAY_PAIN2 + STATE(SPR_PLAY,7,10,NULL,S_PLAY_DIE2,0,0), // S_PLAY_DIE1 + STATE(SPR_PLAY,8,10,A_PlayerScream,S_PLAY_DIE3,0,0), // S_PLAY_DIE2 + STATE(SPR_PLAY,9,10,A_Fall,S_PLAY_DIE4,0,0), // S_PLAY_DIE3 + STATE(SPR_PLAY,10,10,NULL,S_PLAY_DIE5,0,0), // S_PLAY_DIE4 + STATE(SPR_PLAY,11,10,NULL,S_PLAY_DIE6,0,0), // S_PLAY_DIE5 + STATE(SPR_PLAY,12,10,NULL,S_PLAY_DIE7,0,0), // S_PLAY_DIE6 + STATE(SPR_PLAY,13,-1,NULL,S_NULL,0,0), // S_PLAY_DIE7 + STATE(SPR_PLAY,14,5,NULL,S_PLAY_XDIE2,0,0), // S_PLAY_XDIE1 + STATE(SPR_PLAY,15,5,A_XScream,S_PLAY_XDIE3,0,0), // S_PLAY_XDIE2 + STATE(SPR_PLAY,16,5,A_Fall,S_PLAY_XDIE4,0,0), // S_PLAY_XDIE3 + STATE(SPR_PLAY,17,5,NULL,S_PLAY_XDIE5,0,0), // S_PLAY_XDIE4 + STATE(SPR_PLAY,18,5,NULL,S_PLAY_XDIE6,0,0), // S_PLAY_XDIE5 + STATE(SPR_PLAY,19,5,NULL,S_PLAY_XDIE7,0,0), // S_PLAY_XDIE6 + STATE(SPR_PLAY,20,5,NULL,S_PLAY_XDIE8,0,0), // S_PLAY_XDIE7 + STATE(SPR_PLAY,21,5,NULL,S_PLAY_XDIE9,0,0), // S_PLAY_XDIE8 + STATE(SPR_PLAY,22,-1,NULL,S_NULL,0,0), // S_PLAY_XDIE9 + STATE(SPR_POSS,0,10,A_Look,S_POSS_STND2,0,0), // S_POSS_STND + STATE(SPR_POSS,1,10,A_Look,S_POSS_STND,0,0), // S_POSS_STND2 + STATE(SPR_POSS,0,4,A_Chase,S_POSS_RUN2,0,0), // S_POSS_RUN1 + STATE(SPR_POSS,0,4,A_Chase,S_POSS_RUN3,0,0), // S_POSS_RUN2 + STATE(SPR_POSS,1,4,A_Chase,S_POSS_RUN4,0,0), // S_POSS_RUN3 + STATE(SPR_POSS,1,4,A_Chase,S_POSS_RUN5,0,0), // S_POSS_RUN4 + STATE(SPR_POSS,2,4,A_Chase,S_POSS_RUN6,0,0), // S_POSS_RUN5 + STATE(SPR_POSS,2,4,A_Chase,S_POSS_RUN7,0,0), // S_POSS_RUN6 + STATE(SPR_POSS,3,4,A_Chase,S_POSS_RUN8,0,0), // S_POSS_RUN7 + STATE(SPR_POSS,3,4,A_Chase,S_POSS_RUN1,0,0), // S_POSS_RUN8 + STATE(SPR_POSS,4,10,A_FaceTarget,S_POSS_ATK2,0,0), // S_POSS_ATK1 + STATE(SPR_POSS,5,8,A_PosAttack,S_POSS_ATK3,0,0), // S_POSS_ATK2 + STATE(SPR_POSS,4,8,NULL,S_POSS_RUN1,0,0), // S_POSS_ATK3 + STATE(SPR_POSS,6,3,NULL,S_POSS_PAIN2,0,0), // S_POSS_PAIN + STATE(SPR_POSS,6,3,A_Pain,S_POSS_RUN1,0,0), // S_POSS_PAIN2 + STATE(SPR_POSS,7,5,NULL,S_POSS_DIE2,0,0), // S_POSS_DIE1 + STATE(SPR_POSS,8,5,A_Scream,S_POSS_DIE3,0,0), // S_POSS_DIE2 + STATE(SPR_POSS,9,5,A_Fall,S_POSS_DIE4,0,0), // S_POSS_DIE3 + STATE(SPR_POSS,10,5,NULL,S_POSS_DIE5,0,0), // S_POSS_DIE4 + STATE(SPR_POSS,11,-1,NULL,S_NULL,0,0), // S_POSS_DIE5 + STATE(SPR_POSS,12,5,NULL,S_POSS_XDIE2,0,0), // S_POSS_XDIE1 + STATE(SPR_POSS,13,5,A_XScream,S_POSS_XDIE3,0,0), // S_POSS_XDIE2 + STATE(SPR_POSS,14,5,A_Fall,S_POSS_XDIE4,0,0), // S_POSS_XDIE3 + STATE(SPR_POSS,15,5,NULL,S_POSS_XDIE5,0,0), // S_POSS_XDIE4 + STATE(SPR_POSS,16,5,NULL,S_POSS_XDIE6,0,0), // S_POSS_XDIE5 + STATE(SPR_POSS,17,5,NULL,S_POSS_XDIE7,0,0), // S_POSS_XDIE6 + STATE(SPR_POSS,18,5,NULL,S_POSS_XDIE8,0,0), // S_POSS_XDIE7 + STATE(SPR_POSS,19,5,NULL,S_POSS_XDIE9,0,0), // S_POSS_XDIE8 + STATE(SPR_POSS,20,-1,NULL,S_NULL,0,0), // S_POSS_XDIE9 + STATE(SPR_POSS,10,5,NULL,S_POSS_RAISE2,0,0), // S_POSS_RAISE1 + STATE(SPR_POSS,9,5,NULL,S_POSS_RAISE3,0,0), // S_POSS_RAISE2 + STATE(SPR_POSS,8,5,NULL,S_POSS_RAISE4,0,0), // S_POSS_RAISE3 + STATE(SPR_POSS,7,5,NULL,S_POSS_RUN1,0,0), // S_POSS_RAISE4 + STATE(SPR_SPOS,0,10,A_Look,S_SPOS_STND2,0,0), // S_SPOS_STND + STATE(SPR_SPOS,1,10,A_Look,S_SPOS_STND,0,0), // S_SPOS_STND2 + STATE(SPR_SPOS,0,3,A_Chase,S_SPOS_RUN2,0,0), // S_SPOS_RUN1 + STATE(SPR_SPOS,0,3,A_Chase,S_SPOS_RUN3,0,0), // S_SPOS_RUN2 + STATE(SPR_SPOS,1,3,A_Chase,S_SPOS_RUN4,0,0), // S_SPOS_RUN3 + STATE(SPR_SPOS,1,3,A_Chase,S_SPOS_RUN5,0,0), // S_SPOS_RUN4 + STATE(SPR_SPOS,2,3,A_Chase,S_SPOS_RUN6,0,0), // S_SPOS_RUN5 + STATE(SPR_SPOS,2,3,A_Chase,S_SPOS_RUN7,0,0), // S_SPOS_RUN6 + STATE(SPR_SPOS,3,3,A_Chase,S_SPOS_RUN8,0,0), // S_SPOS_RUN7 + STATE(SPR_SPOS,3,3,A_Chase,S_SPOS_RUN1,0,0), // S_SPOS_RUN8 + STATE(SPR_SPOS,4,10,A_FaceTarget,S_SPOS_ATK2,0,0), // S_SPOS_ATK1 + STATE(SPR_SPOS,5 | FF_FULLBRIGHT,10,A_SPosAttack,S_SPOS_ATK3,0,0), // S_SPOS_ATK2 + STATE(SPR_SPOS,4,10,NULL,S_SPOS_RUN1,0,0), // S_SPOS_ATK3 + STATE(SPR_SPOS,6,3,NULL,S_SPOS_PAIN2,0,0), // S_SPOS_PAIN + STATE(SPR_SPOS,6,3,A_Pain,S_SPOS_RUN1,0,0), // S_SPOS_PAIN2 + STATE(SPR_SPOS,7,5,NULL,S_SPOS_DIE2,0,0), // S_SPOS_DIE1 + STATE(SPR_SPOS,8,5,A_Scream,S_SPOS_DIE3,0,0), // S_SPOS_DIE2 + STATE(SPR_SPOS,9,5,A_Fall,S_SPOS_DIE4,0,0), // S_SPOS_DIE3 + STATE(SPR_SPOS,10,5,NULL,S_SPOS_DIE5,0,0), // S_SPOS_DIE4 + STATE(SPR_SPOS,11,-1,NULL,S_NULL,0,0), // S_SPOS_DIE5 + STATE(SPR_SPOS,12,5,NULL,S_SPOS_XDIE2,0,0), // S_SPOS_XDIE1 + STATE(SPR_SPOS,13,5,A_XScream,S_SPOS_XDIE3,0,0), // S_SPOS_XDIE2 + STATE(SPR_SPOS,14,5,A_Fall,S_SPOS_XDIE4,0,0), // S_SPOS_XDIE3 + STATE(SPR_SPOS,15,5,NULL,S_SPOS_XDIE5,0,0), // S_SPOS_XDIE4 + STATE(SPR_SPOS,16,5,NULL,S_SPOS_XDIE6,0,0), // S_SPOS_XDIE5 + STATE(SPR_SPOS,17,5,NULL,S_SPOS_XDIE7,0,0), // S_SPOS_XDIE6 + STATE(SPR_SPOS,18,5,NULL,S_SPOS_XDIE8,0,0), // S_SPOS_XDIE7 + STATE(SPR_SPOS,19,5,NULL,S_SPOS_XDIE9,0,0), // S_SPOS_XDIE8 + STATE(SPR_SPOS,20,-1,NULL,S_NULL,0,0), // S_SPOS_XDIE9 + STATE(SPR_SPOS,11,5,NULL,S_SPOS_RAISE2,0,0), // S_SPOS_RAISE1 + STATE(SPR_SPOS,10,5,NULL,S_SPOS_RAISE3,0,0), // S_SPOS_RAISE2 + STATE(SPR_SPOS,9,5,NULL,S_SPOS_RAISE4,0,0), // S_SPOS_RAISE3 + STATE(SPR_SPOS,8,5,NULL,S_SPOS_RAISE5,0,0), // S_SPOS_RAISE4 + STATE(SPR_SPOS,7,5,NULL,S_SPOS_RUN1,0,0), // S_SPOS_RAISE5 + STATE(SPR_VILE,0,10,A_Look,S_VILE_STND2,0,0), // S_VILE_STND + STATE(SPR_VILE,1,10,A_Look,S_VILE_STND,0,0), // S_VILE_STND2 + STATE(SPR_VILE,0,2,A_VileChase,S_VILE_RUN2,0,0), // S_VILE_RUN1 + STATE(SPR_VILE,0,2,A_VileChase,S_VILE_RUN3,0,0), // S_VILE_RUN2 + STATE(SPR_VILE,1,2,A_VileChase,S_VILE_RUN4,0,0), // S_VILE_RUN3 + STATE(SPR_VILE,1,2,A_VileChase,S_VILE_RUN5,0,0), // S_VILE_RUN4 + STATE(SPR_VILE,2,2,A_VileChase,S_VILE_RUN6,0,0), // S_VILE_RUN5 + STATE(SPR_VILE,2,2,A_VileChase,S_VILE_RUN7,0,0), // S_VILE_RUN6 + STATE(SPR_VILE,3,2,A_VileChase,S_VILE_RUN8,0,0), // S_VILE_RUN7 + STATE(SPR_VILE,3,2,A_VileChase,S_VILE_RUN9,0,0), // S_VILE_RUN8 + STATE(SPR_VILE,4,2,A_VileChase,S_VILE_RUN10,0,0), // S_VILE_RUN9 + STATE(SPR_VILE,4,2,A_VileChase,S_VILE_RUN11,0,0), // S_VILE_RUN10 + STATE(SPR_VILE,5,2,A_VileChase,S_VILE_RUN12,0,0), // S_VILE_RUN11 + STATE(SPR_VILE,5,2,A_VileChase,S_VILE_RUN1,0,0), // S_VILE_RUN12 + STATE(SPR_VILE,6 | FF_FULLBRIGHT,0,A_VileStart,S_VILE_ATK2,0,0), // S_VILE_ATK1 + STATE(SPR_VILE,6 | FF_FULLBRIGHT,10,A_FaceTarget,S_VILE_ATK3,0,0), // S_VILE_ATK2 + STATE(SPR_VILE,7 | FF_FULLBRIGHT,8,A_VileTarget,S_VILE_ATK4,0,0), // S_VILE_ATK3 + STATE(SPR_VILE,8 | FF_FULLBRIGHT,8,A_FaceTarget,S_VILE_ATK5,0,0), // S_VILE_ATK4 + STATE(SPR_VILE,9 | FF_FULLBRIGHT,8,A_FaceTarget,S_VILE_ATK6,0,0), // S_VILE_ATK5 + STATE(SPR_VILE,10 | FF_FULLBRIGHT,8,A_FaceTarget,S_VILE_ATK7,0,0), // S_VILE_ATK6 + STATE(SPR_VILE,11 | FF_FULLBRIGHT,8,A_FaceTarget,S_VILE_ATK8,0,0), // S_VILE_ATK7 + STATE(SPR_VILE,12 | FF_FULLBRIGHT,8,A_FaceTarget,S_VILE_ATK9,0,0), // S_VILE_ATK8 + STATE(SPR_VILE,13 | FF_FULLBRIGHT,8,A_FaceTarget,S_VILE_ATK10,0,0), // S_VILE_ATK9 + STATE(SPR_VILE,14 | FF_FULLBRIGHT,8,A_VileAttack,S_VILE_ATK11,0,0), // S_VILE_ATK10 + STATE(SPR_VILE,15 | FF_FULLBRIGHT,20,NULL,S_VILE_RUN1,0,0), // S_VILE_ATK11 + STATE(SPR_VILE,26 | FF_FULLBRIGHT,10,NULL,S_VILE_HEAL2,0,0), // S_VILE_HEAL1 + STATE(SPR_VILE,27 | FF_FULLBRIGHT,10,NULL,S_VILE_HEAL3,0,0), // S_VILE_HEAL2 + STATE(SPR_VILE,28 | FF_FULLBRIGHT,10,NULL,S_VILE_RUN1,0,0), // S_VILE_HEAL3 + STATE(SPR_VILE,16,5,NULL,S_VILE_PAIN2,0,0), // S_VILE_PAIN + STATE(SPR_VILE,16,5,A_Pain,S_VILE_RUN1,0,0), // S_VILE_PAIN2 + STATE(SPR_VILE,16,7,NULL,S_VILE_DIE2,0,0), // S_VILE_DIE1 + STATE(SPR_VILE,17,7,A_Scream,S_VILE_DIE3,0,0), // S_VILE_DIE2 + STATE(SPR_VILE,18,7,A_Fall,S_VILE_DIE4,0,0), // S_VILE_DIE3 + STATE(SPR_VILE,19,7,NULL,S_VILE_DIE5,0,0), // S_VILE_DIE4 + STATE(SPR_VILE,20,7,NULL,S_VILE_DIE6,0,0), // S_VILE_DIE5 + STATE(SPR_VILE,21,7,NULL,S_VILE_DIE7,0,0), // S_VILE_DIE6 + STATE(SPR_VILE,22,7,NULL,S_VILE_DIE8,0,0), // S_VILE_DIE7 + STATE(SPR_VILE,23,5,NULL,S_VILE_DIE9,0,0), // S_VILE_DIE8 + STATE(SPR_VILE,24,5,NULL,S_VILE_DIE10,0,0), // S_VILE_DIE9 + STATE(SPR_VILE,25,-1,NULL,S_NULL,0,0), // S_VILE_DIE10 + STATE(SPR_FIRE,0 | FF_FULLBRIGHT,2,A_StartFire,S_FIRE2,0,0), // S_FIRE1 + STATE(SPR_FIRE,1 | FF_FULLBRIGHT,2,A_Fire,S_FIRE3,0,0), // S_FIRE2 + STATE(SPR_FIRE,0 | FF_FULLBRIGHT,2,A_Fire,S_FIRE4,0,0), // S_FIRE3 + STATE(SPR_FIRE,1 | FF_FULLBRIGHT,2,A_Fire,S_FIRE5,0,0), // S_FIRE4 + STATE(SPR_FIRE,2 | FF_FULLBRIGHT,2,A_FireCrackle,S_FIRE6,0,0), // S_FIRE5 + STATE(SPR_FIRE,1 | FF_FULLBRIGHT,2,A_Fire,S_FIRE7,0,0), // S_FIRE6 + STATE(SPR_FIRE,2 | FF_FULLBRIGHT,2,A_Fire,S_FIRE8,0,0), // S_FIRE7 + STATE(SPR_FIRE,1 | FF_FULLBRIGHT,2,A_Fire,S_FIRE9,0,0), // S_FIRE8 + STATE(SPR_FIRE,2 | FF_FULLBRIGHT,2,A_Fire,S_FIRE10,0,0), // S_FIRE9 + STATE(SPR_FIRE,3 | FF_FULLBRIGHT,2,A_Fire,S_FIRE11,0,0), // S_FIRE10 + STATE(SPR_FIRE,2 | FF_FULLBRIGHT,2,A_Fire,S_FIRE12,0,0), // S_FIRE11 + STATE(SPR_FIRE,3 | FF_FULLBRIGHT,2,A_Fire,S_FIRE13,0,0), // S_FIRE12 + STATE(SPR_FIRE,2 | FF_FULLBRIGHT,2,A_Fire,S_FIRE14,0,0), // S_FIRE13 + STATE(SPR_FIRE,3 | FF_FULLBRIGHT,2,A_Fire,S_FIRE15,0,0), // S_FIRE14 + STATE(SPR_FIRE,4 | FF_FULLBRIGHT,2,A_Fire,S_FIRE16,0,0), // S_FIRE15 + STATE(SPR_FIRE,3 | FF_FULLBRIGHT,2,A_Fire,S_FIRE17,0,0), // S_FIRE16 + STATE(SPR_FIRE,4 | FF_FULLBRIGHT,2,A_Fire,S_FIRE18,0,0), // S_FIRE17 + STATE(SPR_FIRE,3 | FF_FULLBRIGHT,2,A_Fire,S_FIRE19,0,0), // S_FIRE18 + STATE(SPR_FIRE,4 | FF_FULLBRIGHT,2,A_FireCrackle,S_FIRE20,0,0), // S_FIRE19 + STATE(SPR_FIRE,5 | FF_FULLBRIGHT,2,A_Fire,S_FIRE21,0,0), // S_FIRE20 + STATE(SPR_FIRE,4 | FF_FULLBRIGHT,2,A_Fire,S_FIRE22,0,0), // S_FIRE21 + STATE(SPR_FIRE,5 | FF_FULLBRIGHT,2,A_Fire,S_FIRE23,0,0), // S_FIRE22 + STATE(SPR_FIRE,4 | FF_FULLBRIGHT,2,A_Fire,S_FIRE24,0,0), // S_FIRE23 + STATE(SPR_FIRE,5 | FF_FULLBRIGHT,2,A_Fire,S_FIRE25,0,0), // S_FIRE24 + STATE(SPR_FIRE,6 | FF_FULLBRIGHT,2,A_Fire,S_FIRE26,0,0), // S_FIRE25 + STATE(SPR_FIRE,7 | FF_FULLBRIGHT,2,A_Fire,S_FIRE27,0,0), // S_FIRE26 + STATE(SPR_FIRE,6 | FF_FULLBRIGHT,2,A_Fire,S_FIRE28,0,0), // S_FIRE27 + STATE(SPR_FIRE,7 | FF_FULLBRIGHT,2,A_Fire,S_FIRE29,0,0), // S_FIRE28 + STATE(SPR_FIRE,6 | FF_FULLBRIGHT,2,A_Fire,S_FIRE30,0,0), // S_FIRE29 + STATE(SPR_FIRE,7 | FF_FULLBRIGHT,2,A_Fire,S_NULL,0,0), // S_FIRE30 + STATE(SPR_PUFF,1,4,NULL,S_SMOKE2,0,0), // S_SMOKE1 + STATE(SPR_PUFF,2,4,NULL,S_SMOKE3,0,0), // S_SMOKE2 + STATE(SPR_PUFF,1,4,NULL,S_SMOKE4,0,0), // S_SMOKE3 + STATE(SPR_PUFF,2,4,NULL,S_SMOKE5,0,0), // S_SMOKE4 + STATE(SPR_PUFF,3,4,NULL,S_NULL,0,0), // S_SMOKE5 + STATE(SPR_FATB,0 | FF_FULLBRIGHT,2,A_Tracer,S_TRACER2,0,0), // S_TRACER + STATE(SPR_FATB,1 | FF_FULLBRIGHT,2,A_Tracer,S_TRACER,0,0), // S_TRACER2 + STATE(SPR_FBXP,0 | FF_FULLBRIGHT,8,NULL,S_TRACEEXP2,0,0), // S_TRACEEXP1 + STATE(SPR_FBXP,1 | FF_FULLBRIGHT,6,NULL,S_TRACEEXP3,0,0), // S_TRACEEXP2 + STATE(SPR_FBXP,2 | FF_FULLBRIGHT,4,NULL,S_NULL,0,0), // S_TRACEEXP3 + STATE(SPR_SKEL,0,10,A_Look,S_SKEL_STND2,0,0), // S_SKEL_STND + STATE(SPR_SKEL,1,10,A_Look,S_SKEL_STND,0,0), // S_SKEL_STND2 + STATE(SPR_SKEL,0,2,A_Chase,S_SKEL_RUN2,0,0), // S_SKEL_RUN1 + STATE(SPR_SKEL,0,2,A_Chase,S_SKEL_RUN3,0,0), // S_SKEL_RUN2 + STATE(SPR_SKEL,1,2,A_Chase,S_SKEL_RUN4,0,0), // S_SKEL_RUN3 + STATE(SPR_SKEL,1,2,A_Chase,S_SKEL_RUN5,0,0), // S_SKEL_RUN4 + STATE(SPR_SKEL,2,2,A_Chase,S_SKEL_RUN6,0,0), // S_SKEL_RUN5 + STATE(SPR_SKEL,2,2,A_Chase,S_SKEL_RUN7,0,0), // S_SKEL_RUN6 + STATE(SPR_SKEL,3,2,A_Chase,S_SKEL_RUN8,0,0), // S_SKEL_RUN7 + STATE(SPR_SKEL,3,2,A_Chase,S_SKEL_RUN9,0,0), // S_SKEL_RUN8 + STATE(SPR_SKEL,4,2,A_Chase,S_SKEL_RUN10,0,0), // S_SKEL_RUN9 + STATE(SPR_SKEL,4,2,A_Chase,S_SKEL_RUN11,0,0), // S_SKEL_RUN10 + STATE(SPR_SKEL,5,2,A_Chase,S_SKEL_RUN12,0,0), // S_SKEL_RUN11 + STATE(SPR_SKEL,5,2,A_Chase,S_SKEL_RUN1,0,0), // S_SKEL_RUN12 + STATE(SPR_SKEL,6,0,A_FaceTarget,S_SKEL_FIST2,0,0), // S_SKEL_FIST1 + STATE(SPR_SKEL,6,6,A_SkelWhoosh,S_SKEL_FIST3,0,0), // S_SKEL_FIST2 + STATE(SPR_SKEL,7,6,A_FaceTarget,S_SKEL_FIST4,0,0), // S_SKEL_FIST3 + STATE(SPR_SKEL,8,6,A_SkelFist,S_SKEL_RUN1,0,0), // S_SKEL_FIST4 + STATE(SPR_SKEL,9 | FF_FULLBRIGHT,0,A_FaceTarget,S_SKEL_MISS2,0,0), // S_SKEL_MISS1 + STATE(SPR_SKEL,9 | FF_FULLBRIGHT,10,A_FaceTarget,S_SKEL_MISS3,0,0), // S_SKEL_MISS2 + STATE(SPR_SKEL,10,10,A_SkelMissile,S_SKEL_MISS4,0,0), // S_SKEL_MISS3 + STATE(SPR_SKEL,10,10,A_FaceTarget,S_SKEL_RUN1,0,0), // S_SKEL_MISS4 + STATE(SPR_SKEL,11,5,NULL,S_SKEL_PAIN2,0,0), // S_SKEL_PAIN + STATE(SPR_SKEL,11,5,A_Pain,S_SKEL_RUN1,0,0), // S_SKEL_PAIN2 + STATE(SPR_SKEL,11,7,NULL,S_SKEL_DIE2,0,0), // S_SKEL_DIE1 + STATE(SPR_SKEL,12,7,NULL,S_SKEL_DIE3,0,0), // S_SKEL_DIE2 + STATE(SPR_SKEL,13,7,A_Scream,S_SKEL_DIE4,0,0), // S_SKEL_DIE3 + STATE(SPR_SKEL,14,7,A_Fall,S_SKEL_DIE5,0,0), // S_SKEL_DIE4 + STATE(SPR_SKEL,15,7,NULL,S_SKEL_DIE6,0,0), // S_SKEL_DIE5 + STATE(SPR_SKEL,16,-1,NULL,S_NULL,0,0), // S_SKEL_DIE6 + STATE(SPR_SKEL,16,5,NULL,S_SKEL_RAISE2,0,0), // S_SKEL_RAISE1 + STATE(SPR_SKEL,15,5,NULL,S_SKEL_RAISE3,0,0), // S_SKEL_RAISE2 + STATE(SPR_SKEL,14,5,NULL,S_SKEL_RAISE4,0,0), // S_SKEL_RAISE3 + STATE(SPR_SKEL,13,5,NULL,S_SKEL_RAISE5,0,0), // S_SKEL_RAISE4 + STATE(SPR_SKEL,12,5,NULL,S_SKEL_RAISE6,0,0), // S_SKEL_RAISE5 + STATE(SPR_SKEL,11,5,NULL,S_SKEL_RUN1,0,0), // S_SKEL_RAISE6 + STATE(SPR_MANF,0 | FF_FULLBRIGHT,4,NULL,S_FATSHOT2,0,0), // S_FATSHOT1 + STATE(SPR_MANF,1 | FF_FULLBRIGHT,4,NULL,S_FATSHOT1,0,0), // S_FATSHOT2 + STATE(SPR_MISL,1 | FF_FULLBRIGHT,8,NULL,S_FATSHOTX2,0,0), // S_FATSHOTX1 + STATE(SPR_MISL,2 | FF_FULLBRIGHT,6,NULL,S_FATSHOTX3,0,0), // S_FATSHOTX2 + STATE(SPR_MISL,3 | FF_FULLBRIGHT,4,NULL,S_NULL,0,0), // S_FATSHOTX3 + STATE(SPR_FATT,0,15,A_Look,S_FATT_STND2,0,0), // S_FATT_STND + STATE(SPR_FATT,1,15,A_Look,S_FATT_STND,0,0), // S_FATT_STND2 + STATE(SPR_FATT,0,4,A_Chase,S_FATT_RUN2,0,0), // S_FATT_RUN1 + STATE(SPR_FATT,0,4,A_Chase,S_FATT_RUN3,0,0), // S_FATT_RUN2 + STATE(SPR_FATT,1,4,A_Chase,S_FATT_RUN4,0,0), // S_FATT_RUN3 + STATE(SPR_FATT,1,4,A_Chase,S_FATT_RUN5,0,0), // S_FATT_RUN4 + STATE(SPR_FATT,2,4,A_Chase,S_FATT_RUN6,0,0), // S_FATT_RUN5 + STATE(SPR_FATT,2,4,A_Chase,S_FATT_RUN7,0,0), // S_FATT_RUN6 + STATE(SPR_FATT,3,4,A_Chase,S_FATT_RUN8,0,0), // S_FATT_RUN7 + STATE(SPR_FATT,3,4,A_Chase,S_FATT_RUN9,0,0), // S_FATT_RUN8 + STATE(SPR_FATT,4,4,A_Chase,S_FATT_RUN10,0,0), // S_FATT_RUN9 + STATE(SPR_FATT,4,4,A_Chase,S_FATT_RUN11,0,0), // S_FATT_RUN10 + STATE(SPR_FATT,5,4,A_Chase,S_FATT_RUN12,0,0), // S_FATT_RUN11 + STATE(SPR_FATT,5,4,A_Chase,S_FATT_RUN1,0,0), // S_FATT_RUN12 + STATE(SPR_FATT,6,20,A_FatRaise,S_FATT_ATK2,0,0), // S_FATT_ATK1 + STATE(SPR_FATT,7 | FF_FULLBRIGHT,10,A_FatAttack1,S_FATT_ATK3,0,0), // S_FATT_ATK2 + STATE(SPR_FATT,8,5,A_FaceTarget,S_FATT_ATK4,0,0), // S_FATT_ATK3 + STATE(SPR_FATT,6,5,A_FaceTarget,S_FATT_ATK5,0,0), // S_FATT_ATK4 + STATE(SPR_FATT,7 | FF_FULLBRIGHT,10,A_FatAttack2,S_FATT_ATK6,0,0), // S_FATT_ATK5 + STATE(SPR_FATT,8,5,A_FaceTarget,S_FATT_ATK7,0,0), // S_FATT_ATK6 + STATE(SPR_FATT,6,5,A_FaceTarget,S_FATT_ATK8,0,0), // S_FATT_ATK7 + STATE(SPR_FATT,7 | FF_FULLBRIGHT,10,A_FatAttack3,S_FATT_ATK9,0,0), // S_FATT_ATK8 + STATE(SPR_FATT,8,5,A_FaceTarget,S_FATT_ATK10,0,0), // S_FATT_ATK9 + STATE(SPR_FATT,6,5,A_FaceTarget,S_FATT_RUN1,0,0), // S_FATT_ATK10 + STATE(SPR_FATT,9,3,NULL,S_FATT_PAIN2,0,0), // S_FATT_PAIN + STATE(SPR_FATT,9,3,A_Pain,S_FATT_RUN1,0,0), // S_FATT_PAIN2 + STATE(SPR_FATT,10,6,NULL,S_FATT_DIE2,0,0), // S_FATT_DIE1 + STATE(SPR_FATT,11,6,A_Scream,S_FATT_DIE3,0,0), // S_FATT_DIE2 + STATE(SPR_FATT,12,6,A_Fall,S_FATT_DIE4,0,0), // S_FATT_DIE3 + STATE(SPR_FATT,13,6,NULL,S_FATT_DIE5,0,0), // S_FATT_DIE4 + STATE(SPR_FATT,14,6,NULL,S_FATT_DIE6,0,0), // S_FATT_DIE5 + STATE(SPR_FATT,15,6,NULL,S_FATT_DIE7,0,0), // S_FATT_DIE6 + STATE(SPR_FATT,16,6,NULL,S_FATT_DIE8,0,0), // S_FATT_DIE7 + STATE(SPR_FATT,17,6,NULL,S_FATT_DIE9,0,0), // S_FATT_DIE8 + STATE(SPR_FATT,18,6,NULL,S_FATT_DIE10,0,0), // S_FATT_DIE9 + STATE(SPR_FATT,19,-1,A_BossDeath,S_NULL,0,0), // S_FATT_DIE10 + STATE(SPR_FATT,17,5,NULL,S_FATT_RAISE2,0,0), // S_FATT_RAISE1 + STATE(SPR_FATT,16,5,NULL,S_FATT_RAISE3,0,0), // S_FATT_RAISE2 + STATE(SPR_FATT,15,5,NULL,S_FATT_RAISE4,0,0), // S_FATT_RAISE3 + STATE(SPR_FATT,14,5,NULL,S_FATT_RAISE5,0,0), // S_FATT_RAISE4 + STATE(SPR_FATT,13,5,NULL,S_FATT_RAISE6,0,0), // S_FATT_RAISE5 + STATE(SPR_FATT,12,5,NULL,S_FATT_RAISE7,0,0), // S_FATT_RAISE6 + STATE(SPR_FATT,11,5,NULL,S_FATT_RAISE8,0,0), // S_FATT_RAISE7 + STATE(SPR_FATT,10,5,NULL,S_FATT_RUN1,0,0), // S_FATT_RAISE8 + STATE(SPR_CPOS,0,10,A_Look,S_CPOS_STND2,0,0), // S_CPOS_STND + STATE(SPR_CPOS,1,10,A_Look,S_CPOS_STND,0,0), // S_CPOS_STND2 + STATE(SPR_CPOS,0,3,A_Chase,S_CPOS_RUN2,0,0), // S_CPOS_RUN1 + STATE(SPR_CPOS,0,3,A_Chase,S_CPOS_RUN3,0,0), // S_CPOS_RUN2 + STATE(SPR_CPOS,1,3,A_Chase,S_CPOS_RUN4,0,0), // S_CPOS_RUN3 + STATE(SPR_CPOS,1,3,A_Chase,S_CPOS_RUN5,0,0), // S_CPOS_RUN4 + STATE(SPR_CPOS,2,3,A_Chase,S_CPOS_RUN6,0,0), // S_CPOS_RUN5 + STATE(SPR_CPOS,2,3,A_Chase,S_CPOS_RUN7,0,0), // S_CPOS_RUN6 + STATE(SPR_CPOS,3,3,A_Chase,S_CPOS_RUN8,0,0), // S_CPOS_RUN7 + STATE(SPR_CPOS,3,3,A_Chase,S_CPOS_RUN1,0,0), // S_CPOS_RUN8 + STATE(SPR_CPOS,4,10,A_FaceTarget,S_CPOS_ATK2,0,0), // S_CPOS_ATK1 + STATE(SPR_CPOS,5 | FF_FULLBRIGHT,4,A_CPosAttack,S_CPOS_ATK3,0,0), // S_CPOS_ATK2 + STATE(SPR_CPOS,4 | FF_FULLBRIGHT,4,A_CPosAttack,S_CPOS_ATK4,0,0), // S_CPOS_ATK3 + STATE(SPR_CPOS,5,1,A_CPosRefire,S_CPOS_ATK2,0,0), // S_CPOS_ATK4 + STATE(SPR_CPOS,6,3,NULL,S_CPOS_PAIN2,0,0), // S_CPOS_PAIN + STATE(SPR_CPOS,6,3,A_Pain,S_CPOS_RUN1,0,0), // S_CPOS_PAIN2 + STATE(SPR_CPOS,7,5,NULL,S_CPOS_DIE2,0,0), // S_CPOS_DIE1 + STATE(SPR_CPOS,8,5,A_Scream,S_CPOS_DIE3,0,0), // S_CPOS_DIE2 + STATE(SPR_CPOS,9,5,A_Fall,S_CPOS_DIE4,0,0), // S_CPOS_DIE3 + STATE(SPR_CPOS,10,5,NULL,S_CPOS_DIE5,0,0), // S_CPOS_DIE4 + STATE(SPR_CPOS,11,5,NULL,S_CPOS_DIE6,0,0), // S_CPOS_DIE5 + STATE(SPR_CPOS,12,5,NULL,S_CPOS_DIE7,0,0), // S_CPOS_DIE6 + STATE(SPR_CPOS,13,-1,NULL,S_NULL,0,0), // S_CPOS_DIE7 + STATE(SPR_CPOS,14,5,NULL,S_CPOS_XDIE2,0,0), // S_CPOS_XDIE1 + STATE(SPR_CPOS,15,5,A_XScream,S_CPOS_XDIE3,0,0), // S_CPOS_XDIE2 + STATE(SPR_CPOS,16,5,A_Fall,S_CPOS_XDIE4,0,0), // S_CPOS_XDIE3 + STATE(SPR_CPOS,17,5,NULL,S_CPOS_XDIE5,0,0), // S_CPOS_XDIE4 + STATE(SPR_CPOS,18,5,NULL,S_CPOS_XDIE6,0,0), // S_CPOS_XDIE5 + STATE(SPR_CPOS,19,-1,NULL,S_NULL,0,0), // S_CPOS_XDIE6 + STATE(SPR_CPOS,13,5,NULL,S_CPOS_RAISE2,0,0), // S_CPOS_RAISE1 + STATE(SPR_CPOS,12,5,NULL,S_CPOS_RAISE3,0,0), // S_CPOS_RAISE2 + STATE(SPR_CPOS,11,5,NULL,S_CPOS_RAISE4,0,0), // S_CPOS_RAISE3 + STATE(SPR_CPOS,10,5,NULL,S_CPOS_RAISE5,0,0), // S_CPOS_RAISE4 + STATE(SPR_CPOS,9,5,NULL,S_CPOS_RAISE6,0,0), // S_CPOS_RAISE5 + STATE(SPR_CPOS,8,5,NULL,S_CPOS_RAISE7,0,0), // S_CPOS_RAISE6 + STATE(SPR_CPOS,7,5,NULL,S_CPOS_RUN1,0,0), // S_CPOS_RAISE7 + STATE(SPR_TROO,0,10,A_Look,S_TROO_STND2,0,0), // S_TROO_STND + STATE(SPR_TROO,1,10,A_Look,S_TROO_STND,0,0), // S_TROO_STND2 + STATE(SPR_TROO,0,3,A_Chase,S_TROO_RUN2,0,0), // S_TROO_RUN1 + STATE(SPR_TROO,0,3,A_Chase,S_TROO_RUN3,0,0), // S_TROO_RUN2 + STATE(SPR_TROO,1,3,A_Chase,S_TROO_RUN4,0,0), // S_TROO_RUN3 + STATE(SPR_TROO,1,3,A_Chase,S_TROO_RUN5,0,0), // S_TROO_RUN4 + STATE(SPR_TROO,2,3,A_Chase,S_TROO_RUN6,0,0), // S_TROO_RUN5 + STATE(SPR_TROO,2,3,A_Chase,S_TROO_RUN7,0,0), // S_TROO_RUN6 + STATE(SPR_TROO,3,3,A_Chase,S_TROO_RUN8,0,0), // S_TROO_RUN7 + STATE(SPR_TROO,3,3,A_Chase,S_TROO_RUN1,0,0), // S_TROO_RUN8 + STATE(SPR_TROO,4,8,A_FaceTarget,S_TROO_ATK2,0,0), // S_TROO_ATK1 + STATE(SPR_TROO,5,8,A_FaceTarget,S_TROO_ATK3,0,0), // S_TROO_ATK2 + STATE(SPR_TROO,6,6,A_TroopAttack,S_TROO_RUN1,0,0), // S_TROO_ATK3 + STATE(SPR_TROO,7,2,NULL,S_TROO_PAIN2,0,0), // S_TROO_PAIN + STATE(SPR_TROO,7,2,A_Pain,S_TROO_RUN1,0,0), // S_TROO_PAIN2 + STATE(SPR_TROO,8,8,NULL,S_TROO_DIE2,0,0), // S_TROO_DIE1 + STATE(SPR_TROO,9,8,A_Scream,S_TROO_DIE3,0,0), // S_TROO_DIE2 + STATE(SPR_TROO,10,6,NULL,S_TROO_DIE4,0,0), // S_TROO_DIE3 + STATE(SPR_TROO,11,6,A_Fall,S_TROO_DIE5,0,0), // S_TROO_DIE4 + STATE(SPR_TROO,12,-1,NULL,S_NULL,0,0), // S_TROO_DIE5 + STATE(SPR_TROO,13,5,NULL,S_TROO_XDIE2,0,0), // S_TROO_XDIE1 + STATE(SPR_TROO,14,5,A_XScream,S_TROO_XDIE3,0,0), // S_TROO_XDIE2 + STATE(SPR_TROO,15,5,NULL,S_TROO_XDIE4,0,0), // S_TROO_XDIE3 + STATE(SPR_TROO,16,5,A_Fall,S_TROO_XDIE5,0,0), // S_TROO_XDIE4 + STATE(SPR_TROO,17,5,NULL,S_TROO_XDIE6,0,0), // S_TROO_XDIE5 + STATE(SPR_TROO,18,5,NULL,S_TROO_XDIE7,0,0), // S_TROO_XDIE6 + STATE(SPR_TROO,19,5,NULL,S_TROO_XDIE8,0,0), // S_TROO_XDIE7 + STATE(SPR_TROO,20,-1,NULL,S_NULL,0,0), // S_TROO_XDIE8 + STATE(SPR_TROO,12,8,NULL,S_TROO_RAISE2,0,0), // S_TROO_RAISE1 + STATE(SPR_TROO,11,8,NULL,S_TROO_RAISE3,0,0), // S_TROO_RAISE2 + STATE(SPR_TROO,10,6,NULL,S_TROO_RAISE4,0,0), // S_TROO_RAISE3 + STATE(SPR_TROO,9,6,NULL,S_TROO_RAISE5,0,0), // S_TROO_RAISE4 + STATE(SPR_TROO,8,6,NULL,S_TROO_RUN1,0,0), // S_TROO_RAISE5 + STATE(SPR_SARG,0,10,A_Look,S_SARG_STND2,0,0), // S_SARG_STND + STATE(SPR_SARG,1,10,A_Look,S_SARG_STND,0,0), // S_SARG_STND2 + STATE(SPR_SARG,0,2,A_Chase,S_SARG_RUN2,0,0), // S_SARG_RUN1 + STATE(SPR_SARG,0,2,A_Chase,S_SARG_RUN3,0,0), // S_SARG_RUN2 + STATE(SPR_SARG,1,2,A_Chase,S_SARG_RUN4,0,0), // S_SARG_RUN3 + STATE(SPR_SARG,1,2,A_Chase,S_SARG_RUN5,0,0), // S_SARG_RUN4 + STATE(SPR_SARG,2,2,A_Chase,S_SARG_RUN6,0,0), // S_SARG_RUN5 + STATE(SPR_SARG,2,2,A_Chase,S_SARG_RUN7,0,0), // S_SARG_RUN6 + STATE(SPR_SARG,3,2,A_Chase,S_SARG_RUN8,0,0), // S_SARG_RUN7 + STATE(SPR_SARG,3,2,A_Chase,S_SARG_RUN1,0,0), // S_SARG_RUN8 + STATE(SPR_SARG,4,8,A_FaceTarget,S_SARG_ATK2,0,0), // S_SARG_ATK1 + STATE(SPR_SARG,5,8,A_FaceTarget,S_SARG_ATK3,0,0), // S_SARG_ATK2 + STATE(SPR_SARG,6,8,A_SargAttack,S_SARG_RUN1,0,0), // S_SARG_ATK3 + STATE(SPR_SARG,7,2,NULL,S_SARG_PAIN2,0,0), // S_SARG_PAIN + STATE(SPR_SARG,7,2,A_Pain,S_SARG_RUN1,0,0), // S_SARG_PAIN2 + STATE(SPR_SARG,8,8,NULL,S_SARG_DIE2,0,0), // S_SARG_DIE1 + STATE(SPR_SARG,9,8,A_Scream,S_SARG_DIE3,0,0), // S_SARG_DIE2 + STATE(SPR_SARG,10,4,NULL,S_SARG_DIE4,0,0), // S_SARG_DIE3 + STATE(SPR_SARG,11,4,A_Fall,S_SARG_DIE5,0,0), // S_SARG_DIE4 + STATE(SPR_SARG,12,4,NULL,S_SARG_DIE6,0,0), // S_SARG_DIE5 + STATE(SPR_SARG,13,-1,NULL,S_NULL,0,0), // S_SARG_DIE6 + STATE(SPR_SARG,13,5,NULL,S_SARG_RAISE2,0,0), // S_SARG_RAISE1 + STATE(SPR_SARG,12,5,NULL,S_SARG_RAISE3,0,0), // S_SARG_RAISE2 + STATE(SPR_SARG,11,5,NULL,S_SARG_RAISE4,0,0), // S_SARG_RAISE3 + STATE(SPR_SARG,10,5,NULL,S_SARG_RAISE5,0,0), // S_SARG_RAISE4 + STATE(SPR_SARG,9,5,NULL,S_SARG_RAISE6,0,0), // S_SARG_RAISE5 + STATE(SPR_SARG,8,5,NULL,S_SARG_RUN1,0,0), // S_SARG_RAISE6 + STATE(SPR_HEAD,0,10,A_Look,S_HEAD_STND,0,0), // S_HEAD_STND + STATE(SPR_HEAD,0,3,A_Chase,S_HEAD_RUN1,0,0), // S_HEAD_RUN1 + STATE(SPR_HEAD,1,5,A_FaceTarget,S_HEAD_ATK2,0,0), // S_HEAD_ATK1 + STATE(SPR_HEAD,2,5,A_FaceTarget,S_HEAD_ATK3,0,0), // S_HEAD_ATK2 + STATE(SPR_HEAD,3 | FF_FULLBRIGHT,5,A_HeadAttack,S_HEAD_RUN1,0,0), // S_HEAD_ATK3 + STATE(SPR_HEAD,4,3,NULL,S_HEAD_PAIN2,0,0), // S_HEAD_PAIN + STATE(SPR_HEAD,4,3,A_Pain,S_HEAD_PAIN3,0,0), // S_HEAD_PAIN2 + STATE(SPR_HEAD,5,6,NULL,S_HEAD_RUN1,0,0), // S_HEAD_PAIN3 + STATE(SPR_HEAD,6,8,NULL,S_HEAD_DIE2,0,0), // S_HEAD_DIE1 + STATE(SPR_HEAD,7,8,A_Scream,S_HEAD_DIE3,0,0), // S_HEAD_DIE2 + STATE(SPR_HEAD,8,8,NULL,S_HEAD_DIE4,0,0), // S_HEAD_DIE3 + STATE(SPR_HEAD,9,8,NULL,S_HEAD_DIE5,0,0), // S_HEAD_DIE4 + STATE(SPR_HEAD,10,8,A_Fall,S_HEAD_DIE6,0,0), // S_HEAD_DIE5 + STATE(SPR_HEAD,11,-1,NULL,S_NULL,0,0), // S_HEAD_DIE6 + STATE(SPR_HEAD,11,8,NULL,S_HEAD_RAISE2,0,0), // S_HEAD_RAISE1 + STATE(SPR_HEAD,10,8,NULL,S_HEAD_RAISE3,0,0), // S_HEAD_RAISE2 + STATE(SPR_HEAD,9,8,NULL,S_HEAD_RAISE4,0,0), // S_HEAD_RAISE3 + STATE(SPR_HEAD,8,8,NULL,S_HEAD_RAISE5,0,0), // S_HEAD_RAISE4 + STATE(SPR_HEAD,7,8,NULL,S_HEAD_RAISE6,0,0), // S_HEAD_RAISE5 + STATE(SPR_HEAD,6,8,NULL,S_HEAD_RUN1,0,0), // S_HEAD_RAISE6 + STATE(SPR_BAL7,0 | FF_FULLBRIGHT,4,NULL,S_BRBALL2,0,0), // S_BRBALL1 + STATE(SPR_BAL7,1 | FF_FULLBRIGHT,4,NULL,S_BRBALL1,0,0), // S_BRBALL2 + STATE(SPR_BAL7,2 | FF_FULLBRIGHT,6,NULL,S_BRBALLX2,0,0), // S_BRBALLX1 + STATE(SPR_BAL7,3 | FF_FULLBRIGHT,6,NULL,S_BRBALLX3,0,0), // S_BRBALLX2 + STATE(SPR_BAL7,4 | FF_FULLBRIGHT,6,NULL,S_NULL,0,0), // S_BRBALLX3 + STATE(SPR_BOSS,0,10,A_Look,S_BOSS_STND2,0,0), // S_BOSS_STND + STATE(SPR_BOSS,1,10,A_Look,S_BOSS_STND,0,0), // S_BOSS_STND2 + STATE(SPR_BOSS,0,3,A_Chase,S_BOSS_RUN2,0,0), // S_BOSS_RUN1 + STATE(SPR_BOSS,0,3,A_Chase,S_BOSS_RUN3,0,0), // S_BOSS_RUN2 + STATE(SPR_BOSS,1,3,A_Chase,S_BOSS_RUN4,0,0), // S_BOSS_RUN3 + STATE(SPR_BOSS,1,3,A_Chase,S_BOSS_RUN5,0,0), // S_BOSS_RUN4 + STATE(SPR_BOSS,2,3,A_Chase,S_BOSS_RUN6,0,0), // S_BOSS_RUN5 + STATE(SPR_BOSS,2,3,A_Chase,S_BOSS_RUN7,0,0), // S_BOSS_RUN6 + STATE(SPR_BOSS,3,3,A_Chase,S_BOSS_RUN8,0,0), // S_BOSS_RUN7 + STATE(SPR_BOSS,3,3,A_Chase,S_BOSS_RUN1,0,0), // S_BOSS_RUN8 + STATE(SPR_BOSS,4,8,A_FaceTarget,S_BOSS_ATK2,0,0), // S_BOSS_ATK1 + STATE(SPR_BOSS,5,8,A_FaceTarget,S_BOSS_ATK3,0,0), // S_BOSS_ATK2 + STATE(SPR_BOSS,6,8,A_BruisAttack,S_BOSS_RUN1,0,0), // S_BOSS_ATK3 + STATE(SPR_BOSS,7,2,NULL,S_BOSS_PAIN2,0,0), // S_BOSS_PAIN + STATE(SPR_BOSS,7,2,A_Pain,S_BOSS_RUN1,0,0), // S_BOSS_PAIN2 + STATE(SPR_BOSS,8,8,NULL,S_BOSS_DIE2,0,0), // S_BOSS_DIE1 + STATE(SPR_BOSS,9,8,A_Scream,S_BOSS_DIE3,0,0), // S_BOSS_DIE2 + STATE(SPR_BOSS,10,8,NULL,S_BOSS_DIE4,0,0), // S_BOSS_DIE3 + STATE(SPR_BOSS,11,8,A_Fall,S_BOSS_DIE5,0,0), // S_BOSS_DIE4 + STATE(SPR_BOSS,12,8,NULL,S_BOSS_DIE6,0,0), // S_BOSS_DIE5 + STATE(SPR_BOSS,13,8,NULL,S_BOSS_DIE7,0,0), // S_BOSS_DIE6 + STATE(SPR_BOSS,14,-1,A_BossDeath,S_NULL,0,0), // S_BOSS_DIE7 + STATE(SPR_BOSS,14,8,NULL,S_BOSS_RAISE2,0,0), // S_BOSS_RAISE1 + STATE(SPR_BOSS,13,8,NULL,S_BOSS_RAISE3,0,0), // S_BOSS_RAISE2 + STATE(SPR_BOSS,12,8,NULL,S_BOSS_RAISE4,0,0), // S_BOSS_RAISE3 + STATE(SPR_BOSS,11,8,NULL,S_BOSS_RAISE5,0,0), // S_BOSS_RAISE4 + STATE(SPR_BOSS,10,8,NULL,S_BOSS_RAISE6,0,0), // S_BOSS_RAISE5 + STATE(SPR_BOSS,9,8,NULL,S_BOSS_RAISE7,0,0), // S_BOSS_RAISE6 + STATE(SPR_BOSS,8,8,NULL,S_BOSS_RUN1,0,0), // S_BOSS_RAISE7 + STATE(SPR_BOS2,0,10,A_Look,S_BOS2_STND2,0,0), // S_BOS2_STND + STATE(SPR_BOS2,1,10,A_Look,S_BOS2_STND,0,0), // S_BOS2_STND2 + STATE(SPR_BOS2,0,3,A_Chase,S_BOS2_RUN2,0,0), // S_BOS2_RUN1 + STATE(SPR_BOS2,0,3,A_Chase,S_BOS2_RUN3,0,0), // S_BOS2_RUN2 + STATE(SPR_BOS2,1,3,A_Chase,S_BOS2_RUN4,0,0), // S_BOS2_RUN3 + STATE(SPR_BOS2,1,3,A_Chase,S_BOS2_RUN5,0,0), // S_BOS2_RUN4 + STATE(SPR_BOS2,2,3,A_Chase,S_BOS2_RUN6,0,0), // S_BOS2_RUN5 + STATE(SPR_BOS2,2,3,A_Chase,S_BOS2_RUN7,0,0), // S_BOS2_RUN6 + STATE(SPR_BOS2,3,3,A_Chase,S_BOS2_RUN8,0,0), // S_BOS2_RUN7 + STATE(SPR_BOS2,3,3,A_Chase,S_BOS2_RUN1,0,0), // S_BOS2_RUN8 + STATE(SPR_BOS2,4,8,A_FaceTarget,S_BOS2_ATK2,0,0), // S_BOS2_ATK1 + STATE(SPR_BOS2,5,8,A_FaceTarget,S_BOS2_ATK3,0,0), // S_BOS2_ATK2 + STATE(SPR_BOS2,6,8,A_BruisAttack,S_BOS2_RUN1,0,0), // S_BOS2_ATK3 + STATE(SPR_BOS2,7,2,NULL,S_BOS2_PAIN2,0,0), // S_BOS2_PAIN + STATE(SPR_BOS2,7,2,A_Pain,S_BOS2_RUN1,0,0), // S_BOS2_PAIN2 + STATE(SPR_BOS2,8,8,NULL,S_BOS2_DIE2,0,0), // S_BOS2_DIE1 + STATE(SPR_BOS2,9,8,A_Scream,S_BOS2_DIE3,0,0), // S_BOS2_DIE2 + STATE(SPR_BOS2,10,8,NULL,S_BOS2_DIE4,0,0), // S_BOS2_DIE3 + STATE(SPR_BOS2,11,8,A_Fall,S_BOS2_DIE5,0,0), // S_BOS2_DIE4 + STATE(SPR_BOS2,12,8,NULL,S_BOS2_DIE6,0,0), // S_BOS2_DIE5 + STATE(SPR_BOS2,13,8,NULL,S_BOS2_DIE7,0,0), // S_BOS2_DIE6 + STATE(SPR_BOS2,14,-1,NULL,S_NULL,0,0), // S_BOS2_DIE7 + STATE(SPR_BOS2,14,8,NULL,S_BOS2_RAISE2,0,0), // S_BOS2_RAISE1 + STATE(SPR_BOS2,13,8,NULL,S_BOS2_RAISE3,0,0), // S_BOS2_RAISE2 + STATE(SPR_BOS2,12,8,NULL,S_BOS2_RAISE4,0,0), // S_BOS2_RAISE3 + STATE(SPR_BOS2,11,8,NULL,S_BOS2_RAISE5,0,0), // S_BOS2_RAISE4 + STATE(SPR_BOS2,10,8,NULL,S_BOS2_RAISE6,0,0), // S_BOS2_RAISE5 + STATE(SPR_BOS2,9,8,NULL,S_BOS2_RAISE7,0,0), // S_BOS2_RAISE6 + STATE(SPR_BOS2,8,8,NULL,S_BOS2_RUN1,0,0), // S_BOS2_RAISE7 + STATE(SPR_SKUL,0 | FF_FULLBRIGHT,10,A_Look,S_SKULL_STND2,0,0), // S_SKULL_STND + STATE(SPR_SKUL,1 | FF_FULLBRIGHT,10,A_Look,S_SKULL_STND,0,0), // S_SKULL_STND2 + STATE(SPR_SKUL,0 | FF_FULLBRIGHT,6,A_Chase,S_SKULL_RUN2,0,0), // S_SKULL_RUN1 + STATE(SPR_SKUL,1 | FF_FULLBRIGHT,6,A_Chase,S_SKULL_RUN1,0,0), // S_SKULL_RUN2 + STATE(SPR_SKUL,2 | FF_FULLBRIGHT,10,A_FaceTarget,S_SKULL_ATK2,0,0), // S_SKULL_ATK1 + STATE(SPR_SKUL,3 | FF_FULLBRIGHT,4,A_SkullAttack,S_SKULL_ATK3,0,0), // S_SKULL_ATK2 + STATE(SPR_SKUL,2 | FF_FULLBRIGHT,4,NULL,S_SKULL_ATK4,0,0), // S_SKULL_ATK3 + STATE(SPR_SKUL,3 | FF_FULLBRIGHT,4,NULL,S_SKULL_ATK3,0,0), // S_SKULL_ATK4 + STATE(SPR_SKUL,4 | FF_FULLBRIGHT,3,NULL,S_SKULL_PAIN2,0,0), // S_SKULL_PAIN + STATE(SPR_SKUL,4 | FF_FULLBRIGHT,3,A_Pain,S_SKULL_RUN1,0,0), // S_SKULL_PAIN2 + STATE(SPR_SKUL,5 | FF_FULLBRIGHT,6,NULL,S_SKULL_DIE2,0,0), // S_SKULL_DIE1 + STATE(SPR_SKUL,6 | FF_FULLBRIGHT,6,A_Scream,S_SKULL_DIE3,0,0), // S_SKULL_DIE2 + STATE(SPR_SKUL,7 | FF_FULLBRIGHT,6,NULL,S_SKULL_DIE4,0,0), // S_SKULL_DIE3 + STATE(SPR_SKUL,8 | FF_FULLBRIGHT,6,A_Fall,S_SKULL_DIE5,0,0), // S_SKULL_DIE4 + STATE(SPR_SKUL,9,6,NULL,S_SKULL_DIE6,0,0), // S_SKULL_DIE5 + STATE(SPR_SKUL,10,6,NULL,S_NULL,0,0), // S_SKULL_DIE6 + STATE(SPR_SPID,0,10,A_Look,S_SPID_STND2,0,0), // S_SPID_STND + STATE(SPR_SPID,1,10,A_Look,S_SPID_STND,0,0), // S_SPID_STND2 + STATE(SPR_SPID,0,3,A_Metal,S_SPID_RUN2,0,0), // S_SPID_RUN1 + STATE(SPR_SPID,0,3,A_Chase,S_SPID_RUN3,0,0), // S_SPID_RUN2 + STATE(SPR_SPID,1,3,A_Chase,S_SPID_RUN4,0,0), // S_SPID_RUN3 + STATE(SPR_SPID,1,3,A_Chase,S_SPID_RUN5,0,0), // S_SPID_RUN4 + STATE(SPR_SPID,2,3,A_Metal,S_SPID_RUN6,0,0), // S_SPID_RUN5 + STATE(SPR_SPID,2,3,A_Chase,S_SPID_RUN7,0,0), // S_SPID_RUN6 + STATE(SPR_SPID,3,3,A_Chase,S_SPID_RUN8,0,0), // S_SPID_RUN7 + STATE(SPR_SPID,3,3,A_Chase,S_SPID_RUN9,0,0), // S_SPID_RUN8 + STATE(SPR_SPID,4,3,A_Metal,S_SPID_RUN10,0,0), // S_SPID_RUN9 + STATE(SPR_SPID,4,3,A_Chase,S_SPID_RUN11,0,0), // S_SPID_RUN10 + STATE(SPR_SPID,5,3,A_Chase,S_SPID_RUN12,0,0), // S_SPID_RUN11 + STATE(SPR_SPID,5,3,A_Chase,S_SPID_RUN1,0,0), // S_SPID_RUN12 + STATE(SPR_SPID,0 | FF_FULLBRIGHT,20,A_FaceTarget,S_SPID_ATK2,0,0), // S_SPID_ATK1 + STATE(SPR_SPID,6 | FF_FULLBRIGHT,4,A_SPosAttack,S_SPID_ATK3,0,0), // S_SPID_ATK2 + STATE(SPR_SPID,7 | FF_FULLBRIGHT,4,A_SPosAttack,S_SPID_ATK4,0,0), // S_SPID_ATK3 + STATE(SPR_SPID,7 | FF_FULLBRIGHT,1,A_SpidRefire,S_SPID_ATK2,0,0), // S_SPID_ATK4 + STATE(SPR_SPID,8,3,NULL,S_SPID_PAIN2,0,0), // S_SPID_PAIN + STATE(SPR_SPID,8,3,A_Pain,S_SPID_RUN1,0,0), // S_SPID_PAIN2 + STATE(SPR_SPID,9,20,A_Scream,S_SPID_DIE2,0,0), // S_SPID_DIE1 + STATE(SPR_SPID,10,10,A_Fall,S_SPID_DIE3,0,0), // S_SPID_DIE2 + STATE(SPR_SPID,11,10,NULL,S_SPID_DIE4,0,0), // S_SPID_DIE3 + STATE(SPR_SPID,12,10,NULL,S_SPID_DIE5,0,0), // S_SPID_DIE4 + STATE(SPR_SPID,13,10,NULL,S_SPID_DIE6,0,0), // S_SPID_DIE5 + STATE(SPR_SPID,14,10,NULL,S_SPID_DIE7,0,0), // S_SPID_DIE6 + STATE(SPR_SPID,15,10,NULL,S_SPID_DIE8,0,0), // S_SPID_DIE7 + STATE(SPR_SPID,16,10,NULL,S_SPID_DIE9,0,0), // S_SPID_DIE8 + STATE(SPR_SPID,17,10,NULL,S_SPID_DIE10,0,0), // S_SPID_DIE9 + STATE(SPR_SPID,18,30,NULL,S_SPID_DIE11,0,0), // S_SPID_DIE10 + STATE(SPR_SPID,18,-1,A_BossDeath,S_NULL,0,0), // S_SPID_DIE11 + STATE(SPR_BSPI,0,10,A_Look,S_BSPI_STND2,0,0), // S_BSPI_STND + STATE(SPR_BSPI,1,10,A_Look,S_BSPI_STND,0,0), // S_BSPI_STND2 + STATE(SPR_BSPI,0,20,NULL,S_BSPI_RUN1,0,0), // S_BSPI_SIGHT + STATE(SPR_BSPI,0,3,A_BabyMetal,S_BSPI_RUN2,0,0), // S_BSPI_RUN1 + STATE(SPR_BSPI,0,3,A_Chase,S_BSPI_RUN3,0,0), // S_BSPI_RUN2 + STATE(SPR_BSPI,1,3,A_Chase,S_BSPI_RUN4,0,0), // S_BSPI_RUN3 + STATE(SPR_BSPI,1,3,A_Chase,S_BSPI_RUN5,0,0), // S_BSPI_RUN4 + STATE(SPR_BSPI,2,3,A_Chase,S_BSPI_RUN6,0,0), // S_BSPI_RUN5 + STATE(SPR_BSPI,2,3,A_Chase,S_BSPI_RUN7,0,0), // S_BSPI_RUN6 + STATE(SPR_BSPI,3,3,A_BabyMetal,S_BSPI_RUN8,0,0), // S_BSPI_RUN7 + STATE(SPR_BSPI,3,3,A_Chase,S_BSPI_RUN9,0,0), // S_BSPI_RUN8 + STATE(SPR_BSPI,4,3,A_Chase,S_BSPI_RUN10,0,0), // S_BSPI_RUN9 + STATE(SPR_BSPI,4,3,A_Chase,S_BSPI_RUN11,0,0), // S_BSPI_RUN10 + STATE(SPR_BSPI,5,3,A_Chase,S_BSPI_RUN12,0,0), // S_BSPI_RUN11 + STATE(SPR_BSPI,5,3,A_Chase,S_BSPI_RUN1,0,0), // S_BSPI_RUN12 + STATE(SPR_BSPI,0 | FF_FULLBRIGHT,20,A_FaceTarget,S_BSPI_ATK2,0,0), // S_BSPI_ATK1 + STATE(SPR_BSPI,6 | FF_FULLBRIGHT,4,A_BspiAttack,S_BSPI_ATK3,0,0), // S_BSPI_ATK2 + STATE(SPR_BSPI,7 | FF_FULLBRIGHT,4,NULL,S_BSPI_ATK4,0,0), // S_BSPI_ATK3 + STATE(SPR_BSPI,7 | FF_FULLBRIGHT,1,A_SpidRefire,S_BSPI_ATK2,0,0), // S_BSPI_ATK4 + STATE(SPR_BSPI,8,3,NULL,S_BSPI_PAIN2,0,0), // S_BSPI_PAIN + STATE(SPR_BSPI,8,3,A_Pain,S_BSPI_RUN1,0,0), // S_BSPI_PAIN2 + STATE(SPR_BSPI,9,20,A_Scream,S_BSPI_DIE2,0,0), // S_BSPI_DIE1 + STATE(SPR_BSPI,10,7,A_Fall,S_BSPI_DIE3,0,0), // S_BSPI_DIE2 + STATE(SPR_BSPI,11,7,NULL,S_BSPI_DIE4,0,0), // S_BSPI_DIE3 + STATE(SPR_BSPI,12,7,NULL,S_BSPI_DIE5,0,0), // S_BSPI_DIE4 + STATE(SPR_BSPI,13,7,NULL,S_BSPI_DIE6,0,0), // S_BSPI_DIE5 + STATE(SPR_BSPI,14,7,NULL,S_BSPI_DIE7,0,0), // S_BSPI_DIE6 + STATE(SPR_BSPI,15,-1,A_BossDeath,S_NULL,0,0), // S_BSPI_DIE7 + STATE(SPR_BSPI,15,5,NULL,S_BSPI_RAISE2,0,0), // S_BSPI_RAISE1 + STATE(SPR_BSPI,14,5,NULL,S_BSPI_RAISE3,0,0), // S_BSPI_RAISE2 + STATE(SPR_BSPI,13,5,NULL,S_BSPI_RAISE4,0,0), // S_BSPI_RAISE3 + STATE(SPR_BSPI,12,5,NULL,S_BSPI_RAISE5,0,0), // S_BSPI_RAISE4 + STATE(SPR_BSPI,11,5,NULL,S_BSPI_RAISE6,0,0), // S_BSPI_RAISE5 + STATE(SPR_BSPI,10,5,NULL,S_BSPI_RAISE7,0,0), // S_BSPI_RAISE6 + STATE(SPR_BSPI,9,5,NULL,S_BSPI_RUN1,0,0), // S_BSPI_RAISE7 + STATE(SPR_APLS,0 | FF_FULLBRIGHT,5,NULL,S_ARACH_PLAZ2,0,0), // S_ARACH_PLAZ + STATE(SPR_APLS,1 | FF_FULLBRIGHT,5,NULL,S_ARACH_PLAZ,0,0), // S_ARACH_PLAZ2 + STATE(SPR_APBX,0 | FF_FULLBRIGHT,5,NULL,S_ARACH_PLEX2,0,0), // S_ARACH_PLEX + STATE(SPR_APBX,1 | FF_FULLBRIGHT,5,NULL,S_ARACH_PLEX3,0,0), // S_ARACH_PLEX2 + STATE(SPR_APBX,2 | FF_FULLBRIGHT,5,NULL,S_ARACH_PLEX4,0,0), // S_ARACH_PLEX3 + STATE(SPR_APBX,3 | FF_FULLBRIGHT,5,NULL,S_ARACH_PLEX5,0,0), // S_ARACH_PLEX4 + STATE(SPR_APBX,4 | FF_FULLBRIGHT,5,NULL,S_NULL,0,0), // S_ARACH_PLEX5 + STATE(SPR_CYBR,0,10,A_Look,S_CYBER_STND2,0,0), // S_CYBER_STND + STATE(SPR_CYBR,1,10,A_Look,S_CYBER_STND,0,0), // S_CYBER_STND2 + STATE(SPR_CYBR,0,3,A_Hoof,S_CYBER_RUN2,0,0), // S_CYBER_RUN1 + STATE(SPR_CYBR,0,3,A_Chase,S_CYBER_RUN3,0,0), // S_CYBER_RUN2 + STATE(SPR_CYBR,1,3,A_Chase,S_CYBER_RUN4,0,0), // S_CYBER_RUN3 + STATE(SPR_CYBR,1,3,A_Chase,S_CYBER_RUN5,0,0), // S_CYBER_RUN4 + STATE(SPR_CYBR,2,3,A_Chase,S_CYBER_RUN6,0,0), // S_CYBER_RUN5 + STATE(SPR_CYBR,2,3,A_Chase,S_CYBER_RUN7,0,0), // S_CYBER_RUN6 + STATE(SPR_CYBR,3,3,A_Metal,S_CYBER_RUN8,0,0), // S_CYBER_RUN7 + STATE(SPR_CYBR,3,3,A_Chase,S_CYBER_RUN1,0,0), // S_CYBER_RUN8 + STATE(SPR_CYBR,4,6,A_FaceTarget,S_CYBER_ATK2,0,0), // S_CYBER_ATK1 + STATE(SPR_CYBR,5,12,A_CyberAttack,S_CYBER_ATK3,0,0), // S_CYBER_ATK2 + STATE(SPR_CYBR,4,12,A_FaceTarget,S_CYBER_ATK4,0,0), // S_CYBER_ATK3 + STATE(SPR_CYBR,5,12,A_CyberAttack,S_CYBER_ATK5,0,0), // S_CYBER_ATK4 + STATE(SPR_CYBR,4,12,A_FaceTarget,S_CYBER_ATK6,0,0), // S_CYBER_ATK5 + STATE(SPR_CYBR,5,12,A_CyberAttack,S_CYBER_RUN1,0,0), // S_CYBER_ATK6 + STATE(SPR_CYBR,6,10,A_Pain,S_CYBER_RUN1,0,0), // S_CYBER_PAIN + STATE(SPR_CYBR,7,10,NULL,S_CYBER_DIE2,0,0), // S_CYBER_DIE1 + STATE(SPR_CYBR,8,10,A_Scream,S_CYBER_DIE3,0,0), // S_CYBER_DIE2 + STATE(SPR_CYBR,9,10,NULL,S_CYBER_DIE4,0,0), // S_CYBER_DIE3 + STATE(SPR_CYBR,10,10,NULL,S_CYBER_DIE5,0,0), // S_CYBER_DIE4 + STATE(SPR_CYBR,11,10,NULL,S_CYBER_DIE6,0,0), // S_CYBER_DIE5 + STATE(SPR_CYBR,12,10,A_Fall,S_CYBER_DIE7,0,0), // S_CYBER_DIE6 + STATE(SPR_CYBR,13,10,NULL,S_CYBER_DIE8,0,0), // S_CYBER_DIE7 + STATE(SPR_CYBR,14,10,NULL,S_CYBER_DIE9,0,0), // S_CYBER_DIE8 + STATE(SPR_CYBR,15,30,NULL,S_CYBER_DIE10,0,0), // S_CYBER_DIE9 + STATE(SPR_CYBR,15,-1,A_BossDeath,S_NULL,0,0), // S_CYBER_DIE10 + STATE(SPR_PAIN,0,10,A_Look,S_PAIN_STND,0,0), // S_PAIN_STND + STATE(SPR_PAIN,0,3,A_Chase,S_PAIN_RUN2,0,0), // S_PAIN_RUN1 + STATE(SPR_PAIN,0,3,A_Chase,S_PAIN_RUN3,0,0), // S_PAIN_RUN2 + STATE(SPR_PAIN,1,3,A_Chase,S_PAIN_RUN4,0,0), // S_PAIN_RUN3 + STATE(SPR_PAIN,1,3,A_Chase,S_PAIN_RUN5,0,0), // S_PAIN_RUN4 + STATE(SPR_PAIN,2,3,A_Chase,S_PAIN_RUN6,0,0), // S_PAIN_RUN5 + STATE(SPR_PAIN,2,3,A_Chase,S_PAIN_RUN1,0,0), // S_PAIN_RUN6 + STATE(SPR_PAIN,3,5,A_FaceTarget,S_PAIN_ATK2,0,0), // S_PAIN_ATK1 + STATE(SPR_PAIN,4,5,A_FaceTarget,S_PAIN_ATK3,0,0), // S_PAIN_ATK2 + STATE(SPR_PAIN,5 | FF_FULLBRIGHT,5,A_FaceTarget,S_PAIN_ATK4,0,0), // S_PAIN_ATK3 + STATE(SPR_PAIN,5 | FF_FULLBRIGHT,0,A_PainAttack,S_PAIN_RUN1,0,0), // S_PAIN_ATK4 + STATE(SPR_PAIN,6,6,NULL,S_PAIN_PAIN2,0,0), // S_PAIN_PAIN + STATE(SPR_PAIN,6,6,A_Pain,S_PAIN_RUN1,0,0), // S_PAIN_PAIN2 + STATE(SPR_PAIN,7 | FF_FULLBRIGHT,8,NULL,S_PAIN_DIE2,0,0), // S_PAIN_DIE1 + STATE(SPR_PAIN,8 | FF_FULLBRIGHT,8,A_Scream,S_PAIN_DIE3,0,0), // S_PAIN_DIE2 + STATE(SPR_PAIN,9 | FF_FULLBRIGHT,8,NULL,S_PAIN_DIE4,0,0), // S_PAIN_DIE3 + STATE(SPR_PAIN,10 | FF_FULLBRIGHT,8,NULL,S_PAIN_DIE5,0,0), // S_PAIN_DIE4 + STATE(SPR_PAIN,11 | FF_FULLBRIGHT,8,A_PainDie,S_PAIN_DIE6,0,0), // S_PAIN_DIE5 + STATE(SPR_PAIN,12 | FF_FULLBRIGHT,8,NULL,S_NULL,0,0), // S_PAIN_DIE6 + STATE(SPR_PAIN,12,8,NULL,S_PAIN_RAISE2,0,0), // S_PAIN_RAISE1 + STATE(SPR_PAIN,11,8,NULL,S_PAIN_RAISE3,0,0), // S_PAIN_RAISE2 + STATE(SPR_PAIN,10,8,NULL,S_PAIN_RAISE4,0,0), // S_PAIN_RAISE3 + STATE(SPR_PAIN,9,8,NULL,S_PAIN_RAISE5,0,0), // S_PAIN_RAISE4 + STATE(SPR_PAIN,8,8,NULL,S_PAIN_RAISE6,0,0), // S_PAIN_RAISE5 + STATE(SPR_PAIN,7,8,NULL,S_PAIN_RUN1,0,0), // S_PAIN_RAISE6 + STATE(SPR_SSWV,0,10,A_Look,S_SSWV_STND2,0,0), // S_SSWV_STND + STATE(SPR_SSWV,1,10,A_Look,S_SSWV_STND,0,0), // S_SSWV_STND2 + STATE(SPR_SSWV,0,3,A_Chase,S_SSWV_RUN2,0,0), // S_SSWV_RUN1 + STATE(SPR_SSWV,0,3,A_Chase,S_SSWV_RUN3,0,0), // S_SSWV_RUN2 + STATE(SPR_SSWV,1,3,A_Chase,S_SSWV_RUN4,0,0), // S_SSWV_RUN3 + STATE(SPR_SSWV,1,3,A_Chase,S_SSWV_RUN5,0,0), // S_SSWV_RUN4 + STATE(SPR_SSWV,2,3,A_Chase,S_SSWV_RUN6,0,0), // S_SSWV_RUN5 + STATE(SPR_SSWV,2,3,A_Chase,S_SSWV_RUN7,0,0), // S_SSWV_RUN6 + STATE(SPR_SSWV,3,3,A_Chase,S_SSWV_RUN8,0,0), // S_SSWV_RUN7 + STATE(SPR_SSWV,3,3,A_Chase,S_SSWV_RUN1,0,0), // S_SSWV_RUN8 + STATE(SPR_SSWV,4,10,A_FaceTarget,S_SSWV_ATK2,0,0), // S_SSWV_ATK1 + STATE(SPR_SSWV,5,10,A_FaceTarget,S_SSWV_ATK3,0,0), // S_SSWV_ATK2 + STATE(SPR_SSWV,6 | FF_FULLBRIGHT,4,A_CPosAttack,S_SSWV_ATK4,0,0), // S_SSWV_ATK3 + STATE(SPR_SSWV,5,6,A_FaceTarget,S_SSWV_ATK5,0,0), // S_SSWV_ATK4 + STATE(SPR_SSWV,6 | FF_FULLBRIGHT,4,A_CPosAttack,S_SSWV_ATK6,0,0), // S_SSWV_ATK5 + STATE(SPR_SSWV,5,1,A_CPosRefire,S_SSWV_ATK2,0,0), // S_SSWV_ATK6 + STATE(SPR_SSWV,7,3,NULL,S_SSWV_PAIN2,0,0), // S_SSWV_PAIN + STATE(SPR_SSWV,7,3,A_Pain,S_SSWV_RUN1,0,0), // S_SSWV_PAIN2 + STATE(SPR_SSWV,8,5,NULL,S_SSWV_DIE2,0,0), // S_SSWV_DIE1 + STATE(SPR_SSWV,9,5,A_Scream,S_SSWV_DIE3,0,0), // S_SSWV_DIE2 + STATE(SPR_SSWV,10,5,A_Fall,S_SSWV_DIE4,0,0), // S_SSWV_DIE3 + STATE(SPR_SSWV,11,5,NULL,S_SSWV_DIE5,0,0), // S_SSWV_DIE4 + STATE(SPR_SSWV,12,-1,NULL,S_NULL,0,0), // S_SSWV_DIE5 + STATE(SPR_SSWV,13,5,NULL,S_SSWV_XDIE2,0,0), // S_SSWV_XDIE1 + STATE(SPR_SSWV,14,5,A_XScream,S_SSWV_XDIE3,0,0), // S_SSWV_XDIE2 + STATE(SPR_SSWV,15,5,A_Fall,S_SSWV_XDIE4,0,0), // S_SSWV_XDIE3 + STATE(SPR_SSWV,16,5,NULL,S_SSWV_XDIE5,0,0), // S_SSWV_XDIE4 + STATE(SPR_SSWV,17,5,NULL,S_SSWV_XDIE6,0,0), // S_SSWV_XDIE5 + STATE(SPR_SSWV,18,5,NULL,S_SSWV_XDIE7,0,0), // S_SSWV_XDIE6 + STATE(SPR_SSWV,19,5,NULL,S_SSWV_XDIE8,0,0), // S_SSWV_XDIE7 + STATE(SPR_SSWV,20,5,NULL,S_SSWV_XDIE9,0,0), // S_SSWV_XDIE8 + STATE(SPR_SSWV,21,-1,NULL,S_NULL,0,0), // S_SSWV_XDIE9 + STATE(SPR_SSWV,12,5,NULL,S_SSWV_RAISE2,0,0), // S_SSWV_RAISE1 + STATE(SPR_SSWV,11,5,NULL,S_SSWV_RAISE3,0,0), // S_SSWV_RAISE2 + STATE(SPR_SSWV,10,5,NULL,S_SSWV_RAISE4,0,0), // S_SSWV_RAISE3 + STATE(SPR_SSWV,9,5,NULL,S_SSWV_RAISE5,0,0), // S_SSWV_RAISE4 + STATE(SPR_SSWV,8,5,NULL,S_SSWV_RUN1,0,0), // S_SSWV_RAISE5 + STATE(SPR_KEEN,0,-1,NULL,S_KEENSTND,0,0), // S_KEENSTND + STATE(SPR_KEEN,0,6,NULL,S_COMMKEEN2,0,0), // S_COMMKEEN + STATE(SPR_KEEN,1,6,NULL,S_COMMKEEN3,0,0), // S_COMMKEEN2 + STATE(SPR_KEEN,2,6,A_Scream,S_COMMKEEN4,0,0), // S_COMMKEEN3 + STATE(SPR_KEEN,3,6,NULL,S_COMMKEEN5,0,0), // S_COMMKEEN4 + STATE(SPR_KEEN,4,6,NULL,S_COMMKEEN6,0,0), // S_COMMKEEN5 + STATE(SPR_KEEN,5,6,NULL,S_COMMKEEN7,0,0), // S_COMMKEEN6 + STATE(SPR_KEEN,6,6,NULL,S_COMMKEEN8,0,0), // S_COMMKEEN7 + STATE(SPR_KEEN,7,6,NULL,S_COMMKEEN9,0,0), // S_COMMKEEN8 + STATE(SPR_KEEN,8,6,NULL,S_COMMKEEN10,0,0), // S_COMMKEEN9 + STATE(SPR_KEEN,9,6,NULL,S_COMMKEEN11,0,0), // S_COMMKEEN10 + STATE(SPR_KEEN,10,6,A_KeenDie,S_COMMKEEN12,0,0),// S_COMMKEEN11 + STATE(SPR_KEEN,11,-1,NULL,S_NULL,0,0), // S_COMMKEEN12 + STATE(SPR_KEEN,12,4,NULL,S_KEENPAIN2,0,0), // S_KEENPAIN + STATE(SPR_KEEN,12,8,A_Pain,S_KEENSTND,0,0), // S_KEENPAIN2 + STATE(SPR_BBRN,0,-1,NULL,S_NULL,0,0), // S_BRAIN + STATE(SPR_BBRN,1,36,A_BrainPain,S_BRAIN,0,0), // S_BRAIN_PAIN + STATE(SPR_BBRN,0,100,A_BrainScream,S_BRAIN_DIE2,0,0), // S_BRAIN_DIE1 + STATE(SPR_BBRN,0,10,NULL,S_BRAIN_DIE3,0,0), // S_BRAIN_DIE2 + STATE(SPR_BBRN,0,10,NULL,S_BRAIN_DIE4,0,0), // S_BRAIN_DIE3 + STATE(SPR_BBRN,0,-1,A_BrainDie,S_NULL,0,0), // S_BRAIN_DIE4 + STATE(SPR_SSWV,0,10,A_Look,S_BRAINEYE,0,0), // S_BRAINEYE + STATE(SPR_SSWV,0,181,A_BrainAwake,S_BRAINEYE1,0,0), // S_BRAINEYESEE + STATE(SPR_SSWV,0,150,A_BrainSpit,S_BRAINEYE1,0,0), // S_BRAINEYE1 + STATE(SPR_BOSF,0 | FF_FULLBRIGHT,3,A_SpawnSound,S_SPAWN2,0,0), // S_SPAWN1 + STATE(SPR_BOSF,1 | FF_FULLBRIGHT,3,A_SpawnFly,S_SPAWN3,0,0), // S_SPAWN2 + STATE(SPR_BOSF,2 | FF_FULLBRIGHT,3,A_SpawnFly,S_SPAWN4,0,0), // S_SPAWN3 + STATE(SPR_BOSF,3 | FF_FULLBRIGHT,3,A_SpawnFly,S_SPAWN1,0,0), // S_SPAWN4 + STATE(SPR_FIRE,0 | FF_FULLBRIGHT,4,A_Fire,S_SPAWNFIRE2,0,0), // S_SPAWNFIRE1 + STATE(SPR_FIRE,1 | FF_FULLBRIGHT,4,A_Fire,S_SPAWNFIRE3,0,0), // S_SPAWNFIRE2 + STATE(SPR_FIRE,2 | FF_FULLBRIGHT,4,A_Fire,S_SPAWNFIRE4,0,0), // S_SPAWNFIRE3 + STATE(SPR_FIRE,3 | FF_FULLBRIGHT,4,A_Fire,S_SPAWNFIRE5,0,0), // S_SPAWNFIRE4 + STATE(SPR_FIRE,4 | FF_FULLBRIGHT,4,A_Fire,S_SPAWNFIRE6,0,0), // S_SPAWNFIRE5 + STATE(SPR_FIRE,5 | FF_FULLBRIGHT,4,A_Fire,S_SPAWNFIRE7,0,0), // S_SPAWNFIRE6 + STATE(SPR_FIRE,6 | FF_FULLBRIGHT,4,A_Fire,S_SPAWNFIRE8,0,0), // S_SPAWNFIRE7 + STATE(SPR_FIRE,7 | FF_FULLBRIGHT,4,A_Fire,S_NULL,0,0), // S_SPAWNFIRE8 + STATE(SPR_MISL,1 | FF_FULLBRIGHT,10,NULL,S_BRAINEXPLODE2,0,0), // S_BRAINEXPLODE1 + STATE(SPR_MISL,2 | FF_FULLBRIGHT,10,NULL,S_BRAINEXPLODE3,0,0), // S_BRAINEXPLODE2 + STATE(SPR_MISL,3 | FF_FULLBRIGHT,10,A_BrainExplode,S_NULL,0,0), // S_BRAINEXPLODE3 + STATE(SPR_ARM1,0,6,NULL,S_ARM1A,0,0), // S_ARM1 + STATE(SPR_ARM1,1 | FF_FULLBRIGHT,7,NULL,S_ARM1,0,0), // S_ARM1A + STATE(SPR_ARM2,0,6,NULL,S_ARM2A,0,0), // S_ARM2 + STATE(SPR_ARM2,1 | FF_FULLBRIGHT,6,NULL,S_ARM2,0,0), // S_ARM2A + STATE(SPR_BAR1,0,6,NULL,S_BAR2,0,0), // S_BAR1 + STATE(SPR_BAR1,1,6,NULL,S_BAR1,0,0), // S_BAR2 + STATE(SPR_BEXP,0 | FF_FULLBRIGHT,5,NULL,S_BEXP2,0,0), // S_BEXP + STATE(SPR_BEXP,1 | FF_FULLBRIGHT,5,A_Scream,S_BEXP3,0,0), // S_BEXP2 + STATE(SPR_BEXP,2 | FF_FULLBRIGHT,5,NULL,S_BEXP4,0,0), // S_BEXP3 + STATE(SPR_BEXP,3 | FF_FULLBRIGHT,10,A_Explode,S_BEXP5,0,0), // S_BEXP4 + STATE(SPR_BEXP,4 | FF_FULLBRIGHT,10,NULL,S_NULL,0,0), // S_BEXP5 + STATE(SPR_FCAN,0 | FF_FULLBRIGHT,4,NULL,S_BBAR2,0,0), // S_BBAR1 + STATE(SPR_FCAN,1 | FF_FULLBRIGHT,4,NULL,S_BBAR3,0,0), // S_BBAR2 + STATE(SPR_FCAN,2 | FF_FULLBRIGHT,4,NULL,S_BBAR1,0,0), // S_BBAR3 + STATE(SPR_BON1,0,6,NULL,S_BON1A,0,0), // S_BON1 + STATE(SPR_BON1,1,6,NULL,S_BON1B,0,0), // S_BON1A + STATE(SPR_BON1,2,6,NULL,S_BON1C,0,0), // S_BON1B + STATE(SPR_BON1,3,6,NULL,S_BON1D,0,0), // S_BON1C + STATE(SPR_BON1,2,6,NULL,S_BON1E,0,0), // S_BON1D + STATE(SPR_BON1,1,6,NULL,S_BON1,0,0), // S_BON1E + STATE(SPR_BON2,0,6,NULL,S_BON2A,0,0), // S_BON2 + STATE(SPR_BON2,1,6,NULL,S_BON2B,0,0), // S_BON2A + STATE(SPR_BON2,2,6,NULL,S_BON2C,0,0), // S_BON2B + STATE(SPR_BON2,3,6,NULL,S_BON2D,0,0), // S_BON2C + STATE(SPR_BON2,2,6,NULL,S_BON2E,0,0), // S_BON2D + STATE(SPR_BON2,1,6,NULL,S_BON2,0,0), // S_BON2E + STATE(SPR_BKEY,0,10,NULL,S_BKEY2,0,0), // S_BKEY + STATE(SPR_BKEY,1 | FF_FULLBRIGHT,10,NULL,S_BKEY,0,0), // S_BKEY2 + STATE(SPR_RKEY,0,10,NULL,S_RKEY2,0,0), // S_RKEY + STATE(SPR_RKEY,1 | FF_FULLBRIGHT,10,NULL,S_RKEY,0,0), // S_RKEY2 + STATE(SPR_YKEY,0,10,NULL,S_YKEY2,0,0), // S_YKEY + STATE(SPR_YKEY,1 | FF_FULLBRIGHT,10,NULL,S_YKEY,0,0), // S_YKEY2 + STATE(SPR_BSKU,0,10,NULL,S_BSKULL2,0,0), // S_BSKULL + STATE(SPR_BSKU,1 | FF_FULLBRIGHT,10,NULL,S_BSKULL,0,0), // S_BSKULL2 + STATE(SPR_RSKU,0,10,NULL,S_RSKULL2,0,0), // S_RSKULL + STATE(SPR_RSKU,1 | FF_FULLBRIGHT,10,NULL,S_RSKULL,0,0), // S_RSKULL2 + STATE(SPR_YSKU,0,10,NULL,S_YSKULL2,0,0), // S_YSKULL + STATE(SPR_YSKU,1 | FF_FULLBRIGHT,10,NULL,S_YSKULL,0,0), // S_YSKULL2 + STATE(SPR_STIM,0,-1,NULL,S_NULL,0,0), // S_STIM + STATE(SPR_MEDI,0,-1,NULL,S_NULL,0,0), // S_MEDI + STATE(SPR_SOUL,0 | FF_FULLBRIGHT,6,NULL,S_SOUL2,0,0), // S_SOUL + STATE(SPR_SOUL,1 | FF_FULLBRIGHT,6,NULL,S_SOUL3,0,0), // S_SOUL2 + STATE(SPR_SOUL,2 | FF_FULLBRIGHT,6,NULL,S_SOUL4,0,0), // S_SOUL3 + STATE(SPR_SOUL,3 | FF_FULLBRIGHT,6,NULL,S_SOUL5,0,0), // S_SOUL4 + STATE(SPR_SOUL,2 | FF_FULLBRIGHT,6,NULL,S_SOUL6,0,0), // S_SOUL5 + STATE(SPR_SOUL,1 | FF_FULLBRIGHT,6,NULL,S_SOUL,0,0), // S_SOUL6 + STATE(SPR_PINV,0 | FF_FULLBRIGHT,6,NULL,S_PINV2,0,0), // S_PINV + STATE(SPR_PINV,1 | FF_FULLBRIGHT,6,NULL,S_PINV3,0,0), // S_PINV2 + STATE(SPR_PINV,2 | FF_FULLBRIGHT,6,NULL,S_PINV4,0,0), // S_PINV3 + STATE(SPR_PINV,3 | FF_FULLBRIGHT,6,NULL,S_PINV,0,0), // S_PINV4 + STATE(SPR_PSTR,0 | FF_FULLBRIGHT,-1,NULL,S_NULL,0,0), // S_PSTR + STATE(SPR_PINS,0 | FF_FULLBRIGHT,6,NULL,S_PINS2,0,0), // S_PINS + STATE(SPR_PINS,1 | FF_FULLBRIGHT,6,NULL,S_PINS3,0,0), // S_PINS2 + STATE(SPR_PINS,2 | FF_FULLBRIGHT,6,NULL,S_PINS4,0,0), // S_PINS3 + STATE(SPR_PINS,3 | FF_FULLBRIGHT,6,NULL,S_PINS,0,0), // S_PINS4 + STATE(SPR_MEGA,0 | FF_FULLBRIGHT,6,NULL,S_MEGA2,0,0), // S_MEGA + STATE(SPR_MEGA,1 | FF_FULLBRIGHT,6,NULL,S_MEGA3,0,0), // S_MEGA2 + STATE(SPR_MEGA,2 | FF_FULLBRIGHT,6,NULL,S_MEGA4,0,0), // S_MEGA3 + STATE(SPR_MEGA,3 | FF_FULLBRIGHT,6,NULL,S_MEGA,0,0), // S_MEGA4 + STATE(SPR_SUIT,0 | FF_FULLBRIGHT,-1,NULL,S_NULL,0,0), // S_SUIT + STATE(SPR_PMAP,0 | FF_FULLBRIGHT,6,NULL,S_PMAP2,0,0), // S_PMAP + STATE(SPR_PMAP,1 | FF_FULLBRIGHT,6,NULL,S_PMAP3,0,0), // S_PMAP2 + STATE(SPR_PMAP,2 | FF_FULLBRIGHT,6,NULL,S_PMAP4,0,0), // S_PMAP3 + STATE(SPR_PMAP,3 | FF_FULLBRIGHT,6,NULL,S_PMAP5,0,0), // S_PMAP4 + STATE(SPR_PMAP,2 | FF_FULLBRIGHT,6,NULL,S_PMAP6,0,0), // S_PMAP5 + STATE(SPR_PMAP,1 | FF_FULLBRIGHT,6,NULL,S_PMAP,0,0), // S_PMAP6 + STATE(SPR_PVIS,0 | FF_FULLBRIGHT,6,NULL,S_PVIS2,0,0), // S_PVIS + STATE(SPR_PVIS,1,6,NULL,S_PVIS,0,0), // S_PVIS2 + STATE(SPR_CLIP,0,-1,NULL,S_NULL,0,0), // S_CLIP + STATE(SPR_AMMO,0,-1,NULL,S_NULL,0,0), // S_AMMO + STATE(SPR_ROCK,0,-1,NULL,S_NULL,0,0), // S_ROCK + STATE(SPR_BROK,0,-1,NULL,S_NULL,0,0), // S_BROK + STATE(SPR_CELL,0,-1,NULL,S_NULL,0,0), // S_CELL + STATE(SPR_CELP,0,-1,NULL,S_NULL,0,0), // S_CELP + STATE(SPR_SHEL,0,-1,NULL,S_NULL,0,0), // S_SHEL + STATE(SPR_SBOX,0,-1,NULL,S_NULL,0,0), // S_SBOX + STATE(SPR_BPAK,0,-1,NULL,S_NULL,0,0), // S_BPAK + STATE(SPR_BFUG,0,-1,NULL,S_NULL,0,0), // S_BFUG + STATE(SPR_MGUN,0,-1,NULL,S_NULL,0,0), // S_MGUN + STATE(SPR_CSAW,0,-1,NULL,S_NULL,0,0), // S_CSAW + STATE(SPR_LAUN,0,-1,NULL,S_NULL,0,0), // S_LAUN + STATE(SPR_PLAS,0,-1,NULL,S_NULL,0,0), // S_PLAS + STATE(SPR_SHOT,0,-1,NULL,S_NULL,0,0), // S_SHOT + STATE(SPR_SGN2,0,-1,NULL,S_NULL,0,0), // S_SHOT2 + STATE(SPR_COLU,0 | FF_FULLBRIGHT,-1,NULL,S_NULL,0,0), // S_COLU + STATE(SPR_SMT2,0,-1,NULL,S_NULL,0,0), // S_STALAG + STATE(SPR_GOR1,0,10,NULL,S_BLOODYTWITCH2,0,0), // S_BLOODYTWITCH + STATE(SPR_GOR1,1,15,NULL,S_BLOODYTWITCH3,0,0), // S_BLOODYTWITCH2 + STATE(SPR_GOR1,2,8,NULL,S_BLOODYTWITCH4,0,0), // S_BLOODYTWITCH3 + STATE(SPR_GOR1,1,6,NULL,S_BLOODYTWITCH,0,0), // S_BLOODYTWITCH4 + STATE(SPR_PLAY,13,-1,NULL,S_NULL,0,0), // S_DEADTORSO + STATE(SPR_PLAY,18,-1,NULL,S_NULL,0,0), // S_DEADBOTTOM + STATE(SPR_POL2,0,-1,NULL,S_NULL,0,0), // S_HEADSONSTICK + STATE(SPR_POL5,0,-1,NULL,S_NULL,0,0), // S_GIBS + STATE(SPR_POL4,0,-1,NULL,S_NULL,0,0), // S_HEADONASTICK + STATE(SPR_POL3,0 | FF_FULLBRIGHT,6,NULL,S_HEADCANDLES2,0,0), // S_HEADCANDLES + STATE(SPR_POL3,1 | FF_FULLBRIGHT,6,NULL,S_HEADCANDLES,0,0), // S_HEADCANDLES2 + STATE(SPR_POL1,0,-1,NULL,S_NULL,0,0), // S_DEADSTICK + STATE(SPR_POL6,0,6,NULL,S_LIVESTICK2,0,0), // S_LIVESTICK + STATE(SPR_POL6,1,8,NULL,S_LIVESTICK,0,0), // S_LIVESTICK2 + STATE(SPR_GOR2,0,-1,NULL,S_NULL,0,0), // S_MEAT2 + STATE(SPR_GOR3,0,-1,NULL,S_NULL,0,0), // S_MEAT3 + STATE(SPR_GOR4,0,-1,NULL,S_NULL,0,0), // S_MEAT4 + STATE(SPR_GOR5,0,-1,NULL,S_NULL,0,0), // S_MEAT5 + STATE(SPR_SMIT,0,-1,NULL,S_NULL,0,0), // S_STALAGTITE + STATE(SPR_COL1,0,-1,NULL,S_NULL,0,0), // S_TALLGRNCOL + STATE(SPR_COL2,0,-1,NULL,S_NULL,0,0), // S_SHRTGRNCOL + STATE(SPR_COL3,0,-1,NULL,S_NULL,0,0), // S_TALLREDCOL + STATE(SPR_COL4,0,-1,NULL,S_NULL,0,0), // S_SHRTREDCOL + STATE(SPR_CAND,0 | FF_FULLBRIGHT,-1,NULL,S_NULL,0,0), // S_CANDLESTIK + STATE(SPR_CBRA,0 | FF_FULLBRIGHT,-1,NULL,S_NULL,0,0), // S_CANDELABRA + STATE(SPR_COL6,0,-1,NULL,S_NULL,0,0), // S_SKULLCOL + STATE(SPR_TRE1,0,-1,NULL,S_NULL,0,0), // S_TORCHTREE + STATE(SPR_TRE2,0,-1,NULL,S_NULL,0,0), // S_BIGTREE + STATE(SPR_ELEC,0,-1,NULL,S_NULL,0,0), // S_TECHPILLAR + STATE(SPR_CEYE,0 | FF_FULLBRIGHT,6,NULL,S_EVILEYE2,0,0), // S_EVILEYE + STATE(SPR_CEYE,1 | FF_FULLBRIGHT,6,NULL,S_EVILEYE3,0,0), // S_EVILEYE2 + STATE(SPR_CEYE,2 | FF_FULLBRIGHT,6,NULL,S_EVILEYE4,0,0), // S_EVILEYE3 + STATE(SPR_CEYE,1 | FF_FULLBRIGHT,6,NULL,S_EVILEYE,0,0), // S_EVILEYE4 + STATE(SPR_FSKU,0 | FF_FULLBRIGHT,6,NULL,S_FLOATSKULL2,0,0), // S_FLOATSKULL + STATE(SPR_FSKU,1 | FF_FULLBRIGHT,6,NULL,S_FLOATSKULL3,0,0), // S_FLOATSKULL2 + STATE(SPR_FSKU,2 | FF_FULLBRIGHT,6,NULL,S_FLOATSKULL,0,0), // S_FLOATSKULL3 + STATE(SPR_COL5,0,14,NULL,S_HEARTCOL2,0,0), // S_HEARTCOL + STATE(SPR_COL5,1,14,NULL,S_HEARTCOL,0,0), // S_HEARTCOL2 + STATE(SPR_TBLU,0 | FF_FULLBRIGHT,4,NULL,S_BLUETORCH2,0,0), // S_BLUETORCH + STATE(SPR_TBLU,1 | FF_FULLBRIGHT,4,NULL,S_BLUETORCH3,0,0), // S_BLUETORCH2 + STATE(SPR_TBLU,2 | FF_FULLBRIGHT,4,NULL,S_BLUETORCH4,0,0), // S_BLUETORCH3 + STATE(SPR_TBLU,3 | FF_FULLBRIGHT,4,NULL,S_BLUETORCH,0,0), // S_BLUETORCH4 + STATE(SPR_TGRN,0 | FF_FULLBRIGHT,4,NULL,S_GREENTORCH2,0,0), // S_GREENTORCH + STATE(SPR_TGRN,1 | FF_FULLBRIGHT,4,NULL,S_GREENTORCH3,0,0), // S_GREENTORCH2 + STATE(SPR_TGRN,2 | FF_FULLBRIGHT,4,NULL,S_GREENTORCH4,0,0), // S_GREENTORCH3 + STATE(SPR_TGRN,3 | FF_FULLBRIGHT,4,NULL,S_GREENTORCH,0,0), // S_GREENTORCH4 + STATE(SPR_TRED,0 | FF_FULLBRIGHT,4,NULL,S_REDTORCH2,0,0), // S_REDTORCH + STATE(SPR_TRED,1 | FF_FULLBRIGHT,4,NULL,S_REDTORCH3,0,0), // S_REDTORCH2 + STATE(SPR_TRED,2 | FF_FULLBRIGHT,4,NULL,S_REDTORCH4,0,0), // S_REDTORCH3 + STATE(SPR_TRED,3 | FF_FULLBRIGHT,4,NULL,S_REDTORCH,0,0), // S_REDTORCH4 + STATE(SPR_SMBT,0 | FF_FULLBRIGHT,4,NULL,S_BTORCHSHRT2,0,0), // S_BTORCHSHRT + STATE(SPR_SMBT,1 | FF_FULLBRIGHT,4,NULL,S_BTORCHSHRT3,0,0), // S_BTORCHSHRT2 + STATE(SPR_SMBT,2 | FF_FULLBRIGHT,4,NULL,S_BTORCHSHRT4,0,0), // S_BTORCHSHRT3 + STATE(SPR_SMBT,3 | FF_FULLBRIGHT,4,NULL,S_BTORCHSHRT,0,0), // S_BTORCHSHRT4 + STATE(SPR_SMGT,0 | FF_FULLBRIGHT,4,NULL,S_GTORCHSHRT2,0,0), // S_GTORCHSHRT + STATE(SPR_SMGT,1 | FF_FULLBRIGHT,4,NULL,S_GTORCHSHRT3,0,0), // S_GTORCHSHRT2 + STATE(SPR_SMGT,2 | FF_FULLBRIGHT,4,NULL,S_GTORCHSHRT4,0,0), // S_GTORCHSHRT3 + STATE(SPR_SMGT,3 | FF_FULLBRIGHT,4,NULL,S_GTORCHSHRT,0,0), // S_GTORCHSHRT4 + STATE(SPR_SMRT,0 | FF_FULLBRIGHT,4,NULL,S_RTORCHSHRT2,0,0), // S_RTORCHSHRT + STATE(SPR_SMRT,1 | FF_FULLBRIGHT,4,NULL,S_RTORCHSHRT3,0,0), // S_RTORCHSHRT2 + STATE(SPR_SMRT,2 | FF_FULLBRIGHT,4,NULL,S_RTORCHSHRT4,0,0), // S_RTORCHSHRT3 + STATE(SPR_SMRT,3 | FF_FULLBRIGHT,4,NULL,S_RTORCHSHRT,0,0), // S_RTORCHSHRT4 + STATE(SPR_HDB1,0,-1,NULL,S_NULL,0,0), // S_HANGNOGUTS + STATE(SPR_HDB2,0,-1,NULL,S_NULL,0,0), // S_HANGBNOBRAIN + STATE(SPR_HDB3,0,-1,NULL,S_NULL,0,0), // S_HANGTLOOKDN + STATE(SPR_HDB4,0,-1,NULL,S_NULL,0,0), // S_HANGTSKULL + STATE(SPR_HDB5,0,-1,NULL,S_NULL,0,0), // S_HANGTLOOKUP + STATE(SPR_HDB6,0,-1,NULL,S_NULL,0,0), // S_HANGTNOBRAIN + STATE(SPR_POB1,0,-1,NULL,S_NULL,0,0), // S_COLONGIBS + STATE(SPR_POB2,0,-1,NULL,S_NULL,0,0), // S_SMALLPOOL + STATE(SPR_BRS1,0,-1,NULL,S_NULL,0,0), // S_BRAINSTEM + STATE(SPR_TLMP,0 | FF_FULLBRIGHT,4,NULL,S_TECHLAMP2,0,0), // S_TECHLAMP + STATE(SPR_TLMP,1 | FF_FULLBRIGHT,4,NULL,S_TECHLAMP3,0,0), // S_TECHLAMP2 + STATE(SPR_TLMP,2 | FF_FULLBRIGHT,4,NULL,S_TECHLAMP4,0,0), // S_TECHLAMP3 + STATE(SPR_TLMP,3 | FF_FULLBRIGHT,4,NULL,S_TECHLAMP,0,0), // S_TECHLAMP4 + STATE(SPR_TLP2,0 | FF_FULLBRIGHT,4,NULL,S_TECH2LAMP2,0,0), // S_TECH2LAMP + STATE(SPR_TLP2,1 | FF_FULLBRIGHT,4,NULL,S_TECH2LAMP3,0,0), // S_TECH2LAMP2 + STATE(SPR_TLP2,2 | FF_FULLBRIGHT,4,NULL,S_TECH2LAMP4,0,0), // S_TECH2LAMP3 + STATE(SPR_TLP2,3 | FF_FULLBRIGHT,4,NULL,S_TECH2LAMP,0,0) // S_TECH2LAMP4 }; -mobjinfo_t mobjinfo[NUMMOBJTYPES] = { +should_be_const mobjinfo_t mobjinfo[NUMMOBJTYPES] = { { // MT_PLAYER -1, // doomednum @@ -3173,7 +3204,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3199,7 +3230,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3225,7 +3256,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3251,7 +3282,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3277,7 +3308,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3303,7 +3334,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3329,7 +3360,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3355,7 +3386,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3381,7 +3412,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3407,7 +3438,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3433,7 +3464,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3459,7 +3490,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3485,7 +3516,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3511,7 +3542,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3537,7 +3568,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3563,7 +3594,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3589,7 +3620,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3615,7 +3646,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3641,7 +3672,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3667,7 +3698,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3693,7 +3724,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - 0, // flags +0 | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3719,7 +3750,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3745,7 +3776,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3771,7 +3802,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3797,7 +3828,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3823,7 +3854,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3849,7 +3880,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3875,7 +3906,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3901,7 +3932,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3927,7 +3958,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3953,7 +3984,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -3979,7 +4010,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4005,7 +4036,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - 0, // flags +0 | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4031,7 +4062,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - 0, // flags +0 | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4057,7 +4088,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - 0, // flags +0 | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4083,7 +4114,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - 0, // flags +0 | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4109,7 +4140,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - 0, // flags +0 | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4135,7 +4166,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - 0, // flags +0 | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4161,7 +4192,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - 0, // flags +0 | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4187,7 +4218,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - 0, // flags +0 | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4213,7 +4244,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - 0, // flags +0 | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4239,7 +4270,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4265,7 +4296,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - 0, // flags +0 | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4291,7 +4322,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4317,7 +4348,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4343,7 +4374,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4369,7 +4400,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4395,7 +4426,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4421,7 +4452,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID, // flags +MF_SOLID | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4447,7 +4478,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4473,7 +4504,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4499,7 +4530,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4525,7 +4556,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4551,7 +4582,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4577,7 +4608,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags +MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4603,7 +4634,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_NOBLOCKMAP, // flags +MF_NOBLOCKMAP | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4629,7 +4660,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_NOBLOCKMAP, // flags +MF_NOBLOCKMAP | MF_DECORATION, // flags S_NULL // raisestate }, @@ -4655,7 +4686,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { 100, // mass 0, // damage sfx_None, // activesound - MF_NOBLOCKMAP, // flags + MF_NOBLOCKMAP | MF_DECORATION, // flags S_NULL // raisestate } }; diff --git a/src/doom/info.h b/src/doom/info.h index c426d07b..bfda7e5e 100644 --- a/src/doom/info.h +++ b/src/doom/info.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -21,6 +22,8 @@ #ifndef __INFO__ #define __INFO__ +#include "doomdef.h" + // Needed for action function pointer handling. #include "d_think.h" @@ -166,7 +169,11 @@ typedef enum SPR_TLP2, NUMSPRITES -} spritenum_t; +} spritenum_t_orig; + +#include +static_assert(NUMSPRITES <= 256, ""); +typedef uint8_t spritenum_t; typedef enum { @@ -1138,22 +1145,52 @@ typedef enum S_TECH2LAMP3, S_TECH2LAMP4, NUMSTATES -} statenum_t; +} statenum_t_orig; +static_assert(NUMSTATES < 1024, ""); // just so we know +typedef uint16_t statenum_t; + +#include typedef struct { +#if !DOOM_SMALL spritenum_t sprite; int frame; int tics; // void (*action) (); actionf_t action; statenum_t nextstate; +#ifndef NO_USE_STATE_MISC int misc1; int misc2; +#endif +#else + uint8_t sprite; +#if !DOOM_SMALL + uint16_t frame; // todo highbit is a flag +#else + uint8_t frame; +#endif + int8_t xtics; + // void (*action) (); + actionf_t action; // could be trivially small + statenum_t nextstate; +#if !NO_USE_STATE_MISC + int misc1; + int misc2; +#endif +#endif } state_t; +#if !DOOM_CONST +#define state_tics(s) ((s)->tics) +#else +int state_tics(const state_t *s); +extern boolean nightmare_speeds; +#endif + +extern should_be_const state_t states[NUMSTATES]; -extern state_t states[NUMSTATES]; extern const char *sprnames[]; typedef enum { @@ -1296,36 +1333,40 @@ typedef enum { MT_MISC86, NUMMOBJTYPES -} mobjtype_t; +} mobjtype_t_orig; +static_assert(NUMMOBJTYPES<256, ""); +typedef uint8_t mobjtype_t; typedef struct { - int doomednum; - int spawnstate; - int spawnhealth; - int seestate; - int seesound; - int reactiontime; - int attacksound; - int painstate; - int painchance; - int painsound; - int meleestate; - int missilestate; - int deathstate; - int xdeathstate; - int deathsound; + isb_int16_t doomednum; + statenum_t spawnstate; + + isb_int16_t spawnhealth; + statenum_t seestate; + + isb_uint8_t seesound; + isb_int8_t reactiontime; + isb_uint8_t attacksound; + statenum_t painstate; + isb_int16_t painchance; + isb_uint8_t painsound; + statenum_t meleestate; + statenum_t missilestate; + statenum_t deathstate; + statenum_t xdeathstate; + isb_uint8_t deathsound; int speed; - int radius; - int height; + int radius; // todo wasteful + int height; // todo wasteful int mass; - int damage; - int activesound; + isb_int8_t damage; + isb_uint8_t activesound; int flags; - int raisestate; + statenum_t raisestate; } mobjinfo_t; -extern mobjinfo_t mobjinfo[NUMMOBJTYPES]; +extern should_be_const mobjinfo_t mobjinfo[NUMMOBJTYPES]; #endif diff --git a/src/doom/m_menu.c b/src/doom/m_menu.c index ee9e6719..a61968ad 100644 --- a/src/doom/m_menu.c +++ b/src/doom/m_menu.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -60,8 +61,16 @@ #include "m_menu.h" +#if PICO_DOOM && USE_PICO_NET +#include "piconet.h" +#include "net_client.h" -extern patch_t* hu_font[HU_FONTSIZE]; +#define NET_MENU 1 +#endif + +#define DEFAULTPLAYERNAME "DOOMGUY" + +extern vpatch_sequence_t hu_font; extern boolean message_dontfuckwithme; extern boolean chat_on; // in heads-up code @@ -69,26 +78,31 @@ extern boolean chat_on; // in heads-up code // // defaulted values // -int mouseSensitivity = 5; +isb_int8_t mouseSensitivity = 5; -// Show messages has default, 0 = off, 1 = on -int showMessages = 1; +// Show messages fdefault, 0 = off, 1 = on +isb_int8_t showMessages = 1; // Blocky mode, has default, 0 = high, 1 = normal -int detailLevel = 0; -int screenblocks = 9; +isb_int8_t detailLevel = 0; +#if DOOM_TINY +isb_int8_t screenblocks = 10; // default to preferred size as we don't have setting right now +#else +isb_int8_t screenblocks = 9; +#endif // temp for screenblocks (0-9) -int screenSize; +static isb_int8_t screenSize; // -1 = no quicksave slot picked! -int quickSaveSlot; +static isb_int8_t quickSaveSlot; // 1 = message to be printed int messageToPrint; // ...and here is the message string! const char *messageString; +const char *messageString2; // message x & y int messx; @@ -98,9 +112,9 @@ int messageLastMenuActive; // timed message = no input from user boolean messageNeedsInput; -void (*messageRoutine)(int response); +boolean (*messageRoutine)(int response); -char gammamsg[5][26] = +const char gammamsg[5][26] = { GAMMALVL0, GAMMALVL1, @@ -109,26 +123,39 @@ char gammamsg[5][26] = GAMMALVL4 }; -// we are going to be entering a savegame string -int saveStringEnter; -int saveSlot; // which slot to save in -int saveCharIndex; // which char we're editing -static boolean joypadSave = false; // was the save action initiated by joypad? -// old save description before edit -char saveOldString[SAVESTRINGSIZE]; +// we are going to be entering a savegame (or other) string +isb_int8_t stringEntry; +isb_int8_t saveSlot; // which slot to save in +isb_int8_t stringEntryIndex; // which char we're editing +isb_int8_t stringEntryMax; +char *stringEntryBuffer; -boolean inhelpscreens; +#if NET_MENU +char player_name[MAXPLAYERNAME]; +#endif +#if !NO_USE_SAVE +static boolean joypadSave = false; // was the save action initiated by joypad? +#endif +// old save description before edit +char stringEntryOldString[MAX(SAVESTRINGSIZE,MAXPLAYERNAME)]; + +uint8_t inhelpscreens; boolean menuactive; #define SKULLXOFF -32 #define LINEHEIGHT 16 extern boolean sendpause; -char savegamestrings[10][SAVESTRINGSIZE]; - +char savegamestrings[6][SAVESTRINGSIZE]; +#if !DOOM_TINY char endstring[160]; +#endif +#if !DOOM_TINY static boolean opldev; +#else +#define opldev false +#endif // // MENU TYPEDEFS @@ -137,16 +164,17 @@ typedef struct { // 0 = no cursor here, 1 = ok, 2 = arrows ok short status; - - char name[10]; - + + vpatchname_t name; + + // hotkey in menu + char alphaKey; + // choice = menu item #. // if status = 2, // choice=0:leftarrow,1:rightarrow void (*routine)(int choice); - // hotkey in menu - char alphaKey; } menuitem_t; @@ -155,7 +183,7 @@ typedef struct menu_s { short numitems; // # of menu items struct menu_s* prevMenu; // previous menu - menuitem_t* menuitems; // menu items + const menuitem_t* menuitems; // menu items void (*routine)(); // draw routine short x; short y; // x,y of menu @@ -168,20 +196,27 @@ short whichSkull; // which skull to draw // graphic name of skulls // warning: initializer-string for array of chars is too long -const char *skullName[2] = {"M_SKULL1","M_SKULL2"}; +vpatchname_t skullName[2] = {VPATCH_NAME(M_SKULL1), VPATCH_NAME(M_SKULL2)}; // current menudef menu_t* currentMenu; - // // PROTOTYPES // static void M_NewGame(int choice); static void M_Episode(int choice); static void M_ChooseSkill(int choice); +#if !NO_USE_LOAD static void M_LoadGame(int choice); -static void M_SaveGame(int choice); +#endif static void M_Options(int choice); +#if NET_MENU +static void M_NetGame(int choice); +static void M_NetName(int choice); +static void M_HostGame(int choice); +static void M_JoinGame(int choice); +static void M_NetGameStart(int choice); +#endif static void M_EndGame(int choice); static void M_ReadThis(int choice); static void M_ReadThis2(int choice); @@ -196,11 +231,17 @@ static void M_SizeDisplay(int choice); static void M_Sound(int choice); static void M_FinishReadThis(int choice); +#if !NO_USE_LOAD static void M_LoadSelect(int choice); -static void M_SaveSelect(int choice); -static void M_ReadSaveStrings(void); -static void M_QuickSave(void); static void M_QuickLoad(void); +#endif +#if !NO_USE_SAVE +static void M_SaveSelect(int choice); +static void M_QuickSave(void); +#endif +#if !NO_USE_LOAD +static void M_ReadSaveStrings(void); +#endif static void M_DrawMainMenu(void); static void M_DrawReadThis1(void); @@ -208,18 +249,26 @@ static void M_DrawReadThis2(void); static void M_DrawNewGame(void); static void M_DrawEpisode(void); static void M_DrawOptions(void); +#if NET_MENU +static void M_DrawNetGame(void); +static void M_DrawNetFoyer(void); +#endif static void M_DrawSound(void); +#if !NO_USE_LOAD static void M_DrawLoad(void); +#endif +#if !NO_USE_SAVE static void M_DrawSave(void); +#endif -static void M_DrawSaveLoadBorder(int x,int y); +#if !NO_USE_LOAD || !NO_USE_SAVE +static void M_DrawSaveLoadBorder(int x,int y, int l); +#endif static void M_SetupNextMenu(menu_t *menudef); static void M_DrawThermo(int x,int y,int thermWidth,int thermDot); static void M_WriteText(int x, int y, const char *string); static int M_StringWidth(const char *string); static int M_StringHeight(const char *string); -static void M_StartMessage(const char *string, void *routine, boolean input); -static void M_ClearMenus (void); @@ -231,22 +280,30 @@ enum { newgame = 0, options, +#if !NO_USE_LOAD loadgame, +#endif +#if !NO_USE_SAVE savegame, +#endif readthis, quitdoom, main_end } main_e; -menuitem_t MainMenu[]= +static menuitem_t MainMenu[]= { - {1,"M_NGAME",M_NewGame,'n'}, - {1,"M_OPTION",M_Options,'o'}, - {1,"M_LOADG",M_LoadGame,'l'}, - {1,"M_SAVEG",M_SaveGame,'s'}, + {1,VPATCH_NAME(M_NGAME),'n', M_NewGame}, + {1,VPATCH_NAME(M_OPTION), 'o', M_Options}, +#if !NO_USE_LOAD + {1,VPATCH_NAME(M_LOADG), 'l', M_LoadGame}, +#endif +#if !NO_USE_SAVE + {1,VPATCH_NAME(M_SAVEG), 's', M_SaveGame}, +#endif // Another hickup with Special edition. - {1,"M_RDTHIS",M_ReadThis,'r'}, - {1,"M_QUITG",M_QuitDOOM,'q'} + {1,VPATCH_NAME(M_RDTHIS), 'r', M_ReadThis}, + {1,VPATCH_NAME(M_QUITG),'q',M_QuitDOOM} }; menu_t MainDef = @@ -272,12 +329,12 @@ enum ep_end } episodes_e; -menuitem_t EpisodeMenu[]= +static const menuitem_t EpisodeMenu[]= { - {1,"M_EPI1", M_Episode,'k'}, - {1,"M_EPI2", M_Episode,'t'}, - {1,"M_EPI3", M_Episode,'i'}, - {1,"M_EPI4", M_Episode,'t'} + {1,VPATCH_NAME(M_EPI1),'k', M_Episode}, + {1,VPATCH_NAME(M_EPI2),'t', M_Episode}, + {1,VPATCH_NAME(M_EPI3),'i', M_Episode}, + {1,VPATCH_NAME(M_EPI4),'t', M_Episode} }; menu_t EpiDef = @@ -303,13 +360,13 @@ enum newg_end } newgame_e; -menuitem_t NewGameMenu[]= +static const menuitem_t NewGameMenu[]= { - {1,"M_JKILL", M_ChooseSkill, 'i'}, - {1,"M_ROUGH", M_ChooseSkill, 'h'}, - {1,"M_HURT", M_ChooseSkill, 'h'}, - {1,"M_ULTRA", M_ChooseSkill, 'u'}, - {1,"M_NMARE", M_ChooseSkill, 'n'} + {1,VPATCH_NAME(M_JKILL), 'i', M_ChooseSkill}, + {1,VPATCH_NAME(M_ROUGH), 'h', M_ChooseSkill}, + {1,VPATCH_NAME(M_HURT), 'h', M_ChooseSkill}, + {1,VPATCH_NAME(M_ULTRA), 'u', M_ChooseSkill}, + {1,VPATCH_NAME(M_NMARE), 'n', M_ChooseSkill} }; menu_t NewDef = @@ -329,27 +386,41 @@ menu_t NewDef = // enum { +#if NET_MENU + networkgame, +#endif endgame, messages, +#if !DOOM_TINY detail, scrnsize, option_empty1, +#endif +#if !NO_USE_MOUSE mousesens, option_empty2, +#endif soundvol, opt_end } options_e; -menuitem_t OptionsMenu[]= +static const menuitem_t OptionsMenu[]= { - {1,"M_ENDGAM", M_EndGame,'e'}, - {1,"M_MESSG", M_ChangeMessages,'m'}, - {1,"M_DETAIL", M_ChangeDetail,'g'}, - {2,"M_SCRNSZ", M_SizeDisplay,'s'}, - {-1,"",0,'\0'}, - {2,"M_MSENS", M_ChangeSensitivity,'m'}, - {-1,"",0,'\0'}, - {1,"M_SVOL", M_Sound,'s'} +#if NET_MENU + {1,VPATCH_NAME(M_NETWK),'n', M_NetGame}, +#endif + {1,VPATCH_NAME(M_ENDGAM),'e', M_EndGame}, + {1,VPATCH_NAME(M_MESSG), 'm',M_ChangeMessages}, +#if !DOOM_TINY + {1,VPATCH_NAME(M_DETAIL),'g', M_ChangeDetail}, + {2,VPATCH_NAME(M_SCRNSZ),'s', M_SizeDisplay}, + {-1,VPATCH_NAME_INVALID,'\0',0}, +#endif +#if !NO_USE_MOUSE + {2,VPATCH_NAME(M_MSENS),'m', M_ChangeSensitivity}, + {-1,VPATCH_NAME_INVALID,'\0',0}, +#endif + {1,VPATCH_NAME(M_SVOL),'s', M_Sound} }; menu_t OptionsDef = @@ -362,6 +433,64 @@ menu_t OptionsDef = 0 }; +#if NET_MENU +static const menuitem_t NetGameMenu[]= + { + {1,VPATCH_NAME(M_NAME),'n', M_NetName}, + {1,VPATCH_NAME(M_HOST),'h', M_HostGame}, + {1,VPATCH_NAME(M_HOST), 'd',M_HostGame}, + {1,VPATCH_NAME(M_HOST), 'f',M_HostGame}, + {1,VPATCH_NAME(M_JOIN), 'j',M_JoinGame}, + }; + +enum +{ + ng_name, + ng_host_normal, + ng_host_deathmatch, + ng_host_deathmatch2, + ng_join, + ng_end, +} netgame_e; + +static int8_t netgame_choice; + +menu_t NetGameDef = + { + ng_end, + &OptionsDef, + NetGameMenu, + M_DrawNetGame, + 56,47, + 0 + }; + +// +// Read This! MENU 1 & 2 +// +enum +{ + nfoyerempty1, + nfoyer_end +} net_foyer_e; + +static menuitem_t NetFoyerMenu[] = + { + {1,VPATCH_NAME_INVALID,0, M_NetGameStart} + }; + +menu_t NetFoyerDef = + { + nfoyer_end, + &NewDef, + NetFoyerMenu, + M_DrawNetFoyer, + -1,0, // force hide of skull + 0 + }; + +#endif + // // Read This! MENU 1 & 2 // @@ -371,9 +500,9 @@ enum read1_end } read_e; -menuitem_t ReadMenu1[] = +static menuitem_t ReadMenu1[] = { - {1,"",M_ReadThis2,0} + {1,VPATCH_NAME_INVALID,0, M_ReadThis2} }; menu_t ReadDef1 = @@ -392,9 +521,9 @@ enum read2_end } read_e2; -menuitem_t ReadMenu2[]= +static const menuitem_t ReadMenu2[]= { - {1,"",M_FinishReadThis,0} + {1,VPATCH_NAME_INVALID,0, M_FinishReadThis} }; menu_t ReadDef2 = @@ -419,12 +548,12 @@ enum sound_end } sound_e; -menuitem_t SoundMenu[]= +static const menuitem_t SoundMenu[]= { - {2,"M_SFXVOL",M_SfxVol,'s'}, - {-1,"",0,'\0'}, - {2,"M_MUSVOL",M_MusicVol,'m'}, - {-1,"",0,'\0'} + {2,VPATCH_NAME(M_SFXVOL), 's', M_SfxVol}, + {-1,VPATCH_NAME_INVALID,'\0',0,}, + {2,VPATCH_NAME(M_MUSVOL), 'm', M_MusicVol}, + {-1,VPATCH_NAME_INVALID,'\0',0,}, }; menu_t SoundDef = @@ -437,6 +566,7 @@ menu_t SoundDef = 0 }; +#if !NO_USE_LOAD // // LOAD GAME MENU // @@ -451,14 +581,17 @@ enum load_end } load_e; +static_assert(count_of(savegamestrings) == load_end, ""); + +// this is the only one which is mutated menuitem_t LoadMenu[]= { - {1,"", M_LoadSelect,'1'}, - {1,"", M_LoadSelect,'2'}, - {1,"", M_LoadSelect,'3'}, - {1,"", M_LoadSelect,'4'}, - {1,"", M_LoadSelect,'5'}, - {1,"", M_LoadSelect,'6'} + {1,VPATCH_NAME_INVALID, '1', M_LoadSelect}, + {1,VPATCH_NAME_INVALID, '2', M_LoadSelect}, + {1,VPATCH_NAME_INVALID, '3', M_LoadSelect}, + {1,VPATCH_NAME_INVALID, '4', M_LoadSelect}, + {1,VPATCH_NAME_INVALID, '5', M_LoadSelect}, + {1,VPATCH_NAME_INVALID, '6', M_LoadSelect} }; menu_t LoadDef = @@ -470,18 +603,20 @@ menu_t LoadDef = 80,54, 0 }; +#endif +#if !NO_USE_SAVE // // SAVE GAME MENU // menuitem_t SaveMenu[]= { - {1,"", M_SaveSelect,'1'}, - {1,"", M_SaveSelect,'2'}, - {1,"", M_SaveSelect,'3'}, - {1,"", M_SaveSelect,'4'}, - {1,"", M_SaveSelect,'5'}, - {1,"", M_SaveSelect,'6'} + {1,VPATCH_NAME_INVALID, '1', M_SaveSelect}, + {1,VPATCH_NAME_INVALID, '2', M_SaveSelect}, + {1,VPATCH_NAME_INVALID, '3', M_SaveSelect}, + {1,VPATCH_NAME_INVALID, '4', M_SaveSelect}, + {1,VPATCH_NAME_INVALID, '5', M_SaveSelect}, + {1,VPATCH_NAME_INVALID, '6', M_SaveSelect} }; menu_t SaveDef = @@ -494,13 +629,15 @@ menu_t SaveDef = 0 }; - +#endif +#if !NO_USE_LOAD // // M_ReadSaveStrings // read the strings from the savegame files // void M_ReadSaveStrings(void) { +#if !NO_FILE_ACCESS FILE *handle; int i; char name[256]; @@ -521,9 +658,24 @@ void M_ReadSaveStrings(void) fclose(handle); LoadMenu[i].status = retval == SAVESTRINGSIZE; } +#elif PICO_ON_DEVICE + flash_slot_info_t slots[load_end]; + P_SaveGameGetExistingFlashSlotAddresses(slots, count_of(slots)); + for (int i = 0;i < load_end;i++) { + if (slots[i].data) { + M_StringCopy(savegamestrings[i], (const char *)slots[i].data, SAVESTRINGSIZE); + LoadMenu[i].status = 1; + } else { + M_StringCopy(savegamestrings[i], EMPTYSTRING, SAVESTRINGSIZE); + LoadMenu[i].status = 0; + } + } +#endif } +#endif +#if !NO_USE_LOAD // // M_LoadGame & Cie. // @@ -531,41 +683,58 @@ void M_DrawLoad(void) { int i; - V_DrawPatchDirect(72, 28, - W_CacheLumpName(DEH_String("M_LOADG"), PU_CACHE)); + V_DrawPatchDirect(72, 28, VPATCH_HANDLE(VPATCH_NAME(M_LOADG))); for (i = 0;i < load_end; i++) { - M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i); + M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i, 24); M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]); } } +#endif + +#if !NO_USE_LOAD || !NO_USE_SAVE + +static char tempstring[90]; + // // Draw border for the savegame description // -void M_DrawSaveLoadBorder(int x,int y) +void M_DrawSaveLoadBorder(int x,int y, int l) { int i; V_DrawPatchDirect(x - 8, y + 7, - W_CacheLumpName(DEH_String("M_LSLEFT"), PU_CACHE)); - - for (i = 0;i < 24;i++) + VPATCH_HANDLE(VPATCH_NAME(M_LSLEFT))); + +#if !DOOM_TINY + for (i = 0;i < l;i++) { - V_DrawPatchDirect(x, y + 7, + V_DrawPatchDirect(x, y + 7, W_CacheLumpName(DEH_String("M_LSCNTR"), PU_CACHE)); - x += 8; + x += 8; } +#else + V_DrawPatchDirectN(x, y + 7, + VPATCH_HANDLE(VPATCH_NAME(M_LSCNTR)), l-1); + x += l * 8; +#endif V_DrawPatchDirect(x, y + 7, - W_CacheLumpName(DEH_String("M_LSRGHT"), PU_CACHE)); + VPATCH_HANDLE(VPATCH_NAME(M_LSRGHT))); } +#endif + +#if !NO_USE_LOAD +#if PICO_ON_DEVICE +uint8_t g_load_slot; +#endif // // User wants to load this game // @@ -574,7 +743,9 @@ void M_LoadSelect(int choice) char name[256]; M_StringCopy(name, P_SaveGameFile(choice), sizeof(name)); - +#if PICO_ON_DEVICE + g_load_slot = choice; +#endif G_LoadGame (name); M_ClearMenus (); } @@ -594,6 +765,9 @@ void M_LoadGame (int choice) M_ReadSaveStrings(); } +#endif + +#if !NO_USE_SAVE // // M_SaveGame & Cie. @@ -602,14 +776,14 @@ void M_DrawSave(void) { int i; - V_DrawPatchDirect(72, 28, W_CacheLumpName(DEH_String("M_SAVEG"), PU_CACHE)); + V_DrawPatchDirect(72, 28, VPATCH_HANDLE(VPATCH_NAME(M_SAVEG))); for (i = 0;i < load_end; i++) { - M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i); + M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i, 24); M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]); } - if (saveStringEnter) + if (stringEntry) { i = M_StringWidth(savegamestrings[saveSlot]); M_WriteText(LoadDef.x + i,LoadDef.y+LINEHEIGHT*saveSlot,"_"); @@ -624,7 +798,7 @@ void M_DoSave(int slot) G_SaveGame (slot,savegamestrings[slot]); M_ClearMenus (); - // PICK QUICKSAVE SLOT YET? + // PICK QUICKSAVE SLOT_RENDER YET? if (quickSaveSlot == -2) quickSaveSlot = slot; } @@ -635,6 +809,7 @@ void M_DoSave(int slot) // static void SetDefaultSaveName(int slot) { +#if !DOOM_TINY // map from IWAD or PWAD? if (W_IsIWADLump(maplumpinfo)) { @@ -647,6 +822,9 @@ static void SetDefaultSaveName(int slot) "%s: %s", W_WadNameForLump(maplumpinfo), maplumpinfo->name); } +#else + strcpy(savegamestrings[itemOn], "TEST"); +#endif M_ForceUppercase(savegamestrings[itemOn]); joypadSave = false; } @@ -659,7 +837,7 @@ void M_SaveSelect(int choice) int x, y; // we are going to be intercepting all chars - saveStringEnter = 1; + stringEntry = 1; // We need to turn on text input: x = LoadDef.x - 11; @@ -667,7 +845,7 @@ void M_SaveSelect(int choice) I_StartTextInput(x, y, x + 8 + 24 * 8 + 8, y + LINEHEIGHT - 2); saveSlot = choice; - M_StringCopy(saveOldString,savegamestrings[choice], SAVESTRINGSIZE); + M_StringCopy(stringEntryOldString, savegamestrings[choice], SAVESTRINGSIZE); if (!strcmp(savegamestrings[choice], EMPTYSTRING)) { savegamestrings[choice][0] = 0; @@ -677,7 +855,20 @@ void M_SaveSelect(int choice) SetDefaultSaveName(choice); } } - saveCharIndex = strlen(savegamestrings[choice]); + stringEntryBuffer = savegamestrings[choice]; + stringEntryIndex = strlen(savegamestrings[choice]); + stringEntryMax = SAVESTRINGSIZE; +} + +static void M_StringEntryEnd() { + // save using another variable since we only have two usages and they have differnet max lengths + if (stringEntryMax == SAVESTRINGSIZE) { + if (stringEntryBuffer[0]) M_DoSave(saveSlot); +#if NET_MENU + } else if (!player_name[0]) { + strcpy(player_name, DEFAULTPLAYERNAME); +#endif + } } // @@ -703,22 +894,22 @@ void M_SaveGame (int choice) // // M_QuickSave // -static char tempstring[90]; -void M_QuickSaveResponse(int key) +boolean M_QuickSaveResponse(int key) { if (key == key_menu_confirm) { M_DoSave(quickSaveSlot); - S_StartSound(NULL,sfx_swtchx); + S_StartUnpositionedSound( sfx_swtchx); } + return false; } void M_QuickSave(void) { if (!usergame) { - S_StartSound(NULL,sfx_oof); + S_StartUnpositionedSound( sfx_oof); return; } @@ -738,18 +929,22 @@ void M_QuickSave(void) M_StartMessage(tempstring, M_QuickSaveResponse, true); } +#endif + +#if !NO_USE_LOAD // // M_QuickLoad // -void M_QuickLoadResponse(int key) +boolean M_QuickLoadResponse(int key) { if (key == key_menu_confirm) { M_LoadSelect(quickSaveSlot); - S_StartSound(NULL,sfx_swtchx); + S_StartUnpositionedSound( sfx_swtchx); } + return false; } @@ -771,7 +966,7 @@ void M_QuickLoad(void) M_StartMessage(tempstring, M_QuickLoadResponse, true); } - +#endif // @@ -780,9 +975,17 @@ void M_QuickLoad(void) // void M_DrawReadThis1(void) { - inhelpscreens = true; +#if DOOM_TINY + if (gamestate == GS_FINALE) { + M_ClearMenus(); + return; + } +#endif + inhelpscreens = 2; +#if !USE_WHD V_DrawPatchDirect(0, 0, W_CacheLumpName(DEH_String("HELP2"), PU_CACHE)); +#endif } @@ -792,19 +995,35 @@ void M_DrawReadThis1(void) // void M_DrawReadThis2(void) { - inhelpscreens = true; +#if DOOM_TINY + if (gamestate == GS_FINALE) { + M_ClearMenus(); + return; + } +#endif + inhelpscreens = 1; // We only ever draw the second page if this is // gameversion == exe_doom_1_9 and gamemode == registered +#if !USE_WHD V_DrawPatchDirect(0, 0, W_CacheLumpName(DEH_String("HELP1"), PU_CACHE)); +#endif } void M_DrawReadThisCommercial(void) { - inhelpscreens = true; +#if DOOM_TINY + if (gamestate == GS_FINALE) { + M_ClearMenus(); + return; + } +#endif + inhelpscreens = 3; +#if !USE_WHD V_DrawPatchDirect(0, 0, W_CacheLumpName(DEH_String("HELP"), PU_CACHE)); +#endif } @@ -813,7 +1032,7 @@ void M_DrawReadThisCommercial(void) // void M_DrawSound(void) { - V_DrawPatchDirect (60, 38, W_CacheLumpName(DEH_String("M_SVOL"), PU_CACHE)); + V_DrawPatchDirect (60, 38, VPATCH_HANDLE(VPATCH_NAME(M_SVOL))); M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(sfx_vol+1), 16,sfxVolume); @@ -870,7 +1089,7 @@ void M_MusicVol(int choice) void M_DrawMainMenu(void) { V_DrawPatchDirect(94, 2, - W_CacheLumpName(DEH_String("M_DOOM"), PU_CACHE)); + VPATCH_HANDLE(VPATCH_NAME(M_DOOM))); } @@ -881,8 +1100,8 @@ void M_DrawMainMenu(void) // void M_DrawNewGame(void) { - V_DrawPatchDirect(96, 14, W_CacheLumpName(DEH_String("M_NEWG"), PU_CACHE)); - V_DrawPatchDirect(54, 38, W_CacheLumpName(DEH_String("M_SKILL"), PU_CACHE)); + V_DrawPatchDirect(96, 14, VPATCH_HANDLE(VPATCH_NAME(M_NEWG))); + V_DrawPatchDirect(54, 38, VPATCH_HANDLE(VPATCH_NAME(M_SKILL))); } void M_NewGame(int choice) @@ -892,10 +1111,14 @@ void M_NewGame(int choice) M_StartMessage(DEH_String(NEWGAME),NULL,false); return; } - + // Chex Quest disabled the episode select screen, as did Doom II. - if (gamemode == commercial || gameversion == exe_chex) +#if NET_MENU + EpiDef.prevMenu = &MainDef; + netgame_choice = -1; +#endif + if (gamemode == commercial || gameversion_is_chex(gameversion)) M_SetupNextMenu(&NewDef); else M_SetupNextMenu(&EpiDef); @@ -905,20 +1128,35 @@ void M_NewGame(int choice) // // M_Episode // -int epi; +isb_int8_t epi; +isb_uint8_t skill; void M_DrawEpisode(void) { - V_DrawPatchDirect(54, 38, W_CacheLumpName(DEH_String("M_EPISOD"), PU_CACHE)); + V_DrawPatchDirect(54, 38, VPATCH_HANDLE(VPATCH_NAME(M_EPISOD))); } -void M_VerifyNightmare(int key) +boolean M_FinishGameSelection() { +#if NET_MENU + if (netgame_choice >= 0) { +#if USE_PICO_NET + piconet_start_host(netgame_choice-ng_host_normal, epi+1, skill); +#endif + M_SetupNextMenu(&NetFoyerDef); + return true; + } +#endif + G_DeferedInitNew(skill,epi+1,1, false); + M_ClearMenus (); + return false; +} + +boolean M_VerifyNightmare(int key) { if (key != key_menu_confirm) - return; - - G_DeferedInitNew(nightmare,epi+1,1); - M_ClearMenus (); + return false; + skill = nightmare; + return M_FinishGameSelection(); } void M_ChooseSkill(int choice) @@ -928,9 +1166,9 @@ void M_ChooseSkill(int choice) M_StartMessage(DEH_String(NIGHTMARE),M_VerifyNightmare,true); return; } - - G_DeferedInitNew(choice,epi+1,1); - M_ClearMenus (); + + skill = choice; + M_FinishGameSelection(); } void M_Episode(int choice) @@ -952,27 +1190,38 @@ void M_Episode(int choice) // // M_Options // +#if !DOOM_TINY static const char *detailNames[2] = {"M_GDHIGH","M_GDLOW"}; -static const char *msgNames[2] = {"M_MSGOFF","M_MSGON"}; +#endif +vpatchname_t msgNames[2] = {VPATCH_NAME(M_MSGOFF), VPATCH_NAME(M_MSGON)}; void M_DrawOptions(void) { - V_DrawPatchDirect(108, 15, W_CacheLumpName(DEH_String("M_OPTTTL"), - PU_CACHE)); - + V_DrawPatchDirect(108, 15, VPATCH_HANDLE(VPATCH_NAME(M_OPTTTL))); + +#if !DOOM_TINY V_DrawPatchDirect(OptionsDef.x + 175, OptionsDef.y + LINEHEIGHT * detail, W_CacheLumpName(DEH_String(detailNames[detailLevel]), PU_CACHE)); +#else + // "Game" for Network + V_DrawPatchDirect(OptionsDef.x + 105, OptionsDef.y + LINEHEIGHT * networkgame, + VPATCH_HANDLE(VPATCH_NAME(M_GAME))); + +#endif V_DrawPatchDirect(OptionsDef.x + 120, OptionsDef.y + LINEHEIGHT * messages, - W_CacheLumpName(DEH_String(msgNames[showMessages]), - PU_CACHE)); + VPATCH_HANDLE(msgNames[showMessages])); +#if !NO_USE_MOUSE M_DrawThermo(OptionsDef.x, OptionsDef.y + LINEHEIGHT * (mousesens + 1), 10, mouseSensitivity); +#endif +#if !DOOM_TINY M_DrawThermo(OptionsDef.x,OptionsDef.y+LINEHEIGHT*(scrnsize+1), 9,screenSize); +#endif } void M_Options(int choice) @@ -980,7 +1229,163 @@ void M_Options(int choice) M_SetupNextMenu(&OptionsDef); } +#if NET_MENU +extern net_module_t net_loop_server_module; +void M_NetGameStart(int choice) { + if (choice == 0) { + // we came from enter in the menu + if (netgame_choice == ng_join) return; // ignore ENTER from join menu + } + lobby_state_t ls; + /** + * I'm dreaming of a COVID christmas + * Just like the ones I used to know + * Where the friends are missin' + * and there ain't no kissin' + * and it goes so slow, oh no. + */ + int pnum = piconet_get_lobby_state(&ls); + if (pnum >= 0) { + consoleplayer = pnum; + netgame = true; + deathmatch = ls.deathmatch; + int i = 0; + for (; i < ls.nplayers; i++) { + playeringame[i] = 1; + } + for (; i < NET_MAXPLAYERS; i++) { + playeringame[i] = 0; + } +#if PICO_DOOM_INFO + printf("PLAYERS: %d\n", ls.nplayers); +#endif + G_DeferedInitNew(ls.skill,ls.epi,1,true); + M_ClearMenus (); + D_StartPicoNetGame(); + piconet_start_game(); + } +} + +void M_DrawNetGame(void) +{ + V_DrawPatchDirect(68, 15, VPATCH_HANDLE(VPATCH_NAME(M_NETWK))); + // "Game" for Title + V_DrawPatchDirect(173, 15, + VPATCH_HANDLE(VPATCH_NAME(M_GAME))); + + M_DrawSaveLoadBorder(NetGameDef.x+81,NetGameDef.y+LINEHEIGHT*ng_name+5, MAXPLAYERNAME); + M_WriteText(NetGameDef.x+81,NetGameDef.y+LINEHEIGHT*ng_name+5,player_name); + if (stringEntry) { + int i = M_StringWidth(player_name); + M_WriteText(NetGameDef.x+81 + i,NetGameDef.y+LINEHEIGHT*ng_name+5,"_"); + } + + V_DrawPatchDirect(NetGameDef.x + 64, NetGameDef.y + LINEHEIGHT * ng_host_normal, + VPATCH_HANDLE(VPATCH_NAME(M_GAME))); + + V_DrawPatchDirect(NetGameDef.x + 63, NetGameDef.y + LINEHEIGHT * ng_host_deathmatch, + VPATCH_HANDLE(VPATCH_NAME(M_DTHMCH))); + + V_DrawPatchDirect(NetGameDef.x + 63, NetGameDef.y + LINEHEIGHT * ng_host_deathmatch2, + VPATCH_HANDLE(VPATCH_NAME(M_DTHMCH))); + + V_DrawPatchDirect(NetGameDef.x + 203, NetGameDef.y + LINEHEIGHT * ng_host_deathmatch2, + VPATCH_HANDLE(VPATCH_NAME(M_TWO))); + + V_DrawPatchDirect(NetGameDef.x + 56, NetGameDef.y + LINEHEIGHT * ng_join, + VPATCH_HANDLE(VPATCH_NAME(M_GAME))); +} + +void M_DrawNetFoyer(void) { + V_DrawPatchDirect(72, 15, VPATCH_HANDLE(VPATCH_NAME(M_NETWK))); + // "Game" for Title + V_DrawPatchDirect(177, 15, + VPATCH_HANDLE(VPATCH_NAME(M_GAME))); + int x = 40; + int y = 50; + char buf[40]; + lobby_state_t ls; + int pnum = piconet_get_lobby_state(&ls); + if (ls.status == lobby_no_connection) { + M_WriteText(46, y + LINEHEIGHT * 1, "There is no game to connect to."); + } else if (ls.status == lobby_game_started) { + if (!netgame) { + if (pnum > 0) { + M_NetGameStart(-1); + } else { + M_WriteText(50, y + LINEHEIGHT * 1, "The game has already started."); + } + } + } else if (ls.status == lobby_game_not_compatible) { + M_WriteText(29, y + LINEHEIGHT * 1, "The network game is not compatible"); + M_WriteText(70, y + LINEHEIGHT * 2, "with your WAD or client."); + } else { + for(int i=0;ilastOn = itemOn; M_ClearMenus (); D_StartTitle (); + return false; } void M_EndGame(int choice) @@ -1018,16 +1429,18 @@ void M_EndGame(int choice) choice = 0; if (!usergame) { - S_StartSound(NULL,sfx_oof); + S_StartUnpositionedSound( sfx_oof); return; } - + +#if !USE_PICO_NET if (netgame) { M_StartMessage(DEH_String(NETEND),NULL,false); return; } - +#endif + M_StartMessage(DEH_String(ENDGAME),M_EndGameResponse,true); } @@ -1061,7 +1474,7 @@ void M_FinishReadThis(int choice) // // M_QuitDOOM // -int quitsounds[8] = +static const sfxenum_t quitsounds[8] = { sfx_pldeth, sfx_dmpain, @@ -1073,7 +1486,7 @@ int quitsounds[8] = sfx_sgtatk }; -int quitsounds2[8] = +static const sfxenum_t quitsounds2[8] = { sfx_vilact, sfx_getpow, @@ -1087,25 +1500,34 @@ int quitsounds2[8] = -void M_QuitResponse(int key) +boolean M_QuitResponse(int key) { if (key != key_menu_confirm) - return; + return false; if (!netgame) { if (gamemode == commercial) - S_StartSound(NULL,quitsounds2[(gametic>>2)&7]); + S_StartUnpositionedSound( quitsounds2[(gametic>>2)&7]); else - S_StartSound(NULL,quitsounds[(gametic>>2)&7]); + S_StartUnpositionedSound( quitsounds[(gametic>>2)&7]); +#if !PICO_DOOM I_WaitVBL(105); +#endif } +#if !DOOM_TINY I_Quit (); +#else + // quitting acquires a display frame semaphore, and we may be called from within pd_end_frame which + // means we may not be able to get it, so punt to the main loop + gameaction = ga_deferredquit; +#endif + return false; } static const char *M_SelectEndMessage(void) { - const char **endmsg; + const constcharstar *endmsg; if (logical_gamemission == doom) { @@ -1126,15 +1548,17 @@ static const char *M_SelectEndMessage(void) void M_QuitDOOM(int choice) { +#if !DOOM_TINY DEH_snprintf(endstring, sizeof(endstring), "%s\n\n" DOSY, DEH_String(M_SelectEndMessage())); M_StartMessage(endstring,M_QuitResponse,true); +#else + // one less \n as M_StartMessage2 adds a newline between + M_StartMessage2(M_SelectEndMessage(),M_QuitResponse,true, "\n" DOSY); +#endif } - - - void M_ChangeSensitivity(int choice) { switch(choice) @@ -1210,35 +1634,48 @@ M_DrawThermo int i; xx = x; - V_DrawPatchDirect(xx, y, W_CacheLumpName(DEH_String("M_THERML"), PU_CACHE)); + V_DrawPatchDirect(xx, y, VPATCH_HANDLE(VPATCH_NAME(M_THERML))); xx += 8; - for (i=0;i= HU_FONTSIZE) w += 4; else - w += SHORT (hu_font[c]->width); + w += vpatch_width(resolve_vpatch_handle(vpatch_n(hu_font, c))); } return w; @@ -1270,7 +1707,7 @@ int M_StringHeight(const char* string) { size_t i; int h; - int height = SHORT(hu_font[0]->height); + int height = vpatch_height(resolve_vpatch_handle(vpatch_n(hu_font, 0))); h = height; for (i = 0;i < strlen(string);i++) @@ -1320,10 +1757,10 @@ M_WriteText continue; } - w = SHORT (hu_font[c]->width); + w = vpatch_width(resolve_vpatch_handle(vpatch_n(hu_font,c))); if (cx+w > SCREENWIDTH) break; - V_DrawPatchDirect(cx, cy, hu_font[c]); + V_DrawPatchDirect(cx, cy, vpatch_n(hu_font, c)); cx+=w; } } @@ -1383,7 +1820,7 @@ boolean M_Responder (event_t* ev) } else { - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); M_QuitDOOM(0); } @@ -1394,7 +1831,8 @@ boolean M_Responder (event_t* ev) ch = 0; key = -1; - + +#if !NO_USE_JOYSTICK if (ev->type == ev_joystick) { // Simulate key presses from joystick events to interact with the menu. @@ -1433,17 +1871,19 @@ boolean M_Responder (event_t* ev) key = key_menu_confirm; } // Simulate pressing "Enter" when we are supplying a save slot name - else if (saveStringEnter) + else if (stringEntry) { key = KEY_ENTER; } else { +#if !NO_USE_SAVE // if selecting a save slot via joypad, set a flag if (currentMenu == &SaveDef) { joypadSave = true; } +#endif key = key_menu_forward; } joywait = I_GetTime() + 5; @@ -1456,7 +1896,7 @@ boolean M_Responder (event_t* ev) key = key_menu_abort; } // If user was entering a save name, back out - else if (saveStringEnter) + else if (stringEntry) { key = KEY_ESCAPE; } @@ -1474,7 +1914,9 @@ boolean M_Responder (event_t* ev) } } else +#endif { +#if !NO_USE_MOUSE if (ev->type == ev_mouse && mousewait < I_GetTime()) { mousey += ev->data3; @@ -1518,6 +1960,7 @@ boolean M_Responder (event_t* ev) } } else +#endif { if (ev->type == ev_keydown) { @@ -1530,31 +1973,31 @@ boolean M_Responder (event_t* ev) if (key == -1) return false; +#if !NO_USE_SAVE // Save Game string input - if (saveStringEnter) + if (stringEntry) { switch(key) { case KEY_BACKSPACE: - if (saveCharIndex > 0) + if (stringEntryIndex > 0) { - saveCharIndex--; - savegamestrings[saveSlot][saveCharIndex] = 0; + stringEntryIndex--; + stringEntryBuffer[stringEntryIndex] = 0; } break; case KEY_ESCAPE: - saveStringEnter = 0; + stringEntry = 0; I_StopTextInput(); - M_StringCopy(savegamestrings[saveSlot], saveOldString, - SAVESTRINGSIZE); + M_StringCopy(stringEntryBuffer, stringEntryOldString, + stringEntryMax); break; case KEY_ENTER: - saveStringEnter = 0; + stringEntry = 0; I_StopTextInput(); - if (savegamestrings[saveSlot][0]) - M_DoSave(saveSlot); + M_StringEntryEnd(); break; default: @@ -1588,18 +2031,18 @@ boolean M_Responder (event_t* ev) } if (ch >= 32 && ch <= 127 && - saveCharIndex < SAVESTRINGSIZE-1 && - M_StringWidth(savegamestrings[saveSlot]) < - (SAVESTRINGSIZE-2)*8) + stringEntryIndex < stringEntryMax - 1 && + M_StringWidth(stringEntryBuffer) < + (stringEntryMax-2)*8) { - savegamestrings[saveSlot][saveCharIndex++] = ch; - savegamestrings[saveSlot][saveCharIndex] = 0; + stringEntryBuffer[stringEntryIndex++] = ch; + stringEntryBuffer[stringEntryIndex] = 0; } break; } return true; } - +#endif // Take care of any messages that need input if (messageToPrint) { @@ -1614,30 +2057,34 @@ boolean M_Responder (event_t* ev) menuactive = messageLastMenuActive; messageToPrint = 0; - if (messageRoutine) - messageRoutine(key); - - menuactive = false; - S_StartSound(NULL,sfx_swtchx); + if (messageRoutine) { + menuactive = messageRoutine(key); + } else { + menuactive = false; + } + S_StartUnpositionedSound( sfx_swtchx); return true; } +#if !NO_SCREENSHOT if ((devparm && key == key_menu_help) || (key != 0 && key == key_menu_screenshot)) { G_ScreenShot (); return true; } +#endif // F-Keys if (!menuactive) { +#if !DOOM_TINY if (key == key_menu_decscreen) // Screen size down { if (automapactive || chat_on) return false; M_SizeDisplay(0); - S_StartSound(NULL,sfx_stnmov); + S_StartUnpositionedSound( sfx_stnmov); return true; } else if (key == key_menu_incscreen) // Screen size up @@ -1645,10 +2092,12 @@ boolean M_Responder (event_t* ev) if (automapactive || chat_on) return false; M_SizeDisplay(1); - S_StartSound(NULL,sfx_stnmov); + S_StartUnpositionedSound( sfx_stnmov); return true; } - else if (key == key_menu_help) // Help key + else +#endif + if (key == key_menu_help) // Help key { M_StartControlPanel (); @@ -1658,64 +2107,74 @@ boolean M_Responder (event_t* ev) currentMenu = &ReadDef1; itemOn = 0; - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); return true; } +#if !NO_USE_SAVE else if (key == key_menu_save) // Save { M_StartControlPanel(); - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); M_SaveGame(0); return true; } +#endif +#if !NO_USE_LOAD else if (key == key_menu_load) // Load { M_StartControlPanel(); - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); M_LoadGame(0); return true; } +#endif else if (key == key_menu_volume) // Sound Volume { M_StartControlPanel (); currentMenu = &SoundDef; itemOn = sfx_vol; - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); return true; } +#if !DOOM_TINY else if (key == key_menu_detail) // Detail toggle { M_ChangeDetail(0); - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); return true; } +#endif +#if !NO_USE_SAVE else if (key == key_menu_qsave) // Quicksave { - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); M_QuickSave(); return true; } +#endif else if (key == key_menu_endgame) // End game { - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); M_EndGame(0); return true; } else if (key == key_menu_messages) // Toggle messages { M_ChangeMessages(0); - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); return true; } +#if !NO_USE_LOAD else if (key == key_menu_qload) // Quickload { - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); M_QuickLoad(); return true; } +#endif else if (key == key_menu_quit) // Quit DOOM { - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); M_QuitDOOM(0); return true; } @@ -1725,7 +2184,11 @@ boolean M_Responder (event_t* ev) if (usegamma > 4) usegamma = 0; players[consoleplayer].message = DEH_String(gammamsg[usegamma]); +#if !USE_WHD I_SetPalette (W_CacheLumpName (DEH_String("PLAYPAL"),PU_CACHE)); +#else + I_SetPaletteNum(0); +#endif return true; } } @@ -1736,7 +2199,7 @@ boolean M_Responder (event_t* ev) if (key == key_menu_activate) { M_StartControlPanel (); - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); return true; } return false; @@ -1753,7 +2216,7 @@ boolean M_Responder (event_t* ev) if (itemOn+1 > currentMenu->numitems-1) itemOn = 0; else itemOn++; - S_StartSound(NULL,sfx_pstop); + S_StartUnpositionedSound( sfx_pstop); } while(currentMenu->menuitems[itemOn].status==-1); return true; @@ -1767,7 +2230,7 @@ boolean M_Responder (event_t* ev) if (!itemOn) itemOn = currentMenu->numitems-1; else itemOn--; - S_StartSound(NULL,sfx_pstop); + S_StartUnpositionedSound( sfx_pstop); } while(currentMenu->menuitems[itemOn].status==-1); return true; @@ -1779,7 +2242,7 @@ boolean M_Responder (event_t* ev) if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) { - S_StartSound(NULL,sfx_stnmov); + S_StartUnpositionedSound( sfx_stnmov); currentMenu->menuitems[itemOn].routine(0); } return true; @@ -1791,7 +2254,7 @@ boolean M_Responder (event_t* ev) if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) { - S_StartSound(NULL,sfx_stnmov); + S_StartUnpositionedSound( sfx_stnmov); currentMenu->menuitems[itemOn].routine(1); } return true; @@ -1807,12 +2270,12 @@ boolean M_Responder (event_t* ev) if (currentMenu->menuitems[itemOn].status == 2) { currentMenu->menuitems[itemOn].routine(1); // right arrow - S_StartSound(NULL,sfx_stnmov); + S_StartUnpositionedSound( sfx_stnmov); } else { currentMenu->menuitems[itemOn].routine(itemOn); - S_StartSound(NULL,sfx_pistol); + S_StartUnpositionedSound( sfx_pistol); } } return true; @@ -1823,7 +2286,7 @@ boolean M_Responder (event_t* ev) currentMenu->lastOn = itemOn; M_ClearMenus (); - S_StartSound(NULL,sfx_swtchx); + S_StartUnpositionedSound( sfx_swtchx); return true; } else if (key == key_menu_back) @@ -1835,7 +2298,7 @@ boolean M_Responder (event_t* ev) { currentMenu = currentMenu->prevMenu; itemOn = currentMenu->lastOn; - S_StartSound(NULL,sfx_swtchn); + S_StartUnpositionedSound( sfx_swtchn); } return true; } @@ -1851,7 +2314,7 @@ boolean M_Responder (event_t* ev) if (currentMenu->menuitems[i].alphaKey == ch) { itemOn = i; - S_StartSound(NULL,sfx_pstop); + S_StartUnpositionedSound( sfx_pstop); return true; } } @@ -1861,7 +2324,7 @@ boolean M_Responder (event_t* ev) if (currentMenu->menuitems[i].alphaKey == ch) { itemOn = i; - S_StartSound(NULL,sfx_pstop); + S_StartUnpositionedSound( sfx_pstop); return true; } } @@ -1927,6 +2390,7 @@ static void M_DrawOPLDev(void) // void M_Drawer (void) { +#if !NO_DRAW_MENU static short x; static short y; unsigned int i; @@ -1935,22 +2399,26 @@ void M_Drawer (void) const char *name; int start; - inhelpscreens = false; +#if DOOM_TINY + int oldinhelpscreens = inhelpscreens; +#endif + inhelpscreens = 0; // Horiz. & Vertically center string and print it. if (messageToPrint) { start = 0; - y = SCREENHEIGHT/2 - M_StringHeight(messageString) / 2; - while (messageString[start] != '\0') + y = SCREENHEIGHT/2 - (M_StringHeight(messageString) + (messageString2?M_StringHeight(messageString2):0)) / 2; + const char *current_message = messageString; + while (current_message[start] != '\0') { boolean foundnewline = false; - for (i = 0; messageString[start + i] != '\0'; i++) + for (i = 0; current_message[start + i] != '\0'; i++) { - if (messageString[start + i] == '\n') + if (current_message[start + i] == '\n') { - M_StringCopy(string, messageString + start, + M_StringCopy(string, current_message + start, sizeof(string)); if (i < sizeof(string)) { @@ -1965,14 +2433,20 @@ void M_Drawer (void) if (!foundnewline) { - M_StringCopy(string, messageString + start, sizeof(string)); + M_StringCopy(string, current_message + start, sizeof(string)); start += strlen(string); } x = SCREENWIDTH/2 - M_StringWidth(string) / 2; M_WriteText(x, y, string); - y += SHORT(hu_font[0]->height); - } + y += vpatch_height(resolve_vpatch_handle(vpatch_n(hu_font, 0))); + + // we can move from messageString to messageString2 between lines only + if (messageString[start] == '\0' && messageString2 && current_message == messageString) { + current_message = messageString2; + start = 0; + } + } return; } @@ -1995,20 +2469,34 @@ void M_Drawer (void) for (i=0;imenuitems[i].name); if (name[0]) { V_DrawPatchDirect (x, y, W_CacheLumpName(name, PU_CACHE)); } +#else + if (currentMenu->menuitems[i].name) { + V_DrawPatchDirect (x, y, currentMenu->menuitems[i].name); + } +#endif y += LINEHEIGHT; } // DRAW SKULL - V_DrawPatchDirect(x + SKULLXOFF, currentMenu->y - 5 + itemOn*LINEHEIGHT, - W_CacheLumpName(DEH_String(skullName[whichSkull]), - PU_CACHE)); +#if DOOM_TINY + // we don't want to draw skull here immediately as it is split across screens, and whilst we could clip, it is simpler + // to wait for one more frame and draw it to an overlay + if (inhelpscreens) { + if (!oldinhelpscreens) return; + } +#endif + if (x >= 0) + V_DrawPatchDirect(x + SKULLXOFF, currentMenu->y - 5 + itemOn*LINEHEIGHT, + VPATCH_HANDLE(skullName[whichSkull])); + #endif } @@ -2040,6 +2528,32 @@ void M_SetupNextMenu(menu_t *menudef) // void M_Ticker (void) { +#if NET_MENU +#if 0 + static boolean launched; + if (!launched) { + printf("LOOPo\n"); + launched = true; + menuactive = 1; + M_JoinGame(ng_join); + } +#endif + + if (menuactive && currentMenu == &NetFoyerDef) { +#if USE_PICO_NET + static uint8_t repeat; + if (++repeat == 35) { + piconet_client_check_for_dropped_connection(); + repeat = 0; + } +#endif + } else { +#if USE_PICO_NET + // todo obviously don't stop if we're in game + if (!netgame) piconet_stop(); +#endif + } +#endif if (--skullAnimCounter <= 0) { whichSkull ^= 1; @@ -2054,15 +2568,21 @@ void M_Ticker (void) void M_Init (void) { currentMenu = &MainDef; - menuactive = 0; itemOn = currentMenu->lastOn; - whichSkull = 0; skullAnimCounter = 10; screenSize = screenblocks - 3; - messageToPrint = 0; - messageString = NULL; messageLastMenuActive = menuactive; quickSaveSlot = -1; +#if !DOOM_TINY // already 0 + menuactive = 0; + whichSkull = 0; + messageToPrint = 0; + messageString = NULL; + messageString2 = NULL; +#endif +#if NET_MENU + strcpy(player_name, DEFAULTPLAYERNAME); +#endif // Here we could catch other version dependencies, // like HELP1/2, and four episodes. @@ -2101,11 +2621,13 @@ void M_Init (void) EpiDef.numitems--; } // chex.exe shows only one episode. - else if (gameversion == exe_chex) + else if (gameversion_is_chex(gameversion)) { EpiDef.numitems = 1; } +#if !DOOM_TINY opldev = M_CheckParm("-opldev") > 0; +#endif } diff --git a/src/doom/m_menu.h b/src/doom/m_menu.h index ce41db39..3fd3c43a 100644 --- a/src/doom/m_menu.h +++ b/src/doom/m_menu.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -50,12 +51,15 @@ void M_Init (void); // Called by intro code to force menu up upon a keypress, // does nothing if menu is already up. void M_StartControlPanel (void); +void M_StartMessage(const char *string, boolean (*routine)(int), boolean input); +void M_StartMessage2(const char *string, boolean (*routine)(int), boolean input, const char *string2); +#if !NO_USE_SAVE +void M_SaveGame(int choice); +#endif +void M_ClearMenus (void); - - -extern int detailLevel; -extern int screenblocks; - - +extern uint8_t inhelpscreens; +extern isb_int8_t detailLevel; +extern isb_int8_t screenblocks; #endif diff --git a/src/doom/p_ceilng.c b/src/doom/p_ceilng.c index d1e61c89..9d1bf8b9 100644 --- a/src/doom/p_ceilng.c +++ b/src/doom/p_ceilng.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -35,7 +36,7 @@ // -ceiling_t* activeceilings[MAXCEILINGS]; +shortptr_t/*ceiling_t**/ activeceilings[MAXCEILINGS]; // @@ -191,8 +192,8 @@ EV_DoCeiling rtn = 1; ceiling = Z_Malloc (sizeof(*ceiling), PU_LEVSPEC, 0); P_AddThinker (&ceiling->thinker); - sec->specialdata = ceiling; - ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling; + sec->specialdata = ptr_to_shortptr(ceiling); + ceiling->thinker.function = ThinkF_T_MoveCeiling; ceiling->sector = sec; ceiling->crush = false; @@ -200,8 +201,8 @@ EV_DoCeiling { case fastCrushAndRaise: ceiling->crush = true; - ceiling->topheight = sec->ceilingheight; - ceiling->bottomheight = sec->floorheight + (8*FRACUNIT); + ceiling->topheight = sector_ceilingheight(sec); + ceiling->bottomheight = sector_floorheight(sec) + (8*FRACUNIT); ceiling->direction = -1; ceiling->speed = CEILSPEED * 2; break; @@ -209,10 +210,10 @@ EV_DoCeiling case silentCrushAndRaise: case crushAndRaise: ceiling->crush = true; - ceiling->topheight = sec->ceilingheight; + ceiling->topheight = sector_ceilingheight(sec); case lowerAndCrush: case lowerToFloor: - ceiling->bottomheight = sec->floorheight; + ceiling->bottomheight = sector_floorheight(sec); if (type != lowerToFloor) ceiling->bottomheight += 8*FRACUNIT; ceiling->direction = -1; @@ -243,9 +244,9 @@ void P_AddActiveCeiling(ceiling_t* c) for (i = 0; i < MAXCEILINGS;i++) { - if (activeceilings[i] == NULL) + if (!activeceilings[i]) { - activeceilings[i] = c; + activeceilings[i] = ceiling_to_shortptr(c); return; } } @@ -262,11 +263,12 @@ void P_RemoveActiveCeiling(ceiling_t* c) for (i = 0;i < MAXCEILINGS;i++) { - if (activeceilings[i] == c) + ceiling_t *ci = shortptr_to_ceiling(activeceilings[i]); + if (ci == c) { - activeceilings[i]->sector->specialdata = NULL; - P_RemoveThinker (&activeceilings[i]->thinker); - activeceilings[i] = NULL; + ci->sector->specialdata = 0; + P_RemoveThinker (&ci->thinker); + activeceilings[i] = 0; break; } } @@ -283,14 +285,13 @@ void P_ActivateInStasisCeiling(line_t* line) for (i = 0;i < MAXCEILINGS;i++) { - if (activeceilings[i] - && (activeceilings[i]->tag == line->tag) - && (activeceilings[i]->direction == 0)) - { - activeceilings[i]->direction = activeceilings[i]->olddirection; - activeceilings[i]->thinker.function.acp1 - = (actionf_p1)T_MoveCeiling; - } + if (activeceilings[i]) { + ceiling_t *ci = shortptr_to_ceiling(activeceilings[i]); + if (ci->tag == line_tag(line) && ci->direction == 0) { + ci->direction = ci->olddirection; + ci->thinker.function = ThinkF_T_MoveCeiling; + } + } } } @@ -308,17 +309,16 @@ int EV_CeilingCrushStop(line_t *line) rtn = 0; for (i = 0;i < MAXCEILINGS;i++) { - if (activeceilings[i] - && (activeceilings[i]->tag == line->tag) - && (activeceilings[i]->direction != 0)) - { - activeceilings[i]->olddirection = activeceilings[i]->direction; - activeceilings[i]->thinker.function.acv = (actionf_v)NULL; - activeceilings[i]->direction = 0; // in-stasis - rtn = 1; + if (activeceilings[i]) { + ceiling_t *ci = shortptr_to_ceiling(activeceilings[i]); + if (ci->tag == line_tag(line) && ci->direction != 0) { + ci->olddirection = ci->direction; + ci->thinker.function = ThinkF_NULL; + ci->direction = 0; // in-stasis + rtn = 1; + } } } - return rtn; } diff --git a/src/doom/p_doors.c b/src/doom/p_doors.c index 6f01043d..7191cacb 100644 --- a/src/doom/p_doors.c +++ b/src/doom/p_doors.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -110,7 +111,7 @@ void T_VerticalDoor (vldoor_t* door) // DOWN res = T_MovePlane(door->sector, door->speed, - door->sector->floorheight, + sector_floorheight(door->sector), false,1,door->direction); if (res == pastdest) { @@ -118,14 +119,14 @@ void T_VerticalDoor (vldoor_t* door) { case vld_blazeRaise: case vld_blazeClose: - door->sector->specialdata = NULL; + door->sector->specialdata = 0; P_RemoveThinker (&door->thinker); // unlink and free S_StartSound(&door->sector->soundorg, sfx_bdcls); break; case vld_normal: case vld_close: - door->sector->specialdata = NULL; + door->sector->specialdata = 0; P_RemoveThinker (&door->thinker); // unlink and free break; @@ -174,7 +175,7 @@ void T_VerticalDoor (vldoor_t* door) case vld_close30ThenOpen: case vld_blazeOpen: case vld_open: - door->sector->specialdata = NULL; + door->sector->specialdata = 0; P_RemoveThinker (&door->thinker); // unlink and free break; @@ -200,12 +201,12 @@ EV_DoLockedDoor { player_t* p; - p = thing->player; + p = mobj_player(thing); if (!p) return 0; - switch(line->special) + switch(line_special(line)) { case 99: // Blue Lock case 133: @@ -214,7 +215,7 @@ EV_DoLockedDoor if (!p->cards[it_bluecard] && !p->cards[it_blueskull]) { p->message = DEH_String(PD_BLUEO); - S_StartSound(NULL,sfx_oof); + S_StartUnpositionedSound( sfx_oof); return 0; } break; @@ -226,7 +227,7 @@ EV_DoLockedDoor if (!p->cards[it_redcard] && !p->cards[it_redskull]) { p->message = DEH_String(PD_REDO); - S_StartSound(NULL,sfx_oof); + S_StartUnpositionedSound( sfx_oof); return 0; } break; @@ -239,7 +240,7 @@ EV_DoLockedDoor !p->cards[it_yellowskull]) { p->message = DEH_String(PD_YELLOWO); - S_StartSound(NULL,sfx_oof); + S_StartUnpositionedSound( sfx_oof); return 0; } break; @@ -272,9 +273,9 @@ EV_DoDoor rtn = 1; door = Z_Malloc (sizeof(*door), PU_LEVSPEC, 0); P_AddThinker (&door->thinker); - sec->specialdata = door; + sec->specialdata = ptr_to_shortptr(door); - door->thinker.function.acp1 = (actionf_p1) T_VerticalDoor; + door->thinker.function = ThinkF_T_VerticalDoor; door->sector = sec; door->type = type; door->topwait = VDOORWAIT; @@ -298,7 +299,7 @@ EV_DoDoor break; case vld_close30ThenOpen: - door->topheight = sec->ceilingheight; + door->topheight = sector_ceilingheight(sec); door->direction = -1; S_StartSound(&door->sector->soundorg, sfx_dorcls); break; @@ -309,7 +310,7 @@ EV_DoDoor door->topheight = P_FindLowestCeilingSurrounding(sec); door->topheight -= 4*FRACUNIT; door->speed = VDOORSPEED * 4; - if (door->topheight != sec->ceilingheight) + if (door->topheight != sector_ceilingheight(sec)) S_StartSound(&door->sector->soundorg, sfx_bdopn); break; @@ -318,7 +319,7 @@ EV_DoDoor door->direction = 1; door->topheight = P_FindLowestCeilingSurrounding(sec); door->topheight -= 4*FRACUNIT; - if (door->topheight != sec->ceilingheight) + if (door->topheight != sector_ceilingheight(sec)) S_StartSound(&door->sector->soundorg, sfx_doropn); break; @@ -342,14 +343,12 @@ EV_VerticalDoor player_t* player; sector_t* sec; vldoor_t* door; - int side; - - side = 0; // only front sides can be used + const int side = 0; // only front sides can be used // Check for locks - player = thing->player; + player = mobj_player(thing); - switch(line->special) + switch(line_special(line)) { case 26: // Blue Lock case 32: @@ -359,7 +358,7 @@ EV_VerticalDoor if (!player->cards[it_bluecard] && !player->cards[it_blueskull]) { player->message = DEH_String(PD_BLUEK); - S_StartSound(NULL,sfx_oof); + S_StartUnpositionedSound( sfx_oof); return; } break; @@ -373,7 +372,7 @@ EV_VerticalDoor !player->cards[it_yellowskull]) { player->message = DEH_String(PD_YELLOWK); - S_StartSound(NULL,sfx_oof); + S_StartUnpositionedSound( sfx_oof); return; } break; @@ -386,7 +385,7 @@ EV_VerticalDoor if (!player->cards[it_redcard] && !player->cards[it_redskull]) { player->message = DEH_String(PD_REDK); - S_StartSound(NULL,sfx_oof); + S_StartUnpositionedSound( sfx_oof); return; } break; @@ -394,17 +393,17 @@ EV_VerticalDoor // if the sector has an active thinker, use it - if (line->sidenum[side^1] == -1) + if (line_onesided(line)) { I_Error("EV_VerticalDoor: DR special type on 1-sided linedef"); } - sec = sides[ line->sidenum[side^1]] .sector; + sec = side_sector(sidenum_to_side(line_sidenum(line, side^1))); if (sec->specialdata) { - door = sec->specialdata; - switch(line->special) + door = shortptr_to_ptr(sec->specialdata); + switch(line_special(line)) { case 1: // ONLY FOR "RAISE" DOORS, NOT "OPEN"s case 26: @@ -415,18 +414,18 @@ EV_VerticalDoor door->direction = 1; // go back up else { - if (!thing->player) + if (!mobj_full(thing)->sp_player) return; // JDC: bad guys never close doors // When is a door not a door? // In Vanilla, door->direction is set, even though // "specialdata" might not actually point at a door. - if (door->thinker.function.acp1 == (actionf_p1) T_VerticalDoor) + if (door->thinker.function == ThinkF_T_VerticalDoor) { door->direction = -1; // start going down immediately } - else if (door->thinker.function.acp1 == (actionf_p1) T_PlatRaise) + else if (door->thinker.function == ThinkF_T_PlatRaise) { // Erm, this is a plat, not a door. // This notably causes a problem in ep1-0500.lmp where @@ -444,7 +443,7 @@ EV_VerticalDoor { // This isn't a door OR a plat. Now we're in trouble. - fprintf(stderr, "EV_VerticalDoor: Tried to close " + stderr_print( "EV_VerticalDoor: Tried to close " "something that wasn't a door.\n"); // Try closing it anyway. At least it will work on 32-bit @@ -458,7 +457,7 @@ EV_VerticalDoor } // for proper sound - switch(line->special) + switch(line_special(line)) { case 117: // BLAZING DOOR RAISE case 118: // BLAZING DOOR OPEN @@ -479,14 +478,14 @@ EV_VerticalDoor // new door thinker door = Z_Malloc (sizeof(*door), PU_LEVSPEC, 0); P_AddThinker (&door->thinker); - sec->specialdata = door; - door->thinker.function.acp1 = (actionf_p1) T_VerticalDoor; + sec->specialdata = ptr_to_shortptr(door); + door->thinker.function = ThinkF_T_VerticalDoor; door->sector = sec; door->direction = 1; door->speed = VDOORSPEED; door->topwait = VDOORWAIT; - switch(line->special) + switch(line_special(line)) { case 1: case 26: @@ -500,7 +499,7 @@ EV_VerticalDoor case 33: case 34: door->type = vld_open; - line->special = 0; + clear_line_special(line); break; case 117: // blazing door raise @@ -509,7 +508,7 @@ EV_VerticalDoor break; case 118: // blazing door open door->type = vld_blazeOpen; - line->special = 0; + clear_line_special(line); door->speed = VDOORSPEED*4; break; } @@ -531,10 +530,10 @@ void P_SpawnDoorCloseIn30 (sector_t* sec) P_AddThinker (&door->thinker); - sec->specialdata = door; + sec->specialdata = ptr_to_shortptr(door); sec->special = 0; - door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + door->thinker.function = ThinkF_T_VerticalDoor; door->sector = sec; door->direction = 0; door->type = vld_normal; @@ -556,10 +555,10 @@ P_SpawnDoorRaiseIn5Mins P_AddThinker (&door->thinker); - sec->specialdata = door; + sec->specialdata = ptr_to_shortptr(door); sec->special = 0; - door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + door->thinker.function = ThinkF_T_VerticalDoor; door->sector = sec; door->direction = 2; door->type = vld_raiseIn5Mins; diff --git a/src/doom/p_enemy.c b/src/doom/p_enemy.c index 1f48fe90..3fb9db4c 100644 --- a/src/doom/p_enemy.c +++ b/src/doom/p_enemy.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -60,21 +61,17 @@ typedef enum // // P_NewChaseDir related LUT. // -dirtype_t opposite[] = +static const dirtype_t opposite[] = { DI_WEST, DI_SOUTHWEST, DI_SOUTH, DI_SOUTHEAST, DI_EAST, DI_NORTHEAST, DI_NORTH, DI_NORTHWEST, DI_NODIR }; -dirtype_t diags[] = +static const dirtype_t diags[] = { DI_NORTHWEST, DI_NORTHEAST, DI_SOUTHWEST, DI_SOUTHEAST }; - - - - void A_Fall (mobj_t *actor); @@ -103,22 +100,20 @@ P_RecursiveSound int i; line_t* check; sector_t* other; - + + assert(soundblocks <= 1); // wake up all monsters in this sector - if (sec->validcount == validcount - && sec->soundtraversed <= soundblocks+1) - { + if (sector_validcount_update_check(sec, validcount) && sec->soundtraversed <= soundblocks+1) { return; // already flooded } - sec->validcount = validcount; sec->soundtraversed = soundblocks+1; - sec->soundtarget = soundtarget; + sec->soundtarget = mobj_to_shortptr(soundtarget); for (i=0 ;ilinecount ; i++) { - check = sec->lines[i]; - if (! (check->flags & ML_TWOSIDED) ) + check = sector_line(sec, i); + if (! (line_flags(check) & ML_TWOSIDED) ) continue; P_LineOpening (check); @@ -126,12 +121,12 @@ P_RecursiveSound if (openrange <= 0) continue; // closed door - if ( sides[ check->sidenum[0] ].sector == sec) - other = sides[ check->sidenum[1] ] .sector; + if ( side_sector(sidenum_to_side(line_sidenum(check, 0))) == sec) + other = side_sector(sidenum_to_side(line_sidenum(check, 1))); else - other = sides[ check->sidenum[0] ].sector; + other = side_sector(sidenum_to_side(line_sidenum(check, 0))); - if (check->flags & ML_SOUNDBLOCK) + if (line_flags(check) & ML_SOUNDBLOCK) { if (!soundblocks) P_RecursiveSound (other, 1); @@ -155,7 +150,8 @@ P_NoiseAlert { soundtarget = target; validcount++; - P_RecursiveSound (emmiter->subsector->sector, 0); + sector_check_reset(); + P_RecursiveSound (mobj_sector(emmiter), 0); } @@ -169,16 +165,16 @@ boolean P_CheckMeleeRange (mobj_t* actor) mobj_t* pl; fixed_t dist; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return false; - pl = actor->target; - dist = P_AproxDistance (pl->x-actor->x, pl->y-actor->y); + pl = mobj_target(actor); + dist = P_AproxDistance (pl->xy.x-actor->xy.x, pl->xy.y-actor->xy.y); - if (dist >= MELEERANGE-20*FRACUNIT+pl->info->radius) + if (dist >= MELEERANGE-20*FRACUNIT+mobj_info(pl)->radius) return false; - if (! P_CheckSight (actor, actor->target) ) + if (! P_CheckSight (actor, pl)) return false; return true; @@ -190,8 +186,9 @@ boolean P_CheckMeleeRange (mobj_t* actor) boolean P_CheckMissileRange (mobj_t* actor) { fixed_t dist; - - if (! P_CheckSight (actor, actor->target) ) + + mobj_t* pl = mobj_target(actor); + if (! P_CheckSight (actor, pl) ) return false; if ( actor->flags & MF_JUSTHIT ) @@ -202,14 +199,14 @@ boolean P_CheckMissileRange (mobj_t* actor) return true; } - if (actor->reactiontime) + if (mobj_reactiontime(actor)) return false; // do not attack yet // OPTIMIZE: get this from a global checksight - dist = P_AproxDistance ( actor->x-actor->target->x, - actor->y-actor->target->y) - 64*FRACUNIT; + dist = P_AproxDistance ( actor->xy.x-pl->xy.x, + actor->xy.y - pl->xy.y) - 64 * FRACUNIT; - if (!actor->info->meleestate) + if (!mobj_info(actor)->meleestate) dist -= 128*FRACUNIT; // no melee attack, so fire more dist >>= FRACBITS; @@ -254,8 +251,8 @@ boolean P_CheckMissileRange (mobj_t* actor) // Move in the current direction, // returns false if the move is blocked. // -fixed_t xspeed[8] = {FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000}; -fixed_t yspeed[8] = {0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000}; +static const fixed_t xspeed[8] = {FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000}; +static const fixed_t yspeed[8] = {0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000}; boolean P_Move (mobj_t* actor) { @@ -269,14 +266,14 @@ boolean P_Move (mobj_t* actor) boolean try_ok; boolean good; - if (actor->movedir == DI_NODIR) + if (mobj_full(actor)->movedir == DI_NODIR) return false; - if ((unsigned)actor->movedir >= 8) + if ((unsigned)mobj_full(actor)->movedir >= 8) I_Error ("Weird actor->movedir!"); - tryx = actor->x + actor->info->speed*xspeed[actor->movedir]; - tryy = actor->y + actor->info->speed*yspeed[actor->movedir]; + tryx = actor->xy.x + mobj_speed(actor) * xspeed[mobj_full(actor)->movedir]; + tryy = actor->xy.y + mobj_speed(actor) * yspeed[mobj_full(actor)->movedir]; try_ok = P_TryMove (actor, tryx, tryy); @@ -297,8 +294,8 @@ boolean P_Move (mobj_t* actor) if (!numspechit) return false; - - actor->movedir = DI_NODIR; + + mobj_full(actor)->movedir = DI_NODIR; good = false; while (numspechit--) { @@ -318,7 +315,7 @@ boolean P_Move (mobj_t* actor) if (! (actor->flags & MF_FLOAT) ) - actor->z = actor->floorz; + actor->z = mobj_full(actor)->floorz; return true; } @@ -341,7 +338,7 @@ boolean P_TryWalk (mobj_t* actor) return false; } - actor->movecount = P_Random()&15; + mobj_full(actor)->movecount = P_Random()&15; return true; } @@ -360,14 +357,15 @@ void P_NewChaseDir (mobj_t* actor) dirtype_t turnaround; - if (!actor->target) + if (!mobj_full(actor)->sp_target) I_Error ("P_NewChaseDir: called with no target"); - olddir = actor->movedir; + olddir = mobj_full(actor)->movedir; turnaround=opposite[olddir]; - deltax = actor->target->x - actor->x; - deltay = actor->target->y - actor->y; + mobj_t *pl = mobj_target(actor); + deltax = pl->xy.x - actor->xy.x; + deltay = pl->xy.y - actor->xy.y; if (deltax>10*FRACUNIT) d[1]= DI_EAST; @@ -387,8 +385,8 @@ void P_NewChaseDir (mobj_t* actor) if (d[1] != DI_NODIR && d[2] != DI_NODIR) { - actor->movedir = diags[((deltay<0)<<1)+(deltax>0)]; - if (actor->movedir != (int) turnaround && P_TryWalk(actor)) + mobj_full(actor)->movedir = diags[((deltay<0)<<1)+(deltax>0)]; + if (mobj_full(actor)->movedir != (int) turnaround && P_TryWalk(actor)) return; } @@ -408,7 +406,7 @@ void P_NewChaseDir (mobj_t* actor) if (d[1]!=DI_NODIR) { - actor->movedir = d[1]; + mobj_full(actor)->movedir = d[1]; if (P_TryWalk(actor)) { // either moved forward or attacked @@ -418,7 +416,7 @@ void P_NewChaseDir (mobj_t* actor) if (d[2]!=DI_NODIR) { - actor->movedir =d[2]; + mobj_full(actor)->movedir =d[2]; if (P_TryWalk(actor)) return; @@ -428,7 +426,7 @@ void P_NewChaseDir (mobj_t* actor) // so pick another direction. if (olddir!=DI_NODIR) { - actor->movedir =olddir; + mobj_full(actor)->movedir =olddir; if (P_TryWalk(actor)) return; @@ -443,7 +441,7 @@ void P_NewChaseDir (mobj_t* actor) { if (tdir != (int) turnaround) { - actor->movedir =tdir; + mobj_full(actor)->movedir =tdir; if ( P_TryWalk(actor) ) return; @@ -458,7 +456,7 @@ void P_NewChaseDir (mobj_t* actor) { if (tdir != (int) turnaround) { - actor->movedir = tdir; + mobj_full(actor)->movedir = tdir; if ( P_TryWalk(actor) ) return; @@ -468,12 +466,12 @@ void P_NewChaseDir (mobj_t* actor) if (turnaround != DI_NODIR) { - actor->movedir =turnaround; + mobj_full(actor)->movedir =turnaround; if ( P_TryWalk(actor) ) return; } - actor->movedir = DI_NODIR; // can not move + mobj_full(actor)->movedir = DI_NODIR; // can not move } @@ -495,21 +493,21 @@ P_LookForPlayers fixed_t dist; c = 0; - stop = (actor->lastlook-1)&3; + stop = (mobj_full(actor)->lastlook-1)&3; - for ( ; ; actor->lastlook = (actor->lastlook+1)&3 ) + for ( ; ; mobj_full(actor)->lastlook = (mobj_full(actor)->lastlook+1)&3 ) { - if (!playeringame[actor->lastlook]) + if (!playeringame[mobj_full(actor)->lastlook]) continue; if (c++ == 2 - || actor->lastlook == stop) + || mobj_full(actor)->lastlook == stop) { // done looking return false; } - player = &players[actor->lastlook]; + player = &players[mobj_full(actor)->lastlook]; if (player->health <= 0) continue; // dead @@ -519,23 +517,23 @@ P_LookForPlayers if (!allaround) { - an = R_PointToAngle2 (actor->x, - actor->y, - player->mo->x, - player->mo->y) - - actor->angle; + an = R_PointToAngle2 (actor->xy.x, + actor->xy.y, + player->mo->xy.x, + player->mo->xy.y) + - mobj_full(actor)->angle; if (an > ANG90 && an < ANG270) { - dist = P_AproxDistance (player->mo->x - actor->x, - player->mo->y - actor->y); + dist = P_AproxDistance (player->mo->xy.x - actor->xy.x, + player->mo->xy.y - actor->xy.y); // if real close, react anyway if (dist > MELEERANGE) continue; // behind back } } - actor->target = player->mo; + mobj_full(actor)->sp_target = mobj_to_shortptr(player->mo); return true; } @@ -552,29 +550,29 @@ void A_KeenDie (mobj_t* mo) { thinker_t* th; mobj_t* mo2; - line_t junk; + fake_line_t junk; A_Fall (mo); // scan the remaining thinkers // to see if all Keens are dead - for (th = thinkercap.next ; th != &thinkercap ; th=th->next) + for (th = thinker_next(&thinkercap) ; th != &thinkercap ; th=thinker_next(th)) { - if (th->function.acp1 != (actionf_p1)P_MobjThinker) + if (th->function != ThinkF_P_MobjThinker) continue; mo2 = (mobj_t *)th; if (mo2 != mo && mo2->type == mo->type - && mo2->health > 0) + && mobj_full(mo2)->health > 0) { // other Keen not dead return; } } - junk.tag = 666; - EV_DoDoor(&junk, vld_open); + init_fake_line(&junk, TAG_666); + EV_DoDoor(fakeline_to_line(&junk), vld_open); } @@ -590,17 +588,17 @@ void A_Look (mobj_t* actor) { mobj_t* targ; - actor->threshold = 0; // any shot will wake up - targ = actor->subsector->sector->soundtarget; + mobj_full(actor)->threshold = 0; // any shot will wake up + targ = shortptr_to_mobj(mobj_sector(actor)->soundtarget); if (targ && (targ->flags & MF_SHOOTABLE) ) { - actor->target = targ; + mobj_full(actor)->sp_target = mobj_to_shortptr(targ); if ( actor->flags & MF_AMBUSH ) { - if (P_CheckSight (actor, actor->target)) + if (P_CheckSight (actor, mobj_target(actor))) goto seeyou; } else @@ -613,11 +611,11 @@ void A_Look (mobj_t* actor) // go into chase state seeyou: - if (actor->info->seesound) + if (mobj_info(actor)->seesound) { int sound; - switch (actor->info->seesound) + switch (mobj_info(actor)->seesound) { case sfx_posit1: case sfx_posit2: @@ -631,7 +629,7 @@ void A_Look (mobj_t* actor) break; default: - sound = actor->info->seesound; + sound = mobj_info(actor)->seesound; break; } @@ -642,10 +640,10 @@ void A_Look (mobj_t* actor) S_StartSound (NULL, sound); } else - S_StartSound (actor, sound); + S_StartObjSound (actor, sound); } - P_SetMobjState (actor, actor->info->seestate); + P_SetMobjState (actor, mobj_info(actor)->seestate); } @@ -658,42 +656,42 @@ void A_Chase (mobj_t* actor) { int delta; - if (actor->reactiontime) - actor->reactiontime--; + if (mobj_reactiontime(actor)) + mobj_reactiontime(actor)--; // modify target threshold - if (actor->threshold) + if (mobj_full(actor)->threshold) { - if (!actor->target - || actor->target->health <= 0) + if (!mobj_full(actor)->sp_target + || mobj_full(mobj_target(actor))->health <= 0) { - actor->threshold = 0; + mobj_full(actor)->threshold = 0; } else - actor->threshold--; + mobj_full(actor)->threshold--; } // turn towards movement direction if not there yet - if (actor->movedir < 8) + if (mobj_full(actor)->movedir < 8) { - actor->angle &= (7<<29); - delta = actor->angle - (actor->movedir << 29); + mobj_full(actor)->angle &= (7<<29); + delta = mobj_full(actor)->angle - (mobj_full(actor)->movedir << 29); if (delta > 0) - actor->angle -= ANG90/2; + mobj_full(actor)->angle -= ANG90/2; else if (delta < 0) - actor->angle += ANG90/2; + mobj_full(actor)->angle += ANG90/2; } - if (!actor->target - || !(actor->target->flags&MF_SHOOTABLE)) + if (!mobj_full(actor)->sp_target + || !(mobj_target(actor)->flags&MF_SHOOTABLE)) { // look for a new target if (P_LookForPlayers(actor,true)) return; // got a new target - P_SetMobjState (actor, actor->info->spawnstate); + P_SetMobjState (actor, mobj_info(actor)->spawnstate); return; } @@ -707,21 +705,21 @@ void A_Chase (mobj_t* actor) } // check for melee attack - if (actor->info->meleestate + if (mobj_info(actor)->meleestate && P_CheckMeleeRange (actor)) { - if (actor->info->attacksound) - S_StartSound (actor, actor->info->attacksound); + if (mobj_info(actor)->attacksound) + S_StartObjSound (actor, mobj_info(actor)->attacksound); - P_SetMobjState (actor, actor->info->meleestate); + P_SetMobjState (actor, mobj_info(actor)->meleestate); return; } // check for missile attack - if (actor->info->missilestate) + if (mobj_info(actor)->missilestate) { if (gameskill < sk_nightmare - && !fastparm && actor->movecount) + && !fastparm && mobj_full(actor)->movecount) { goto nomissile; } @@ -729,7 +727,7 @@ void A_Chase (mobj_t* actor) if (!P_CheckMissileRange (actor)) goto nomissile; - P_SetMobjState (actor, actor->info->missilestate); + P_SetMobjState (actor, mobj_info(actor)->missilestate); actor->flags |= MF_JUSTATTACKED; return; } @@ -738,25 +736,25 @@ void A_Chase (mobj_t* actor) nomissile: // possibly choose another target if (netgame - && !actor->threshold - && !P_CheckSight (actor, actor->target) ) + && !mobj_full(actor)->threshold + && !P_CheckSight (actor, mobj_target(actor)) ) { if (P_LookForPlayers(actor,true)) return; // got a new target } // chase towards player - if (--actor->movecount<0 + if (--mobj_full(actor)->movecount<0 || !P_Move (actor)) { P_NewChaseDir (actor); } // make active sound - if (actor->info->activesound + if (mobj_info(actor)->activesound && P_Random () < 3) { - S_StartSound (actor, actor->info->activesound); + S_StartObjSound (actor, mobj_info(actor)->activesound); } } @@ -766,18 +764,20 @@ void A_Chase (mobj_t* actor) // void A_FaceTarget (mobj_t* actor) { - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; + mobj_t *target = mobj_target(actor); + actor->flags &= ~MF_AMBUSH; - actor->angle = R_PointToAngle2 (actor->x, - actor->y, - actor->target->x, - actor->target->y); + mobj_full(actor)->angle = R_PointToAngle2 (actor->xy.x, + actor->xy.y, + target->xy.x, + target->xy.y); - if (actor->target->flags & MF_SHADOW) - actor->angle += P_SubRandom() << 21; + if (target->flags & MF_SHADOW) + mobj_full(actor)->angle += P_SubRandom() << 21; } @@ -790,14 +790,14 @@ void A_PosAttack (mobj_t* actor) int damage; int slope; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; A_FaceTarget (actor); - angle = actor->angle; + angle = mobj_full(actor)->angle; slope = P_AimLineAttack (actor, angle, MISSILERANGE); - S_StartSound (actor, sfx_pistol); + S_StartObjSound (actor, sfx_pistol); angle += P_SubRandom() << 20; damage = ((P_Random()%5)+1)*3; P_LineAttack (actor, angle, MISSILERANGE, slope, damage); @@ -811,12 +811,12 @@ void A_SPosAttack (mobj_t* actor) int damage; int slope; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; - S_StartSound (actor, sfx_shotgn); + S_StartObjSound (actor, sfx_shotgn); A_FaceTarget (actor); - bangle = actor->angle; + bangle = mobj_full(actor)->angle; slope = P_AimLineAttack (actor, bangle, MISSILERANGE); for (i=0 ; i<3 ; i++) @@ -834,12 +834,12 @@ void A_CPosAttack (mobj_t* actor) int damage; int slope; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; - S_StartSound (actor, sfx_shotgn); + S_StartObjSound (actor, sfx_shotgn); A_FaceTarget (actor); - bangle = actor->angle; + bangle = mobj_full(actor)->angle; slope = P_AimLineAttack (actor, bangle, MISSILERANGE); angle = bangle + (P_SubRandom() << 20); @@ -855,11 +855,11 @@ void A_CPosRefire (mobj_t* actor) if (P_Random () < 40) return; - if (!actor->target - || actor->target->health <= 0 - || !P_CheckSight (actor, actor->target) ) + if (!mobj_full(actor)->sp_target + || mobj_full(mobj_target(actor))->health <= 0 + || !P_CheckSight (actor, mobj_target(actor)) ) { - P_SetMobjState (actor, actor->info->seestate); + P_SetMobjState (actor, mobj_info(actor)->seestate); } } @@ -872,23 +872,23 @@ void A_SpidRefire (mobj_t* actor) if (P_Random () < 10) return; - if (!actor->target - || actor->target->health <= 0 - || !P_CheckSight (actor, actor->target) ) + if (!mobj_full(actor)->sp_target + || mobj_full(mobj_target(actor))->health <= 0 + || !P_CheckSight (actor, mobj_target(actor)) ) { - P_SetMobjState (actor, actor->info->seestate); + P_SetMobjState (actor, mobj_info(actor)->seestate); } } void A_BspiAttack (mobj_t *actor) { - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; A_FaceTarget (actor); // launch a missile - P_SpawnMissile (actor, actor->target, MT_ARACHPLAZ); + P_SpawnMissile (actor, mobj_target(actor), MT_ARACHPLAZ); } @@ -899,21 +899,21 @@ void A_TroopAttack (mobj_t* actor) { int damage; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; A_FaceTarget (actor); if (P_CheckMeleeRange (actor)) { - S_StartSound (actor, sfx_claw); + S_StartObjSound (actor, sfx_claw); damage = (P_Random()%8+1)*3; - P_DamageMobj (actor->target, actor, actor, damage); + P_DamageMobj (mobj_target(actor), actor, actor, damage); return; } // launch a missile - P_SpawnMissile (actor, actor->target, MT_TROOPSHOT); + P_SpawnMissile (actor, mobj_target(actor), MT_TROOPSHOT); } @@ -921,14 +921,14 @@ void A_SargAttack (mobj_t* actor) { int damage; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; A_FaceTarget (actor); if (P_CheckMeleeRange (actor)) { damage = ((P_Random()%10)+1)*4; - P_DamageMobj (actor->target, actor, actor, damage); + P_DamageMobj (mobj_target(actor), actor, actor, damage); } } @@ -936,28 +936,28 @@ void A_HeadAttack (mobj_t* actor) { int damage; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; A_FaceTarget (actor); if (P_CheckMeleeRange (actor)) { damage = (P_Random()%6+1)*10; - P_DamageMobj (actor->target, actor, actor, damage); + P_DamageMobj (mobj_target(actor), actor, actor, damage); return; } // launch a missile - P_SpawnMissile (actor, actor->target, MT_HEADSHOT); + P_SpawnMissile (actor, mobj_target(actor), MT_HEADSHOT); } void A_CyberAttack (mobj_t* actor) { - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; A_FaceTarget (actor); - P_SpawnMissile (actor, actor->target, MT_ROCKET); + P_SpawnMissile (actor, mobj_target(actor), MT_ROCKET); } @@ -965,19 +965,19 @@ void A_BruisAttack (mobj_t* actor) { int damage; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; if (P_CheckMeleeRange (actor)) { - S_StartSound (actor, sfx_claw); + S_StartObjSound (actor, sfx_claw); damage = (P_Random()%8+1)*10; - P_DamageMobj (actor->target, actor, actor, damage); + P_DamageMobj (mobj_target(actor), actor, actor, damage); return; } // launch a missile - P_SpawnMissile (actor, actor->target, MT_BRUISERSHOT); + P_SpawnMissile (actor, mobj_target(actor), MT_BRUISERSHOT); } @@ -988,20 +988,21 @@ void A_SkelMissile (mobj_t* actor) { mobj_t* mo; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; A_FaceTarget (actor); actor->z += 16*FRACUNIT; // so missile spawns higher - mo = P_SpawnMissile (actor, actor->target, MT_TRACER); + mo = P_SpawnMissile (actor, mobj_target(actor), MT_TRACER); actor->z -= 16*FRACUNIT; // back to normal - mo->x += mo->momx; - mo->y += mo->momy; - mo->tracer = actor->target; + mo->xy.x += mobj_full(mo)->momx; + mo->xy.y += mobj_full(mo)->momy; + mobj_full(mo)->sp_tracer = mobj_full(actor)->sp_target; } -int TRACEANGLE = 0xc000000; +//int TRACEANGLE = 0xc000000; +#define TRACEANGLE 0xc000000 void A_Tracer (mobj_t* actor) { @@ -1015,79 +1016,80 @@ void A_Tracer (mobj_t* actor) return; // spawn a puff of smoke behind the rocket - P_SpawnPuff (actor->x, actor->y, actor->z); + P_SpawnPuff (actor->xy.x, actor->xy.y, actor->z); - th = P_SpawnMobj (actor->x-actor->momx, - actor->y-actor->momy, + th = P_SpawnMobj (actor->xy.x - mobj_full(actor)->momx, + actor->xy.y - mobj_full(actor)->momy, actor->z, MT_SMOKE); - - th->momz = FRACUNIT; + + mobj_full(th)->momz = FRACUNIT; th->tics -= P_Random()&3; if (th->tics < 1) th->tics = 1; // adjust direction - dest = actor->tracer; + dest = mobj_tracer(actor); - if (!dest || dest->health <= 0) + if (!dest || mobj_full(dest)->health <= 0) return; // change angle - exact = R_PointToAngle2 (actor->x, - actor->y, - dest->x, - dest->y); + exact = R_PointToAngle2 (actor->xy.x, + actor->xy.y, + dest->xy.x, + dest->xy.y); - if (exact != actor->angle) + if (exact != mobj_full(actor)->angle) { - if (exact - actor->angle > 0x80000000) + if (exact - mobj_full(actor)->angle > 0x80000000) { - actor->angle -= TRACEANGLE; - if (exact - actor->angle < 0x80000000) - actor->angle = exact; + mobj_full(actor)->angle -= TRACEANGLE; + if (exact - mobj_full(actor)->angle < 0x80000000) + mobj_full(actor)->angle = exact; } else { - actor->angle += TRACEANGLE; - if (exact - actor->angle > 0x80000000) - actor->angle = exact; + mobj_full(actor)->angle += TRACEANGLE; + if (exact - mobj_full(actor)->angle > 0x80000000) + mobj_full(actor)->angle = exact; } } - exact = actor->angle>>ANGLETOFINESHIFT; - actor->momx = FixedMul (actor->info->speed, finecosine[exact]); - actor->momy = FixedMul (actor->info->speed, finesine[exact]); + exact = mobj_full(actor)->angle>>ANGLETOFINESHIFT; + int speed = mobj_speed(actor); + mobj_full(actor)->momx = FixedMul (speed, finecosine(exact)); + mobj_full(actor)->momy = FixedMul (speed, finesine(exact)); // change slope - dist = P_AproxDistance (dest->x - actor->x, - dest->y - actor->y); + dist = P_AproxDistance (dest->xy.x - actor->xy.x, + dest->xy.y - actor->xy.y); - dist = dist / actor->info->speed; + dist = dist / speed; if (dist < 1) dist = 1; slope = (dest->z+40*FRACUNIT - actor->z) / dist; - if (slope < actor->momz) - actor->momz -= FRACUNIT/8; + if (slope < mobj_full(actor)->momz) + mobj_full(actor)->momz -= FRACUNIT/8; else - actor->momz += FRACUNIT/8; + mobj_full(actor)->momz += FRACUNIT/8; } void A_SkelWhoosh (mobj_t* actor) { - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; A_FaceTarget (actor); - S_StartSound (actor,sfx_skeswg); + S_StartObjSound (actor,sfx_skeswg); } void A_SkelFist (mobj_t* actor) { int damage; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; A_FaceTarget (actor); @@ -1095,8 +1097,8 @@ void A_SkelFist (mobj_t* actor) if (P_CheckMeleeRange (actor)) { damage = ((P_Random()%10)+1)*6; - S_StartSound (actor, sfx_skepch); - P_DamageMobj (actor->target, actor, actor, damage); + S_StartObjSound (actor, sfx_skepch); + P_DamageMobj (mobj_target(actor), actor, actor, damage); } } @@ -1122,20 +1124,20 @@ boolean PIT_VileCheck (mobj_t* thing) if (thing->tics != -1) return true; // not lying still yet - if (thing->info->raisestate == S_NULL) + if (mobj_info(thing)->raisestate == S_NULL) return true; // monster doesn't have a raise state - maxdist = thing->info->radius + mobjinfo[MT_VILE].radius; + maxdist = mobj_info(thing)->radius + mobjinfo[MT_VILE].radius; - if ( abs(thing->x - viletryx) > maxdist - || abs(thing->y - viletryy) > maxdist ) + if (abs(thing->xy.x - viletryx) > maxdist + || abs(thing->xy.y - viletryy) > maxdist ) return true; // not actually touching corpsehit = thing; - corpsehit->momx = corpsehit->momy = 0; - corpsehit->height <<= 2; - check = P_CheckPosition (corpsehit, corpsehit->x, corpsehit->y); - corpsehit->height >>= 2; + mobj_full(corpsehit)->momx = mobj_full(corpsehit)->momy = 0; + mobj_full(corpsehit)->height <<= 2; + check = P_CheckPosition (corpsehit, corpsehit->xy.x, corpsehit->xy.y); + mobj_full(corpsehit)->height >>= 2; if (!check) return true; // doesn't fit here @@ -1159,16 +1161,17 @@ void A_VileChase (mobj_t* actor) int bx; int by; - mobjinfo_t* info; - mobj_t* temp; + const mobjinfo_t* info; + shortptr_t temp; - if (actor->movedir != DI_NODIR) + if (mobj_full(actor)->movedir != DI_NODIR) { + int speed = mobj_speed(actor); // check for corpses to raise viletryx = - actor->x + actor->info->speed*xspeed[actor->movedir]; + actor->xy.x + speed * xspeed[mobj_full(actor)->movedir]; viletryy = - actor->y + actor->info->speed*yspeed[actor->movedir]; + actor->xy.y + speed * yspeed[mobj_full(actor)->movedir]; xl = (viletryx - bmaporgx - MAXRADIUS*2)>>MAPBLOCKSHIFT; xh = (viletryx - bmaporgx + MAXRADIUS*2)>>MAPBLOCKSHIFT; @@ -1186,20 +1189,20 @@ void A_VileChase (mobj_t* actor) if (!P_BlockThingsIterator(bx,by,PIT_VileCheck)) { // got one! - temp = actor->target; - actor->target = corpsehit; + temp = mobj_full(actor)->sp_target; + mobj_full(actor)->sp_target = mobj_to_shortptr(corpsehit); A_FaceTarget (actor); - actor->target = temp; + mobj_full(actor)->sp_target = temp; P_SetMobjState (actor, S_VILE_HEAL1); - S_StartSound (corpsehit, sfx_slop); - info = corpsehit->info; + S_StartObjSound (corpsehit, sfx_slop); + info = mobj_info(corpsehit); P_SetMobjState (corpsehit,info->raisestate); - corpsehit->height <<= 2; + mobj_full(corpsehit)->height <<= 2; corpsehit->flags = info->flags; - corpsehit->health = info->spawnhealth; - corpsehit->target = NULL; + mobj_full(corpsehit)->health = info->spawnhealth; + mobj_full(corpsehit)->sp_target = 0; return; } @@ -1217,7 +1220,7 @@ void A_VileChase (mobj_t* actor) // void A_VileStart (mobj_t* actor) { - S_StartSound (actor, sfx_vilatk); + S_StartObjSound (actor, sfx_vilatk); } @@ -1229,13 +1232,13 @@ void A_Fire (mobj_t* actor); void A_StartFire (mobj_t* actor) { - S_StartSound(actor,sfx_flamst); + S_StartObjSound(actor,sfx_flamst); A_Fire(actor); } void A_FireCrackle (mobj_t* actor) { - S_StartSound(actor,sfx_flame); + S_StartObjSound(actor,sfx_flame); A_Fire(actor); } @@ -1245,23 +1248,20 @@ void A_Fire (mobj_t* actor) mobj_t* target; unsigned an; - dest = actor->tracer; + dest = mobj_tracer(actor); if (!dest) return; - target = P_SubstNullMobj(actor->target); + target = P_SubstNullMobj(mobj_target(actor)); // don't move it if the vile lost sight if (!P_CheckSight (target, dest) ) return; - an = dest->angle >> ANGLETOFINESHIFT; + an = mobj_full(dest)->angle >> ANGLETOFINESHIFT; - P_UnsetThingPosition (actor); - actor->x = dest->x + FixedMul (24*FRACUNIT, finecosine[an]); - actor->y = dest->y + FixedMul (24*FRACUNIT, finesine[an]); + P_ResetThingPosition (actor, dest->xy.x + FixedMul (24 * FRACUNIT, finecosine(an)), dest->xy.y + FixedMul (24 * FRACUNIT, finesine(an))); actor->z = dest->z; - P_SetThingPosition (actor); } @@ -1274,18 +1274,19 @@ void A_VileTarget (mobj_t* actor) { mobj_t* fog; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; A_FaceTarget (actor); - fog = P_SpawnMobj (actor->target->x, - actor->target->x, - actor->target->z, MT_FIRE); - - actor->tracer = fog; - fog->target = actor; - fog->tracer = actor->target; + mobj_t *target = mobj_target(actor); + fog = P_SpawnMobj (target->xy.x, + target->xy.x, + target->z, MT_FIRE); + + mobj_full(actor)->sp_tracer = mobj_to_shortptr(fog); + mobj_full(fog)->sp_target = mobj_to_shortptr(actor); + mobj_full(fog)->sp_tracer = mobj_to_shortptr(target); A_Fire (fog); } @@ -1300,28 +1301,29 @@ void A_VileAttack (mobj_t* actor) mobj_t* fire; int an; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; A_FaceTarget (actor); - if (!P_CheckSight (actor, actor->target) ) + mobj_t *target = mobj_target(actor); + if (!P_CheckSight (actor, target) ) return; - S_StartSound (actor, sfx_barexp); - P_DamageMobj (actor->target, actor, actor, 20); - actor->target->momz = 1000*FRACUNIT/actor->target->info->mass; + S_StartObjSound (actor, sfx_barexp); + P_DamageMobj (target, actor, actor, 20); + mobj_full(target)->momz = 1000*FRACUNIT/mobj_info(target)->mass; - an = actor->angle >> ANGLETOFINESHIFT; + an = mobj_full(actor)->angle >> ANGLETOFINESHIFT; - fire = actor->tracer; + fire = mobj_tracer(actor); if (!fire) return; // move the fire between the vile and the player - fire->x = actor->target->x - FixedMul (24*FRACUNIT, finecosine[an]); - fire->y = actor->target->y - FixedMul (24*FRACUNIT, finesine[an]); + fire->xy.x = target->xy.x - FixedMul (24 * FRACUNIT, finecosine(an)); + fire->xy.y = target->xy.y - FixedMul (24 * FRACUNIT, finesine(an)); P_RadiusAttack (fire, actor, 70 ); } @@ -1339,7 +1341,7 @@ void A_VileAttack (mobj_t* actor) void A_FatRaise (mobj_t *actor) { A_FaceTarget (actor); - S_StartSound (actor, sfx_manatk); + S_StartObjSound (actor, sfx_manatk); } @@ -1352,15 +1354,16 @@ void A_FatAttack1 (mobj_t* actor) A_FaceTarget (actor); // Change direction to ... - actor->angle += FATSPREAD; - target = P_SubstNullMobj(actor->target); + mobj_full(actor)->angle += FATSPREAD; + target = P_SubstNullMobj(mobj_target(actor)); P_SpawnMissile (actor, target, MT_FATSHOT); mo = P_SpawnMissile (actor, target, MT_FATSHOT); - mo->angle += FATSPREAD; - an = mo->angle >> ANGLETOFINESHIFT; - mo->momx = FixedMul (mo->info->speed, finecosine[an]); - mo->momy = FixedMul (mo->info->speed, finesine[an]); + mobj_full(mo)->angle += FATSPREAD; + an = mobj_full(mo)->angle >> ANGLETOFINESHIFT; + int speed = mobj_speed(mo); + mobj_full(mo)->momx = FixedMul (speed, finecosine(an)); + mobj_full(mo)->momy = FixedMul (speed, finesine(an)); } void A_FatAttack2 (mobj_t* actor) @@ -1371,15 +1374,16 @@ void A_FatAttack2 (mobj_t* actor) A_FaceTarget (actor); // Now here choose opposite deviation. - actor->angle -= FATSPREAD; - target = P_SubstNullMobj(actor->target); + mobj_full(actor)->angle -= FATSPREAD; + target = P_SubstNullMobj(mobj_target(actor)); P_SpawnMissile (actor, target, MT_FATSHOT); mo = P_SpawnMissile (actor, target, MT_FATSHOT); - mo->angle -= FATSPREAD*2; - an = mo->angle >> ANGLETOFINESHIFT; - mo->momx = FixedMul (mo->info->speed, finecosine[an]); - mo->momy = FixedMul (mo->info->speed, finesine[an]); + mobj_full(mo)->angle -= FATSPREAD*2; + an = mobj_full(mo)->angle >> ANGLETOFINESHIFT; + int speed = mobj_speed(mo); + mobj_full(mo)->momx = FixedMul (speed, finecosine(an)); + mobj_full(mo)->momy = FixedMul (speed, finesine(an)); } void A_FatAttack3 (mobj_t* actor) @@ -1390,19 +1394,21 @@ void A_FatAttack3 (mobj_t* actor) A_FaceTarget (actor); - target = P_SubstNullMobj(actor->target); + target = P_SubstNullMobj(mobj_target(actor)); mo = P_SpawnMissile (actor, target, MT_FATSHOT); - mo->angle -= FATSPREAD/2; - an = mo->angle >> ANGLETOFINESHIFT; - mo->momx = FixedMul (mo->info->speed, finecosine[an]); - mo->momy = FixedMul (mo->info->speed, finesine[an]); + mobj_full(mo)->angle -= FATSPREAD/2; + an = mobj_full(mo)->angle >> ANGLETOFINESHIFT; + int speed = mobj_speed(mo); + mobj_full(mo)->momx = FixedMul (speed, finecosine(an)); + mobj_full(mo)->momy = FixedMul (speed, finesine(an)); mo = P_SpawnMissile (actor, target, MT_FATSHOT); - mo->angle += FATSPREAD/2; - an = mo->angle >> ANGLETOFINESHIFT; - mo->momx = FixedMul (mo->info->speed, finecosine[an]); - mo->momy = FixedMul (mo->info->speed, finesine[an]); + mobj_full(mo)->angle += FATSPREAD/2; + speed = mobj_speed(mo); + an = mobj_full(mo)->angle >> ANGLETOFINESHIFT; + mobj_full(mo)->momx = FixedMul (speed, finecosine(an)); + mobj_full(mo)->momy = FixedMul (speed, finesine(an)); } @@ -1418,23 +1424,23 @@ void A_SkullAttack (mobj_t* actor) angle_t an; int dist; - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; - dest = actor->target; + dest = mobj_target(actor); actor->flags |= MF_SKULLFLY; - S_StartSound (actor, actor->info->attacksound); + S_StartObjSound (actor, mobj_info(actor)->attacksound); A_FaceTarget (actor); - an = actor->angle >> ANGLETOFINESHIFT; - actor->momx = FixedMul (SKULLSPEED, finecosine[an]); - actor->momy = FixedMul (SKULLSPEED, finesine[an]); - dist = P_AproxDistance (dest->x - actor->x, dest->y - actor->y); + an = mobj_full(actor)->angle >> ANGLETOFINESHIFT; + mobj_full(actor)->momx = FixedMul (SKULLSPEED, finecosine(an)); + mobj_full(actor)->momy = FixedMul (SKULLSPEED, finesine(an)); + dist = P_AproxDistance (dest->xy.x - actor->xy.x, dest->xy.y - actor->xy.y); dist = dist / SKULLSPEED; if (dist < 1) dist = 1; - actor->momz = (dest->z+(dest->height>>1) - actor->z) / dist; + mobj_full(actor)->momz = (dest->z+(mobj_height(dest)>>1) - actor->z) / dist; } @@ -1460,13 +1466,13 @@ A_PainShootSkull // count total number of skull currently on the level count = 0; - currentthinker = thinkercap.next; + currentthinker = thinker_next(&thinkercap); while (currentthinker != &thinkercap) { - if ( (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker) + if ( (currentthinker->function == ThinkF_P_MobjThinker) && ((mobj_t *)currentthinker)->type == MT_SKULL) count++; - currentthinker = currentthinker->next; + currentthinker = thinker_next(currentthinker); } // if there are allready 20 skulls on the level, @@ -1480,23 +1486,23 @@ A_PainShootSkull prestep = 4*FRACUNIT - + 3*(actor->info->radius + mobjinfo[MT_SKULL].radius)/2; + + 3*(mobj_info(actor)->radius + mobjinfo[MT_SKULL].radius)/2; - x = actor->x + FixedMul (prestep, finecosine[an]); - y = actor->y + FixedMul (prestep, finesine[an]); + x = actor->xy.x + FixedMul (prestep, finecosine(an)); + y = actor->xy.y + FixedMul (prestep, finesine(an)); z = actor->z + 8*FRACUNIT; newmobj = P_SpawnMobj (x , y, z, MT_SKULL); // Check for movements. - if (!P_TryMove (newmobj, newmobj->x, newmobj->y)) + if (!P_TryMove (newmobj, newmobj->xy.x, newmobj->xy.y)) { // kill it immediately P_DamageMobj (newmobj,actor,actor,10000); return; } - newmobj->target = actor->target; + mobj_full(newmobj)->sp_target = mobj_full(actor)->sp_target; A_SkullAttack (newmobj); } @@ -1507,20 +1513,20 @@ A_PainShootSkull // void A_PainAttack (mobj_t* actor) { - if (!actor->target) + if (!mobj_full(actor)->sp_target) return; A_FaceTarget (actor); - A_PainShootSkull (actor, actor->angle); + A_PainShootSkull (actor, mobj_full(actor)->angle); } void A_PainDie (mobj_t* actor) { A_Fall (actor); - A_PainShootSkull (actor, actor->angle+ANG90); - A_PainShootSkull (actor, actor->angle+ANG180); - A_PainShootSkull (actor, actor->angle+ANG270); + A_PainShootSkull (actor, mobj_full(actor)->angle+ANG90); + A_PainShootSkull (actor, mobj_full(actor)->angle+ANG180); + A_PainShootSkull (actor, mobj_full(actor)->angle+ANG270); } @@ -1532,7 +1538,7 @@ void A_Scream (mobj_t* actor) { int sound; - switch (actor->info->deathsound) + switch (mobj_info(actor)->deathsound) { case 0: return; @@ -1549,7 +1555,7 @@ void A_Scream (mobj_t* actor) break; default: - sound = actor->info->deathsound; + sound = mobj_info(actor)->deathsound; break; } @@ -1561,19 +1567,19 @@ void A_Scream (mobj_t* actor) S_StartSound (NULL, sound); } else - S_StartSound (actor, sound); + S_StartObjSound (actor, sound); } void A_XScream (mobj_t* actor) { - S_StartSound (actor, sfx_slop); + S_StartObjSound (actor, sfx_slop); } void A_Pain (mobj_t* actor) { - if (actor->info->painsound) - S_StartSound (actor, actor->info->painsound); + if (mobj_info(actor)->painsound) + S_StartObjSound (actor, mobj_info(actor)->painsound); } @@ -1593,7 +1599,7 @@ void A_Fall (mobj_t *actor) // void A_Explode (mobj_t* thingy) { - P_RadiusAttack(thingy, thingy->target, 128); + P_RadiusAttack(thingy, mobj_target(thingy), 128); } // Check whether the death of the specified monster type is allowed @@ -1657,7 +1663,7 @@ void A_BossDeath (mobj_t* mo) { thinker_t* th; mobj_t* mo2; - line_t junk; + fake_line_t junk; int i; if ( gamemode == commercial) @@ -1687,15 +1693,15 @@ void A_BossDeath (mobj_t* mo) // scan the remaining thinkers to see // if all bosses are dead - for (th = thinkercap.next ; th != &thinkercap ; th=th->next) + for (th = thinker_next(&thinkercap) ; th != &thinkercap ; th=thinker_next(th)) { - if (th->function.acp1 != (actionf_p1)P_MobjThinker) + if (th->function != ThinkF_P_MobjThinker) continue; mo2 = (mobj_t *)th; if (mo2 != mo && mo2->type == mo->type - && mo2->health > 0) + && mobj_full(mo2)->health > 0) { // other boss not dead return; @@ -1709,15 +1715,15 @@ void A_BossDeath (mobj_t* mo) { if (mo->type == MT_FATSO) { - junk.tag = 666; - EV_DoFloor(&junk,lowerFloorToLowest); + init_fake_line(&junk, TAG_666); + EV_DoFloor(fakeline_to_line(&junk),lowerFloorToLowest); return; } if (mo->type == MT_BABY) { - junk.tag = 667; - EV_DoFloor(&junk,raiseToTexture); + init_fake_line(&junk, TAG_667); + EV_DoFloor(fakeline_to_line(&junk),raiseToTexture); return; } } @@ -1727,8 +1733,8 @@ void A_BossDeath (mobj_t* mo) switch(gameepisode) { case 1: - junk.tag = 666; - EV_DoFloor (&junk, lowerFloorToLowest); + init_fake_line(&junk, TAG_666); + EV_DoFloor (fakeline_to_line(&junk), lowerFloorToLowest); return; break; @@ -1736,14 +1742,14 @@ void A_BossDeath (mobj_t* mo) switch(gamemap) { case 6: - junk.tag = 666; - EV_DoDoor (&junk, vld_blazeOpen); + init_fake_line(&junk, TAG_666); + EV_DoDoor (fakeline_to_line(&junk), vld_blazeOpen); return; break; case 8: - junk.tag = 666; - EV_DoFloor (&junk, lowerFloorToLowest); + init_fake_line(&junk, TAG_666); + EV_DoFloor (fakeline_to_line(&junk), lowerFloorToLowest); return; break; } @@ -1756,19 +1762,19 @@ void A_BossDeath (mobj_t* mo) void A_Hoof (mobj_t* mo) { - S_StartSound (mo, sfx_hoof); + S_StartObjSound (mo, sfx_hoof); A_Chase (mo); } void A_Metal (mobj_t* mo) { - S_StartSound (mo, sfx_metal); + S_StartObjSound (mo, sfx_metal); A_Chase (mo); } void A_BabyMetal (mobj_t* mo) { - S_StartSound (mo, sfx_bspwlk); + S_StartObjSound (mo, sfx_bspwlk); A_Chase (mo); } @@ -1777,7 +1783,7 @@ A_OpenShotgun2 ( player_t* player, pspdef_t* psp ) { - S_StartSound (player->mo, sfx_dbopn); + S_StartObjSound (player->mo, sfx_dbopn); } void @@ -1785,7 +1791,7 @@ A_LoadShotgun2 ( player_t* player, pspdef_t* psp ) { - S_StartSound (player->mo, sfx_dbload); + S_StartObjSound (player->mo, sfx_dbload); } void @@ -1798,15 +1804,15 @@ A_CloseShotgun2 ( player_t* player, pspdef_t* psp ) { - S_StartSound (player->mo, sfx_dbcls); + S_StartObjSound (player->mo, sfx_dbcls); A_ReFire(player,psp); } -mobj_t* braintargets[32]; -int numbraintargets; -int braintargeton = 0; +static shortptr_t /*mobj_t **/ braintargets[32]; +static isb_int8_t numbraintargets; +static isb_int8_t braintargeton = 0; void A_BrainAwake (mobj_t* mo) { @@ -1816,20 +1822,19 @@ void A_BrainAwake (mobj_t* mo) // find all the target spots numbraintargets = 0; braintargeton = 0; - - thinker = thinkercap.next; - for (thinker = thinkercap.next ; + + for (thinker = thinker_next(&thinkercap) ; thinker != &thinkercap ; - thinker = thinker->next) + thinker = thinker_next(thinker)) { - if (thinker->function.acp1 != (actionf_p1)P_MobjThinker) + if (thinker->function != ThinkF_P_MobjThinker) continue; // not a mobj m = (mobj_t *)thinker; if (m->type == MT_BOSSTARGET ) { - braintargets[numbraintargets] = m; + braintargets[numbraintargets] = mobj_to_shortptr(m); numbraintargets++; } } @@ -1851,12 +1856,12 @@ void A_BrainScream (mobj_t* mo) int z; mobj_t* th; - for (x=mo->x - 196*FRACUNIT ; x< mo->x + 320*FRACUNIT ; x+= FRACUNIT*8) + for (x= mo->xy.x - 196 * FRACUNIT ; x < mo->xy.x + 320 * FRACUNIT ; x+= FRACUNIT * 8) { - y = mo->y - 320*FRACUNIT; + y = mo->xy.y - 320 * FRACUNIT; z = 128 + P_Random()*2*FRACUNIT; th = P_SpawnMobj (x,y,z, MT_ROCKET); - th->momz = P_Random()*512; + mobj_full(th)->momz = P_Random()*512; P_SetMobjState (th, S_BRAINEXPLODE1); @@ -1877,11 +1882,11 @@ void A_BrainExplode (mobj_t* mo) int z; mobj_t* th; - x = mo->x + P_SubRandom() * 2048; - y = mo->y; + x = mo->xy.x + P_SubRandom() * 2048; + y = mo->xy.y; z = 128 + P_Random()*2*FRACUNIT; th = P_SpawnMobj (x,y,z, MT_ROCKET); - th->momz = P_Random()*512; + mobj_full(th)->momz = P_Random()*512; P_SetMobjState (th, S_BRAINEXPLODE1); @@ -1908,20 +1913,27 @@ void A_BrainSpit (mobj_t* mo) return; // shoot a cube at current target - targ = braintargets[braintargeton]; + targ = shortptr_to_mobj(braintargets[braintargeton]); if (numbraintargets == 0) { I_Error("A_BrainSpit: numbraintargets was 0 (vanilla crashes here)"); } - braintargeton = (braintargeton+1)%numbraintargets; + braintargeton = (isb_int8_t)((braintargeton+1)%numbraintargets); // spawn brain missile newmobj = P_SpawnMissile (mo, targ, MT_SPAWNSHOT); - newmobj->target = targ; - newmobj->reactiontime = - ((targ->y - mo->y)/newmobj->momy) / newmobj->state->tics; + mobj_full(newmobj)->sp_target = mobj_to_shortptr(targ); + int reactiontime =((targ->xy.y - mo->xy.y) / mobj_full(newmobj)->momy) / state_tics(mobj_state(newmobj)); +#if DOOM_SMALL && !PICO_ON_DEVICE + // todo graham no idea if this can happen + if (reactiontime > 255) { + printf("WARNING: reaction time > 255"); // this is doom II onyl anyway + reactiontime = 255; + } +#endif + mobj_reactiontime(newmobj) = reactiontime; - S_StartSound(NULL, sfx_bospit); + S_StartUnpositionedSound( sfx_bospit); } @@ -1931,7 +1943,7 @@ void A_SpawnFly (mobj_t* mo); // travelling cube sound void A_SpawnSound (mobj_t* mo) { - S_StartSound (mo,sfx_boscub); + S_StartObjSound (mo,sfx_boscub); A_SpawnFly(mo); } @@ -1943,14 +1955,14 @@ void A_SpawnFly (mobj_t* mo) int r; mobjtype_t type; - if (--mo->reactiontime) + if (--mobj_reactiontime(mo)) return; // still flying - targ = P_SubstNullMobj(mo->target); + targ = P_SubstNullMobj(mobj_target(mo)); // First spawn teleport fog. - fog = P_SpawnMobj (targ->x, targ->y, targ->z, MT_SPAWNFIRE); - S_StartSound (fog, sfx_telept); + fog = P_SpawnMobj (targ->xy.x, targ->xy.y, targ->z, MT_SPAWNFIRE); + S_StartObjSound (fog, sfx_telept); // Randomly select monster to spawn. r = P_Random (); @@ -1980,12 +1992,12 @@ void A_SpawnFly (mobj_t* mo) else type = MT_BRUISER; - newmobj = P_SpawnMobj (targ->x, targ->y, targ->z, type); + newmobj = P_SpawnMobj (targ->xy.x, targ->xy.y, targ->z, type); if (P_LookForPlayers (newmobj, true) ) - P_SetMobjState (newmobj, newmobj->info->seestate); + P_SetMobjState (newmobj, mobj_info(newmobj)->seestate); // telefrag anything in this spot - P_TeleportMove (newmobj, newmobj->x, newmobj->y); + P_TeleportMove (newmobj, newmobj->xy.x, newmobj->xy.y); // remove self (i.e., cube). P_RemoveMobj (mo); @@ -1999,12 +2011,12 @@ void A_PlayerScream (mobj_t* mo) int sound = sfx_pldeth; if ( (gamemode == commercial) - && (mo->health < -50)) + && (mobj_full(mo)->health < -50)) { // IF THE PLAYER DIES // LESS THAN -50% WITHOUT GIBBING sound = sfx_pdiehi; } - S_StartSound (mo, sound); + S_StartObjSound (mo, sound); } diff --git a/src/doom/p_floor.c b/src/doom/p_floor.c index c120d616..1ec17cca 100644 --- a/src/doom/p_floor.c +++ b/src/doom/p_floor.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -48,7 +49,7 @@ T_MovePlane int direction ) { boolean flag; - fixed_t lastpos; + sectorheight_t lastpos; switch(floorOrCeiling) { @@ -58,14 +59,14 @@ T_MovePlane { case -1: // DOWN - if (sector->floorheight - speed < dest) + if (sector_floorheight(sector) - speed < dest) { - lastpos = sector->floorheight; - sector->floorheight = dest; + lastpos = sector->rawfloorheight; + sector_set_floorheight(sector, dest); flag = P_ChangeSector(sector,crush); if (flag == true) { - sector->floorheight =lastpos; + sector->rawfloorheight =lastpos; P_ChangeSector(sector,crush); //return crushed; } @@ -73,12 +74,12 @@ T_MovePlane } else { - lastpos = sector->floorheight; - sector->floorheight -= speed; + lastpos = sector->rawfloorheight; + sector_delta_floorheight(sector, -speed); flag = P_ChangeSector(sector,crush); if (flag == true) { - sector->floorheight = lastpos; + sector->rawfloorheight =lastpos; P_ChangeSector(sector,crush); return crushed; } @@ -87,14 +88,14 @@ T_MovePlane case 1: // UP - if (sector->floorheight + speed > dest) + if (sector_floorheight(sector) + speed > dest) { - lastpos = sector->floorheight; - sector->floorheight = dest; + lastpos = sector->rawfloorheight; + sector_set_floorheight(sector, dest); flag = P_ChangeSector(sector,crush); if (flag == true) { - sector->floorheight = lastpos; + sector->rawfloorheight = lastpos; P_ChangeSector(sector,crush); //return crushed; } @@ -103,14 +104,14 @@ T_MovePlane else { // COULD GET CRUSHED - lastpos = sector->floorheight; - sector->floorheight += speed; + lastpos = sector->rawfloorheight; + sector_delta_floorheight(sector, speed); flag = P_ChangeSector(sector,crush); if (flag == true) { if (crush == true) return crushed; - sector->floorheight = lastpos; + sector->rawfloorheight = lastpos; P_ChangeSector(sector,crush); return crushed; } @@ -125,15 +126,15 @@ T_MovePlane { case -1: // DOWN - if (sector->ceilingheight - speed < dest) + if (sector_ceilingheight(sector) - speed < dest) { - lastpos = sector->ceilingheight; - sector->ceilingheight = dest; + lastpos = sector->rawceilingheight; + sector_set_ceilingheight(sector, dest); flag = P_ChangeSector(sector,crush); if (flag == true) { - sector->ceilingheight = lastpos; + sector->rawceilingheight = lastpos; P_ChangeSector(sector,crush); //return crushed; } @@ -142,15 +143,15 @@ T_MovePlane else { // COULD GET CRUSHED - lastpos = sector->ceilingheight; - sector->ceilingheight -= speed; + lastpos = sector->rawceilingheight; + sector_delta_ceilingheight(sector, -speed); flag = P_ChangeSector(sector,crush); if (flag == true) { if (crush == true) return crushed; - sector->ceilingheight = lastpos; + sector->rawceilingheight = lastpos; P_ChangeSector(sector,crush); return crushed; } @@ -159,14 +160,14 @@ T_MovePlane case 1: // UP - if (sector->ceilingheight + speed > dest) + if (sector_ceilingheight(sector) + speed > dest) { - lastpos = sector->ceilingheight; - sector->ceilingheight = dest; + lastpos = sector->rawceilingheight; + sector_set_ceilingheight(sector, dest); flag = P_ChangeSector(sector,crush); if (flag == true) { - sector->ceilingheight = lastpos; + sector->rawceilingheight = lastpos; P_ChangeSector(sector,crush); //return crushed; } @@ -174,8 +175,8 @@ T_MovePlane } else { - lastpos = sector->ceilingheight; - sector->ceilingheight += speed; + lastpos = sector->rawceilingheight; + sector_delta_ceilingheight(sector, speed); flag = P_ChangeSector(sector,crush); // UNUSED #if 0 @@ -213,7 +214,7 @@ void T_MoveFloor(floormove_t* floor) if (res == pastdest) { - floor->sector->specialdata = NULL; + floor->sector->specialdata = 0; if (floor->direction == 1) { @@ -272,8 +273,8 @@ EV_DoFloor rtn = 1; floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0); P_AddThinker (&floor->thinker); - sec->specialdata = floor; - floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + sec->specialdata = ptr_to_shortptr(floor); + floor->thinker.function = ThinkF_T_MoveFloor; floor->type = floortype; floor->crush = false; @@ -301,7 +302,7 @@ EV_DoFloor floor->speed = FLOORSPEED * 4; floor->floordestheight = P_FindHighestFloorSurrounding(sec); - if (floor->floordestheight != sec->floorheight) + if (floor->floordestheight != sector_floorheight(sec)) floor->floordestheight += 8*FRACUNIT; break; @@ -313,8 +314,8 @@ EV_DoFloor floor->speed = FLOORSPEED; floor->floordestheight = P_FindLowestCeilingSurrounding(sec); - if (floor->floordestheight > sec->ceilingheight) - floor->floordestheight = sec->ceilingheight; + if (floor->floordestheight > sector_ceilingheight(sec)) + floor->floordestheight = sector_ceilingheight(sec); floor->floordestheight -= (8*FRACUNIT)* (floortype == raiseFloorCrush); break; @@ -324,7 +325,7 @@ EV_DoFloor floor->sector = sec; floor->speed = FLOORSPEED*4; floor->floordestheight = - P_FindNextHighestFloor(sec,sec->floorheight); + P_FindNextHighestFloor(sec,sector_floorheight(sec)); break; case raiseFloorToNearest: @@ -332,21 +333,21 @@ EV_DoFloor floor->sector = sec; floor->speed = FLOORSPEED; floor->floordestheight = - P_FindNextHighestFloor(sec,sec->floorheight); + P_FindNextHighestFloor(sec,sector_floorheight(sec)); break; case raiseFloor24: floor->direction = 1; floor->sector = sec; floor->speed = FLOORSPEED; - floor->floordestheight = floor->sector->floorheight + + floor->floordestheight = sector_floorheight(floor->sector) + 24 * FRACUNIT; break; case raiseFloor512: floor->direction = 1; floor->sector = sec; floor->speed = FLOORSPEED; - floor->floordestheight = floor->sector->floorheight + + floor->floordestheight = sector_floorheight(floor->sector) + 512 * FRACUNIT; break; @@ -354,10 +355,10 @@ EV_DoFloor floor->direction = 1; floor->sector = sec; floor->speed = FLOORSPEED; - floor->floordestheight = floor->sector->floorheight + + floor->floordestheight = sector_floorheight(floor->sector) + 24 * FRACUNIT; - sec->floorpic = line->frontsector->floorpic; - sec->special = line->frontsector->special; + sec->floorpic = line_frontsector(line)->floorpic; + sec->special = line_frontsector(line)->special; break; case raiseToTexture: @@ -373,21 +374,21 @@ EV_DoFloor if (twoSided (secnum, i) ) { side = getSide(secnum,i,0); - if (side->bottomtexture >= 0) - if (textureheight[side->bottomtexture] < + if (side_bottomtexture(side) >= 0) + if (texture_height(side_bottomtexture(side)) < minsize) minsize = - textureheight[side->bottomtexture]; + texture_height(side_bottomtexture(side)); side = getSide(secnum,i,1); - if (side->bottomtexture >= 0) - if (textureheight[side->bottomtexture] < + if (side_bottomtexture(side) >= 0) + if (texture_height(side_bottomtexture(side)) < minsize) minsize = - textureheight[side->bottomtexture]; + texture_height(side_bottomtexture(side)); } } floor->floordestheight = - floor->sector->floorheight + minsize; + sector_floorheight(floor->sector) + minsize; } break; @@ -403,11 +404,11 @@ EV_DoFloor { if ( twoSided(secnum, i) ) { - if (getSide(secnum,i,0)->sector-sectors == secnum) + if (side_sectornum(getSide(secnum,i,0)) == secnum) { sec = getSector(secnum,i,1); - if (sec->floorheight == floor->floordestheight) + if (sector_floorheight(sec) == floor->floordestheight) { floor->texture = sec->floorpic; floor->newspecial = sec->special; @@ -418,7 +419,7 @@ EV_DoFloor { sec = getSector(secnum,i,0); - if (sec->floorheight == floor->floordestheight) + if (sector_floorheight(sec) == floor->floordestheight) { floor->texture = sec->floorpic; floor->newspecial = sec->special; @@ -475,8 +476,8 @@ EV_BuildStairs rtn = 1; floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0); P_AddThinker (&floor->thinker); - sec->specialdata = floor; - floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + sec->specialdata = ptr_to_shortptr(floor); + floor->thinker.function = ThinkF_T_MoveFloor; floor->direction = 1; floor->sector = sec; switch(type) @@ -491,7 +492,7 @@ EV_BuildStairs break; } floor->speed = speed; - height = sec->floorheight + stairsize; + height = sector_floorheight(sec) + stairsize; floor->floordestheight = height; // Initialize floor->type = lowerFloor; @@ -507,16 +508,16 @@ EV_BuildStairs ok = 0; for (i = 0;i < sec->linecount;i++) { - if ( !((sec->lines[i])->flags & ML_TWOSIDED) ) + if ( !((line_flags(sector_line(sec,i))) & ML_TWOSIDED) ) continue; - - tsec = (sec->lines[i])->frontsector; + + tsec = line_frontsector(sector_line(sec, i)); newsecnum = tsec-sectors; if (secnum != newsecnum) continue; - tsec = (sec->lines[i])->backsector; + tsec = line_backsector(sector_line(sec, i)); newsecnum = tsec - sectors; if (tsec->floorpic != texture) @@ -533,8 +534,8 @@ EV_BuildStairs P_AddThinker (&floor->thinker); - sec->specialdata = floor; - floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + sec->specialdata = ptr_to_shortptr(floor); + floor->thinker.function = ThinkF_T_MoveFloor; floor->direction = 1; floor->sector = sec; floor->speed = speed; diff --git a/src/doom/p_inter.c b/src/doom/p_inter.c index 633408fe..343072b6 100644 --- a/src/doom/p_inter.c +++ b/src/doom/p_inter.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -228,7 +229,7 @@ P_GiveBody player->health += num; if (player->health > MAXHEALTH) player->health = MAXHEALTH; - player->mo->health = player->health; + mobj_full(player->mo)->health = player->health; return true; } @@ -339,7 +340,7 @@ P_TouchSpecialThing delta = special->z - toucher->z; - if (delta > toucher->height + if (delta > mobj_height(toucher) || delta < -8*FRACUNIT) { // out of reach @@ -348,15 +349,15 @@ P_TouchSpecialThing sound = sfx_itemup; - player = toucher->player; + player = mobj_player(toucher); // Dead thing touching. // Can happen with a sliding player corpse. - if (toucher->health <= 0) + if (mobj_full(toucher)->health <= 0) return; // Identify by sprite. - switch (special->sprite) + switch (mobj_sprite(special)) { // armor case SPR_ARM1: @@ -376,7 +377,7 @@ P_TouchSpecialThing player->health++; // can go over 100% if (player->health > deh_max_health) player->health = deh_max_health; - player->mo->health = player->health; + mobj_full(player->mo)->health = player->health; player->message = DEH_String(GOTHTHBONUS); break; @@ -395,7 +396,7 @@ P_TouchSpecialThing player->health += deh_soulsphere_health; if (player->health > deh_max_soulsphere) player->health = deh_max_soulsphere; - player->mo->health = player->health; + mobj_full(player->mo)->health = player->health; player->message = DEH_String(GOTSUPER); sound = sfx_getpow; break; @@ -404,7 +405,7 @@ P_TouchSpecialThing if (gamemode != commercial) return; player->health = deh_megasphere_health; - player->mo->health = player->health; + mobj_full(player->mo)->health = player->health; // We always give armor type 2 for the megasphere; dehacked only // affects the MegaArmor. P_GiveArmor (player, 2); @@ -677,16 +678,16 @@ P_KillMobj target->flags &= ~MF_NOGRAVITY; target->flags |= MF_CORPSE|MF_DROPOFF; - target->height >>= 2; + mobj_full(target)->height >>= 2; - if (source && source->player) + if (source && mobj_full(source)->sp_player) { // count for intermission if (target->flags & MF_COUNTKILL) - source->player->killcount++; + mobj_player(source)->killcount++; - if (target->player) - source->player->frags[target->player-players]++; + if (mobj_full(target)->sp_player) + mobj_player(source)->frags[mobj_player(target)-players]++; } else if (!netgame && (target->flags & MF_COUNTKILL) ) { @@ -695,17 +696,17 @@ P_KillMobj players[0].killcount++; } - if (target->player) + if (mobj_full(target)->sp_player) { // count environment kills against you if (!source) - target->player->frags[target->player-players]++; + mobj_player(target)->frags[mobj_player(target)-players]++; target->flags &= ~MF_SOLID; - target->player->playerstate = PST_DEAD; - P_DropWeapon (target->player); + mobj_player(target)->playerstate = PST_DEAD; + P_DropWeapon (mobj_player(target)); - if (target->player == &players[consoleplayer] + if (mobj_player(target) == &players[consoleplayer] && automapactive) { // don't die in auto map, @@ -715,13 +716,13 @@ P_KillMobj } - if (target->health < -target->info->spawnhealth - && target->info->xdeathstate) + if (mobj_full(target)->health < -mobj_info(target)->spawnhealth + && mobj_info(target)->xdeathstate) { - P_SetMobjState (target, target->info->xdeathstate); + P_SetMobjState (target, mobj_info(target)->xdeathstate); } else - P_SetMobjState (target, target->info->deathstate); + P_SetMobjState (target, mobj_info(target)->deathstate); target->tics -= P_Random()&3; if (target->tics < 1) @@ -731,10 +732,12 @@ P_KillMobj // In Chex Quest, monsters don't drop items. +#if !DOOM_ONLY if (gameversion == exe_chex) { return; } +#endif // Drop stuff. // This determines the kind of object spawned @@ -758,7 +761,7 @@ P_KillMobj return; } - mo = P_SpawnMobj (target->x,target->y,ONFLOORZ, item); + mo = P_SpawnMobj (target->xy.x, target->xy.y, ONFLOORZ, item); mo->flags |= MF_DROPPED; // special versions of items } @@ -792,15 +795,15 @@ P_DamageMobj if ( !(target->flags & MF_SHOOTABLE) ) return; // shouldn't happen... - if (target->health <= 0) + if (mobj_full(target)->health <= 0) return; if ( target->flags & MF_SKULLFLY ) { - target->momx = target->momy = target->momz = 0; + mobj_full(target)->momx = mobj_full(target)->momy = mobj_full(target)->momz = 0; } - player = target->player; + player = mobj_player(target); if (player && gameskill == sk_baby) damage >>= 1; // take half damage in trainer mode @@ -811,19 +814,19 @@ P_DamageMobj if (inflictor && !(target->flags & MF_NOCLIP) && (!source - || !source->player - || source->player->readyweapon != wp_chainsaw)) + || !mobj_full(source)->sp_player + || mobj_player(source)->readyweapon != wp_chainsaw)) { - ang = R_PointToAngle2 ( inflictor->x, - inflictor->y, - target->x, - target->y); + ang = R_PointToAngle2 ( inflictor->xy.x, + inflictor->xy.y, + target->xy.x, + target->xy.y); - thrust = damage*(FRACUNIT>>3)*100/target->info->mass; + thrust = damage*(FRACUNIT>>3)*100/mobj_info(target)->mass; // make fall forwards sometimes if ( damage < 40 - && damage > target->health + && damage > mobj_full(target)->health && target->z - inflictor->z > 64*FRACUNIT && (P_Random ()&1) ) { @@ -832,18 +835,18 @@ P_DamageMobj } ang >>= ANGLETOFINESHIFT; - target->momx += FixedMul (thrust, finecosine[ang]); - target->momy += FixedMul (thrust, finesine[ang]); + mobj_full(target)->momx += FixedMul (thrust, finecosine(ang)); + mobj_full(target)->momy += FixedMul (thrust, finesine(ang)); } // player specific if (player) { // end of game hell hack - if (target->subsector->sector->special == 11 - && damage >= target->health) + if (mobj_sector(target)->special == 11 + && damage >= mobj_full(target)->health) { - damage = target->health - 1; + damage = mobj_full(target)->health - 1; } @@ -889,34 +892,34 @@ P_DamageMobj } // do the damage - target->health -= damage; - if (target->health <= 0) + mobj_full(target)->health -= damage; + if (mobj_full(target)->health <= 0) { P_KillMobj (source, target); return; } - if ( (P_Random () < target->info->painchance) + if ( (P_Random () < mobj_info(target)->painchance) && !(target->flags&MF_SKULLFLY) ) { target->flags |= MF_JUSTHIT; // fight back! - P_SetMobjState (target, target->info->painstate); + P_SetMobjState (target, mobj_info(target)->painstate); } - target->reactiontime = 0; // we're awake now... + mobj_reactiontime(target) = 0; // we're awake now... - if ( (!target->threshold || target->type == MT_VILE) + if ( (!mobj_full(target)->threshold || target->type == MT_VILE) && source && source != target && source->type != MT_VILE) { // if not intent on another player, // chase after this one - target->target = source; - target->threshold = BASETHRESHOLD; - if (target->state == &states[target->info->spawnstate] - && target->info->seestate != S_NULL) - P_SetMobjState (target, target->info->seestate); + mobj_full(target)->sp_target = mobj_to_shortptr(source); + mobj_full(target)->threshold = BASETHRESHOLD; + if (mobj_state_num(target) == mobj_info(target)->spawnstate + && mobj_info(target)->seestate != S_NULL) + P_SetMobjState (target, mobj_info(target)->seestate); } } diff --git a/src/doom/p_lights.c b/src/doom/p_lights.c index 863338dc..dbb2160b 100644 --- a/src/doom/p_lights.c +++ b/src/doom/p_lights.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -70,7 +71,7 @@ void P_SpawnFireFlicker (sector_t* sector) P_AddThinker (&flick->thinker); - flick->thinker.function.acp1 = (actionf_p1) T_FireFlicker; + flick->thinker.function = ThinkF_T_FireFlicker; flick->sector = sector; flick->maxlight = sector->lightlevel; flick->minlight = P_FindMinSurroundingLight(sector,sector->lightlevel)+16; @@ -125,7 +126,7 @@ void P_SpawnLightFlash (sector_t* sector) P_AddThinker (&flash->thinker); - flash->thinker.function.acp1 = (actionf_p1) T_LightFlash; + flash->thinker.function = ThinkF_T_LightFlash; flash->sector = sector; flash->maxlight = sector->lightlevel; @@ -185,7 +186,7 @@ P_SpawnStrobeFlash flash->sector = sector; flash->darktime = fastOrSlow; flash->brighttime = STROBEBRIGHT; - flash->thinker.function.acp1 = (actionf_p1) T_StrobeFlash; + flash->thinker.function = ThinkF_T_StrobeFlash; flash->maxlight = sector->lightlevel; flash->minlight = P_FindMinSurroundingLight(sector, sector->lightlevel); @@ -239,12 +240,12 @@ void EV_TurnTagLightsOff(line_t* line) for (j = 0;j < numsectors; j++, sector++) { - if (sector->tag == line->tag) + if (sector->tag == line_tag(line)) { min = sector->lightlevel; for (i = 0;i < sector->linecount; i++) { - templine = sector->lines[i]; + templine = sector_line(sector, i); tsec = getNextSector(templine,sector); if (!tsec) continue; @@ -275,7 +276,7 @@ EV_LightTurnOn for (i=0;itag == line->tag) + if (sector->tag == line_tag(line)) { // bright = 0 means to search // for highest light level @@ -284,7 +285,7 @@ EV_LightTurnOn { for (j = 0;j < sector->linecount; j++) { - templine = sector->lines[j]; + templine = sector_line(sector, j); temp = getNextSector(templine,sector); if (!temp) @@ -342,7 +343,7 @@ void P_SpawnGlowingLight(sector_t* sector) g->sector = sector; g->minlight = P_FindMinSurroundingLight(sector,sector->lightlevel); g->maxlight = sector->lightlevel; - g->thinker.function.acp1 = (actionf_p1) T_Glow; + g->thinker.function = ThinkF_T_Glow; g->direction = -1; sector->special = 0; diff --git a/src/doom/p_local.h b/src/doom/p_local.h index 95fa4053..4776cce1 100644 --- a/src/doom/p_local.h +++ b/src/doom/p_local.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -95,10 +96,10 @@ void P_PlayerThink (player_t* player); // Time interval for item respawning. #define ITEMQUESIZE 128 -extern mapthing_t itemrespawnque[ITEMQUESIZE]; -extern int itemrespawntime[ITEMQUESIZE]; -extern int iquehead; -extern int iquetail; +extern spawnpoint_t itemrespawnque[ITEMQUESIZE]; +extern isb_int16_t itemrespawntime[ITEMQUESIZE]; +extern isb_uint8_t iquehead; +extern isb_uint8_t iquetail; void P_RespawnSpecials (void); @@ -136,7 +137,6 @@ typedef struct fixed_t y; fixed_t dx; fixed_t dy; - } divline_t; typedef struct @@ -152,7 +152,11 @@ typedef struct // Extended MAXINTERCEPTS, to allow for intercepts overrun emulation. #define MAXINTERCEPTS_ORIGINAL 128 +#if !NO_INTERCEPTS_OVERRUN #define MAXINTERCEPTS (MAXINTERCEPTS_ORIGINAL + 61) +#else +#define MAXINTERCEPTS MAXINTERCEPTS_ORIGINAL +#endif extern intercept_t intercepts[MAXINTERCEPTS]; extern intercept_t* intercept_p; @@ -193,6 +197,7 @@ P_PathTraverse void P_UnsetThingPosition (mobj_t* thing); void P_SetThingPosition (mobj_t* thing); +void P_ResetThingPosition (mobj_t* thing, fixed_t x, fixed_t y); // @@ -258,14 +263,18 @@ P_RadiusAttack // // P_SETUP // -extern byte* rejectmatrix; // for fast sight rejection -extern short* blockmaplump; // offsets in blockmap are from here -extern short* blockmap; -extern int bmapwidth; -extern int bmapheight; // in mapblocks +extern should_be_const byte* rejectmatrix; // for fast sight rejection +extern rowad_const short* blockmaplump; // offsets in blockmap are from here +#if !USE_WHD +extern rowad_const short* blockmap; +#else +extern byte *blockmap_whd; +#endif +extern cardinal_t bmapwidth; +extern cardinal_t bmapheight; // in mapblocks extern fixed_t bmaporgx; extern fixed_t bmaporgy; // origin of block map -extern mobj_t** blocklinks; // for thing chains +extern shortptr_t /*mobj_t*/* blocklinks; // for thing chains diff --git a/src/doom/p_map.c b/src/doom/p_map.c index 005a8155..96b41d30 100644 --- a/src/doom/p_map.c +++ b/src/doom/p_map.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard, Andrey Budko +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -101,10 +102,10 @@ boolean PIT_StompThing (mobj_t* thing) if (!(thing->flags & MF_SHOOTABLE) ) return true; - blockdist = thing->radius + tmthing->radius; + blockdist = mobj_radius(thing) + mobj_radius(tmthing); - if ( abs(thing->x - tmx) >= blockdist - || abs(thing->y - tmy) >= blockdist ) + if (abs(thing->xy.x - tmx) >= blockdist + || abs(thing->xy.y - tmy) >= blockdist ) { // didn't hit it return true; @@ -115,7 +116,7 @@ boolean PIT_StompThing (mobj_t* thing) return true; // monsters don't stomp things except on boss level - if ( !tmthing->player && gamemap != 30) + if ( !mobj_full(tmthing)->sp_player && gamemap != 30) return false; P_DamageMobj (thing, tmthing, tmthing, 10000); @@ -149,10 +150,10 @@ P_TeleportMove tmx = x; tmy = y; - tmbbox[BOXTOP] = y + tmthing->radius; - tmbbox[BOXBOTTOM] = y - tmthing->radius; - tmbbox[BOXRIGHT] = x + tmthing->radius; - tmbbox[BOXLEFT] = x - tmthing->radius; + tmbbox[BOXTOP] = y + mobj_radius(tmthing); + tmbbox[BOXBOTTOM] = y - mobj_radius(tmthing); + tmbbox[BOXRIGHT] = x + mobj_radius(tmthing); + tmbbox[BOXLEFT] = x - mobj_radius(tmthing); newsubsec = R_PointInSubsector (x,y); ceilingline = NULL; @@ -161,8 +162,9 @@ P_TeleportMove // that contains the point. // Any contacted lines the step closer together // will adjust them. - tmfloorz = tmdropoffz = newsubsec->sector->floorheight; - tmceilingz = newsubsec->sector->ceilingheight; + const sector_t *sec = subsector_sector(newsubsec); + tmfloorz = tmdropoffz = sector_floorheight(sec); + tmceilingz = sector_ceilingheight(sec); validcount++; numspechit = 0; @@ -180,14 +182,9 @@ P_TeleportMove // the move is ok, // so link the thing into its new position - P_UnsetThingPosition (thing); - - thing->floorz = tmfloorz; - thing->ceilingz = tmceilingz; - thing->x = x; - thing->y = y; - - P_SetThingPosition (thing); + mobj_full(thing)->floorz = tmfloorz; + mobj_full(thing)->ceilingz = tmceilingz; + P_ResetThingPosition (thing, x, y); return true; } @@ -205,11 +202,20 @@ static void SpechitOverrun(line_t *ld); // boolean PIT_CheckLine (line_t* ld) { +#if !USE_RAW_MAPLINEDEF if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT] || tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT] || tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM] || tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP] ) return true; +#else + // todo graham revisit + if ((tmbbox[BOXRIGHT] <= vertex_x(line_v1(ld)) && tmbbox[BOXRIGHT] <= vertex_x(line_v2(ld))) + || (tmbbox[BOXLEFT] >= vertex_x(line_v1(ld)) && tmbbox[BOXLEFT] >= vertex_x(line_v2(ld))) + || (tmbbox[BOXTOP] <= vertex_y(line_v1(ld)) && tmbbox[BOXTOP] <= vertex_y(line_v2(ld))) + || (tmbbox[BOXBOTTOM] >= vertex_y(line_v1(ld)) && tmbbox[BOXBOTTOM] >= vertex_y(line_v2(ld)))) + return true; +#endif if (P_BoxOnLineSide (tmbbox, ld) != -1) return true; @@ -225,15 +231,15 @@ boolean PIT_CheckLine (line_t* ld) // so two special lines that are only 8 pixels apart // could be crossed in either order. - if (!ld->backsector) + if (!line_backsector(ld)) return false; // one sided line if (!(tmthing->flags & MF_MISSILE) ) { - if ( ld->flags & ML_BLOCKING ) + if ( line_flags(ld) & ML_BLOCKING ) return false; // explicitly blocking everything - if ( !tmthing->player && ld->flags & ML_BLOCKMONSTERS ) + if ( !mobj_is_player(tmthing) && line_flags(ld) & ML_BLOCKMONSTERS ) return false; // block monsters only } @@ -254,7 +260,7 @@ boolean PIT_CheckLine (line_t* ld) tmdropoffz = lowfloor; // if contacted a special line, add it to the list - if (ld->special) + if (line_special(ld)) { spechit[numspechit] = ld; numspechit++; @@ -281,10 +287,10 @@ boolean PIT_CheckThing (mobj_t* thing) if (!(thing->flags & (MF_SOLID|MF_SPECIAL|MF_SHOOTABLE) )) return true; - blockdist = thing->radius + tmthing->radius; + blockdist = mobj_radius(thing) + mobj_radius(tmthing); - if ( abs(thing->x - tmx) >= blockdist - || abs(thing->y - tmy) >= blockdist ) + if (abs(thing->xy.x - tmx) >= blockdist + || abs(thing->xy.y - tmy) >= blockdist ) { // didn't hit it return true; @@ -297,14 +303,14 @@ boolean PIT_CheckThing (mobj_t* thing) // check for skulls slamming into things if (tmthing->flags & MF_SKULLFLY) { - damage = ((P_Random()%8)+1)*tmthing->info->damage; + damage = ((P_Random()%8)+1)*mobj_info(tmthing)->damage; P_DamageMobj (thing, tmthing, tmthing, damage); tmthing->flags &= ~MF_SKULLFLY; - tmthing->momx = tmthing->momy = tmthing->momz = 0; + mobj_full(tmthing)->momx = mobj_full(tmthing)->momy = mobj_full(tmthing)->momz = 0; - P_SetMobjState (tmthing, tmthing->info->spawnstate); + P_SetMobjState (tmthing, mobj_info(tmthing)->spawnstate); return false; // stop moving } @@ -314,18 +320,18 @@ boolean PIT_CheckThing (mobj_t* thing) if (tmthing->flags & MF_MISSILE) { // see if it went over / under - if (tmthing->z > thing->z + thing->height) + if (tmthing->z > thing->z + mobj_height(thing)) return true; // overhead - if (tmthing->z+tmthing->height < thing->z) + if (tmthing->z+mobj_height(tmthing) < thing->z) return true; // underneath - if (tmthing->target - && (tmthing->target->type == thing->type || - (tmthing->target->type == MT_KNIGHT && thing->type == MT_BRUISER)|| - (tmthing->target->type == MT_BRUISER && thing->type == MT_KNIGHT) ) ) + if (mobj_full(tmthing)->sp_target + && (mobj_target(tmthing)->type == thing->type || + (mobj_target(tmthing)->type == MT_KNIGHT && thing->type == MT_BRUISER)|| + (mobj_target(tmthing)->type == MT_BRUISER && thing->type == MT_KNIGHT) ) ) { // Don't hit same species as originator. - if (thing == tmthing->target) + if (thing == mobj_target(tmthing)) return true; // sdh: Add deh_species_infighting here. We can override the @@ -347,8 +353,8 @@ boolean PIT_CheckThing (mobj_t* thing) } // damage / explode - damage = ((P_Random()%8)+1)*tmthing->info->damage; - P_DamageMobj (thing, tmthing, tmthing->target, damage); + damage = ((P_Random()%8)+1)*mobj_info(tmthing)->damage; + P_DamageMobj (thing, tmthing, mobj_target(tmthing), damage); // don't traverse any more return false; @@ -418,10 +424,10 @@ P_CheckPosition tmx = x; tmy = y; - tmbbox[BOXTOP] = y + tmthing->radius; - tmbbox[BOXBOTTOM] = y - tmthing->radius; - tmbbox[BOXRIGHT] = x + tmthing->radius; - tmbbox[BOXLEFT] = x - tmthing->radius; + tmbbox[BOXTOP] = y + mobj_radius(tmthing); + tmbbox[BOXBOTTOM] = y - mobj_radius(tmthing); + tmbbox[BOXRIGHT] = x + mobj_radius(tmthing); + tmbbox[BOXLEFT] = x - mobj_radius(tmthing); newsubsec = R_PointInSubsector (x,y); ceilingline = NULL; @@ -430,9 +436,10 @@ P_CheckPosition // that contains the point. // Any contacted lines the step closer together // will adjust them. - tmfloorz = tmdropoffz = newsubsec->sector->floorheight; - tmceilingz = newsubsec->sector->ceilingheight; - + const sector_t *sec = subsector_sector(newsubsec); + tmfloorz = tmdropoffz = sector_floorheight(sec); + tmceilingz = sector_ceilingheight(sec); + validcount++; numspechit = 0; @@ -455,6 +462,7 @@ P_CheckPosition return false; // check lines + line_check_reset(); xl = (tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT; xh = (tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT; yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT; @@ -492,13 +500,13 @@ P_TryMove if ( !(thing->flags & MF_NOCLIP) ) { - if (tmceilingz - tmfloorz < thing->height) + if (tmceilingz - tmfloorz < mobj_height(thing)) return false; // doesn't fit floatok = true; if ( !(thing->flags&MF_TELEPORT) - &&tmceilingz - thing->z < thing->height) + &&tmceilingz - thing->z < mobj_height(thing)) return false; // mobj must lower itself to fit if ( !(thing->flags&MF_TELEPORT) @@ -512,16 +520,11 @@ P_TryMove // the move is ok, // so link the thing into its new position - P_UnsetThingPosition (thing); - - oldx = thing->x; - oldy = thing->y; - thing->floorz = tmfloorz; - thing->ceilingz = tmceilingz; - thing->x = x; - thing->y = y; - - P_SetThingPosition (thing); + oldx = thing->xy.x; + oldy = thing->xy.y; + mobj_full(thing)->floorz = tmfloorz; + mobj_full(thing)->ceilingz = tmceilingz; + P_ResetThingPosition (thing, x, y); // if any special lines were hit, do the effect if (! (thing->flags&(MF_TELEPORT|MF_NOCLIP)) ) @@ -530,11 +533,11 @@ P_TryMove { // see if the line was crossed ld = spechit[numspechit]; - side = P_PointOnLineSide (thing->x, thing->y, ld); + side = P_PointOnLineSide (thing->xy.x, thing->xy.y, ld); oldside = P_PointOnLineSide (oldx, oldy, ld); if (side != oldside) { - if (ld->special) + if (line_special(ld)) P_CrossSpecialLine (ld-lines, oldside, thing); } } @@ -558,27 +561,31 @@ boolean P_ThingHeightClip (mobj_t* thing) { boolean onfloor; - onfloor = (thing->z == thing->floorz); + onfloor = (thing->z == mobj_floorz(thing)); - P_CheckPosition (thing, thing->x, thing->y); + P_CheckPosition (thing, thing->xy.x, thing->xy.y); // what about stranding a monster partially off an edge? - - thing->floorz = tmfloorz; - thing->ceilingz = tmceilingz; + + if (!mobj_is_static(thing)) { + mobj_full(thing)->floorz = tmfloorz; + mobj_full(thing)->ceilingz = tmceilingz; + } else { + onfloor = !(thing->flags & MF_FLOAT); + } if (onfloor) { // walking monsters rise and fall with the floor - thing->z = thing->floorz; + thing->z = mobj_floorz(thing); } else { // don't adjust a floating monster unless forced to - if (thing->z+thing->height > thing->ceilingz) - thing->z = thing->ceilingz - thing->height; + if (thing->z+mobj_height(thing) > mobj_ceilingz(thing)) + thing->z = mobj_ceilingz(thing) - mobj_height(thing); } - if (thing->ceilingz - thing->floorz < thing->height) + if (mobj_ceilingz(thing) - mobj_floorz(thing) < mobj_height(thing)) return false; return true; @@ -620,21 +627,22 @@ void P_HitSlideLine (line_t* ld) fixed_t newlen; - if (ld->slopetype == ST_HORIZONTAL) + if (line_is_horiz(ld)) { tmymove = 0; return; } - if (ld->slopetype == ST_VERTICAL) + if (line_is_vert(ld)) { tmxmove = 0; return; } - side = P_PointOnLineSide (slidemo->x, slidemo->y, ld); - - lineangle = R_PointToAngle2 (0,0, ld->dx, ld->dy); + side = P_PointOnLineSide (slidemo->xy.x, slidemo->xy.y, ld); + + // todo graham this is a fixed value + lineangle = R_PointToAngle2 (0,0, line_dx(ld), line_dy(ld)); if (side == 1) lineangle += ANG180; @@ -650,10 +658,10 @@ void P_HitSlideLine (line_t* ld) deltaangle >>= ANGLETOFINESHIFT; movelen = P_AproxDistance (tmxmove, tmymove); - newlen = FixedMul (movelen, finecosine[deltaangle]); + newlen = FixedMul (movelen, finecosine(deltaangle)); - tmxmove = FixedMul (newlen, finecosine[lineangle]); - tmymove = FixedMul (newlen, finesine[lineangle]); + tmxmove = FixedMul (newlen, finecosine(lineangle)); + tmymove = FixedMul (newlen, finesine(lineangle)); } @@ -669,9 +677,9 @@ boolean PTR_SlideTraverse (intercept_t* in) li = in->d.line; - if ( ! (li->flags & ML_TWOSIDED) ) + if ( ! (line_flags(li) & ML_TWOSIDED) ) { - if (P_PointOnLineSide (slidemo->x, slidemo->y, li)) + if (P_PointOnLineSide (slidemo->xy.x, slidemo->xy.y, li)) { // don't hit the back side return true; @@ -682,10 +690,10 @@ boolean PTR_SlideTraverse (intercept_t* in) // set openrange, opentop, openbottom P_LineOpening (li); - if (openrange < slidemo->height) + if (openrange < mobj_height(slidemo)) goto isblocking; // doesn't fit - if (opentop - slidemo->z < slidemo->height) + if (opentop - slidemo->z < mobj_height(slidemo)) goto isblocking; // mobj is too high if (openbottom - slidemo->z > 24*FRACUNIT ) @@ -738,35 +746,35 @@ void P_SlideMove (mobj_t* mo) // trace along the three leading corners - if (mo->momx > 0) + if (mobj_full(mo)->momx > 0) { - leadx = mo->x + mo->radius; - trailx = mo->x - mo->radius; + leadx = mo->xy.x + mobj_radius(mo); + trailx = mo->xy.x - mobj_radius(mo); } else { - leadx = mo->x - mo->radius; - trailx = mo->x + mo->radius; + leadx = mo->xy.x - mobj_radius(mo); + trailx = mo->xy.x + mobj_radius(mo); } - if (mo->momy > 0) + if (mobj_full(mo)->momy > 0) { - leady = mo->y + mo->radius; - traily = mo->y - mo->radius; + leady = mo->xy.y + mobj_radius(mo); + traily = mo->xy.y - mobj_radius(mo); } else { - leady = mo->y - mo->radius; - traily = mo->y + mo->radius; + leady = mo->xy.y - mobj_radius(mo); + traily = mo->xy.y + mobj_radius(mo); } bestslidefrac = FRACUNIT+1; - P_PathTraverse ( leadx, leady, leadx+mo->momx, leady+mo->momy, + P_PathTraverse ( leadx, leady, leadx+mobj_full(mo)->momx, leady+mobj_full(mo)->momy, PT_ADDLINES, PTR_SlideTraverse ); - P_PathTraverse ( trailx, leady, trailx+mo->momx, leady+mo->momy, + P_PathTraverse ( trailx, leady, trailx+mobj_full(mo)->momx, leady+mobj_full(mo)->momy, PT_ADDLINES, PTR_SlideTraverse ); - P_PathTraverse ( leadx, traily, leadx+mo->momx, traily+mo->momy, + P_PathTraverse ( leadx, traily, leadx+mobj_full(mo)->momx, traily+mobj_full(mo)->momy, PT_ADDLINES, PTR_SlideTraverse ); // move up to the wall @@ -774,8 +782,8 @@ void P_SlideMove (mobj_t* mo) { // the move most have hit the middle, so stairstep stairstep: - if (!P_TryMove (mo, mo->x, mo->y + mo->momy)) - P_TryMove (mo, mo->x + mo->momx, mo->y); + if (!P_TryMove (mo, mo->xy.x, mo->xy.y + mobj_full(mo)->momy)) + P_TryMove (mo, mo->xy.x + mobj_full(mo)->momx, mo->xy.y); return; } @@ -783,10 +791,10 @@ void P_SlideMove (mobj_t* mo) bestslidefrac -= 0x800; if (bestslidefrac > 0) { - newx = FixedMul (mo->momx, bestslidefrac); - newy = FixedMul (mo->momy, bestslidefrac); + newx = FixedMul (mobj_full(mo)->momx, bestslidefrac); + newy = FixedMul (mobj_full(mo)->momy, bestslidefrac); - if (!P_TryMove (mo, mo->x+newx, mo->y+newy)) + if (!P_TryMove (mo, mo->xy.x + newx, mo->xy.y + newy)) goto stairstep; } @@ -800,15 +808,15 @@ void P_SlideMove (mobj_t* mo) if (bestslidefrac <= 0) return; - tmxmove = FixedMul (mo->momx, bestslidefrac); - tmymove = FixedMul (mo->momy, bestslidefrac); + tmxmove = FixedMul (mobj_full(mo)->momx, bestslidefrac); + tmymove = FixedMul (mobj_full(mo)->momy, bestslidefrac); P_HitSlideLine (bestslideline); // clip the moves - mo->momx = tmxmove; - mo->momy = tmymove; + mobj_full(mo)->momx = tmxmove; + mobj_full(mo)->momy = tmymove; - if (!P_TryMove (mo, mo->x+tmxmove, mo->y+tmymove)) + if (!P_TryMove (mo, mo->xy.x + tmxmove, mo->xy.y + tmymove)) { goto retry; } @@ -853,7 +861,7 @@ PTR_AimTraverse (intercept_t* in) { li = in->d.line; - if ( !(li->flags & ML_TWOSIDED) ) + if ( !(line_flags(li) & ML_TWOSIDED) ) return false; // stop // Crosses a two sided line. @@ -866,16 +874,16 @@ PTR_AimTraverse (intercept_t* in) dist = FixedMul (attackrange, in->frac); - if (li->backsector == NULL - || li->frontsector->floorheight != li->backsector->floorheight) + if (line_backsector(li) == NULL + || line_frontsector(li)->rawfloorheight != line_backsector(li)->rawfloorheight) { slope = FixedDiv (openbottom - shootz , dist); if (slope > bottomslope) bottomslope = slope; } - if (li->backsector == NULL - || li->frontsector->ceilingheight != li->backsector->ceilingheight) + if (line_backsector(li) == NULL + || line_frontsector(li)->rawceilingheight != line_backsector(li)->rawceilingheight) { slope = FixedDiv (opentop - shootz , dist); if (slope < topslope) @@ -898,7 +906,7 @@ PTR_AimTraverse (intercept_t* in) // check angles to see if the thing can be aimed at dist = FixedMul (attackrange, in->frac); - thingtopslope = FixedDiv (th->z+th->height - shootz , dist); + thingtopslope = FixedDiv (th->z+mobj_height(th) - shootz , dist); if (thingtopslope < bottomslope) return true; // shot over the thing @@ -945,10 +953,10 @@ boolean PTR_ShootTraverse (intercept_t* in) { li = in->d.line; - if (li->special) + if (line_special(li)) P_ShootSpecialLine (shootthing, li); - if ( !(li->flags & ML_TWOSIDED) ) + if ( !(line_flags(li) & ML_TWOSIDED) ) goto hitline; // crosses a two sided line @@ -959,7 +967,7 @@ boolean PTR_ShootTraverse (intercept_t* in) // e6y: emulation of missed back side on two-sided lines. // backsector can be NULL when emulating missing back side. - if (li->backsector == NULL) + if (line_backsector(li) == NULL) { slope = FixedDiv (openbottom - shootz , dist); if (slope > aimslope) @@ -971,14 +979,14 @@ boolean PTR_ShootTraverse (intercept_t* in) } else { - if (li->frontsector->floorheight != li->backsector->floorheight) + if (line_frontsector(li)->rawfloorheight != line_backsector(li)->rawfloorheight) { slope = FixedDiv (openbottom - shootz , dist); if (slope > aimslope) goto hitline; } - if (li->frontsector->ceilingheight != li->backsector->ceilingheight) + if (line_frontsector(li)->rawceilingheight != line_backsector(li)->rawceilingheight) { slope = FixedDiv (opentop - shootz , dist); if (slope < aimslope) @@ -998,14 +1006,14 @@ boolean PTR_ShootTraverse (intercept_t* in) y = trace.y + FixedMul (trace.dy, frac); z = shootz + FixedMul (aimslope, FixedMul(frac, attackrange)); - if (li->frontsector->ceilingpic == skyflatnum) + if (line_frontsector(li)->ceilingpic == skyflatnum) { // don't shoot the sky! - if (z > li->frontsector->ceilingheight) + if (z > sector_ceilingheight(line_frontsector(li))) return false; // it's a sky hack wall - if (li->backsector && li->backsector->ceilingpic == skyflatnum) + if (line_backsector(li) && line_backsector(li)->ceilingpic == skyflatnum) return false; } @@ -1026,7 +1034,7 @@ boolean PTR_ShootTraverse (intercept_t* in) // check angles to see if the thing can be aimed at dist = FixedMul (attackrange, in->frac); - thingtopslope = FixedDiv (th->z+th->height - shootz , dist); + thingtopslope = FixedDiv (th->z+mobj_height(th) - shootz , dist); if (thingtopslope < aimslope) return true; // shot over the thing @@ -1078,9 +1086,9 @@ P_AimLineAttack angle >>= ANGLETOFINESHIFT; shootthing = t1; - x2 = t1->x + (distance>>FRACBITS)*finecosine[angle]; - y2 = t1->y + (distance>>FRACBITS)*finesine[angle]; - shootz = t1->z + (t1->height>>1) + 8*FRACUNIT; + x2 = t1->xy.x + (distance >> FRACBITS) * finecosine(angle); + y2 = t1->xy.y + (distance >> FRACBITS) * finesine(angle); + shootz = t1->z + (mobj_height(t1)>>1) + 8*FRACUNIT; // can't shoot outside view angles topslope = (SCREENHEIGHT/2)*FRACUNIT/(SCREENWIDTH/2); @@ -1089,10 +1097,10 @@ P_AimLineAttack attackrange = distance; linetarget = NULL; - P_PathTraverse ( t1->x, t1->y, - x2, y2, + P_PathTraverse (t1->xy.x, t1->xy.y, + x2, y2, PT_ADDLINES|PT_ADDTHINGS, - PTR_AimTraverse ); + PTR_AimTraverse ); if (linetarget) return aimslope; @@ -1120,16 +1128,16 @@ P_LineAttack angle >>= ANGLETOFINESHIFT; shootthing = t1; la_damage = damage; - x2 = t1->x + (distance>>FRACBITS)*finecosine[angle]; - y2 = t1->y + (distance>>FRACBITS)*finesine[angle]; - shootz = t1->z + (t1->height>>1) + 8*FRACUNIT; + x2 = t1->xy.x + (distance >> FRACBITS) * finecosine(angle); + y2 = t1->xy.y + (distance >> FRACBITS) * finesine(angle); + shootz = t1->z + (mobj_height(t1)>>1) + 8*FRACUNIT; attackrange = distance; aimslope = slope; - P_PathTraverse ( t1->x, t1->y, - x2, y2, + P_PathTraverse (t1->xy.x, t1->xy.y, + x2, y2, PT_ADDLINES|PT_ADDTHINGS, - PTR_ShootTraverse ); + PTR_ShootTraverse ); } @@ -1143,12 +1151,12 @@ boolean PTR_UseTraverse (intercept_t* in) { int side; - if (!in->d.line->special) + if (!line_special(in->d.line)) { P_LineOpening (in->d.line); if (openrange <= 0) { - S_StartSound (usething, sfx_noway); + S_StartObjSound (usething, sfx_noway); // can't use through a wall return false; @@ -1158,7 +1166,7 @@ boolean PTR_UseTraverse (intercept_t* in) } side = 0; - if (P_PointOnLineSide (usething->x, usething->y, in->d.line) == 1) + if (P_PointOnLineSide (usething->xy.x, usething->xy.y, in->d.line) == 1) side = 1; // return false; // don't use back side @@ -1184,12 +1192,12 @@ void P_UseLines (player_t* player) usething = player->mo; - angle = player->mo->angle >> ANGLETOFINESHIFT; + angle = mobj_full(player->mo)->angle >> ANGLETOFINESHIFT; - x1 = player->mo->x; - y1 = player->mo->y; - x2 = x1 + (USERANGE>>FRACBITS)*finecosine[angle]; - y2 = y1 + (USERANGE>>FRACBITS)*finesine[angle]; + x1 = player->mo->xy.x; + y1 = player->mo->xy.y; + x2 = x1 + (USERANGE>>FRACBITS)*finecosine(angle); + y2 = y1 + (USERANGE>>FRACBITS)*finesine(angle); P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse ); } @@ -1200,7 +1208,7 @@ void P_UseLines (player_t* player) // mobj_t* bombsource; mobj_t* bombspot; -int bombdamage; +isb_uint8_t bombdamage; // @@ -1223,11 +1231,11 @@ boolean PIT_RadiusAttack (mobj_t* thing) || thing->type == MT_SPIDER) return true; - dx = abs(thing->x - bombspot->x); - dy = abs(thing->y - bombspot->y); + dx = abs(thing->xy.x - bombspot->xy.x); + dy = abs(thing->xy.y - bombspot->xy.y); dist = dx>dy ? dx : dy; - dist = (dist - thing->radius) >> FRACBITS; + dist = (dist - mobj_radius(thing)) >> FRACBITS; if (dist < 0) dist = 0; @@ -1238,7 +1246,7 @@ boolean PIT_RadiusAttack (mobj_t* thing) if ( P_CheckSight (thing, bombspot) ) { // must be in direct path - P_DamageMobj (thing, bombspot, bombsource, bombdamage - dist); + P_DamageMobj (thing, bombspot, bombsource, ((fixed_t)bombdamage) - dist); } return true; @@ -1266,10 +1274,10 @@ P_RadiusAttack fixed_t dist; dist = (damage+MAXRADIUS)<y + dist - bmaporgy)>>MAPBLOCKSHIFT; - yl = (spot->y - dist - bmaporgy)>>MAPBLOCKSHIFT; - xh = (spot->x + dist - bmaporgx)>>MAPBLOCKSHIFT; - xl = (spot->x - dist - bmaporgx)>>MAPBLOCKSHIFT; + yh = (spot->xy.y + dist - bmaporgy) >> MAPBLOCKSHIFT; + yl = (spot->xy.y - dist - bmaporgy) >> MAPBLOCKSHIFT; + xh = (spot->xy.x + dist - bmaporgx) >> MAPBLOCKSHIFT; + xl = (spot->xy.x - dist - bmaporgx) >> MAPBLOCKSHIFT; bombspot = spot; bombsource = source; bombdamage = damage; @@ -1313,13 +1321,13 @@ boolean PIT_ChangeSector (mobj_t* thing) // crunch bodies to giblets - if (thing->health <= 0) + if (!mobj_is_static(thing) && mobj_full(thing)->health <= 0) { P_SetMobjState (thing, S_GIBS); thing->flags &= ~MF_SOLID; - thing->height = 0; - thing->radius = 0; + mobj_full(thing)->height = 0; + mobj_full(thing)->radius = 0; // keep checking return true; @@ -1347,12 +1355,12 @@ boolean PIT_ChangeSector (mobj_t* thing) P_DamageMobj(thing,NULL,NULL,10); // spray blood in a random direction - mo = P_SpawnMobj (thing->x, - thing->y, - thing->z + thing->height/2, MT_BLOOD); + mo = P_SpawnMobj (thing->xy.x, + thing->xy.y, + thing->z + mobj_height(thing)/2, MT_BLOOD); - mo->momx = P_SubRandom() << 12; - mo->momy = P_SubRandom() << 12; + mobj_full(mo)->momx = P_SubRandom() << 12; + mobj_full(mo)->momy = P_SubRandom() << 12; } // keep checking (crush other things) @@ -1408,6 +1416,7 @@ static void SpechitOverrun(line_t *ld) // Use the specified magic value when emulating spechit overruns. // +#if !NO_USE_ARGS p = M_CheckParmWithArgs("-spechit", 1); if (p > 0) @@ -1415,6 +1424,7 @@ static void SpechitOverrun(line_t *ld) M_StrToInt(myargv[p+1], (int *) &baseaddr); } else +#endif { baseaddr = DEFAULT_SPECHIT_MAGIC; } @@ -1439,7 +1449,7 @@ static void SpechitOverrun(line_t *ld) nofit = addr; break; default: - fprintf(stderr, "SpechitOverrun: Warning: unable to emulate" + stderr_print( "SpechitOverrun: Warning: unable to emulate" "an overrun where numspechit=%i\n", numspechit); break; diff --git a/src/doom/p_maputl.c b/src/doom/p_maputl.c index 6cc35a5a..5e722507 100644 --- a/src/doom/p_maputl.c +++ b/src/doom/p_maputl.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard // Copyright(C) 2005, 2006 Andrey Budko +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -68,26 +69,26 @@ P_PointOnLineSide fixed_t left; fixed_t right; - if (!line->dx) + if (line_is_vert(line)) { - if (x <= line->v1->x) - return line->dy > 0; + if (x <= vertex_x(line_v1(line))) + return line_dy_gt_0(line); - return line->dy < 0; + return line_dy_lt_0(line); } - if (!line->dy) + if (line_is_horiz(line)) { - if (y <= line->v1->y) - return line->dx < 0; + if (y <= vertex_y(line_v1(line))) + return line_dx_lt_0(line); - return line->dx > 0; + return line_dx_gt_0(line); } - dx = (x - line->v1->x); - dy = (y - line->v1->y); + dx = (x - vertex_x(line_v1(line))); + dy = (y - vertex_y(line_v1(line))); - left = FixedMul ( line->dy>>FRACBITS , dx ); - right = FixedMul ( dy , line->dx>>FRACBITS ); + left = FixedMul ( line_dy(line)>>FRACBITS , dx ); + right = FixedMul ( dy , line_dx(line)>>FRACBITS ); if (right < left) return 0; // front side @@ -108,23 +109,23 @@ P_BoxOnLineSide { int p1 = 0; int p2 = 0; - - switch (ld->slopetype) + + switch (line_slopetype(ld)) { case ST_HORIZONTAL: - p1 = tmbox[BOXTOP] > ld->v1->y; - p2 = tmbox[BOXBOTTOM] > ld->v1->y; - if (ld->dx < 0) + p1 = tmbox[BOXTOP] > vertex_y(line_v1(ld)); + p2 = tmbox[BOXBOTTOM] > vertex_y(line_v1(ld)); + if (line_dx_gt_0(ld)) { p1 ^= 1; p2 ^= 1; } break; - + case ST_VERTICAL: - p1 = tmbox[BOXRIGHT] < ld->v1->x; - p2 = tmbox[BOXLEFT] < ld->v1->x; - if (ld->dy < 0) + p1 = tmbox[BOXRIGHT] < vertex_x(line_v1(ld)); + p2 = tmbox[BOXLEFT] < vertex_x(line_v1(ld)); + if (line_dy_lt_0(ld)) { p1 ^= 1; p2 ^= 1; @@ -207,10 +208,10 @@ P_MakeDivline ( line_t* li, divline_t* dl ) { - dl->x = li->v1->x; - dl->y = li->v1->y; - dl->dx = li->dx; - dl->dy = li->dy; + dl->x = vertex_x(line_v1(li)); + dl->y = vertex_y(line_v1(li)); + dl->dx = line_dx(li); + dl->dy = line_dy(li); } @@ -297,30 +298,30 @@ void P_LineOpening (line_t* linedef) sector_t* front; sector_t* back; - if (linedef->sidenum[1] == -1) + if (line_onesided(linedef)) { // single sided line openrange = 0; return; } - front = linedef->frontsector; - back = linedef->backsector; + front = line_frontsector(linedef); + back = line_backsector(linedef); - if (front->ceilingheight < back->ceilingheight) - opentop = front->ceilingheight; + if (front->rawceilingheight < back->rawceilingheight) + opentop = sector_ceilingheight(front); else - opentop = back->ceilingheight; + opentop = sector_ceilingheight(back); - if (front->floorheight > back->floorheight) + if (front->rawfloorheight > back->rawfloorheight) { - openbottom = front->floorheight; - lowfloor = back->floorheight; + openbottom = sector_floorheight(front); + lowfloor = sector_floorheight(back); } else { - openbottom = back->floorheight; - lowfloor = front->floorheight; + openbottom = sector_floorheight(back); + lowfloor = sector_floorheight(front); } openrange = opentop - openbottom; @@ -346,37 +347,71 @@ void P_UnsetThingPosition (mobj_t* thing) if ( ! (thing->flags & MF_NOSECTOR) ) { +#if !SHRINK_MOBJ // inert things don't need to be in blockmap? // unlink from subsector - if (thing->snext) - thing->snext->sprev = thing->sprev; + if (thing->sp_snext) + mobj_snext(thing)->sp_sprev = thing->sp_sprev; - if (thing->sprev) - thing->sprev->snext = thing->snext; + if (thing->sp_sprev) + mobj_sprev(thing)->sp_snext = thing->sp_snext; else - thing->subsector->sector->thinglist = thing->snext; + mobj_sector(thing)->thinglist = thing->sp_snext; +#else + // removed back link, so start from beginning + shortptr_t *prev = &mobj_sector(thing)->thinglist; + //assert(*prev); + while (*prev) { + mobj_t *mobj = shortptr_to_mobj(*prev); + if (mobj == thing) { + *prev = mobj->sp_snext; + break; + } + prev = &mobj->sp_snext; + } +#endif } if ( ! (thing->flags & MF_NOBLOCKMAP) ) { +#if !SHRINK_MOBJ // inert things don't need to be in blockmap // unlink from block map - if (thing->bnext) - thing->bnext->bprev = thing->bprev; + if (thing->sp_bnext) + mobj_bnext(thing)->sp_bprev = thing->sp_bprev; - if (thing->bprev) - thing->bprev->bnext = thing->bnext; + if (thing->sp_bprev) + mobj_bprev(thing)->sp_bnext = thing->sp_bnext; else { - blockx = (thing->x - bmaporgx)>>MAPBLOCKSHIFT; - blocky = (thing->y - bmaporgy)>>MAPBLOCKSHIFT; + blockx = (thing->xy.x - bmaporgx) >> MAPBLOCKSHIFT; + blocky = (thing->xy.y - bmaporgy) >> MAPBLOCKSHIFT; if (blockx>=0 && blockx < bmapwidth && blocky>=0 && blocky bnext; + blocklinks[blocky*bmapwidth+blockx] = thing->sp_bnext; } } +#else + blockx = (thing->xy.x - bmaporgx) >> MAPBLOCKSHIFT; + blocky = (thing->xy.y - bmaporgy) >> MAPBLOCKSHIFT; + if (blockx>=0 && blockx < bmapwidth + && blocky>=0 && blocky sp_bnext; + break; + } + prev = &mobj->sp_bnext; + } + } else { + assert(!thing->sp_bnext); + } +#endif } } @@ -394,25 +429,33 @@ P_SetThingPosition (mobj_t* thing) sector_t* sec; int blockx; int blocky; - mobj_t** link; + shortptr_t /*mobj_t**/* link; // link into subsector - ss = R_PointInSubsector (thing->x,thing->y); + ss = R_PointInSubsector (thing->xy.x, thing->xy.y); +#if !SHRINK_MOBJ thing->subsector = ss; +#else + thing->sector_num = subsector_sector(ss) - sectors; +#endif if ( ! (thing->flags & MF_NOSECTOR) ) { // invisible things don't go into the sector links - sec = ss->sector; - - thing->sprev = NULL; - thing->snext = sec->thinglist; + sec = subsector_sector(ss); +#if !SHRINK_MOBJ + thing->sp_sprev = 0; +#endif + thing->sp_snext = sec->thinglist; + +#if !SHRINK_MOBJ if (sec->thinglist) - sec->thinglist->sprev = thing; + shortptr_to_mobj(sec->thinglist)->sp_sprev = mobj_to_shortptr(thing); +#endif - sec->thinglist = thing; + sec->thinglist = mobj_to_shortptr(thing); } @@ -420,8 +463,8 @@ P_SetThingPosition (mobj_t* thing) if ( ! (thing->flags & MF_NOBLOCKMAP) ) { // inert things don't need to be in blockmap - blockx = (thing->x - bmaporgx)>>MAPBLOCKSHIFT; - blocky = (thing->y - bmaporgy)>>MAPBLOCKSHIFT; + blockx = (thing->xy.x - bmaporgx) >> MAPBLOCKSHIFT; + blocky = (thing->xy.y - bmaporgy) >> MAPBLOCKSHIFT; if (blockx>=0 && blockx < bmapwidth @@ -429,22 +472,102 @@ P_SetThingPosition (mobj_t* thing) && blocky < bmapheight) { link = &blocklinks[blocky*bmapwidth+blockx]; - thing->bprev = NULL; - thing->bnext = *link; - if (*link) - (*link)->bprev = thing; - - *link = thing; +#if !SHRINK_MOBJ + thing->sp_bprev = 0; +#endif + thing->sp_bnext = *link; +#if !SHRINK_MOBJ + if (*link) + shortptr_to_mobj(*link)->sp_bprev = mobj_to_shortptr(thing); +#endif + *link = mobj_to_shortptr(thing); } else { // thing is off the map - thing->bnext = thing->bprev = NULL; + thing->sp_bnext = 0; +#if !SHRINK_MOBJ + thing->sp_bprev = 0; +#endif } } } - +void P_ResetThingPosition (mobj_t* thing, fixed_t x, fixed_t y) { + // arghh.. DoomII demo 3 relies on the oreder of the things in the blockmap... + // don't think this is probably a huge speed concern, so reverting always to unset/set which moves the item + // to the front. +#if true || !SHRINK_MOBJ + P_UnsetThingPosition(thing); + thing->xy.x = x; + thing->xy.y = y; + P_SetThingPosition(thing); +#else + if ( ! (thing->flags & MF_NOSECTOR) ) { + sector_t *oldsec = mobj_sector(thing); + subsector_t *newsub = R_PointInSubsector(x, y); +#if !SHRINK_MOBJ + thing->subsector = newsub; +#else + thing->sector_num = subsector_sector(newsub) - sectors; +#endif + sector_t *newsec = subsector_sector(newsub); + if (oldsec != newsec) { + // removed back link, so start from beginning + shortptr_t *prev = &oldsec->thinglist; + assert(*prev); + while (*prev) { + mobj_t *mobj = shortptr_to_mobj(*prev); + if (mobj == thing) { + *prev = mobj->sp_snext; + break; + } + prev = &mobj->sp_snext; + } + thing->sp_snext = newsec->thinglist; + newsec->thinglist = mobj_to_shortptr(thing); + } + } + if ( ! (thing->flags & MF_NOBLOCKMAP) ) + { + // inert things don't need to be in blockmap + fixed_t oldblockx = (thing->xy.x - bmaporgx) >> MAPBLOCKSHIFT; + fixed_t blockx = (x - bmaporgx) >> MAPBLOCKSHIFT; + fixed_t oldblocky = (thing->xy.y - bmaporgy) >> MAPBLOCKSHIFT; + fixed_t blocky = (y - bmaporgy) >> MAPBLOCKSHIFT; + if (oldblockx != blockx || oldblocky != blocky) { + if (oldblockx>=0 && oldblockx < bmapwidth + && oldblocky>=0 && oldblocky sp_bnext; + break; + } + prev = &mobj->sp_bnext; + } + } + if (blockx>=0 + && blockx < bmapwidth + && blocky>=0 + && blocky < bmapheight) + { + thing->sp_bnext = blocklinks[blocky*bmapwidth+blockx]; + blocklinks[blocky*bmapwidth+blockx] = mobj_to_shortptr(thing); + } + else + { + // thing is off the map + thing->sp_bnext = 0; + } + } + } + thing->xy.x = x; + thing->xy.y = y; +#endif +} // // BLOCK MAP ITERATORS @@ -455,6 +578,31 @@ P_SetThingPosition (mobj_t* thing) // +#if USE_WHD +// todo double in size for speed? +const uint8_t popcount8_table[128] = { + 0x10, 0x21, 0x21, 0x32, 0x21, 0x32, 0x32, 0x43, 0x21, 0x32, 0x32, 0x43, 0x32, 0x43, 0x43, 0x54, + 0x21, 0x32, 0x32, 0x43, 0x32, 0x43, 0x43, 0x54, 0x32, 0x43, 0x43, 0x54, 0x43, 0x54, 0x54, 0x65, + 0x21, 0x32, 0x32, 0x43, 0x32, 0x43, 0x43, 0x54, 0x32, 0x43, 0x43, 0x54, 0x43, 0x54, 0x54, 0x65, + 0x32, 0x43, 0x43, 0x54, 0x43, 0x54, 0x54, 0x65, 0x43, 0x54, 0x54, 0x65, 0x54, 0x65, 0x65, 0x76, + 0x21, 0x32, 0x32, 0x43, 0x32, 0x43, 0x43, 0x54, 0x32, 0x43, 0x43, 0x54, 0x43, 0x54, 0x54, 0x65, + 0x32, 0x43, 0x43, 0x54, 0x43, 0x54, 0x54, 0x65, 0x43, 0x54, 0x54, 0x65, 0x54, 0x65, 0x65, 0x76, + 0x32, 0x43, 0x43, 0x54, 0x43, 0x54, 0x54, 0x65, 0x43, 0x54, 0x54, 0x65, 0x54, 0x65, 0x65, 0x76, + 0x43, 0x54, 0x54, 0x65, 0x54, 0x65, 0x65, 0x76, 0x54, 0x65, 0x65, 0x76, 0x65, 0x76, 0x76, 0x87, +}; +// todo half size for space? +// note this savus us about 1.5K in metadata over a 32 entry table +const uint8_t bitcount8_table[256] = { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, +}; +#endif // // P_BlockLinesIterator // The validcount flags are used to avoid checking lines @@ -470,7 +618,7 @@ P_BlockLinesIterator boolean(*func)(line_t*) ) { int offset; - short* list; + rowad_const short* list; line_t* ld; if (x<0 @@ -480,7 +628,8 @@ P_BlockLinesIterator { return true; } - + +#if !USE_WHD offset = y*bmapwidth+x; offset = *(blockmap+offset); @@ -489,14 +638,45 @@ P_BlockLinesIterator { ld = &lines[*list]; - if (ld->validcount == validcount) + if (line_validcount_update_check(ld, validcount)) continue; // line has already been checked - ld->validcount = validcount; - if ( !func(ld) ) return false; } +#else + // handle 0 + if (!line_validcount_update_check(&lines[0], validcount) && !func(&lines[0])) return false; + + uint row_header_offset = y * (2 + (bmapwidth + 7) / 8); + uint bmx = x / 8; + + if (blockmap_whd[row_header_offset + 2 + bmx] & (1u << (x & 7))) { + uint data_offset = blockmap_whd[row_header_offset] | (blockmap_whd[row_header_offset + 1] << 8u); + uint cell_metadata_index = popcount8(blockmap_whd[row_header_offset + 2 + bmx] & ((1u << (x & 7)) - 1)); + for(int xx = 0; xx < bmx; xx++) { + cell_metadata_index += popcount8(blockmap_whd[row_header_offset + 2 + xx]); + } + uint cell_metadata = blockmap_whd[data_offset + cell_metadata_index * 2] | (blockmap_whd[data_offset + cell_metadata_index * 2 + 1] << 8u); + if ((cell_metadata & 0xf000) == 0xf000) { + const line_t *line = &lines[cell_metadata & 0xfffu]; + if (!line_validcount_update_check(line, validcount) && !func(line)) return false; + } else { + uint count = (cell_metadata >> 10u) + 1; + uint element_offset = data_offset + (cell_metadata & 0x3ff); + uint last = 0; + while (count--) { + uint b = blockmap_whd[element_offset++]; + if (b & 0x80) { + b = ((b & 0x7f) << 8) + blockmap_whd[element_offset++]; + } + last += b + 1; + const line_t *line = &lines[last]; + if (!line_validcount_update_check(line, validcount) && !func(line)) return false; + } + } + } +#endif return true; // everything was checked } @@ -510,6 +690,7 @@ P_BlockThingsIterator int y, boolean(*func)(mobj_t*) ) { + // todo graham we can make do with a smaller hashtable I'm sure mobj_t* mobj; if ( x<0 @@ -519,11 +700,11 @@ P_BlockThingsIterator { return true; } - - for (mobj = blocklinks[y*bmapwidth+x] ; + + for (mobj = shortptr_to_mobj(blocklinks[y*bmapwidth+x]) ; mobj ; - mobj = mobj->bnext) + mobj = mobj_bnext(mobj)) { if (!func( mobj ) ) return false; @@ -569,8 +750,8 @@ PIT_AddLineIntercepts (line_t* ld) || trace.dx < -FRACUNIT*16 || trace.dy < -FRACUNIT*16) { - s1 = P_PointOnDivlineSide (ld->v1->x, ld->v1->y, &trace); - s2 = P_PointOnDivlineSide (ld->v2->x, ld->v2->y, &trace); + s1 = P_PointOnDivlineSide (vertex_x(line_v1(ld)), vertex_y(line_v1(ld)), &trace); + s2 = P_PointOnDivlineSide (vertex_x(line_v2(ld)), vertex_y(line_v2(ld)), &trace); } else { @@ -591,16 +772,21 @@ PIT_AddLineIntercepts (line_t* ld) // try to early out the check if (earlyout && frac < FRACUNIT - && !ld->backsector) + && !line_backsector(ld)) { return false; // stop checking } - +#if NO_INTERCEPTS_OVERRUN + if (intercept_p - intercepts == MAXINTERCEPTS) return false; +#endif + intercept_p->frac = frac; intercept_p->isaline = true; intercept_p->d.line = ld; +#if !NO_INTERCEPTS_OVERRUN InterceptsOverrun(intercept_p - intercepts, intercept_p); +#endif intercept_p++; return true; // continue @@ -632,19 +818,19 @@ boolean PIT_AddThingIntercepts (mobj_t* thing) // check a corner to corner crossection for hit if (tracepositive) { - x1 = thing->x - thing->radius; - y1 = thing->y + thing->radius; + x1 = thing->xy.x - mobj_radius(thing); + y1 = thing->xy.y + mobj_radius(thing); - x2 = thing->x + thing->radius; - y2 = thing->y - thing->radius; + x2 = thing->xy.x + mobj_radius(thing); + y2 = thing->xy.y - mobj_radius(thing); } else { - x1 = thing->x - thing->radius; - y1 = thing->y - thing->radius; + x1 = thing->xy.x - mobj_radius(thing); + y1 = thing->xy.y - mobj_radius(thing); - x2 = thing->x + thing->radius; - y2 = thing->y + thing->radius; + x2 = thing->xy.x + mobj_radius(thing); + y2 = thing->xy.y + mobj_radius(thing); } s1 = P_PointOnDivlineSide (x1, y1, &trace); @@ -888,7 +1074,8 @@ P_PathTraverse int count; earlyout = (flags & PT_EARLYOUT) != 0; - + + line_check_reset(); validcount++; intercept_p = intercepts; diff --git a/src/doom/p_mobj.c b/src/doom/p_mobj.c index 285d9b94..3610d5b9 100644 --- a/src/doom/p_mobj.c +++ b/src/doom/p_mobj.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -35,8 +36,7 @@ void G_PlayerReborn (int player); -void P_SpawnMapThing (mapthing_t* mthing); - +void P_SpawnMapThing (spawnpoint_t spawnpoint); // // P_SetMobjState @@ -55,23 +55,34 @@ P_SetMobjState ( mobj_t* mobj, statenum_t state ) { - state_t* st; + should_be_const state_t* st; int cycle_counter = 0; do { if (state == S_NULL) { +#if !SHRINK_MOBJ mobj->state = (state_t *) S_NULL; +#else + mobj->state_num = state; +#endif P_RemoveMobj (mobj); return false; } st = &states[state]; +#if !SHRINK_MOBJ mobj->state = st; - mobj->tics = st->tics; +#else + mobj->state_num = state; +#endif + mobj->tics = state_tics(st); +#if !SHRINK_MOBJ + // ^ for shrunk, they are read from st-> mobj->sprite = st->sprite; mobj->frame = st->frame; +#endif // Modified handling. // Call action functions when the state is set @@ -95,7 +106,7 @@ P_SetMobjState // void P_ExplodeMissile (mobj_t* mo) { - mo->momx = mo->momy = mo->momz = 0; + mobj_full(mo)->momx = mobj_full(mo)->momy = mobj_full(mo)->momz = 0; P_SetMobjState (mo, mobjinfo[mo->type].deathstate); @@ -106,8 +117,8 @@ void P_ExplodeMissile (mobj_t* mo) mo->flags &= ~MF_MISSILE; - if (mo->info->deathsound) - S_StartSound (mo, mo->info->deathsound); + if (mobj_info(mo)->deathsound) + S_StartObjSound (mo, mobj_info(mo)->deathsound); } @@ -117,62 +128,63 @@ void P_ExplodeMissile (mobj_t* mo) #define STOPSPEED 0x1000 #define FRICTION 0xe800 -void P_XYMovement (mobj_t* mo) +void P_XYMovement (mobj_t* mo) { fixed_t ptryx; fixed_t ptryy; player_t* player; fixed_t xmove; fixed_t ymove; - - if (!mo->momx && !mo->momy) + + mobjfull_t *mof = mobj_full(mo); + if (!mof->momx && !mof->momy) { if (mo->flags & MF_SKULLFLY) { // the skull slammed into something mo->flags &= ~MF_SKULLFLY; - mo->momx = mo->momy = mo->momz = 0; + mof->momx = mof->momy = mof->momz = 0; - P_SetMobjState (mo, mo->info->spawnstate); + P_SetMobjState (mo, mobj_info(mo)->spawnstate); } return; } - player = mo->player; + player = mobj_player(mo); - if (mo->momx > MAXMOVE) - mo->momx = MAXMOVE; - else if (mo->momx < -MAXMOVE) - mo->momx = -MAXMOVE; + if (mof->momx > MAXMOVE) + mof->momx = MAXMOVE; + else if (mof->momx < -MAXMOVE) + mof->momx = -MAXMOVE; - if (mo->momy > MAXMOVE) - mo->momy = MAXMOVE; - else if (mo->momy < -MAXMOVE) - mo->momy = -MAXMOVE; + if (mof->momy > MAXMOVE) + mof->momy = MAXMOVE; + else if (mof->momy < -MAXMOVE) + mof->momy = -MAXMOVE; - xmove = mo->momx; - ymove = mo->momy; + xmove = mof->momx; + ymove = mof->momy; do { if (xmove > MAXMOVE/2 || ymove > MAXMOVE/2) { - ptryx = mo->x + xmove/2; - ptryy = mo->y + ymove/2; + ptryx = mo->xy.x + xmove / 2; + ptryy = mo->xy.y + ymove / 2; xmove >>= 1; ymove >>= 1; } else { - ptryx = mo->x + xmove; - ptryy = mo->y + ymove; + ptryx = mo->xy.x + xmove; + ptryy = mo->xy.y + ymove; xmove = ymove = 0; } if (!P_TryMove (mo, ptryx, ptryy)) { // blocked move - if (mo->player) + if (mof->sp_player) { // try to slide along it P_SlideMove (mo); } @@ -180,8 +192,8 @@ void P_XYMovement (mobj_t* mo) { // explode a missile if (ceilingline && - ceilingline->backsector && - ceilingline->backsector->ceilingpic == skyflatnum) + line_backsector(ceilingline) && + line_backsector(ceilingline)->ceilingpic == skyflatnum) { // Hack to prevent missiles exploding // against the sky. @@ -192,7 +204,7 @@ void P_XYMovement (mobj_t* mo) P_ExplodeMissile (mo); } else - mo->momx = mo->momy = 0; + mof->momx = mof->momy = 0; } } while (xmove || ymove); @@ -200,49 +212,49 @@ void P_XYMovement (mobj_t* mo) if (player && player->cheats & CF_NOMOMENTUM) { // debug option for no sliding at all - mo->momx = mo->momy = 0; + mof->momx = mof->momy = 0; return; } if (mo->flags & (MF_MISSILE | MF_SKULLFLY) ) return; // no friction for missiles ever - if (mo->z > mo->floorz) + if (mo->z > mof->floorz) return; // no friction when airborne if (mo->flags & MF_CORPSE) { // do not stop sliding // if halfway off a step with some momentum - if (mo->momx > FRACUNIT/4 - || mo->momx < -FRACUNIT/4 - || mo->momy > FRACUNIT/4 - || mo->momy < -FRACUNIT/4) + if (mof->momx > FRACUNIT/4 + || mof->momx < -FRACUNIT/4 + || mof->momy > FRACUNIT/4 + || mof->momy < -FRACUNIT/4) { - if (mo->floorz != mo->subsector->sector->floorheight) + if (mof->floorz != sector_floorheight(mobj_sector(mo))) return; } } - if (mo->momx > -STOPSPEED - && mo->momx < STOPSPEED - && mo->momy > -STOPSPEED - && mo->momy < STOPSPEED + if (mof->momx > -STOPSPEED + && mof->momx < STOPSPEED + && mof->momy > -STOPSPEED + && mof->momy < STOPSPEED && (!player || (player->cmd.forwardmove== 0 && player->cmd.sidemove == 0 ) ) ) { // if in a walking frame, stop moving - if ( player&&(unsigned)((player->mo->state - states)- S_PLAY_RUN1) < 4) + if ( player&&(unsigned)(mobj_state_num(player->mo)- S_PLAY_RUN1) < 4) P_SetMobjState (player->mo, S_PLAY); - mo->momx = 0; - mo->momy = 0; + mof->momx = 0; + mof->momy = 0; } else { - mo->momx = FixedMul (mo->momx, FRICTION); - mo->momy = FixedMul (mo->momy, FRICTION); + mof->momx = FixedMul (mof->momx, FRICTION); + mof->momy = FixedMul (mof->momy, FRICTION); } } @@ -253,30 +265,32 @@ void P_ZMovement (mobj_t* mo) { fixed_t dist; fixed_t delta; - - // check for smooth step up - if (mo->player && mo->z < mo->floorz) - { - mo->player->viewheight -= mo->floorz-mo->z; - mo->player->deltaviewheight - = (VIEWHEIGHT - mo->player->viewheight)>>3; + mobjfull_t *mof = mobj_full(mo); + + // check for smooth step up + if (mof->sp_player && mo->z < mof->floorz) + { + mobj_player(mo)->viewheight -= mof->floorz-mo->z; + + mobj_player(mo)->deltaviewheight + = (VIEWHEIGHT - mobj_player(mo)->viewheight)>>3; } // adjust height - mo->z += mo->momz; + mo->z += mof->momz; if ( mo->flags & MF_FLOAT - && mo->target) + && mof->sp_target) { // float down towards target if too close if ( !(mo->flags & MF_SKULLFLY) && !(mo->flags & MF_INFLOAT) ) { - dist = P_AproxDistance (mo->x - mo->target->x, - mo->y - mo->target->y); + dist = P_AproxDistance (mo->xy.x - mobj_target(mo)->xy.x, + mo->xy.y - mobj_target(mo)->xy.y); - delta =(mo->target->z + (mo->height>>1)) - mo->z; + delta =(mobj_target(mo)->z + (mof->height>>1)) - mo->z; if (delta<0 && dist < -(delta*3) ) mo->z -= FLOATSPEED; @@ -287,7 +301,7 @@ void P_ZMovement (mobj_t* mo) } // clip movement - if (mo->z <= mo->floorz) + if (mo->z <= mof->floorz) { // hit the floor @@ -319,24 +333,24 @@ void P_ZMovement (mobj_t* mo) if (correct_lost_soul_bounce && mo->flags & MF_SKULLFLY) { // the skull slammed into something - mo->momz = -mo->momz; + mof->momz = -mof->momz; } - if (mo->momz < 0) + if (mof->momz < 0) { - if (mo->player - && mo->momz < -GRAVITY*8) + if (mof->sp_player + && mof->momz < -GRAVITY*8) { // Squat down. // Decrease viewheight for a moment // after hitting the ground (hard), // and utter appropriate sound. - mo->player->deltaviewheight = mo->momz>>3; - S_StartSound (mo, sfx_oof); + mobj_player(mo)->deltaviewheight = mof->momz>>3; + S_StartObjSound (mo, sfx_oof); } - mo->momz = 0; + mof->momz = 0; } - mo->z = mo->floorz; + mo->z = mof->floorz; // cph 2001/05/26 - @@ -346,7 +360,7 @@ void P_ZMovement (mobj_t* mo) // if (!correct_lost_soul_bounce && mo->flags & MF_SKULLFLY) - mo->momz = -mo->momz; + mof->momz = -mof->momz; if ( (mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP) ) @@ -357,24 +371,24 @@ void P_ZMovement (mobj_t* mo) } else if (! (mo->flags & MF_NOGRAVITY) ) { - if (mo->momz == 0) - mo->momz = -GRAVITY*2; + if (mof->momz == 0) + mof->momz = -GRAVITY*2; else - mo->momz -= GRAVITY; + mof->momz -= GRAVITY; } - if (mo->z + mo->height > mo->ceilingz) + if (mo->z + mof->height > mof->ceilingz) { // hit the ceiling - if (mo->momz > 0) - mo->momz = 0; + if (mof->momz > 0) + mof->momz = 0; { - mo->z = mo->ceilingz - mo->height; + mo->z = mof->ceilingz - mof->height; } if (mo->flags & MF_SKULLFLY) { // the skull slammed into something - mo->momz = -mo->momz; + mof->momz = -mof->momz; } if ( (mo->flags & MF_MISSILE) @@ -399,10 +413,10 @@ P_NightmareRespawn (mobj_t* mobj) fixed_t z; subsector_t* ss; mobj_t* mo; - mapthing_t* mthing; + //mapthing_t* mthing; - x = mobj->spawnpoint.x << FRACBITS; - y = mobj->spawnpoint.y << FRACBITS; + x = mobj_spawnpoint(mobj).x << FRACBITS; + y = mobj_spawnpoint(mobj).y << FRACBITS; // somthing is occupying it's position? if (!P_CheckPosition (mobj, x, y) ) @@ -410,67 +424,112 @@ P_NightmareRespawn (mobj_t* mobj) // spawn a teleport fog at old spot // because of removal of the body? - mo = P_SpawnMobj (mobj->x, - mobj->y, - mobj->subsector->sector->floorheight , MT_TFOG); + mo = P_SpawnMobj (mobj->xy.x, + mobj->xy.y, + sector_floorheight(mobj_sector(mobj)) , MT_TFOG); // initiate teleport sound - S_StartSound (mo, sfx_telept); + S_StartObjSound (mo, sfx_telept); // spawn a teleport fog at the new spot ss = R_PointInSubsector (x,y); - mo = P_SpawnMobj (x, y, ss->sector->floorheight , MT_TFOG); + mo = P_SpawnMobj (x, y, sector_floorheight(subsector_sector(ss)) , MT_TFOG); - S_StartSound (mo, sfx_telept); + S_StartObjSound (mo, sfx_telept); // spawn the new monster - mthing = &mobj->spawnpoint; + const mapthing_t *mthing = &mobj_spawnpoint(mobj); // spawn it - if (mobj->info->flags & MF_SPAWNCEILING) + if (mobj_info(mobj)->flags & MF_SPAWNCEILING) z = ONCEILINGZ; else z = ONFLOORZ; // inherit attributes from deceased one mo = P_SpawnMobj (x,y,z, mobj->type); - mo->spawnpoint = mobj->spawnpoint; - mo->angle = ANG45 * (mthing->angle/45); + mo->spawnpoint = mobj->spawnpoint; + mobj_full(mo)->angle = ANG45 * (mthing->angle/45); if (mthing->options & MTF_AMBUSH) mo->flags |= MF_AMBUSH; - mo->reactiontime = 18; + mobj_reactiontime(mo) = 18; // remove the old monster, P_RemoveMobj (mobj); } +#if DEBUG_MOBJ +#define COUNTER_FROM 0 +static int counter; +#endif // // P_MobjThinker // void P_MobjThinker (mobj_t* mobj) { - // momentum movement - if (mobj->momx - || mobj->momy - || (mobj->flags&MF_SKULLFLY) ) - { - P_XYMovement (mobj); +#if DEBUG_MOBJ + counter++; +#endif + if (!mobj_is_static(mobj)) { +#if DEBUG_MOBJ + mobjfull_t *mobjf = mobj_full(mobj); + if (counter > COUNTER_FROM && mobj->type!=73 && mobj->type!=77) { + printf("%d %d: ", mobj->debug_id, mobj->think_count); + if (mobj->flags & MF_STATIC) { + printf("S"); + } + printf("MOBJ t %d dn %d fl %08x ti %d st %d %08x,%08x,%08x a=%08x r=%08x sec=%d\n", mobj->type, mobj_info(mobj)->doomednum, mobj->flags, mobj->tics, (int)mobj_state_num(mobj), mobj->xy.x, + mobj->xy.y, mobj->z, mobj_angle(mobj), mobj_radius(mobj), (int)(mobj_sector(mobj)-sectors)); + printf(" mom %08x, %08x, %08x md %d fl:cl %08x:%08x trg? %d trc? %d\n", mobjf->momx, mobjf->momy, mobjf->momz, + mobjf->movedir, mobjf->floorz, mobjf->ceilingz, mobjf->sp_target != 0, mobjf->sp_tracer != 0); + if (mobj->flags & MF_STATIC) { + printf(" rt %d th %d\n", mobjf->reactiontime, mobjf->threshold); + } else { + printf(" rt %d ll %d th %d\n", mobjf->reactiontime, mobjf->lastlook, mobjf->threshold); + } + if (mobjf->sp_player) { + printf(" player health=%d damage %d\n", mobj_player(mobj)->health, mobj_player(mobj)->damagecount); + } + } +#endif + // momentum movement + if (mobj_full(mobj)->momx + || mobj_full(mobj)->momy + || (mobj->flags & MF_SKULLFLY)) { + P_XYMovement(mobj); - // FIXME: decent NOP/NULL/Nil function pointer please. - if (mobj->thinker.function.acv == (actionf_v) (-1)) - return; // mobj was removed - } - if ( (mobj->z != mobj->floorz) - || mobj->momz ) - { - P_ZMovement (mobj); - - // FIXME: decent NOP/NULL/Nil function pointer please. - if (mobj->thinker.function.acv == (actionf_v) (-1)) - return; // mobj was removed + // FIXME: decent NOP/NULL/Nil function pointer please. + if (mobj->thinker.function == ThinkF_REMOVED) + return; // mobj was removed + } + if ((mobj->z != mobj_full(mobj)->floorz) + || mobj_full(mobj)->momz) { + P_ZMovement(mobj); + + // FIXME: decent NOP/NULL/Nil function pointer please. + if (mobj->thinker.function == ThinkF_REMOVED) + return; // mobj was removed + } + } else { +#if DEBUG_MOBJ + if (counter > COUNTER_FROM && mobj->type!=73 && mobj->type!=77) { + printf("%d %d: ", mobj->debug_id, mobj->think_count); + printf("SMOBJ t %d dn %d fl %08x ti %d st %d %08x,%08x,%08x a=%08x r=%08x sec=%d\n", mobj->type, mobj_info(mobj)->doomednum, mobj->flags, mobj->tics, (int)mobj_state_num(mobj), mobj->xy.x, + mobj->xy.y, mobj->z, mobj_angle(mobj), mobj_radius(mobj), (int)(mobj_sector(mobj)-sectors)); + printf(" mom %08x, %08x, %08x md %d fl:cl %08x:%08x trg? %d trc? %d\n", 0, 0, 0, + 0, mobj_floorz(mobj), mobj_ceilingz(mobj), 0, 0); + printf(" rt %d th %d\n", mobj_info(mobj)->reactiontime, 0); + } +#endif +#ifndef NDEBUG + // todo graham not sure if this can happen... see if i can identify one; maybe just set it to floor? ah hah this happens in E3M1 + if (mobj->z != mobj_floorz(mobj)) { + //I_Error("OOPS\n"); + } +#endif } @@ -482,7 +541,7 @@ void P_MobjThinker (mobj_t* mobj) // you can cycle through multiple states in a tic if (!mobj->tics) - if (!P_SetMobjState (mobj, mobj->state->nextstate) ) + if (!P_SetMobjState (mobj, mobj_state(mobj)->nextstate) ) return; // freed itself } else @@ -494,9 +553,9 @@ void P_MobjThinker (mobj_t* mobj) if (!respawnmonsters) return; - mobj->movecount++; + mobj_full(mobj)->movecount++; - if (mobj->movecount < 12*TICRATE) + if (mobj_full(mobj)->movecount < 12*TICRATE) return; if ( leveltime&31 ) @@ -510,7 +569,6 @@ void P_MobjThinker (mobj_t* mobj) } - // // P_SpawnMobj // @@ -521,51 +579,69 @@ P_SpawnMobj fixed_t z, mobjtype_t type ) { - mobj_t* mobj; - state_t* st; - mobjinfo_t* info; - - mobj = Z_Malloc (sizeof(*mobj), PU_LEVEL, NULL); - memset (mobj, 0, sizeof (*mobj)); - info = &mobjinfo[type]; - - mobj->type = type; - mobj->info = info; - mobj->x = x; - mobj->y = y; - mobj->radius = info->radius; - mobj->height = info->height; - mobj->flags = info->flags; - mobj->health = info->spawnhealth; + should_be_const state_t* st; + const mobjinfo_t* info; + + info = &mobjinfo[type]; + int size = mobj_flags_is_static(info->flags) ? sizeof(mobj_t) : sizeof(mobjfull_t); + mobj_t *mobj = Z_ThinkMalloc (size, PU_LEVEL, 0); + + mobj->type = type; +#if DEBUG_MOBJ + static int debug_id; + mobj->debug_id = ++debug_id; +#endif +#if !SHRINK_MOBJ + mobj_info(mobj) = info; +#endif + mobj->xy.x = x; + mobj->xy.y = y; + + mobj->flags = info->flags; + if (!mobj_is_static(mobj)) { + mobj_full(mobj)->radius = info->radius; + mobj_full(mobj)->health = info->spawnhealth; + if (gameskill != sk_nightmare) + mobj_full(mobj)->reactiontime = info->reactiontime; + mobj_full(mobj)->height = info->height; + mobj_full(mobj)->lastlook = P_Random () % MAXPLAYERS; + } else { + P_Random(); + } - if (gameskill != sk_nightmare) - mobj->reactiontime = info->reactiontime; - - mobj->lastlook = P_Random () % MAXPLAYERS; // do not set the state with P_SetMobjState, // because action routines can not be called yet st = &states[info->spawnstate]; +#if !SHRINK_MOBJ mobj->state = st; - mobj->tics = st->tics; +#else + mobj->state_num = info->spawnstate; +#endif + mobj->tics = state_tics(st); +#if !SHRINK_MOBJ + // ^ for shrunk, they are read from st-> mobj->sprite = st->sprite; mobj->frame = st->frame; +#endif // set subsector and/or block links P_SetThingPosition (mobj); - - mobj->floorz = mobj->subsector->sector->floorheight; - mobj->ceilingz = mobj->subsector->sector->ceilingheight; + + if (!mobj_is_static(mobj)) { + mobj_full(mobj)->floorz = sector_floorheight(mobj_sector(mobj)); + mobj_full(mobj)->ceilingz = sector_ceilingheight(mobj_sector(mobj)); + } if (z == ONFLOORZ) - mobj->z = mobj->floorz; + mobj->z = mobj_floorz(mobj); else if (z == ONCEILINGZ) - mobj->z = mobj->ceilingz - mobj->info->height; + mobj->z = mobj_ceilingz(mobj) - mobj_info(mobj)->height; else mobj->z = z; - mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; - + mobj->thinker.function = ThinkF_P_MobjThinker; + P_AddThinker (&mobj->thinker); return mobj; @@ -575,10 +651,10 @@ P_SpawnMobj // // P_RemoveMobj // -mapthing_t itemrespawnque[ITEMQUESIZE]; -int itemrespawntime[ITEMQUESIZE]; -int iquehead; -int iquetail; +spawnpoint_t itemrespawnque[ITEMQUESIZE]; +isb_int16_t itemrespawntime[ITEMQUESIZE]; +isb_uint8_t iquehead; +isb_uint8_t iquetail; void P_RemoveMobj (mobj_t* mobj) @@ -589,7 +665,7 @@ void P_RemoveMobj (mobj_t* mobj) && (mobj->type != MT_INS)) { itemrespawnque[iquehead] = mobj->spawnpoint; - itemrespawntime[iquehead] = leveltime; + itemrespawntime[iquehead] = (isb_int16_t)leveltime; iquehead = (iquehead+1)&(ITEMQUESIZE-1); // lose one off the end? @@ -601,7 +677,7 @@ void P_RemoveMobj (mobj_t* mobj) P_UnsetThingPosition (mobj); // stop any playing sound - S_StopSound (mobj); + S_StopObjSound (mobj); // free block P_RemoveThinker ((thinker_t*)mobj); @@ -621,7 +697,7 @@ void P_RespawnSpecials (void) subsector_t* ss; mobj_t* mo; - mapthing_t* mthing; + const mapthing_t* mthing; int i; @@ -634,18 +710,22 @@ void P_RespawnSpecials (void) return; // wait at least 30 seconds - if (leveltime - itemrespawntime[iquetail] < 30*TICRATE) - return; + if ((isb_int16_t)(leveltime - itemrespawntime[iquetail]) < 30*TICRATE) + return; +#if !SHRINK_MOBJ mthing = &itemrespawnque[iquetail]; +#else + mthing = &mapthings[itemrespawnque[iquetail]]; +#endif x = mthing->x << FRACBITS; y = mthing->y << FRACBITS; // spawn a teleport fog at the new spot ss = R_PointInSubsector (x,y); - mo = P_SpawnMobj (x, y, ss->sector->floorheight , MT_IFOG); - S_StartSound (mo, sfx_itmbk); + mo = P_SpawnMobj (x, y, sector_floorheight(subsector_sector(ss)) , MT_IFOG); + S_StartObjSound (mo, sfx_itmbk); // find which type to spawn for (i=0 ; i< NUMMOBJTYPES ; i++) @@ -668,8 +748,8 @@ void P_RespawnSpecials (void) z = ONFLOORZ; mo = P_SpawnMobj (x,y,z, i); - mo->spawnpoint = *mthing; - mo->angle = ANG45 * (mthing->angle/45); + mo->spawnpoint = itemrespawnque[iquetail]; + mobj_full(mo)->angle = ANG45 * (mthing->angle/45); // pull it from the que iquetail = (iquetail+1)&(ITEMQUESIZE-1); @@ -684,7 +764,7 @@ void P_RespawnSpecials (void) // Most of the player structure stays unchanged // between levels. // -void P_SpawnPlayer (mapthing_t* mthing) +void P_SpawnPlayer (const mapthing_t* mthing) { player_t* p; fixed_t x; @@ -717,10 +797,10 @@ void P_SpawnPlayer (mapthing_t* mthing) // set color translations for player sprites if (mthing->type > 1) mobj->flags |= (mthing->type-1)<angle = ANG45 * (mthing->angle/45); - mobj->player = p; - mobj->health = p->health; + + mobj_full(mobj)->angle = ANG45 * (mthing->angle/45); + mobj_full(mobj)->sp_player = ptr_to_shortptr(p); + mobj_full(mobj)->health = p->health; p->mo = mobj; p->playerstate = PST_LIVE; @@ -755,7 +835,7 @@ void P_SpawnPlayer (mapthing_t* mthing) // The fields of the mapthing should // already be in host byte order. // -void P_SpawnMapThing (mapthing_t* mthing) +void P_SpawnMapThing (spawnpoint_t spawnpoint) { int i; int bit; @@ -763,7 +843,8 @@ void P_SpawnMapThing (mapthing_t* mthing) fixed_t x; fixed_t y; fixed_t z; - + + const mapthing_t *mthing = &spawnpoint_mapthing(spawnpoint); // count deathmatch start positions if (mthing->type == 11) { @@ -841,7 +922,7 @@ void P_SpawnMapThing (mapthing_t* mthing) z = ONFLOORZ; mobj = P_SpawnMobj (x,y,z, i); - mobj->spawnpoint = *mthing; + mobj->spawnpoint = spawnpoint; if (mobj->tics > 0) mobj->tics = 1 + (P_Random () % mobj->tics); @@ -849,10 +930,11 @@ void P_SpawnMapThing (mapthing_t* mthing) totalkills++; if (mobj->flags & MF_COUNTITEM) totalitems++; - - mobj->angle = ANG45 * (mthing->angle/45); + + if (!mobj_is_static(mobj)) // todo XXX temp? + mobj_full(mobj)->angle = ANG45 * (mthing->angle/45); if (mthing->options & MTF_AMBUSH) - mobj->flags |= MF_AMBUSH; + mobj->flags |= MF_AMBUSH; } @@ -878,7 +960,7 @@ P_SpawnPuff z += (P_SubRandom() << 10); th = P_SpawnMobj (x,y,z, MT_PUFF); - th->momz = FRACUNIT; + mobj_full(th)->momz = FRACUNIT; th->tics -= P_Random()&3; if (th->tics < 1) @@ -905,7 +987,7 @@ P_SpawnBlood z += (P_SubRandom() << 10); th = P_SpawnMobj (x,y,z, MT_BLOOD); - th->momz = FRACUNIT*2; + mobj_full(th)->momz = FRACUNIT*2; th->tics -= P_Random()&3; if (th->tics < 1) @@ -932,11 +1014,11 @@ void P_CheckMissileSpawn (mobj_t* th) // move a little forward so an angle can // be computed if it immediately explodes - th->x += (th->momx>>1); - th->y += (th->momy>>1); - th->z += (th->momz>>1); + th->xy.x += (mobj_full(th)->momx >> 1); + th->xy.y += (mobj_full(th)->momy >> 1); + th->z += (mobj_full(th)->momz>>1); - if (!P_TryMove (th, th->x, th->y)) + if (!P_TryMove (th, th->xy.x, th->xy.y)) P_ExplodeMissile (th); } @@ -952,8 +1034,8 @@ mobj_t *P_SubstNullMobj(mobj_t *mobj) { static mobj_t dummy_mobj; - dummy_mobj.x = 0; - dummy_mobj.y = 0; + dummy_mobj.xy.x = 0; + dummy_mobj.xy.y = 0; dummy_mobj.z = 0; dummy_mobj.flags = 0; @@ -976,32 +1058,33 @@ P_SpawnMissile angle_t an; int dist; - th = P_SpawnMobj (source->x, - source->y, + th = P_SpawnMobj (source->xy.x, + source->xy.y, source->z + 4*8*FRACUNIT, type); - if (th->info->seesound) - S_StartSound (th, th->info->seesound); + if (mobj_info(th)->seesound) + S_StartObjSound (th, mobj_info(th)->seesound); - th->target = source; // where it came from - an = R_PointToAngle2 (source->x, source->y, dest->x, dest->y); + mobj_full(th)->sp_target = mobj_to_shortptr(source); // where it came from + an = R_PointToAngle2 (source->xy.x, source->xy.y, dest->xy.x, dest->xy.y); // fuzzy player if (dest->flags & MF_SHADOW) an += P_SubRandom() << 20; - th->angle = an; + mobj_full(th)->angle = an; an >>= ANGLETOFINESHIFT; - th->momx = FixedMul (th->info->speed, finecosine[an]); - th->momy = FixedMul (th->info->speed, finesine[an]); + int speed = mobj_speed(th); + mobj_full(th)->momx = FixedMul (speed, finecosine(an)); + mobj_full(th)->momy = FixedMul (speed, finesine(an)); - dist = P_AproxDistance (dest->x - source->x, dest->y - source->y); - dist = dist / th->info->speed; + dist = P_AproxDistance (dest->xy.x - source->xy.x, dest->xy.y - source->xy.y); + dist = dist / speed; if (dist < 1) dist = 1; - th->momz = (dest->z - source->z) / dist; + mobj_full(th)->momz = (dest->z - source->z) / dist; P_CheckMissileSpawn (th); return th; @@ -1026,7 +1109,7 @@ P_SpawnPlayerMissile fixed_t slope; // see which target is to be aimed at - an = source->angle; + an = mobj_full(source)->angle; slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); if (!linetarget) @@ -1042,28 +1125,40 @@ P_SpawnPlayerMissile if (!linetarget) { - an = source->angle; + an = mobj_full(source)->angle; slope = 0; } } - x = source->x; - y = source->y; + x = source->xy.x; + y = source->xy.y; z = source->z + 4*8*FRACUNIT; th = P_SpawnMobj (x,y,z, type); - if (th->info->seesound) - S_StartSound (th, th->info->seesound); + if (mobj_info(th)->seesound) + S_StartObjSound (th, mobj_info(th)->seesound); - th->target = source; - th->angle = an; - th->momx = FixedMul( th->info->speed, - finecosine[an>>ANGLETOFINESHIFT]); - th->momy = FixedMul( th->info->speed, - finesine[an>>ANGLETOFINESHIFT]); - th->momz = FixedMul( th->info->speed, slope); + mobj_full(th)->sp_target = mobj_to_shortptr(source); + mobj_full(th)->angle = an; + int speed = mobj_speed(th); + mobj_full(th)->momx = FixedMul( speed, + finecosine(an>>ANGLETOFINESHIFT)); + mobj_full(th)->momy = FixedMul( speed, + finesine(an>>ANGLETOFINESHIFT)); + mobj_full(th)->momz = FixedMul( speed, slope); P_CheckMissileSpawn (th); } +#if DOOM_CONST +int mobj_speed(mobj_t *mo) { + const mobjinfo_t *inf = mobj_info(mo); + int speed = inf->speed; + if (nightmare_speeds && (mo->type == MT_BRUISERSHOT || mo->type == MT_HEADSHOT || mo->type == MT_TROOPSHOT)) { + speed = 20 * FRACUNIT; + } + return speed; +} +#endif + diff --git a/src/doom/p_mobj.h b/src/doom/p_mobj.h index 90ed764b..d1fee0e6 100644 --- a/src/doom/p_mobj.h +++ b/src/doom/p_mobj.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -35,12 +36,7 @@ // tied to animation frames. // Needs precompiled tables/data structures. #include "info.h" - - - - - - +#include "r_defs.h" // // NOTES: mobj_t // @@ -192,10 +188,40 @@ typedef enum // use a translation table for player colormaps MF_TRANSLATION = 0xc000000, // Hmm ???. - MF_TRANSSHIFT = 26 + MF_TRANSSHIFT = 26, + MF_DECORATION = 0x10000000, + MF_STATIC = (MF_DECORATION | MF_SPECIAL) // Objects with these flags go in the static mobj zone, and occupy less RAM } mobjflag_t; +#if !SHRINK_MOBJ +typedef mapthing_t spawnpoint_t; +#define spawnpoint_mapthing(s) (s) +#define mobj_info(o) ((o)->info) +#define mobj_subsector(o) ((o)->subsector) +#define mobj_sector(o) subsector_sector(mobj_subsector(o)) +#define mobj_state(o) ((o)->state) +#define mobj_state_num(o) (mobj_state(o) - states) +#define mobj_sprite(o) ((o)->sprite) +#define mobj_frame(o) ((o)->frame) +#else + +extern const mapthing_t *mapthings; +extern uint16_t nummapthings; +typedef uint16_t spawnpoint_t; +#define spawnpoint_mapthing(s) mapthings[s] +#define mobj_info(o) (&mobjinfo[(o)->type]) +#define mobj_sector(o) (§ors[(o)->sector_num]) +#define mobj_state_num(o) ((o)->state_num) +#define mobj_state(o) (&states[mobj_state_num(o)]) +#define mobj_sprite(o) (mobj_state(o)->sprite) +#define mobj_frame(o) (mobj_state(o)->frame) +#endif +#define mobj_spawnpoint(o) spawnpoint_mapthing((o)->spawnpoint) +#define mobj_snext(o) ((mobj_t *)shortptr_to_ptr((o)->sp_snext)) +#define mobj_sprev(o) ((mobj_t *)shortptr_to_ptr((o)->sp_sprev)) +#define mobj_bnext(o) ((mobj_t *)shortptr_to_ptr((o)->sp_bnext)) +#define mobj_bprev(o) ((mobj_t *)shortptr_to_ptr((o)->sp_bprev)) // Map Object definition. typedef struct mobj_s @@ -203,33 +229,111 @@ typedef struct mobj_s // List: thinker links. thinker_t thinker; +#if DEBUG_MOBJ + int debug_id; + int think_count; +#endif +#if !SHRINK_MOBJ + int tics; // state tic counter +#else + int8_t tics; +#endif + +#if !SHRINK_MOBJ + spritenum_t sprite; // used to find patch_t and flip value + int frame; // might be ORed with FF_FULLBRIGHT +#endif + + mobjtype_t type; +#if !SHRINK_MOBJ + const mobjinfo_t* info; // &mobjinfo[mobj->type] +#endif + +#if !SHRINK_MOBJ + should_be_const state_t* state; +#else + statenum_t state_num; // probably only 10 bits +#endif + + // ===== 16 bit fields (when SHRUNK) todo collapse furher once we know everything====== +#if !SHRINK_MOBJ + subsector_t* subsector; +#else + uint16_t sector_num; +#endif + + spawnpoint_t spawnpoint; + // Info for drawing: position. - fixed_t x; - fixed_t y; + xy_positioned_t xy; fixed_t z; - // More list: links in sector (if needed) - struct mobj_s* snext; - struct mobj_s* sprev; + int flags; - //More drawing info: to determine current sprite. - angle_t angle; // orientation - spritenum_t sprite; // used to find patch_t and flip value - int frame; // might be ORed with FF_FULLBRIGHT + // More list: links in sector (if needed) + shortptr_t/*struct mobj_s*/ sp_snext; +#if !SHRINK_MOBJ + shortptr_t/*struct mobj_s*/ sp_sprev; +#endif // Interaction info, by BLOCKMAP. // Links in blocks (if needed). - struct mobj_s* bnext; - struct mobj_s* bprev; - - struct subsector_s* subsector; + shortptr_t/*struct mobj_s*/ sp_bnext; +#if !SHRINK_MOBJ + shortptr_t/*struct mobj_s*/ sp_bprev; +#endif + +} mobj_t; + +// ===== NON-STATIC object fields ===== + +typedef struct mobjfull_s +{ + mobj_t core; + + // Movement direction, movement generation (zig-zagging). + int8_t movedir; // 0-7 + +#if !SHRINK_MOBJ + // Reaction time: if non 0, don't attack yet. + // Used by player to freeze a bit after teleporting. + int reactiontime; +#else + uint8_t reactiontime; +#endif + + // Player number last looked for. +#if !SHRINK_MOBJ + int lastlook; +#else + int8_t lastlook; +#endif + +#if !SHRINK_MOBJ + // If >0, the target will be chased + // no matter what (even if shot) + int threshold; +#else + int8_t threshold; +#endif + +#if !SHRINK_MOBJ + int health; +#else + int16_t health; +#endif + + int movecount; // when 0, select a new dir + + //More drawing info: to determine current sprite. + angle_t angle; // orientation // The closest interval over all contacted Sectors. fixed_t floorz; fixed_t ceilingz; // For movement checking. - fixed_t radius; + fixed_t radius; // mostly readonly (set to 0 at some point) fixed_t height; // Momentums, used to update position. @@ -238,46 +342,87 @@ typedef struct mobj_s fixed_t momz; // If == validcount, already checked. - int validcount; - - mobjtype_t type; - mobjinfo_t* info; // &mobjinfo[mobj->type] - - int tics; // state tic counter - state_t* state; - int flags; - int health; - - // Movement direction, movement generation (zig-zagging). - int movedir; // 0-7 - int movecount; // when 0, select a new dir + //int validcount; // Thing being chased/attacked (or NULL), // also the originator for missiles. - struct mobj_s* target; - - // Reaction time: if non 0, don't attack yet. - // Used by player to freeze a bit after teleporting. - int reactiontime; - - // If >0, the target will be chased - // no matter what (even if shot) - int threshold; + shortptr_t /*struct mobj_s*/ sp_target; // Additional info record for player avatars only. // Only valid if type == MT_PLAYER - struct player_s* player; - - // Player number last looked for. - int lastlook; - - // For nightmare respawn. - mapthing_t spawnpoint; + shortptr_t /*struct player_s*/ sp_player; // Thing being chased/attacked for tracers. - struct mobj_s* tracer; - -} mobj_t; + shortptr_t /*struct mobj_s*/ sp_tracer; + +} mobjfull_t; +#define mobj_target(o) ((mobj_t *)shortptr_to_ptr(mobj_full(o)->sp_target)) +#define mobj_player(o) ((struct player_s *)shortptr_to_ptr(mobj_full(o)->sp_player)) +struct player_s; +static inline shortptr_t player_to_shortptr(struct player_s *p) { + return ptr_to_shortptr(p); +} +#define mobj_tracer(o) ((mobj_t *)shortptr_to_ptr(mobj_full(o)->sp_tracer)) +static inline shortptr_t mobj_to_shortptr(mobj_t *o) { + return ptr_to_shortptr(o); +} +#define shortptr_to_mobj(s) ((mobj_t *)shortptr_to_ptr(s)) + +#if !DOOM_SMALL +#define mobj_flags_is_static(i) 0 +#else +#define mobj_flags_is_static(i) ((i) & MF_STATIC) +#endif +#define mobj_is_static(o) (mobj_flags_is_static((o)->flags)) + +static inline mobjfull_t *mobj_full(mobj_t *mobj) { + assert(!mobj_is_static(mobj)); + return (mobjfull_t *)mobj; +} + +static inline int mobj_radius(mobj_t *mobj) { + return mobj_is_static(mobj) ? mobj_info(mobj)->radius : mobj_full(mobj)->radius; +} + +static inline int mobj_height(mobj_t *mobj) { + return mobj_is_static(mobj) ? mobj_info(mobj)->height : mobj_full(mobj)->height; +} + +static inline int mobj_is_player(mobj_t *mobj) { + return !mobj_is_static(mobj) && mobj_full(mobj)->sp_player; +} + +#if !DOOM_CONST +#define mobj_speed(o) (mobj_info(o)->speed) +#else +int mobj_speed(mobj_t *mo); +#endif + +// todo we assume static objects are not crossing sectors; is this true? +#define mobj_floorz(mobj) (mobj_is_static(mobj) ? sector_floorheight(mobj_sector(mobj)) : mobj_full(mobj)->floorz) +#define mobj_ceilingz(mobj) (mobj_is_static(mobj) ? sector_ceilingheight(mobj_sector(mobj)) : mobj_full(mobj)->ceilingz) +//#define mobj_radius(o) mobj_radius(o) + +// there is a reaction time in the info struct, however I've not seen it used on a static object +#define mobj_reactiontime(o) mobj_full(o)->reactiontime + +static inline angle_t mobj_angle(mobj_t *mobj) { + if (mobj_is_static(mobj)) { + return ANG45 * (spawnpoint_mapthing(mobj->spawnpoint).angle / 45); + } else { + return mobj_full(mobj)->angle; + } +} +#if PICO_BUILD && !DEBUG_MOBJ +#include "pico.h" +#include +#if PICO_ON_DEVICE +static_assert(sizeof(mobj_t)==0x20, ""); +static_assert(sizeof(mobjfull_t)==0x54, ""); +#else +static_assert(sizeof(mobj_t)==0x38, ""); +#endif +#endif diff --git a/src/doom/p_plats.c b/src/doom/p_plats.c index 9e773d55..ae0c3cd2 100644 --- a/src/doom/p_plats.c +++ b/src/doom/p_plats.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -35,7 +36,7 @@ #include "sounds.h" -plat_t* activeplats[MAXPLATS]; +shortptr_t /*plat_t* */ activeplats[MAXPLATS]; @@ -109,7 +110,7 @@ void T_PlatRaise(plat_t* plat) case waiting: if (!--plat->count) { - if (plat->sector->floorheight == plat->low) + if (sector_floorheight(plat->sector) == plat->low) plat->status = up; else plat->status = down; @@ -144,7 +145,7 @@ EV_DoPlat switch(type) { case perpetualRaise: - P_ActivateInStasis(line->tag); + P_ActivateInStasis(line_tag(line)); break; default: @@ -165,17 +166,17 @@ EV_DoPlat plat->type = type; plat->sector = sec; - plat->sector->specialdata = plat; - plat->thinker.function.acp1 = (actionf_p1) T_PlatRaise; + plat->sector->specialdata = ptr_to_shortptr(plat); + plat->thinker.function = ThinkF_T_PlatRaise; plat->crush = false; - plat->tag = line->tag; + plat->tag = line_tag(line); switch(type) { case raiseToNearestAndChange: plat->speed = PLATSPEED/2; - sec->floorpic = sides[line->sidenum[0]].sector->floorpic; - plat->high = P_FindNextHighestFloor(sec,sec->floorheight); + sec->floorpic = side_sector(sidenum_to_side(line_sidenum(line, 0)))->floorpic; + plat->high = P_FindNextHighestFloor(sec,sector_floorheight(sec)); plat->wait = 0; plat->status = up; // NO MORE DAMAGE, IF APPLICABLE @@ -186,8 +187,8 @@ EV_DoPlat case raiseAndChange: plat->speed = PLATSPEED/2; - sec->floorpic = sides[line->sidenum[0]].sector->floorpic; - plat->high = sec->floorheight + amount*FRACUNIT; + sec->floorpic = side_sector(sidenum_to_side(line_sidenum(line, 0)))->floorpic; + plat->high = sector_floorheight(sec) + amount*FRACUNIT; plat->wait = 0; plat->status = up; @@ -198,10 +199,10 @@ EV_DoPlat plat->speed = PLATSPEED * 4; plat->low = P_FindLowestFloorSurrounding(sec); - if (plat->low > sec->floorheight) - plat->low = sec->floorheight; + if (plat->low > sector_floorheight(sec)) + plat->low = sector_floorheight(sec); - plat->high = sec->floorheight; + plat->high = sector_floorheight(sec); plat->wait = TICRATE*PLATWAIT; plat->status = down; S_StartSound(&sec->soundorg,sfx_pstart); @@ -211,10 +212,10 @@ EV_DoPlat plat->speed = PLATSPEED * 8; plat->low = P_FindLowestFloorSurrounding(sec); - if (plat->low > sec->floorheight) - plat->low = sec->floorheight; + if (plat->low > sector_floorheight(sec)) + plat->low = sector_floorheight(sec); - plat->high = sec->floorheight; + plat->high = sector_floorheight(sec); plat->wait = TICRATE*PLATWAIT; plat->status = down; S_StartSound(&sec->soundorg,sfx_pstart); @@ -224,13 +225,13 @@ EV_DoPlat plat->speed = PLATSPEED; plat->low = P_FindLowestFloorSurrounding(sec); - if (plat->low > sec->floorheight) - plat->low = sec->floorheight; + if (plat->low > sector_floorheight(sec)) + plat->low = sector_floorheight(sec); plat->high = P_FindHighestFloorSurrounding(sec); - if (plat->high < sec->floorheight) - plat->high = sec->floorheight; + if (plat->high < sector_floorheight(sec)) + plat->high = sector_floorheight(sec); plat->wait = TICRATE*PLATWAIT; plat->status = P_Random()&1; @@ -249,30 +250,32 @@ void P_ActivateInStasis(int tag) { int i; - for (i = 0;i < MAXPLATS;i++) - if (activeplats[i] - && (activeplats[i])->tag == tag - && (activeplats[i])->status == in_stasis) - { - (activeplats[i])->status = (activeplats[i])->oldstatus; - (activeplats[i])->thinker.function.acp1 - = (actionf_p1) T_PlatRaise; + for (i = 0;i < MAXPLATS;i++) { + if (activeplats[i]) { + plat_t *p = shortptr_to_plat(activeplats[i]); + if (p->tag == tag && p->status == in_stasis) + { + p->status = p->oldstatus; + p->thinker.function = ThinkF_T_PlatRaise; + } } + } } void EV_StopPlat(line_t* line) { int j; - for (j = 0;j < MAXPLATS;j++) - if (activeplats[j] - && ((activeplats[j])->status != in_stasis) - && ((activeplats[j])->tag == line->tag)) - { - (activeplats[j])->oldstatus = (activeplats[j])->status; - (activeplats[j])->status = in_stasis; - (activeplats[j])->thinker.function.acv = (actionf_v)NULL; + for (j = 0;j < MAXPLATS;j++) { + if (activeplats[j]) { + plat_t *p = shortptr_to_plat(activeplats[j]); + if (p->status != in_stasis && (p->tag == line_tag(line))) { + p->oldstatus = p->status; + p->status = in_stasis; + p->thinker.function = ThinkF_NULL; + } } + } } void P_AddActivePlat(plat_t* plat) @@ -280,9 +283,9 @@ void P_AddActivePlat(plat_t* plat) int i; for (i = 0;i < MAXPLATS;i++) - if (activeplats[i] == NULL) + if (!activeplats[i]) { - activeplats[i] = plat; + activeplats[i] = plat_to_shortptr(plat); return; } I_Error ("P_AddActivePlat: no more plats!"); @@ -291,14 +294,15 @@ void P_AddActivePlat(plat_t* plat) void P_RemoveActivePlat(plat_t* plat) { int i; - for (i = 0;i < MAXPLATS;i++) - if (plat == activeplats[i]) - { - (activeplats[i])->sector->specialdata = NULL; - P_RemoveThinker(&(activeplats[i])->thinker); - activeplats[i] = NULL; + for (i = 0;i < MAXPLATS;i++) { + if (plat == shortptr_to_plat(activeplats[i])) + { + plat->sector->specialdata = 0; + P_RemoveThinker(&plat->thinker); + activeplats[i] = 0; - return; - } + return; + } + } I_Error ("P_RemoveActivePlat: can't find plat!"); } diff --git a/src/doom/p_pspr.c b/src/doom/p_pspr.c index 097cace2..c72ec065 100644 --- a/src/doom/p_pspr.c +++ b/src/doom/p_pspr.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -53,7 +54,7 @@ P_SetPsprite statenum_t stnum ) { pspdef_t* psp; - state_t* state; + should_be_const state_t* state; psp = &player->psprites[position]; @@ -65,17 +66,19 @@ P_SetPsprite psp->state = NULL; break; } - + state = &states[stnum]; psp->state = state; - psp->tics = state->tics; // could be 0 + psp->tics = state_tics(state); // could be 0 +#if !NO_USE_STATE_MISC if (state->misc1) { // coordinate set psp->sx = state->misc1 << FRACBITS; psp->sy = state->misc2 << FRACBITS; } +#endif // Call action routine. // Modified handling. @@ -112,10 +115,10 @@ void P_CalcSwing (player_t* player) swing = player->bob; angle = (FINEANGLES/70*leveltime)&FINEMASK; - swingx = FixedMul ( swing, finesine[angle]); + swingx = FixedMul ( swing, finesine(angle)); angle = (FINEANGLES/70*leveltime+FINEANGLES/2)&FINEMASK; - swingy = -FixedMul ( swingx, finesine[angle]); + swingy = -FixedMul ( swingx, finesine(angle)); } @@ -134,7 +137,7 @@ void P_BringUpWeapon (player_t* player) player->pendingweapon = player->readyweapon; if (player->pendingweapon == wp_chainsaw) - S_StartSound (player->mo, sfx_sawup); + S_StartObjSound (player->mo, sfx_sawup); newstate = weaponinfo[player->pendingweapon].upstate; @@ -278,8 +281,8 @@ A_WeaponReady int angle; // get out of attack state - if (player->mo->state == &states[S_PLAY_ATK1] - || player->mo->state == &states[S_PLAY_ATK2] ) + if (mobj_state_num(player->mo) == S_PLAY_ATK1 + || mobj_state_num(player->mo) == S_PLAY_ATK2) { P_SetMobjState (player->mo, S_PLAY); } @@ -287,7 +290,7 @@ A_WeaponReady if (player->readyweapon == wp_chainsaw && psp->state == &states[S_SAW]) { - S_StartSound (player->mo, sfx_sawidl); + S_StartObjSound (player->mo, sfx_sawidl); } // check for change @@ -319,9 +322,9 @@ A_WeaponReady // bob the weapon based on movement speed angle = (128*leveltime)&FINEMASK; - psp->sx = FRACUNIT + FixedMul (player->bob, finecosine[angle]); + psp->sx = FRACUNIT + FixedMul (player->bob, finecosine(angle)); angle &= FINEANGLES/2-1; - psp->sy = WEAPONTOP + FixedMul (player->bob, finesine[angle]); + psp->sy = WEAPONTOP + FixedMul (player->bob, finesine(angle)); } @@ -469,7 +472,7 @@ A_Punch if (player->powers[pw_strength]) damage *= 10; - angle = player->mo->angle; + angle = mobj_full(player->mo)->angle; angle += P_SubRandom() << 18; slope = P_AimLineAttack (player->mo, angle, MELEERANGE); P_LineAttack (player->mo, angle, MELEERANGE, slope, damage); @@ -477,11 +480,11 @@ A_Punch // turn to face target if (linetarget) { - S_StartSound (player->mo, sfx_punch); - player->mo->angle = R_PointToAngle2 (player->mo->x, - player->mo->y, - linetarget->x, - linetarget->y); + S_StartObjSound (player->mo, sfx_punch); + mobj_full(player->mo)->angle = R_PointToAngle2 (player->mo->xy.x, + player->mo->xy.y, + linetarget->xy.x, + linetarget->xy.y); } } @@ -499,7 +502,7 @@ A_Saw int slope; damage = 2*(P_Random ()%10+1); - angle = player->mo->angle; + angle = mobj_full(player->mo)->angle; angle += P_SubRandom() << 18; // use meleerange + 1 se the puff doesn't skip the flash @@ -508,27 +511,27 @@ A_Saw if (!linetarget) { - S_StartSound (player->mo, sfx_sawful); + S_StartObjSound (player->mo, sfx_sawful); return; } - S_StartSound (player->mo, sfx_sawhit); + S_StartObjSound (player->mo, sfx_sawhit); // turn to face target - angle = R_PointToAngle2 (player->mo->x, player->mo->y, - linetarget->x, linetarget->y); - if (angle - player->mo->angle > ANG180) + angle = R_PointToAngle2 (player->mo->xy.x, player->mo->xy.y, + linetarget->xy.x, linetarget->xy.y); + if (angle - mobj_full(player->mo)->angle > ANG180) { - if ((signed int) (angle - player->mo->angle) < -ANG90/20) - player->mo->angle = angle + ANG90/21; + if ((signed int) (angle - mobj_full(player->mo)->angle) < -ANG90/20) + mobj_full(player->mo)->angle = angle + ANG90/21; else - player->mo->angle -= ANG90/20; + mobj_full(player->mo)->angle -= ANG90/20; } else { - if (angle - player->mo->angle > ANG90/20) - player->mo->angle = angle - ANG90/21; + if (angle - mobj_full(player->mo)->angle > ANG90/20) + mobj_full(player->mo)->angle = angle - ANG90/21; else - player->mo->angle += ANG90/20; + mobj_full(player->mo)->angle += ANG90/20; } player->mo->flags |= MF_JUSTATTACKED; } @@ -612,7 +615,7 @@ void P_BulletSlope (mobj_t* mo) angle_t an; // see which target is to be aimed at - an = mo->angle; + an = mobj_full(mo)->angle; bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); if (!linetarget) @@ -640,7 +643,7 @@ P_GunShot int damage; damage = 5*(P_Random ()%3+1); - angle = mo->angle; + angle = mobj_full(mo)->angle; if (!accurate) angle += P_SubRandom() << 18; @@ -657,7 +660,7 @@ A_FirePistol ( player_t* player, pspdef_t* psp ) { - S_StartSound (player->mo, sfx_pistol); + S_StartObjSound (player->mo, sfx_pistol); P_SetMobjState (player->mo, S_PLAY_ATK2); DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 1); @@ -681,7 +684,7 @@ A_FireShotgun { int i; - S_StartSound (player->mo, sfx_shotgn); + S_StartObjSound (player->mo, sfx_shotgn); P_SetMobjState (player->mo, S_PLAY_ATK2); DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 1); @@ -711,7 +714,7 @@ A_FireShotgun2 int damage; - S_StartSound (player->mo, sfx_dshtgn); + S_StartObjSound (player->mo, sfx_dshtgn); P_SetMobjState (player->mo, S_PLAY_ATK2); DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 2); @@ -725,7 +728,7 @@ A_FireShotgun2 for (i=0 ; i<20 ; i++) { damage = 5*(P_Random ()%3+1); - angle = player->mo->angle; + angle = mobj_full(player->mo)->angle; angle += P_SubRandom() << ANGLETOFINESHIFT; P_LineAttack (player->mo, angle, @@ -743,7 +746,7 @@ A_FireCGun ( player_t* player, pspdef_t* psp ) { - S_StartSound (player->mo, sfx_pistol); + S_StartObjSound (player->mo, sfx_pistol); if (!player->ammo[weaponinfo[player->readyweapon].ammo]) return; @@ -797,25 +800,26 @@ void A_BFGSpray (mobj_t* mo) // offset angles from its attack angle for (i=0 ; i<40 ; i++) { - an = mo->angle - ANG90/2 + ANG90/40*i; + an = mobj_full(mo)->angle - ANG90/2 + ANG90/40*i; + mobj_t *target = mobj_target(mo); // mo->target is the originator (player) // of the missile - P_AimLineAttack (mo->target, an, 16*64*FRACUNIT); + P_AimLineAttack (target, an, 16*64*FRACUNIT); if (!linetarget) continue; - P_SpawnMobj (linetarget->x, - linetarget->y, - linetarget->z + (linetarget->height>>2), + P_SpawnMobj (linetarget->xy.x, + linetarget->xy.y, + linetarget->z + (mobj_height(linetarget)>>2), MT_EXTRABFG); damage = 0; for (j=0;j<15;j++) damage += (P_Random()&7) + 1; - P_DamageMobj (linetarget, mo->target,mo->target, damage); + P_DamageMobj (linetarget, target, target, damage); } } @@ -828,7 +832,7 @@ A_BFGsound ( player_t* player, pspdef_t* psp ) { - S_StartSound (player->mo, sfx_bfg); + S_StartObjSound (player->mo, sfx_bfg); } @@ -861,7 +865,7 @@ void P_MovePsprites (player_t* player) { int i; pspdef_t* psp; - state_t* state; + should_be_const state_t* state; psp = &player->psprites[0]; for (i=0 ; iframe #define FF_FRAMEMASK 0x7fff +#else +#define FF_FULLBRIGHT 0x80 // flag in thing->frame +#define FF_FRAMEMASK 0x7f +#endif @@ -61,7 +67,7 @@ typedef enum typedef struct { - state_t* state; // a NULL state means not active + should_be_const state_t* state; // a NULL state means not active int tics; fixed_t sx; fixed_t sy; diff --git a/src/doom/p_saveg.c b/src/doom/p_saveg.c index 1192bff9..97d8a6ca 100644 --- a/src/doom/p_saveg.c +++ b/src/doom/p_saveg.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -16,7 +17,7 @@ // Archiving: SaveGame I/O. // - +#define xprintf(x, ...) ((void)0) #include #include @@ -33,7 +34,16 @@ #include "m_misc.h" #include "r_state.h" +#if !NO_FILE_ACCESS FILE *save_stream; +#endif +#if LOAD_COMPRESSED +th_bit_input *sg_bi; +#endif +#if SAVE_COMPRESSED +th_bit_output *sg_bo; +#endif +#if !NO_USE_SAVE int savegamelength; boolean savegame_error; @@ -41,6 +51,7 @@ boolean savegame_error; // the file has been successfully saved, it will be renamed to the // real file. +#if !NO_FILE_ACCESS char *P_TempSaveGameFile(void) { static char *filename = NULL; @@ -52,11 +63,14 @@ char *P_TempSaveGameFile(void) return filename; } +#endif +#endif // Get the filename of the save game file to use for the specified slot. char *P_SaveGameFile(int slot) { +#if !NO_FILE_ACCESS static char *filename = NULL; static size_t filename_size = 0; char basename[32]; @@ -71,59 +85,108 @@ char *P_SaveGameFile(int slot) M_snprintf(filename, filename_size, "%s%s", savegamedir, basename); return filename; +#else + static char fn[2]; + fn[0] = slot + '0'; + return fn; +#endif } // Endian-safe integer read/write functions static byte saveg_read8(void) { +#if !LOAD_COMPRESSED byte result = -1; +#if !NO_FILE_ACCESS if (fread(&result, 1, 1, save_stream) < 1) { if (!savegame_error) { - fprintf(stderr, "saveg_read8: Unexpected end of file while " + stderr_print( "saveg_read8: Unexpected end of file while " "reading save game\n"); savegame_error = true; } } +#else + panic_unsupported(); +#endif return result; +#else + return th_read_bits(sg_bi, 8); +#endif } +#if LOAD_COMPRESSED +static uint saveg_read_bits(uint n) { + return th_read_bits(sg_bi, n); +} + +static uint saveg_read_bit() { + return th_read_bits(sg_bi, 1); +} +#endif +#if SAVE_COMPRESSED +static void saveg_write_bits(uint bits, uint n) { + th_write_bits(sg_bo, bits, n); +} + +static void saveg_write_bit(uint bit) { + th_write_bits(sg_bo, bit, 1); +} +#endif + static void saveg_write8(byte value) { +#if !SAVE_COMPRESSED +#if !NO_FILE_ACCESS if (fwrite(&value, 1, 1, save_stream) < 1) { if (!savegame_error) { - fprintf(stderr, "saveg_write8: Error while writing save game\n"); + stderr_print( "saveg_write8: Error while writing save game\n"); savegame_error = true; } } +#elif !NO_USE_SAVE + savegame_error = true; +#endif +#else + saveg_write_bits(value, 8); +#endif } static short saveg_read16(void) { +#if !LOAD_COMPRESSED int result; result = saveg_read8(); result |= saveg_read8() << 8; return result; +#else + return th_read_bits(sg_bi, 16); +#endif } static void saveg_write16(short value) { +#if !SAVE_COMPRESSED saveg_write8(value & 0xff); saveg_write8((value >> 8) & 0xff); +#else + th_write_bits(sg_bo, (uint16_t)value, 16); +#endif } static int saveg_read32(void) { +#if !LOAD_COMPRESSED int result; result = saveg_read8(); @@ -132,25 +195,105 @@ static int saveg_read32(void) result |= saveg_read8() << 24; return result; +#else + return th_read32(sg_bi); +#endif } -static void saveg_write32(int value) +static void saveg_write32(uint32_t value) { +#if !SAVE_COMPRESSED saveg_write8(value & 0xff); saveg_write8((value >> 8) & 0xff); saveg_write8((value >> 16) & 0xff); saveg_write8((value >> 24) & 0xff); +#else + th_write32(sg_bo, value); +#endif } +static boolean saveg_read_boolean() +{ +#if !SAVE_COMPRESSED + return saveg_read32(); +#else + return saveg_read_bit(); +#endif +} + +static void saveg_write_boolean(boolean value) +{ +#if !SAVE_COMPRESSED + saveg_write32(value); +#else + saveg_write_bit(value); +#endif +} + +#if LOAD_COMPRESSED +static int saveg_read_low_zeros() { + uint zb = saveg_read_bits(2); + uint rc = 0; + for(uint i=zb;i<4;i++) { + rc |= saveg_read8() << (8 * i); + } + return (int)rc; +} + +typedef int (*saveg_reader)(); + +static uint saveg_read_maybe(uint32_t static_val, saveg_reader reader, const char *desc) { + if (saveg_read_bit()) { + uint rc = reader(); + xprintf(" %s explicit %08x\n", desc, (int)rc); + return rc; + } else { + xprintf(" %s default %08x\n", desc, (int)static_val); + return static_val; + } +} +#endif +#if SAVE_COMPRESSED +static void saveg_write_low_zeros(uint32_t value) { + uint zb = value ? __builtin_ctz(value) / 8 : 3; + saveg_write_bits(zb,2); + int i; + for(i=0;i>= 8; + } + for(;i<4;i++) { + saveg_write8(value & 0xff); + value >>= 8; + } +} + +typedef void (*saveg_writer)(uint32_t); + +static void saveg_write_maybe(uint32_t val, uint32_t static_val, saveg_writer writer, const char *desc) { + if (val != static_val) { + xprintf(" %s explicit %08x %08x\n", desc, (int)val, (int)static_val); + saveg_write_bit(1); + writer(val); + } else { + xprintf(" %s default %08x\n", desc, (int)static_val); + saveg_write_bit(0); + } +} +#endif // Pad to 4-byte boundaries static void saveg_read_pad(void) { +#if !LOAD_COMPRESSED unsigned long pos; int padding; int i; +#if !NO_FILE_ACCESS pos = ftell(save_stream); +#else + panic_unsupported(); +#endif padding = (4 - (pos & 3)) & 3; @@ -158,15 +301,21 @@ static void saveg_read_pad(void) { saveg_read8(); } +#endif } static void saveg_write_pad(void) { +#if !SAVE_COMPRESSED unsigned long pos; int padding; int i; +#if !NO_FILE_ACCESS pos = ftell(save_stream); +#else + panic_unsupported(); +#endif padding = (4 - (pos & 3)) & 3; @@ -174,6 +323,7 @@ static void saveg_write_pad(void) { saveg_write8(0); } +#endif } @@ -181,19 +331,32 @@ static void saveg_write_pad(void) static void *saveg_readp(void) { +#if !DOOM_TINY return (void *) (intptr_t) saveg_read32(); +#else + return NULL; +#endif } static void saveg_writep(const void *p) { +#if !DOOM_TINY saveg_write32((intptr_t) p); +#endif } // Enum values are 32-bit integers. -#define saveg_read_enum saveg_read32 -#define saveg_write_enum saveg_write32 - +#if !LOAD_COMPRESSED +#define saveg_read_enum() saveg_read32() +#else +#define saveg_read_enum() saveg_read8(); +#endif +#if !SAVE_COMPRESSED +#define saveg_write_enum(x) saveg_write32(x) +#else +#define saveg_write_enum(x) saveg_write8(x) +#endif // // Structure read/write functions // @@ -220,7 +383,7 @@ static void saveg_read_mapthing_t(mapthing_t *str) str->options = saveg_read16(); } -static void saveg_write_mapthing_t(mapthing_t *str) +static void saveg_write_mapthing_t(const mapthing_t *str) { // short x; saveg_write16(str->x); @@ -238,30 +401,32 @@ static void saveg_write_mapthing_t(mapthing_t *str) saveg_write16(str->options); } -// -// actionf_t -// - -static void saveg_read_actionf_t(actionf_t *str) -{ - // actionf_p1 acp1; - str->acp1 = saveg_readp(); -} - -static void saveg_write_actionf_t(actionf_t *str) -{ - // actionf_p1 acp1; - saveg_writep(str->acp1); -} - // // think_t // // This is just an actionf_t. // -#define saveg_read_think_t saveg_read_actionf_t -#define saveg_write_think_t saveg_write_actionf_t +void saveg_read_think_t(think_t *think) { +#if !LOAD_COMPRESSED + uint32_t p = saveg_read32(); +#else + uint32_t p = saveg_read_bit(); +#endif + if (!p) *think = ThinkF_NULL; + else *think = ThinkF_INVALID; // should be overwritten +} + +void saveg_write_think_t(think_t think) { +#if !SAVE_COMPRESSED + if (think != ThinkF_NULL) + saveg_write32(1); + else + saveg_write32(0); +#else + saveg_write_bit(think != ThinkF_NULL); +#endif +} // // thinker_t @@ -269,265 +434,590 @@ static void saveg_write_actionf_t(actionf_t *str) static void saveg_read_thinker_t(thinker_t *str) { - // struct thinker_s* prev; - str->prev = saveg_readp(); +#if !LOAD_COMPRESSED + saveg_readp(); // prev pointer (doesn't exist any more) // struct thinker_s* next; - str->next = saveg_readp(); + /*str->sp_next = */saveg_readp(); +#endif // think_t function; saveg_read_think_t(&str->function); } static void saveg_write_thinker_t(thinker_t *str) { +#if !SAVE_COMPRESSED // struct thinker_s* prev; - saveg_writep(str->prev); + saveg_writep((void *)1); // doesn't exist any more // struct thinker_s* next; - saveg_writep(str->next); + saveg_writep(shortptr_to_ptr(str->sp_next)); // garbage +#endif // think_t function; - saveg_write_think_t(&str->function); + saveg_write_think_t(str->function); } // // mobj_t // -static void saveg_read_mobj_t(mobj_t *str) +static mobj_t *saveg_read_mobj_t() { int pl; + mobj_t *str; +#if LOAD_COMPRESSED + int type = saveg_read8(); + int size = mobj_flags_is_static(mobjinfo[type].flags) ? sizeof(mobj_t) : sizeof(mobjfull_t); + str = Z_ThinkMalloc (size, PU_LEVEL, 0); + // these are needed for decoding early + str->type = type; + str->spawnpoint = saveg_read16(); + str->flags = mobjinfo[str->type].flags; // we need MF_DECORATION, but only change modified bits later anyway +#else + str = Z_ThinkMalloc (sizeof(mobjfull_t), PU_LEVEL, 0); +#endif + xprintf(" type %d spawn %d flags %08x\n", str->type, str->spawnpoint, str->flags); + // todo we need to merge in m_obj's + // thinker_t thinker; saveg_read_thinker_t(&str->thinker); +#if !LOAD_COMPRESSED // fixed_t x; - str->x = saveg_read32(); + str->xy.x = saveg_read32(); // fixed_t y; - str->y = saveg_read32(); + str->xy.y = saveg_read32(); +#else + if (saveg_read_bit()) { + str->xy.x = saveg_read32(); + str->xy.y = saveg_read32(); + xprintf(" xy explicit %08x, %08x\n", str->xy.x, str->xy.y); + } else { + str->xy.x = spawnpoint_mapthing(str->spawnpoint).x << FRACBITS; + str->xy.y = spawnpoint_mapthing(str->spawnpoint).y << FRACBITS; + xprintf(" xy spawn %08x, %08x\n", str->xy.x, str->xy.y); + } + + // we need the subsector to decode some other stuff + subsector_t *ss = R_PointInSubsector (str->xy.x, str->xy.y); +#if !SHRINK_MOBJ + str->subsector = ss; +#else + str->sector_num = subsector_sector(ss) - sectors; + xprintf(" sector %d\n", str->sector_num); +#endif +#endif // fixed_t z; +#if !LOAD_COMPRESSED str->z = saveg_read32(); +#else + if (!mobj_is_static(str)) { + mobj_full(str)->height = saveg_read_maybe(mobj_info(str)->height, + saveg_read32, "height"); + mobj_full(str)->floorz = saveg_read_maybe(sector_floorheight(mobj_sector(str)), saveg_read_low_zeros, "floorz"); + mobj_full(str)->ceilingz = saveg_read_maybe(sector_ceilingheight(mobj_sector(str)), saveg_read_low_zeros, "ceilingz"); + } + if (!saveg_read_bit()) { + // 0 floor + str->z = mobj_floorz(str); + xprintf(" on floor z %08x\n", str->z); + } else if (!saveg_read_bit()) { + // 10 ceiling + str->z = mobj_ceilingz(str) - mobj_height(str); + xprintf(" on ceiling z %08x\n", str->z); + } else { + // 11 arbitrary + str->z = saveg_read32(); + xprintf(" explicit z %08x\n", str->z); + } +#endif + +#if !LOAD_COMPRESSED // struct mobj_s* snext; - str->snext = saveg_readp(); + /* str->sp_snext = */ saveg_readp(); // struct mobj_s* sprev; - str->sprev = saveg_readp(); + /* str->sp_sprev = */ saveg_readp(); +#endif +#if !LOAD_COMPRESSED // angle_t angle; - str->angle = saveg_read32(); + fixed_t angle = saveg_read32(); +#else + if (!mobj_is_static(str)) { + mobj_full(str)->angle = saveg_read_maybe(spawnpoint_mapthing(str->spawnpoint).angle << FRACBITS, + saveg_read_low_zeros, "angle"); + } +#endif +#if !LOAD_COMPRESSED +#if !SHRINK_MOBJ // spritenum_t sprite; str->sprite = saveg_read_enum(); // int frame; str->frame = saveg_read32(); +#if FF_FULLBRIGHT != 0x8000 + if (str->frame & 0x8000) str->frame = (str->frame & ~0x8000) | FF_FULLBRIGHT; +#endif +#else + saveg_read_enum(); saveg_read32(); +#endif +#endif +#if !LOAD_COMPRESSED // struct mobj_s* bnext; - str->bnext = saveg_readp(); + /* str->sp_bnext = */ saveg_readp(); // struct mobj_s* bprev; - str->bprev = saveg_readp(); + /* str->sp_bprev = */ saveg_readp(); // struct subsector_s* subsector; - str->subsector = saveg_readp(); + /* str->subsector = */saveg_readp(); +#endif - // fixed_t floorz; - str->floorz = saveg_read32(); - - // fixed_t ceilingz; - str->ceilingz = saveg_read32(); - - // fixed_t radius; - str->radius = saveg_read32(); - - // fixed_t height; - str->height = saveg_read32(); - - // fixed_t momx; - str->momx = saveg_read32(); - - // fixed_t momy; - str->momy = saveg_read32(); - - // fixed_t momz; - str->momz = saveg_read32(); +#if !LOAD_COMPRESSED + fixed_t floorz = saveg_read32(); + fixed_t ceilingz = saveg_read32(); + fixed_t radius = saveg_read32(); + fixed_t height = saveg_read32(); + fixed_t momx = saveg_read32(); + fixed_t momy = saveg_read32(); + fixed_t momz = saveg_read32(); +#else + if (!mobj_is_static(str)) { + mobj_full(str)->radius = saveg_read_maybe(mobj_info(str)->radius, saveg_read32, "radius"); + mobj_full(str)->momx = saveg_read_maybe(0, saveg_read32, "momx"); + mobj_full(str)->momy = saveg_read_maybe(0, saveg_read32, "momy"); + mobj_full(str)->momz = saveg_read_maybe(0, saveg_read32, "momz"); + } +#endif +#if !LOAD_COMPRESSED // int validcount; - str->validcount = saveg_read32(); + /* str->validcount = */saveg_read32(); +#endif // mobjtype_t type; +#if !LOAD_COMPRESSED str->type = saveg_read_enum(); +#endif + +#if !LOAD_COMPRESSED + // note static check depends on type so we need to know that first + if (!mobj_is_static(str)) { + mobj_full(str)->angle = angle; + mobj_full(str)->floorz = floorz; + mobj_full(str)->ceilingz = ceilingz; + mobj_full(str)->radius = radius; + mobj_full(str)->height = height; + mobj_full(str)->momx = momx; + mobj_full(str)->momy = momy; + mobj_full(str)->momz = momz; + } +#endif // mobjinfo_t* info; +#if !LOAD_COMPRESSED +#if !SHRINK_MOBJ str->info = saveg_readp(); +#else + saveg_readp(); +#endif +#endif +#if !LOAD_COMPRESSED // int tics; str->tics = saveg_read32(); +#else + str->tics = saveg_read_maybe(-1, (saveg_reader)saveg_read8, "tics"); +#endif +#if !LOAD_COMPRESSED // state_t* state; +#if !SHRINK_MOBJ str->state = &states[saveg_read32()]; +#else + str->state_num = saveg_read32(); +#endif +#else + // todo fewer bits? + str->state_num = saveg_read_maybe(mobj_info(str)->spawnstate, (saveg_reader)saveg_read16, "state"); +#endif // int flags; +#if !LOAD_COMPRESSED str->flags = saveg_read32(); +#else + // note we already set these to mobj_info()->flags above hence str->flags as default + str->flags = saveg_read_maybe(str->flags, saveg_read32, "flags"); +#endif + if (mobjinfo[str->type].flags & MF_DECORATION) { + str->flags |= MF_DECORATION; + } - // int health; - str->health = saveg_read32(); - - // int movedir; - str->movedir = saveg_read32(); - - // int movecount; - str->movecount = saveg_read32(); - +#if !LOAD_COMPRESSED + int health = saveg_read32(); + int movedir = saveg_read32(); + int movecount = saveg_read32(); // struct mobj_s* target; - str->target = saveg_readp(); + /*str->target = */saveg_readp(); - // int reactiontime; - str->reactiontime = saveg_read32(); + int reactiontime = saveg_read32(); + int threshold = saveg_read32(); - // int threshold; - str->threshold = saveg_read32(); + if (!mobj_is_static(str)) { + mobj_full(str)->health = health; + mobj_full(str)->movedir = movedir; + mobj_full(str)->movecount = movecount; + mobj_reactiontime(str) = reactiontime; + mobj_full(str)->threshold = threshold; + } +#else + if (!mobj_is_static(str)) { + mobj_full(str)->health = saveg_read_maybe(mobj_info(str)->spawnhealth, (saveg_reader)saveg_read16, "health"); + mobj_full(str)->movedir = saveg_read_bits(3); + // todo just curioous want this is + mobj_full(str)->movecount = saveg_read_maybe(0, saveg_read32, "move"); + mobj_full(str)->reactiontime = saveg_read_maybe( mobj_info(str)->reactiontime, (saveg_reader)saveg_read8, + "reaction"); + mobj_full(str)->threshold = saveg_read_maybe(0, (saveg_reader)saveg_read8, "threshold"); + } +#endif // struct player_s* player; +#if !LOAD_COMPRESSED pl = saveg_read32(); - - if (pl > 0) - { - str->player = &players[pl - 1]; - str->player->mo = str; + int lastlook = saveg_read32(); +#else + int lastlook; + if (!mobj_is_static(str)) { + pl = saveg_read_bit() ? saveg_read_bits(2) + 1 : 0; + xprintf("PL %d\n", pl); + lastlook = saveg_read_bits(2); + xprintf("LL %d\n", lastlook); } - else - { - str->player = NULL; +#endif + + if (!mobj_is_static(str)) { + if (pl > 0) { + mobj_full(str)->sp_player = player_to_shortptr(&players[pl - 1]); + mobj_player(str)->mo = str; + } else { + mobj_full(str)->sp_player = 0; + } + + // int lastlook; + mobj_full(str)->lastlook = lastlook; } - // int lastlook; - str->lastlook = saveg_read32(); - +#if !LOAD_COMPRESSED // mapthing_t spawnpoint; - saveg_read_mapthing_t(&str->spawnpoint); + mapthing_t mt; + saveg_read_mapthing_t(&mt); +#if !SHRINK_MOBJ + mobj_spawnpoint(str) = mt; +#else + str->spawnpoint = 0; + for(int i=0;ispawnpoint = i; + break; + } + } +#endif +#else +#endif +#if !LOAD_COMPRESSED // struct mobj_s* tracer; - str->tracer = saveg_readp(); + /*str->tracer = */saveg_readp(); +#endif + return str; } static void saveg_write_mobj_t(mobj_t *str) { +#if SAVE_COMPRESSED + // needed to decode other things + saveg_write8(str->type); + saveg_write16(str->spawnpoint); +#endif + // thinker_t thinker; saveg_write_thinker_t(&str->thinker); +#if !SAVE_COMPRESSED // fixed_t x; - saveg_write32(str->x); + saveg_write32(str->xy.x); // fixed_t y; - saveg_write32(str->y); + saveg_write32(str->xy.y); +#else + if (str->xy.x == (spawnpoint_mapthing(str->spawnpoint).x << FRACBITS) && + str->xy.y == (spawnpoint_mapthing(str->spawnpoint).y << FRACBITS)) { + saveg_write_bit(0); + xprintf("Unmoved %d %08x,%08x\n", str->spawnpoint, str->xy.x, str->xy.y); + } else { + saveg_write_bit(1); + xprintf("Moved %d %08x,%08x %08x,%08x\n", str->spawnpoint, str->xy.x, str->xy.y, spawnpoint_mapthing(str->spawnpoint).x << FRACBITS, spawnpoint_mapthing(str->spawnpoint).y << FRACBITS); + // fixed_t x; + saveg_write32(str->xy.x); + + // fixed_t y; + saveg_write32(str->xy.y); + } +#endif + + xprintf(" sector %d\n", str->sector_num); + +#if !SAVE_COMPRESSED + saveg_write32(str->z); +#else + // height is needed for decode of on ceiling + if (!mobj_is_static(str)) { + saveg_write_maybe(mobj_height(str), mobj_info(str)->height, saveg_write32, "height"); + saveg_write_maybe(mobj_floorz(str), sector_floorheight(mobj_sector(str)), saveg_write_low_zeros, "floorz"); + saveg_write_maybe(mobj_ceilingz(str), sector_ceilingheight(mobj_sector(str)), saveg_write_low_zeros, "ceilingz"); + } // fixed_t z; - saveg_write32(str->z); + if (str->z == mobj_floorz(str)) { + saveg_write_bit(0); + xprintf(" on the floor %08x\n", str->z); + } else if (str->z == mobj_ceilingz(str) - mobj_height(str)) { + saveg_write_bits(1, 2); + xprintf(" on the ceiling %08x\n", str->z); + } else { + saveg_write_bits(3, 2); + saveg_write32(str->z); + } +#endif // struct mobj_s* snext; - saveg_writep(str->snext); +#if !SAVE_COMPRESSED + saveg_writep(shortptr_to_ptr(str->sp_snext)); // note these are garbage anyway // struct mobj_s* sprev; - saveg_writep(str->sprev); + saveg_writep((void*)1);; // garbage anyway, so maybe collapse +#endif // angle_t angle; - saveg_write32(str->angle); +#if !SAVE_COMPRESSED + saveg_write32(mobj_is_static(str) ? 0 : (int)mobj_full(str)->angle); +#else + if (!mobj_is_static(str)) { + saveg_write_maybe(mobj_full(str)->angle, spawnpoint_mapthing(str->spawnpoint).angle << FRACBITS, + saveg_write_low_zeros, "angle"); + } +#endif +#if !SAVE_COMPRESSED // these are implicit in the current state (saved below) // spritenum_t sprite; - saveg_write_enum(str->sprite); - + saveg_write_enum(mobj_sprite(str)); // int frame; - saveg_write32(str->frame); + int frame = mobj_frame(str); +#if FF_FULLBRIGHT != 0x8000 + if (frame & FF_FULLBRIGHT) frame = (frame & ~FF_FULLBRIGHT) | 0x8000; +#endif + saveg_write32(frame); +#endif +#if !SAVE_COMPRESSED // struct mobj_s* bnext; - saveg_writep(str->bnext); + saveg_writep(shortptr_to_ptr(str->sp_bnext)); // garbage anyway, so maybe collapse // struct mobj_s* bprev; - saveg_writep(str->bprev); + saveg_writep((void*)1); // garbage anyway, so maybe collapse // struct subsector_s* subsector; +#if !SHRINK_MOBJ saveg_writep(str->subsector); +#else + saveg_writep(NULL); // should do this anyway, but still +#endif +#endif +#if !SAVE_COMPRESSED // fixed_t floorz; - saveg_write32(str->floorz); + saveg_write32(mobj_floorz(str)); // fixed_t ceilingz; - saveg_write32(str->ceilingz); - + saveg_write32(mobj_ceilingz(str)); // fixed_t radius; - saveg_write32(str->radius); + saveg_write32(mobj_radius(str)); // fixed_t height; - saveg_write32(str->height); + saveg_write32(mobj_height(str)); // fixed_t momx; - saveg_write32(str->momx); + if (mobj_is_static(str)) { + saveg_write32(0); + saveg_write32(0); + saveg_write32(0); + } else { + saveg_write32(mobj_full(str)->momx); - // fixed_t momy; - saveg_write32(str->momy); + // fixed_t momy; + saveg_write32(mobj_full(str)->momy); - // fixed_t momz; - saveg_write32(str->momz); + // fixed_t momz; + saveg_write32(mobj_full(str)->momz); + } +#else + if (!mobj_is_static(str)) { + saveg_write_maybe(mobj_radius(str), mobj_info(str)->radius, saveg_write32, "radius"); + saveg_write_maybe(mobj_full(str)->momx, 0, saveg_write32, "momx"); + saveg_write_maybe(mobj_full(str)->momy, 0, saveg_write32, "momy"); + saveg_write_maybe(mobj_full(str)->momz, 0, saveg_write32, "momz"); + } +#endif // int validcount; - saveg_write32(str->validcount); +#if !SAVE_COMPRESSED + saveg_write32(0);//str->validcount); +#endif +#if !SAVE_COMPRESSED // mobjtype_t type; saveg_write_enum(str->type); +#endif // mobjinfo_t* info; - saveg_writep(str->info); +#if !SAVE_COMPRESSED + saveg_writep(mobj_info(str)); +#endif // int tics; +#if !SAVE_COMPRESSED saveg_write32(str->tics); +#else + saveg_write_maybe(str->tics, -1, (saveg_writer) saveg_write8, "tics"); +#endif // state_t* state; - saveg_write32(str->state - states); +#if !SAVE_COMPRESSED + saveg_write32(mobj_state_num(str)); +#else + // todo fewer bits? + saveg_write_maybe(mobj_state_num(str), mobj_info(str)->spawnstate, (saveg_writer) saveg_write16, "state"); +#endif +#if !SAVE_COMPRESSED + saveg_write32(str->flags & ~MF_DECORATION); +#else // int flags; - saveg_write32(str->flags); + saveg_write_maybe(str->flags, mobj_info(str)->flags, saveg_write32, "flags"); +#endif - // int health; - saveg_write32(str->health); + if (mobj_is_static(str)) { +#if !SAVE_COMPRESSED + // int health; + saveg_write32(mobj_info(str)->spawnhealth); - // int movedir; - saveg_write32(str->movedir); - - // int movecount; - saveg_write32(str->movecount); - - // struct mobj_s* target; - saveg_writep(str->target); - - // int reactiontime; - saveg_write32(str->reactiontime); - - // int threshold; - saveg_write32(str->threshold); - - // struct player_s* player; - if (str->player) - { - saveg_write32(str->player - players + 1); - } - else - { + // int movedir; saveg_write32(0); + + // int movecount; + saveg_write32(0); + + // struct mobj_s* target; + saveg_writep(0); + + // int reactiontime; + saveg_write32(mobj_info(str)->reactiontime); + + // int threshold; + saveg_write32(0); + + // struct player_s* player; + saveg_write32(0); + + // int lastlook; + saveg_write32(0); +#endif + } else { + xprintf(" non static\n"); + // int health; +#if !SAVE_COMPRESSED + saveg_write32(mobj_full(str)->health); + + // int movedir; + saveg_write32(mobj_full(str)->movedir); + + // int movecount; + saveg_write32(mobj_full(str)->movecount); + + // struct mobj_s* target; + saveg_writep(mobj_target(str)); + + // int reactiontime; + saveg_write32(mobj_reactiontime(str)); + + // int threshold; + saveg_write32(mobj_full(str)->threshold); +#else + saveg_write_maybe(mobj_full(str)->health, mobj_info(str)->spawnhealth, (saveg_writer) saveg_write16, "health"); + saveg_write_bits(mobj_full(str)->movedir, 3); + // todo just curioous want this is + saveg_write_maybe(mobj_full(str)->movecount, 0, saveg_write32, "move"); + saveg_write_maybe(mobj_full(str)->reactiontime, mobj_info(str)->reactiontime, (saveg_writer) saveg_write8, + "reaction"); + saveg_write_maybe(mobj_full(str)->threshold, 0, (saveg_writer) saveg_write8, "threshold"); +#endif + + // struct player_s* player; + if (mobj_full(str)->sp_player) { +#if !SAVE_COMPRESSED + saveg_write32(mobj_player(str) - players + 1); +#else + assert(!mobj_is_static(str)); + if (!mobj_is_static(str)) { + xprintf("PL %d\n", (mobj_player(str) - players) + 1); + saveg_write_bit(1); + saveg_write_bits((mobj_player(str) - players), 2); + } +#endif + } else { +#if !SAVE_COMPRESSED + saveg_write32(0); +#else + if (!mobj_is_static(str)) { + xprintf("PL (0)\n"); + saveg_write_bit(0); + } +#endif + } + +#if !SAVE_COMPRESSED + // int lastlook; + saveg_write32(mobj_full(str)->lastlook); +#else + if (!mobj_is_static(str)) { + xprintf("LL %d\n", mobj_full(str)->lastlook); + saveg_write_bits(mobj_full(str)->lastlook, 2); + } +#endif } - - // int lastlook; - saveg_write32(str->lastlook); - +#if !SAVE_COMPRESSED // implicit with spawnpoint // mapthing_t spawnpoint; - saveg_write_mapthing_t(&str->spawnpoint); + saveg_write_mapthing_t(&mobj_spawnpoint(str)); +#endif - // struct mobj_s* tracer; - saveg_writep(str->tracer); +#if !SAVE_COMPRESSED + if (mobj_is_static(str)) { + saveg_writep(0); + } else { + // struct mobj_s* tracer; + saveg_writep(mobj_tracer(str)); + } +#endif } @@ -678,11 +1168,11 @@ static void saveg_read_player_t(player_t *str) // boolean cards[NUMCARDS]; for (i=0; icards[i] = saveg_read32(); + str->cards[i] = saveg_read_boolean(); } // boolean backpack; - str->backpack = saveg_read32(); + str->backpack = saveg_read_boolean(); // int frags[MAXPLAYERS]; for (i=0; iweaponowned[i] = saveg_read32(); + str->weaponowned[i] = saveg_read_boolean(); } // int ammo[NUMAMMO]; @@ -763,7 +1253,7 @@ static void saveg_read_player_t(player_t *str) } // boolean didsecret; - str->didsecret = saveg_read32(); + str->didsecret = saveg_read_boolean(); } static void saveg_write_player_t(player_t *str) @@ -809,11 +1299,11 @@ static void saveg_write_player_t(player_t *str) // boolean cards[NUMCARDS]; for (i=0; icards[i]); + saveg_write_boolean(str->cards[i]); } // boolean backpack; - saveg_write32(str->backpack); + saveg_write_boolean(str->backpack); // int frags[MAXPLAYERS]; for (i=0; iweaponowned[i]); + saveg_write_boolean(str->weaponowned[i]); } // int ammo[NUMAMMO]; @@ -894,7 +1384,7 @@ static void saveg_write_player_t(player_t *str) } // boolean didsecret; - saveg_write32(str->didsecret); + saveg_write_boolean(str->didsecret); } @@ -913,7 +1403,11 @@ static void saveg_read_ceiling_t(ceiling_t *str) str->type = saveg_read_enum(); // sector_t* sector; +#if !LOAD_COMPRESSED sector = saveg_read32(); +#else + sector = saveg_read16(); +#endif str->sector = §ors[sector]; // fixed_t bottomheight; @@ -926,7 +1420,7 @@ static void saveg_read_ceiling_t(ceiling_t *str) str->speed = saveg_read32(); // boolean crush; - str->crush = saveg_read32(); + str->crush = saveg_read_boolean(); // int direction; str->direction = saveg_read32(); @@ -946,8 +1440,12 @@ static void saveg_write_ceiling_t(ceiling_t *str) // ceiling_e type; saveg_write_enum(str->type); +#if !SAVE_COMPRESSED // sector_t* sector; saveg_write32(str->sector - sectors); +#else + saveg_write16(str->sector - sectors); +#endif // fixed_t bottomheight; saveg_write32(str->bottomheight); @@ -959,7 +1457,7 @@ static void saveg_write_ceiling_t(ceiling_t *str) saveg_write32(str->speed); // boolean crush; - saveg_write32(str->crush); + saveg_write_boolean(str->crush); // int direction; saveg_write32(str->direction); @@ -985,8 +1483,11 @@ static void saveg_read_vldoor_t(vldoor_t *str) // vldoor_e type; str->type = saveg_read_enum(); - // sector_t* sector; +#if !LOAD_COMPRESSED sector = saveg_read32(); +#else + sector = saveg_read16(); +#endif str->sector = §ors[sector]; // fixed_t topheight; @@ -1047,10 +1548,14 @@ static void saveg_read_floormove_t(floormove_t *str) str->type = saveg_read_enum(); // boolean crush; - str->crush = saveg_read32(); + str->crush = saveg_read_boolean(); // sector_t* sector; +#if !LOAD_COMPRESSED sector = saveg_read32(); +#else + sector = saveg_read16(); +#endif str->sector = §ors[sector]; // int direction; @@ -1078,10 +1583,14 @@ static void saveg_write_floormove_t(floormove_t *str) saveg_write_enum(str->type); // boolean crush; - saveg_write32(str->crush); + saveg_write_boolean(str->crush); // sector_t* sector; +#if !SAVE_COMPRESSED saveg_write32(str->sector - sectors); +#else + saveg_write16(str->sector - sectors); +#endif // int direction; saveg_write32(str->direction); @@ -1111,7 +1620,11 @@ static void saveg_read_plat_t(plat_t *str) saveg_read_thinker_t(&str->thinker); // sector_t* sector; +#if !LOAD_COMPRESSED sector = saveg_read32(); +#else + sector = saveg_read16(); +#endif str->sector = §ors[sector]; // fixed_t speed; @@ -1136,7 +1649,7 @@ static void saveg_read_plat_t(plat_t *str) str->oldstatus = saveg_read_enum(); // boolean crush; - str->crush = saveg_read32(); + str->crush = saveg_read_boolean(); // int tag; str->tag = saveg_read32(); @@ -1151,7 +1664,11 @@ static void saveg_write_plat_t(plat_t *str) saveg_write_thinker_t(&str->thinker); // sector_t* sector; +#if !SAVE_COMPRESSED saveg_write32(str->sector - sectors); +#else + saveg_write16(str->sector - sectors); +#endif // fixed_t speed; saveg_write32(str->speed); @@ -1175,7 +1692,7 @@ static void saveg_write_plat_t(plat_t *str) saveg_write_enum(str->oldstatus); // boolean crush; - saveg_write32(str->crush); + saveg_write_boolean(str->crush); // int tag; saveg_write32(str->tag); @@ -1196,7 +1713,11 @@ static void saveg_read_lightflash_t(lightflash_t *str) saveg_read_thinker_t(&str->thinker); // sector_t* sector; +#if !LOAD_COMPRESSED sector = saveg_read32(); +#else + sector = saveg_read16(); +#endif str->sector = §ors[sector]; // int count; @@ -1221,7 +1742,11 @@ static void saveg_write_lightflash_t(lightflash_t *str) saveg_write_thinker_t(&str->thinker); // sector_t* sector; +#if !SAVE_COMPRESSED saveg_write32(str->sector - sectors); +#else + saveg_write16(str->sector - sectors); +#endif // int count; saveg_write32(str->count); @@ -1251,7 +1776,11 @@ static void saveg_read_strobe_t(strobe_t *str) saveg_read_thinker_t(&str->thinker); // sector_t* sector; +#if !LOAD_COMPRESSED sector = saveg_read32(); +#else + sector = saveg_read16(); +#endif str->sector = §ors[sector]; // int count; @@ -1276,7 +1805,11 @@ static void saveg_write_strobe_t(strobe_t *str) saveg_write_thinker_t(&str->thinker); // sector_t* sector; +#if !SAVE_COMPRESSED saveg_write32(str->sector - sectors); +#else + saveg_write16(str->sector - sectors); +#endif // int count; saveg_write32(str->count); @@ -1306,7 +1839,11 @@ static void saveg_read_glow_t(glow_t *str) saveg_read_thinker_t(&str->thinker); // sector_t* sector; +#if !LOAD_COMPRESSED sector = saveg_read32(); +#else + sector = saveg_read16(); +#endif str->sector = §ors[sector]; // int minlight; @@ -1325,7 +1862,11 @@ static void saveg_write_glow_t(glow_t *str) saveg_write_thinker_t(&str->thinker); // sector_t* sector; +#if !SAVE_COMPRESSED saveg_write32(str->sector - sectors); +#else + saveg_write16(str->sector - sectors); +#endif // int minlight; saveg_write32(str->minlight); @@ -1341,32 +1882,67 @@ static void saveg_write_glow_t(glow_t *str) // Write the header for a savegame // +#if LOAD_COMPRESSED || SAVE_COMPRESSED +#define LOAD_SAVE_VERSION 1 + +static uint32_t calc_wad_hash() { + uint32_t hash = LOAD_SAVE_VERSION; +#if USE_WHD + hash = hash * 31u + whdheader->hash; +#endif + return hash; +} +#endif + void P_WriteSaveGameHeader(char *description) { - char name[VERSIONSIZE]; - int i; - + int i; + +#if true || !SAVE_COMPRESSED // leave this for now since save game menu uses it for (i=0; description[i] != '\0'; ++i) saveg_write8(description[i]); for (; i SAVESTRINGSIZE) l = SAVESTRINGSIZE; + static_assert(SAVESTRINGSIZE < 32, ""); + saveg_write_bits(l, 5); + for(i=0;i> 16) & 0xff); saveg_write8((leveltime >> 8) & 0xff); saveg_write8(leveltime & 0xff); +#else + saveg_write_bits(leveltime & 0xffffffu, 24); +#endif } // @@ -1385,26 +1961,40 @@ boolean P_ReadSaveGameHeader(void) for (i=0; ifloorheight >> FRACBITS); - saveg_write16(sec->ceilingheight >> FRACBITS); + saveg_write16(sector_floorheight(sec) >> FRACBITS); + saveg_write16(sector_ceilingheight(sec) >> FRACBITS); saveg_write16(sec->floorpic); saveg_write16(sec->ceilingpic); saveg_write16(sec->lightlevel); saveg_write16(sec->special); // needed? saveg_write16(sec->tag); // needed? } - - - // do lines +#else + // do sectors + uint8_t *po = sg_bo->cur; + uint bi0 = sg_bo->bits; + for (i=0, sec = sectors; icur - po) * 8 + sg_bo->bits - bi0); + th_bit_output mark = *sg_bo; + saveg_write_bit(1); + saveg_write_maybe(sector_floorheight(sec) >> FRACBITS, whd_sectors[i].floorheight, (saveg_writer)saveg_write16, "floorheight"); + saveg_write_maybe(sector_ceilingheight(sec) >> FRACBITS, whd_sectors[i].ceilingheight, (saveg_writer)saveg_write16, "ceilingheight"); + saveg_write_maybe(sec->floorpic, whd_sectors[i].floorpic, (saveg_writer)saveg_write8, "floorpic"); + saveg_write_maybe(sec->ceilingpic, whd_sectors[i].ceilingpic, (saveg_writer)saveg_write8, "ceilingpic"); + saveg_write_maybe(sec->lightlevel, whd_sectors[i].lightlevel, (saveg_writer)saveg_write16, "lightlevel"); + if (sec->special != whd_sectors[i].special) { + saveg_write_bit(1); + saveg_write_bits(sec->special, 5); // needed? + } else { + saveg_write_bit(0); + } + // tags are immutable + // saveg_write_maybe(sec->tag, whd_sectors[i].tag, (saveg_writer)saveg_write16, "tag"); + uint used = (sg_bo->cur - mark.cur) * 8 + (sg_bo->bits - mark.bits); + if (used == 7) { + // there were no diffs + *sg_bo = mark; + saveg_write_bit(0); + } + } +#endif +#if !SAVE_COMPRESSED + // do lines for (i=0, li = lines ; iflags); - saveg_write16(li->special); - saveg_write16(li->tag); + saveg_write16(line_flags(li)); + saveg_write16(line_special(li)); + saveg_write16(line_tag(li)); for (j=0 ; j<2 ; j++) { - if (li->sidenum[j] == -1) + if (line_sidenum(li, j) == -1) continue; - si = &sides[li->sidenum[j]]; + si = sidenum_to_side(line_sidenum(li, j)); - saveg_write16(si->textureoffset >> FRACBITS); - saveg_write16(si->rowoffset >> FRACBITS); - saveg_write16(si->toptexture); - saveg_write16(si->bottomtexture); - saveg_write16(si->midtexture); + saveg_write16(side_textureoffset(si) >> FRACBITS); + saveg_write16(side_rowoffset(si) >> FRACBITS); + saveg_write16(side_toptexture(si)); + saveg_write16(side_bottomtexture(si)); + saveg_write16(side_midtexture(si)); } } +#else + saveg_write16(linespecialoffset); + for (i = 0,li=lines; i < numlines; i++) { + // flags are immutable + //saveg_write16(line_flags(li)); + // special can only be cleared + xprintf("LI %d %p %08x\n", i, li, (sg_bo->cur - po) * 8 + sg_bo->bits - bi0); + assert(!line_special(li) || line_special(li) == whd_line_special((li))); + if (whd_line_special(li)) { + saveg_write_bit(!line_special(li)); + } + saveg_write_bit(line_is_mapped(li)); + // tags are immutable +// saveg_write16(line_tag(li)); + for (j=0 ; j<2 ; j++) + { + if (line_sidenum(li, j) == -1) + continue; + + si = sidenum_to_side(line_sidenum(li, j)); + + // we keep use linespecialoffset (global) instead + //saveg_write16(side_textureoffset(si) >> FRACBITS); + + // never changed it seems +// saveg_write16(side_rowoffset(si) >> FRACBITS); + + uint tex = side_toptexture(si); + xprintf("%d %d", tex <= LAST_SWITCH_TEXTURE, tex); + if (tex <= LAST_SWITCH_TEXTURE) saveg_write_bit(is_switched_texture(si, CST_TOP)); + tex = side_bottomtexture(si); + xprintf(" %d %d", tex <= LAST_SWITCH_TEXTURE, tex); + if (tex <= LAST_SWITCH_TEXTURE) saveg_write_bit(is_switched_texture(si, CST_BOTTOM)); + tex = side_midtexture(si); + xprintf(" %d %d\n", tex <= LAST_SWITCH_TEXTURE, tex); + if (tex < LAST_SWITCH_TEXTURE) saveg_write_bit(is_switched_texture(si, CST_MID)); + } + li += line_next_step(li); + } +#endif } @@ -1533,12 +2192,13 @@ void P_UnArchiveWorld (void) sector_t* sec; line_t* li; side_t* si; - + +#if !LOAD_COMPRESSED // do sectors for (i=0, sec = sectors ; ifloorheight = saveg_read16() << FRACBITS; - sec->ceilingheight = saveg_read16() << FRACBITS; + sector_set_floorheight(sec, saveg_read16() << FRACBITS); + sector_set_ceilingheight(sec, saveg_read16() << FRACBITS); sec->floorpic = saveg_read16(); sec->ceilingpic = saveg_read16(); sec->lightlevel = saveg_read16(); @@ -1547,25 +2207,79 @@ void P_UnArchiveWorld (void) sec->specialdata = 0; sec->soundtarget = 0; } - +#else + for (i=0, sec = sectors ; ifloorpic = saveg_read_maybe( whd_sectors[i].floorpic, (saveg_reader)saveg_read8, "floorpic"); + sec->ceilingpic = saveg_read_maybe(whd_sectors[i].ceilingpic, (saveg_reader)saveg_read8, "ceilingpic"); + sec->lightlevel = saveg_read_maybe(whd_sectors[i].lightlevel, (saveg_reader)saveg_read16, "lightlevel"); + if (saveg_read_bit()) { + sec->special = saveg_read_bits(5); + } + } + } +#endif + +#if !LOAD_COMPRESSED // do lines for (i=0, li = lines ; iflags = saveg_read16(); - li->special = saveg_read16(); - li->tag = saveg_read16(); +#if !(USE_RAW_MAPLINEDEF && TEMP_IMMUTABLE_DISABLED) + hack_rowad_p(line_t, li, flags) = saveg_read16(); + hack_rowad_p(line_t, li, special) = saveg_read16(); + hack_rowad_p(line_t, li, tag) = saveg_read16(); +#else + // todo we can set these flags correctly + saveg_read16(); saveg_read16(); saveg_read16(); +#endif for (j=0 ; j<2 ; j++) { - if (li->sidenum[j] == -1) + if (line_sidenum(li, j) == -1) continue; - si = &sides[li->sidenum[j]]; - si->textureoffset = saveg_read16() << FRACBITS; - si->rowoffset = saveg_read16() << FRACBITS; - si->toptexture = saveg_read16(); - si->bottomtexture = saveg_read16(); - si->midtexture = saveg_read16(); + si = (side_t *)sidenum_to_side(line_sidenum(li, j)); + side_settextureoffset16(si, saveg_read16()); + side_setrowoffset16(si, saveg_read16()); + side_settoptexture(si, saveg_read16()); + side_setbottomtexture(si, saveg_read16()); + side_setmidtexture(si, saveg_read16()); } } +#else + linespecialoffset = saveg_read16(); + // do lines + for (i=0, li = lines ; icur - po) * 8 + bi0- sg_bi->bits); + if (whd_line_special(li)) { + if (saveg_read_bit()) clear_line_special(li); + } + if (saveg_read_bit()) line_set_mapped(li); + // no tags as these are immutable + for (j=0 ; j<2 ; j++) + { + if (line_sidenum(li, j) == -1) + continue; + si = (side_t *)sidenum_to_side(line_sidenum(li, j)); +// side_settextureoffset16(si, saveg_read16()); // uses linespecial +// side_setrowoffset16(si, saveg_read16()); // never used + uint tex = side_toptexture(si); + xprintf("%d %d", tex <= LAST_SWITCH_TEXTURE, tex); + if (tex <= LAST_SWITCH_TEXTURE && saveg_read_bit()) + side_settoptexture(si, tex ^ 1); + tex = side_bottomtexture(si); + xprintf(" %d %d", tex <= LAST_SWITCH_TEXTURE, tex); + if (tex <= LAST_SWITCH_TEXTURE && saveg_read_bit()) + side_setbottomtexture(si, tex ^ 1); + tex = side_midtexture(si); + xprintf(" %d %d\n", tex <= LAST_SWITCH_TEXTURE, tex); + if (tex <= LAST_SWITCH_TEXTURE && saveg_read_bit()) + side_setmidtexture(si, tex ^ 1); + } + li += line_next_step(li); + } +#endif } @@ -1591,13 +2305,17 @@ void P_ArchiveThinkers (void) thinker_t* th; // save off the current thinkers - for (th = thinkercap.next ; th != &thinkercap ; th=th->next) + for (th = thinker_next(&thinkercap) ; th != &thinkercap ; th=thinker_next(th)) { - if (th->function.acp1 == (actionf_p1)P_MobjThinker) + if (th->function == ThinkF_P_MobjThinker) { +#if !SAVE_COMPRESSED saveg_write8(tc_mobj); saveg_write_pad(); - saveg_write_mobj_t((mobj_t *) th); +#else + saveg_write_bit(0); +#endif + saveg_write_mobj_t((mobj_t *) th); continue; } @@ -1606,7 +2324,11 @@ void P_ArchiveThinkers (void) } // add a terminating marker +#if !SAVE_COMPRESSED saveg_write8(tc_end); +#else + saveg_write_bit(1); +#endif } @@ -1622,15 +2344,15 @@ void P_UnArchiveThinkers (void) mobj_t* mobj; // remove all the current thinkers - currentthinker = thinkercap.next; + currentthinker = thinker_next(&thinkercap); while (currentthinker != &thinkercap) { - next = currentthinker->next; - - if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker) - P_RemoveMobj ((mobj_t *)currentthinker); - else - Z_Free (currentthinker); + next = thinker_next(currentthinker); + + if (currentthinker->function == ThinkF_P_MobjThinker) { + P_RemoveMobj((mobj_t *) currentthinker); + } + Z_ThinkFree (currentthinker); currentthinker = next; } @@ -1639,6 +2361,7 @@ void P_UnArchiveThinkers (void) // read in saved thinkers while (1) { +#if !LOAD_COMPRESSED tclass = saveg_read8(); switch (tclass) { @@ -1646,24 +2369,35 @@ void P_UnArchiveThinkers (void) return; // end of list case tc_mobj: - saveg_read_pad(); - mobj = Z_Malloc (sizeof(*mobj), PU_LEVEL, NULL); - saveg_read_mobj_t(mobj); - - mobj->target = NULL; - mobj->tracer = NULL; - P_SetThingPosition (mobj); - mobj->info = &mobjinfo[mobj->type]; - mobj->floorz = mobj->subsector->sector->floorheight; - mobj->ceilingz = mobj->subsector->sector->ceilingheight; - mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; - P_AddThinker (&mobj->thinker); - break; + break; default: I_Error ("Unknown tclass %i in savegame",tclass); } - +#else + if (saveg_read_bit()) break; +#endif + xprintf("MOBJ\n"); + saveg_read_pad(); + + mobj = saveg_read_mobj_t(); + +//#if !LOAD_COMPRESSED // should be zero anyway + if (!mobj_is_static(mobj)) { + mobj_full(mobj)->sp_target = 0; + mobj_full(mobj)->sp_tracer = 0; + } +//#endif + P_SetThingPosition (mobj); +#if !SHRINK_MOBJ + mobj->info = &mobjinfo[mobj->type]; +#endif + if (!mobj_is_static(mobj)) { + mobj_full(mobj)->floorz = sector_floorheight(mobj_sector(mobj)); + mobj_full(mobj)->ceilingz = sector_ceilingheight(mobj_sector(mobj)); + } + mobj->thinker.function = ThinkF_P_MobjThinker; + P_AddThinker (&mobj->thinker); } } @@ -1683,9 +2417,25 @@ enum tc_glow, tc_endspecials -} specials_e; +} specials_e; +static void saveg_write_special_code(uint8_t e) { +#if !SAVE_COMPRESSED + saveg_write8(e); +#else + static_assert(tc_endspecials < 8, ""); + saveg_write_bits(e, 3); +#endif +} +static int saveg_read_special_code() { +#if !SAVE_COMPRESSED + return saveg_read8(); +#else + static_assert(tc_endspecials < 8, ""); + return saveg_read_bits(3); +#endif +} // // Things to handle: @@ -1702,76 +2452,76 @@ void P_ArchiveSpecials (void) { thinker_t* th; int i; - + // save off the current thinkers - for (th = thinkercap.next ; th != &thinkercap ; th=th->next) + for (th = thinker_next(&thinkercap) ; th != &thinkercap ; th=thinker_next(th)) { - if (th->function.acv == (actionf_v)NULL) + if (th->function == ThinkF_NULL) { for (i = 0; i < MAXCEILINGS;i++) - if (activeceilings[i] == (ceiling_t *)th) + if (activeceilings[i] == ptr_to_shortptr((ceiling_t *)th)) break; if (ifunction.acp1 == (actionf_p1)T_MoveCeiling) + if (th->function == ThinkF_T_MoveCeiling) { - saveg_write8(tc_ceiling); + saveg_write_special_code(tc_ceiling); saveg_write_pad(); saveg_write_ceiling_t((ceiling_t *) th); continue; } - - if (th->function.acp1 == (actionf_p1)T_VerticalDoor) + + if (th->function == ThinkF_T_VerticalDoor) { - saveg_write8(tc_door); + saveg_write_special_code(tc_door); saveg_write_pad(); saveg_write_vldoor_t((vldoor_t *) th); continue; } - - if (th->function.acp1 == (actionf_p1)T_MoveFloor) + + if (th->function == ThinkF_T_MoveFloor) { - saveg_write8(tc_floor); + saveg_write_special_code(tc_floor); saveg_write_pad(); saveg_write_floormove_t((floormove_t *) th); continue; } - - if (th->function.acp1 == (actionf_p1)T_PlatRaise) + + if (th->function == ThinkF_T_PlatRaise) { - saveg_write8(tc_plat); + saveg_write_special_code(tc_plat); saveg_write_pad(); saveg_write_plat_t((plat_t *) th); continue; } - - if (th->function.acp1 == (actionf_p1)T_LightFlash) + + if (th->function == ThinkF_T_LightFlash) { - saveg_write8(tc_flash); + saveg_write_special_code(tc_flash); saveg_write_pad(); saveg_write_lightflash_t((lightflash_t *) th); continue; } - - if (th->function.acp1 == (actionf_p1)T_StrobeFlash) + + if (th->function == ThinkF_T_StrobeFlash) { - saveg_write8(tc_strobe); + saveg_write_special_code(tc_strobe); saveg_write_pad(); saveg_write_strobe_t((strobe_t *) th); continue; } - - if (th->function.acp1 == (actionf_p1)T_Glow) + + if (th->function == ThinkF_T_Glow) { - saveg_write8(tc_glow); + saveg_write_special_code(tc_glow); saveg_write_pad(); saveg_write_glow_t((glow_t *) th); continue; @@ -1779,7 +2529,7 @@ void P_ArchiveSpecials (void) } // add a terminating marker - saveg_write8(tc_endspecials); + saveg_write_special_code(tc_endspecials); } @@ -1802,7 +2552,7 @@ void P_UnArchiveSpecials (void) // read in saved thinkers while (1) { - tclass = saveg_read8(); + tclass = saveg_read_special_code(); switch (tclass) { @@ -1811,12 +2561,12 @@ void P_UnArchiveSpecials (void) case tc_ceiling: saveg_read_pad(); - ceiling = Z_Malloc (sizeof(*ceiling), PU_LEVEL, NULL); - saveg_read_ceiling_t(ceiling); - ceiling->sector->specialdata = ceiling; + ceiling = Z_Malloc (sizeof(*ceiling), PU_LEVEL, 0); + saveg_read_ceiling_t(ceiling); + ceiling->sector->specialdata = ptr_to_shortptr(ceiling); - if (ceiling->thinker.function.acp1) - ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling; + if (ceiling->thinker.function != ThinkF_NULL) + ceiling->thinker.function = ThinkF_T_MoveCeiling; P_AddThinker (&ceiling->thinker); P_AddActiveCeiling(ceiling); @@ -1824,30 +2574,30 @@ void P_UnArchiveSpecials (void) case tc_door: saveg_read_pad(); - door = Z_Malloc (sizeof(*door), PU_LEVEL, NULL); + door = Z_Malloc (sizeof(*door), PU_LEVEL, 0); saveg_read_vldoor_t(door); - door->sector->specialdata = door; - door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + door->sector->specialdata = ptr_to_shortptr(door); + door->thinker.function = ThinkF_T_VerticalDoor; P_AddThinker (&door->thinker); break; case tc_floor: saveg_read_pad(); - floor = Z_Malloc (sizeof(*floor), PU_LEVEL, NULL); + floor = Z_Malloc (sizeof(*floor), PU_LEVEL, 0); saveg_read_floormove_t(floor); - floor->sector->specialdata = floor; - floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor; + floor->sector->specialdata = ptr_to_shortptr(floor); + floor->thinker.function = ThinkF_T_MoveFloor; P_AddThinker (&floor->thinker); break; case tc_plat: saveg_read_pad(); - plat = Z_Malloc (sizeof(*plat), PU_LEVEL, NULL); + plat = Z_Malloc (sizeof(*plat), PU_LEVEL, 0); saveg_read_plat_t(plat); - plat->sector->specialdata = plat; + plat->sector->specialdata = ptr_to_shortptr(plat); - if (plat->thinker.function.acp1) - plat->thinker.function.acp1 = (actionf_p1)T_PlatRaise; + if (plat->thinker.function != ThinkF_NULL) + plat->thinker.function = ThinkF_T_PlatRaise; P_AddThinker (&plat->thinker); P_AddActivePlat(plat); @@ -1855,25 +2605,25 @@ void P_UnArchiveSpecials (void) case tc_flash: saveg_read_pad(); - flash = Z_Malloc (sizeof(*flash), PU_LEVEL, NULL); + flash = Z_Malloc (sizeof(*flash), PU_LEVEL, 0); saveg_read_lightflash_t(flash); - flash->thinker.function.acp1 = (actionf_p1)T_LightFlash; + flash->thinker.function = ThinkF_T_LightFlash; P_AddThinker (&flash->thinker); break; case tc_strobe: saveg_read_pad(); - strobe = Z_Malloc (sizeof(*strobe), PU_LEVEL, NULL); + strobe = Z_Malloc (sizeof(*strobe), PU_LEVEL, 0); saveg_read_strobe_t(strobe); - strobe->thinker.function.acp1 = (actionf_p1)T_StrobeFlash; + strobe->thinker.function = ThinkF_T_StrobeFlash; P_AddThinker (&strobe->thinker); break; case tc_glow: saveg_read_pad(); - glow = Z_Malloc (sizeof(*glow), PU_LEVEL, NULL); + glow = Z_Malloc (sizeof(*glow), PU_LEVEL, 0); saveg_read_glow_t(glow); - glow->thinker.function.acp1 = (actionf_p1)T_Glow; + glow->thinker.function = ThinkF_T_Glow; P_AddThinker (&glow->thinker); break; @@ -1886,3 +2636,189 @@ void P_UnArchiveSpecials (void) } +#if PICO_ON_DEVICE +#include "w_wad.h" +#include "picoflash.h" +#include "hardware/sync.h" +#include "hardware/address_mapped.h" +#include "hardware/timer.h" +#include "picodoom.h" + +const uint8_t *get_end_of_flash(void) { + static const uint8_t *end_of_flash; + if (!end_of_flash) { + // look for end of flash by repeated data + for(end_of_flash = (const uint8_t *)XIP_BASE + 2 * 1024*1024; end_of_flash < (const uint8_t *)XIP_BASE + 16 * 1024 * 1024; end_of_flash += 2 * 1024 * 1024) { + if (!memcmp(end_of_flash, (const uint8_t *)XIP_BASE, 512)) { + break; + } + } +// printf("FLASH SPACE %p -> %p\n", whd_map_base + whdheader->size, end_of_flash); + } + return end_of_flash; +} + +void P_SaveGameGetExistingFlashSlotAddresses(flash_slot_info_t *slots, int count) { + const uint8_t *index = get_end_of_flash() - 4; + int i; + const uint8_t *limit = whd_map_base + whdheader->size; + for(i=0;i= i && index[1] < count) { + int size = index[2] + (index[3] << 8); + const uint8_t *start = index - size - 4; + if (start >= limit && + start[0] == 0xb7 && + start[1] == index[1] && + start[2] == (uint8_t)(size ^ 0x55) && + start[3] == (uint8_t)((size>>8) ^ 0xaa)) { + ok = true; + for(;i%p (+%04x)\n", i, slots[i].data, slots[i].data-4, slots[i].data-4 + slots[i].size+8, slots[i].size+8); + } + } + if (!ok) break; + } + for(;i high_dest) { +// panic("bad ranges"); +// } +// } + +// uint32_t save = spin_lock_blocking(spin_lock_instance(PICO_SPINLOCK_ID_HARDWARE_CLAIM)); + for(const uint8_t *sector = forwards ? first_sector : last_sector; + forwards ? sector <= last_sector : sector >= first_sector; + sector += forwards ? FLASH_SECTOR_SIZE : -FLASH_SECTOR_SIZE) { + memcpy(buffer4k, sector, FLASH_SECTOR_SIZE); +// printf("SAVE/ERASE %08x + %04x\n", sector, FLASH_SECTOR_SIZE); + for(int i=0;i elements[i].size) { + size = elements[i].size - from_offset; + } + if (size > 0) { +// printf(" WRITE %p (%p+%04x) + %04x at %p (%p+%04x)\n", elements[i].src + from_offset, elements[i].src, from_offset, size, to, sector, to-sector); +// hard_assert( to >= sector && to + size - sector <= FLASH_SECTOR_SIZE); + memmove(buffer4k + (to - sector), elements[i].src + from_offset, size); + } + } + uint32_t save = save_and_disable_interrupts(); + picoflash_sector_program((uintptr_t)sector - XIP_BASE, buffer4k); + restore_interrupts(save); + } +// spin_unlock(spin_lock_instance(PICO_SPINLOCK_ID_HARDWARE_CLAIM), save); +} + +boolean __noinline P_SaveGameWriteFlashSlot(int slot, const uint8_t *buffer, uint size, uint8_t *buffer4k) { +#define MAX_SLOTS 8 +#define SLOT_OVERHEAD 8 + flash_slot_info_t slots[MAX_SLOTS]; + P_SaveGameGetExistingFlashSlotAddresses(slots, count_of(slots)); + int used = 0; + const uint8_t *prev_slot_bottom = get_end_of_flash(); + int last_slot = 0; + for(int i=0;i< count_of(slots);i++) { + if (slots[i].data) { + if (i < slot) { + prev_slot_bottom = slots[i].data - 4; + } + if (i != slot) { + used += SLOT_OVERHEAD + slots[i].size; + } + last_slot = i; + } + } + const uint8_t *limit = whd_map_base + whdheader->size; + int freespace = (get_end_of_flash() - limit) - used; +// printf("SPACE %d, required %d\n", freespace, size + SLOT_OVERHEAD); + if (freespace < size + SLOT_OVERHEAD) { + return false; + } + pd_start_save_pause(); +// printf("Need to add %p->%p (+%04x)\n", prev_slot_bottom - 4 - size, prev_slot_bottom, size + 8); + if (last_slot > slot) { + assert(slots[last_slot.data]); + const uint8_t *from_top = slots[slot].data ? slots[slot].data - 4 : prev_slot_bottom; + const uint8_t *from_bottom = slots[last_slot].data - 4; + const uint8_t *to_top = prev_slot_bottom; + if (buffer) to_top -= size + SLOT_OVERHEAD; + assert(from_top - from_bottom > 0); + flash_write_element element = { + .size = from_top - from_bottom, + .dest = to_top - (from_top - from_bottom), + .src = from_bottom + }; +// printf("Need to move %p->%p (+%04x) to %p->%p\n", from_bottom, from_top, element.size, to_top - element.size, to_top); + write_flash_elements(&element, 1, to_top - element.size, to_top, buffer4k, to_top < from_top); + } + if (buffer) { + uint8_t high_marker[] = { + 0x53, (uint8_t) slot, size & 0xff, (size >> 8) & 0xff + }; + uint8_t low_marker[] = { + 0xb7, (uint8_t) slot, 0x55 ^ (size & 0xff), 0xaa ^ ((size >> 8) & 0xff) + }; + + flash_write_element elements[3] = { + { + .dest = prev_slot_bottom - 4, + .src = high_marker, + .size = 4, + }, + { + .dest = prev_slot_bottom - 4 - size, + .src = buffer, + .size = (int) size, + }, + { + .dest = prev_slot_bottom - 8 - size, + .src = low_marker, + .size = 4, + } + }; + write_flash_elements(elements, count_of(elements), prev_slot_bottom - 8 - size, prev_slot_bottom, buffer4k, + true); + } else if (slot == last_slot && slots[slot].data) { +// printf("Nuking slot %d\n", slot); + uint8_t dummy[4] = {0}; + flash_write_element element = { + .size = 4, + .dest = slots[slot].data + slots[slot].size, + .src = dummy + }; + write_flash_elements(&element, 1, element.dest, element.dest+4, buffer4k, true); + } + pd_end_save_pause(); + return true; +} + +#endif diff --git a/src/doom/p_saveg.h b/src/doom/p_saveg.h index 518a5b70..fbbd2506 100644 --- a/src/doom/p_saveg.h +++ b/src/doom/p_saveg.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -31,10 +32,22 @@ // temporary filename to use while saving. +#if !NO_FILE_ACCESS char *P_TempSaveGameFile(void); // filename to use for a savegame slot +#endif +#if PICO_ON_DEVICE +typedef struct { + const uint8_t *data; // null for empty slot + int size; +} flash_slot_info_t; + +void P_SaveGameGetExistingFlashSlotAddresses(flash_slot_info_t *slots, int count); +// can pass null to clear a slot +boolean P_SaveGameWriteFlashSlot(int slot, const uint8_t *buffer, uint size, uint8_t *buffer4k); +#endif char *P_SaveGameFile(int slot); // Savegame file header read/write functions @@ -58,7 +71,18 @@ void P_UnArchiveThinkers (void); void P_ArchiveSpecials (void); void P_UnArchiveSpecials (void); +#if !NO_FILE_ACCESS extern FILE *save_stream; +#endif +#if SAVE_COMPRESSED || LOAD_COMPRESSED +#include "tiny_huff.h" +#endif +#if LOAD_COMPRESSED +extern th_bit_input *sg_bi; +#endif +#if SAVE_COMPRESSED +extern th_bit_output *sg_bo; +#endif extern boolean savegame_error; diff --git a/src/doom/p_setup.c b/src/doom/p_setup.c index a6e65b5a..570795e9 100644 --- a/src/doom/p_setup.c +++ b/src/doom/p_setup.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -41,33 +42,67 @@ #include "doomstat.h" -void P_SpawnMapThing (mapthing_t* mthing); +void P_SpawnMapThing (spawnpoint_t spawnpoint); +#if USE_RAW_MAPTHING +const mapthing_t *mapthings; +cardinal_t nummapthings; +#endif // // MAP related Lookup tables. // Store VERTEXES, LINEDEFS, SIDEDEFS, etc. // -int numvertexes; +cardinal_t numvertexes; vertex_t* vertexes; -int numsegs; +#if !WHD_SUPER_TINY +cardinal_t numsegs; +#endif seg_t* segs; -int numsectors; +cardinal_t numsectors; sector_t* sectors; +#if LOAD_COMPRESSED || SAVE_COMPRESSED +whdsector_t *whd_sectors; +#endif -int numsubsectors; + +cardinal_t numsubsectors; subsector_t* subsectors; -int numnodes; +cardinal_t numnodes; node_t* nodes; -int numlines; +cardinal_t numlines; +#if WHD_SUPER_TINY +// this is size of line data / 5... used for line bitmap ... we divide +// the "line index" byte offset by 5 to get unique bitmap index (it wastes about 4% above numlines) +cardinal_t numlines5; +#endif line_t* lines; -int numsides; + +#if USE_WHD +uint8_t num_switched_sides = 0; +#if WHD_SUPER_TINY +const side_t* sides_z; +uint16_t whd_sidemul; +uint16_t whd_vmul; +#else +const side_t* sides; +cardinal_t numsides; +#endif +#else +cardinal_t numsides; side_t* sides; +#endif + +#if !USE_INDEX_LINEBUFFER +line_t** linebuffer; +#else +cardinal_t * linebuffer; +#endif static int totallines; @@ -79,16 +114,20 @@ static int totallines; // by spatial subdivision in 2D. // // Blockmap size. -int bmapwidth; -int bmapheight; // size in mapblocks -short* blockmap; // int for larger maps +cardinal_t bmapwidth; +cardinal_t bmapheight; // size in mapblocks +#if !USE_WHD +rowad_const short* blockmap; // int for larger maps +#else +byte *blockmap_whd; +#endif // offsets in blockmap are from here -short* blockmaplump; +rowad_const short* blockmaplump; // origin of block map fixed_t bmaporgx; fixed_t bmaporgy; // for thing chains -mobj_t** blocklinks; +shortptr_t /*mobj_t**/* blocklinks; // REJECT @@ -98,7 +137,7 @@ mobj_t** blocklinks; // Without special effect, this could be // used as a PVS lookup as well. // -byte* rejectmatrix; +should_be_const byte* rejectmatrix; // Maintain single and multi player starting spots. @@ -118,23 +157,33 @@ boolean playerstartsingame[MAXPLAYERS]; // void P_LoadVertexes (int lump) { - byte* data; int i; mapvertex_t* ml; vertex_t* li; + should_be_const byte* data; // Determine number of lumps: // total lump length / vertex record length. numvertexes = W_LumpLength (lump) / sizeof(mapvertex_t); +#if USE_RAW_MAPVERTEX + static_assert(sizeof(mapvertex_t) == sizeof(vertex_t), ""); + static_assert(offsetof(mapvertex_t, y) == offsetof(vertex_t, y), ""); + data = W_CacheLumpNum (lump,PU_LEVEL); + vertexes = (vertex_t *)data; +#if PRINT_LEVEL_SIZE + printf("VERTEX LOAD map %d vertexes x 0x%03x : size = %08x\n", numvertexes, (int)sizeof(mapvertex_t ), numvertexes*(int)sizeof(mapvertex_t)); +#endif +#else // Allocate zone memory for buffer. - vertexes = Z_Malloc (numvertexes*sizeof(vertex_t),PU_LEVEL,0); + vertexes = Z_Malloc (numvertexes*sizeof(vertex_t),PU_LEVEL,0); + printf("VERTEX SIZE %d\n", (int)(numvertexes*sizeof(vertex_t))); // Load data into cache. data = W_CacheLumpNum (lump, PU_STATIC); - + ml = (mapvertex_t *)data; - li = vertexes; + li = (vertex_t *)vertexes; // Copy and convert vertex coordinates, // internal representation as fixed. @@ -146,6 +195,7 @@ void P_LoadVertexes (int lump) // Free buffer memory. W_ReleaseLumpNum(lump); +#endif } // @@ -159,8 +209,8 @@ sector_t* GetSectorAtNullAddress(void) if (!null_sector_is_initialized) { memset(&null_sector, 0, sizeof(null_sector)); - I_GetMemoryValue(0, &null_sector.floorheight, 4); - I_GetMemoryValue(4, &null_sector.ceilingheight, 4); + I_GetMemoryValue(0, &null_sector.rawfloorheight, sizeof(null_sector.rawfloorheight)); + I_GetMemoryValue(null_sector.rawfloorheight, &null_sector.rawceilingheight, null_sector.rawceilingheight); null_sector_is_initialized = true; } @@ -172,32 +222,52 @@ sector_t* GetSectorAtNullAddress(void) // void P_LoadSegs (int lump) { - byte* data; + should_be_const byte* data; int i; mapseg_t* ml; - seg_t* li; + seg_t* si; line_t* ldef; int linedef; int side; int sidenum; +#if USE_RAW_MAPSEG + data = W_CacheLumpNum (lump,PU_LEVEL); +#if !WHD_SUPER_TINY numsegs = W_LumpLength (lump) / sizeof(mapseg_t); - segs = Z_Malloc (numsegs*sizeof(seg_t),PU_LEVEL,0); - memset (segs, 0, numsegs*sizeof(seg_t)); +#if PRINT_LEVEL_SIZE + printf("SEG LOAD map %d segs x 0x%03x : size = %08x\n", numsegs, (int)sizeof(mapseg_t), numsegs*(int)sizeof(mapseg_t)); +#endif + static_assert(sizeof(seg_t) == sizeof(mapseg_t), ""); +#else +#if PRINT_LEVEL_SIZE + printf("SEG LOAD map segs : size = %08x\n", W_LumpLength(lump)); +#endif +#endif + segs = (seg_t *)data; + +#else + numsegs = W_LumpLength (lump) / sizeof(mapseg_t); +#if PRINT_LEVEL_SIZE + printf("SEG LOAD alloc %d segs x 0x%03x : size = %08x\n", numsegs, (int)sizeof(seg_t), numsegs*(int)sizeof(seg_t)); +#endif + segs = Z_Malloc (numsegs*sizeof(seg_t),PU_LEVEL,0); + // todo rowad + si = (seg_t *)segs; + memset (si, 0, numsegs*sizeof(seg_t)); data = W_CacheLumpNum (lump,PU_STATIC); ml = (mapseg_t *)data; - li = segs; - for (i=0 ; iv1 = &vertexes[SHORT(ml->v1)]; - li->v2 = &vertexes[SHORT(ml->v2)]; + si->v1 = &vertexes[SHORT(ml->v1)]; + si->v2 = &vertexes[SHORT(ml->v2)]; - li->angle = (SHORT(ml->angle))<offset = (SHORT(ml->offset))<angle = (SHORT(ml->angle)) << FRACBITS; + si->offset = (SHORT(ml->offset)) << FRACBITS; linedef = SHORT(ml->linedef); ldef = &lines[linedef]; - li->linedef = ldef; + si->linedef = ldef; side = SHORT(ml->side); // e6y: check for wrong indexes @@ -207,8 +277,8 @@ void P_LoadSegs (int lump) linedef, i, (unsigned)ldef->sidenum[side]); } - li->sidedef = &sides[ldef->sidenum[side]]; - li->frontsector = sides[ldef->sidenum[side]].sector; + si->sidedef = &sides[ldef->sidenum[side]]; + si->frontsector = sides[ldef->sidenum[side]].sector; if (ldef-> flags & ML_TWOSIDED) { @@ -222,20 +292,21 @@ void P_LoadSegs (int lump) if (sidenum < 0 || sidenum >= numsides) { - li->backsector = GetSectorAtNullAddress(); + si->backsector = GetSectorAtNullAddress(); } else { - li->backsector = sides[sidenum].sector; + si->backsector = sides[sidenum].sector; } } else { - li->backsector = 0; + si->backsector = 0; } } W_ReleaseLumpNum(lump); +#endif } @@ -244,13 +315,25 @@ void P_LoadSegs (int lump) // void P_LoadSubsectors (int lump) { - byte* data; + should_be_const byte* data; int i; mapsubsector_t* ms; subsector_t* ss; +#if USE_WHD + static_assert(sizeof(subsector_t) == 2, ""); + numsubsectors = W_LumpLength (lump) / 2; +#if PRINT_LEVEL_SIZE + printf("SUBSECTOR LOAD map %d subsectors x 0x%03x : size = %08x\n", numsubsectors, (int)sizeof(subsector_t), numsubsectors*(int)sizeof(subsector_t)); +#endif + data = W_CacheLumpNum (lump,PU_LEVEL); + subsectors = (subsector_t *)data; +#else numsubsectors = W_LumpLength (lump) / sizeof(mapsubsector_t); - subsectors = Z_Malloc (numsubsectors*sizeof(subsector_t),PU_LEVEL,0); +#if PRINT_LEVEL_SIZE + printf("SUBSECTOR LOAD alloc %d subsectors x 0x%03x : size = %08x\n", numsubsectors, (int)sizeof(subsector_t), numsubsectors*(int)sizeof(subsector_t)); +#endif + subsectors = Z_Malloc (numsubsectors*sizeof(subsector_t),PU_LEVEL,0); data = W_CacheLumpNum (lump,PU_STATIC); ms = (mapsubsector_t *)data; @@ -264,6 +347,7 @@ void P_LoadSubsectors (int lump) } W_ReleaseLumpNum(lump); +#endif } @@ -273,29 +357,56 @@ void P_LoadSubsectors (int lump) // void P_LoadSectors (int lump) { - byte* data; + should_be_const byte* data; int i; - mapsector_t* ms; sector_t* ss; - - numsectors = W_LumpLength (lump) / sizeof(mapsector_t); - sectors = Z_Malloc (numsectors*sizeof(sector_t),PU_LEVEL,0); - memset (sectors, 0, numsectors*sizeof(sector_t)); + data = W_CacheLumpNum (lump,PU_STATIC); - - ms = (mapsector_t *)data; + +#if !USE_WHD + numsectors = W_LumpLength (lump) / sizeof(mapsector_t); +#if PRINT_LEVEL_SIZE + printf("SECTOR LOAD alloc %d sectors x 0x%03x : size = %08x\n", numsectors, (int)sizeof(sector_t), numsectors*(int)sizeof(sector_t)); +#endif + sectors = Z_Malloc (numsectors*sizeof(sector_t),PU_LEVEL,0); + memset (sectors, 0, numsectors*sizeof(sector_t)); + mapsector_t *ms = (mapsector_t *)data; ss = sectors; for (i=0 ; ifloorheight = SHORT(ms->floorheight)<ceilingheight = SHORT(ms->ceilingheight)<floorheight)<ceilingheight)<floorpic = R_FlatNumForName(ms->floorpic); ss->ceilingpic = R_FlatNumForName(ms->ceilingpic); ss->lightlevel = SHORT(ms->lightlevel); ss->special = SHORT(ms->special); ss->tag = SHORT(ms->tag); - ss->thinglist = NULL; + ss->thinglist = 0; } +#else + numsectors = W_LumpLength (lump) / sizeof(whdsector_t); +#if PRINT_LEVEL_SIZE + printf("SECTOR LOAD alloc %d sectors x 0x%03x : size = %08x\n", numsectors, (int)sizeof(sector_t), numsectors*(int)sizeof(sector_t)); +#endif + sectors = Z_Malloc (numsectors*sizeof(sector_t),PU_LEVEL,0); + memset (sectors, 0, numsectors*sizeof(sector_t)); + whdsector_t *whss = (whdsector_t *)data; +#if !NO_USE_SAVE + whd_sectors = whss; +#endif + ss = sectors; + for (i=0 ; ifloorheight)<ceilingheight)<floorpic = whss->floorpic; + ss->ceilingpic = whss->ceilingpic; + ss->lightlevel = whss->lightlevel; + ss->special = whss->special; + ss->tag = whss->tag; + ss->thinglist = 0; + } +#endif W_ReleaseLumpNum(lump); } @@ -306,35 +417,45 @@ void P_LoadSectors (int lump) // void P_LoadNodes (int lump) { - byte* data; + should_be_const byte* data; int i; int j; int k; mapnode_t* mn; node_t* no; - + +#if !WHD_SUPER_TINY numnodes = W_LumpLength (lump) / sizeof(mapnode_t); - nodes = Z_Malloc (numnodes*sizeof(node_t),PU_LEVEL,0); +#else + numnodes = W_LumpLength (lump) / sizeof(whdnode_t); +#endif + +#if USE_RAW_MAPNODE + data = W_CacheLumpNum (lump,PU_LEVEL); + nodes = (node_t *)data; +#else data = W_CacheLumpNum (lump,PU_STATIC); - mn = (mapnode_t *)data; - no = nodes; + // todo rowad + nodes = Z_Malloc (numnodes*sizeof(node_t),PU_LEVEL,0); + no = (node_t *)nodes; for (i=0 ; ix = SHORT(mn->x)<y = SHORT(mn->y)<dx = SHORT(mn->dx)<dy = SHORT(mn->dy)<x = int16_to_node_coord(SHORT(mn->x)); + no->y = int16_to_node_coord(SHORT(mn->y)); + no->dx = int16_to_node_coord(mn->dx); + no->dy = int16_to_node_coord(mn->dy); for (j=0 ; j<2 ; j++) { no->children[j] = SHORT(mn->children[j]); for (k=0 ; k<4 ; k++) - no->bbox[j][k] = SHORT(mn->bbox[j][k])<bbox[j][k] = int16_to_node_coord(mn->bbox[j][k]); } } - + W_ReleaseLumpNum(lump); +#endif } @@ -343,17 +464,21 @@ void P_LoadNodes (int lump) // void P_LoadThings (int lump) { - byte *data; + should_be_const byte *data; int i; - mapthing_t *mt; + const mapthing_t *mt; mapthing_t spawnthing; int numthings; boolean spawn; data = W_CacheLumpNum (lump,PU_STATIC); numthings = W_LumpLength (lump) / sizeof(mapthing_t); - + mt = (mapthing_t *)data; +#if USE_RAW_MAPTHING + mapthings = mt; + nummapthings = numthings; +#endif for (i=0 ; ix); spawnthing.y = SHORT(mt->y); spawnthing.angle = SHORT(mt->angle); spawnthing.type = SHORT(mt->type); spawnthing.options = SHORT(mt->options); - - P_SpawnMapThing(&spawnthing); +#endif + +#if !SHRINK_MOBJ + P_SpawnMapThing(spawnthing); +#else + P_SpawnMapThing(i); +#endif + } if (!deathmatch) @@ -402,7 +534,9 @@ void P_LoadThings (int lump) } } +#if !USE_RAW_MAPTHING W_ReleaseLumpNum(lump); +#endif } @@ -412,20 +546,53 @@ void P_LoadThings (int lump) // void P_LoadLineDefs (int lump) { - byte* data; + should_be_const byte* data; int i; maplinedef_t* mld; line_t* ld; - vertex_t* v1; - vertex_t* v2; + const vertex_t* v1; + const vertex_t* v2; +#if USE_RAW_MAPLINEDEF + data = W_CacheLumpNum (lump,PU_LEVEL); +#if !WHD_SUPER_TINY numlines = W_LumpLength (lump) / sizeof(maplinedef_t); - lines = Z_Malloc (numlines*sizeof(line_t),PU_LEVEL,0); - memset (lines, 0, numlines*sizeof(line_t)); + static_assert(sizeof(line_t) == sizeof(maplinedef_t), ""); +#if PRINT_LEVEL_SIZE + printf("LINE LOAD map %d linedefs x 0x%03x : size = %08x\n", numlines, (int)sizeof(maplinedef_t), numlines*(int)sizeof(maplinedef_t)); +#endif + lines = (line_t *) data; + unsigned int bitmap_size = (numlines + 31) / 8; +#else + numlines = ((uint16_t *)data)[0]; + whd_sidemul = ((uint16_t *)data)[1]; + whd_vmul = ((uint16_t *)data)[2]; + lines = (line_t *) (data + 6); + numlines5 = line_bitmap_index(W_LumpLength(lump) - 6); +#if PRINT_LEVEL_SIZE + printf("LINE LOAD map %d linedefs : size = %08x\n", numlines, W_LumpLength(lump)); +#endif + unsigned int bitmap_size = (numlines5 + 31) / 8; +#endif + line_sector_check_bitmap = Z_Malloc(bitmap_size, PU_LEVEL,0); + assert(numlines >= numsectors); // would seem to make sense + line_mapped_bitmap = Z_Malloc(bitmap_size, PU_LEVEL,0); + memset(line_mapped_bitmap, 0, bitmap_size); + line_special_cleared_bitmap = Z_Malloc(bitmap_size, PU_LEVEL,0); + memset(line_special_cleared_bitmap, 0, bitmap_size); +#else + numlines = W_LumpLength (lump) / sizeof(maplinedef_t); +#if PRINT_LEVEL_SIZE + printf("LINE LOAD alloc %d linedefs x 0x%03x : size = %08x\n", numlines, (int)sizeof(line_t), numlines*(int)sizeof(line_t)); +#endif + lines = Z_Malloc (numlines*sizeof(line_t),PU_LEVEL,0); + // todo rowad + memset ((line_t *)lines, 0, numlines*sizeof(line_t)); data = W_CacheLumpNum (lump,PU_STATIC); mld = (maplinedef_t *)data; - ld = lines; + // todo rowad + ld = (line_t*)lines; for (i=0 ; iflags = SHORT(mld->flags); @@ -433,8 +600,8 @@ void P_LoadLineDefs (int lump) ld->tag = SHORT(mld->tag); v1 = ld->v1 = &vertexes[SHORT(mld->v1)]; v2 = ld->v2 = &vertexes[SHORT(mld->v2)]; - ld->dx = v2->x - v1->x; - ld->dy = v2->y - v1->y; + ld->dx = vertex_x(v2) - vertex_x(v1); + ld->dy = vertex_y(v2) - vertex_y(v1); if (!ld->dx) ld->slopetype = ST_VERTICAL; @@ -448,26 +615,26 @@ void P_LoadLineDefs (int lump) ld->slopetype = ST_NEGATIVE; } - if (v1->x < v2->x) + if (vertex_x(v1) < vertex_x(v2)) { - ld->bbox[BOXLEFT] = v1->x; - ld->bbox[BOXRIGHT] = v2->x; + ld->bbox[BOXLEFT] = vertex_x(v1); + ld->bbox[BOXRIGHT] = vertex_x(v2); } else { - ld->bbox[BOXLEFT] = v2->x; - ld->bbox[BOXRIGHT] = v1->x; + ld->bbox[BOXLEFT] = vertex_x(v2); + ld->bbox[BOXRIGHT] = vertex_x(v1); } - if (v1->y < v2->y) + if (vertex_y(v1) < vertex_y(v2)) { - ld->bbox[BOXBOTTOM] = v1->y; - ld->bbox[BOXTOP] = v2->y; + ld->bbox[BOXBOTTOM] = vertex_y(v1); + ld->bbox[BOXTOP] = vertex_y(v2); } else { - ld->bbox[BOXBOTTOM] = v2->y; - ld->bbox[BOXTOP] = v1->y; + ld->bbox[BOXBOTTOM] = vertex_y(v2); + ld->bbox[BOXTOP] = vertex_y(v1); } ld->sidenum[0] = SHORT(mld->sidenum[0]); @@ -485,21 +652,40 @@ void P_LoadLineDefs (int lump) } W_ReleaseLumpNum(lump); +#endif } - // // P_LoadSideDefs // void P_LoadSideDefs (int lump) { - byte* data; +#if USE_WHD + num_switched_sides = 0; +#if WHD_SUPER_TINY +#if PRINT_LEVEL_SIZE + printf("SIDEDEF LOAD map ? sides size = %08x\n", W_LumpLength(lump)); +#endif + sides_z = (const side_t *) W_CacheLumpNum (lump,PU_LEVEL); +#else + static_assert(sizeof(side_t)==12, ""); + numsides = W_LumpLength (lump) / sizeof(side_t); // todo should really be packed? +#if PRINT_LEVEL_SIZE + printf("SIDEDEF LOAD map %d sides x 0x%03x : size = %08x\n", numsides, (int)sizeof(side_t), numsides * (int)sizeof(side_t)); +#endif + sides = (const side_t *) W_CacheLumpNum (lump,PU_LEVEL); +#endif +#else + should_be_const byte* data; int i; mapsidedef_t* msd; side_t* sd; numsides = W_LumpLength (lump) / sizeof(mapsidedef_t); - sides = Z_Malloc (numsides*sizeof(side_t),PU_LEVEL,0); +#if PRINT_LEVEL_SIZE + printf("SIDEDEF ** HAS NAMES ** LOAD alloc %d sides x 0x%03x : size = %08x\n", numsides, (int)sizeof(side_t), numsides * (int)sizeof(side_t)); +#endif + sides = Z_Malloc (numsides*sizeof(side_t),PU_LEVEL,0); memset (sides, 0, numsides*sizeof(side_t)); data = W_CacheLumpNum (lump,PU_STATIC); @@ -507,15 +693,16 @@ void P_LoadSideDefs (int lump) sd = sides; for (i=0 ; itextureoffset = SHORT(msd->textureoffset)<rowoffset = SHORT(msd->rowoffset)<toptexture = R_TextureNumForName(msd->toptexture); - sd->bottomtexture = R_TextureNumForName(msd->bottomtexture); - sd->midtexture = R_TextureNumForName(msd->midtexture); - sd->sector = §ors[SHORT(msd->sector)]; + side_settextureoffset16(sd, SHORT(msd->textureoffset)); + side_setrowoffset16(sd, SHORT(msd->rowoffset)); + side_settoptexture(sd, R_TextureNumForName(msd->toptexture)); + side_setbottomtexture(sd, R_TextureNumForName(msd->bottomtexture)); + side_setmidtexture(sd, R_TextureNumForName(msd->midtexture)); + sd->sector = §ors[SHORT(msd->sector)]; } W_ReleaseLumpNum(lump); +#endif } @@ -526,32 +713,59 @@ void P_LoadBlockMap (int lump) { int i; int count; - int lumplen; - lumplen = W_LumpLength(lump); - count = lumplen / 2; - - blockmaplump = Z_Malloc(lumplen, PU_LEVEL, NULL); - W_ReadLump(lump, blockmaplump); - blockmap = blockmaplump + 4; // Swap all short integers to native byte ordering. - + +#if !USE_ROWAD + int lumplen = W_LumpLength(lump); + count = lumplen / 2; + blockmaplump = Z_Malloc(lumplen, PU_LEVEL, 0); + W_ReadLump(lump, blockmaplump); + for (i=0; ifirstline]; - ss->sector = seg->sidedef->sector; + ss->sector = seg_sidedef(seg)->sector; } +#endif // count number of lines in each sector li = lines; totallines = 0; - for (i=0 ; ifrontsector->linecount++; + for (i = 0; i < numlines; i++) { + totallines++; + line_frontsector(li)->linecount++; - if (li->backsector && li->backsector != li->frontsector) - { - li->backsector->linecount++; - totallines++; - } + if (line_backsector(li) && line_backsector(li) != line_frontsector(li)) { + line_backsector(li)->linecount++; + totallines++; + } + li += line_next_step(li); } - // build line tables for each sector + // build line tables for each sector +#if !USE_INDEX_LINEBUFFER +#if PRINT_LEVEL_SIZE + printf("LINEBUFFER alloc %d total lines x 0x%03x : size = %08x\n", totallines, (int)sizeof(line_t *), totallines*(int)sizeof(line_t *)); +#endif linebuffer = Z_Malloc (totallines*sizeof(line_t *), PU_LEVEL, 0); +#else +#if PRINT_LEVEL_SIZE + printf("LINEBUFFER alloc %d total lines x 0x%03x : size = %08x\n", totallines, (int)sizeof(short), totallines*(int)sizeof(short)); +#endif + linebuffer = Z_Malloc (totallines*sizeof(cardinal_t), PU_LEVEL, 0); +#endif +#if USE_INDEX_LINEBUFFER + totallines=0; +#endif for (i=0; ifrontsector != NULL) + if (line_frontsector(li) != NULL) { - sector = li->frontsector; + sector = line_frontsector(li); +#if !USE_INDEX_LINEBUFFER sector->lines[sector->linecount] = li; +#else + linebuffer[sector->line_index + sector->linecount] = li - lines; +#endif ++sector->linecount; } - if (li->backsector != NULL && li->frontsector != li->backsector) + if (line_backsector(li) != NULL && line_frontsector(li) != line_backsector(li)) { - sector = li->backsector; + sector = line_backsector(li); +#if !USE_INDEX_LINEBUFFER sector->lines[sector->linecount] = li; +#else + linebuffer[sector->line_index + sector->linecount] = li - lines; +#endif ++sector->linecount; } + li += line_next_step(li); } // Generate bounding boxes for sectors @@ -646,10 +885,10 @@ void P_GroupLines (void) for (j=0 ; jlinecount; j++) { - li = sector->lines[j]; + li = sector_line(sector, j); - M_AddToBox (bbox, li->v1->x, li->v1->y); - M_AddToBox (bbox, li->v2->x, li->v2->y); + M_AddToBox (bbox, vertex_x(line_v1(li)), vertex_y(line_v1(li))); + M_AddToBox (bbox, vertex_x(line_v2(li)), vertex_y(line_v2(li))); } // set the degenmobj_t to the middle of the bounding box @@ -714,7 +953,7 @@ static void PadRejectArray(byte *array, unsigned int len) if (len > sizeof(rejectpad)) { - fprintf(stderr, "PadRejectArray: REJECT lump too short to pad! (%i > %i)\n", + stderr_print("PadRejectArray: REJECT lump too short to pad! (%i > %i)\n", len, (int) sizeof(rejectpad)); // Pad remaining space with 0 (or 0xff, if specified on command line). @@ -753,15 +992,21 @@ static void P_LoadReject(int lumpnum) } else { - rejectmatrix = Z_Malloc(minlength, PU_LEVEL, &rejectmatrix); - W_ReadLump(lumpnum, rejectmatrix); +#if !USE_WHD + byte* tmp = Z_Malloc(minlength, PU_LEVEL, &rejectmatrix); + W_ReadLump(lumpnum, tmp); - PadRejectArray(rejectmatrix + lumplen, minlength - lumplen); + PadRejectArray(tmp + lumplen, minlength - lumplen); + rejectmatrix = tmp; +#else + // we don't care about invalid reject lumps (laos we don't handle z_malloc user ptr + assert(false); +#endif } } // pointer to the current map lump info struct -lumpinfo_t *maplumpinfo; +should_be_const lumpinfo_t *maplumpinfo; // // P_SetupLevel @@ -776,6 +1021,10 @@ P_SetupLevel int i; char lumpname[9]; int lumpnum; + +#if PICO_DOOM_INFO + printf("SETUP LEVEL E%dM%d\n", episode, map); +#endif totalkills = totalitems = totalsecret = wminfo.maxfrags = 0; wminfo.partime = 180; @@ -797,8 +1046,10 @@ P_SetupLevel // UNUSED W_Profile (); P_InitThinkers (); +#if !NO_USE_RELOAD // if working with a devlopment map, reload it W_Reload (); +#endif // find map name if ( gamemode == commercial) @@ -819,11 +1070,11 @@ P_SetupLevel lumpnum = W_GetNumForName (lumpname); - maplumpinfo = lumpinfo[lumpnum]; + maplumpinfo = lump_info(lumpnum); leveltime = 0; - - // note: most of this ordering is important + + // note: most of this ordering is important P_LoadBlockMap (lumpnum+ML_BLOCKMAP); P_LoadVertexes (lumpnum+ML_VERTEXES); P_LoadSectors (lumpnum+ML_SECTORS); @@ -879,7 +1130,7 @@ void P_Init (void) { P_InitSwitchList (); P_InitPicAnims (); - R_InitSprites (sprnames); + R_InitSprites (); } diff --git a/src/doom/p_setup.h b/src/doom/p_setup.h index 97ca104e..9858ca46 100644 --- a/src/doom/p_setup.h +++ b/src/doom/p_setup.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -22,8 +23,7 @@ #include "w_wad.h" - -extern lumpinfo_t *maplumpinfo; +extern should_be_const lumpinfo_t *maplumpinfo; // NOT called by W_Ticker. Fixme. void diff --git a/src/doom/p_sight.c b/src/doom/p_sight.c index 48ba6838..3a98c86a 100644 --- a/src/doom/p_sight.c +++ b/src/doom/p_sight.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -48,7 +49,7 @@ int P_DivlineSide ( fixed_t x, fixed_t y, - divline_t* node ) + const divline_t* node ) { fixed_t dx; fixed_t dy; @@ -128,18 +129,18 @@ P_InterceptVector2 boolean P_CrossSubsector (int num) { seg_t* seg; + seg_t* seg_end; line_t* line; int s1; int s2; - int count; subsector_t* sub; sector_t* front; sector_t* back; fixed_t opentop; fixed_t openbottom; divline_t divl; - vertex_t* v1; - vertex_t* v2; + const vertex_t* v1; + const vertex_t* v2; fixed_t frac; fixed_t slope; @@ -153,32 +154,30 @@ boolean P_CrossSubsector (int num) sub = &subsectors[num]; // check lines - count = sub->numlines; - seg = &segs[sub->firstline]; + seg_end = subsector_linelimit(sub); + seg = subsector_firstline(sub); - for ( ; count ; seg++, count--) + for ( ; seg != seg_end; seg += seg_next_step(seg)) { - line = seg->linedef; + line = seg_linedef(seg); // allready checked other side? - if (line->validcount == validcount) + if (line_validcount_update_check(line, validcount)) continue; - - line->validcount = validcount; - v1 = line->v1; - v2 = line->v2; - s1 = P_DivlineSide (v1->x,v1->y, &strace); - s2 = P_DivlineSide (v2->x, v2->y, &strace); + v1 = line_v1(line); + v2 = line_v2(line); + s1 = P_DivlineSide (vertex_x(v1), vertex_y(v1), &strace); + s2 = P_DivlineSide (vertex_x(v2), vertex_y(v2), &strace); // line isn't crossed? if (s1 == s2) continue; - divl.x = v1->x; - divl.y = v1->y; - divl.dx = v2->x - v1->x; - divl.dy = v2->y - v1->y; + divl.x = vertex_x(v1); + divl.y = vertex_y(v1); + divl.dx = vertex_x(v2) - vertex_x(v1); + divl.dy = vertex_y(v2) - vertex_y(v1); s1 = P_DivlineSide (strace.x, strace.y, &divl); s2 = P_DivlineSide (t2x, t2y, &divl); @@ -189,37 +188,37 @@ boolean P_CrossSubsector (int num) // Backsector may be NULL if this is an "impassible // glass" hack line. - if (line->backsector == NULL) + if (line_backsector(line) == NULL) { return false; } // stop because it is not two sided anyway // might do this after updating validcount? - if ( !(line->flags & ML_TWOSIDED) ) + if ( !(line_flags(line) & ML_TWOSIDED) ) return false; // crosses a two sided line - front = seg->frontsector; - back = seg->backsector; + front = seg_frontsector(seg); + back = seg_backsector(seg); // no wall to block sight with? - if (front->floorheight == back->floorheight - && front->ceilingheight == back->ceilingheight) + if (front->rawfloorheight == back->rawfloorheight + && front->rawceilingheight == back->rawceilingheight) continue; // possible occluder // because of ceiling height differences - if (front->ceilingheight < back->ceilingheight) - opentop = front->ceilingheight; + if (front->rawceilingheight < back->rawceilingheight) + opentop = sector_ceilingheight(front); else - opentop = back->ceilingheight; + opentop = sector_ceilingheight(back); // because of ceiling height differences - if (front->floorheight > back->floorheight) - openbottom = front->floorheight; + if (front->rawfloorheight > back->rawfloorheight) + openbottom = sector_floorheight(front); else - openbottom = back->floorheight; + openbottom = sector_floorheight(back); // quick test for totally closed doors if (openbottom >= opentop) @@ -227,14 +226,14 @@ boolean P_CrossSubsector (int num) frac = P_InterceptVector2 (&strace, &divl); - if (front->floorheight != back->floorheight) + if (front->rawfloorheight != back->rawfloorheight) { slope = FixedDiv (openbottom - sightzstart , frac); if (slope > bottomslope) bottomslope = slope; } - if (front->ceilingheight != back->ceilingheight) + if (front->rawceilingheight != back->rawceilingheight) { slope = FixedDiv (opentop - sightzstart , frac); if (slope < topslope) @@ -271,23 +270,33 @@ boolean P_CrossBSPNode (int bspnum) bsp = &nodes[bspnum]; // decide which side the start point is on +#if USE_RAW_MAPNODE + // these functions seem identical anyway + side = R_PointOnSide(strace.x, strace.y, bsp); +#else side = P_DivlineSide (strace.x, strace.y, (divline_t *)bsp); +#endif if (side == 2) side = 0; // an "on" should cross both sides // cross the starting side - if (!P_CrossBSPNode (bsp->children[side]) ) + if (!P_CrossBSPNode (bsp_child(bspnum, side)) ) return false; // the partition plane is crossed here +#if USE_RAW_MAPNODE + // these functions seem identical anyway + if (side == R_PointOnSide(t2x, t2y, bsp)) +#else if (side == P_DivlineSide (t2x, t2y,(divline_t *)bsp)) +#endif { // the line doesn't touch the other side return true; } // cross the ending side - return P_CrossBSPNode (bsp->children[side^1]); + return P_CrossBSPNode (bsp_child(bspnum, side^1)); } @@ -311,8 +320,8 @@ P_CheckSight // First check for trivial rejection. // Determine subsector entries in REJECT table. - s1 = (t1->subsector->sector - sectors); - s2 = (t2->subsector->sector - sectors); + s1 = (mobj_sector(t1) - sectors); + s2 = (mobj_sector(t2) - sectors); pnum = s1*numsectors + s2; bytenum = pnum>>3; bitnum = 1 << (pnum&7); @@ -330,18 +339,19 @@ P_CheckSight // Now look from eyes of t1 to any part of t2. sightcounts[1]++; + line_check_reset(); validcount++; - sightzstart = t1->z + t1->height - (t1->height>>2); - topslope = (t2->z+t2->height) - sightzstart; + sightzstart = t1->z + mobj_height(t1) - (mobj_height(t1)>>2); + topslope = (t2->z+mobj_height(t2)) - sightzstart; bottomslope = (t2->z) - sightzstart; - strace.x = t1->x; - strace.y = t1->y; - t2x = t2->x; - t2y = t2->y; - strace.dx = t2->x - t1->x; - strace.dy = t2->y - t1->y; + strace.x = t1->xy.x; + strace.y = t1->xy.y; + t2x = t2->xy.x; + t2y = t2->xy.y; + strace.dx = t2->xy.x - t1->xy.x; + strace.dy = t2->xy.y - t1->xy.y; // the head node is the last node output return P_CrossBSPNode (numnodes-1); diff --git a/src/doom/p_spec.c b/src/doom/p_spec.c index b5c8fa7e..c1284563 100644 --- a/src/doom/p_spec.c +++ b/src/doom/p_spec.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -48,6 +49,7 @@ #include "sounds.h" +#if !USE_WHD // whd just using an integer for the equivalent of picnum // // Animating textures and planes // There is another anim_t used in wi_stuff, unrelated. @@ -61,24 +63,21 @@ typedef struct int speed; } anim_t; +#endif // // source animation definition // -typedef struct +typedef PACKED_STRUCT( { - int istexture; // if false, it is a flat - char endname[9]; - char startname[9]; - int speed; -} animdef_t; + textureorflatname_def_t endname; + textureorflatname_def_t startname; + uint8_t istexture; // if false, it is a flat + uint8_t speed; +}) animdef_t; - - -#define MAXANIMS 32 - -extern anim_t anims[MAXANIMS]; -extern anim_t* lastanim; +#define FLATANIM_DEF(last,first,speed) { FLAT_NAME(last), FLAT_NAME(first), 0, speed } +#define TEXANIM_DEF(last,first,speed) { TEXTURE_NAME(last), TEXTURE_NAME(first), 1, speed } // // P_InitPicAnims @@ -93,64 +92,57 @@ extern anim_t* lastanim; // and end entry, in the order found in // the WAD file. // -animdef_t animdefs[] = +const animdef_t animdefs[] = { - {false, "NUKAGE3", "NUKAGE1", 8}, - {false, "FWATER4", "FWATER1", 8}, - {false, "SWATER4", "SWATER1", 8}, - {false, "LAVA4", "LAVA1", 8}, - {false, "BLOOD3", "BLOOD1", 8}, + FLATANIM_DEF(NUKAGE3, NUKAGE1, 8), + FLATANIM_DEF(FWATER4, FWATER1, 8), + FLATANIM_DEF(SWATER4, SWATER1, 8), + FLATANIM_DEF(LAVA4, LAVA1, 8), + FLATANIM_DEF(BLOOD3, BLOOD1, 8), +#if !DEMO1_ONLY // DOOM II flat animations. - {false, "RROCK08", "RROCK05", 8}, - {false, "SLIME04", "SLIME01", 8}, - {false, "SLIME08", "SLIME05", 8}, - {false, "SLIME12", "SLIME09", 8}, + FLATANIM_DEF(RROCK08, RROCK05, 8), + FLATANIM_DEF(SLIME04, SLIME01, 8), + FLATANIM_DEF(SLIME08, SLIME05, 8), + FLATANIM_DEF(SLIME12, SLIME09, 8), +#endif - {true, "BLODGR4", "BLODGR1", 8}, - {true, "SLADRIP3", "SLADRIP1", 8}, + TEXANIM_DEF(SLADRIP3, SLADRIP1, 8), - {true, "BLODRIP4", "BLODRIP1", 8}, - {true, "FIREWALL", "FIREWALA", 8}, - {true, "GSTFONT3", "GSTFONT1", 8}, - {true, "FIRELAVA", "FIRELAV3", 8}, - {true, "FIREMAG3", "FIREMAG1", 8}, - {true, "FIREBLU2", "FIREBLU1", 8}, - {true, "ROCKRED3", "ROCKRED1", 8}, +#if !DEMO1_ONLY + TEXANIM_DEF(BLODGR4, BLODGR1, 8), + TEXANIM_DEF(BLODRIP4, BLODRIP1, 8), + TEXANIM_DEF(FIREWALL, FIREWALA, 8), + TEXANIM_DEF(GSTFONT3, GSTFONT1, 8), + TEXANIM_DEF(FIRELAVA, FIRELAV3, 8), + TEXANIM_DEF(FIREMAG3, FIREMAG1, 8), + TEXANIM_DEF(FIREBLU2, FIREBLU1, 8), + TEXANIM_DEF(ROCKRED3, ROCKRED1, 8), - {true, "BFALL4", "BFALL1", 8}, - {true, "SFALL4", "SFALL1", 8}, - {true, "WFALL4", "WFALL1", 8}, - {true, "DBRAIN4", "DBRAIN1", 8}, - - {-1, "", "", 0}, + TEXANIM_DEF(BFALL4, BFALL1, 8), + TEXANIM_DEF(SFALL4, SFALL1, 8), + TEXANIM_DEF(WFALL4, WFALL1, 8), + TEXANIM_DEF(DBRAIN4, DBRAIN1, 8), +#endif }; -anim_t anims[MAXANIMS]; +#if !USE_WHD +anim_t anims[count_of(animdefs)]; anim_t* lastanim; - - -// -// Animating line specials -// -#define MAXLINEANIMS 64 - -extern short numlinespecials; -extern line_t* linespeciallist[MAXLINEANIMS]; - +#endif void P_InitPicAnims (void) { +#if !USE_WHD int i; - // Init animation lastanim = anims; - for (i=0 ; animdefs[i].istexture != -1 ; i++) + for (i=0 ; i < count_of(animdefs); i++) { const char *startname, *endname; - startname = DEH_String(animdefs[i].startname); endname = DEH_String(animdefs[i].endname); @@ -182,7 +174,8 @@ void P_InitPicAnims (void) lastanim->speed = animdefs[i].speed; lastanim++; } - + +#endif } @@ -205,7 +198,7 @@ getSide int line, int side ) { - return &sides[ (sectors[currentSector].lines[line])->sidenum[side] ]; + return sidenum_to_side( line_sidenum(sector_line(§ors[currentSector], line), side)); } @@ -221,7 +214,7 @@ getSector int line, int side ) { - return sides[ (sectors[currentSector].lines[line])->sidenum[side] ].sector; + return side_sector(sidenum_to_side(line_sidenum(sector_line(§ors[currentSector], line), side) )); } @@ -235,7 +228,7 @@ twoSided ( int sector, int line ) { - return (sectors[sector].lines[line])->flags & ML_TWOSIDED; + return (line_flags(sector_line(§ors[sector], line))) & ML_TWOSIDED; } @@ -251,13 +244,13 @@ getNextSector ( line_t* line, sector_t* sec ) { - if (!(line->flags & ML_TWOSIDED)) + if (!(line_flags(line) & ML_TWOSIDED)) return NULL; - if (line->frontsector == sec) - return line->backsector; + if (line_frontsector(line) == sec) + return line_backsector(line); - return line->frontsector; + return line_frontsector(line); } @@ -271,18 +264,18 @@ fixed_t P_FindLowestFloorSurrounding(sector_t* sec) int i; line_t* check; sector_t* other; - fixed_t floor = sec->floorheight; + fixed_t floor = sector_floorheight(sec); for (i=0 ;i < sec->linecount ; i++) { - check = sec->lines[i]; + check = sector_line(sec, i); other = getNextSector(check,sec); if (!other) continue; - if (other->floorheight < floor) - floor = other->floorheight; + if (sector_floorheight(other) < floor) + floor = sector_floorheight(other); } return floor; } @@ -302,14 +295,14 @@ fixed_t P_FindHighestFloorSurrounding(sector_t *sec) for (i=0 ;i < sec->linecount ; i++) { - check = sec->lines[i]; + check = sector_line(sec, i); other = getNextSector(check,sec); if (!other) continue; - if (other->floorheight > floor) - floor = other->floorheight; + if (sector_floorheight(other) > floor) + floor = sector_floorheight(other); } return floor; } @@ -341,18 +334,18 @@ P_FindNextHighestFloor for (i=0, h=0; i < sec->linecount; i++) { - check = sec->lines[i]; + check = sector_line(sec, i); other = getNextSector(check,sec); if (!other) continue; - if (other->floorheight > height) + if (sector_floorheight(other) > height) { // Emulation of memory (stack) overflow if (h == MAX_ADJOINING_SECTORS + 1) { - height = other->floorheight; + height = sector_floorheight(other); } else if (h == MAX_ADJOINING_SECTORS + 2) { @@ -361,7 +354,7 @@ P_FindNextHighestFloor "Vanilla will crash here"); } - heightlist[h++] = other->floorheight; + heightlist[h++] = sector_floorheight(other); } } @@ -398,14 +391,14 @@ P_FindLowestCeilingSurrounding(sector_t* sec) for (i=0 ;i < sec->linecount ; i++) { - check = sec->lines[i]; + check = sector_line(sec, i); other = getNextSector(check,sec); if (!other) continue; - if (other->ceilingheight < height) - height = other->ceilingheight; + if (sector_ceilingheight(other) < height) + height = sector_ceilingheight(other); } return height; } @@ -423,14 +416,14 @@ fixed_t P_FindHighestCeilingSurrounding(sector_t* sec) for (i=0 ;i < sec->linecount ; i++) { - check = sec->lines[i]; + check = sector_line(sec, i); other = getNextSector(check,sec); if (!other) continue; - if (other->ceilingheight > height) - height = other->ceilingheight; + if (sector_ceilingheight(other) > height) + height = sector_ceilingheight(other); } return height; } @@ -446,9 +439,10 @@ P_FindSectorFromLineTag int start ) { int i; - + + int tag = line_tag(line); for (i=start+1;itag) + if (sectors[i].tag == tag) return i; return -1; @@ -473,7 +467,7 @@ P_FindMinSurroundingLight min = max; for (i=0 ; i < sector->linecount ; i++) { - line = sector->lines[i]; + line = sector_line(sector, i); check = getNextSector(line,sector); if (!check) @@ -510,7 +504,7 @@ P_CrossSpecialLine line = &lines[linenum]; // Triggers that other things can activate - if (!thing->player) + if (!mobj_full(thing)->sp_player) { // Things that should NOT trigger specials... switch(thing->type) @@ -528,7 +522,7 @@ P_CrossSpecialLine } ok = 0; - switch(line->special) + switch(line_special(line)) { case 39: // TELEPORT TRIGGER case 97: // TELEPORT RETRIGGER @@ -544,144 +538,144 @@ P_CrossSpecialLine return; } - + // Note: could use some const's here. - switch (line->special) + switch (line_special(line)) { // TRIGGERS. // All from here to RETRIGGERS. case 2: // Open Door EV_DoDoor(line,vld_open); - line->special = 0; + clear_line_special(line); break; case 3: // Close Door EV_DoDoor(line,vld_close); - line->special = 0; + clear_line_special(line); break; case 4: // Raise Door EV_DoDoor(line,vld_normal); - line->special = 0; + clear_line_special(line); break; case 5: // Raise Floor EV_DoFloor(line,raiseFloor); - line->special = 0; + clear_line_special(line); break; case 6: // Fast Ceiling Crush & Raise EV_DoCeiling(line,fastCrushAndRaise); - line->special = 0; + clear_line_special(line); break; case 8: // Build Stairs EV_BuildStairs(line,build8); - line->special = 0; + clear_line_special(line); break; case 10: // PlatDownWaitUp EV_DoPlat(line,downWaitUpStay,0); - line->special = 0; + clear_line_special(line); break; case 12: // Light Turn On - brightest near EV_LightTurnOn(line,0); - line->special = 0; + clear_line_special(line); break; case 13: // Light Turn On 255 EV_LightTurnOn(line,255); - line->special = 0; + clear_line_special(line); break; case 16: // Close Door 30 EV_DoDoor(line,vld_close30ThenOpen); - line->special = 0; + clear_line_special(line); break; case 17: // Start Light Strobing EV_StartLightStrobing(line); - line->special = 0; + clear_line_special(line); break; case 19: // Lower Floor EV_DoFloor(line,lowerFloor); - line->special = 0; + clear_line_special(line); break; case 22: // Raise floor to nearest height and change texture EV_DoPlat(line,raiseToNearestAndChange,0); - line->special = 0; + clear_line_special(line); break; case 25: // Ceiling Crush and Raise EV_DoCeiling(line,crushAndRaise); - line->special = 0; + clear_line_special(line); break; case 30: // Raise floor to shortest texture height // on either side of lines. EV_DoFloor(line,raiseToTexture); - line->special = 0; + clear_line_special(line); break; case 35: // Lights Very Dark EV_LightTurnOn(line,35); - line->special = 0; + clear_line_special(line); break; case 36: // Lower Floor (TURBO) EV_DoFloor(line,turboLower); - line->special = 0; + clear_line_special(line); break; case 37: // LowerAndChange EV_DoFloor(line,lowerAndChange); - line->special = 0; + clear_line_special(line); break; case 38: // Lower Floor To Lowest EV_DoFloor( line, lowerFloorToLowest ); - line->special = 0; + clear_line_special(line); break; case 39: // TELEPORT! EV_Teleport( line, side, thing ); - line->special = 0; + clear_line_special(line); break; case 40: // RaiseCeilingLowerFloor EV_DoCeiling( line, raiseToHighest ); EV_DoFloor( line, lowerFloorToLowest ); - line->special = 0; + clear_line_special(line); break; case 44: // Ceiling Crush EV_DoCeiling( line, lowerAndCrush ); - line->special = 0; + clear_line_special(line); break; case 52: @@ -692,79 +686,79 @@ P_CrossSpecialLine case 53: // Perpetual Platform Raise EV_DoPlat(line,perpetualRaise,0); - line->special = 0; + clear_line_special(line); break; case 54: // Platform Stop EV_StopPlat(line); - line->special = 0; + clear_line_special(line); break; case 56: // Raise Floor Crush EV_DoFloor(line,raiseFloorCrush); - line->special = 0; + clear_line_special(line); break; case 57: // Ceiling Crush Stop EV_CeilingCrushStop(line); - line->special = 0; + clear_line_special(line); break; case 58: // Raise Floor 24 EV_DoFloor(line,raiseFloor24); - line->special = 0; + clear_line_special(line); break; case 59: // Raise Floor 24 And Change EV_DoFloor(line,raiseFloor24AndChange); - line->special = 0; + clear_line_special(line); break; case 104: // Turn lights off in sector(tag) EV_TurnTagLightsOff(line); - line->special = 0; + clear_line_special(line); break; case 108: // Blazing Door Raise (faster than TURBO!) EV_DoDoor (line,vld_blazeRaise); - line->special = 0; + clear_line_special(line); break; case 109: // Blazing Door Open (faster than TURBO!) EV_DoDoor (line,vld_blazeOpen); - line->special = 0; + clear_line_special(line); break; case 100: // Build Stairs Turbo 16 EV_BuildStairs(line,turbo16); - line->special = 0; + clear_line_special(line); break; case 110: // Blazing Door Close (faster than TURBO!) EV_DoDoor (line,vld_blazeClose); - line->special = 0; + clear_line_special(line); break; case 119: // Raise floor to nearest surr. floor EV_DoFloor(line,raiseFloorToNearest); - line->special = 0; + clear_line_special(line); break; case 121: // Blazing PlatDownWaitUpStay EV_DoPlat(line,blazeDWUS,0); - line->special = 0; + clear_line_special(line); break; case 124: @@ -774,23 +768,23 @@ P_CrossSpecialLine case 125: // TELEPORT MonsterONLY - if (!thing->player) + if (!mobj_full(thing)->sp_player) { EV_Teleport( line, side, thing ); - line->special = 0; + clear_line_special(line); } break; case 130: // Raise Floor Turbo EV_DoFloor(line,raiseFloorTurbo); - line->special = 0; + clear_line_special(line); break; case 141: // Silent Ceiling Crush & Raise EV_DoCeiling(line,silentCrushAndRaise); - line->special = 0; + clear_line_special(line); break; // RETRIGGERS. All from here till end. @@ -943,7 +937,7 @@ P_CrossSpecialLine case 126: // TELEPORT MonsterONLY. - if (!thing->player) + if (!mobj_full(thing)->sp_player) EV_Teleport( line, side, thing ); break; @@ -973,10 +967,10 @@ P_ShootSpecialLine int ok; // Impacts that other things can activate. - if (!thing->player) + if (!mobj_full(thing)->sp_player) { ok = 0; - switch(line->special) + switch(line_special(line)) { case 46: // OPEN DOOR IMPACT @@ -987,7 +981,7 @@ P_ShootSpecialLine return; } - switch(line->special) + switch(line_special(line)) { case 24: // RAISE FLOOR @@ -1020,10 +1014,10 @@ void P_PlayerInSpecialSector (player_t* player) { sector_t* sector; - sector = player->mo->subsector->sector; + sector = mobj_sector(player->mo); // Falling, not all the way down yet? - if (player->mo->z != sector->floorheight) + if (player->mo->z != sector_floorheight(sector)) return; // Has hitten ground. @@ -1092,8 +1086,6 @@ int levelTimeCount; void P_UpdateSpecials (void) { - anim_t* anim; - int pic; int i; line_t* line; @@ -1105,33 +1097,55 @@ void P_UpdateSpecials (void) if (!levelTimeCount) G_ExitLevel(); } - + // ANIMATE FLATS AND TEXTURES GLOBALLY - for (anim = anims ; anim < lastanim ; anim++) +#if !USE_WHD + for (anim_t *anim = anims ; anim < lastanim ; anim++) { for (i=anim->basepic ; ibasepic+anim->numpics ; i++) { - pic = anim->basepic + ( (leveltime/anim->speed + i)%anim->numpics ); + int pic = anim->basepic + ( (leveltime/anim->speed + i)%anim->numpics ); if (anim->istexture) texturetranslation[i] = pic; else flattranslation[i] = pic; } } +#else + for (int a=0;astartname ; i<=anim->endname ; i++) + { + textureorflatname_t pic = anim->startname + ( (leveltime/anim->speed + i)%(anim->endname + 1 - anim->startname)); + if (anim->istexture) { + assert(i < count_of(whd_texturetranslation)); + whd_texturetranslation[i] = pic; + } else { + assert(i < count_of(whd_flattranslation)); + whd_flattranslation[i] = pic; + } + } + } +#endif - +#if !USE_WHD // ANIMATE LINE SPECIALS for (i = 0; i < numlinespecials; i++) { line = linespeciallist[i]; - switch(line->special) + switch(line_special(line)) { case 48: // EFFECT FIRSTCOL SCROLL + - sides[line->sidenum[0]].textureoffset += FRACUNIT; + side_settextureoffset16(sidenum_to_side(line_sidenum(line, 0)), side_textureoffset16(sidenum_to_side(line_sidenum(line, 0)))+1); break; } } +#else + // single global variable + linespecialoffset++; +#endif // DO BUTTONS @@ -1144,21 +1158,18 @@ void P_UpdateSpecials (void) switch(buttonlist[i].where) { case top: - sides[buttonlist[i].line->sidenum[0]].toptexture = - buttonlist[i].btexture; + side_settoptexture(sidenum_to_side(line_sidenum(buttonlist[i].line, 0)), buttonlist[i].btexture); break; case middle: - sides[buttonlist[i].line->sidenum[0]].midtexture = - buttonlist[i].btexture; + side_setmidtexture(sidenum_to_side(line_sidenum(buttonlist[i].line, 0)), buttonlist[i].btexture); break; case bottom: - sides[buttonlist[i].line->sidenum[0]].bottomtexture = - buttonlist[i].btexture; + side_setbottomtexture(sidenum_to_side(line_sidenum(buttonlist[i].line, 0)), buttonlist[i].btexture); break; } - S_StartSound(&buttonlist[i].soundorg,sfx_swtchn); + S_StartSound(buttonlist[i].soundorg,sfx_swtchn); memset(&buttonlist[i],0,sizeof(button_t)); } } @@ -1178,7 +1189,7 @@ void P_UpdateSpecials (void) static void DonutOverrun(fixed_t *s3_floorheight, short *s3_floorpic, line_t *line, sector_t *pillar_sector) { - static int first = 1; + static isb_int8_t first = 1; static int tmp_s3_floorheight; static int tmp_s3_floorpic; @@ -1205,6 +1216,7 @@ static void DonutOverrun(fixed_t *s3_floorheight, short *s3_floorpic, // system. The default (if this option is not specified) is to // emulate the behavior when running under Windows 98. +#if !NO_USE_ARGS p = M_CheckParmWithArgs("-donut", 2); if (p > 0) @@ -1228,7 +1240,7 @@ static void DonutOverrun(fixed_t *s3_floorheight, short *s3_floorpic, if (tmp_s3_floorpic >= numflats) { - fprintf(stderr, + stderr_print( "DonutOverrun: The second parameter for \"-donut\" " "switch should be greater than 0 and less than number " "of flats (%d). Using default value (%d) instead. \n", @@ -1236,10 +1248,11 @@ static void DonutOverrun(fixed_t *s3_floorheight, short *s3_floorpic, tmp_s3_floorpic = DONUT_FLOORPIC_DEFAULT; } } +#endif } /* - fprintf(stderr, + stderr_print( "Linedef: %d; Sector: %d; " "New floor height: %d; New floor pic: %d\n", line->iLineID, pillar_sector->iSectorID, @@ -1277,7 +1290,7 @@ int EV_DoDonut(line_t* line) continue; rtn = 1; - s2 = getNextSector(s1->lines[0],s1); + s2 = getNextSector(sector_line(s1, 0),s1); // Vanilla Doom does not check if the linedef is one sided. The // game does not crash, but reads invalid memory and causes the @@ -1290,7 +1303,7 @@ int EV_DoDonut(line_t* line) if (s2 == NULL) { - fprintf(stderr, + stderr_print( "EV_DoDonut: linedef had no second sidedef! " "Unexpected behavior may occur in Vanilla Doom. \n"); break; @@ -1298,7 +1311,7 @@ int EV_DoDonut(line_t* line) for (i = 0; i < s2->linecount; i++) { - s3 = s2->lines[i]->backsector; + s3 = line_backsector(sector_line(s2, i)); if (s3 == s1) continue; @@ -1311,7 +1324,7 @@ int EV_DoDonut(line_t* line) // s3->floorpic is a short at 0000:0008 // Trying to emulate - fprintf(stderr, + stderr_print( "EV_DoDonut: WARNING: emulating buffer overrun due to " "NULL back sector. " "Unexpected behavior may occur in Vanilla Doom.\n"); @@ -1320,15 +1333,15 @@ int EV_DoDonut(line_t* line) } else { - s3_floorheight = s3->floorheight; + s3_floorheight = sector_floorheight(s3); s3_floorpic = s3->floorpic; } // Spawn rising slime floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0); P_AddThinker (&floor->thinker); - s2->specialdata = floor; - floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + s2->specialdata = ptr_to_shortptr(floor); + floor->thinker.function = ThinkF_T_MoveFloor; floor->type = donutRaise; floor->crush = false; floor->direction = 1; @@ -1341,8 +1354,8 @@ int EV_DoDonut(line_t* line) // Spawn lowering donut-hole floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0); P_AddThinker (&floor->thinker); - s1->specialdata = floor; - floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + s1->specialdata = ptr_to_shortptr(floor); + floor->thinker.function = ThinkF_T_MoveFloor; floor->type = lowerFloor; floor->crush = false; floor->direction = -1; @@ -1367,7 +1380,12 @@ int EV_DoDonut(line_t* line) // that spawn thinkers // short numlinespecials; +#if !USE_WHD line_t* linespeciallist[MAXLINEANIMS]; +#else +uint16_t linespecialoffsetlist[MAXLINEANIMS]; +uint16_t linespecialoffset; +#endif // Parses command line parameters. @@ -1456,9 +1474,10 @@ void P_SpawnSpecials (void) // Init line EFFECTs numlinespecials = 0; + line_t *li = lines; for (i = 0;i < numlines; i++) { - switch(lines[i].special) + switch(line_special(li)) { case 48: if (numlinespecials >= MAXLINEANIMS) @@ -1467,19 +1486,26 @@ void P_SpawnSpecials (void) "(Vanilla limit is 64)"); } // EFFECT FIRSTCOL SCROLL+ - linespeciallist[numlinespecials] = &lines[i]; +#if !USE_WHD + linespeciallist[numlinespecials] = li; +#else + assert(li - lines < 65536); + linespecialoffsetlist[numlinespecials] = li - lines; +#endif numlinespecials++; break; } + li += line_next_step(li); + } // Init other misc stuff for (i = 0;i < MAXCEILINGS;i++) - activeceilings[i] = NULL; + activeceilings[i] = 0; for (i = 0;i < MAXPLATS;i++) - activeplats[i] = NULL; + activeplats[i] = 0; for (i = 0;i < MAXBUTTONS;i++) memset(&buttonlist[i],0,sizeof(button_t)); diff --git a/src/doom/p_spec.h b/src/doom/p_spec.h index a1343bfa..c2701a94 100644 --- a/src/doom/p_spec.h +++ b/src/doom/p_spec.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -179,6 +180,7 @@ void P_SpawnFireFlicker (sector_t* sector); void T_LightFlash (lightflash_t* flash); void P_SpawnLightFlash (sector_t* sector); void T_StrobeFlash (strobe_t* flash); +void T_FireFlicker (fireflicker_t* flick); void P_SpawnStrobeFlash @@ -205,9 +207,13 @@ void P_SpawnGlowingLight(sector_t* sector); // typedef struct { - char name1[9]; - char name2[9]; + texturename_def_t name1; + texturename_def_t name2; +#if DOOM_TINY + int8_t episode; +#else short episode; +#endif } switchlist_t; @@ -224,11 +230,18 @@ typedef enum typedef struct { line_t* line; +#if !DOOM_SMALL bwhere_e where; - int btexture; +#else + uint8_t where; +#endif +#if !DOOM_SMALL int btimer; - degenmobj_t *soundorg; - +#else + uint8_t btimer; +#endif + lumpindex_t btexture; + xy_positioned_t *soundorg; } button_t; @@ -296,6 +309,8 @@ typedef struct } plat_t; +static inline shortptr_t plat_to_shortptr(plat_t *c) { return ptr_to_shortptr(c); } +#define shortptr_to_plat(s) ((plat_t *)shortptr_to_ptr(s)) #define PLATWAIT 3 @@ -303,7 +318,7 @@ typedef struct #define MAXPLATS 30 -extern plat_t* activeplats[MAXPLATS]; +extern shortptr_t /*plat_t* */ activeplats[MAXPLATS]; void T_PlatRaise(plat_t* plat); @@ -485,8 +500,6 @@ typedef enum } ceiling_e; - - typedef struct { thinker_t thinker; @@ -506,15 +519,14 @@ typedef struct } ceiling_t; - - - +static inline shortptr_t ceiling_to_shortptr(ceiling_t *c) { return ptr_to_shortptr(c); } +#define shortptr_to_ceiling(s) ((ceiling_t *)shortptr_to_ptr(s)) #define CEILSPEED FRACUNIT #define CEILWAIT 150 #define MAXCEILINGS 30 -extern ceiling_t* activeceilings[MAXCEILINGS]; +extern shortptr_t/*ceiling_t**/ activeceilings[MAXCEILINGS]; int EV_DoCeiling @@ -634,4 +646,21 @@ EV_Teleport int side, mobj_t* thing ); + +// +// Animating line specials +// +#if WHD_SUPER_TINY +#define MAXLINEANIMS 32 // 64 seems excessive +#else +#define MAXLINEANIMS 64 +#endif + +extern short numlinespecials; +#if !USE_WHD +extern line_t* linespeciallist[MAXLINEANIMS]; +#else +extern uint16_t linespecialoffsetlist[MAXLINEANIMS]; +extern uint16_t linespecialoffset; +#endif #endif diff --git a/src/doom/p_switch.c b/src/doom/p_switch.c index b9ef2195..504539e0 100644 --- a/src/doom/p_switch.c +++ b/src/doom/p_switch.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -39,58 +40,59 @@ // // CHANGE THE TEXTURE OF A WALL SWITCH TO ITS OPPOSITE // -switchlist_t alphSwitchList[] = +const switchlist_t alphSwitchList[] = { // Doom shareware episode 1 switches - {"SW1BRCOM", "SW2BRCOM", 1}, - {"SW1BRN1", "SW2BRN1", 1}, - {"SW1BRN2", "SW2BRN2", 1}, - {"SW1BRNGN", "SW2BRNGN", 1}, - {"SW1BROWN", "SW2BROWN", 1}, - {"SW1COMM", "SW2COMM", 1}, - {"SW1COMP", "SW2COMP", 1}, - {"SW1DIRT", "SW2DIRT", 1}, - {"SW1EXIT", "SW2EXIT", 1}, - {"SW1GRAY", "SW2GRAY", 1}, - {"SW1GRAY1", "SW2GRAY1", 1}, - {"SW1METAL", "SW2METAL", 1}, - {"SW1PIPE", "SW2PIPE", 1}, - {"SW1SLAD", "SW2SLAD", 1}, - {"SW1STARG", "SW2STARG", 1}, - {"SW1STON1", "SW2STON1", 1}, - {"SW1STON2", "SW2STON2", 1}, - {"SW1STONE", "SW2STONE", 1}, - {"SW1STRTN", "SW2STRTN", 1}, + {TEXTURE_NAME(SW1BRCOM), TEXTURE_NAME(SW2BRCOM), 1}, + {TEXTURE_NAME(SW1BRN1), TEXTURE_NAME(SW2BRN1), 1}, + {TEXTURE_NAME(SW1BRN2), TEXTURE_NAME(SW2BRN2), 1}, + {TEXTURE_NAME(SW1BRNGN), TEXTURE_NAME(SW2BRNGN), 1}, + {TEXTURE_NAME(SW1BROWN), TEXTURE_NAME(SW2BROWN), 1}, + {TEXTURE_NAME(SW1COMM), TEXTURE_NAME(SW2COMM), 1}, + {TEXTURE_NAME(SW1COMP), TEXTURE_NAME(SW2COMP), 1}, + {TEXTURE_NAME(SW1DIRT), TEXTURE_NAME(SW2DIRT), 1}, + {TEXTURE_NAME(SW1EXIT), TEXTURE_NAME(SW2EXIT), 1}, + {TEXTURE_NAME(SW1GRAY), TEXTURE_NAME(SW2GRAY), 1}, + {TEXTURE_NAME(SW1GRAY1), TEXTURE_NAME(SW2GRAY1), 1}, + {TEXTURE_NAME(SW1METAL), TEXTURE_NAME(SW2METAL), 1}, + {TEXTURE_NAME(SW1PIPE), TEXTURE_NAME(SW2PIPE), 1}, + {TEXTURE_NAME(SW1SLAD), TEXTURE_NAME(SW2SLAD), 1}, + {TEXTURE_NAME(SW1STARG), TEXTURE_NAME(SW2STARG), 1}, + {TEXTURE_NAME(SW1STON1), TEXTURE_NAME(SW2STON1), 1}, + {TEXTURE_NAME(SW1STON2), TEXTURE_NAME(SW2STON2), 1}, + {TEXTURE_NAME(SW1STONE), TEXTURE_NAME(SW2STONE), 1}, + {TEXTURE_NAME(SW1STRTN), TEXTURE_NAME(SW2STRTN), 1}, // Doom registered episodes 2&3 switches - {"SW1BLUE", "SW2BLUE", 2}, - {"SW1CMT", "SW2CMT", 2}, - {"SW1GARG", "SW2GARG", 2}, - {"SW1GSTON", "SW2GSTON", 2}, - {"SW1HOT", "SW2HOT", 2}, - {"SW1LION", "SW2LION", 2}, - {"SW1SATYR", "SW2SATYR", 2}, - {"SW1SKIN", "SW2SKIN", 2}, - {"SW1VINE", "SW2VINE", 2}, - {"SW1WOOD", "SW2WOOD", 2}, + {TEXTURE_NAME(SW1BLUE), TEXTURE_NAME(SW2BLUE), 2}, + {TEXTURE_NAME(SW1CMT), TEXTURE_NAME(SW2CMT), 2}, + {TEXTURE_NAME(SW1GARG), TEXTURE_NAME(SW2GARG), 2}, + {TEXTURE_NAME(SW1GSTON), TEXTURE_NAME(SW2GSTON), 2}, + {TEXTURE_NAME(SW1HOT), TEXTURE_NAME(SW2HOT), 2}, + {TEXTURE_NAME(SW1LION), TEXTURE_NAME(SW2LION), 2}, + {TEXTURE_NAME(SW1SATYR), TEXTURE_NAME(SW2SATYR), 2}, + {TEXTURE_NAME(SW1SKIN), TEXTURE_NAME(SW2SKIN), 2}, + {TEXTURE_NAME(SW1VINE), TEXTURE_NAME(SW2VINE), 2}, + {TEXTURE_NAME(SW1WOOD), TEXTURE_NAME(SW2WOOD), 2}, // Doom II switches - {"SW1PANEL", "SW2PANEL", 3}, - {"SW1ROCK", "SW2ROCK", 3}, - {"SW1MET2", "SW2MET2", 3}, - {"SW1WDMET", "SW2WDMET", 3}, - {"SW1BRIK", "SW2BRIK", 3}, - {"SW1MOD1", "SW2MOD1", 3}, - {"SW1ZIM", "SW2ZIM", 3}, - {"SW1STON6", "SW2STON6", 3}, - {"SW1TEK", "SW2TEK", 3}, - {"SW1MARB", "SW2MARB", 3}, - {"SW1SKULL", "SW2SKULL", 3}, + {TEXTURE_NAME(SW1PANEL), TEXTURE_NAME(SW2PANEL), 3}, + {TEXTURE_NAME(SW1ROCK), TEXTURE_NAME(SW2ROCK), 3}, + {TEXTURE_NAME(SW1MET2), TEXTURE_NAME(SW2MET2), 3}, + {TEXTURE_NAME(SW1WDMET), TEXTURE_NAME(SW2WDMET), 3}, + {TEXTURE_NAME(SW1BRIK), TEXTURE_NAME(SW2BRIK), 3}, + {TEXTURE_NAME(SW1MOD1), TEXTURE_NAME(SW2MOD1), 3}, + {TEXTURE_NAME(SW1ZIM), TEXTURE_NAME(SW2ZIM), 3}, + {TEXTURE_NAME(SW1STON6), TEXTURE_NAME(SW2STON6), 3}, + {TEXTURE_NAME(SW1TEK), TEXTURE_NAME(SW2TEK), 3}, + {TEXTURE_NAME(SW1MARB), TEXTURE_NAME(SW2MARB), 3}, + {TEXTURE_NAME(SW1SKULL), TEXTURE_NAME(SW2SKULL), 3}, }; -int switchlist[MAXSWITCHES * 2]; -int numswitches; -button_t buttonlist[MAXBUTTONS]; +// todo graham can go in the WHD, but then again pretty small +lumpindex_t switchlist[MAXSWITCHES * 2]; +uint8_t numswitches; +button_t buttonlist[MAXBUTTONS]; // // P_InitSwitchList @@ -124,9 +126,9 @@ void P_InitSwitchList(void) if (alphSwitchList[i].episode <= episode) { switchlist[slindex++] = - R_TextureNumForName(DEH_String(alphSwitchList[i].name1)); + R_TextureNumForName(DEH_TextureName(alphSwitchList[i].name1)); switchlist[slindex++] = - R_TextureNumForName(DEH_String(alphSwitchList[i].name2)); + R_TextureNumForName(DEH_TextureName(alphSwitchList[i].name2)); } } @@ -158,7 +160,9 @@ P_StartButton } } - +#if DOOM_SMALL + assert(time < 256); +#endif for (i = 0;i < MAXBUTTONS;i++) { @@ -168,7 +172,7 @@ P_StartButton buttonlist[i].where = w; buttonlist[i].btexture = texture; buttonlist[i].btimer = time; - buttonlist[i].soundorg = &line->frontsector->soundorg; + buttonlist[i].soundorg = &line_frontsector(line)->soundorg; return; } } @@ -194,18 +198,19 @@ P_ChangeSwitchTexture int texBot; int i; int sound; - - if (!useAgain) - line->special = 0; - texTop = sides[line->sidenum[0]].toptexture; - texMid = sides[line->sidenum[0]].midtexture; - texBot = sides[line->sidenum[0]].bottomtexture; + if (!useAgain) + clear_line_special(line); + + side_t *side = sidenum_to_side(line_sidenum(line, 0)); + texTop = side_toptexture(side); + texMid = side_midtexture(side); + texBot = side_bottomtexture(side); sound = sfx_swtchn; // EXIT SWITCH? - if (line->special == 11) + if (line_special(line) == 11) sound = sfx_swtchx; for (i = 0;i < numswitches*2;i++) @@ -213,7 +218,7 @@ P_ChangeSwitchTexture if (switchlist[i] == texTop) { S_StartSound(buttonlist->soundorg,sound); - sides[line->sidenum[0]].toptexture = switchlist[i^1]; + side_settoptexture(side, switchlist[i^1]); if (useAgain) P_StartButton(line,top,switchlist[i],BUTTONTIME); @@ -225,7 +230,7 @@ P_ChangeSwitchTexture if (switchlist[i] == texMid) { S_StartSound(buttonlist->soundorg,sound); - sides[line->sidenum[0]].midtexture = switchlist[i^1]; + side_setmidtexture(side, switchlist[i^1]); if (useAgain) P_StartButton(line, middle,switchlist[i],BUTTONTIME); @@ -237,8 +242,7 @@ P_ChangeSwitchTexture if (switchlist[i] == texBot) { S_StartSound(buttonlist->soundorg,sound); - sides[line->sidenum[0]].bottomtexture = switchlist[i^1]; - + side_setbottomtexture(side, switchlist[i^1]); if (useAgain) P_StartButton(line, bottom,switchlist[i],BUTTONTIME); @@ -270,7 +274,7 @@ P_UseSpecialLine // Use the back sides of VERY SPECIAL lines... if (side) { - switch(line->special) + switch(line_special(line)) { case 124: // Sliding door open&close @@ -285,13 +289,13 @@ P_UseSpecialLine // Switches that other things can activate. - if (!thing->player) + if (!mobj_full(thing)->sp_player) { // never open secret doors - if (line->flags & ML_SECRET) + if (line_flags(line) & ML_SECRET) return false; - switch(line->special) + switch(line_special(line)) { case 1: // MANUAL DOOR RAISE case 32: // MANUAL BLUE @@ -307,7 +311,7 @@ P_UseSpecialLine // do something - switch (line->special) + switch (line_special(line)) { // MANUALS case 1: // Vertical Door diff --git a/src/doom/p_telept.c b/src/doom/p_telept.c index 45cdfb01..db11acec 100644 --- a/src/doom/p_telept.c +++ b/src/doom/p_telept.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -64,19 +65,21 @@ EV_Teleport if (side == 1) return 0; - - tag = line->tag; + + mobjfull_t *thingf = mobj_full(thing); + + tag = line_tag(line); for (i = 0; i < numsectors; i++) { if (sectors[ i ].tag == tag ) { - thinker = thinkercap.next; - for (thinker = thinkercap.next; + + for (thinker = thinker_next(&thinkercap); thinker != &thinkercap; - thinker = thinker->next) + thinker = thinker_next(thinker)) { // not a mobj - if (thinker->function.acp1 != (actionf_p1)P_MobjThinker) + if (thinker->function != ThinkF_P_MobjThinker) continue; m = (mobj_t *)thinker; @@ -85,16 +88,16 @@ EV_Teleport if (m->type != MT_TELEPORTMAN ) continue; - sector = m->subsector->sector; + sector = mobj_sector(m); // wrong sector if (sector-sectors != i ) continue; - oldx = thing->x; - oldy = thing->y; + oldx = thing->xy.x; + oldy = thing->xy.y; oldz = thing->z; - if (!P_TeleportMove (thing, m->x, m->y)) + if (!P_TeleportMove (thing, m->xy.x, m->xy.y)) return 0; // The first Final Doom executable does not set thing->z @@ -103,27 +106,27 @@ EV_Teleport // some versions of the Id Anthology fixed this. if (gameversion != exe_final) - thing->z = thing->floorz; + thing->z = thingf->floorz; - if (thing->player) - thing->player->viewz = thing->z+thing->player->viewheight; + if (thingf->sp_player) + mobj_player(thing)->viewz = thing->z+mobj_player(thing)->viewheight; // spawn teleport fog at source and destination fog = P_SpawnMobj (oldx, oldy, oldz, MT_TFOG); - S_StartSound (fog, sfx_telept); - an = m->angle >> ANGLETOFINESHIFT; - fog = P_SpawnMobj (m->x+20*finecosine[an], m->y+20*finesine[an] + S_StartObjSound (fog, sfx_telept); + an = mobj_full(m)->angle >> ANGLETOFINESHIFT; + fog = P_SpawnMobj (m->xy.x+20*finecosine(an), m->xy.y + 20 * finesine(an) , thing->z, MT_TFOG); // emit sound, where? - S_StartSound (fog, sfx_telept); + S_StartObjSound (fog, sfx_telept); // don't move for a bit - if (thing->player) - thing->reactiontime = 18; + if (thingf->sp_player) + thingf->reactiontime = 18; - thing->angle = m->angle; - thing->momx = thing->momy = thing->momz = 0; + thingf->angle = mobj_full(m)->angle; + thingf->momx = thingf->momy = thingf->momz = 0; return 1; } } diff --git a/src/doom/p_tick.c b/src/doom/p_tick.c index 5a93b80f..92024d13 100644 --- a/src/doom/p_tick.c +++ b/src/doom/p_tick.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -18,6 +19,7 @@ // +#include #include "z_zone.h" #include "p_local.h" @@ -37,15 +39,24 @@ int leveltime; // Both the head and tail of the thinker list. -thinker_t thinkercap; +thinker_t thinkercap; +thinker_t *thinkertail; +#if USE_THINKER_POOL +#define MAX_THINKER_POOLS 2 +static shortptr_t thinker_pool[MAX_THINKER_POOLS]; +#endif // // P_InitThinkers // void P_InitThinkers (void) { - thinkercap.prev = thinkercap.next = &thinkercap; + thinkercap.sp_next = thinker_to_shortptr(&thinkercap); + thinkertail = &thinkercap; +#if USE_THINKER_POOL + memset(thinker_pool, 0, sizeof(thinker_pool)); +#endif } @@ -57,14 +68,12 @@ void P_InitThinkers (void) // void P_AddThinker (thinker_t* thinker) { - thinkercap.prev->next = thinker; - thinker->next = &thinkercap; - thinker->prev = thinkercap.prev; - thinkercap.prev = thinker; + assert(thinker_next(thinkertail) == &thinkercap); + thinker->sp_next = thinkertail->sp_next; + thinkertail->sp_next = thinker_to_shortptr(thinker); + thinkertail = thinker; } - - // // P_RemoveThinker // Deallocation is lazy -- it will not actually be freed @@ -73,7 +82,7 @@ void P_AddThinker (thinker_t* thinker) void P_RemoveThinker (thinker_t* thinker) { // FIXME: NOP. - thinker->function.acv = (actionf_v)(-1); + thinker->function = ThinkF_REMOVED; } @@ -93,26 +102,55 @@ void P_AllocateThinker (thinker_t* thinker) // void P_RunThinkers (void) { - thinker_t *currentthinker, *nextthinker; + thinker_t *prevthinker, *currentthinker; - currentthinker = thinkercap.next; - while (currentthinker != &thinkercap) - { - if ( currentthinker->function.acv == (actionf_v)(-1) ) - { - // time to remove it - nextthinker = currentthinker->next; - currentthinker->next->prev = currentthinker->prev; - currentthinker->prev->next = currentthinker->next; - Z_Free(currentthinker); - } - else - { - if (currentthinker->function.acp1) - currentthinker->function.acp1 (currentthinker); - nextthinker = currentthinker->next; - } - currentthinker = nextthinker; + prevthinker = &thinkercap; + currentthinker = thinker_next(prevthinker); + while (currentthinker != &thinkercap) { + if (currentthinker->function == ThinkF_REMOVED) { + // time to remove it + prevthinker->sp_next = currentthinker->sp_next; + if (prevthinker->sp_next == ptr_to_shortptr(&thinkercap)) { + thinkertail = prevthinker; + } + Z_ThinkFree(currentthinker); + } else { + switch (currentthinker->function) { + case ThinkF_NULL: + break; + case ThinkF_T_MoveCeiling: + T_MoveCeiling((ceiling_t *) currentthinker); + break; + case ThinkF_T_VerticalDoor: + T_VerticalDoor((vldoor_t *) currentthinker); + break; + case ThinkF_T_PlatRaise: + T_PlatRaise((plat_t *) currentthinker); + break; + case ThinkF_T_FireFlicker: + T_FireFlicker((fireflicker_t *) currentthinker); + break; + case ThinkF_T_LightFlash: + T_LightFlash((lightflash_t *) currentthinker); + break; + case ThinkF_T_StrobeFlash: + T_StrobeFlash((strobe_t *) currentthinker); + break; + case ThinkF_T_MoveFloor: + T_MoveFloor((floormove_t *) currentthinker); + break; + case ThinkF_T_Glow: + T_Glow((glow_t *) currentthinker); + break; + case ThinkF_P_MobjThinker: + P_MobjThinker((mobj_t *) currentthinker); + break; + default: + I_Error("Unexpected thinker"); + } + prevthinker = currentthinker; + } + currentthinker = thinker_next(prevthinker); } } @@ -151,3 +189,174 @@ void P_Ticker (void) // for par times leveltime++; } + +// ================================================================= +// thinker_t objects are the most common dynamically allocated things +// and include our mobj_t (and mobjfull_t). We therefore try to minimize +// the Z_Zone malloc overhead (8 bytes) by simple pooling. +// +// We use one memory object allocation "block" to store up to 8 slots of the same +// size. (we only do this for known sizes). +// +// There is actually a padding byte spare in the malloc header (which we use +// for a 8 entry bit set for which of the block's slots are free) +// +// There is a byte spare in the thinker_t (which we call pool_info) which +// is used to identity a slot entry rather than a raw object, and to locate +// the enclosing block if this is indeed a slot object. +// +// pool_info is two nibbles of the form tttt:1sss where t is an object type +// which identifies the size of each slot, and sss is the 0-7 slot number. +// the pool_info is 0 for non slot thinker_t objects. +// +// We keep a linked list of partially full blocks which starts in the thinker_pool +// array below. The forward link from each block to the next partially full block +// is stored in the "sp_next" thinker_t field of the highest numbered +// free slot of the block +// ================================================================= + +#if USE_THINKER_POOL +static int thinker_pool_type(int size) { + switch (size) { + case sizeof(mobj_t): + return 0; + case sizeof(mobjfull_t): + return 1; + default: + return -1; + } +} + +static int thinker_pool_object_size(int type) { + switch (type) { + case 0: + return sizeof(mobj_t); + case 1: + return sizeof(mobjfull_t); + default: + I_Error("what?"); + return 0; + } +} + +static inline thinker_t *thinker_n(void *obj, int n, int size) { + assert(!(size & 3)); + return (thinker_t *)(obj + n * size); +} + +thinker_t *Z_ThinkMallocImpl(int size) { + int type = thinker_pool_type(size); + if (type < 0) { + // not pooled, so just malloc + thinker_t *thinker = Z_Malloc(size, PU_LEVEL, 0); + memset(thinker, 0, size); + return thinker; + } + if (!thinker_pool[type]) { + // we don't have any partial pools, so allocate into new pool + void *block = Z_Malloc(size * 8, PU_LEVEL, 0); + thinker_pool[type] = ptr_to_shortptr(block); + uint8_t *bitset = Z_ObjectExtra(block); + // all free but first + *bitset = 0xfe; + thinker_t *thinker = (thinker_t *)block; + memset(thinker, 0, size); + thinker->pool_info = (type << 4u) | 0x8; + // pointer to next free pool (none) in the last free thinker + thinker_n(block, 7, size)->sp_next = 0; +// printf("Pool %d @ %p, allocating new block slot %d %02x (free) = %p pi %02x\n", size, block, 0, *bitset, thinker, thinker->pool_info); + return thinker; + } + void *block = shortptr_to_ptr(thinker_pool[type]); + uint8_t *bitset = Z_ObjectExtra(block); + int slot = __builtin_ctz(*bitset); + assert(slot >= 0 && slot < 8); + thinker_t *thinker = thinker_n(block, slot, size); + memset(thinker, 0, size); + // indicate that this thinker is in a pool (and where) + thinker->pool_info = (type << 4u) | 0x8 | slot; + *bitset &= ~(1u << slot); +// printf("Pool %d @ %p, allocating slot %d %02x (free) = %p pi %02x\n", size, block, slot, *bitset, thinker, thinker->pool_info); + if (!*bitset) { + // if the pool is now full, so follow the link from the last + // allocated thinker (us) +// printf(" unlinking full block from head\n"); + thinker_pool[type] = thinker->sp_next; + } + return thinker; +} + +void dump_chain(int type, int size) { +#if 1 + printf("P-chain(%d): ", size); + void *cur_block = shortptr_to_ptr(thinker_pool[type]); + while (cur_block) { + uint8_t *current_bitset = Z_ObjectExtra(cur_block); + printf("-> %p(%02x) ", cur_block, *current_bitset); + int highest_free_slot = 31 - __builtin_clz(*current_bitset); + assert(highest_free_slot >=0 && highest_free_slot < 8); + cur_block = shortptr_to_ptr(thinker_n(cur_block, highest_free_slot, size)->sp_next); + } + printf("\n"); +#endif +} + +void Z_ThinkFree(thinker_t *thinker) { + if (!thinker->pool_info) { + // not in a pool + Z_Free(thinker); + return; + } + + assert(thinker->pool_info & 0x8); + int type = thinker->pool_info >> 4; + int slot = thinker->pool_info & 0x7; + assert(type <= MAX_THINKER_POOLS); + + int size = thinker_pool_object_size(type); + void *block = ((void *)thinker) - slot * size; + uint8_t *bitset = Z_ObjectExtra(block); + assert(!(*bitset & (1u << slot))); +// printf("Pool %d @ %p, freeing slot %d %02x (free) = %p pi %02x\n", size, block, slot, *bitset, thinker, thinker->pool_info); + + shortptr_t next_pool; + if (!*bitset) { + // was full before +// printf(" from full pool, so inserting %p as head\n", block); + next_pool = thinker_pool[type]; + // new addition to partial block list, so stick us at the front + thinker_pool[type] = ptr_to_shortptr(block); + } else { + int highest_free_slot = 31 - __builtin_clz(*bitset); + next_pool = thinker_n(block, highest_free_slot, size)->sp_next; + } + *bitset |= 1u << slot; + if (*bitset == 0xff) { + // we need to unlink +// *bitset &= ~(1u << slot); +// dump_chain(type, size); +// *bitset |= 1u << slot; +// printf(" block %p now empty, so unlinking and freeing\n", block); + shortptr_t *pprev = &thinker_pool[type]; + void *cur_block = shortptr_to_ptr(*pprev); + while (cur_block) { + if (cur_block == block) { + *pprev = next_pool; + break; + } + uint8_t *cur_bitset = Z_ObjectExtra(cur_block); + int highest_free_slot = 31 - __builtin_clz(*cur_bitset); + pprev = &thinker_n(cur_block, highest_free_slot, size)->sp_next; + cur_block = shortptr_to_ptr(*pprev); + } + Z_Free(block); + } else { + if (*bitset < (2u << slot)) { + // we are now the highest free bit, and should hold the forward pointer +// printf(" slot %d is topmost free slot, so updating next link to %p\n", slot, next_pool); + thinker_n(block, slot, size)->sp_next = next_pool; + } + } +// dump_chain(type, size); +} +#endif diff --git a/src/doom/p_user.c b/src/doom/p_user.c index 772871ef..fb0d0893 100644 --- a/src/doom/p_user.c +++ b/src/doom/p_user.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -56,8 +57,8 @@ P_Thrust { angle >>= ANGLETOFINESHIFT; - player->mo->momx += FixedMul(move,finecosine[angle]); - player->mo->momy += FixedMul(move,finesine[angle]); + mobj_full(player->mo)->momx += FixedMul(move,finecosine(angle)); + mobj_full(player->mo)->momy += FixedMul(move,finesine(angle)); } @@ -79,8 +80,8 @@ void P_CalcHeight (player_t* player) // Note: a LUT allows for effects // like a ramp with low health. player->bob = - FixedMul (player->mo->momx, player->mo->momx) - + FixedMul (player->mo->momy,player->mo->momy); + FixedMul (mobj_full(player->mo)->momx, mobj_full(player->mo)->momx) + + FixedMul (mobj_full(player->mo)->momy,mobj_full(player->mo)->momy); player->bob >>= 2; @@ -91,15 +92,15 @@ void P_CalcHeight (player_t* player) { player->viewz = player->mo->z + VIEWHEIGHT; - if (player->viewz > player->mo->ceilingz-4*FRACUNIT) - player->viewz = player->mo->ceilingz-4*FRACUNIT; + if (player->viewz > mobj_full(player->mo)->ceilingz-4*FRACUNIT) + player->viewz = mobj_full(player->mo)->ceilingz-4*FRACUNIT; player->viewz = player->mo->z + player->viewheight; return; } angle = (FINEANGLES/20*leveltime)&FINEMASK; - bob = FixedMul ( player->bob/2, finesine[angle]); + bob = FixedMul ( player->bob/2, finesine(angle)); // move viewheight @@ -129,8 +130,8 @@ void P_CalcHeight (player_t* player) } player->viewz = player->mo->z + player->viewheight + bob; - if (player->viewz > player->mo->ceilingz-4*FRACUNIT) - player->viewz = player->mo->ceilingz-4*FRACUNIT; + if (player->viewz > mobj_full(player->mo)->ceilingz-4*FRACUNIT) + player->viewz = mobj_full(player->mo)->ceilingz-4*FRACUNIT; } @@ -144,20 +145,20 @@ void P_MovePlayer (player_t* player) cmd = &player->cmd; - player->mo->angle += (cmd->angleturn<mo)->angle += (cmd->angleturn<mo->z <= player->mo->floorz); + onground = (player->mo->z <= mobj_full(player->mo)->floorz); if (cmd->forwardmove && onground) - P_Thrust (player, player->mo->angle, cmd->forwardmove*2048); + P_Thrust (player, mobj_full(player->mo)->angle, cmd->forwardmove*2048); if (cmd->sidemove && onground) - P_Thrust (player, player->mo->angle-ANG90, cmd->sidemove*2048); + P_Thrust (player, mobj_full(player->mo)->angle-ANG90, cmd->sidemove*2048); if ( (cmd->forwardmove || cmd->sidemove) - && player->mo->state == &states[S_PLAY] ) + && mobj_state_num(player->mo) == S_PLAY ) { P_SetMobjState (player->mo, S_PLAY_RUN1); } @@ -187,31 +188,31 @@ void P_DeathThink (player_t* player) player->viewheight = 6*FRACUNIT; player->deltaviewheight = 0; - onground = (player->mo->z <= player->mo->floorz); + onground = (player->mo->z <= mobj_full(player->mo)->floorz); P_CalcHeight (player); if (player->attacker && player->attacker != player->mo) { - angle = R_PointToAngle2 (player->mo->x, - player->mo->y, - player->attacker->x, - player->attacker->y); + angle = R_PointToAngle2 (player->mo->xy.x, + player->mo->xy.y, + player->attacker->xy.x, + player->attacker->xy.y); - delta = angle - player->mo->angle; + delta = angle - mobj_full(player->mo)->angle; if (delta < ANG5 || delta > (unsigned)-ANG5) { // Looking at killer, // so fade damage flash down. - player->mo->angle = angle; + mobj_full(player->mo)->angle = angle; if (player->damagecount) player->damagecount--; } else if (delta < ANG180) - player->mo->angle += ANG5; + mobj_full(player->mo)->angle += ANG5; else - player->mo->angle -= ANG5; + mobj_full(player->mo)->angle -= ANG5; } else if (player->damagecount) player->damagecount--; @@ -257,14 +258,14 @@ void P_PlayerThink (player_t* player) // Move around. // Reactiontime is used to prevent movement // for a bit after a teleport. - if (player->mo->reactiontime) - player->mo->reactiontime--; + if (mobj_reactiontime(player->mo)) + mobj_reactiontime(player->mo)--; else P_MovePlayer (player); P_CalcHeight (player); - if (player->mo->subsector->sector->special) + if (mobj_sector(player->mo)->special) P_PlayerInSpecialSector (player); // Check for weapon change. diff --git a/src/doom/r_bsp.c b/src/doom/r_bsp.c index 755d2eef..56f9ff99 100644 --- a/src/doom/r_bsp.c +++ b/src/doom/r_bsp.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -32,49 +33,51 @@ // State. #include "doomstat.h" #include "r_state.h" - +#if PICO_ON_DEVICE +#include "i_sound.h" +#include "pico/time.h" +#endif //#include "r_local.h" -seg_t* curline; -side_t* sidedef; -line_t* linedef; -sector_t* frontsector; -sector_t* backsector; +seg_t *curline; +side_t *sidedef; +line_t *linedef; +sector_t *frontsector; +sector_t *backsector; -drawseg_t drawsegs[MAXDRAWSEGS]; -drawseg_t* ds_p; +#if !NO_DRAWSEGS +drawseg_t drawsegs[MAXDRAWSEGS]; +drawseg_t *ds_p; +#endif void R_StoreWallRange -( int start, - int stop ); - - + (int start, + int stop); // // R_ClearDrawSegs // -void R_ClearDrawSegs (void) -{ +void R_ClearDrawSegs(void) { +#if !NO_DRAWSEGS ds_p = drawsegs; +#endif } - // // ClipWallSegment // Clips the given range of columns // and includes it in the new clip list. // -typedef struct -{ - int first; - int last; - +typedef struct { + int16_t first; + int16_t last; + } cliprange_t; // We must expand MAXSEGS to the theoretical limit of the number of solidsegs @@ -86,12 +89,10 @@ typedef struct //#define MAXSEGS 32 #define MAXSEGS (SCREENWIDTH / 2 + 1) +// todo pico possible space savings here vs runtime... a bitmap might be just as good // newend is one past the last valid seg -cliprange_t* newend; -cliprange_t solidsegs[MAXSEGS]; - - - +cliprange_t *newend; +cliprange_t solidsegs[MAXSEGS]; // // R_ClipSolidWallSegment @@ -101,90 +102,81 @@ cliprange_t solidsegs[MAXSEGS]; // void R_ClipSolidWallSegment -( int first, - int last ) -{ - cliprange_t* next; - cliprange_t* start; + (int first, + int last) { + cliprange_t *next; + cliprange_t *start; // Find the first range that touches the range // (adjacent pixels are touching). start = solidsegs; - while (start->last < first-1) - start++; + while (start->last < first - 1) + start++; - if (first < start->first) - { - if (last < start->first-1) - { - // Post is entirely visible (above start), - // so insert a new clippost. - R_StoreWallRange (first, last); - next = newend; - newend++; - - while (next != start) - { - *next = *(next-1); - next--; - } - next->first = first; - next->last = last; - return; - } - - // There is a fragment above *start. - R_StoreWallRange (first, start->first - 1); - // Now adjust the clip size. - start->first = first; + if (first < start->first) { + if (last < start->first - 1) { + // Post is entirely visible (above start), + // so insert a new clippost. + R_StoreWallRange(first, last); + next = newend; + newend++; + + while (next != start) { + *next = *(next - 1); + next--; + } + next->first = first; + next->last = last; + return; + } + + // There is a fragment above *start. + R_StoreWallRange(first, start->first - 1); + // Now adjust the clip size. + start->first = first; } // Bottom contained in start? if (last <= start->last) - return; - + return; + next = start; - while (last >= (next+1)->first-1) - { - // There is a fragment between two posts. - R_StoreWallRange (next->last + 1, (next+1)->first - 1); - next++; - - if (last <= next->last) - { - // Bottom is contained in next. - // Adjust the clip size. - start->last = next->last; - goto crunch; - } + while (last >= (next + 1)->first - 1) { + // There is a fragment between two posts. + R_StoreWallRange(next->last + 1, (next + 1)->first - 1); + next++; + + if (last <= next->last) { + // Bottom is contained in next. + // Adjust the clip size. + start->last = next->last; + goto crunch; + } } - + // There is a fragment after *next. - R_StoreWallRange (next->last + 1, last); + R_StoreWallRange(next->last + 1, last); // Adjust the clip size. start->last = last; - + // Remove start+1 to next from the clip list, // because start now covers their area. - crunch: - if (next == start) - { - // Post just extended past the bottom of one post. - return; - } - - - while (next++ != newend) - { - // Remove a post. - *++start = *next; + crunch: + if (next == start) { + // Post just extended past the bottom of one post. + return; } - newend = start+1; + + while (next++ != newend) { + // Remove a post. + *++start = *next; + } + + newend = start + 1; } - // // R_ClipPassWallSegment // Clips the given range of columns, @@ -194,60 +186,54 @@ R_ClipSolidWallSegment // void R_ClipPassWallSegment -( int first, - int last ) -{ - cliprange_t* start; + (int first, + int last) { + cliprange_t *start; // Find the first range that touches the range // (adjacent pixels are touching). start = solidsegs; - while (start->last < first-1) - start++; + while (start->last < first - 1) + start++; - if (first < start->first) - { - if (last < start->first-1) - { - // Post is entirely visible (above start). - R_StoreWallRange (first, last); - return; - } - - // There is a fragment above *start. - R_StoreWallRange (first, start->first - 1); + if (first < start->first) { + if (last < start->first - 1) { + // Post is entirely visible (above start). + R_StoreWallRange(first, last); + return; + } + + // There is a fragment above *start. + R_StoreWallRange(first, start->first - 1); } // Bottom contained in start? if (last <= start->last) - return; - - while (last >= (start+1)->first-1) - { - // There is a fragment between two posts. - R_StoreWallRange (start->last + 1, (start+1)->first - 1); - start++; - - if (last <= start->last) - return; - } - - // There is a fragment after *next. - R_StoreWallRange (start->last + 1, last); -} + return; + while (last >= (start + 1)->first - 1) { + // There is a fragment between two posts. + R_StoreWallRange(start->last + 1, (start + 1)->first - 1); + start++; + + if (last <= start->last) + return; + } + + // There is a fragment after *next. + R_StoreWallRange(start->last + 1, last); +} // // R_ClearClipSegs // -void R_ClearClipSegs (void) -{ - solidsegs[0].first = -0x7fffffff; +void R_ClearClipSegs(void) { + solidsegs[0].first = -0x7fff; solidsegs[0].last = -1; solidsegs[1].first = viewwidth; - solidsegs[1].last = 0x7fffffff; - newend = solidsegs+2; + solidsegs[1].last = 0x7fff; + newend = solidsegs + 2; } // @@ -255,103 +241,99 @@ void R_ClearClipSegs (void) // Clips the given segment // and adds any visible pieces to the line list. // -void R_AddLine (seg_t* line) -{ - int x1; - int x2; - angle_t angle1; - angle_t angle2; - angle_t span; - angle_t tspan; - +void R_AddLine(seg_t *line) { + int x1; + int x2; + angle_t angle1; + angle_t angle2; + angle_t span; + angle_t tspan; + curline = line; // OPTIMIZE: quickly reject orthogonal back sides. - angle1 = R_PointToAngle (line->v1->x, line->v1->y); - angle2 = R_PointToAngle (line->v2->x, line->v2->y); - + angle1 = R_PointToAngle(vertex_x(seg_v1(line)), vertex_y(seg_v1(line))); + angle2 = R_PointToAngle(vertex_x(seg_v2(line)), vertex_y(seg_v2(line))); + // Clip to view edges. // OPTIMIZE: make constant out of 2*clipangle (FIELDOFVIEW). span = angle1 - angle2; - + // Back side? I.e. backface culling? if (span >= ANG180) - return; + return; // Global angle needed by segcalc. rw_angle1 = angle1; angle1 -= viewangle; angle2 -= viewangle; - - tspan = angle1 + clipangle; - if (tspan > 2*clipangle) - { - tspan -= 2*clipangle; - // Totally off the left edge? - if (tspan >= span) - return; - - angle1 = clipangle; + tspan = angle1 + clipangle; + if (tspan > 2 * clipangle) { + tspan -= 2 * clipangle; + + // Totally off the left edge? + if (tspan >= span) + return; + + angle1 = clipangle; } tspan = clipangle - angle2; - if (tspan > 2*clipangle) - { - tspan -= 2*clipangle; + if (tspan > 2 * clipangle) { + tspan -= 2 * clipangle; - // Totally off the left edge? - if (tspan >= span) - return; - angle2 = -clipangle; + // Totally off the left edge? + if (tspan >= span) + return; + angle2 = -clipangle; } - + // The seg is in the view range, // but not necessarily visible. - angle1 = (angle1+ANG90)>>ANGLETOFINESHIFT; - angle2 = (angle2+ANG90)>>ANGLETOFINESHIFT; + angle1 = (angle1 + ANG90) >> ANGLETOFINESHIFT; + angle2 = (angle2 + ANG90) >> ANGLETOFINESHIFT; x1 = viewangletox[angle1]; x2 = viewangletox[angle2]; // Does not cross a pixel? if (x1 == x2) - return; - - backsector = line->backsector; + return; + + backsector = seg_backsector(line); // Single sided line? if (!backsector) - goto clipsolid; + goto clipsolid; // Closed door. - if (backsector->ceilingheight <= frontsector->floorheight - || backsector->floorheight >= frontsector->ceilingheight) - goto clipsolid; + if (backsector->rawceilingheight <= frontsector->rawfloorheight + || backsector->rawfloorheight >= frontsector->rawceilingheight) + goto clipsolid; // Window. - if (backsector->ceilingheight != frontsector->ceilingheight - || backsector->floorheight != frontsector->floorheight) - goto clippass; - + if (backsector->rawceilingheight != frontsector->rawceilingheight + || backsector->rawfloorheight != frontsector->rawfloorheight) + goto clippass; + // Reject empty lines used for triggers // and special events. // Identical floor and ceiling on both sides, // identical light levels on both sides, // and no middle texture. if (backsector->ceilingpic == frontsector->ceilingpic - && backsector->floorpic == frontsector->floorpic - && backsector->lightlevel == frontsector->lightlevel - && curline->sidedef->midtexture == 0) - { - return; + && backsector->floorpic == frontsector->floorpic + && backsector->lightlevel == frontsector->lightlevel + && side_midtexture(seg_sidedef(curline)) == 0) { + return; } - - - clippass: - R_ClipPassWallSegment (x1, x2-1); + + + clippass: + R_ClipPassWallSegment(x1, x2 - 1); return; - - clipsolid: - R_ClipSolidWallSegment (x1, x2-1); + + clipsolid: + R_ClipSolidWallSegment(x1, x2 - 1); } @@ -361,223 +343,253 @@ void R_AddLine (seg_t* line) // Returns true // if some part of the bbox might be visible. // -int checkcoord[12][4] = -{ - {3,0,2,1}, - {3,0,2,0}, - {3,1,2,0}, - {0}, - {2,0,2,1}, - {0,0,0,0}, - {3,1,3,0}, - {0}, - {2,0,3,1}, - {2,1,3,1}, - {2,1,3,0} -}; +isb_int8_t checkcoord[12][4] = + { + {3, 0, 2, 1}, + {3, 0, 2, 0}, + {3, 1, 2, 0}, + {0}, + {2, 0, 2, 1}, + {0, 0, 0, 0}, + {3, 1, 3, 0}, + {0}, + {2, 0, 3, 1}, + {2, 1, 3, 1}, + {2, 1, 3, 0} + }; +boolean R_CheckBBox(const node_coord_t *bspcoord) { + int boxx; + int boxy; + int boxpos; -boolean R_CheckBBox (fixed_t* bspcoord) -{ - int boxx; - int boxy; - int boxpos; + fixed_t x1; + fixed_t y1; + fixed_t x2; + fixed_t y2; - fixed_t x1; - fixed_t y1; - fixed_t x2; - fixed_t y2; - - angle_t angle1; - angle_t angle2; - angle_t span; - angle_t tspan; - - cliprange_t* start; + angle_t angle1; + angle_t angle2; + angle_t span; + angle_t tspan; + + cliprange_t *start; + + int sx1; + int sx2; - int sx1; - int sx2; - // Find the corners of the box // that define the edges from current viewpoint. - if (viewx <= bspcoord[BOXLEFT]) - boxx = 0; - else if (viewx < bspcoord[BOXRIGHT]) - boxx = 1; + if (viewx <= node_coord_to_fixed(bspcoord[BOXLEFT])) + boxx = 0; + else if (viewx < node_coord_to_fixed(bspcoord[BOXRIGHT])) + boxx = 1; else - boxx = 2; - - if (viewy >= bspcoord[BOXTOP]) - boxy = 0; - else if (viewy > bspcoord[BOXBOTTOM]) - boxy = 1; + boxx = 2; + + if (viewy >= node_coord_to_fixed(bspcoord[BOXTOP])) + boxy = 0; + else if (viewy > node_coord_to_fixed(bspcoord[BOXBOTTOM])) + boxy = 1; else - boxy = 2; - - boxpos = (boxy<<2)+boxx; + boxy = 2; + + boxpos = (boxy << 2) + boxx; if (boxpos == 5) - return true; - - x1 = bspcoord[checkcoord[boxpos][0]]; - y1 = bspcoord[checkcoord[boxpos][1]]; - x2 = bspcoord[checkcoord[boxpos][2]]; - y2 = bspcoord[checkcoord[boxpos][3]]; - + return true; + + x1 = node_coord_to_fixed(bspcoord[checkcoord[boxpos][0]]); + y1 = node_coord_to_fixed(bspcoord[checkcoord[boxpos][1]]); + x2 = node_coord_to_fixed(bspcoord[checkcoord[boxpos][2]]); + y2 = node_coord_to_fixed(bspcoord[checkcoord[boxpos][3]]); + // check clip list for an open space - angle1 = R_PointToAngle (x1, y1) - viewangle; - angle2 = R_PointToAngle (x2, y2) - viewangle; - + angle1 = R_PointToAngle(x1, y1) - viewangle; + angle2 = R_PointToAngle(x2, y2) - viewangle; + span = angle1 - angle2; // Sitting on a line? if (span >= ANG180) - return true; - + return true; + tspan = angle1 + clipangle; - if (tspan > 2*clipangle) - { - tspan -= 2*clipangle; + if (tspan > 2 * clipangle) { + tspan -= 2 * clipangle; - // Totally off the left edge? - if (tspan >= span) - return false; + // Totally off the left edge? + if (tspan >= span) + return false; - angle1 = clipangle; + angle1 = clipangle; } tspan = clipangle - angle2; - if (tspan > 2*clipangle) - { - tspan -= 2*clipangle; + if (tspan > 2 * clipangle) { + tspan -= 2 * clipangle; - // Totally off the left edge? - if (tspan >= span) - return false; - - angle2 = -clipangle; + // Totally off the left edge? + if (tspan >= span) + return false; + + angle2 = -clipangle; } // Find the first clippost // that touches the source post // (adjacent pixels are touching). - angle1 = (angle1+ANG90)>>ANGLETOFINESHIFT; - angle2 = (angle2+ANG90)>>ANGLETOFINESHIFT; + angle1 = (angle1 + ANG90) >> ANGLETOFINESHIFT; + angle2 = (angle2 + ANG90) >> ANGLETOFINESHIFT; sx1 = viewangletox[angle1]; sx2 = viewangletox[angle2]; // Does not cross a pixel. if (sx1 == sx2) - return false; + return false; sx2--; - + start = solidsegs; while (start->last < sx2) - start++; - + start++; + if (sx1 >= start->first - && sx2 <= start->last) - { - // The clippost contains the new span. - return false; + && sx2 <= start->last) { + // The clippost contains the new span. + return false; } return true; } - // // R_Subsector // Determine floor/ceiling planes. // Add sprites of things in sector. // Draw one or more line segments. // -void R_Subsector (int num) -{ - int count; - seg_t* line; - subsector_t* sub; - +void R_Subsector(int num) { + seg_t *line; + seg_t *line_limit; + subsector_t *sub; + #ifdef RANGECHECK - if (num>=numsubsectors) - I_Error ("R_Subsector: ss %i with numss = %i", - num, - numsubsectors); + if (num >= numsubsectors) + I_Error("R_Subsector: ss %i with numss = %i", + num, + numsubsectors); #endif - sscount++; +// sscount++; sub = &subsectors[num]; - frontsector = sub->sector; - count = sub->numlines; - line = &segs[sub->firstline]; + frontsector = subsector_sector(sub); + line_limit = subsector_linelimit(sub); + line = subsector_firstline(sub); - if (frontsector->floorheight < viewz) - { - floorplane = R_FindPlane (frontsector->floorheight, - frontsector->floorpic, - frontsector->lightlevel); - } - else - floorplane = NULL; +#if !NO_VISPLANES + if (sector_floorheight(frontsector) < viewz) { + floorplane = R_FindPlane(sector_floorheight(frontsector), + frontsector->floorpic, + frontsector->lightlevel); + assert(frontsector->floorpic == skyflatnum || + (floorplane->height == sector_floorheight(frontsector) && + floorplane->picnum == frontsector->floorpic)); + } else + floorplane = NULL; + + if (sector_ceilingheight(frontsector) > viewz + || frontsector->ceilingpic == skyflatnum) { + ceilingplane = R_FindPlane(sector_ceilingheight(frontsector), + frontsector->ceilingpic, + frontsector->lightlevel); + assert(frontsector->ceilingpic == skyflatnum || + (ceilingplane->height == sector_ceilingheight(frontsector) && + ceilingplane->picnum == frontsector->ceilingpic)); + } else + ceilingplane = NULL; +#endif - if (frontsector->ceilingheight > viewz - || frontsector->ceilingpic == skyflatnum) - { - ceilingplane = R_FindPlane (frontsector->ceilingheight, - frontsector->ceilingpic, - frontsector->lightlevel); - } - else - ceilingplane = NULL; - - R_AddSprites (frontsector); + R_AddSprites(frontsector); - while (count--) - { - R_AddLine (line); - line++; + while (line < line_limit) { + R_AddLine(line); + line += seg_next_step(line); } // check for solidsegs overflow - extremely unsatisfactory! - if(newend > &solidsegs[32]) + if (newend > &solidsegs[32]) I_Error("R_Subsector: solidsegs overflow (vanilla may crash here)\n"); } - - // // RenderBSPNode // Renders all subsectors below a given node, // traversing subtree recursively. // Just call with BSP root. -void R_RenderBSPNode (int bspnum) +#if !WHD_SUPER_TINY +void R_RenderBSPNode(int bspnum) +#else +void R_RenderBSPNode (int bspnum, node_coord_t *bbox) +#endif { - node_t* bsp; - int side; + node_t *bsp; + int side; + + // todo graham we should loop not recurse perhaps? // Found a subsector? - if (bspnum & NF_SUBSECTOR) - { - if (bspnum == -1) - R_Subsector (0); - else - R_Subsector (bspnum&(~NF_SUBSECTOR)); - return; + if (bspnum & NF_SUBSECTOR) { +// printf("SS %d\n", bspnum); + if (bspnum == -1) + R_Subsector(0); + else + R_Subsector(bspnum & (~NF_SUBSECTOR)); + return; } - + +#if PICO_ON_DEVICE + static int t0; + uint32_t t = time_us_32(); + if ((t - t0) > 3000) { + I_UpdateSound(); + t0 = t; + } +#endif bsp = &nodes[bspnum]; - + // Decide which side the view point is on. - side = R_PointOnSide (viewx, viewy, bsp); + side = R_PointOnSide(viewx, viewy, bsp); +// printf("NODE %d v %d,%d p %d,%d dir %d,%d\n", bspnum, viewx, viewy, bsp->x, bsp->y, bsp->dx, bsp->dy); // Recursively divide front space. - R_RenderBSPNode (bsp->children[side]); +#if !WHD_SUPER_TINY + R_RenderBSPNode(bsp->children[side]); +#else + node_coord_t subbox[4]; + subbox[BOXLEFT] = bbox[BOXLEFT] + (((bsp->bbox_lw[side] & 0xf0u) * (bbox[BOXRIGHT] - bbox[BOXLEFT])) >> 8u); + subbox[BOXRIGHT] = subbox[BOXLEFT] + ((((bsp->bbox_lw[side] & 0xfu) + 1) * (bbox[BOXRIGHT] - subbox[BOXLEFT])) >> 4u); + subbox[BOXBOTTOM] = bbox[BOXBOTTOM] + (((bsp->bbox_th[side] & 0xf0u) * (bbox[BOXTOP] - bbox[BOXBOTTOM])) >> 8u); + subbox[BOXTOP] = subbox[BOXBOTTOM] + ((((bsp->bbox_th[side] & 0xfu) + 1) * (bbox[BOXTOP] - subbox[BOXBOTTOM])) >> 4u); + R_RenderBSPNode(bsp_child(bspnum, side), subbox); +#endif // Possibly divide back space. - if (R_CheckBBox (bsp->bbox[side^1])) - R_RenderBSPNode (bsp->children[side^1]); +#if !WHD_SUPER_TINY +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" + if (R_CheckBBox(bsp->bbox[side ^ 1])) + R_RenderBSPNode(bsp->children[side ^ 1]); +#pragma GCC diagnostic pop +#else + subbox[BOXLEFT] = bbox[BOXLEFT] + (((bsp->bbox_lw[side^1] & 0xf0u) * (bbox[BOXRIGHT] - bbox[BOXLEFT])) >> 8u); + subbox[BOXRIGHT] = subbox[BOXLEFT] + ((((bsp->bbox_lw[side^1] & 0xfu) + 1) * (bbox[BOXRIGHT] - subbox[BOXLEFT])) >> 4u); + subbox[BOXBOTTOM] = bbox[BOXBOTTOM] + (((bsp->bbox_th[side^1] & 0xf0u) * (bbox[BOXTOP] - bbox[BOXBOTTOM])) >> 8u); + subbox[BOXTOP] = subbox[BOXBOTTOM] + ((((bsp->bbox_th[side^1] & 0xfu) + 1) * (bbox[BOXTOP] - subbox[BOXBOTTOM])) >> 4u); + if (R_CheckBBox(subbox)) + R_RenderBSPNode(bsp_child(bspnum, side^1), subbox); +#endif } diff --git a/src/doom/r_bsp.h b/src/doom/r_bsp.h index 1723e686..a6ea6ae1 100644 --- a/src/doom/r_bsp.h +++ b/src/doom/r_bsp.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -39,8 +40,10 @@ extern boolean markceiling; extern boolean skymap; +#if !NO_DRAWSEGS extern drawseg_t drawsegs[MAXDRAWSEGS]; extern drawseg_t* ds_p; +#endif extern lighttable_t** hscalelight; extern lighttable_t** vscalelight; @@ -54,8 +57,11 @@ typedef void (*drawfunc_t) (int start, int stop); void R_ClearClipSegs (void); void R_ClearDrawSegs (void); - +#if !USE_WHD void R_RenderBSPNode (int bspnum); +#else +void R_RenderBSPNode (int bspnum, node_coord_t *bbox); +#endif #endif diff --git a/src/doom/r_data.c b/src/doom/r_data.c index 4a452702..0cf5b3e6 100644 --- a/src/doom/r_data.c +++ b/src/doom/r_data.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -17,6 +18,7 @@ // generation of lookups, caching, retrieval by name. // +#if !USE_WHD #include #include "deh_main.h" @@ -34,9 +36,8 @@ #include "doomstat.h" #include "r_sky.h" - - #include "r_data.h" +#include // // Graphics. @@ -47,7 +48,6 @@ // - // // Texture definition. // Each texture is composed of one or more patches, @@ -126,41 +126,35 @@ struct texture_s -int firstflat; -int lastflat; -int numflats; +lumpindex_t firstflat; +lumpindex_t numflats; -int firstpatch; -int lastpatch; -int numpatches; +lumpindex_t firstspritelump; +lumpindex_t lastspritelump; +lumpindex_t numspritelumps; -int firstspritelump; -int lastspritelump; -int numspritelumps; - -int numtextures; -texture_t** textures; -texture_t** textures_hashtable; - - -int* texturewidthmask; +cardinal_t numtextures; +static texture_t** textures; +static texture_t** textures_hashtable; +static int* texturewidthmask; // needed for texture pegging -fixed_t* textureheight; -int* texturecompositesize; -short** texturecolumnlump; -unsigned short** texturecolumnofs; -byte** texturecomposite; +fixed_t* textureheight; + +static int* texturecompositesize; +static short** texturecolumnlump; +static unsigned short** texturecolumnofs; +static byte** texturecomposite; // for global animation -int* flattranslation; -int* texturetranslation; +flatnum_t* flattranslation; +texnum_t* texturetranslation; // needed for pre rendering -fixed_t* spritewidth; +fixed_t* spritewidth; fixed_t* spriteoffset; fixed_t* spritetopoffset; -lighttable_t *colormaps; +const lighttable_t *colormaps; // @@ -216,7 +210,6 @@ R_DrawColumnInCache } - // // R_GenerateComposite // Using the texture definition, @@ -227,8 +220,8 @@ void R_GenerateComposite (int texnum) { byte* block; texture_t* texture; - texpatch_t* patch; - patch_t* realpatch; + texpatch_t* patch; + should_be_const patch_t* realpatch; int x; int x1; int x2; @@ -239,6 +232,8 @@ void R_GenerateComposite (int texnum) texture = textures[texnum]; +// DEH_printf("Composite texture %d %.8s %d\n", texnum, textures[texnum]->name, texturecompositesize[texnum]); + block = Z_Malloc (texturecompositesize[texnum], PU_STATIC, &texturecomposite[texnum]); @@ -247,15 +242,13 @@ void R_GenerateComposite (int texnum) colofs = texturecolumnofs[texnum]; // Composite the columns together. - patch = texture->patches; - for (i=0 , patch = texture->patches; ipatchcount; i++, patch++) { realpatch = W_CacheLumpNum (patch->patch, PU_CACHE); x1 = patch->originx; - x2 = x1 + SHORT(realpatch->width); + x2 = x1 + patch_width(realpatch); if (x1<0) x = 0; @@ -272,7 +265,7 @@ void R_GenerateComposite (int texnum) continue; patchcol = (column_t *)((byte *)realpatch - + LONG(realpatch->columnofs[x-x1])); + + patch_columnofs(realpatch, x-x1)); R_DrawColumnInCache (patchcol, block + colofs[x], patch->originy, @@ -286,8 +279,6 @@ void R_GenerateComposite (int texnum) Z_ChangeTag (block, PU_CACHE); } - - // // R_GenerateLookup // @@ -295,8 +286,8 @@ void R_GenerateLookup (int texnum) { texture_t* texture; byte* patchcount; // patchcount[texture->width] - texpatch_t* patch; - patch_t* realpatch; + texpatch_t* patch; + should_be_const patch_t* realpatch; int x; int x1; int x2; @@ -327,7 +318,7 @@ void R_GenerateLookup (int texnum) { realpatch = W_CacheLumpNum (patch->patch, PU_CACHE); x1 = patch->originx; - x2 = x1 + SHORT(realpatch->width); + x2 = x1 + patch_width(realpatch); if (x1 < 0) x = 0; @@ -340,7 +331,7 @@ void R_GenerateLookup (int texnum) { patchcount[x]++; collump[x] = patch->patch; - colofs[x] = LONG(realpatch->columnofs[x-x1])+3; + colofs[x] = patch_columnofs(realpatch, x-x1)+3; } } @@ -379,7 +370,7 @@ void R_GenerateLookup (int texnum) // // R_GetColumn // -byte* +texturecolumn_t R_GetColumn ( int tex, int col ) @@ -400,6 +391,14 @@ R_GetColumn return texturecomposite[tex] + ofs; } +maskedcolumn_t R_GetMaskedColumn(int tex, int col) { + const byte *p = R_GetColumn(tex, col); + return (column_t *)(p - 3); +} + +maskedcolumn_t R_GetPatchColumn(const patch_t *patch, int col) { + return (const column_t *) ((byte *) patch + patch_columnofs(patch, col)); +} static void GenerateTextureHashTable(void) { @@ -458,13 +457,13 @@ void R_InitTextures (void) int i; int j; - int* maptex; - int* maptex2; - int* maptex1; + should_be_const int* maptex; + should_be_const int* maptex2; + should_be_const int* maptex1; char name[9]; - char* names; - char* name_p; + should_be_const char* names; + should_be_const char* name_p; int* patchlookup; @@ -476,19 +475,20 @@ void R_InitTextures (void) int numtextures1; int numtextures2; - int* directory; + should_be_const int* directory; int temp1; int temp2; int temp3; - // Load the patch names from pnames.lmp. + // Load the patch names from pes.lmp. name[8] = 0; names = W_CacheLumpName (DEH_String("PNAMES"), PU_STATIC); nummappatches = LONG ( *((int *)names) ); + printf("nummappatches %d\n", nummappatches); name_p = names + 4; - patchlookup = Z_Malloc(nummappatches*sizeof(*patchlookup), PU_STATIC, NULL); + patchlookup = Z_Malloc(nummappatches*sizeof(*patchlookup), PU_STATIC, 0); for (i = 0; i < nummappatches; i++) { @@ -518,7 +518,7 @@ void R_InitTextures (void) maxoff2 = 0; } numtextures = numtextures1 + numtextures2; - + textures = Z_Malloc (numtextures * sizeof(*textures), PU_STATIC, 0); texturecolumnlump = Z_Malloc (numtextures * sizeof(*texturecolumnlump), PU_STATIC, 0); texturecolumnofs = Z_Malloc (numtextures * sizeof(*texturecolumnofs), PU_STATIC, 0); @@ -528,7 +528,8 @@ void R_InitTextures (void) textureheight = Z_Malloc (numtextures * sizeof(*textureheight), PU_STATIC, 0); totalwidth = 0; - + +#if !PICO_BUILD // Really complex printing shit... temp1 = W_GetNumForName (DEH_String("S_START")); // P_??????? temp2 = W_GetNumForName (DEH_String("S_END")) - 1; @@ -547,11 +548,14 @@ void R_InitTextures (void) for (i = 0; i < temp3 + 10; i++) printf("\b"); } +#endif for (i=0 ; ipatchcount)-1), PU_STATIC, 0); - + texture->patchcount = SHORT(mtexture->patchcount); + texture->width = SHORT(mtexture->width); texture->height = SHORT(mtexture->height); - texture->patchcount = SHORT(mtexture->patchcount); - + memcpy (texture->name, mtexture->name, sizeof(texture->name)); mpatch = &mtexture->patches[0]; patch = &texture->patches[0]; +// printf("Texture %d %.8s patches %d\n", i, texture->name, texture->patchcount); for (j=0 ; jpatchcount ; j++, mpatch++, patch++) { patch->originx = SHORT(mpatch->originx); @@ -591,7 +596,11 @@ void R_InitTextures (void) I_Error ("R_InitTextures: Missing patch in texture %s", texture->name); } - } +// { +// lumpinfo_t *lump = lumpinfo[patch->patch]; +// printf(" %d, %d %.8s\n", patch->originx, patch->originy, lump->name); +// } + } texturecolumnlump[i] = Z_Malloc (texture->width*sizeof(**texturecolumnlump), PU_STATIC,0); texturecolumnofs[i] = Z_Malloc (texture->width*sizeof(**texturecolumnofs), PU_STATIC,0); @@ -615,7 +624,7 @@ void R_InitTextures (void) for (i=0 ; iwidth)<leftoffset)<topoffset)<name, name, 8) ) @@ -761,7 +793,7 @@ int R_CheckTextureNumForName(const char *name) texture = texture->next; } - + return -1; } @@ -775,7 +807,7 @@ int R_CheckTextureNumForName(const char *name) int R_TextureNumForName(const char *name) { int i; - + i = R_CheckTextureNumForName (name); if (i==-1) @@ -816,7 +848,7 @@ void R_PrecacheLevel (void) return; // Precache flats. - flatpresent = Z_Malloc(numflats, PU_STATIC, NULL); + flatpresent = Z_Malloc(numflats, PU_STATIC, 0); memset (flatpresent,0,numflats); for (i=0 ; isize; + flatmemory += lump_info(lump)->size; W_CacheLumpNum(lump, PU_CACHE); } } @@ -840,14 +874,14 @@ void R_PrecacheLevel (void) Z_Free(flatpresent); // Precache textures. - texturepresent = Z_Malloc(numtextures, PU_STATIC, NULL); + texturepresent = Z_Malloc(numtextures, PU_STATIC, 0); memset (texturepresent,0, numtextures); for (i=0 ; ipatchcount ; j++) { lump = texture->patches[j].patch; - texturememory += lumpinfo[lump]->size; + texturememory += lump_info(lump)->size; W_CacheLumpNum(lump , PU_CACHE); } } @@ -877,20 +913,22 @@ void R_PrecacheLevel (void) Z_Free(texturepresent); // Precache sprites. - spritepresent = Z_Malloc(numsprites, PU_STATIC, NULL); + spritepresent = Z_Malloc(numsprites, PU_STATIC, 0); memset (spritepresent,0, numsprites); - for (th = thinkercap.next ; th != &thinkercap ; th=th->next) + for (th = thinker_next(&thinkercap) ; th != &thinkercap ; th=thinker_next(th)) { - if (th->function.acp1 == (actionf_p1)P_MobjThinker) - spritepresent[((mobj_t *)th)->sprite] = 1; + if (th->function == ThinkF_P_MobjThinker) + spritepresent[mobj_sprite((mobj_t *)th)] = 1; } spritememory = 0; + int sprite_count = 0; for (i=0 ; ilump[k]; - spritememory += lumpinfo[lump]->size; + spritememory += lump_info(lump)->size; W_CacheLumpNum(lump , PU_CACHE); } } } + printf("PRECACHE Summary:\n"); + printf(" Flats %d/%d = %d bytes\n", flat_count, numflats, flatmemory); + printf(" Textures %d/%d = %d bytes\n", texture_count, numtextures, texturememory); + printf(" Sprites %d/%d = %d bytes\n", sprite_count, numsprites, spritememory); Z_Free(spritepresent); } - - - - +#endif \ No newline at end of file diff --git a/src/doom/r_data.h b/src/doom/r_data.h index 30ab1af6..99f1668f 100644 --- a/src/doom/r_data.h +++ b/src/doom/r_data.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -24,12 +25,82 @@ #include "r_defs.h" #include "r_state.h" +#if !USE_WHD +typedef const byte *texturecolumn_t; +typedef const column_t *maskedcolumn_t; -// Retrieve column data for span blitting. -byte* -R_GetColumn -( int tex, - int col ); +texturecolumn_t R_GetColumn + ( int tex, + int col ); +maskedcolumn_t R_GetMaskedColumn + ( int tex, + int col ); +maskedcolumn_t R_GetPatchColumn(const patch_t *patch, int col); +#define lookup_texture(t) (t) +#define lookup_masked_texture(t) (t) +#else + +// drawable is local +// make this non packed ideally divisible by power of 2 +typedef struct { + int16_t real_id; +} framedrawable_t; +#define MAX_FRAME_DRAWABLES 128 +extern framedrawable_t framedrawables[MAX_FRAME_DRAWABLES]; +extern uint8_t translated_fds[3]; // fds which are for trasnlated other players +extern uint8_t num_framedrawables; +framedrawable_t *lookup_texture(int texture_id); +framedrawable_t *lookup_masked_texture(int texture_id); +framedrawable_t *lookup_patch(int patch_id); +void reset_framedrawables(void); + +typedef struct { + uint8_t fd_num; + uint8_t col; + int16_t real_id; +} drawcolumn_t; + +static_assert(sizeof(drawcolumn_t) == 4, ""); + +typedef drawcolumn_t texturecolumn_t; +typedef drawcolumn_t maskedcolumn_t; + +static inline drawcolumn_t make_drawcolumn(framedrawable_t *fd, int col) { + assert(col >=0 && col < 256); + assert(fd); + int fd_num = fd - framedrawables; + assert(fd_num >=0 && fd_num < MAX_FRAME_DRAWABLES); + drawcolumn_t rc = { + .fd_num = (uint8_t)fd_num, + .col = (uint8_t)col, + .real_id = fd->real_id, + }; + return rc; +} + +static inline texturecolumn_t R_GetColumn(framedrawable_t *fd, int col) { + assert(fd->real_id >= 0); + // had seen this in the past with bug related to certain maps (hopefully now fixed everywhere) + // todo ^ happens in tnt for now due to anim issue + assert( whd_textures[fd->real_id].width != 0); // texture is missing + col &= (whd_textures[fd->real_id].width - 1); + return make_drawcolumn(fd, col); +} + +static inline maskedcolumn_t R_GetMaskedColumn(framedrawable_t *fd, uint8_t col) { + //assert(fd->real_id <= 0); // this should be a patch also + if (fd->real_id >= 0) { + // we do see regular textures as masked columns.. we'll just draw as it + return R_GetColumn(fd, col); + } + return make_drawcolumn(fd, col); +} + +static inline maskedcolumn_t R_GetPatchColumn(framedrawable_t *fd, uint8_t col) { + assert(fd->real_id <= 0); + return make_drawcolumn(fd, col); +} +#endif // I/O, setting up the stuff. @@ -37,15 +108,23 @@ void R_InitData (void); void R_PrecacheLevel (void); -// Retrieval. -// Floor/ceiling opaque texture tiles, -// lookup by name. For animation? -int R_FlatNumForName(const char *name); // Called by P_Ticker for switches and animations, // returns the texture number for the texture name. -int R_TextureNumForName(const char *name); -int R_CheckTextureNumForName(const char *name); +#if !USE_WHD +int R_TextureNumForName(texturename_t name); +int R_CheckTextureNumForName(texturename_t name); +int R_FlatNumForName(flatname_t name); +#define resolve_vpatch_handle(h) (h) +#else +#define R_TextureNumForName(x) (x) +#define R_CheckTextureNumForName(x) (x) +#define R_FlatNumForName(x) (x) +extern const uint16_t *whd_vpatch_numbers; +//#define check_vpatch_handle(handle) ({assert((handle) + +#include "deh_main.h" +#include "i_swap.h" +#include "i_system.h" +#include "z_zone.h" + + +#include "w_wad.h" + +#include "doomdef.h" +#include "m_misc.h" +#include "r_local.h" +#include "p_local.h" + +#include "doomstat.h" +#include "r_sky.h" +#include "r_data.h" +#include + +// +// Graphics. +// DOOM graphics for walls and sprites +// is stored in vertical runs of opaque pixels (posts). +// A column is composed of zero or more posts, +// a patch or sprite is composed of zero or more columns. +// + + + +lumpindex_t firstflat; +lumpindex_t numflats; + +lumpindex_t firstspritelump; +lumpindex_t lastspritelump; +lumpindex_t numspritelumps; + +cardinal_t numtextures; +const whdtexture_t *whd_textures; +// todo if this is too big, we can do cachelump num directly (and save about 400 bytes) +const uint16_t *whd_vpatch_numbers; + +// needed for texture pegging +fixed_t* textureheight; + +// for global animation +flatname_t whd_flattranslation[NUM_SPECIAL_FLATS]; +const uint8_t *whd_specialtoflat; +texturename_t whd_texturetranslation[NUM_SPECIAL_TEXTURES]; + +// framedrawable stuff +framedrawable_t framedrawables[MAX_FRAME_DRAWABLES]; +uint8_t num_framedrawables; +static_assert(MAX_FRAME_DRAWABLES < (1u << (8 * sizeof(num_framedrawables))), ""); +framedrawable_t *skytexture_fd; +uint8_t dc_translation_index; +byte translated_fds[3]; + +// needed for pre rendering +const int32_t *whd_sprite_meta; +const uint16_t *whd_sprite_frame_meta; + +const lighttable_t *colormaps; + +// +// MAPTEXTURE_T CACHING +// When a texture is first needed, +// it counts the number of composite columns +// required in the texture and allocates space +// for a column directory and any new columns. +// The directory will simply point inside other patches +// if there is only one patch in a given column, +// but any columns with multiple patches +// will have new column_ts generated. +// + + + +// +// R_DrawColumnInCache +// Clip and draw a column +// from a patch into a cached post. +// +static void +R_DrawColumnInCache + ( column_t* patch, + byte* cache, + int originy, + int cacheheight ) +{ + I_Error("no can do"); +} + + + +// +// R_GenerateComposite +// Using the texture definition, +// the composite texture is created from the patches, +// and each column is cached. +// +void R_GenerateComposite (int texnum) +{ +} + +// +// R_GenerateLookup +// +void R_GenerateLookup (int texnum) +{ +} + +void reset_framedrawables(void) { +// printf("FD %d\n", num_framedrawables); + num_framedrawables = 0; + translated_fds[0] = translated_fds[1] = translated_fds[2] = 0xff; + skytexture_fd = lookup_texture(skytexture); + if (!whd_textures[skytexture].patch_count) { + // this is a single covering opaque patch at 0, 0 + skytexture_patch = whd_textures[skytexture].patch0; + } else { + assert(whd_textures[skytexture].patch_count == 1); + uint8_t *patch_table = &((uint8_t *)whd_textures)[whd_textures[skytexture].metdata_offset]; + skytexture_patch = patch_table[0] + (patch_table[1] << 8); + } +} + +framedrawable_t *lookup_texture(int real_id) { + if (!real_id) return NULL; // E4M5 at least has 0 as texture values + // todo hash table + framedrawable_t *fd = framedrawables; + if (dc_translation_index) { + // since the only things translated are the players, we just track one fd per translation index + if (translated_fds[dc_translation_index-1] < num_framedrawables) { + if (framedrawables[translated_fds[dc_translation_index-1]].real_id == real_id) return &framedrawables[translated_fds[dc_translation_index-1]]; + } + translated_fds[dc_translation_index-1] = num_framedrawables; + fd += num_framedrawables; + } else { + for (int i = 0; i < num_framedrawables; i++, fd++) { + if (fd->real_id == real_id) { + return fd; + } + } + } + hard_assert(num_framedrawables < MAX_FRAME_DRAWABLES); + num_framedrawables++; + fd->real_id = (int16_t)real_id; + return fd; +} + +framedrawable_t *lookup_patch(int patchnum) { + return lookup_texture(-(patchnum+firstspritelump)); +} + +framedrawable_t *lookup_masked_texture(int texturenum) { + if (!whd_textures[texturenum].patch_count) { + // this is currently a single patch at 0,0 + return lookup_texture(-whd_textures[texturenum].patch0); + } else { + // turns out this does happen e.g. in E4M3 in ultimate doom + return lookup_texture(texturenum); + } +} +// +// R_InitTextures +// Initializes the texture list +// with the textures from the world map. +// +void R_InitTextures (void) +{ + lumpindex_t lump = W_CheckNumForName("TEXTURE1"); + const uint8_t *data = W_CacheLumpNum(lump, PU_STATIC); + numtextures = data[0] | (data[1] << 8); + whd_textures = (const whdtexture_t *)(data + 2); + assert(numtextures >= NUM_SPECIAL_TEXTURES); + + for (int i=0 ; i< count_of(whd_texturetranslation) ; i++) + whd_texturetranslation[i] = i; +} + +// +// R_InitFlats +// +void R_InitFlats (void) +{ + int i; + + firstflat = W_GetNumForName (DEH_String("F_START")) + 1; + lumpindex_t lastflat = W_GetNumForName (DEH_String("F_END")) - 1; + numflats = lastflat - firstflat + 1; +#if USE_FLAT_MAX_256 + assert(numflats <= 256); +#endif + assert(W_LumpLength(firstflat-1) == NUM_SPECIAL_FLATS + numflats ); + whd_specialtoflat = W_CacheLumpNum(firstflat - 1, PU_STATIC); + + for (i=0 ; ipatchcount ; j++) +// { +// lump = texture->patches[j].patch; +// texturememory += lump_info(lump)->size; +// W_CacheLumpNum(lump , PU_CACHE); +// } +// } +// +// Z_Free(texturepresent); +// + // Precache sprites. +// spritepresent = Z_Malloc(numsprites, PU_STATIC, 0); +// memset (spritepresent,0, numsprites); +// +// for (th = thinker_next(&thinkercap) ; th != &thinkercap ; th=thinker_next(th)) +// { +// if (th->function == ThinkF_P_MobjThinker) +// spritepresent[mobj_sprite((mobj_t *)th)] = 1; +// } +// +// spritememory = 0; +// int sprite_count = 0; +// for (i=0 ; ilump[k]; +// spritememory += lump_info(lump)->size; +// W_CacheLumpNum(lump , PU_CACHE); +// } +// } +// } + +// printf("PRECACHE Summary:\n"); +// printf(" Flats %d/%d = %d bytes\n", flat_count, numflats, flatmemory); +// printf(" Textures %d/%d = %d bytes\n", texture_count, numtextures, texturememory); +// printf(" Sprites %d/%d = %d bytes\n", sprite_count, numsprites, spritememory); +// Z_Free(spritepresent); +#endif +} + +#endif \ No newline at end of file diff --git a/src/doom/r_defs.h b/src/doom/r_defs.h index d87ca38a..8e40bd2b 100644 --- a/src/doom/r_defs.h +++ b/src/doom/r_defs.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -27,20 +28,26 @@ // Some more or less basic data types // we depend on. #include "m_fixed.h" +#include "tables.h" // We rely on the thinker data struct // to handle sound origins in sectors. #include "d_think.h" // SECTORS do store MObjs anyway. -#include "p_mobj.h" +#include "doomdata.h" +#include "whddata.h" #include "i_video.h" #include "v_patch.h" +// xy position, but also noting that the address of this can +// be used to identify an object which has an XY position +typedef struct xy_positioned_s { + fixed_t x, y; +} xy_positioned_t; - - +#if !NO_DRAWSEGS // Silhouette, needed for clipping Segs (mainly) // and sprites representing things. #define SIL_NONE 0 @@ -49,9 +56,7 @@ #define SIL_BOTH 3 #define MAXDRAWSEGS 256 - - - +#endif // @@ -64,79 +69,144 @@ // Note: transformed values not buffered locally, // like some DOOM-alikes ("wt", "WebView") did. // +#if !USE_RAW_MAPVERTEX typedef struct { - fixed_t x; - fixed_t y; - + fixed_t x; + fixed_t y; } vertex_t; - +#else +typedef const mapvertex_t vertex_t; +#endif // Forward of LineDefs, for Sectors. struct line_s; -// Each sector has a degenmobj_t in its center -// for sound origin purposes. -// I suppose this does not handle sound from -// moving objects (doppler), because -// position is prolly just buffered, not -// updated. -typedef struct -{ - thinker_t thinker; // not used for anything - fixed_t x; - fixed_t y; - fixed_t z; - -} degenmobj_t; +typedef struct mobj_s mobj_t; +// floor/ceiling height +#if DOOM_SMALL +typedef int16_t sectorheight_t; +#else +typedef fixed_t sectorheight_t; +#endif // // The SECTORS record, at runtime. // Stores things/mobjs. // typedef struct { - fixed_t floorheight; - fixed_t ceilingheight; - short floorpic; - short ceilingpic; - short lightlevel; - short special; - short tag; - + sectorheight_t rawfloorheight; // mutable (sometimes) + sectorheight_t rawceilingheight; // mutable (sometimes) + short lightlevel; // mutable // todo seems like this could be 4 bits // 0 = untraversed, 1,2 = sndlines -1 - int soundtraversed; +#if !DOOM_SMALL +int soundtraversed; // temporary used by P_RecurseiveSound +#else +uint8_t soundtraversed:2; // actually i think it is only ever 0 or 1 +#endif +#if !DOOM_SMALL + short special; +#else + uint8_t special:5; +#endif + +#if !DOOM_SMALL + short floorpic; // mutable (sometimes) + short ceilingpic; // seems not to be mutable +#else + uint8_t floorpic; + uint8_t ceilingpic; // (CAN BE CONST) +#endif + + short tag; // immutable; how big? (CAN BE CONST) + cardinal_t linecount; // seems unlikely to be more than 8 bit really (CAN BE CONST) // thing that made a sound (or null) - mobj_t* soundtarget; + shortptr_t /*mobj_t*/ soundtarget; +#if !DOOM_SMALL // mapblock bounding box for height changes int blockbox[4]; - - // origin for any sounds played by the sector - degenmobj_t soundorg; - - // if == validcount, already checked - int validcount; +#else + uint8_t blockbox[4]; // (CAN BE CONST) +#endif // list of mobjs in sector - mobj_t* thinglist; + shortptr_t /*mobj_t*/thinglist; // thinker_t for reversable actions - void* specialdata; + shortptr_t /*void*/ specialdata; - int linecount; - struct line_s** lines; // [linecount] size - +#if USE_INDEX_LINEBUFFER + cardinal_t line_index; // within linebuffer of first line (CAN BE CONST) +#else + rowad_const struct line_s** lines; // [linecount] size +#endif + + // origin for any sounds played by the sector + xy_positioned_t soundorg; // middle of bbox (CAN BE CONST) + +#if !DOOM_SMALL + // if == validcount, already checked + int validcount; +#endif } sector_t; +#if !DOOM_SMALL +#define sector_check_reset() ((void)0) +static inline boolean sector_validcount_update_check(should_be_const sector_t *s, int validcount) { + if (validcount == s->validcount) { + return true; + } + hack_rowad_p(sector_t, s, validcount) = validcount; + return false; +} +#else +extern uint32_t *line_sector_check_bitmap; // shared by both line and sector checking - todo actually just alloc on the fly +void sector_check_reset(void); +boolean sector_validcount_update_check_impl(should_be_const sector_t *s); +#define sector_validcount_update_check(s, vc) sector_validcount_update_check_impl(s) +#endif +#if !DOOM_SMALL +#define sector_floorheight(s) ((s)->rawfloorheight) +#define sector_ceilingheight(s) ((s)->rawceilingheight) +#define sector_set_floorheight(s, h) (s)->rawfloorheight = h +#define sector_set_ceilingheight(s, h) (s)->rawceilingheight = h +#define sector_delta_floorheight(s, d) (s)->rawfloorheight += d +#define sector_delta_ceilingheight(s, d) (s)->rawceilingheight += d +#else +#define SECTORHEIGHT_SHIFT 14 +#define SECTORHEIGHT_ZERO_MASK (1u << (SECTORHEIGHT_SHIFT - 1)) +#define sector_floorheight(s) ((s)->rawfloorheight << SECTORHEIGHT_SHIFT) +#define sector_ceilingheight(s) ((s)->rawceilingheight << SECTORHEIGHT_SHIFT) +static inline void sector_set_floorheight(sector_t *s, fixed_t h) { + assert(!(h & SECTORHEIGHT_ZERO_MASK)); + s->rawfloorheight = h >> SECTORHEIGHT_SHIFT; +} +static inline void sector_delta_floorheight(sector_t *s, fixed_t d) { + assert(!(d & SECTORHEIGHT_ZERO_MASK)); + s->rawfloorheight += d >> SECTORHEIGHT_SHIFT; +} +static inline void sector_set_ceilingheight(sector_t *s, fixed_t h) { + assert(!(h & SECTORHEIGHT_ZERO_MASK)); + s->rawceilingheight = h >> SECTORHEIGHT_SHIFT; +} +static inline void sector_delta_ceilingheight(sector_t *s, fixed_t d) { + assert(!(d & SECTORHEIGHT_ZERO_MASK)); + s->rawceilingheight += d >> SECTORHEIGHT_SHIFT; +} +#endif - +#if PICO_ON_DEVICE +static_assert(sizeof(sector_t) == 0x24, ""); +#endif // // The SideDef. // +#if !USE_WHD typedef struct { // add this to the calculated texture column @@ -153,8 +223,14 @@ typedef struct // Sector the SideDef is facing. sector_t* sector; - } side_t; +#else +#if WHD_SUPER_TINY +typedef const uint8_t side_t; // these are compressed +#else +typedef const whdsidedef_t side_t; +#endif +#endif @@ -167,16 +243,14 @@ typedef enum ST_VERTICAL, ST_POSITIVE, ST_NEGATIVE - } slopetype_t; - - +#if !USE_RAW_MAPLINEDEF typedef struct line_s { // Vertices, from v1 to v2. - vertex_t* v1; - vertex_t* v2; + const vertex_t* v1; + const vertex_t* v2; // Precalculated v2 - v1 for side checking. fixed_t dx; @@ -204,14 +278,35 @@ typedef struct line_s sector_t* backsector; // if == validcount, already checked - int validcount; + int validcount; // ACK_MUTATED */ +#if !DOOM_SMALL // thinker_t for reversable actions - void* specialdata; + void* specialdata; +#endif } line_t; - - - +typedef line_t fake_line_t; +#define fakeline_to_line(fake) (fake) +static inline void init_fake_line(fake_line_t *line, int tag) { line->tag = tag; } +#else +#if WHD_SUPER_TINY +typedef const uint8_t line_t; +typedef struct { + uint8_t data[6]; +} fake_line_t; +static inline void init_fake_line(fake_line_t *line, int tag) { + line->data[1] = ML_HAS_TAG >> 8; + assert(tag >=0 && tag <256); + line->data[5] = tag; +} +#define fakeline_to_line(fake) (fake)->data +#else +typedef const maplinedef_t line_t; +typedef maplinedef_t fake_line_t; // just used for a temp junk instance +#define fakeline_to_line(fake) (fake) +static inline void init_fake_line(fake_line_t *line, int tag) { line->tag = tag; } +#endif +#endif // // A SubSector. @@ -220,23 +315,25 @@ typedef struct line_s // indicating the visible walls that define // (all or some) sides of a convex BSP leaf. // +#if !USE_WHD typedef struct subsector_s { sector_t* sector; short numlines; short firstline; - } subsector_t; - - +#else +typedef const uint16_t subsector_t; +#endif // // The LineSeg. // +#if !USE_RAW_MAPSEG typedef struct { - vertex_t* v1; - vertex_t* v2; + const vertex_t* v1; + const vertex_t* v2; fixed_t offset; @@ -250,33 +347,50 @@ typedef struct // backsector is NULL for one sided lines sector_t* frontsector; sector_t* backsector; - } seg_t; - - +#else +#if !WHD_SUPER_TINY +typedef const mapseg_t seg_t; +#else +typedef const uint8_t seg_t; +#endif +#endif // // BSP node. // +#if !USE_RAW_MAPNODE +typedef fixed_t node_coord_t; typedef struct { // Partition line. - fixed_t x; - fixed_t y; - fixed_t dx; - fixed_t dy; + node_coord_t x; + node_coord_t y; + node_coord_t dx; + node_coord_t dy; // Bounding box for each child. - fixed_t bbox[2][4]; + node_coord_t bbox[2][4]; // If NF_SUBSECTOR its a subsector. unsigned short children[2]; - + } node_t; - - - +#define node_coord_to_fixed(n) (n) +#define node_coord_to_int16(n) ((n) >> FRACBITS) +#define int16_to_node_coord(n) ((n) << FRACBITS) +#else +typedef int16_t node_coord_t; +#if !WHD_SUPER_TINY +typedef const mapnode_t node_t; +#else +typedef const whdnode_t node_t; +#endif +#define node_coord_to_fixed(n) (((fixed_t)(n)) << FRACBITS) +#define node_coord_to_int16(n) (n) +#define int16_to_node_coord(n) (n) +#endif // PC direct to screen pointers //B UNUSED - keep till detailshift in r_draw.c resolved //extern byte* destview; @@ -299,7 +413,7 @@ typedef pixel_t lighttable_t; - +#ifndef NO_DRAWSEGS // // ? // @@ -329,7 +443,7 @@ typedef struct drawseg_s short* maskedtexturecol; } drawseg_t; - +#endif // A vissprite_t is a thing @@ -344,6 +458,7 @@ typedef struct vissprite_s int x1; int x2; +#if !DOOM_TINY // for line side calculation fixed_t gx; fixed_t gy; @@ -351,6 +466,7 @@ typedef struct vissprite_s // global bottom / top for silhouette clipping fixed_t gz; fixed_t gzt; +#endif // horizontal position of x1 fixed_t startfrac; @@ -365,13 +481,16 @@ typedef struct vissprite_s // for color translation and shadow draw, // maxbright frames as well - lighttable_t* colormap; +#if !USE_LIGHTMAP_INDEXES + const lighttable_t* colormap; +#else + int8_t colormap; +#endif int mobjflags; } vissprite_t; - // // Sprites are patches with a special naming convention // so they can be recognized by R_InitSprites. @@ -387,23 +506,33 @@ typedef struct vissprite_s // Some sprites will only have one picture used // for all views: NNNNF0 // +#if !USE_WHD typedef struct { + // Lump to use for view angles 0-7. + // todo graham, seems like this is actually a spritenum + lumpindex_t lump[8]; + // If false use 0 for any position. // Note: as eight entries are available, // we might as well insert the same name eight times. + // todo graham actually lets use a small spriteframe instead boolean rotate; - // Lump to use for view angles 0-7. - short lump[8]; - + #if !DOOM_SMALL // Flip bit (1 = flip) to use for view angles 0-7. byte flip[8]; + #else + byte flips; + #endif } spriteframe_t; +typedef spriteframe_t *spriteframeref_t; +#else +typedef uint16_t spriteframeref_t; +#endif - - +#if !USE_WHD // // A sprite definition: // a number of animation frames. @@ -412,10 +541,14 @@ typedef struct { int numframes; spriteframe_t* spriteframes; - } spritedef_t; +#endif +#if PICODOOM_RENDER_BABY +extern uint8_t render_frame_index; +#endif +#if !NO_VISPLANES // // Now what is a visplane, anyway? @@ -423,8 +556,12 @@ typedef struct typedef struct { fixed_t height; - int picnum; - int lightlevel; + isb_int16_t lightlevel; + flatnum_t picnum; +#if PICODOOM_RENDER_BABY + uint8_t used_by; +#endif +#if !NO_VISPLANE_GUTS int minx; int maxx; @@ -439,9 +576,9 @@ typedef struct // See above. byte bottom[SCREENWIDTH]; byte pad4; - +#endif } visplane_t; - +#endif diff --git a/src/doom/r_draw.c b/src/doom/r_draw.c index 43c4b4b8..0b48a2c1 100644 --- a/src/doom/r_draw.c +++ b/src/doom/r_draw.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -20,7 +21,6 @@ - #include "doomdef.h" #include "deh_main.h" @@ -36,13 +36,54 @@ // State. #include "doomstat.h" +// todo graham revisit what combination of flags we should have these under +const byte *translationtables; + +fixed_t ds_xfrac; +fixed_t ds_yfrac; +fixed_t ds_xstep; +fixed_t ds_ystep; + +int ds_y; +int ds_x1; +int ds_x2; + +#if !NO_USE_DS_COLORMAP +const lighttable_t *ds_colormap; +#else +int8_t ds_colormap_index; +#endif + +// start of a 64*64 tile image +byte *ds_source; + +#if !NO_USE_DC_COLORMAP +const lighttable_t *dc_colormap; +#else +int8_t dc_colormap_index; +#endif +int dc_x; +int dc_yl; +int dc_yh; +fixed_t dc_iscale; +fixed_t dc_texturemid; + +const byte *dc_translation; + +// first pixel in a column (possibly virtual) - (graham: now refers to a whole column) +texturecolumn_t dc_source; // ? -#define MAXWIDTH 1120 -#define MAXHEIGHT 832 +#if DOOM_SMALL +#define MAXWIDTH SCREENWIDTH +#define MAXHEIGHT SCREENHEIGHT +#else +#define MAXWIDTH 1120 +#define MAXHEIGHT 832 +#endif // status bar height at bottom of screen -#define SBARHEIGHT 32 +#define SBARHEIGHT 32 // // All drawing to the view buffer is accomplished in this file. @@ -54,22 +95,26 @@ // -byte* viewimage; -int viewwidth; -int scaledviewwidth; -int viewheight; -int viewwindowx; -int viewwindowy; -pixel_t* ylookup[MAXHEIGHT]; -int columnofs[MAXWIDTH]; +byte *viewimage; +int viewwidth; +int scaledviewwidth; +int viewheight; +int viewwindowx; +int viewwindowy; +#if !NO_RDRAW +pixel_t *ylookup[MAXHEIGHT]; +int columnofs[MAXWIDTH]; + +#if 0 // guess this was the old translation_tables // Color tables for different players, // translate a limited part to another // (color ramps used for suit colors). // -byte translations[3][256]; - -// Backing buffer containing the bezel drawn around the screen and +byte translations[3][256]; +#endif + +// Backing buffer containing the bezel drawn around the screen and // surrounding background. static pixel_t *background_buffer = NULL; @@ -79,18 +124,9 @@ static pixel_t *background_buffer = NULL; // R_DrawColumn // Source is the top of the column to scale. // -lighttable_t* dc_colormap; -int dc_x; -int dc_yl; -int dc_yh; -fixed_t dc_iscale; -fixed_t dc_texturemid; - -// first pixel in a column (possibly virtual) -byte* dc_source; // just for profiling -int dccount; +int dccount; // // A column is a vertical slice/span from a wall texture that, @@ -99,50 +135,62 @@ int dccount; // Thus a special case loop for very fast rendering can // be used. It has also been used with Wolfenstein 3D. // -void R_DrawColumn (void) -{ - int count; - pixel_t* dest; - fixed_t frac; - fixed_t fracstep; - - count = dc_yh - dc_yl; +void R_DrawColumn(void) { + int count; + pixel_t *dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_yh - dc_yl; // Zero length, column does not exceed a pixel. - if (count < 0) - return; - -#ifdef RANGECHECK - if ((unsigned)dc_x >= SCREENWIDTH - || dc_yl < 0 - || dc_yh >= SCREENHEIGHT) - I_Error ("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); -#endif + if (count < 0) + return; + +#ifdef RANGECHECK + if ((unsigned) dc_x >= SCREENWIDTH + || dc_yl < 0 + || dc_yh >= SCREENHEIGHT) + I_Error("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); +#endif // Framebuffer destination address. // Use ylookup LUT to avoid multiply with ScreenWidth. // Use columnofs LUT for subwindows? - dest = ylookup[dc_yl] + columnofs[dc_x]; + dest = ylookup[dc_yl] + columnofs[dc_x]; // Determine scaling, // which is the only mapping to be done. - fracstep = dc_iscale; - frac = dc_texturemid + (dc_yl-centery)*fracstep; + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; +#if NO_USE_DC_COLORMAP + should_be_const lighttable_t *dc_colormap = colormaps + 256 * dc_colormap_index; +#endif // Inner loop that does the actual texture mapping, // e.g. a DDA-lile scaling. // This is as fast as it gets. - do - { - // Re-map color indices from wall texture column - // using a lighting/special effects LUT. - *dest = dc_colormap[dc_source[(frac>>FRACBITS)&127]]; - - dest += SCREENWIDTH; - frac += fracstep; - - } while (count--); -} + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + *dest = dc_colormap[dc_source[(frac >> FRACBITS) & 127]]; + + dest += SCREENWIDTH; + frac += fracstep; + + } while (count--); +} + +void R_DrawPixel(int x, int y, int color) { +#ifdef RANGECHECK + if ((unsigned) x >= SCREENWIDTH) + I_Error("R_DrawColumn: %i, %i", x, y); +#endif + if (y < 0) return; + if (y >= viewheight) return; + pixel_t *dest = ylookup[y] + columnofs[x]; + *dest = color; +} @@ -167,85 +215,85 @@ void R_DrawColumn (void) source = dc_source; colormap = dc_colormap; dest = ylookup[dc_yl] + columnofs[dc_x]; - - fracstep = dc_iscale<<9; - frac = (dc_texturemid + (dc_yl-centery)*dc_iscale)<<9; - + + fracstep = dc_iscale<<9; + frac = (dc_texturemid + (dc_yl-centery)*dc_iscale)<<9; + fracstep2 = fracstep+fracstep; fracstep3 = fracstep2+fracstep; fracstep4 = fracstep3+fracstep; - - while (count >= 8) - { - dest[0] = colormap[source[frac>>25]]; - dest[SCREENWIDTH] = colormap[source[(frac+fracstep)>>25]]; - dest[SCREENWIDTH*2] = colormap[source[(frac+fracstep2)>>25]]; - dest[SCREENWIDTH*3] = colormap[source[(frac+fracstep3)>>25]]; - - frac += fracstep4; - dest[SCREENWIDTH*4] = colormap[source[frac>>25]]; - dest[SCREENWIDTH*5] = colormap[source[(frac+fracstep)>>25]]; - dest[SCREENWIDTH*6] = colormap[source[(frac+fracstep2)>>25]]; - dest[SCREENWIDTH*7] = colormap[source[(frac+fracstep3)>>25]]; + while (count >= 8) + { + dest[0] = colormap[source[frac>>25]]; + dest[SCREENWIDTH] = colormap[source[(frac+fracstep)>>25]]; + dest[SCREENWIDTH*2] = colormap[source[(frac+fracstep2)>>25]]; + dest[SCREENWIDTH*3] = colormap[source[(frac+fracstep3)>>25]]; + + frac += fracstep4; + + dest[SCREENWIDTH*4] = colormap[source[frac>>25]]; + dest[SCREENWIDTH*5] = colormap[source[(frac+fracstep)>>25]]; + dest[SCREENWIDTH*6] = colormap[source[(frac+fracstep2)>>25]]; + dest[SCREENWIDTH*7] = colormap[source[(frac+fracstep3)>>25]]; + + frac += fracstep4; + dest += SCREENWIDTH*8; + count -= 8; + } - frac += fracstep4; - dest += SCREENWIDTH*8; - count -= 8; - } - while (count > 0) - { - *dest = colormap[source[frac>>25]]; - dest += SCREENWIDTH; - frac += fracstep; - count--; + { + *dest = colormap[source[frac>>25]]; + dest += SCREENWIDTH; + frac += fracstep; + count--; } } #endif -void R_DrawColumnLow (void) -{ - int count; - pixel_t* dest; - pixel_t* dest2; - fixed_t frac; - fixed_t fracstep; - int x; - - count = dc_yh - dc_yl; +void R_DrawColumnLow(void) { + int count; + pixel_t *dest; + pixel_t *dest2; + fixed_t frac; + fixed_t fracstep; + int x; + + count = dc_yh - dc_yl; // Zero length. - if (count < 0) - return; - -#ifdef RANGECHECK - if ((unsigned)dc_x >= SCREENWIDTH - || dc_yl < 0 - || dc_yh >= SCREENHEIGHT) - { - - I_Error ("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); + if (count < 0) + return; + +#ifdef RANGECHECK + if ((unsigned) dc_x >= SCREENWIDTH + || dc_yl < 0 + || dc_yh >= SCREENHEIGHT) { + + I_Error("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); } // dccount++; -#endif +#endif // Blocky mode, need to multiply by 2. x = dc_x << 1; - + dest = ylookup[dc_yl] + columnofs[x]; - dest2 = ylookup[dc_yl] + columnofs[x+1]; - - fracstep = dc_iscale; - frac = dc_texturemid + (dc_yl-centery)*fracstep; - - do - { - // Hack. Does not work corretly. - *dest2 = *dest = dc_colormap[dc_source[(frac>>FRACBITS)&127]]; - dest += SCREENWIDTH; - dest2 += SCREENWIDTH; - frac += fracstep; + dest2 = ylookup[dc_yl] + columnofs[x + 1]; + + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + +#if NO_USE_DC_COLORMAP + should_be_const lighttable_t *dc_colormap = colormaps + 256 * dc_colormap_index; +#endif + do { + // Hack. Does not work corretly. + *dest2 = *dest = dc_colormap[dc_source[(frac >> FRACBITS) & 127]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; } while (count--); } @@ -254,22 +302,22 @@ void R_DrawColumnLow (void) // // Spectre/Invisibility. // -#define FUZZTABLE 50 -#define FUZZOFF (SCREENWIDTH) +#define FUZZTABLE 50 +#define FUZZOFF (SCREENWIDTH) -int fuzzoffset[FUZZTABLE] = -{ - FUZZOFF,-FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF, - FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF, - FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF, - FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF, - FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF, - FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF, - FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF -}; +int fuzzoffset[FUZZTABLE] = + { + FUZZOFF, -FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, + FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, + FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, + FUZZOFF, -FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, + FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, -FUZZOFF, FUZZOFF, + FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, + FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF + }; -int fuzzpos = 0; +int fuzzpos = 0; // @@ -280,134 +328,125 @@ int fuzzpos = 0; // could create the SHADOW effect, // i.e. spectres and invisible players. // -void R_DrawFuzzColumn (void) -{ - int count; - pixel_t* dest; - fixed_t frac; - fixed_t fracstep; +void R_DrawFuzzColumn(void) { + int count; + pixel_t *dest; + fixed_t frac; + fixed_t fracstep; // Adjust borders. Low... - if (!dc_yl) - dc_yl = 1; + if (!dc_yl) + dc_yl = 1; // .. and high. - if (dc_yh == viewheight-1) - dc_yh = viewheight - 2; - - count = dc_yh - dc_yl; + if (dc_yh == viewheight - 1) + dc_yh = viewheight - 2; + + count = dc_yh - dc_yl; // Zero length. - if (count < 0) - return; + if (count < 0) + return; -#ifdef RANGECHECK - if ((unsigned)dc_x >= SCREENWIDTH - || dc_yl < 0 || dc_yh >= SCREENHEIGHT) - { - I_Error ("R_DrawFuzzColumn: %i to %i at %i", - dc_yl, dc_yh, dc_x); +#ifdef RANGECHECK + if ((unsigned) dc_x >= SCREENWIDTH + || dc_yl < 0 || dc_yh >= SCREENHEIGHT) { + I_Error("R_DrawFuzzColumn: %i to %i at %i", + dc_yl, dc_yh, dc_x); } #endif - + dest = ylookup[dc_yl] + columnofs[dc_x]; // Looks familiar. - fracstep = dc_iscale; - frac = dc_texturemid + (dc_yl-centery)*fracstep; + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; // Looks like an attempt at dithering, // using the colormap #6 (of 0-31, a bit // brighter than average). - do - { - // Lookup framebuffer, and retrieve - // a pixel that is either one column - // left or right of the current one. - // Add index from colormap to index. - *dest = colormaps[6*256+dest[fuzzoffset[fuzzpos]]]; + do { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + *dest = colormaps[6 * 256 + dest[fuzzoffset[fuzzpos]]]; - // Clamp table lookup index. - if (++fuzzpos == FUZZTABLE) - fuzzpos = 0; - - dest += SCREENWIDTH; + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) + fuzzpos = 0; - frac += fracstep; - } while (count--); -} + dest += SCREENWIDTH; + + frac += fracstep; + } while (count--); +} // low detail mode version - -void R_DrawFuzzColumnLow (void) -{ - int count; - pixel_t* dest; - pixel_t* dest2; - fixed_t frac; - fixed_t fracstep; + +void R_DrawFuzzColumnLow(void) { + int count; + pixel_t *dest; + pixel_t *dest2; + fixed_t frac; + fixed_t fracstep; int x; // Adjust borders. Low... - if (!dc_yl) - dc_yl = 1; + if (!dc_yl) + dc_yl = 1; // .. and high. - if (dc_yh == viewheight-1) - dc_yh = viewheight - 2; - - count = dc_yh - dc_yl; + if (dc_yh == viewheight - 1) + dc_yh = viewheight - 2; + + count = dc_yh - dc_yl; // Zero length. - if (count < 0) - return; + if (count < 0) + return; // low detail mode, need to multiply by 2 - + x = dc_x << 1; - -#ifdef RANGECHECK - if ((unsigned)x >= SCREENWIDTH - || dc_yl < 0 || dc_yh >= SCREENHEIGHT) - { - I_Error ("R_DrawFuzzColumn: %i to %i at %i", - dc_yl, dc_yh, dc_x); + +#ifdef RANGECHECK + if ((unsigned) x >= SCREENWIDTH + || dc_yl < 0 || dc_yh >= SCREENHEIGHT) { + I_Error("R_DrawFuzzColumn: %i to %i at %i", + dc_yl, dc_yh, dc_x); } #endif - + dest = ylookup[dc_yl] + columnofs[x]; - dest2 = ylookup[dc_yl] + columnofs[x+1]; + dest2 = ylookup[dc_yl] + columnofs[x + 1]; // Looks familiar. - fracstep = dc_iscale; - frac = dc_texturemid + (dc_yl-centery)*fracstep; + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; // Looks like an attempt at dithering, // using the colormap #6 (of 0-31, a bit // brighter than average). - do - { - // Lookup framebuffer, and retrieve - // a pixel that is either one column - // left or right of the current one. - // Add index from colormap to index. - *dest = colormaps[6*256+dest[fuzzoffset[fuzzpos]]]; - *dest2 = colormaps[6*256+dest2[fuzzoffset[fuzzpos]]]; + do { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + *dest = colormaps[6 * 256 + dest[fuzzoffset[fuzzpos]]]; + *dest2 = colormaps[6 * 256 + dest2[fuzzoffset[fuzzpos]]]; - // Clamp table lookup index. - if (++fuzzpos == FUZZTABLE) - fuzzpos = 0; - - dest += SCREENWIDTH; - dest2 += SCREENWIDTH; + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) + fuzzpos = 0; + + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + frac += fracstep; + } while (count--); +} - frac += fracstep; - } while (count--); -} - - - - // // R_DrawTranslatedColumn @@ -418,143 +457,106 @@ void R_DrawFuzzColumnLow (void) // of the BaronOfHell, the HellKnight, uses // identical sprites, kinda brightened up. // -byte* dc_translation; -byte* translationtables; -void R_DrawTranslatedColumn (void) -{ - int count; - pixel_t* dest; - fixed_t frac; - fixed_t fracstep; - - count = dc_yh - dc_yl; - if (count < 0) - return; - -#ifdef RANGECHECK - if ((unsigned)dc_x >= SCREENWIDTH - || dc_yl < 0 - || dc_yh >= SCREENHEIGHT) - { - I_Error ( "R_DrawColumn: %i to %i at %i", - dc_yl, dc_yh, dc_x); +void R_DrawTranslatedColumn(void) { + int count; + pixel_t *dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_yh - dc_yl; + if (count < 0) + return; + +#ifdef RANGECHECK + if ((unsigned) dc_x >= SCREENWIDTH + || dc_yl < 0 + || dc_yh >= SCREENHEIGHT) { + I_Error("R_DrawColumn: %i to %i at %i", + dc_yl, dc_yh, dc_x); } - -#endif + +#endif - dest = ylookup[dc_yl] + columnofs[dc_x]; + dest = ylookup[dc_yl] + columnofs[dc_x]; // Looks familiar. - fracstep = dc_iscale; - frac = dc_texturemid + (dc_yl-centery)*fracstep; + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + +#if NO_USE_DC_COLORMAP + should_be_const lighttable_t *dc_colormap = colormaps + 256 * dc_colormap_index; +#endif // Here we do an additional index re-mapping. - do - { - // Translation tables are used - // to map certain colorramps to other ones, - // used with PLAY sprites. - // Thus the "green" ramp of the player 0 sprite - // is mapped to gray, red, black/indigo. - *dest = dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]; - dest += SCREENWIDTH; - - frac += fracstep; - } while (count--); -} + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + *dest = dc_colormap[dc_translation[dc_source[frac >> FRACBITS]]]; + dest += SCREENWIDTH; -void R_DrawTranslatedColumnLow (void) -{ - int count; - pixel_t* dest; - pixel_t* dest2; - fixed_t frac; - fixed_t fracstep; - int x; - - count = dc_yh - dc_yl; - if (count < 0) - return; + frac += fracstep; + } while (count--); +} + +void R_DrawTranslatedColumnLow(void) { + int count; + pixel_t *dest; + pixel_t *dest2; + fixed_t frac; + fixed_t fracstep; + int x; + + count = dc_yh - dc_yl; + if (count < 0) + return; // low detail, need to scale by 2 x = dc_x << 1; - -#ifdef RANGECHECK - if ((unsigned)x >= SCREENWIDTH - || dc_yl < 0 - || dc_yh >= SCREENHEIGHT) - { - I_Error ( "R_DrawColumn: %i to %i at %i", - dc_yl, dc_yh, x); + +#ifdef RANGECHECK + if ((unsigned) x >= SCREENWIDTH + || dc_yl < 0 + || dc_yh >= SCREENHEIGHT) { + I_Error("R_DrawColumn: %i to %i at %i", + dc_yl, dc_yh, x); } - -#endif + +#endif - dest = ylookup[dc_yl] + columnofs[x]; - dest2 = ylookup[dc_yl] + columnofs[x+1]; + dest = ylookup[dc_yl] + columnofs[x]; + dest2 = ylookup[dc_yl] + columnofs[x + 1]; // Looks familiar. - fracstep = dc_iscale; - frac = dc_texturemid + (dc_yl-centery)*fracstep; + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; +#if NO_USE_DC_COLORMAP + should_be_const lighttable_t *dc_colormap = colormaps + 256 * dc_colormap_index; +#endif // Here we do an additional index re-mapping. - do - { - // Translation tables are used - // to map certain colorramps to other ones, - // used with PLAY sprites. - // Thus the "green" ramp of the player 0 sprite - // is mapped to gray, red, black/indigo. - *dest = dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]; - *dest2 = dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]; - dest += SCREENWIDTH; - dest2 += SCREENWIDTH; - - frac += fracstep; - } while (count--); -} + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + *dest = dc_colormap[dc_translation[dc_source[frac >> FRACBITS]]]; + *dest2 = dc_colormap[dc_translation[dc_source[frac >> FRACBITS]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; - - - -// -// R_InitTranslationTables -// Creates the translation tables to map -// the green color ramp to gray, brown, red. -// Assumes a given structure of the PLAYPAL. -// Could be read from a lump instead. -// -void R_InitTranslationTables (void) -{ - int i; - - translationtables = Z_Malloc (256*3, PU_STATIC, 0); - - // translate just the 16 green colors - for (i=0 ; i<256 ; i++) - { - if (i >= 0x70 && i<= 0x7f) - { - // map green ramp to gray, brown, red - translationtables[i] = 0x60 + (i&0xf); - translationtables [i+256] = 0x40 + (i&0xf); - translationtables [i+512] = 0x20 + (i&0xf); - } - else - { - // Keep all other colors as is. - translationtables[i] = translationtables[i+256] - = translationtables[i+512] = i; - } - } + frac += fracstep; + } while (count--); } - // // R_DrawSpan // With DOOM style restrictions on view orientation, @@ -567,28 +569,15 @@ void R_InitTranslationTables (void) // In consequence, flats are not stored by column (like walls), // and the inner loop has to step in texture space u and v. // -int ds_y; -int ds_x1; -int ds_x2; -lighttable_t* ds_colormap; - -fixed_t ds_xfrac; -fixed_t ds_yfrac; -fixed_t ds_xstep; -fixed_t ds_ystep; - -// start of a 64*64 tile image -byte* ds_source; // just for profiling -int dscount; +int dscount; // // Draws the actual span. -void R_DrawSpan (void) -{ +void R_DrawSpan(void) { unsigned int position, step; pixel_t *dest; int count; @@ -597,12 +586,11 @@ void R_DrawSpan (void) #ifdef RANGECHECK if (ds_x2 < ds_x1 - || ds_x1<0 - || ds_x2>=SCREENWIDTH - || (unsigned)ds_y>SCREENHEIGHT) - { - I_Error( "R_DrawSpan: %i to %i at %i", - ds_x1,ds_x2,ds_y); + || ds_x1 < 0 + || ds_x2 >= SCREENWIDTH + || (unsigned) ds_y > SCREENHEIGHT) { + I_Error("R_DrawSpan: %i to %i at %i", + ds_x1, ds_x2, ds_y); } // dscount++; #endif @@ -613,25 +601,28 @@ void R_DrawSpan (void) // bottom 10 bits are the fractional part of the pixel position. position = ((ds_xfrac << 10) & 0xffff0000) - | ((ds_yfrac >> 6) & 0x0000ffff); + | ((ds_yfrac >> 6) & 0x0000ffff); step = ((ds_xstep << 10) & 0xffff0000) - | ((ds_ystep >> 6) & 0x0000ffff); + | ((ds_ystep >> 6) & 0x0000ffff); dest = ylookup[ds_y] + columnofs[ds_x1]; // We do not check for zero spans here? count = ds_x2 - ds_x1; - do - { - // Calculate current texture index in u,v. +#if NO_USE_DS_COLORMAP + should_be_const lighttable_t *ds_colormap = colormaps + ds_colormap_index * 256; +#endif + do { + // Calculate current texture index in u,v. ytemp = (position >> 4) & 0x0fc0; xtemp = (position >> 26); spot = xtemp | ytemp; - // Lookup pixel from flat texture tile, - // re-index using light/colormap. - *dest++ = ds_colormap[ds_source[spot]]; + // Lookup pixel from flat texture tile, + // re-index using light/colormap. + *dest++ = ds_colormap[ds_source[spot]]; +// *dest++ = ((ytemp >> 6)&32) + ((xtemp >> 1)&16) + ((((uintptr_t)ds_source)>>2)&15); position += step; @@ -657,67 +648,66 @@ void R_DrawSpan (void) unsigned temp; unsigned xtemp; unsigned ytemp; - + position = ((ds_xfrac<<10)&0xffff0000) | ((ds_yfrac>>6)&0xffff); step = ((ds_xstep<<10)&0xffff0000) | ((ds_ystep>>6)&0xffff); - + source = ds_source; colormap = ds_colormap; - dest = ylookup[ds_y] + columnofs[ds_x1]; - count = ds_x2 - ds_x1 + 1; - - while (count >= 4) - { - ytemp = position>>4; - ytemp = ytemp & 4032; - xtemp = position>>26; - spot = xtemp | ytemp; - position += step; - dest[0] = colormap[source[spot]]; + dest = ylookup[ds_y] + columnofs[ds_x1]; + count = ds_x2 - ds_x1 + 1; - ytemp = position>>4; - ytemp = ytemp & 4032; - xtemp = position>>26; - spot = xtemp | ytemp; - position += step; - dest[1] = colormap[source[spot]]; - - ytemp = position>>4; - ytemp = ytemp & 4032; - xtemp = position>>26; - spot = xtemp | ytemp; - position += step; - dest[2] = colormap[source[spot]]; - - ytemp = position>>4; - ytemp = ytemp & 4032; - xtemp = position>>26; - spot = xtemp | ytemp; - position += step; - dest[3] = colormap[source[spot]]; - - count -= 4; - dest += 4; + while (count >= 4) + { + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[0] = colormap[source[spot]]; + + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[1] = colormap[source[spot]]; + + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[2] = colormap[source[spot]]; + + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[3] = colormap[source[spot]]; + + count -= 4; + dest += 4; + } + while (count > 0) + { + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + *dest++ = colormap[source[spot]]; + count--; } - while (count > 0) - { - ytemp = position>>4; - ytemp = ytemp & 4032; - xtemp = position>>26; - spot = xtemp | ytemp; - position += step; - *dest++ = colormap[source[spot]]; - count--; - } -} +} #endif // // Again.. // -void R_DrawSpanLow (void) -{ +void R_DrawSpanLow(void) { unsigned int position, step; unsigned int xtemp, ytemp; pixel_t *dest; @@ -726,20 +716,19 @@ void R_DrawSpanLow (void) #ifdef RANGECHECK if (ds_x2 < ds_x1 - || ds_x1<0 - || ds_x2>=SCREENWIDTH - || (unsigned)ds_y>SCREENHEIGHT) - { - I_Error( "R_DrawSpan: %i to %i at %i", - ds_x1,ds_x2,ds_y); + || ds_x1 < 0 + || ds_x2 >= SCREENWIDTH + || (unsigned) ds_y > SCREENHEIGHT) { + I_Error("R_DrawSpan: %i to %i at %i", + ds_x1, ds_x2, ds_y); } // dscount++; #endif position = ((ds_xfrac << 10) & 0xffff0000) - | ((ds_yfrac >> 6) & 0x0000ffff); + | ((ds_yfrac >> 6) & 0x0000ffff); step = ((ds_xstep << 10) & 0xffff0000) - | ((ds_ystep >> 6) & 0x0000ffff); + | ((ds_ystep >> 6) & 0x0000ffff); count = (ds_x2 - ds_x1); @@ -748,20 +737,22 @@ void R_DrawSpanLow (void) ds_x2 <<= 1; dest = ylookup[ds_y] + columnofs[ds_x1]; +#if NO_USE_DS_COLORMAP + should_be_const lighttable_t *ds_colormap = colormaps + ds_colormap_index * 256; +#endif - do - { - // Calculate current texture index in u,v. + do { + // Calculate current texture index in u,v. ytemp = (position >> 4) & 0x0fc0; xtemp = (position >> 26); spot = xtemp | ytemp; - // Lowres/blocky mode does it twice, - // while scale is adjusted appropriately. - *dest++ = ds_colormap[ds_source[spot]]; - *dest++ = ds_colormap[ds_source[spot]]; + // Lowres/blocky mode does it twice, + // while scale is adjusted appropriately. + *dest++ = ds_colormap[ds_source[spot]]; + *dest++ = ds_colormap[ds_source[spot]]; - position += step; + position += step; } while (count--); } @@ -775,32 +766,29 @@ void R_DrawSpanLow (void) // void R_InitBuffer -( int width, - int height ) -{ - int i; + (int width, + int height) { + int i; // Handle resize, // e.g. smaller view windows // with border and/or status bar. - viewwindowx = (SCREENWIDTH-width) >> 1; + viewwindowx = (SCREENWIDTH - width) >> 1; // Column offset. For windows. - for (i=0 ; i> 1; + if (width == SCREENWIDTH) + viewwindowy = 0; + else + viewwindowy = (SCREENHEIGHT - SBARHEIGHT - height) >> 1; // Preclaculate all row offsets. - for (i=0 ; i= 0x70 && i <= 0x7f) { + // map green ramp to gray, brown, red + tt[i] = 0x60 + (i & 0xf); + tt[i + 256] = 0x40 + (i & 0xf); + tt[i + 512] = 0x20 + (i & 0xf); + } else { + // Keep all other colors as is. + tt[i] = tt[i + 256] + = tt[i + 512] = i; + } + } + translationtables = tt; +#endif +} + diff --git a/src/doom/r_draw.h b/src/doom/r_draw.h index 5b0c818b..17ad4ad5 100644 --- a/src/doom/r_draw.h +++ b/src/doom/r_draw.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -23,17 +24,22 @@ -extern lighttable_t* dc_colormap; +#if !NO_USE_DC_COLORMAP +extern const lighttable_t* dc_colormap; +#else +extern int8_t dc_colormap_index; +#endif extern int dc_x; extern int dc_yl; extern int dc_yh; extern fixed_t dc_iscale; extern fixed_t dc_texturemid; -// first pixel in a column -extern byte* dc_source; +// first pixel in a column (possibly virtual) - (graham: now refers to a whole column) +extern texturecolumn_t dc_source; +#if !NO_RDRAW // The span blitting interface. // Hook in assembler or system specific BLT // here. @@ -55,11 +61,17 @@ R_VideoErase ( unsigned ofs, int count ); +void R_VideoClear(); +#endif extern int ds_y; extern int ds_x1; extern int ds_x2; -extern lighttable_t* ds_colormap; +#if !NO_USE_DS_COLORMAP +extern const lighttable_t* ds_colormap; +#else +extern int8_t ds_colormap_index; +#endif extern fixed_t ds_xfrac; extern fixed_t ds_yfrac; @@ -69,10 +81,14 @@ extern fixed_t ds_ystep; // start of a 64*64 tile image extern byte* ds_source; -extern byte* translationtables; -extern byte* dc_translation; - +#if !DOOM_TINY +extern const byte* translationtables; +extern const byte* dc_translation; +#else +extern byte dc_translation_index; +#endif +#if !NO_RDRAW // Span blitting for rows, floor/ceiling. // No Sepctre effect needed. void R_DrawSpan (void); @@ -87,10 +103,6 @@ R_InitBuffer int height ); -// Initialize color translation tables, -// for player rendering etc. -void R_InitTranslationTables (void); - // Rendering function. @@ -99,6 +111,12 @@ void R_FillBackScreen (void); // If the view size is not full screen, draws a border around it. void R_DrawViewBorder (void); +void R_DrawPixel(int x, int y, int color); +#endif +// todo graham this should go +// Initialize color translation tables, +// for player rendering etc. +void R_InitTranslationTables (void); #endif diff --git a/src/doom/r_main.c b/src/doom/r_main.c index 567ea4be..97098598 100644 --- a/src/doom/r_main.c +++ b/src/doom/r_main.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -35,84 +36,393 @@ #include "r_local.h" #include "r_sky.h" - - - - +#if PICO_DOOM +#include "picodoom.h" +#if PICO_BUILD +#include "hardware/gpio.h" +#endif +#endif +#if USE_WHD +#include "p_spec.h" +#endif // Fineangles in the SCREENWIDTH wide window. -#define FIELDOFVIEW 2048 +#define FIELDOFVIEW 2048 +#if MU_STATS +uint32_t stats_dc_iscale_min, stats_dc_iscale_max; +#endif - -int viewangleoffset; +int viewangleoffset; // increment every time a check is made -int validcount = 1; +#if !USE_WHD +int validcount = 1; +#else +int8_t validcount = 1; +#endif -lighttable_t* fixedcolormap; -extern lighttable_t** walllights; +#if !USE_LIGHTMAP_INDEXES +extern const lighttable_t** walllights; +const lighttable_t* fixedcolormap; +#else +extern int8_t *walllights; +int8_t fixedcolormap; +#endif -int centerx; -int centery; +int centerx; +int centery; -fixed_t centerxfrac; -fixed_t centeryfrac; -fixed_t projection; +fixed_t centerxfrac; +fixed_t centeryfrac; +fixed_t projection; // just for profiling purposes -int framecount; +int framecount; -int sscount; -int linecount; -int loopcount; +//int sscount; +int linecount; +//int loopcount; -fixed_t viewx; -fixed_t viewy; -fixed_t viewz; +fixed_t viewx; +fixed_t viewy; +fixed_t viewz; -angle_t viewangle; +angle_t viewangle; -fixed_t viewcos; -fixed_t viewsin; +fixed_t viewcos; +fixed_t viewsin; -player_t* viewplayer; +player_t *viewplayer; // 0 = high, 1 = low -int detailshift; +int detailshift; // // precalculated math tables // -angle_t clipangle; +angle_t clipangle; +#if !FIXED_SCREENWIDTH // The viewangletox[viewangle + FINEANGLES/4] lookup // maps the visible view angles to screen X coordinates, // flattening the arc to a flat projection plane. -// There will be many angles mapped to the same X. -int viewangletox[FINEANGLES/2]; +// There will be many angles mapped to the same X. +isb_int16_t viewangletox[FINEANGLES / 2]; // The xtoviewangleangle[] table maps a screen pixel // to the lowest viewangle that maps back to x ranges // from clipangle to -clipangle. -angle_t xtoviewangle[SCREENWIDTH+1]; +angle_t xtoviewangle[SCREENWIDTH + 1]; +#else +const uint16_t xtoviewangle_[SCREENWIDTH + 1] = { + 0x2008, 0x1fe0, 0x1fc0, 0x1fa0, 0x1f80, 0x1f60, 0x1f40, 0x1f18, 0x1ef8, 0x1ed8, 0x1eb0, 0x1e90, 0x1e70, 0x1e48, 0x1e28, 0x1e00, + 0x1de0, 0x1db8, 0x1d98, 0x1d70, 0x1d50, 0x1d28, 0x1d00, 0x1ce0, 0x1cb8, 0x1c90, 0x1c68, 0x1c48, 0x1c20, 0x1bf8, 0x1bd0, 0x1ba8, + 0x1b80, 0x1b58, 0x1b30, 0x1b08, 0x1ae0, 0x1ab8, 0x1a90, 0x1a68, 0x1a38, 0x1a10, 0x19e8, 0x19c0, 0x1990, 0x1968, 0x1940, 0x1910, + 0x18e8, 0x18b8, 0x1890, 0x1860, 0x1838, 0x1808, 0x17d8, 0x17b0, 0x1780, 0x1750, 0x1720, 0x16f8, 0x16c8, 0x1698, 0x1668, 0x1638, + 0x1608, 0x15d8, 0x15a8, 0x1578, 0x1548, 0x1518, 0x14e0, 0x14b0, 0x1480, 0x1450, 0x1418, 0x13e8, 0x13b8, 0x1380, 0x1350, 0x1318, + 0x12e8, 0x12b0, 0x1280, 0x1248, 0x1218, 0x11e0, 0x11a8, 0x1170, 0x1140, 0x1108, 0x10d0, 0x1098, 0x1060, 0x1028, 0x0ff0, 0x0fb8, + 0x0f80, 0x0f48, 0x0f10, 0x0ed8, 0x0ea0, 0x0e68, 0x0e30, 0x0df8, 0x0db8, 0x0d80, 0x0d48, 0x0d08, 0x0cd0, 0x0c98, 0x0c58, 0x0c20, + 0x0be0, 0x0ba8, 0x0b68, 0x0b30, 0x0af0, 0x0ab8, 0x0a78, 0x0a38, 0x0a00, 0x09c0, 0x0980, 0x0948, 0x0908, 0x08c8, 0x0888, 0x0848, + 0x0810, 0x07d0, 0x0790, 0x0750, 0x0710, 0x06d0, 0x0690, 0x0650, 0x0610, 0x05d0, 0x0590, 0x0550, 0x0510, 0x04d0, 0x0490, 0x0450, + 0x0410, 0x03d0, 0x0390, 0x0350, 0x0310, 0x02d0, 0x0288, 0x0248, 0x0208, 0x01c8, 0x0188, 0x0148, 0x0108, 0x00c0, 0x0080, 0x0040, + 0x0000, 0xffc0, 0xff80, 0xff40, 0xfef8, 0xfeb8, 0xfe78, 0xfe38, 0xfdf8, 0xfdb8, 0xfd78, 0xfd30, 0xfcf0, 0xfcb0, 0xfc70, 0xfc30, + 0xfbf0, 0xfbb0, 0xfb70, 0xfb30, 0xfaf0, 0xfab0, 0xfa70, 0xfa30, 0xf9f0, 0xf9b0, 0xf970, 0xf930, 0xf8f0, 0xf8b0, 0xf870, 0xf830, + 0xf7f0, 0xf7b8, 0xf778, 0xf738, 0xf6f8, 0xf6b8, 0xf680, 0xf640, 0xf600, 0xf5c8, 0xf588, 0xf548, 0xf510, 0xf4d0, 0xf498, 0xf458, + 0xf420, 0xf3e0, 0xf3a8, 0xf368, 0xf330, 0xf2f8, 0xf2b8, 0xf280, 0xf248, 0xf208, 0xf1d0, 0xf198, 0xf160, 0xf128, 0xf0f0, 0xf0b8, + 0xf080, 0xf048, 0xf010, 0xefd8, 0xefa0, 0xef68, 0xef30, 0xeef8, 0xeec0, 0xee90, 0xee58, 0xee20, 0xede8, 0xedb8, 0xed80, 0xed50, + 0xed18, 0xece8, 0xecb0, 0xec80, 0xec48, 0xec18, 0xebe8, 0xebb0, 0xeb80, 0xeb50, 0xeb20, 0xeae8, 0xeab8, 0xea88, 0xea58, 0xea28, + 0xe9f8, 0xe9c8, 0xe998, 0xe968, 0xe938, 0xe908, 0xe8e0, 0xe8b0, 0xe880, 0xe850, 0xe828, 0xe7f8, 0xe7c8, 0xe7a0, 0xe770, 0xe748, + 0xe718, 0xe6f0, 0xe6c0, 0xe698, 0xe670, 0xe640, 0xe618, 0xe5f0, 0xe5c8, 0xe598, 0xe570, 0xe548, 0xe520, 0xe4f8, 0xe4d0, 0xe4a8, + 0xe480, 0xe458, 0xe430, 0xe408, 0xe3e0, 0xe3b8, 0xe398, 0xe370, 0xe348, 0xe320, 0xe300, 0xe2d8, 0xe2b0, 0xe290, 0xe268, 0xe248, + 0xe220, 0xe200, 0xe1d8, 0xe1b8, 0xe190, 0xe170, 0xe150, 0xe128, 0xe108, 0xe0e8, 0xe0c0, 0xe0a0, 0xe080, 0xe060, 0xe040, 0xe020, + 0xdff8, +}; +const int16_t viewangletox[FINEANGLES / 2] = { + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, 0x0140, + 0x0140, 0x0140, 0x0140, 0x0140, 0x013f, 0x013f, 0x013f, 0x013f, 0x013e, 0x013e, 0x013e, 0x013e, 0x013d, 0x013d, 0x013d, 0x013d, + 0x013c, 0x013c, 0x013c, 0x013c, 0x013b, 0x013b, 0x013b, 0x013b, 0x013a, 0x013a, 0x013a, 0x013a, 0x013a, 0x0139, 0x0139, 0x0139, + 0x0139, 0x0138, 0x0138, 0x0138, 0x0138, 0x0137, 0x0137, 0x0137, 0x0137, 0x0137, 0x0136, 0x0136, 0x0136, 0x0136, 0x0135, 0x0135, + 0x0135, 0x0135, 0x0134, 0x0134, 0x0134, 0x0134, 0x0134, 0x0133, 0x0133, 0x0133, 0x0133, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, + 0x0131, 0x0131, 0x0131, 0x0131, 0x0130, 0x0130, 0x0130, 0x0130, 0x0130, 0x012f, 0x012f, 0x012f, 0x012f, 0x012e, 0x012e, 0x012e, + 0x012e, 0x012e, 0x012d, 0x012d, 0x012d, 0x012d, 0x012c, 0x012c, 0x012c, 0x012c, 0x012c, 0x012b, 0x012b, 0x012b, 0x012b, 0x012b, + 0x012a, 0x012a, 0x012a, 0x012a, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0128, 0x0128, 0x0128, 0x0128, 0x0128, 0x0127, 0x0127, + 0x0127, 0x0127, 0x0127, 0x0126, 0x0126, 0x0126, 0x0126, 0x0125, 0x0125, 0x0125, 0x0125, 0x0125, 0x0124, 0x0124, 0x0124, 0x0124, + 0x0124, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0122, 0x0122, 0x0122, 0x0122, 0x0122, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, + 0x0120, 0x0120, 0x0120, 0x0120, 0x0120, 0x011f, 0x011f, 0x011f, 0x011f, 0x011f, 0x011e, 0x011e, 0x011e, 0x011e, 0x011e, 0x011d, + 0x011d, 0x011d, 0x011d, 0x011d, 0x011c, 0x011c, 0x011c, 0x011c, 0x011c, 0x011b, 0x011b, 0x011b, 0x011b, 0x011b, 0x011a, 0x011a, + 0x011a, 0x011a, 0x011a, 0x0119, 0x0119, 0x0119, 0x0119, 0x0119, 0x0119, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0117, 0x0117, + 0x0117, 0x0117, 0x0117, 0x0116, 0x0116, 0x0116, 0x0116, 0x0116, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0114, 0x0114, + 0x0114, 0x0114, 0x0114, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0112, 0x0112, 0x0112, 0x0112, 0x0112, 0x0112, 0x0111, 0x0111, + 0x0111, 0x0111, 0x0111, 0x0110, 0x0110, 0x0110, 0x0110, 0x0110, 0x0110, 0x010f, 0x010f, 0x010f, 0x010f, 0x010f, 0x010e, 0x010e, + 0x010e, 0x010e, 0x010e, 0x010e, 0x010d, 0x010d, 0x010d, 0x010d, 0x010d, 0x010c, 0x010c, 0x010c, 0x010c, 0x010c, 0x010c, 0x010b, + 0x010b, 0x010b, 0x010b, 0x010b, 0x010b, 0x010a, 0x010a, 0x010a, 0x010a, 0x010a, 0x0109, 0x0109, 0x0109, 0x0109, 0x0109, 0x0109, + 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0107, 0x0107, 0x0107, 0x0107, 0x0107, 0x0107, 0x0106, 0x0106, 0x0106, 0x0106, + 0x0106, 0x0105, 0x0105, 0x0105, 0x0105, 0x0105, 0x0105, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0103, 0x0103, 0x0103, + 0x0103, 0x0103, 0x0103, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0100, + 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00fe, 0x00fe, 0x00fe, 0x00fe, 0x00fe, + 0x00fe, 0x00fd, 0x00fd, 0x00fd, 0x00fd, 0x00fd, 0x00fd, 0x00fc, 0x00fc, 0x00fc, 0x00fc, 0x00fc, 0x00fc, 0x00fb, 0x00fb, 0x00fb, + 0x00fb, 0x00fb, 0x00fb, 0x00fb, 0x00fa, 0x00fa, 0x00fa, 0x00fa, 0x00fa, 0x00fa, 0x00f9, 0x00f9, 0x00f9, 0x00f9, 0x00f9, 0x00f9, + 0x00f8, 0x00f8, 0x00f8, 0x00f8, 0x00f8, 0x00f8, 0x00f7, 0x00f7, 0x00f7, 0x00f7, 0x00f7, 0x00f7, 0x00f7, 0x00f6, 0x00f6, 0x00f6, + 0x00f6, 0x00f6, 0x00f6, 0x00f5, 0x00f5, 0x00f5, 0x00f5, 0x00f5, 0x00f5, 0x00f4, 0x00f4, 0x00f4, 0x00f4, 0x00f4, 0x00f4, 0x00f4, + 0x00f3, 0x00f3, 0x00f3, 0x00f3, 0x00f3, 0x00f3, 0x00f2, 0x00f2, 0x00f2, 0x00f2, 0x00f2, 0x00f2, 0x00f2, 0x00f1, 0x00f1, 0x00f1, + 0x00f1, 0x00f1, 0x00f1, 0x00f0, 0x00f0, 0x00f0, 0x00f0, 0x00f0, 0x00f0, 0x00f0, 0x00ef, 0x00ef, 0x00ef, 0x00ef, 0x00ef, 0x00ef, + 0x00ee, 0x00ee, 0x00ee, 0x00ee, 0x00ee, 0x00ee, 0x00ee, 0x00ed, 0x00ed, 0x00ed, 0x00ed, 0x00ed, 0x00ed, 0x00ec, 0x00ec, 0x00ec, + 0x00ec, 0x00ec, 0x00ec, 0x00ec, 0x00eb, 0x00eb, 0x00eb, 0x00eb, 0x00eb, 0x00eb, 0x00eb, 0x00ea, 0x00ea, 0x00ea, 0x00ea, 0x00ea, + 0x00ea, 0x00ea, 0x00e9, 0x00e9, 0x00e9, 0x00e9, 0x00e9, 0x00e9, 0x00e8, 0x00e8, 0x00e8, 0x00e8, 0x00e8, 0x00e8, 0x00e8, 0x00e7, + 0x00e7, 0x00e7, 0x00e7, 0x00e7, 0x00e7, 0x00e7, 0x00e6, 0x00e6, 0x00e6, 0x00e6, 0x00e6, 0x00e6, 0x00e6, 0x00e5, 0x00e5, 0x00e5, + 0x00e5, 0x00e5, 0x00e5, 0x00e5, 0x00e4, 0x00e4, 0x00e4, 0x00e4, 0x00e4, 0x00e4, 0x00e4, 0x00e3, 0x00e3, 0x00e3, 0x00e3, 0x00e3, + 0x00e3, 0x00e3, 0x00e2, 0x00e2, 0x00e2, 0x00e2, 0x00e2, 0x00e2, 0x00e2, 0x00e1, 0x00e1, 0x00e1, 0x00e1, 0x00e1, 0x00e1, 0x00e1, + 0x00e0, 0x00e0, 0x00e0, 0x00e0, 0x00e0, 0x00e0, 0x00e0, 0x00df, 0x00df, 0x00df, 0x00df, 0x00df, 0x00df, 0x00df, 0x00de, 0x00de, + 0x00de, 0x00de, 0x00de, 0x00de, 0x00de, 0x00dd, 0x00dd, 0x00dd, 0x00dd, 0x00dd, 0x00dd, 0x00dd, 0x00dc, 0x00dc, 0x00dc, 0x00dc, + 0x00dc, 0x00dc, 0x00dc, 0x00db, 0x00db, 0x00db, 0x00db, 0x00db, 0x00db, 0x00db, 0x00da, 0x00da, 0x00da, 0x00da, 0x00da, 0x00da, + 0x00da, 0x00d9, 0x00d9, 0x00d9, 0x00d9, 0x00d9, 0x00d9, 0x00d9, 0x00d9, 0x00d8, 0x00d8, 0x00d8, 0x00d8, 0x00d8, 0x00d8, 0x00d8, + 0x00d7, 0x00d7, 0x00d7, 0x00d7, 0x00d7, 0x00d7, 0x00d7, 0x00d6, 0x00d6, 0x00d6, 0x00d6, 0x00d6, 0x00d6, 0x00d6, 0x00d6, 0x00d5, + 0x00d5, 0x00d5, 0x00d5, 0x00d5, 0x00d5, 0x00d5, 0x00d4, 0x00d4, 0x00d4, 0x00d4, 0x00d4, 0x00d4, 0x00d4, 0x00d3, 0x00d3, 0x00d3, + 0x00d3, 0x00d3, 0x00d3, 0x00d3, 0x00d3, 0x00d2, 0x00d2, 0x00d2, 0x00d2, 0x00d2, 0x00d2, 0x00d2, 0x00d1, 0x00d1, 0x00d1, 0x00d1, + 0x00d1, 0x00d1, 0x00d1, 0x00d1, 0x00d0, 0x00d0, 0x00d0, 0x00d0, 0x00d0, 0x00d0, 0x00d0, 0x00cf, 0x00cf, 0x00cf, 0x00cf, 0x00cf, + 0x00cf, 0x00cf, 0x00cf, 0x00ce, 0x00ce, 0x00ce, 0x00ce, 0x00ce, 0x00ce, 0x00ce, 0x00cd, 0x00cd, 0x00cd, 0x00cd, 0x00cd, 0x00cd, + 0x00cd, 0x00cd, 0x00cc, 0x00cc, 0x00cc, 0x00cc, 0x00cc, 0x00cc, 0x00cc, 0x00cb, 0x00cb, 0x00cb, 0x00cb, 0x00cb, 0x00cb, 0x00cb, + 0x00cb, 0x00ca, 0x00ca, 0x00ca, 0x00ca, 0x00ca, 0x00ca, 0x00ca, 0x00ca, 0x00c9, 0x00c9, 0x00c9, 0x00c9, 0x00c9, 0x00c9, 0x00c9, + 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00c7, 0x00c7, 0x00c7, 0x00c7, 0x00c7, 0x00c7, 0x00c7, 0x00c7, + 0x00c6, 0x00c6, 0x00c6, 0x00c6, 0x00c6, 0x00c6, 0x00c6, 0x00c5, 0x00c5, 0x00c5, 0x00c5, 0x00c5, 0x00c5, 0x00c5, 0x00c5, 0x00c4, + 0x00c4, 0x00c4, 0x00c4, 0x00c4, 0x00c4, 0x00c4, 0x00c4, 0x00c3, 0x00c3, 0x00c3, 0x00c3, 0x00c3, 0x00c3, 0x00c3, 0x00c3, 0x00c2, + 0x00c2, 0x00c2, 0x00c2, 0x00c2, 0x00c2, 0x00c2, 0x00c2, 0x00c1, 0x00c1, 0x00c1, 0x00c1, 0x00c1, 0x00c1, 0x00c1, 0x00c0, 0x00c0, + 0x00c0, 0x00c0, 0x00c0, 0x00c0, 0x00c0, 0x00c0, 0x00bf, 0x00bf, 0x00bf, 0x00bf, 0x00bf, 0x00bf, 0x00bf, 0x00bf, 0x00be, 0x00be, + 0x00be, 0x00be, 0x00be, 0x00be, 0x00be, 0x00be, 0x00bd, 0x00bd, 0x00bd, 0x00bd, 0x00bd, 0x00bd, 0x00bd, 0x00bd, 0x00bc, 0x00bc, + 0x00bc, 0x00bc, 0x00bc, 0x00bc, 0x00bc, 0x00bc, 0x00bb, 0x00bb, 0x00bb, 0x00bb, 0x00bb, 0x00bb, 0x00bb, 0x00bb, 0x00ba, 0x00ba, + 0x00ba, 0x00ba, 0x00ba, 0x00ba, 0x00ba, 0x00ba, 0x00b9, 0x00b9, 0x00b9, 0x00b9, 0x00b9, 0x00b9, 0x00b9, 0x00b9, 0x00b8, 0x00b8, + 0x00b8, 0x00b8, 0x00b8, 0x00b8, 0x00b8, 0x00b8, 0x00b7, 0x00b7, 0x00b7, 0x00b7, 0x00b7, 0x00b7, 0x00b7, 0x00b7, 0x00b6, 0x00b6, + 0x00b6, 0x00b6, 0x00b6, 0x00b6, 0x00b6, 0x00b6, 0x00b5, 0x00b5, 0x00b5, 0x00b5, 0x00b5, 0x00b5, 0x00b5, 0x00b5, 0x00b4, 0x00b4, + 0x00b4, 0x00b4, 0x00b4, 0x00b4, 0x00b4, 0x00b4, 0x00b3, 0x00b3, 0x00b3, 0x00b3, 0x00b3, 0x00b3, 0x00b3, 0x00b3, 0x00b2, 0x00b2, + 0x00b2, 0x00b2, 0x00b2, 0x00b2, 0x00b2, 0x00b2, 0x00b1, 0x00b1, 0x00b1, 0x00b1, 0x00b1, 0x00b1, 0x00b1, 0x00b1, 0x00b0, 0x00b0, + 0x00b0, 0x00b0, 0x00b0, 0x00b0, 0x00b0, 0x00b0, 0x00af, 0x00af, 0x00af, 0x00af, 0x00af, 0x00af, 0x00af, 0x00af, 0x00ae, 0x00ae, + 0x00ae, 0x00ae, 0x00ae, 0x00ae, 0x00ae, 0x00ae, 0x00ad, 0x00ad, 0x00ad, 0x00ad, 0x00ad, 0x00ad, 0x00ad, 0x00ad, 0x00ac, 0x00ac, + 0x00ac, 0x00ac, 0x00ac, 0x00ac, 0x00ac, 0x00ac, 0x00ab, 0x00ab, 0x00ab, 0x00ab, 0x00ab, 0x00ab, 0x00ab, 0x00ab, 0x00ab, 0x00aa, + 0x00aa, 0x00aa, 0x00aa, 0x00aa, 0x00aa, 0x00aa, 0x00aa, 0x00a9, 0x00a9, 0x00a9, 0x00a9, 0x00a9, 0x00a9, 0x00a9, 0x00a9, 0x00a8, + 0x00a8, 0x00a8, 0x00a8, 0x00a8, 0x00a8, 0x00a8, 0x00a8, 0x00a7, 0x00a7, 0x00a7, 0x00a7, 0x00a7, 0x00a7, 0x00a7, 0x00a7, 0x00a6, + 0x00a6, 0x00a6, 0x00a6, 0x00a6, 0x00a6, 0x00a6, 0x00a6, 0x00a5, 0x00a5, 0x00a5, 0x00a5, 0x00a5, 0x00a5, 0x00a5, 0x00a5, 0x00a4, + 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a3, 0x00a3, 0x00a3, 0x00a3, 0x00a3, 0x00a3, 0x00a3, 0x00a3, + 0x00a2, 0x00a2, 0x00a2, 0x00a2, 0x00a2, 0x00a2, 0x00a2, 0x00a2, 0x00a1, 0x00a1, 0x00a1, 0x00a1, 0x00a1, 0x00a1, 0x00a1, 0x00a1, + 0x00a0, 0x00a0, 0x00a0, 0x00a0, 0x00a0, 0x00a0, 0x00a0, 0x00a0, 0x009f, 0x009f, 0x009f, 0x009f, 0x009f, 0x009f, 0x009f, 0x009f, + 0x009e, 0x009e, 0x009e, 0x009e, 0x009e, 0x009e, 0x009e, 0x009e, 0x009d, 0x009d, 0x009d, 0x009d, 0x009d, 0x009d, 0x009d, 0x009d, + 0x009d, 0x009c, 0x009c, 0x009c, 0x009c, 0x009c, 0x009c, 0x009c, 0x009c, 0x009b, 0x009b, 0x009b, 0x009b, 0x009b, 0x009b, 0x009b, + 0x009b, 0x009a, 0x009a, 0x009a, 0x009a, 0x009a, 0x009a, 0x009a, 0x009a, 0x0099, 0x0099, 0x0099, 0x0099, 0x0099, 0x0099, 0x0099, + 0x0099, 0x0098, 0x0098, 0x0098, 0x0098, 0x0098, 0x0098, 0x0098, 0x0098, 0x0097, 0x0097, 0x0097, 0x0097, 0x0097, 0x0097, 0x0097, + 0x0097, 0x0096, 0x0096, 0x0096, 0x0096, 0x0096, 0x0096, 0x0096, 0x0096, 0x0096, 0x0095, 0x0095, 0x0095, 0x0095, 0x0095, 0x0095, + 0x0095, 0x0095, 0x0094, 0x0094, 0x0094, 0x0094, 0x0094, 0x0094, 0x0094, 0x0094, 0x0093, 0x0093, 0x0093, 0x0093, 0x0093, 0x0093, + 0x0093, 0x0093, 0x0092, 0x0092, 0x0092, 0x0092, 0x0092, 0x0092, 0x0092, 0x0092, 0x0091, 0x0091, 0x0091, 0x0091, 0x0091, 0x0091, + 0x0091, 0x0091, 0x0090, 0x0090, 0x0090, 0x0090, 0x0090, 0x0090, 0x0090, 0x0090, 0x008f, 0x008f, 0x008f, 0x008f, 0x008f, 0x008f, + 0x008f, 0x008f, 0x008e, 0x008e, 0x008e, 0x008e, 0x008e, 0x008e, 0x008e, 0x008e, 0x008d, 0x008d, 0x008d, 0x008d, 0x008d, 0x008d, + 0x008d, 0x008d, 0x008c, 0x008c, 0x008c, 0x008c, 0x008c, 0x008c, 0x008c, 0x008c, 0x008b, 0x008b, 0x008b, 0x008b, 0x008b, 0x008b, + 0x008b, 0x008b, 0x008a, 0x008a, 0x008a, 0x008a, 0x008a, 0x008a, 0x008a, 0x008a, 0x0089, 0x0089, 0x0089, 0x0089, 0x0089, 0x0089, + 0x0089, 0x0089, 0x0088, 0x0088, 0x0088, 0x0088, 0x0088, 0x0088, 0x0088, 0x0088, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, + 0x0087, 0x0087, 0x0086, 0x0086, 0x0086, 0x0086, 0x0086, 0x0086, 0x0086, 0x0086, 0x0085, 0x0085, 0x0085, 0x0085, 0x0085, 0x0085, + 0x0085, 0x0085, 0x0084, 0x0084, 0x0084, 0x0084, 0x0084, 0x0084, 0x0084, 0x0084, 0x0083, 0x0083, 0x0083, 0x0083, 0x0083, 0x0083, + 0x0083, 0x0083, 0x0082, 0x0082, 0x0082, 0x0082, 0x0082, 0x0082, 0x0082, 0x0082, 0x0081, 0x0081, 0x0081, 0x0081, 0x0081, 0x0081, + 0x0081, 0x0081, 0x0080, 0x0080, 0x0080, 0x0080, 0x0080, 0x0080, 0x0080, 0x007f, 0x007f, 0x007f, 0x007f, 0x007f, 0x007f, 0x007f, + 0x007f, 0x007e, 0x007e, 0x007e, 0x007e, 0x007e, 0x007e, 0x007e, 0x007e, 0x007d, 0x007d, 0x007d, 0x007d, 0x007d, 0x007d, 0x007d, + 0x007d, 0x007c, 0x007c, 0x007c, 0x007c, 0x007c, 0x007c, 0x007c, 0x007c, 0x007b, 0x007b, 0x007b, 0x007b, 0x007b, 0x007b, 0x007b, + 0x007a, 0x007a, 0x007a, 0x007a, 0x007a, 0x007a, 0x007a, 0x007a, 0x0079, 0x0079, 0x0079, 0x0079, 0x0079, 0x0079, 0x0079, 0x0079, + 0x0078, 0x0078, 0x0078, 0x0078, 0x0078, 0x0078, 0x0078, 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0076, + 0x0076, 0x0076, 0x0076, 0x0076, 0x0076, 0x0076, 0x0076, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0074, 0x0074, + 0x0074, 0x0074, 0x0074, 0x0074, 0x0074, 0x0074, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0072, 0x0072, 0x0072, + 0x0072, 0x0072, 0x0072, 0x0072, 0x0072, 0x0071, 0x0071, 0x0071, 0x0071, 0x0071, 0x0071, 0x0071, 0x0070, 0x0070, 0x0070, 0x0070, + 0x0070, 0x0070, 0x0070, 0x0070, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006e, 0x006e, 0x006e, 0x006e, 0x006e, + 0x006e, 0x006e, 0x006e, 0x006d, 0x006d, 0x006d, 0x006d, 0x006d, 0x006d, 0x006d, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, + 0x006c, 0x006b, 0x006b, 0x006b, 0x006b, 0x006b, 0x006b, 0x006b, 0x006b, 0x006a, 0x006a, 0x006a, 0x006a, 0x006a, 0x006a, 0x006a, + 0x0069, 0x0069, 0x0069, 0x0069, 0x0069, 0x0069, 0x0069, 0x0068, 0x0068, 0x0068, 0x0068, 0x0068, 0x0068, 0x0068, 0x0068, 0x0067, + 0x0067, 0x0067, 0x0067, 0x0067, 0x0067, 0x0067, 0x0066, 0x0066, 0x0066, 0x0066, 0x0066, 0x0066, 0x0066, 0x0065, 0x0065, 0x0065, + 0x0065, 0x0065, 0x0065, 0x0065, 0x0064, 0x0064, 0x0064, 0x0064, 0x0064, 0x0064, 0x0064, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, + 0x0063, 0x0063, 0x0062, 0x0062, 0x0062, 0x0062, 0x0062, 0x0062, 0x0062, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, + 0x0060, 0x0060, 0x0060, 0x0060, 0x0060, 0x0060, 0x0060, 0x005f, 0x005f, 0x005f, 0x005f, 0x005f, 0x005f, 0x005f, 0x005e, 0x005e, + 0x005e, 0x005e, 0x005e, 0x005e, 0x005e, 0x005d, 0x005d, 0x005d, 0x005d, 0x005d, 0x005d, 0x005d, 0x005c, 0x005c, 0x005c, 0x005c, + 0x005c, 0x005c, 0x005c, 0x005b, 0x005b, 0x005b, 0x005b, 0x005b, 0x005b, 0x005b, 0x005a, 0x005a, 0x005a, 0x005a, 0x005a, 0x005a, + 0x005a, 0x0059, 0x0059, 0x0059, 0x0059, 0x0059, 0x0059, 0x0059, 0x0058, 0x0058, 0x0058, 0x0058, 0x0058, 0x0058, 0x0057, 0x0057, + 0x0057, 0x0057, 0x0057, 0x0057, 0x0057, 0x0056, 0x0056, 0x0056, 0x0056, 0x0056, 0x0056, 0x0056, 0x0055, 0x0055, 0x0055, 0x0055, + 0x0055, 0x0055, 0x0055, 0x0054, 0x0054, 0x0054, 0x0054, 0x0054, 0x0054, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, 0x0053, + 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, 0x0052, 0x0051, 0x0051, 0x0051, 0x0051, 0x0051, 0x0051, 0x0051, 0x0050, 0x0050, 0x0050, + 0x0050, 0x0050, 0x0050, 0x004f, 0x004f, 0x004f, 0x004f, 0x004f, 0x004f, 0x004f, 0x004e, 0x004e, 0x004e, 0x004e, 0x004e, 0x004e, + 0x004d, 0x004d, 0x004d, 0x004d, 0x004d, 0x004d, 0x004d, 0x004c, 0x004c, 0x004c, 0x004c, 0x004c, 0x004c, 0x004b, 0x004b, 0x004b, + 0x004b, 0x004b, 0x004b, 0x004a, 0x004a, 0x004a, 0x004a, 0x004a, 0x004a, 0x004a, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, + 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0048, 0x0047, 0x0047, 0x0047, 0x0047, 0x0047, 0x0047, 0x0046, 0x0046, 0x0046, 0x0046, + 0x0046, 0x0046, 0x0046, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0045, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0043, + 0x0043, 0x0043, 0x0043, 0x0043, 0x0043, 0x0042, 0x0042, 0x0042, 0x0042, 0x0042, 0x0042, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, + 0x0041, 0x0040, 0x0040, 0x0040, 0x0040, 0x0040, 0x0040, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003e, 0x003e, 0x003e, + 0x003e, 0x003e, 0x003e, 0x003d, 0x003d, 0x003d, 0x003d, 0x003d, 0x003d, 0x003c, 0x003c, 0x003c, 0x003c, 0x003c, 0x003c, 0x003b, + 0x003b, 0x003b, 0x003b, 0x003b, 0x003a, 0x003a, 0x003a, 0x003a, 0x003a, 0x003a, 0x0039, 0x0039, 0x0039, 0x0039, 0x0039, 0x0039, + 0x0038, 0x0038, 0x0038, 0x0038, 0x0038, 0x0038, 0x0037, 0x0037, 0x0037, 0x0037, 0x0037, 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, + 0x0036, 0x0035, 0x0035, 0x0035, 0x0035, 0x0035, 0x0035, 0x0034, 0x0034, 0x0034, 0x0034, 0x0034, 0x0033, 0x0033, 0x0033, 0x0033, + 0x0033, 0x0033, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0030, 0x0030, 0x0030, + 0x0030, 0x0030, 0x002f, 0x002f, 0x002f, 0x002f, 0x002f, 0x002f, 0x002e, 0x002e, 0x002e, 0x002e, 0x002e, 0x002d, 0x002d, 0x002d, + 0x002d, 0x002d, 0x002c, 0x002c, 0x002c, 0x002c, 0x002c, 0x002c, 0x002b, 0x002b, 0x002b, 0x002b, 0x002b, 0x002a, 0x002a, 0x002a, + 0x002a, 0x002a, 0x0029, 0x0029, 0x0029, 0x0029, 0x0029, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0027, 0x0027, 0x0027, + 0x0027, 0x0027, 0x0026, 0x0026, 0x0026, 0x0026, 0x0026, 0x0025, 0x0025, 0x0025, 0x0025, 0x0025, 0x0024, 0x0024, 0x0024, 0x0024, + 0x0024, 0x0023, 0x0023, 0x0023, 0x0023, 0x0023, 0x0022, 0x0022, 0x0022, 0x0022, 0x0022, 0x0021, 0x0021, 0x0021, 0x0021, 0x0021, + 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x001f, 0x001f, 0x001f, 0x001f, 0x001f, 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, 0x001d, + 0x001d, 0x001d, 0x001d, 0x001d, 0x001c, 0x001c, 0x001c, 0x001c, 0x001c, 0x001b, 0x001b, 0x001b, 0x001b, 0x001a, 0x001a, 0x001a, + 0x001a, 0x001a, 0x0019, 0x0019, 0x0019, 0x0019, 0x0019, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0017, 0x0017, 0x0017, 0x0017, + 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0015, 0x0015, 0x0015, 0x0015, 0x0015, 0x0014, 0x0014, 0x0014, 0x0014, 0x0013, 0x0013, + 0x0013, 0x0013, 0x0013, 0x0012, 0x0012, 0x0012, 0x0012, 0x0011, 0x0011, 0x0011, 0x0011, 0x0011, 0x0010, 0x0010, 0x0010, 0x0010, + 0x000f, 0x000f, 0x000f, 0x000f, 0x000f, 0x000e, 0x000e, 0x000e, 0x000e, 0x000d, 0x000d, 0x000d, 0x000d, 0x000d, 0x000c, 0x000c, + 0x000c, 0x000c, 0x000b, 0x000b, 0x000b, 0x000b, 0x000a, 0x000a, 0x000a, 0x000a, 0x000a, 0x0009, 0x0009, 0x0009, 0x0009, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, 0x0006, 0x0006, 0x0006, 0x0006, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0004, 0x0004, 0x0004, 0x0004, 0x0003, 0x0003, 0x0003, 0x0003, 0x0002, 0x0002, 0x0002, 0x0002, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, -lighttable_t* scalelight[LIGHTLEVELS][MAXLIGHTSCALE]; -lighttable_t* scalelightfixed[MAXLIGHTSCALE]; -lighttable_t* zlight[LIGHTLEVELS][MAXLIGHTZ]; +}; +#endif + +#if !USE_LIGHTMAP_INDEXES +const lighttable_t* scalelight[LIGHTLEVELS][MAXLIGHTSCALE]; +const lighttable_t* scalelightfixed[MAXLIGHTSCALE]; +#if !NO_USE_ZLIGHT +const lighttable_t* zlight[LIGHTLEVELS][MAXLIGHTZ]; +#endif +#else +int8_t scalelight[LIGHTLEVELS][MAXLIGHTSCALE]; +int8_t scalelightfixed[MAXLIGHTSCALE]; +#if !NO_USE_ZLIGHT +int8_t zlight[LIGHTLEVELS][MAXLIGHTZ]; +#endif +#endif // bumped light from gun blasts -int extralight; - - - -void (*colfunc) (void); -void (*basecolfunc) (void); -void (*fuzzcolfunc) (void); -void (*transcolfunc) (void); -void (*spanfunc) (void); - +int extralight; +#if !NO_RDRAW +void (*colfunc)(void); +void (*basecolfunc)(void); +void (*fuzzcolfunc)(void); +void (*transcolfunc)(void); +void (*spanfunc)(void); +#endif // // R_AddPointToBox @@ -121,18 +431,17 @@ void (*spanfunc) (void); // void R_AddPointToBox -( int x, - int y, - fixed_t* box ) -{ - if (x< box[BOXLEFT]) - box[BOXLEFT] = x; - if (x> box[BOXRIGHT]) - box[BOXRIGHT] = x; - if (y< box[BOXBOTTOM]) - box[BOXBOTTOM] = y; - if (y> box[BOXTOP]) - box[BOXTOP] = y; + (int x, + int y, + fixed_t *box) { + if (x < box[BOXLEFT]) + box[BOXLEFT] = x; + if (x > box[BOXRIGHT]) + box[BOXRIGHT] = x; + if (y < box[BOXBOTTOM]) + box[BOXBOTTOM] = y; + if (y > box[BOXTOP]) + box[BOXTOP] = y; } @@ -144,117 +453,106 @@ R_AddPointToBox // int R_PointOnSide -( fixed_t x, - fixed_t y, - node_t* node ) -{ - fixed_t dx; - fixed_t dy; - fixed_t left; - fixed_t right; - - if (!node->dx) - { - if (x <= node->x) - return node->dy > 0; - - return node->dy < 0; + (fixed_t x, + fixed_t y, + node_t *node) { + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!node->dx) { + if (x <= node_coord_to_fixed(node->x)) + return node->dy > 0; + + return node->dy < 0; } - if (!node->dy) - { - if (y <= node->y) - return node->dx < 0; - - return node->dx > 0; - } - - dx = (x - node->x); - dy = (y - node->y); - - // Try to quickly decide by looking at sign bits. - if ( (node->dy ^ node->dx ^ dx ^ dy)&0x80000000 ) - { - if ( (node->dy ^ dx) & 0x80000000 ) - { - // (left is negative) - return 1; - } - return 0; + if (!node->dy) { + if (y <= node_coord_to_fixed(node->y)) + return node->dx < 0; + + return node->dx > 0; } - left = FixedMul ( node->dy>>FRACBITS , dx ); - right = FixedMul ( dy , node->dx>>FRACBITS ); - - if (right < left) - { - // front side - return 0; + dx = (x - node_coord_to_fixed(node->x)); + dy = (y - node_coord_to_fixed(node->y)); + + // todo graham, check code here.. we might be able to do better + // Try to quickly decide by looking at sign bits. + if ((node_coord_to_fixed(node->dy) ^ node_coord_to_fixed(node->dx) ^ dx ^ dy) & 0x80000000) { + if ((node_coord_to_fixed(node->dy) ^ dx) & 0x80000000) { + // (left is negative) + return 1; + } + return 0; + } + + left = FixedMul(node_coord_to_int16(node->dy), dx); + right = FixedMul(dy, node_coord_to_int16(node->dx)); + + if (right < left) { + // front side + return 0; } // back side - return 1; + return 1; } int R_PointOnSegSide -( fixed_t x, - fixed_t y, - seg_t* line ) -{ - fixed_t lx; - fixed_t ly; - fixed_t ldx; - fixed_t ldy; - fixed_t dx; - fixed_t dy; - fixed_t left; - fixed_t right; - - lx = line->v1->x; - ly = line->v1->y; - - ldx = line->v2->x - lx; - ldy = line->v2->y - ly; - - if (!ldx) - { - if (x <= lx) - return ldy > 0; - - return ldy < 0; + (fixed_t x, + fixed_t y, + seg_t *line) { + fixed_t lx; + fixed_t ly; + fixed_t ldx; + fixed_t ldy; + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + lx = vertex_x(seg_v1(line)); + ly = vertex_y(seg_v1(line)); + + ldx = vertex_x(seg_v2(line)) - lx; + ldy = vertex_y(seg_v2(line)) - ly; + + if (!ldx) { + if (x <= lx) + return ldy > 0; + + return ldy < 0; } - if (!ldy) - { - if (y <= ly) - return ldx < 0; - - return ldx > 0; - } - - dx = (x - lx); - dy = (y - ly); - - // Try to quickly decide by looking at sign bits. - if ( (ldy ^ ldx ^ dx ^ dy)&0x80000000 ) - { - if ( (ldy ^ dx) & 0x80000000 ) - { - // (left is negative) - return 1; - } - return 0; + if (!ldy) { + if (y <= ly) + return ldx < 0; + + return ldx > 0; } - left = FixedMul ( ldy>>FRACBITS , dx ); - right = FixedMul ( dy , ldx>>FRACBITS ); - - if (right < left) - { - // front side - return 0; + dx = (x - lx); + dy = (y - ly); + + // Try to quickly decide by looking at sign bits. + if ((ldy ^ ldx ^ dx ^ dy) & 0x80000000) { + if ((ldy ^ dx) & 0x80000000) { + // (left is negative) + return 1; + } + return 0; + } + + left = FixedMul(ldy >> FRACBITS, dx); + right = FixedMul(dy, ldx >> FRACBITS); + + if (right < left) { + // front side + return 0; } // back side - return 1; + return 1; } @@ -269,90 +567,92 @@ R_PointOnSegSide // +// to get a global angle from cartesian coordinates, the coordinates are +// flipped until they are in the first octant of the coordinate system, then +// the y (<=x) is scaled and divided by x to get a tangent (slope) value +// which is looked up in the tantoangle[] table. The +1 size is to handle +// the case when x==y without additional checking. +static int SlopeDiv(unsigned int num, unsigned int den) +{ + unsigned ans; - -angle_t -R_PointToAngle -( fixed_t x, - fixed_t y ) -{ - x -= viewx; - y -= viewy; - - if ( (!x) && (!y) ) - return 0; - - if (x>= 0) + if (den < 512) { - // x >=0 - if (y>= 0) - { - // y>= 0 - - if (x>y) - { - // octant 0 - return tantoangle[ SlopeDiv(y,x)]; - } - else - { - // octant 1 - return ANG90-1-tantoangle[ SlopeDiv(x,y)]; - } - } - else - { - // y<0 - y = -y; - - if (x>y) - { - // octant 8 - return -tantoangle[SlopeDiv(y,x)]; - } - else - { - // octant 7 - return ANG270+tantoangle[ SlopeDiv(x,y)]; - } - } + return SLOPERANGE; } else { - // x<0 - x = -x; + ans = (num << 3) / (den >> 8); - if (y>= 0) - { - // y>= 0 - if (x>y) - { - // octant 3 - return ANG180-1-tantoangle[ SlopeDiv(y,x)]; - } - else - { - // octant 2 - return ANG90+ tantoangle[ SlopeDiv(x,y)]; - } - } - else - { - // y<0 - y = -y; + if (ans <= SLOPERANGE) + { + return ans; + } + else + { + return SLOPERANGE; + } + } +} - if (x>y) - { - // octant 4 - return ANG180+tantoangle[ SlopeDiv(y,x)]; - } - else - { - // octant 5 - return ANG270-1-tantoangle[ SlopeDiv(x,y)]; - } - } + +angle_t +R_PointToAngleDX + (fixed_t x, + fixed_t y) { + if ((!x) && (!y)) + return 0; + + if (x >= 0) { + // x >=0 + if (y >= 0) { + // y>= 0 + + if (x > y) { + // octant 0 + return tantoangle[SlopeDiv(y, x)]; + } else { + // octant 1 + return ANG90 - 1 - tantoangle[SlopeDiv(x, y)]; + } + } else { + // y<0 + y = -y; + + if (x > y) { + // octant 8 + return -tantoangle[SlopeDiv(y, x)]; + } else { + // octant 7 + return ANG270 + tantoangle[SlopeDiv(x, y)]; + } + } + } else { + // x<0 + x = -x; + + if (y >= 0) { + // y>= 0 + if (x > y) { + // octant 3 + return ANG180 - 1 - tantoangle[SlopeDiv(y, x)]; + } else { + // octant 2 + return ANG90 + tantoangle[SlopeDiv(x, y)]; + } + } else { + // y<0 + y = -y; + + if (x > y) { + // octant 4 + return ANG180 + tantoangle[SlopeDiv(y, x)]; + } else { + // octant 5 + return ANG270 - 1 - tantoangle[SlopeDiv(x, y)]; + } + } } return 0; } @@ -360,67 +660,58 @@ R_PointToAngle angle_t R_PointToAngle2 -( fixed_t x1, - fixed_t y1, - fixed_t x2, - fixed_t y2 ) -{ + (fixed_t x1, + fixed_t y1, + fixed_t x2, + fixed_t y2) { viewx = x1; viewy = y1; - - return R_PointToAngle (x2, y2); + + return R_PointToAngle(x2, y2); } fixed_t R_PointToDist -( fixed_t x, - fixed_t y ) -{ - int angle; - fixed_t dx; - fixed_t dy; - fixed_t temp; - fixed_t dist; - fixed_t frac; - + (fixed_t x, + fixed_t y) { + int angle; + fixed_t dx; + fixed_t dy; + fixed_t temp; + fixed_t dist; + fixed_t frac; + dx = abs(x - viewx); dy = abs(y - viewy); - - if (dy>dx) - { - temp = dx; - dx = dy; - dy = temp; + + if (dy > dx) { + temp = dx; + dx = dy; + dy = temp; } // Fix crashes in udm1.wad - if (dx != 0) - { + if (dx != 0) { frac = FixedDiv(dy, dx); + } else { + frac = 0; } - else - { - frac = 0; - } - - angle = (tantoangle[frac>>DBITS]+ANG90) >> ANGLETOFINESHIFT; + + angle = (tantoangle[frac >> DBITS] + ANG90) >> ANGLETOFINESHIFT; // use as cosine - dist = FixedDiv (dx, finesine[angle] ); - + dist = FixedDiv(dx, finesine(angle)); + return dist; } - - // // R_InitPointToAngle // -void R_InitPointToAngle (void) -{ +void R_InitPointToAngle(void) { // UNUSED - now getting from tables.c #if 0 int i; @@ -431,9 +722,9 @@ void R_InitPointToAngle (void) // for (i=0 ; i<=SLOPERANGE ; i++) { - f = atan( (float)i/SLOPERANGE )/(3.141592657*2); - t = 0xffffffff*f; - tantoangle[i] = t; + f = atan( (float)i/SLOPERANGE )/(3.141592657*2); + t = 0xffffffff*f; + tantoangle[i] = t; } #endif } @@ -446,64 +737,59 @@ void R_InitPointToAngle (void) // at the given angle. // rw_distance must be calculated first. // -fixed_t R_ScaleFromGlobalAngle (angle_t visangle) -{ - fixed_t scale; - angle_t anglea; - angle_t angleb; - int sinea; - int sineb; - fixed_t num; - int den; +fixed_t R_ScaleFromGlobalAngle(angle_t visangle) { + fixed_t scale; + angle_t anglea; + angle_t angleb; + int sinea; + int sineb; + fixed_t num; + int den; // UNUSED #if 0 -{ - fixed_t dist; - fixed_t z; - fixed_t sinv; - fixed_t cosv; - - sinv = finesine[(visangle-rw_normalangle)>>ANGLETOFINESHIFT]; - dist = FixedDiv (rw_distance, sinv); - cosv = finecosine[(viewangle-visangle)>>ANGLETOFINESHIFT]; - z = abs(FixedMul (dist, cosv)); - scale = FixedDiv(projection, z); - return scale; -} + { + fixed_t dist; + fixed_t z; + fixed_t sinv; + fixed_t cosv; + + sinv = finesine((visangle-rw_normalangle)>>ANGLETOFINESHIFT); + dist = FixedDiv (rw_distance, sinv); + cosv = finecosine((viewangle-visangle)>>ANGLETOFINESHIFT); + z = abs(FixedMul (dist, cosv)); + scale = FixedDiv(projection, z); + return scale; + } #endif - anglea = ANG90 + (visangle-viewangle); - angleb = ANG90 + (visangle-rw_normalangle); + anglea = ANG90 + (visangle - viewangle); + angleb = ANG90 + (visangle - rw_normalangle); // both sines are allways positive - sinea = finesine[anglea>>ANGLETOFINESHIFT]; - sineb = finesine[angleb>>ANGLETOFINESHIFT]; - num = FixedMul(projection,sineb)<> ANGLETOFINESHIFT); + sineb = finesine(angleb >> ANGLETOFINESHIFT); + num = FixedMul(projection, sineb) << detailshift; + den = FixedMul(rw_distance, sinea); - if (den > num>>FRACBITS) - { - scale = FixedDiv (num, den); + if (den > num >> FRACBITS) { + scale = FixedDiv(num, den); + + if (scale > 64 * FRACUNIT) + scale = 64 * FRACUNIT; + else if (scale < 256) + scale = 256; + } else + scale = 64 * FRACUNIT; - if (scale > 64*FRACUNIT) - scale = 64*FRACUNIT; - else if (scale < 256) - scale = 256; - } - else - scale = 64*FRACUNIT; - return scale; } - // // R_InitTables // -void R_InitTables (void) -{ +void R_InitTables(void) { // UNUSED: now getting from tables.c #if 0 int i; @@ -514,88 +800,104 @@ void R_InitTables (void) // viewangle tangent table for (i=0 ; i FRACUNIT*2) - t = -1; - else if (finetangent[i] < -FRACUNIT*2) - t = viewwidth+1; - else - { - t = FixedMul (finetangent[i], focallength); - t = (centerxfrac - t+FRACUNIT-1)>>FRACBITS; + focallength = FixedDiv(centerxfrac, + finetangent(FINEANGLES / 4 + FIELDOFVIEW / 2)); - if (t < -1) - t = -1; - else if (t>viewwidth+1) - t = viewwidth+1; - } - viewangletox[i] = t; + for (i = 0; i < FINEANGLES / 2; i++) { + if (finetangent(i) > FRACUNIT * 2) + t = -1; + else if (finetangent(i) < -FRACUNIT * 2) + t = viewwidth + 1; + else { + t = FixedMul(finetangent(i), focallength); + t = (centerxfrac - t + FRACUNIT - 1) >> FRACBITS; + + if (t < -1) + t = -1; + else if (t > viewwidth + 1) + t = viewwidth + 1; + } + viewangletox[i] = t; } - // Scan viewangletox[] to generate xtoviewangle[]: // xtoviewangle will give the smallest view angle // that maps to x. - for (x=0;x<=viewwidth;x++) - { - i = 0; - while (viewangletox[i]>x) - i++; - xtoviewangle[x] = (i< x) + i++; + xtoviewangle[x] = (i << ANGLETOFINESHIFT) - ANG90; } - + // Take out the fencepost cases from viewangletox. - for (i=0 ; i>16); + } + printf("\n"); + } +#endif +#if 0 + for(int i=0;i>= LIGHTSCALESHIFT; - level = startmap - scale/DISTMAP; - - if (level < 0) - level = 0; +#if !NO_USE_ZLIGHT + for (i = 0; i < LIGHTLEVELS; i++) { + startmap = ((LIGHTLEVELS - 1 - i) * 2) * NUMCOLORMAPS / LIGHTLEVELS; + for (j = 0; j < MAXLIGHTZ; j++) { + scale = FixedDiv((SCREENWIDTH / 2 * FRACUNIT), (j + 1) << LIGHTZSHIFT); + scale >>= LIGHTSCALESHIFT; + level = startmap - scale / DISTMAP; - if (level >= NUMCOLORMAPS) - level = NUMCOLORMAPS-1; + if (level < 0) + level = 0; - zlight[i][j] = colormaps + level*256; - } + if (level >= NUMCOLORMAPS) + level = NUMCOLORMAPS - 1; + +#if !USE_LIGHTMAP_INDEXES + zlight[i][j] = colormaps + level*256; +#else + zlight[i][j] = level; +#endif + } } +#endif } - // // R_SetViewSize // Do not really change anything here, // because it might be in the middle of a refresh. // The change will take effect next refresh. // -boolean setsizeneeded; -int setblocks; -int setdetail; +boolean setsizeneeded; +#if !DOOM_TINY +int setblocks; +int setdetail; +#else +#define setblocks 10 +#define setdetail 0 +#endif void R_SetViewSize -( int blocks, - int detail ) -{ + (int blocks, + int detail) { setsizeneeded = true; +#if !DOOM_TINY setblocks = blocks; setdetail = detail; +#endif } // // R_ExecuteSetViewSize // -void R_ExecuteSetViewSize (void) -{ - fixed_t cosadj; - fixed_t dy; - int i; - int j; - int level; - int startmap; +void R_ExecuteSetViewSize(void) { + fixed_t cosadj; + fixed_t dy; + int i; + int j; + int level; + int startmap; setsizeneeded = false; - if (setblocks == 11) - { - scaledviewwidth = SCREENWIDTH; - viewheight = SCREENHEIGHT; + if (setblocks == 11) { + scaledviewwidth = SCREENWIDTH; + viewheight = SCREENHEIGHT; + } else { + scaledviewwidth = setblocks * 32; + viewheight = (setblocks * 168 / 10) & ~7; } - else - { - scaledviewwidth = setblocks*32; - viewheight = (setblocks*168/10)&~7; - } - + detailshift = setdetail; - viewwidth = scaledviewwidth>>detailshift; - - centery = viewheight/2; - centerx = viewwidth/2; - centerxfrac = centerx<> detailshift; + + centery = viewheight / 2; + centerx = viewwidth / 2; + centerxfrac = centerx << FRACBITS; + centeryfrac = centery << FRACBITS; projection = centerxfrac; - if (!detailshift) - { - colfunc = basecolfunc = R_DrawColumn; - fuzzcolfunc = R_DrawFuzzColumn; - transcolfunc = R_DrawTranslatedColumn; - spanfunc = R_DrawSpan; +#if !NO_RDRAW + if (!detailshift) { + colfunc = basecolfunc = R_DrawColumn; + fuzzcolfunc = R_DrawFuzzColumn; + transcolfunc = R_DrawTranslatedColumn; + spanfunc = R_DrawSpan; + } else { + colfunc = basecolfunc = R_DrawColumnLow; + fuzzcolfunc = R_DrawFuzzColumnLow; + transcolfunc = R_DrawTranslatedColumnLow; + spanfunc = R_DrawSpanLow; } - else - { - colfunc = basecolfunc = R_DrawColumnLow; - fuzzcolfunc = R_DrawFuzzColumnLow; - transcolfunc = R_DrawTranslatedColumnLow; - spanfunc = R_DrawSpanLow; +#endif + +#if !NO_RDRAW + R_InitBuffer(scaledviewwidth, viewheight); +#endif + + R_InitTextureMapping(); + + // psprite scales + pspritescale = FRACUNIT * viewwidth / SCREENWIDTH; + pspriteiscale = FRACUNIT * SCREENWIDTH / viewwidth; + + // thing clipping + for (i = 0; i < viewwidth; i++) + maxfloorceilingcliparray[i] = viewheight + FLOOR_CEILING_CLIP_OFFSET; + + // planes + for (i = 0; i < viewheight; i++) { + dy = ((i - viewheight / 2) << FRACBITS) + FRACUNIT / 2; + dy = abs(dy); + yslope[i] = FixedDiv((viewwidth << detailshift) / 2 * FRACUNIT, dy); } - R_InitBuffer (scaledviewwidth, viewheight); - - R_InitTextureMapping (); - - // psprite scales - pspritescale = FRACUNIT*viewwidth/SCREENWIDTH; - pspriteiscale = FRACUNIT*SCREENWIDTH/viewwidth; - - // thing clipping - for (i=0 ; i> ANGLETOFINESHIFT)); + _distscale[i] = FixedDiv(FRACUNIT, cosadj); } - - for (i=0 ; i>ANGLETOFINESHIFT]); - distscale[i] = FixedDiv (FRACUNIT,cosadj); - } - +#endif + // Calculate the light levels to use // for each level / scale combination. - for (i=0 ; i< LIGHTLEVELS ; i++) - { - startmap = ((LIGHTLEVELS-1-i)*2)*NUMCOLORMAPS/LIGHTLEVELS; - for (j=0 ; j= NUMCOLORMAPS) - level = NUMCOLORMAPS-1; + if (level < 0) + level = 0; - scalelight[i][j] = colormaps + level*256; - } + if (level >= NUMCOLORMAPS) + level = NUMCOLORMAPS - 1; + + +#if !USE_LIGHTMAP_INDEXES + scalelight[i][j] = colormaps + level*256; +#else + scalelight[i][j] = level; +#endif + + } } } @@ -764,25 +1075,24 @@ void R_ExecuteSetViewSize (void) -void R_Init (void) -{ - R_InitData (); - printf ("."); - R_InitPointToAngle (); - printf ("."); - R_InitTables (); +void R_Init(void) { + R_InitData(); + printf("."); + R_InitPointToAngle(); + printf("."); + R_InitTables(); // viewwidth / viewheight / detailLevel are set by the defaults - printf ("."); + printf("."); + + R_SetViewSize(screenblocks, detailLevel); + R_InitPlanes(); + printf("."); + R_InitLightTables(); + printf("."); + R_InitSkyMap(); + R_InitTranslationTables(); + printf("."); - R_SetViewSize (screenblocks, detailLevel); - R_InitPlanes (); - printf ("."); - R_InitLightTables (); - printf ("."); - R_InitSkyMap (); - R_InitTranslationTables (); - printf ("."); - framecount = 0; } @@ -790,102 +1100,377 @@ void R_Init (void) // // R_PointInSubsector // -subsector_t* +subsector_t * R_PointInSubsector -( fixed_t x, - fixed_t y ) -{ - node_t* node; - int side; - int nodenum; + (fixed_t x, + fixed_t y) { + node_t *node; + int side; + int nodenum; // single subsector is a special case - if (!numnodes) - return subsectors; - - nodenum = numnodes-1; + if (!numnodes) + return subsectors; - while (! (nodenum & NF_SUBSECTOR) ) - { - node = &nodes[nodenum]; - side = R_PointOnSide (x, y, node); - nodenum = node->children[side]; + nodenum = numnodes - 1; + + while (!(nodenum & NF_SUBSECTOR)) { + node = &nodes[nodenum]; + side = R_PointOnSide(x, y, node); + nodenum = bsp_child(nodenum, side); } - + return &subsectors[nodenum & ~NF_SUBSECTOR]; } - // // R_SetupFrame // -void R_SetupFrame (player_t* player) -{ - int i; - +void R_SetupFrame(player_t *player) { + int i; + viewplayer = player; - viewx = player->mo->x; - viewy = player->mo->y; - viewangle = player->mo->angle + viewangleoffset; + viewx = player->mo->xy.x; + viewy = player->mo->xy.y; + viewangle = mobj_full(player->mo)->angle + viewangleoffset; extralight = player->extralight; viewz = player->viewz; - - viewsin = finesine[viewangle>>ANGLETOFINESHIFT]; - viewcos = finecosine[viewangle>>ANGLETOFINESHIFT]; - - sscount = 0; - - if (player->fixedcolormap) - { - fixedcolormap = - colormaps - + player->fixedcolormap*256; - - walllights = scalelightfixed; - for (i=0 ; i> ANGLETOFINESHIFT); + viewcos = finecosine(viewangle >> ANGLETOFINESHIFT); + +// sscount = 0; + + if (player->fixedcolormap) { +#if !USE_LIGHTMAP_INDEXES + fixedcolormap = + colormaps + + player->fixedcolormap*256; +#else + fixedcolormap = player->fixedcolormap; +#endif + + walllights = scalelightfixed; + + for (i = 0; i < MAXLIGHTSCALE; i++) + scalelightfixed[i] = fixedcolormap; + } else + fixedcolormap = 0; + framecount++; validcount++; } - - // // R_RenderView // -void R_RenderPlayerView (player_t* player) -{ - R_SetupFrame (player); +void R_RenderPlayerView(player_t *player) { + R_SetupFrame(player); +#if MU_STATS + stats_dc_iscale_min = 0xffffffff; + stats_dc_iscale_max = 0; +#endif + +#if USE_ERASE_FRAME +#if !NO_RDRAW + R_VideoClear(); +#endif +#endif // Clear buffers. - R_ClearClipSegs (); - R_ClearDrawSegs (); - R_ClearPlanes (); - R_ClearSprites (); - + R_ClearClipSegs(); + R_ClearDrawSegs(); + R_ClearPlanes(); + R_ClearSprites(); + // check for new console commands. - NetUpdate (); + NetUpdate(); +#if PICO_ON_DEVICE +// gpio_put(22, 1); +#endif +#if DOOM_SMALL + sector_check_reset(); +#endif // The head node is the last node output. - R_RenderBSPNode (numnodes-1); - - // Check for new console commands. - NetUpdate (); - - R_DrawPlanes (); - - // Check for new console commands. - NetUpdate (); - - R_DrawMasked (); +#if !USE_WHD + R_RenderBSPNode(numnodes - 1); +#else +#if PICODOOM_RENDER_NEWHOPE + // for pd column lets draw these early since they clip other stuff (and we don't want to discard + // them if we run out of column slots) + // draw the psprites on top of everything + // but does not draw on side views + if (!viewangleoffset) { + extern void R_DrawPlayerSprites(void); + R_DrawPlayerSprites(); + } +#endif + + node_coord_t bbox[4] = { 32767, -32768, -32768, 32767 }; + R_RenderBSPNode(numnodes -1, bbox); +#endif +#if PICO_ON_DEVICE +// gpio_put(22, 0); +#endif // Check for new console commands. - NetUpdate (); + NetUpdate(); + +#if !NO_VISPLANE_GUTS + // Visplanes + R_DrawPlanes(); +#endif + + // Check for new console commands. + NetUpdate(); + + R_DrawMasked(); + + // Check for new console commands. + NetUpdate(); +#if MU_STATS + static uint32_t iscaler_min = 0xffffffff; + static uint32_t iscaler_max = 0x00000000; + if (stats_dc_iscale_min < iscaler_min) { + iscaler_min = stats_dc_iscale_min; + printf("< ISCALE %08x %08x\n", iscaler_min, iscaler_max); + } + if (stats_dc_iscale_max > iscaler_max) { + iscaler_max = stats_dc_iscale_max; + printf("> ISCALE %08x %08x\n", iscaler_min, iscaler_max); + } +#endif } + +typedef unsigned int uint; +#if USE_RAW_MAPLINEDEF || DOOM_SMALL +uint32_t *line_sector_check_bitmap; +#endif +#if USE_RAW_MAPLINEDEF +uint32_t *line_mapped_bitmap, *line_special_cleared_bitmap; +void clear_line_special(should_be_const line_t *line) { +#if !WHD_SUPER_TINY + uint index = line - lines; + assert(index < numlines); +#else + uint index = line_bitmap_index(line - lines); + assert(index < numlines5); +#endif + uint bit = 1u << (index & 31); + line_special_cleared_bitmap[index>>5] |= bit; +} + +#if SAVE_COMPRESSED +int whd_line_special(should_be_const line_t *line) { +#if !WHD_SUPER_TINY + int rc = line->special; + if (rc) { + uint index = line - lines; + assert(index < numlines); + uint bit = 1u << (index & 31); + } +#else + int rc = 0; + if ((line[1] & (ML_HAS_SPECIAL >> 8)) != 0) { + const static uint8_t special_pos[8] = { 5, 6, 6, 7, 6, 7, 7, 8 }; + uint pos = special_pos[line[1]&7]; + rc = line[pos]; + uint index = line_bitmap_index(line - lines); + assert(index < numlines5); + } +#endif + return rc; +} +#endif + +int line_special(should_be_const line_t *line) { +#if !WHD_SUPER_TINY + int rc = line->special; + if (rc) { + uint index = line - lines; + assert(index < numlines); + uint bit = 1u << (index & 31); + if (line_special_cleared_bitmap[index>>5] & bit) { + rc = 0; + } + } +#else + int rc = 0; + if ((line[1] & (ML_HAS_SPECIAL >> 8)) != 0) { + const static uint8_t special_pos[8] = { 5, 6, 6, 7, 6, 7, 7, 8 }; + uint pos = special_pos[line[1]&7]; + rc = line[pos]; + uint index = line_bitmap_index(line - lines); + assert(index < numlines5); + uint bit = 1u << (index & 31); + if (line_special_cleared_bitmap[index>>5] & bit) { + rc = 0; + } + } +#endif + return rc; +} + +boolean line_validcount_update_check_impl(should_be_const line_t *ld) { +#if !WHD_SUPER_TINY + uint index = ld - lines; + assert(index < numlines); +#else + uint index = line_bitmap_index(ld - lines); + assert(index < numlines5); +#endif + uint bit = 1u << (index & 31); + index >>= 5; + if (line_sector_check_bitmap[index] & bit) { + return true; + } + line_sector_check_bitmap[index] |= bit; + return false; +} + +boolean line_is_mapped(should_be_const line_t *ld) { +#if !WHD_SUPER_TINY + uint index = ld - lines; + assert(index < numlines); +#else + uint index = line_bitmap_index(ld - lines); + assert(index < numlines5); +#endif + uint bit = 1u << (index & 31); + return line_mapped_bitmap[index>>5] & bit ? true : false; +} + +void line_set_mapped(should_be_const line_t *ld) { +#if !WHD_SUPER_TINY + uint index = ld - lines; + assert(index < numlines); +#else + uint index = line_bitmap_index(ld - lines); + assert(index < numlines5); +#endif + uint bit = 1u << (index & 31); + line_mapped_bitmap[index>>5] |= bit; +} + +void line_check_reset(void) { +#if !WHD_SUPER_TINY + memset(line_sector_check_bitmap, 0, (numlines + 31) / 8); +#else + memset(line_sector_check_bitmap, 0, (numlines5 + 31) / 8); +#endif +} +#endif +#if DOOM_SMALL +boolean sector_validcount_update_check_impl(should_be_const sector_t *s) { + uint index = s - sectors; + assert(index < numsectors); + uint bit = 1u << (index & 31); + index >>= 5; + if (line_sector_check_bitmap[index] & bit) { + return true; + } + line_sector_check_bitmap[index] |= bit; + return false; +} + +void sector_check_reset(void) { + memset(line_sector_check_bitmap, 0, (numsectors + 31) / 8); +} +#endif + +#if USE_WHD +#define MAX_SWITCHED_SIDES 32 +static uint16_t switched_sides[MAX_SWITCHED_SIDES]; +static uint8_t switched_sides_where[MAX_SWITCHED_SIDES]; + +int check_switch_texture(const side_t *side, int where, int t) { + assert(t <= LAST_SWITCH_TEXTURE); +#if WHD_SUPER_TINY + uint16_t off = side - sides_z; +#else + uint16_t off = side - sides; +#endif + for(int i=0;i>16)==1)?(x)&0xffffu:0xffffffff) +const uint16_t _distscale[SCREENWIDTH] = { + DC(0x00016a75), DC(0x00016912), DC(0x000167fa), DC(0x000166e4), DC(0x000165d0), DC(0x000164bf), DC(0x000163b0), DC(0x00016262), DC(0x00016158), DC(0x00016052), DC(0x00015f0b), DC(0x00015e0b), DC(0x00015d0b), DC(0x00015bce), DC(0x00015ad6), DC(0x0001599f), + DC(0x000158ab), DC(0x0001577c), DC(0x0001568b), DC(0x00015561), DC(0x00015475), DC(0x00015353), DC(0x00015232), DC(0x0001514e), DC(0x00015034), DC(0x00014f1b), DC(0x00014e08), DC(0x00014d2d), DC(0x00014c1d), DC(0x00014b13), DC(0x00014a0a), DC(0x00014902), + DC(0x00014800), DC(0x000146ff), DC(0x00014601), DC(0x00014506), DC(0x0001440d), DC(0x00014319), DC(0x00014226), DC(0x00014136), DC(0x00014018), DC(0x00013f2d), DC(0x00013e46), DC(0x00013d60), DC(0x00013c50), DC(0x00013b70), DC(0x00013a91), DC(0x0001398b), + DC(0x000138b2), DC(0x000137b1), DC(0x000136de), DC(0x000135e4), DC(0x00013516), DC(0x00013420), DC(0x0001332f), DC(0x00013267), DC(0x0001317c), DC(0x00013093), DC(0x00012fae), DC(0x00012ef1), DC(0x00012e10), DC(0x00012d33), DC(0x00012c58), DC(0x00012b80), + DC(0x00012aab), DC(0x000129d9), DC(0x0001290a), DC(0x0001283e), DC(0x00012773), DC(0x000126ac), DC(0x000125c7), DC(0x00012506), DC(0x00012446), DC(0x0001238b), DC(0x000122b2), DC(0x000121fb), DC(0x00012146), DC(0x00012077), DC(0x00011fc7), DC(0x00011efd), + DC(0x00011e54), DC(0x00011d90), DC(0x00011ceb), DC(0x00011c2d), DC(0x00011b8d), DC(0x00011ad5), DC(0x00011a20), DC(0x0001196e), DC(0x000118d7), DC(0x0001182a), DC(0x00011780), DC(0x000116da), DC(0x00011635), DC(0x00011594), DC(0x000114f6), DC(0x0001145a), + DC(0x000113c1), DC(0x0001132a), DC(0x00011297), DC(0x00011206), DC(0x00011177), DC(0x000110eb), DC(0x00011062), DC(0x00010fdb), DC(0x00010f45), DC(0x00010ec3), DC(0x00010e44), DC(0x00010db6), DC(0x00010d3c), DC(0x00010cc5), DC(0x00010c40), DC(0x00010bcd), + DC(0x00010b4e), DC(0x00010ae0), DC(0x00010a66), DC(0x000109fe), DC(0x00010989), DC(0x00010926), DC(0x000108b7), DC(0x0001084b), DC(0x000107f0), DC(0x00010789), DC(0x00010725), DC(0x000106d0), DC(0x00010671), DC(0x00010616), DC(0x000105bd), DC(0x00010567), + DC(0x0001051d), DC(0x000104cc), DC(0x0001047e), DC(0x00010433), DC(0x000103ea), DC(0x000103a4), DC(0x00010360), DC(0x0001031e), DC(0x000102e0), DC(0x000102a4), DC(0x0001026b), DC(0x00010234), DC(0x00010200), DC(0x000101cf), DC(0x000101a0), DC(0x00010174), + DC(0x0001014a), DC(0x00010123), DC(0x000100fd), DC(0x000100db), DC(0x000100bb), DC(0x0001009e), DC(0x00010080), DC(0x00010069), DC(0x00010053), DC(0x00010040), DC(0x00010030), DC(0x00010022), DC(0x00010016), DC(0x0001000c), DC(0x00010006), DC(0x00010002), + DC(0x00010001), DC(0x00010002), DC(0x00010005), DC(0x0001000b), DC(0x00010015), DC(0x00010020), DC(0x0001002e), DC(0x0001003e), DC(0x00010051), DC(0x00010066), DC(0x0001007d), DC(0x0001009b), DC(0x000100b8), DC(0x000100d7), DC(0x000100f9), DC(0x0001011e), + DC(0x00010145), DC(0x0001016f), DC(0x0001019a), DC(0x000101c9), DC(0x000101fa), DC(0x0001022e), DC(0x00010264), DC(0x0001029d), DC(0x000102d9), DC(0x00010317), DC(0x00010358), DC(0x0001039a), DC(0x000103e0), DC(0x0001042a), DC(0x00010475), DC(0x000104c3), + DC(0x00010514), DC(0x0001055c), DC(0x000105b2), DC(0x0001060a), DC(0x00010665), DC(0x000106c4), DC(0x00010719), DC(0x0001077c), DC(0x000107e2), DC(0x0001083e), DC(0x000108aa), DC(0x00010918), DC(0x0001097b), DC(0x000109f0), DC(0x00010a57), DC(0x00010ad1), + DC(0x00010b3e), DC(0x00010bbd), DC(0x00010c2f), DC(0x00010cb4), DC(0x00010d2c), DC(0x00010da4), DC(0x00010e33), DC(0x00010eb1), DC(0x00010f31), DC(0x00010fc8), DC(0x0001104f), DC(0x000110d8), DC(0x00011164), DC(0x000111f1), DC(0x00011282), DC(0x00011315), + DC(0x000113ac), DC(0x00011444), DC(0x000114df), DC(0x0001157e), DC(0x0001161f), DC(0x000116c2), DC(0x00011769), DC(0x00011812), DC(0x000118bf), DC(0x00011954), DC(0x00011a06), DC(0x00011aba), DC(0x00011b72), DC(0x00011c13), DC(0x00011cd0), DC(0x00011d75), + DC(0x00011e37), DC(0x00011ee2), DC(0x00011faa), DC(0x0001205a), DC(0x00012128), DC(0x000121dc), DC(0x00012293), DC(0x0001236c), DC(0x00012427), DC(0x000124e5), DC(0x000125a6), DC(0x0001268c), DC(0x00012752), DC(0x0001281b), DC(0x000128e7), DC(0x000129b7), + DC(0x00012a88), DC(0x00012b5c), DC(0x00012c34), DC(0x00012d0e), DC(0x00012dea), DC(0x00012ecb), DC(0x00012f87), DC(0x0001306d), DC(0x00013155), DC(0x00013241), DC(0x00013307), DC(0x000133f8), DC(0x000134eb), DC(0x000135bb), DC(0x000136b4), DC(0x00013788), + DC(0x00013887), DC(0x00013960), DC(0x00013a65), DC(0x00013b44), DC(0x00013c23), DC(0x00013d32), DC(0x00013e17), DC(0x00013efe), DC(0x00013fe9), DC(0x00014105), DC(0x000141f5), DC(0x000142e7), DC(0x000143dd), DC(0x000144d4), DC(0x000145cf), DC(0x000146cc), + DC(0x000147cb), DC(0x000148cf), DC(0x000149d4), DC(0x00014add), DC(0x00014be8), DC(0x00014cf7), DC(0x00014dd0), DC(0x00014ee5), DC(0x00014ffb), DC(0x00015116), DC(0x000151fa), DC(0x00015319), DC(0x0001543b), DC(0x00015527), DC(0x0001564e), DC(0x0001573f), + DC(0x0001586e), DC(0x00015963), DC(0x00015a97), DC(0x00015b90), DC(0x00015ccc), DC(0x00015dcb), DC(0x00015ecc), DC(0x00016010), DC(0x00016115), DC(0x0001621f), DC(0x0001636d), DC(0x0001647b), DC(0x0001658c), DC(0x0001669e), DC(0x000167b3), DC(0x000168cc), +}; +#endif +fixed_t basexscale; +fixed_t baseyscale; +#if !NO_VISPLANES && !NO_VISPLANE_CACHES +fixed_t cachedheight[SCREENHEIGHT]; +fixed_t cacheddistance[SCREENHEIGHT]; +fixed_t cachedxstep[SCREENHEIGHT]; +fixed_t cachedystep[SCREENHEIGHT]; +#endif // // R_InitPlanes // Only at game startup. // -void R_InitPlanes (void) -{ - // Doh! +void R_InitPlanes(void) { + // Doh! } - +#if !NO_VISPLANES // // R_MapPlane // @@ -112,338 +159,399 @@ void R_InitPlanes (void) // void R_MapPlane -( int y, - int x1, - int x2 ) -{ - angle_t angle; - fixed_t distance; - fixed_t length; - unsigned index; - + (int y, + int x1, + int x2) { + angle_t angle; + fixed_t distance; + fixed_t length; + unsigned index; + #ifdef RANGECHECK if (x2 < x1 - || x1 < 0 - || x2 >= viewwidth - || y > viewheight) - { - I_Error ("R_MapPlane: %i, %i at %i",x1,x2,y); + || x1 < 0 + || x2 >= viewwidth + || y > viewheight) { + I_Error("R_MapPlane: %i, %i at %i", x1, x2, y); } #endif - if (planeheight != cachedheight[y]) - { - cachedheight[y] = planeheight; - distance = cacheddistance[y] = FixedMul (planeheight, yslope[y]); - ds_xstep = cachedxstep[y] = FixedMul (distance,basexscale); - ds_ystep = cachedystep[y] = FixedMul (distance,baseyscale); +#if !NO_VISPLANE_CACHES + if (planeheight != cachedheight[y]) { + cachedheight[y] = planeheight; + distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]); + ds_xstep = cachedxstep[y] = FixedMul(distance, basexscale); + ds_ystep = cachedystep[y] = FixedMul(distance, baseyscale); + } else { + distance = cacheddistance[y]; + ds_xstep = cachedxstep[y]; + ds_ystep = cachedystep[y]; } - else - { - distance = cacheddistance[y]; - ds_xstep = cachedxstep[y]; - ds_ystep = cachedystep[y]; - } - - length = FixedMul (distance,distscale[x1]); - angle = (viewangle + xtoviewangle[x1])>>ANGLETOFINESHIFT; - ds_xfrac = viewx + FixedMul(finecosine[angle], length); - ds_yfrac = -viewy - FixedMul(finesine[angle], length); +#else + distance = FixedMul(planeheight, yslope[y]); + ds_xstep = FixedMul(distance, basexscale); + ds_ystep = FixedMul(distance, baseyscale); +#endif - if (fixedcolormap) - ds_colormap = fixedcolormap; - else - { - index = distance >> LIGHTZSHIFT; - - if (index >= MAXLIGHTZ ) - index = MAXLIGHTZ-1; + length = FixedMul(distance, distscale(x1)); + angle = (viewangle + x_to_viewangle(x1)) >> ANGLETOFINESHIFT; + ds_xfrac = viewx + FixedMul(finecosine(angle), length); + ds_yfrac = -viewy - FixedMul(finesine(angle), length); - ds_colormap = planezlight[index]; + if (fixedcolormap) { +#if !USE_LIGHTMAP_INDEXES + ds_colormap = fixedcolormap; +#else +#if !NO_USE_DS_COLORMAP + ds_colormap = colormaps + fixedcolormap * 256; +#else + ds_colormap_index = fixedcolormap; +#endif +#endif + } else { + index = distance >> LIGHTZSHIFT; + + if (index >= MAXLIGHTZ) + index = MAXLIGHTZ - 1; + +#if !USE_LIGHTMAP_INDEXES + ds_colormap = planezlight[index]; +#else +#if !NO_USE_DS_COLORMAP + ds_colormap = colormaps + planezlight[index] * 256; +#else + ds_colormap_index = planezlight[index]; +#endif +#endif } - + ds_y = y; ds_x1 = x1; ds_x2 = x2; // high or low detail - spanfunc (); -} +#if !PD_COLUMNS + if (!no_draw_spans) + spanfunc(); +#endif +} +#endif // // R_ClearPlanes // At begining of frame. // -void R_ClearPlanes (void) -{ - int i; - angle_t angle; - +void R_ClearPlanes(void) { + int i; + angle_t angle; + // opening / clipping determination - for (i=0 ; i>ANGLETOFINESHIFT; - + angle = (viewangle - ANG90) >> ANGLETOFINESHIFT; + // scale will be unit scale at SCREENWIDTH/2 distance - basexscale = FixedDiv (finecosine[angle],centerxfrac); - baseyscale = -FixedDiv (finesine[angle],centerxfrac); + basexscale = FixedDiv(finecosine(angle), centerxfrac); + baseyscale = -FixedDiv(finesine(angle), centerxfrac); } - - +#if !NO_VISPLANES // // R_FindPlane // -visplane_t* +visplane_t * R_FindPlane -( fixed_t height, - int picnum, - int lightlevel ) -{ - visplane_t* check; - - if (picnum == skyflatnum) - { - height = 0; // all skys map together - lightlevel = 0; + (fixed_t height, + int picnum, + int lightlevel) { + visplane_t *check; + + if (picnum == skyflatnum) { + height = 0; // all skys map together + lightlevel = 0; + } else { +#if PICODOOM_RENDER_BABY + lightlevel = (lightlevel >> LIGHTSEGSHIFT) + extralight; + if (lightlevel >= LIGHTLEVELS) + lightlevel = LIGHTLEVELS-1; + if (lightlevel < 0) + lightlevel = 0; +#endif } - - for (check=visplanes; checkheight - && picnum == check->picnum - && lightlevel == check->lightlevel) - { - break; - } + + // todo graham speed this up +#if PICODOOM_RENDER_BABY + visplane_t *last_unused = NULL; +#endif + for (check = visplanes; check < lastvisplane; check++) { +#if PICODOOM_RENDER_BABY + if (!check->used_by) { + last_unused = check; + } +#endif + if (height == check->height + && picnum == check->picnum + && lightlevel == check->lightlevel) { + break; + } } - - - if (check < lastvisplane) - return check; - + + + if (check < lastvisplane) { +#if PICODOOM_RENDER_BABY + check->used_by |= 1u << render_frame_index; +#endif + return check; + } +#if !PICODOOM_RENDER_BABY if (lastvisplane - visplanes == MAXVISPLANES) - I_Error ("R_FindPlane: no more visplanes"); - + I_Error("R_FindPlane: no more visplanes"); + lastvisplane++; +#else + if (!last_unused) { + if (lastvisplane - visplanes == MAXVISPLANES) + I_Error("R_FindPlane: no more visplanes"); + + lastvisplane++; + } else { + check = last_unused; + } + check->used_by |= 1u << render_frame_index; +#endif check->height = height; check->picnum = picnum; check->lightlevel = lightlevel; +#if !NO_VISPLANE_GUTS check->minx = SCREENWIDTH; check->maxx = -1; - - memset (check->top,0xff,sizeof(check->top)); - + + memset(check->top, 0xff, sizeof(check->top)); + +#endif return check; } - +#if !NO_VISPLANE_GUTS // // R_CheckPlane // -visplane_t* +visplane_t * R_CheckPlane -( visplane_t* pl, - int start, - int stop ) -{ - int intrl; - int intrh; - int unionl; - int unionh; - int x; - - if (start < pl->minx) - { - intrl = pl->minx; - unionl = start; - } - else - { - unionl = pl->minx; - intrl = start; - } - - if (stop > pl->maxx) - { - intrh = pl->maxx; - unionh = stop; - } - else - { - unionh = pl->maxx; - intrh = stop; + (visplane_t *pl, + int start, + int stop) { + int intrl; + int intrh; + int unionl; + int unionh; + int x; + + if (start < pl->minx) { + intrl = pl->minx; + unionl = start; + } else { + unionl = pl->minx; + intrl = start; } - for (x=intrl ; x<= intrh ; x++) - if (pl->top[x] != 0xff) - break; - - if (x > intrh) - { - pl->minx = unionl; - pl->maxx = unionh; - - // use the same one - return pl; + if (stop > pl->maxx) { + intrh = pl->maxx; + unionh = stop; + } else { + unionh = pl->maxx; + intrh = stop; } - + + for (x = intrl; x <= intrh; x++) + if (pl->top[x] != 0xff) + break; + + if (x > intrh) { + pl->minx = unionl; + pl->maxx = unionh; + + // use the same one + return pl; + } + // make a new visplane lastvisplane->height = pl->height; lastvisplane->picnum = pl->picnum; lastvisplane->lightlevel = pl->lightlevel; - + if (lastvisplane - visplanes == MAXVISPLANES) - I_Error ("R_CheckPlane: no more visplanes"); + I_Error("R_CheckPlane: no more visplanes"); pl = lastvisplane++; pl->minx = start; pl->maxx = stop; - memset (pl->top,0xff,sizeof(pl->top)); - + memset(pl->top, 0xff, sizeof(pl->top)); + return pl; } - +#endif // // R_MakeSpans // void R_MakeSpans -( int x, - int t1, - int b1, - int t2, - int b2 ) -{ - while (t1 < t2 && t1<=b1) - { - R_MapPlane (t1,spanstart[t1],x-1); - t1++; + (int x, + int t1, + int b1, + int t2, + int b2) { + while (t1 < t2 && t1 <= b1) { + R_MapPlane(t1, spanstart[t1], x - 1); + t1++; } - while (b1 > b2 && b1>=t1) - { - R_MapPlane (b1,spanstart[b1],x-1); - b1--; + while (b1 > b2 && b1 >= t1) { + R_MapPlane(b1, spanstart[b1], x - 1); + b1--; } - - while (t2 < t1 && t2<=b2) - { - spanstart[t2] = x; - t2++; + + while (t2 < t1 && t2 <= b2) { + spanstart[t2] = x; + t2++; } - while (b2 > b1 && b2>=t2) - { - spanstart[b2] = x; - b2--; + while (b2 > b1 && b2 >= t2) { + spanstart[b2] = x; + b2--; } } - +#if !NO_VISPLANE_GUTS // // R_DrawPlanes // At the end of each frame. // -void R_DrawPlanes (void) -{ - visplane_t* pl; - int light; - int x; - int stop; - int angle; - int lumpnum; - +void R_DrawPlanes(void) { + visplane_t *pl; + int light; + int x; + int stop; + int angle; + int lumpnum; + #ifdef RANGECHECK +#if !NO_DRAWSEGS if (ds_p - drawsegs > MAXDRAWSEGS) - I_Error ("R_DrawPlanes: drawsegs overflow (%" PRIiPTR ")", - ds_p - drawsegs); - - if (lastvisplane - visplanes > MAXVISPLANES) - I_Error ("R_DrawPlanes: visplane overflow (%" PRIiPTR ")", - lastvisplane - visplanes); - + I_Error("R_DrawPlanes: drawsegs overflow (%" PRIiPTR ")", + ds_p - drawsegs); if (lastopening - openings > MAXOPENINGS) - I_Error ("R_DrawPlanes: opening overflow (%" PRIiPTR ")", - lastopening - openings); + I_Error("R_DrawPlanes: opening overflow (%" PRIiPTR ")", + lastopening - openings); #endif - for (pl = visplanes ; pl < lastvisplane ; pl++) - { - if (pl->minx > pl->maxx) - continue; + if (lastvisplane - visplanes > MAXVISPLANES) + I_Error("R_DrawPlanes: visplane overflow (%" PRIiPTR ")", + lastvisplane - visplanes); - - // sky flat - if (pl->picnum == skyflatnum) - { - dc_iscale = pspriteiscale>>detailshift; - - // Sky is allways drawn full bright, - // i.e. colormaps[0] is used. - // Because of this hack, sky is not affected - // by INVUL inverse mapping. - dc_colormap = colormaps; - dc_texturemid = skytexturemid; - for (x=pl->minx ; x <= pl->maxx ; x++) - { - dc_yl = pl->top[x]; - dc_yh = pl->bottom[x]; +#endif - if (dc_yl <= dc_yh) - { - angle = (viewangle + xtoviewangle[x])>>ANGLETOSKYSHIFT; - dc_x = x; - dc_source = R_GetColumn(skytexture, angle); - colfunc (); - } - } - continue; - } - - // regular flat - lumpnum = firstflat + flattranslation[pl->picnum]; - ds_source = W_CacheLumpNum(lumpnum, PU_STATIC); - - planeheight = abs(pl->height-viewz); - light = (pl->lightlevel >> LIGHTSEGSHIFT)+extralight; + for (pl = visplanes; pl < lastvisplane; pl++) { + if (pl->minx > pl->maxx) + continue; - if (light >= LIGHTLEVELS) - light = LIGHTLEVELS-1; - if (light < 0) - light = 0; + // sky flat + if (pl->picnum == skyflatnum) { + dc_iscale = pspriteiscale >> detailshift; - planezlight = zlight[light]; + // Sky is allways drawn full bright, + // i.e. colormaps[0] is used. + // Because of this hack, sky is not affected + // by INVUL inverse mapping. +#if !NO_USE_DC_COLORMAP + dc_colormap = colormaps; +#else + dc_colormap_index = 0; +#endif - pl->top[pl->maxx+1] = 0xff; - pl->top[pl->minx-1] = 0xff; - - stop = pl->maxx + 1; + dc_texturemid = skytexturemid; + for (x = pl->minx; x <= pl->maxx; x++) { + dc_yl = pl->top[x]; + dc_yh = pl->bottom[x]; + + if (dc_yl <= dc_yh) { + angle = (viewangle + xtoviewangle[x]) >> ANGLETOSKYSHIFT; + dc_x = x; + dc_source = R_GetColumn(skytexture, angle); +#if PD_COLUMNS + pd_add_column(PDCOL_SKY); +#endif +#if !NO_DRAW_SKY + colfunc(); +#endif + } + } + continue; + } + +#if !NO_DRAW_VISPLANES + // regular flat + lumpnum = firstflat + flattranslation[pl->picnum]; + ds_source = W_CacheLumpNum(lumpnum, PU_STATIC); + + planeheight = abs(pl->height-viewz); + light = (pl->lightlevel >> LIGHTSEGSHIFT)+extralight; + + if (light >= LIGHTLEVELS) + light = LIGHTLEVELS-1; + + if (light < 0) + light = 0; + + planezlight = zlight[light]; + + pl->top[pl->maxx+1] = 0xff; + pl->top[pl->minx-1] = 0xff; + + stop = pl->maxx + 1; + + for (x=pl->minx ; x<= stop ; x++) + { + R_MakeSpans(x,pl->top[x-1], + pl->bottom[x-1], + pl->top[x], + pl->bottom[x]); + } + + W_ReleaseLumpNum(lumpnum); +#endif - for (x=pl->minx ; x<= stop ; x++) - { - R_MakeSpans(x,pl->top[x-1], - pl->bottom[x-1], - pl->top[x], - pl->bottom[x]); - } - - W_ReleaseLumpNum(lumpnum); } } +#endif +#endif diff --git a/src/doom/r_plane.h b/src/doom/r_plane.h index 57b50e5b..f9cfb754 100644 --- a/src/doom/r_plane.h +++ b/src/doom/r_plane.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -24,9 +25,11 @@ #include "r_data.h" - // Visplane related. + +#if !NO_DRAWSEGS extern short* lastopening; +#endif typedef void (*planefunction_t) (int top, int bottom); @@ -34,11 +37,23 @@ typedef void (*planefunction_t) (int top, int bottom); extern planefunction_t floorfunc; extern planefunction_t ceilingfunc_t; -extern short floorclip[SCREENWIDTH]; -extern short ceilingclip[SCREENWIDTH]; +extern floor_ceiling_clip_t floorclip[SCREENWIDTH]; +extern floor_ceiling_clip_t ceilingclip[SCREENWIDTH]; +#if !DOOM_TINY extern fixed_t yslope[SCREENHEIGHT]; -extern fixed_t distscale[SCREENWIDTH]; +#else +extern fixed_t yslope[MAIN_VIEWHEIGHT]; +#endif +#if !FIXED_SCREENWIDTH +extern fixed_t _distscale[SCREENWIDTH]; +#define distscale(x) _distscale[x] +#else +extern const uint16_t _distscale[SCREENWIDTH]; +#define distscale(x) (_distscale[x] | (fixed_t)0x10000) +#endif +extern fixed_t basexscale; +extern fixed_t baseyscale; void R_InitPlanes (void); void R_ClearPlanes (void); @@ -57,6 +72,7 @@ R_MakeSpans int t2, int b2 ); +#if !NO_VISPLANES void R_DrawPlanes (void); visplane_t* @@ -70,7 +86,7 @@ R_CheckPlane ( visplane_t* pl, int start, int stop ); - +#endif #endif diff --git a/src/doom/r_segs.c b/src/doom/r_segs.c index 06881a52..d6f21aa4 100644 --- a/src/doom/r_segs.c +++ b/src/doom/r_segs.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -20,9 +21,9 @@ - #include #include +#include #include "i_system.h" @@ -31,78 +32,99 @@ #include "r_local.h" #include "r_sky.h" +#if PICO_DOOM +#include "picodoom.h" +#endif +#if USE_WHD +#include "p_spec.h" +#endif +#if PD_COLUMNS && USE_WHD +void pd_add_column2(pd_column_type type); +#else +#define pd_add_column2(x) pd_add_column(x) +#endif // OPTIMIZE: closed two sided lines as single sided // True if any of the segs textures might be visible. -boolean segtextured; +boolean segtextured; // False if the back side is the same plane. -boolean markfloor; -boolean markceiling; +boolean markfloor; +boolean markceiling; -boolean maskedtexture; -int toptexture; -int bottomtexture; -int midtexture; +#if !USE_WHD +isb_int16_t toptexture; +isb_int16_t bottomtexture; +isb_int16_t midtexture; +isb_int16_t maskedtexture; +isb_int16_t maskedtexture_tex; +#else +framedrawable_t *toptexture; +framedrawable_t *bottomtexture; +framedrawable_t *midtexture; +framedrawable_t *maskedtexture; +isb_int16_t maskedtexture_tex; +#endif - -angle_t rw_normalangle; +angle_t rw_normalangle; // angle to line origin -int rw_angle1; +int rw_angle1; // // regular wall // -int rw_x; -int rw_stopx; -angle_t rw_centerangle; -fixed_t rw_offset; -fixed_t rw_distance; -fixed_t rw_scale; -fixed_t rw_scalestep; -fixed_t rw_midtexturemid; -fixed_t rw_toptexturemid; -fixed_t rw_bottomtexturemid; +int rw_x; +int rw_stopx; +angle_t rw_centerangle; +fixed_t rw_offset; +fixed_t rw_distance; +fixed_t rw_scale; +fixed_t rw_scalestep; +fixed_t rw_midtexturemid; +fixed_t rw_toptexturemid; +fixed_t rw_bottomtexturemid; +fixed_t rw_maskedtexturemid; -int worldtop; -int worldbottom; -int worldhigh; -int worldlow; +int worldtop; +int worldbottom; +int worldhigh; +int worldlow; -fixed_t pixhigh; -fixed_t pixlow; -fixed_t pixhighstep; -fixed_t pixlowstep; +fixed_t pixhigh; +fixed_t pixlow; +fixed_t pixhighstep; +fixed_t pixlowstep; -fixed_t topfrac; -fixed_t topstep; +fixed_t topfrac; +fixed_t topstep; -fixed_t bottomfrac; -fixed_t bottomstep; - - -lighttable_t** walllights; - -short* maskedtexturecol; +fixed_t bottomfrac; +fixed_t bottomstep; +#if !USE_LIGHTMAP_INDEXES +const lighttable_t** walllights; +#else +int8_t *walllights; +#endif +#if !NO_DRAWSEGS +short *maskedtexturecol; // // R_RenderMaskedSegRange // void R_RenderMaskedSegRange -( drawseg_t* ds, - int x1, - int x2 ) -{ - unsigned index; - column_t* col; - int lightnum; - int texnum; - + (drawseg_t *ds, + int x1, + int x2) { + unsigned index; + column_t *col; + int lightnum; + int texnum; + // Calculate light table. // Use different light tables // for horizontal / vertical / diagonal. Diagonal? @@ -111,77 +133,93 @@ R_RenderMaskedSegRange frontsector = curline->frontsector; backsector = curline->backsector; texnum = texturetranslation[curline->sidedef->midtexture]; - - lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT)+extralight; - if (curline->v1->y == curline->v2->y) - lightnum--; - else if (curline->v1->x == curline->v2->x) - lightnum++; + lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT) + extralight; - if (lightnum < 0) - walllights = scalelight[0]; + if (vertex_y(seg_v1(curline)) == vertex_y(seg_v2(curline))) + lightnum--; + else if (vertex_x(seg_v1(curline)) == vertex_x(seg_v2(curline))) + lightnum++; + + if (lightnum < 0) + walllights = scalelight[0]; else if (lightnum >= LIGHTLEVELS) - walllights = scalelight[LIGHTLEVELS-1]; + walllights = scalelight[LIGHTLEVELS - 1]; else - walllights = scalelight[lightnum]; + walllights = scalelight[lightnum]; maskedtexturecol = ds->maskedtexturecol; - rw_scalestep = ds->scalestep; - spryscale = ds->scale1 + (x1 - ds->x1)*rw_scalestep; + rw_scalestep = ds->scalestep; + spryscale = ds->scale1 + (x1 - ds->x1) * rw_scalestep; mfloorclip = ds->sprbottomclip; mceilingclip = ds->sprtopclip; - + // find positioning - if (curline->linedef->flags & ML_DONTPEGBOTTOM) - { - dc_texturemid = frontsector->floorheight > backsector->floorheight - ? frontsector->floorheight : backsector->floorheight; - dc_texturemid = dc_texturemid + textureheight[texnum] - viewz; - } - else - { - dc_texturemid =frontsector->ceilingheightceilingheight - ? frontsector->ceilingheight : backsector->ceilingheight; - dc_texturemid = dc_texturemid - viewz; + if (curline->linedef->flags & ML_DONTPEGBOTTOM) { + dc_texturemid = frontsector->rawfloorheight > backsector->rawfloorheight + ? sector_floorheight(frontsector) : sector_floorheight(backsector); + dc_texturemid = dc_texturemid + textureheight[texnum] - viewz; + } else { + dc_texturemid = frontsector->rawceilingheight < backsector->rawceilingheight + ? sector_ceilingheight(frontsector) : sector_ceilingheight(backsector); + dc_texturemid = dc_texturemid - viewz; } dc_texturemid += curline->sidedef->rowoffset; - + +#if !USE_LIGHTMAP_INDEXES if (fixedcolormap) - dc_colormap = fixedcolormap; - - // draw the columns - for (dc_x = x1 ; dc_x <= x2 ; dc_x++) - { - // calculate lighting - if (maskedtexturecol[dc_x] != SHRT_MAX) - { - if (!fixedcolormap) - { - index = spryscale>>LIGHTSCALESHIFT; - - if (index >= MAXLIGHTSCALE ) - index = MAXLIGHTSCALE-1; - - dc_colormap = walllights[index]; - } - - sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); - dc_iscale = 0xffffffffu / (unsigned)spryscale; - - // draw the texture - col = (column_t *)( - (byte *)R_GetColumn(texnum,maskedtexturecol[dc_x]) -3); - - R_DrawMaskedColumn (col); - maskedtexturecol[dc_x] = SHRT_MAX; - } - spryscale += rw_scalestep; + dc_colormap = fixedcolormap; +#else + if (fixedcolormap) { +#if !NO_USE_DC_COLORMAP + dc_colormap = colormaps + fixedcolormap * 256; +#else + dc_colormap_index = fixedcolormap; +#endif } - -} +#endif + // draw the columns + for (dc_x = x1; dc_x <= x2; dc_x++) { + // calculate lighting + if (maskedtexturecol[dc_x] != SHRT_MAX) { + if (!fixedcolormap) { + index = spryscale >> LIGHTSCALESHIFT; + + if (index >= MAXLIGHTSCALE) + index = MAXLIGHTSCALE - 1; + +#if !USE_LIGHTMAP_INDEXES + dc_colormap = walllights[index]; +#else +#if !NO_USE_DS_COLORMAP + dc_colormap = colormaps + walllights[index] * 256; +#else + dc_colormap_index = walllights[index]; +#endif +#endif + } + + sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); + dc_iscale = 0xffffffffu / (unsigned) spryscale; + +#if PD_SCALE_SORT + pd_scale = spryscale; +#endif + // draw the texture + col = (column_t *) ( + (byte *) R_GetColumn(texnum, maskedtexturecol[dc_x]) - 3); + +// printf("Draw masked column %d %08x %08x\n", dc_x, sprtopscreen, spryscale); + R_DrawMaskedColumn(col); + maskedtexturecol[dc_x] = SHRT_MAX; + } + spryscale += rw_scalestep; + } + +} +#endif @@ -193,176 +231,294 @@ R_RenderMaskedSegRange // textures. // CALLED: CORE LOOPING ROUTINE. // -#define HEIGHTBITS 12 -#define HEIGHTUNIT (1<>HEIGHTBITS; +#define EPSILON 1 +void R_RenderSegLoop(void) { + angle_t angle; + unsigned index; + int yl; + int yh; + int mid; + fixed_t texturecolumn; + int top; + int bottom; - // no space above wall? - if (yl < ceilingclip[rw_x]+1) - yl = ceilingclip[rw_x]+1; - - if (markceiling) - { - top = ceilingclip[rw_x]+1; - bottom = yl-1; + for (; rw_x < rw_stopx; rw_x++) { +#if PD_SCALE_SORT + pd_scale = rw_scale; +#endif - if (bottom >= floorclip[rw_x]) - bottom = floorclip[rw_x]-1; + // mark floor / ceiling areas + yl = (topfrac + HEIGHTUNIT - 1) >> HEIGHTBITS; - if (top <= bottom) - { - ceilingplane->top[rw_x] = top; - ceilingplane->bottom[rw_x] = bottom; - } - } - - yh = bottomfrac>>HEIGHTBITS; + // no space above wall? + if (yl < ceilingclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET + 1) + yl = ceilingclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET + 1; - if (yh >= floorclip[rw_x]) - yh = floorclip[rw_x]-1; + if (markceiling) { + top = ceilingclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET + 1; + bottom = yl - 1; - if (markfloor) - { - top = yh+1; - bottom = floorclip[rw_x]-1; - if (top <= ceilingclip[rw_x]) - top = ceilingclip[rw_x]+1; - if (top <= bottom) - { - floorplane->top[rw_x] = top; - floorplane->bottom[rw_x] = bottom; - } - } - - // texturecolumn and lighting are independent of wall tiers - if (segtextured) - { - // calculate texture offset - angle = (rw_centerangle + xtoviewangle[rw_x])>>ANGLETOFINESHIFT; - texturecolumn = rw_offset-FixedMul(finetangent[angle],rw_distance); - texturecolumn >>= FRACBITS; - // calculate lighting - index = rw_scale>>LIGHTSCALESHIFT; + if (bottom >= floorclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET) + bottom = floorclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET - 1; - if (index >= MAXLIGHTSCALE ) - index = MAXLIGHTSCALE-1; + if (top <= bottom) { +#if !NO_VISPLANE_GUTS + ceilingplane->top[rw_x] = top; + ceilingplane->bottom[rw_x] = bottom; +#endif +#if PICO_DOOM + if (frontsector->ceilingpic != skyflatnum) { + pd_add_plane_column(rw_x, top, bottom, rw_scale + EPSILON, false, ceilingplane - visplanes); + } else { + dc_iscale = pspriteiscale >> detailshift; - dc_colormap = walllights[index]; - dc_x = rw_x; - dc_iscale = 0xffffffffu / (unsigned)rw_scale; - } - else - { + // Sky is allways drawn full bright, + // i.e. colormaps[0] is used. + // Because of this hack, sky is not affected + // by INVUL inverse mapping. +#if !NO_USE_DC_COLORMAP + dc_colormap = colormaps; +#else + dc_colormap_index = 0; +#endif + + dc_texturemid = skytexturemid; + + dc_x = rw_x; + dc_yl = top; + dc_yh = bottom; + + if (dc_yl <= dc_yh) { + angle = (viewangle + x_to_viewangle(rw_x)) >> ANGLETOSKYSHIFT; +#if USE_WHD + dc_source = R_GetColumn(skytexture_fd, angle); +#else + dc_source = R_GetColumn(skytexture, angle); +#endif +#if PD_COLUMNS + pd_add_column2(PDCOL_SKY); // to just use sky texture +#endif +#if !NO_DRAW_SKY + colfunc(); +#endif + } + } +#endif + } + } + + yh = bottomfrac >> HEIGHTBITS; + + if (yh >= floorclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET) + yh = floorclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET - 1; + + if (markfloor) { + top = yh + 1; + bottom = floorclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET - 1; + if (top <= ceilingclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET) + top = ceilingclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET + 1; + if (top <= bottom) { +#if !NO_VISPLANE_GUTS + floorplane->top[rw_x] = top; + floorplane->bottom[rw_x] = bottom; +#endif +#if PICO_DOOM + pd_add_plane_column(rw_x, top, bottom, rw_scale + EPSILON, true, floorplane - visplanes); +#endif + } + } + + // texturecolumn and lighting are independent of wall tiers + if (segtextured) { + // calculate texture offset + angle = (rw_centerangle + x_to_viewangle(rw_x)) >> ANGLETOFINESHIFT; + texturecolumn = rw_offset - FixedMul(finetangent(angle), rw_distance); + texturecolumn >>= FRACBITS; + // calculate lighting + index = rw_scale >> LIGHTSCALESHIFT; + + if (index >= MAXLIGHTSCALE) + index = MAXLIGHTSCALE - 1; + +#if !USE_LIGHTMAP_INDEXES + dc_colormap = walllights[index]; +#else +#if !NO_USE_DC_COLORMAP + dc_colormap = colormaps + walllights[index] * 256; +#else + dc_colormap_index = walllights[index]; +#endif +#endif + dc_x = rw_x; + dc_iscale = 0xffffffffu / (unsigned) rw_scale; +#if MU_STATS + stats_dc_iscale_min = MIN(dc_iscale, stats_dc_iscale_min); + stats_dc_iscale_max = MAX(dc_iscale, stats_dc_iscale_max); +#endif + } else { // purely to shut up the compiler texturecolumn = 0; } - - // draw the wall tiers - if (midtexture) - { - // single sided line - dc_yl = yl; - dc_yh = yh; - dc_texturemid = rw_midtexturemid; - dc_source = R_GetColumn(midtexture,texturecolumn); - colfunc (); - ceilingclip[rw_x] = viewheight; - floorclip[rw_x] = -1; - } - else - { - // two sided line - if (toptexture) - { - // top wall - mid = pixhigh>>HEIGHTBITS; - pixhigh += pixhighstep; - if (mid >= floorclip[rw_x]) - mid = floorclip[rw_x]-1; + // draw the wall tiers + if (midtexture) { + // single sided line + dc_yl = yl; + dc_yh = yh; + dc_texturemid = rw_midtexturemid; + dc_source = R_GetColumn(midtexture, texturecolumn); +#if PD_COLUMNS +#if PD_SCALE_SORT + pd_add_column2(PDCOL_MID); +#else + if (!maskedtexture) pd_add_column(PDCOL_MID); +#endif +#endif + if (!no_draw_mid) { +// if (!maskedtexture) { +// I_Error("Duh\n"); +// } + // todo graham; added !maskedtexture as these must be drawn again later + if (!maskedtexture) colfunc(); + } + ceilingclip[rw_x] = viewheight + FLOOR_CEILING_CLIP_OFFSET; + floorclip[rw_x] = FLOOR_CEILING_CLIP_OFFSET - 1; + } else { + // two sided line + if (toptexture) { + // top wall + mid = pixhigh >> HEIGHTBITS; + pixhigh += pixhighstep; - if (mid >= yl) - { - dc_yl = yl; - dc_yh = mid; - dc_texturemid = rw_toptexturemid; - dc_source = R_GetColumn(toptexture,texturecolumn); - colfunc (); - ceilingclip[rw_x] = mid; - } - else - ceilingclip[rw_x] = yl-1; - } - else - { - // no top wall - if (markceiling) - ceilingclip[rw_x] = yl-1; - } - - if (bottomtexture) - { - // bottom wall - mid = (pixlow+HEIGHTUNIT-1)>>HEIGHTBITS; - pixlow += pixlowstep; + if (mid >= floorclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET) + mid = floorclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET - 1; - // no space above wall? - if (mid <= ceilingclip[rw_x]) - mid = ceilingclip[rw_x]+1; - - if (mid <= yh) - { - dc_yl = mid; - dc_yh = yh; - dc_texturemid = rw_bottomtexturemid; - dc_source = R_GetColumn(bottomtexture, - texturecolumn); - colfunc (); - floorclip[rw_x] = mid; - } - else - floorclip[rw_x] = yh+1; - } - else - { - // no bottom wall - if (markfloor) - floorclip[rw_x] = yh+1; - } - - if (maskedtexture) - { - // save texturecol - // for backdrawing of masked mid texture - maskedtexturecol[rw_x] = texturecolumn; - } - } - - rw_scale += rw_scalestep; - topfrac += topstep; - bottomfrac += bottomstep; + if (mid >= yl) { + dc_yl = yl; + dc_yh = mid; + dc_texturemid = rw_toptexturemid; + dc_source = R_GetColumn(toptexture, texturecolumn); +#if PD_COLUMNS + pd_add_column2(PDCOL_TOP); +#endif + if (!no_draw_top) + colfunc(); + ceilingclip[rw_x] = mid + FLOOR_CEILING_CLIP_OFFSET; + } else + ceilingclip[rw_x] = yl + FLOOR_CEILING_CLIP_OFFSET - 1; + } else { + // no top wall + if (markceiling) + ceilingclip[rw_x] = yl + FLOOR_CEILING_CLIP_OFFSET - 1; + } + + if (bottomtexture) { + // bottom wall + mid = (pixlow + HEIGHTUNIT - 1) >> HEIGHTBITS; + pixlow += pixlowstep; + + // no space above wall? + if (mid <= ceilingclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET) + mid = ceilingclip[rw_x] - FLOOR_CEILING_CLIP_OFFSET + 1; + + if (mid <= yh) { + dc_yl = mid; + dc_yh = yh; + dc_texturemid = rw_bottomtexturemid; + dc_source = R_GetColumn(bottomtexture, + texturecolumn); +#if PD_COLUMNS + pd_add_column2(PDCOL_BOTTOM); +#endif + if (!no_draw_bottom) + colfunc(); + assert(mid >= 0 && mid <= viewheight); + floorclip[rw_x] = mid + FLOOR_CEILING_CLIP_OFFSET; + } else { + if (yh + 1 >= 0) { // not sure why this isn't the case some times + assert(yh + 1 >= 0 && yh + 1 <= viewheight); + floorclip[rw_x] = yh + FLOOR_CEILING_CLIP_OFFSET + 1; + } + } + } else { + // no bottom wall + if (markfloor) { + assert(yh + 1 >= 0 && yh + 1 <= viewheight); + floorclip[rw_x] = yh + FLOOR_CEILING_CLIP_OFFSET + 1; + } + } + + if (maskedtexture) { + // save texturecol + // for backdrawing of masked mid texture +#if !NO_DRAWSEGS + maskedtexturecol[rw_x] = texturecolumn; +#else + // graham: added drawing here + // draw the texture +#if USE_WHD + maskedcolumn_t col = R_GetMaskedColumn(maskedtexture, texturecolumn & (whd_textures[maskedtexture_tex].width -1)); +#else + maskedcolumn_t col = R_GetMaskedColumn(maskedtexture, texturecolumn); +#endif + dc_texturemid = rw_maskedtexturemid; + +#if !NO_MASKED_FLOOR_CLIP + mfloorclip = floorclip; + mceilingclip = ceilingclip; +#endif + spryscale = rw_scale; + sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); +#if !USE_WHD + R_DrawMaskedColumn(col); +#else + if (col.real_id < 0) { + R_DrawMaskedColumn(col); + } else { + int sprbottomscreen = sprtopscreen + spryscale * texture_height(col.real_id); + dc_yl = (sprtopscreen + FRACUNIT - 1) >> FRACBITS; + dc_yh = (sprbottomscreen - 1) >> FRACBITS; + + if (dc_yh >= mfloorclip[dc_x] - FLOOR_CEILING_CLIP_OFFSET) + dc_yh = mfloorclip[dc_x] - FLOOR_CEILING_CLIP_OFFSET - 1; + if (dc_yl <= mceilingclip[dc_x] - FLOOR_CEILING_CLIP_OFFSET) + dc_yl = mceilingclip[dc_x] - FLOOR_CEILING_CLIP_OFFSET + 1; + dc_source = col; + pd_add_column2(PDCOL_MID); + } +#endif +#endif + } + } + + rw_scale += rw_scalestep; + topfrac += topstep; + bottomfrac += bottomstep; } } - - // // R_StoreWallRange // A wall segment will be drawn @@ -370,374 +526,436 @@ void R_RenderSegLoop (void) // void R_StoreWallRange -( int start, - int stop ) -{ - fixed_t hyp; - fixed_t sineval; - angle_t distangle, offsetangle; - fixed_t vtop; - int lightnum; + (int start, + int stop) { + fixed_t hyp; + fixed_t sineval; + angle_t distangle, offsetangle; + fixed_t vtop; + int lightnum; +#if DOOM_TINY + dc_translation_index = 0; // no palette translation +#endif + +#if !NO_DRAWSEGS // don't overflow and crash if (ds_p == &drawsegs[MAXDRAWSEGS]) - return; - -#ifdef RANGECHECK - if (start >=viewwidth || start > stop) - I_Error ("Bad R_RenderWallRange: %i to %i", start , stop); + return; #endif - - sidedef = curline->sidedef; - linedef = curline->linedef; + +#ifdef RANGECHECK + if (start >= viewwidth || start > stop) + I_Error("Bad R_RenderWallRange: %i to %i", start, stop); +#endif + + sidedef = seg_sidedef(curline); + linedef = seg_linedef(curline); // mark the segment as visible for auto map - linedef->flags |= ML_MAPPED; - + line_set_mapped(linedef); + // calculate rw_distance for scale calculation - rw_normalangle = curline->angle + ANG90; - offsetangle = abs((int)rw_normalangle-(int)rw_angle1); - +#if WHD_SUPER_TINY + // don't bother to store angle in seg + int dx = vertex_x(seg_v2(curline)) - vertex_x(seg_v1(curline)); + int dy = vertex_y(seg_v2(curline)) - vertex_y(seg_v1(curline)); + rw_normalangle = R_PointToAngleDX(dx, dy) + ANG90; +#else + rw_normalangle = seg_angle(curline) + ANG90; +#endif + offsetangle = abs((int) rw_normalangle - (int) rw_angle1); + if (offsetangle > ANG90) - offsetangle = ANG90; + offsetangle = ANG90; distangle = ANG90 - offsetangle; - hyp = R_PointToDist (curline->v1->x, curline->v1->y); - sineval = finesine[distangle>>ANGLETOFINESHIFT]; - rw_distance = FixedMul (hyp, sineval); - - - ds_p->x1 = rw_x = start; + hyp = R_PointToDist(vertex_x(seg_v1(curline)), vertex_y(seg_v1(curline))); + sineval = finesine(distangle >> ANGLETOFINESHIFT); + rw_distance = FixedMul(hyp, sineval); + + + rw_x = start; +#if !NO_DRAWSEGS + ds_p->x1 = rw_x; ds_p->x2 = stop; ds_p->curline = curline; - rw_stopx = stop+1; - - // calculate scale at both ends and step - ds_p->scale1 = rw_scale = - R_ScaleFromGlobalAngle (viewangle + xtoviewangle[start]); - - if (stop > start ) - { - ds_p->scale2 = R_ScaleFromGlobalAngle (viewangle + xtoviewangle[stop]); - ds_p->scalestep = rw_scalestep = - (ds_p->scale2 - rw_scale) / (stop-start); - } - else - { - // UNUSED: try to fix the stretched line bug -#if 0 - if (rw_distance < FRACUNIT/2) - { - fixed_t trx,try; - fixed_t gxt,gyt; - - trx = curline->v1->x - viewx; - try = curline->v1->y - viewy; - - gxt = FixedMul(trx,viewcos); - gyt = -FixedMul(try,viewsin); - ds_p->scale1 = FixedDiv(projection, gxt-gyt)<scale2 = ds_p->scale1; + rw_stopx = stop + 1; + + + // calculate scale at both ends and step + rw_scale = + R_ScaleFromGlobalAngle(viewangle + x_to_viewangle(start)); +#if !NO_DRAWSEGS + ds_p->scale1 = rw_scale; +#endif + + if (stop > start) { + fixed_t scale2 = R_ScaleFromGlobalAngle(viewangle + x_to_viewangle(stop)); + rw_scalestep = (scale2 - rw_scale) / (stop - start); +#if !NO_DRAWSEGS + ds_p->scale2 = scale2; + ds_p->scalestep = rw_scalestep; +#endif + } else { + // UNUSED: try to fix the stretched line bug +#if 0 + if (rw_distance < FRACUNIT/2) + { + fixed_t trx,try; + fixed_t gxt,gyt; + + trx = seg_v1(curline)->x - viewx; + try = seg_v1(curline)->y - viewy; + + gxt = FixedMul(trx,viewcos); + gyt = -FixedMul(try,viewsin); + ds_p->scale1 = FixedDiv(projection, gxt-gyt)<scale2 = ds_p->scale1; +#endif } - + // calculate texture boundaries // and decide if floor / ceiling marks are needed - worldtop = frontsector->ceilingheight - viewz; - worldbottom = frontsector->floorheight - viewz; - + worldtop = sector_ceilingheight(frontsector) - viewz; + worldbottom = sector_floorheight(frontsector) - viewz; + midtexture = toptexture = bottomtexture = maskedtexture = 0; +#if !NO_DRAWSEGS ds_p->maskedtexturecol = NULL; - - if (!backsector) - { - // single sided line - midtexture = texturetranslation[sidedef->midtexture]; - // a single sided line is terminal, so it must mark ends - markfloor = markceiling = true; - if (linedef->flags & ML_DONTPEGBOTTOM) - { - vtop = frontsector->floorheight + - textureheight[sidedef->midtexture]; - // bottom of texture at bottom - rw_midtexturemid = vtop - viewz; - } - else - { - // top of texture at top - rw_midtexturemid = worldtop; - } - rw_midtexturemid += sidedef->rowoffset; +#endif - ds_p->silhouette = SIL_BOTH; - ds_p->sprtopclip = screenheightarray; - ds_p->sprbottomclip = negonearray; - ds_p->bsilheight = INT_MAX; - ds_p->tsilheight = INT_MIN; + if (!backsector) { + // single sided line + midtexture = lookup_texture(texture_translation(side_midtexture(sidedef))); + // a single sided line is terminal, so it must mark ends + markfloor = markceiling = true; + if (line_flags(linedef) & ML_DONTPEGBOTTOM) { + vtop = sector_floorheight(frontsector) + + texture_height(side_midtexture(sidedef)); + // bottom of texture at bottom + rw_midtexturemid = vtop - viewz; + } else { + // top of texture at top + rw_midtexturemid = worldtop; + } + rw_midtexturemid += side_rowoffset(sidedef); + +#if !NO_DRAWSEGS + ds_p->silhouette = SIL_BOTH; + ds_p->sprtopclip = maxfloorceilingcliparray; + ds_p->sprbottomclip = minfloorceilingcliparray; + ds_p->bsilheight = INT_MAX; + ds_p->tsilheight = INT_MIN; +#endif + } else { +#if !NO_DRAWSEGS + // two sided line + ds_p->sprtopclip = ds_p->sprbottomclip = NULL; + ds_p->silhouette = 0; + + if (frontsector->rawfloorheight > backsector->rawfloorheight) { + ds_p->silhouette = SIL_BOTTOM; + ds_p->bsilheight = sector_floorheight(frontsector); + } else if (sector_floorheight(backsector) > viewz) { + ds_p->silhouette = SIL_BOTTOM; + ds_p->bsilheight = INT_MAX; + // ds_p->sprbottomclip = negonearray; + } + + if (frontsector->rawceilingheight < backsector->rawceilingheight) { + ds_p->silhouette |= SIL_TOP; + ds_p->tsilheight = sector_ceilingheight(frontsector); + } else if (sector_ceilingheight(backsector) < viewz) { + ds_p->silhouette |= SIL_TOP; + ds_p->tsilheight = INT_MIN; + // ds_p->sprtopclip = screenheightarray; + } + + if (backsector->rawceilingheight <= frontsector->rawfloorheight) { + ds_p->sprbottomclip = minfloorceilingcliparray; + ds_p->bsilheight = INT_MAX; + ds_p->silhouette |= SIL_BOTTOM; + } + + if (backsector->rawfloorheight >= frontsector->rawceilingheight) { + ds_p->sprtopclip = maxfloorceilingcliparray; + ds_p->tsilheight = INT_MIN; + ds_p->silhouette |= SIL_TOP; + } +#endif + worldhigh = sector_ceilingheight(backsector) - viewz; + worldlow = sector_floorheight(backsector) - viewz; + + // hack to allow height changes in outdoor areas + if (frontsector->ceilingpic == skyflatnum + && backsector->ceilingpic == skyflatnum) { + worldtop = worldhigh; + } + + + if (worldlow != worldbottom + || backsector->floorpic != frontsector->floorpic + || backsector->lightlevel != frontsector->lightlevel) { + markfloor = true; + } else { + // same plane on both sides + markfloor = false; + } + + + if (worldhigh != worldtop + || backsector->ceilingpic != frontsector->ceilingpic + || backsector->lightlevel != frontsector->lightlevel) { + markceiling = true; + } else { + // same plane on both sides + markceiling = false; + } + + if (backsector->rawceilingheight <= frontsector->rawfloorheight + || backsector->rawfloorheight >= frontsector->rawceilingheight) { + // closed door + markceiling = markfloor = true; + } + + + if (worldhigh < worldtop) { + // top texture + toptexture = lookup_texture(texture_translation(side_toptexture(sidedef))); + if (line_flags(linedef) & ML_DONTPEGTOP) { + // top of texture at top + rw_toptexturemid = worldtop; + } else { + vtop = + sector_ceilingheight(backsector) + + texture_height(side_toptexture(sidedef)); + + // bottom of texture + rw_toptexturemid = vtop - viewz; + } + } + if (worldlow > worldbottom) { + // bottom texture + bottomtexture = lookup_texture(texture_translation(side_bottomtexture(sidedef))); + + if (line_flags(linedef) & ML_DONTPEGBOTTOM) { + // bottom of texture at bottom + // top of texture at top + rw_bottomtexturemid = worldtop; + } else // top of texture at top + rw_bottomtexturemid = worldlow; + } + rw_toptexturemid += side_rowoffset(sidedef); + rw_bottomtexturemid += side_rowoffset(sidedef); + + // allocate space for masked texture tables + if (side_midtexture(sidedef)) { + // masked midtexture + maskedtexture_tex = texture_translation(side_midtexture(sidedef)); + maskedtexture = lookup_masked_texture(maskedtexture_tex); +#if NO_DRAWSEGS + // find positioning + if (line_flags(seg_linedef(curline)) & ML_DONTPEGBOTTOM) { + rw_maskedtexturemid = frontsector->rawfloorheight > backsector->rawfloorheight + ? sector_floorheight(frontsector) : sector_floorheight(backsector); +#if USE_WHD + rw_maskedtexturemid = rw_maskedtexturemid + texture_height(maskedtexture_tex) - viewz; +#else + rw_maskedtexturemid = rw_maskedtexturemid + texture_height(maskedtexture) - viewz; +#endif + } else { + rw_maskedtexturemid = frontsector->rawceilingheight < backsector->rawceilingheight + ? sector_ceilingheight(frontsector) : sector_ceilingheight(backsector); + rw_maskedtexturemid = rw_maskedtexturemid - viewz; + } + rw_maskedtexturemid += side_rowoffset(seg_sidedef(curline)); +#else + maskedtexturecol = lastopening - rw_x; + ds_p->maskedtexturecol = maskedtexturecol; + lastopening += rw_stopx - rw_x; +#endif + } } - else - { - // two sided line - ds_p->sprtopclip = ds_p->sprbottomclip = NULL; - ds_p->silhouette = 0; - - if (frontsector->floorheight > backsector->floorheight) - { - ds_p->silhouette = SIL_BOTTOM; - ds_p->bsilheight = frontsector->floorheight; - } - else if (backsector->floorheight > viewz) - { - ds_p->silhouette = SIL_BOTTOM; - ds_p->bsilheight = INT_MAX; - // ds_p->sprbottomclip = negonearray; - } - - if (frontsector->ceilingheight < backsector->ceilingheight) - { - ds_p->silhouette |= SIL_TOP; - ds_p->tsilheight = frontsector->ceilingheight; - } - else if (backsector->ceilingheight < viewz) - { - ds_p->silhouette |= SIL_TOP; - ds_p->tsilheight = INT_MIN; - // ds_p->sprtopclip = screenheightarray; - } - - if (backsector->ceilingheight <= frontsector->floorheight) - { - ds_p->sprbottomclip = negonearray; - ds_p->bsilheight = INT_MAX; - ds_p->silhouette |= SIL_BOTTOM; - } - - if (backsector->floorheight >= frontsector->ceilingheight) - { - ds_p->sprtopclip = screenheightarray; - ds_p->tsilheight = INT_MIN; - ds_p->silhouette |= SIL_TOP; - } - - worldhigh = backsector->ceilingheight - viewz; - worldlow = backsector->floorheight - viewz; - - // hack to allow height changes in outdoor areas - if (frontsector->ceilingpic == skyflatnum - && backsector->ceilingpic == skyflatnum) - { - worldtop = worldhigh; - } - - - if (worldlow != worldbottom - || backsector->floorpic != frontsector->floorpic - || backsector->lightlevel != frontsector->lightlevel) - { - markfloor = true; - } - else - { - // same plane on both sides - markfloor = false; - } - - - if (worldhigh != worldtop - || backsector->ceilingpic != frontsector->ceilingpic - || backsector->lightlevel != frontsector->lightlevel) - { - markceiling = true; - } - else - { - // same plane on both sides - markceiling = false; - } - - if (backsector->ceilingheight <= frontsector->floorheight - || backsector->floorheight >= frontsector->ceilingheight) - { - // closed door - markceiling = markfloor = true; - } - - if (worldhigh < worldtop) - { - // top texture - toptexture = texturetranslation[sidedef->toptexture]; - if (linedef->flags & ML_DONTPEGTOP) - { - // top of texture at top - rw_toptexturemid = worldtop; - } - else - { - vtop = - backsector->ceilingheight - + textureheight[sidedef->toptexture]; - - // bottom of texture - rw_toptexturemid = vtop - viewz; - } - } - if (worldlow > worldbottom) - { - // bottom texture - bottomtexture = texturetranslation[sidedef->bottomtexture]; - - if (linedef->flags & ML_DONTPEGBOTTOM ) - { - // bottom of texture at bottom - // top of texture at top - rw_bottomtexturemid = worldtop; - } - else // top of texture at top - rw_bottomtexturemid = worldlow; - } - rw_toptexturemid += sidedef->rowoffset; - rw_bottomtexturemid += sidedef->rowoffset; - - // allocate space for masked texture tables - if (sidedef->midtexture) - { - // masked midtexture - maskedtexture = true; - ds_p->maskedtexturecol = maskedtexturecol = lastopening - rw_x; - lastopening += rw_stopx - rw_x; - } - } - // calculate rw_offset (only needed for textured lines) - segtextured = midtexture | toptexture | bottomtexture | maskedtexture; + segtextured = (midtexture!=0) | (toptexture!=0) | (bottomtexture!=0) | (maskedtexture!=0); - if (segtextured) - { - offsetangle = rw_normalangle-rw_angle1; - - if (offsetangle > ANG180) - offsetangle = -offsetangle; + if (segtextured) { + offsetangle = rw_normalangle - rw_angle1; - if (offsetangle > ANG90) - offsetangle = ANG90; + if (offsetangle > ANG180) + offsetangle = -offsetangle; - sineval = finesine[offsetangle >>ANGLETOFINESHIFT]; - rw_offset = FixedMul (hyp, sineval); + if (offsetangle > ANG90) + offsetangle = ANG90; - if (rw_normalangle-rw_angle1 < ANG180) - rw_offset = -rw_offset; + sineval = finesine(offsetangle >> ANGLETOFINESHIFT); + rw_offset = FixedMul(hyp, sineval); - rw_offset += sidedef->textureoffset + curline->offset; - rw_centerangle = ANG90 + viewangle - rw_normalangle; - - // calculate light table - // use different light tables - // for horizontal / vertical / diagonal - // OPTIMIZE: get rid of LIGHTSEGSHIFT globally - if (!fixedcolormap) - { - lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT)+extralight; + if (rw_normalangle - rw_angle1 < ANG180) + rw_offset = -rw_offset; - if (curline->v1->y == curline->v2->y) - lightnum--; - else if (curline->v1->x == curline->v2->x) - lightnum++; + rw_offset += side_textureoffset(sidedef) + seg_offset(curline); +#if USE_WHD + if (!seg_side(curline)) { + uint loff = linedef - lines; + uint min = 0; + uint max = numlinespecials; + while (min < max) { + uint mid = (min + max) / 2; + if (linespecialoffsetlist[mid] <= loff) { + if (linespecialoffsetlist[mid] == loff) { + rw_offset += linespecialoffset << FRACBITS; + break; + } + min = mid + 1; + } else { + max = mid; + } + } + } +#endif + rw_centerangle = ANG90 + viewangle - rw_normalangle; - if (lightnum < 0) - walllights = scalelight[0]; - else if (lightnum >= LIGHTLEVELS) - walllights = scalelight[LIGHTLEVELS-1]; - else - walllights = scalelight[lightnum]; - } + // calculate light table + // use different light tables + // for horizontal / vertical / diagonal + // OPTIMIZE: get rid of LIGHTSEGSHIFT globally + if (!fixedcolormap) { + lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT) + extralight; + + if (line_is_horiz(seg_linedef(curline))) + lightnum--; + else if (line_is_vert(seg_linedef(curline))) + lightnum++; + + if (lightnum < 0) + walllights = scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + walllights = scalelight[LIGHTLEVELS - 1]; + else + walllights = scalelight[lightnum]; + } } - + // if a floor / ceiling plane is on the wrong side // of the view plane, it is definitely invisible // and doesn't need to be marked. - - - if (frontsector->floorheight >= viewz) - { - // above view plane - markfloor = false; - } - - if (frontsector->ceilingheight <= viewz - && frontsector->ceilingpic != skyflatnum) - { - // below view plane - markceiling = false; + + + if (sector_floorheight(frontsector) >= viewz) { + // above view plane + markfloor = false; } - + if (sector_ceilingheight(frontsector) <= viewz + && frontsector->ceilingpic != skyflatnum) { + // below view plane + markceiling = false; + } + + // calculate incremental stepping values for texture edges - worldtop >>= 4; - worldbottom >>= 4; - - topstep = -FixedMul (rw_scalestep, worldtop); - topfrac = (centeryfrac>>4) - FixedMul (worldtop, rw_scale); + worldtop >>= WORLD_TO_HEIGHT_SHIFT; + worldbottom >>= WORLD_TO_HEIGHT_SHIFT; - bottomstep = -FixedMul (rw_scalestep,worldbottom); - bottomfrac = (centeryfrac>>4) - FixedMul (worldbottom, rw_scale); - - if (backsector) - { - worldhigh >>= 4; - worldlow >>= 4; + topstep = -FixedMul(rw_scalestep, worldtop); + topfrac = (centeryfrac >> WORLD_TO_HEIGHT_SHIFT) - FixedMul(worldtop, rw_scale); - if (worldhigh < worldtop) - { - pixhigh = (centeryfrac>>4) - FixedMul (worldhigh, rw_scale); - pixhighstep = -FixedMul (rw_scalestep,worldhigh); - } - - if (worldlow > worldbottom) - { - pixlow = (centeryfrac>>4) - FixedMul (worldlow, rw_scale); - pixlowstep = -FixedMul (rw_scalestep,worldlow); - } + bottomstep = -FixedMul(rw_scalestep, worldbottom); + bottomfrac = (centeryfrac >> WORLD_TO_HEIGHT_SHIFT) - FixedMul(worldbottom, rw_scale); + + if (backsector) { + worldhigh >>= WORLD_TO_HEIGHT_SHIFT; + worldlow >>= WORLD_TO_HEIGHT_SHIFT; + + if (worldhigh < worldtop) { + pixhigh = (centeryfrac >> WORLD_TO_HEIGHT_SHIFT) - FixedMul(worldhigh, rw_scale); + pixhighstep = -FixedMul(rw_scalestep, worldhigh); + } + + if (worldlow > worldbottom) { + pixlow = (centeryfrac >> WORLD_TO_HEIGHT_SHIFT) - FixedMul(worldlow, rw_scale); + pixlowstep = -FixedMul(rw_scalestep, worldlow); + } } - + +#if !NO_VISPLANE_GUTS // render it if (markceiling) - ceilingplane = R_CheckPlane (ceilingplane, rw_x, rw_stopx-1); - + ceilingplane = R_CheckPlane(ceilingplane, rw_x, rw_stopx - 1); + if (markfloor) - floorplane = R_CheckPlane (floorplane, rw_x, rw_stopx-1); + floorplane = R_CheckPlane(floorplane, rw_x, rw_stopx - 1); +#endif - R_RenderSegLoop (); + R_RenderSegLoop(); - + +#if !NO_DRAWSEGS // save sprite clipping info - if ( ((ds_p->silhouette & SIL_TOP) || maskedtexture) - && !ds_p->sprtopclip) - { - memcpy (lastopening, ceilingclip+start, sizeof(*lastopening)*(rw_stopx-start)); - ds_p->sprtopclip = lastopening - start; - lastopening += rw_stopx - start; - } - - if ( ((ds_p->silhouette & SIL_BOTTOM) || maskedtexture) - && !ds_p->sprbottomclip) - { - memcpy (lastopening, floorclip+start, sizeof(*lastopening)*(rw_stopx-start)); - ds_p->sprbottomclip = lastopening - start; - lastopening += rw_stopx - start; + if (((ds_p->silhouette & SIL_TOP) || maskedtexture) + && !ds_p->sprtopclip) { + memcpy(lastopening, ceilingclip + start, sizeof(*lastopening) * (rw_stopx - start)); + ds_p->sprtopclip = lastopening - start; + lastopening += rw_stopx - start; } - if (maskedtexture && !(ds_p->silhouette&SIL_TOP)) - { - ds_p->silhouette |= SIL_TOP; - ds_p->tsilheight = INT_MIN; + if (((ds_p->silhouette & SIL_BOTTOM) || maskedtexture) + && !ds_p->sprbottomclip) { + memcpy(lastopening, floorclip + start, sizeof(*lastopening) * (rw_stopx - start)); + ds_p->sprbottomclip = lastopening - start; + lastopening += rw_stopx - start; } - if (maskedtexture && !(ds_p->silhouette&SIL_BOTTOM)) - { - ds_p->silhouette |= SIL_BOTTOM; - ds_p->bsilheight = INT_MAX; + + if (maskedtexture && !(ds_p->silhouette & SIL_TOP)) { + ds_p->silhouette |= SIL_TOP; + ds_p->tsilheight = INT_MIN; + } + if (maskedtexture && !(ds_p->silhouette & SIL_BOTTOM)) { + ds_p->silhouette |= SIL_BOTTOM; + ds_p->bsilheight = INT_MAX; } ds_p++; +#endif } +#if PD_COLUMNS && USE_WHD +void pd_add_column2(pd_column_type type) { + assert(dc_source.real_id >= 0); + int pc = whd_textures[dc_source.real_id].patch_count; + if (!pc) { + dc_source = make_drawcolumn(lookup_patch(whd_textures[dc_source.real_id].patch0-firstspritelump), dc_source.col); + } else { + uint8_t *patch_table = &((uint8_t *)whd_textures)[whd_textures[dc_source.real_id].metdata_offset]; + uint8_t *metadata = patch_table + pc * 2; + + int xx = 0; + do { + uint b=metadata[0]; + if (xx + 1 + (b&0x7f) > dc_source.col) break; + xx = xx + 1 + (b&0x7f); + metadata += 1 + 2 * (b>>7u); + } while (true); + if (metadata[0] & 0x80) { + int pn = metadata[1]; + if (pn != 0xff) { // todo this was here before zero patch columns, but i can't see why (it used to fall thru to regular add column type) + // single patch column + assert(pn < pc); + uint pnum = patch_table[pn*2] | (patch_table[pn*2+1] << 8); + // note as pre R_GenerateLookup, a single patch column ignores the patch offsety + dc_source = make_drawcolumn(lookup_patch(pnum-firstspritelump), (uint8_t)(dc_source.col - metadata[2])); + } else { +#warning untested no patch column + return; + } + } + } + pd_add_column(type); +} +#endif \ No newline at end of file diff --git a/src/doom/r_segs.h b/src/doom/r_segs.h index d4a4d899..966d6a66 100644 --- a/src/doom/r_segs.h +++ b/src/doom/r_segs.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -22,12 +23,12 @@ - +#if !NO_DRAWSEGS void R_RenderMaskedSegRange ( drawseg_t* ds, int x1, int x2 ); - +#endif #endif diff --git a/src/doom/r_sky.c b/src/doom/r_sky.c index bcd5a75b..59d69275 100644 --- a/src/doom/r_sky.c +++ b/src/doom/r_sky.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -34,8 +35,11 @@ // // sky mapping // -int skyflatnum; -int skytexture; +flatnum_t skyflatnum; +texnum_t skytexture; +#if DOOM_TINY +lumpindex_t skytexture_patch; +#endif int skytexturemid; diff --git a/src/doom/r_sky.h b/src/doom/r_sky.h index 8ad6680a..3b700d6f 100644 --- a/src/doom/r_sky.h +++ b/src/doom/r_sky.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -28,7 +29,13 @@ // The sky map is 256*128*4 maps. #define ANGLETOSKYSHIFT 22 -extern int skytexture; +extern texnum_t skytexture; +#if USE_WHD +extern framedrawable_t *skytexture_fd; +#endif +#if DOOM_TINY +extern lumpindex_t skytexture_patch; +#endif extern int skytexturemid; // Called whenever the view size changes. diff --git a/src/doom/r_state.h b/src/doom/r_state.h index 2a60e2fe..7d8b26d4 100644 --- a/src/doom/r_state.h +++ b/src/doom/r_state.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -20,75 +21,175 @@ #ifndef __R_STATE__ #define __R_STATE__ +#if USE_WHD +#include "whddata.h" +extern const uint8_t popcount8_table[128]; +extern const uint8_t bitcount8_table[256]; +static inline uint8_t popcount8(uint8_t v) { + return (v & 1) ? (popcount8_table[v/2] >> 4) : (popcount8_table[v/2] & 0xf); +} +static inline uint8_t bitcount8(uint8_t v) { + return bitcount8_table[v]; +} +#endif + // Need data structure definitions. #include "d_player.h" #include "r_data.h" +extern int viewwidth; +extern int viewheight; +extern int scaledviewwidth; +extern const lighttable_t* colormaps; +extern lumpindex_t firstflat; +// replacement mappings for for global animation +#if !USE_WHD +extern flatnum_t* flattranslation; +extern texnum_t* texturetranslation; +#define flat_translation(f) flattranslation[f] +#define texture_translation(t) texturetranslation[t] +#else +// note these are actually smaller types the flatnum_t/textnum_t as all the translatable flats/textures +// have been moved to the beginning (i.e. < 256) +extern flatname_t whd_flattranslation[NUM_SPECIAL_FLATS]; +extern const uint8_t *whd_specialtoflat; +#define whd_flattospecial (&whd_specialtoflat[NUM_SPECIAL_FLATS]) +extern texturename_t whd_texturetranslation[NUM_SPECIAL_TEXTURES]; +#define flat_translation(f) ((f)numframes +#define sprite_frame(s,n) (&sprite_sprdef(s)->spriteframes[n]) +#define spriteframe_rotates(f) (f)->rotate +#define spriteframe_unrotated_pic(f) (f)->lump[0] +#define spriteframe_rotated_pic(f,n) (f)->lump[n] +#if !DOOM_SMALL +#define spriteframe_rotated_flipped(f, n) (f)->flip[n] +#define spriteframe_unrotated_flipped(f) (f)->flip[0] +#else +#define spriteframe_rotated_flipped(f, n) (((f)->flips & (1u << (n))) != 0) +#define spriteframe_unrotated_flipped(f) spriteframe_rotated_flipped(f, 0) +#endif +#else +extern const int32_t *whd_sprite_meta; +extern const uint16_t *whd_sprite_frame_meta; +#define sprite_width(s) ((whd_sprite_meta[s]&0x3ff) << FRACBITS) +#define sprite_offset(s) ((whd_sprite_meta[s]>>21) << FRACBITS) +#define sprite_topoffset(s) (((whd_sprite_meta[s]<<11)>>21) << FRACBITS) +#define sprite_numframes(s) (whd_sprite_frame_meta[(s)+1] - whd_sprite_frame_meta[s]) +#define sprite_sprdef(s) whd_sprite_frame_meta[s] +#define sprite_frame(s,n) whd_sprite_frame_meta[sprite_sprdef(s)+(n)] +#define spriteframe_rotates(f) (((f)&32768) != 0) +#define spriteframe_unrotated_pic(f) ((f)&0x7fff) +#define spriteframe_unrotated_flipped(f) 0 +#define spriteframe_rotated_flipped(f, n) ((whd_sprite_frame_meta[((f)&0x7fff) + (n)] & 32768) != 0) +#define spriteframe_rotated_pic(f, n) (whd_sprite_frame_meta[((f)&0x7fff) + (n)] & 0x7fff) +#endif +extern lumpindex_t firstspritelump; +extern lumpindex_t lastspritelump; +extern lumpindex_t numspritelumps; // // Lookup tables for map data. // -extern int numsprites; -extern spritedef_t* sprites; +extern cardinal_t numsprites; -extern int numvertexes; +extern cardinal_t numvertexes; extern vertex_t* vertexes; -extern int numsegs; +#if !WHD_SUPER_TINY +extern cardinal_t numsegs; +#endif extern seg_t* segs; -extern int numsectors; +extern cardinal_t numsectors; extern sector_t* sectors; +#if LOAD_COMPRESSED || SAVE_COMPRESSED +extern whdsector_t *whd_sectors; +#endif -extern int numsubsectors; +extern cardinal_t numsubsectors; extern subsector_t* subsectors; -extern int numnodes; +extern cardinal_t numnodes; extern node_t* nodes; -extern int numlines; +#if !USE_INDEX_LINEBUFFER +extern line_t** linebuffer; +#else +extern cardinal_t * linebuffer; +#endif + +#if !WHD_SUPER_TINY +#define bsp_child(n,w) (nodes[n]).children[w] +#else +static inline int bsp_child(int n, int w) { + // w is 0 R, 1 L + uint coded = nodes[n].coded_children; + // code is 11ll llll lrrr rrrr : l/r are 7 bit signed values; left leaf = 0x8000 + n - l; right leaf = 0x8000 + n - r + // 10ll llll l--- ---- : right node is n- 1, left leaf via l as above + // 01-- ---- -rrr rrrr : left node is n - 1, right leaf via r is as above, + // 00rr rrrr rrrr rrrr : left node is n - 1, right node is n - r + int v[2]; + if (coded & 0x8000) { + if (w == 1) { + return 0x8000 + n - (((int32_t)(coded << 18)) >> 25); + } else if (coded & 0x4000) { + return 0x8000 + n - (((int32_t)(coded << 25)) >> 25); + } else { + return n - 1; + } + } else { + if (w == 1) { + return n - 1; + } else if (coded & 0x4000) { + return 0x8000 + n - (((int32_t)(coded << 25)) >> 25); + } else { + return n - (coded & 0x3fffu); + } + } +} +#endif +extern cardinal_t numlines; +#if WHD_SUPER_TINY +extern cardinal_t numlines5; +// approximately lnum / 5 +#define line_bitmap_index(lnum) (__fast_mul(lnum, 0x34)>>8) +#else +#endif extern line_t* lines; -extern int numsides; +#if !USE_WHD || !WHD_SUPER_TINY +extern cardinal_t numsides; extern side_t* sides; +#else +extern const uint8_t *sides_z; +#endif // @@ -101,27 +202,402 @@ extern fixed_t viewz; extern angle_t viewangle; extern player_t* viewplayer; - // ? -extern angle_t clipangle; - -extern int viewangletox[FINEANGLES/2]; +#if !FIXED_SCREENWIDTH +extern isb_int16_t viewangletox[FINEANGLES/2]; extern angle_t xtoviewangle[SCREENWIDTH+1]; -//extern fixed_t finetangent[FINEANGLES/2]; +#define x_to_viewangle(x) xtoviewangle[x] +#else +extern const int16_t viewangletox[FINEANGLES/2]; +extern const uint16_t xtoviewangle_[SCREENWIDTH+1]; +#define x_to_viewangle(x) (xtoviewangle_[x] << 16) +#endif +extern angle_t clipangle; +//extern fixed_t finetangent(FINEANGLES/2); extern fixed_t rw_distance; extern angle_t rw_normalangle; - - // angle to line origin extern int rw_angle1; // Segs count? -extern int sscount; +//extern int sscount; +#if !NO_VISPLANES +// todo graham i don't think we need this many any more? (more recently i don't know why i thought that) +#define MAXVISPLANES 128 +extern visplane_t visplanes[MAXVISPLANES]; +extern visplane_t* lastvisplane; extern visplane_t* floorplane; extern visplane_t* ceilingplane; +#endif +#if MU_STATS +extern uint32_t stats_dc_iscale_min, stats_dc_iscale_max; +#endif + +#if USE_RAW_MAPVERTEX +#define vertex_x(v) (((v)->x) << FRACBITS) +#define vertex_y(v) (((v)->y) << FRACBITS) +#define vertex_x_raw(v) ((v)->x) +#define vertex_y_raw(v) ((v)->y) +#define vertex_raw_to_fixed(p) (((fixed_t)(p)) << FRACBITS) +#else +#define vertex_x(v) ((v)->x) +#define vertex_y(v) ((v)->y) +#define vertex_x_raw(v) ((v)->x) +#define vertex_y_raw(v) ((v)->y) +#define vertex_raw_to_fixed(v) (v) +#endif + +#if USE_WHD +#define CST_TOP 0 +#define CST_MID 1 +#define CST_BOTTOM 2 +extern int check_switch_texture(side_t * side, int where, int texture); +#if SAVE_COMPRESSED +boolean is_switched_texture(const side_t *side, int where); +#endif +#if WHD_SUPER_TINY +#define sidenum_to_side(sidenum) (sides_z + (sidenum)) +static inline uint side_sectornum(side_t *side) { + return side[1]; +} +#define side_sector(side) (sectors + side_sectornum(side)) + +static inline int side_toptexture(side_t *side) { + if ((side[0] & 0xf) < 5) return 0; + return side[2] <= LAST_SWITCH_TEXTURE ? check_switch_texture(side, CST_TOP, side[2]) : side[2]; +} + +static inline int side_midtexture(side_t *side) { + static uint8_t posM[26] = { 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 3, 3, 3, 3}; + uint enc = side[0] & 0xf; + if (!posM[enc]) return 0; + return side[posM[enc]] <= LAST_SWITCH_TEXTURE ? check_switch_texture(side, CST_MID, side[posM[enc]]) : side[posM[enc]]; +} + +static inline int side_bottomtexture(side_t *side) { + static uint8_t posB[26] = { 0, 2, 0, 2, 3, 0, 2, 3, 0, 2, 3, 4, 0, 2, 3, 4}; + uint enc = side[0] & 0xf; + if (!posB[enc]) return 0; + return side[posB[enc]] <= LAST_SWITCH_TEXTURE ? check_switch_texture(side, CST_BOTTOM, side[posB[enc]]) : side[posB[enc]]; +} + +static inline int side_textureoffset16(side_t *side) { + int16_t toff = 0; + if (side[0] & 48) { + static uint8_t offset_offset[16] = {2, 3, 3, 3, 4, 3, 3, 4, 3, 3, 4, 4, 4, 4, 4, 5}; + uint enc = side[0] & 0xf; + if (side[0] & 32) { + toff = (side[offset_offset[enc]] << 8) + side[1 + offset_offset[enc]]; + } else { + toff = side[offset_offset[enc]] << 1; + } + } + return toff; +} +#define side_textureoffset(side) (side_textureoffset16(side) << FRACBITS) +static inline int side_rowoffset16(side_t *side) { + // todo is this shared with the other? + static uint8_t offset_offset[16] = {2, 3, 3, 3, 4, 3, 3, 4, 3, 3, 4, 4, 4, 4, 4, 5}; + int16_t rowoff = 0; + if (side[0] & 64) { + uint enc = side[0] & 0xf; + uint pos = offset_offset[enc] + ((sides_z[0] >> 4)&3); + rowoff = side[pos]; + if (rowoff & 128) { + rowoff = ((rowoff << 8) | sides_z[pos + 1])<<1; + rowoff /= 2; + } else { + rowoff <<= 1; + } + } + return rowoff; +} +#define side_rowoffset(side) (side_rowoffset16(side) << FRACBITS) +#else +#define sidenum_to_side(sidenum) (&sides[sidenum]) +#define side_sector(side) (§ors[(side)->sector]) +#define side_sectornum(side) (side)->sector +#define side_toptexture(side) ((side)->toptexture < LAST_SWITCH_TEXTURE ? check_switch_texture(side, CST_TOP, (side)->toptexture) : (side)->toptexture) +#define side_midtexture(side) ((side)->midtexture < LAST_SWITCH_TEXTURE ? check_switch_texture(side, CST_MID, (side)->midtexture) : (side)->midtexture) +#define side_bottomtexture(side) ((side)->bottomtexture < LAST_SWITCH_TEXTURE ? check_switch_texture(side, CST_BOTTOM, (side)->bottomtexture) : (side)->bottomtexture) +#define side_textureoffset(side) ((side)->textureoffset << FRACBITS) +#define side_textureoffset16(side) (side)->textureoffset +#define side_rowoffset(side) ((side)->rowoffset << FRACBITS) +#endif + +// there is nothing in the game that changes this +#define side_setrowoffset16(side, o) assert(side_rowoffset(side) == (o)) +extern uint8_t num_switched_sides; +void side_settoptexture(const side_t *side, lumpindex_t t); +void side_setmidtexture(const side_t *side, lumpindex_t t); +void side_setbottomtexture(const side_t *side, lumpindex_t t); +void side_settextureoffset16(const side_t *side, int offset); +#else +#define sidenum_to_side(sidenum) (&sides[sidenum]) +#define side_sector(side) (side)->sector +#define side_sectornum(side) ((side)->sector - sectors) +#define side_toptexture(side) (side)->toptexture +#define side_midtexture(side) (side)->midtexture +#define side_bottomtexture(side) (side)->bottomtexture +#define side_textureoffset(side) (side)->textureoffset +#define side_textureoffset16(side) ((side)->textureoffset >> FRACBITS) +#define side_rowoffset(side) (side)->rowoffset +#define side_settoptexture(side, t) (side)->toptexture = t +#define side_setmidtexture(side, t) (side)->midtexture = t +#define side_setbottomtexture(side, t) (side)->bottomtexture = t +#define side_settextureoffset16(side, o) (side)->textureoffset = (o) << FRACBITS +#define side_setrowoffset16(side, o) (side)->rowoffset = (o) << FRACBITS +#endif + +#if USE_RAW_MAPLINEDEF +#if !WHD_SUPER_TINY +#define line_sidenum(l, s) ((l)->sidenum[s]) +#define line_flags(l) ((l)->flags) +#define line_v1(l) (&vertexes[(l)->v1]) +#define line_v2(l) (&vertexes[(l)->v2]) +#define line_tag(l) ((l)->tag) +// todo special case to check for NULL +// todo bits for front/back sector mismatched heights (along with twosided) +static inline sector_t *line_frontsector(const line_t *l) { + int s = line_sidenum(l, 0); + return s == -1 ? 0 : side_sector(sidenum_to_side(s)); +} +static inline sector_t *line_backsector(const line_t *l) { + int s = line_sidenum(l, 1); + return s == -1 ? 0 : side_sector(sidenum_to_side(s)); +} +#define line_onesided(l) (line_sidenum(l,1)==-1) +#define line_next_step(l) 1 +#else + +#define line_onesided(l) ((l[1] & (ML_SIDE_MASK >> 8)) == 0) +#define line_predict_side(l) ((l[1] & (ML_NO_PREDICT_SIDE >> 8)) == 0) +#define line_predict_v1(l) ((l[1] & (ML_NO_PREDICT_V1 >> 8)) == 0) +#define line_predict_v2(l) ((l[1] & (ML_NO_PREDICT_V2 >> 8)) == 0) + +static inline short line_flags(const line_t *l) { + return l[0];// + (l[1] << 8); // todo mask off top 7? +} + +extern uint16_t whd_sidemul; +static inline int line_sidenum(const line_t *l, int side) { + if (side && line_onesided(l)) return -1; + int s; + if (line_predict_side(l)) { + s = ((l - lines) * whd_sidemul) >> 16; + s += (int8_t)l[2]; + } else { + s = l[2] + (l[3] << 8); + } + if (side) { + s += l[1] >> 5; + } + return s; +} + +extern uint16_t whd_vmul; +static inline vertex_t *line_v1(const line_t *l) { + int v; + uint pos = 3 + (l[1]&1); + if (line_predict_v1(l)) { + v = ((l - lines) * whd_vmul) >> 16; + v += (int8_t)l[pos]; + } else { + v = l[pos] + (l[pos+1] << 8); + } + return vertexes + v; +} + +static inline vertex_t *line_v2(const line_t *l) { + const static uint8_t v2pos[4] = { 4, 5, 5, 6 }; + uint pos = v2pos[l[1]&3]; + int v; + if (line_predict_v2(l)) { + v = ((l - lines) * whd_vmul) >> 16; + v += (int8_t)l[pos]; + } else { + v = l[pos] + (l[pos+1] << 8); + } + return vertexes + v; +} + +static inline int line_tag(const line_t *l) { + int tag = 0; + if ((l[1] & (ML_HAS_TAG >> 8)) != 0) { + const static uint8_t special_pos[8] = { 5, 6, 6, 7, 6, 7, 7, 8 }; + uint pos = special_pos[l[1]&7] + ((l[1] & (ML_HAS_SPECIAL >> 8)) != 0); + tag = l[pos]; + } + return tag; +} + +// todo special case to check for NULL +// todo bits for front/back sector mismatched heights (along with twosided) +static inline sector_t *line_frontsector(const line_t *l) { + return side_sector(sidenum_to_side(line_sidenum(l, 0))); +} + +static inline sector_t *line_backsector(const line_t *l) { + if (line_onesided(l)) + return 0; + else + return side_sector(sidenum_to_side(line_sidenum(l, 1))); +} +#define line_next_step(l) (5 + popcount8((l)[1]&0x1f)) #endif +#define line_is_horiz(l) (vertex_y_raw(line_v1(l)) == vertex_y_raw(line_v2(l))) +#define line_is_vert(l) (vertex_x_raw(line_v1(l)) == vertex_x_raw(line_v2(l))) +#define line_dy_gt_0(l) (vertex_y_raw(line_v2(l)) > vertex_y_raw(line_v1(l))) +#define line_dy_lt_0(l) (vertex_y_raw(line_v2(l)) < vertex_y_raw(line_v1(l))) +#define line_dx_lt_0(l) (vertex_x_raw(line_v2(l)) < vertex_x_raw(line_v1(l))) +#define line_dx_gt_0(l) (vertex_x_raw(line_v2(l)) > vertex_x_raw(line_v1(l))) +#define line_dy(l) (vertex_y(line_v2(l)) - vertex_y(line_v1(l))) +#define line_dx(l) (vertex_x(line_v2(l)) - vertex_x(line_v1(l))) +#define line_dy_raw(l) (vertex_y_raw(line_v2(l)) - vertex_y_raw(line_v1(l))) +#define line_dx_raw(l) (vertex_x_raw(line_v2(l)) - vertex_x_raw(line_v1(l))) + +int line_special(should_be_const line_t *line); +void clear_line_special(should_be_const line_t *line); +#if SAVE_COMPRESSED +int whd_line_special(should_be_const line_t *line); +#endif +boolean line_validcount_update_check_impl(should_be_const line_t *ld); +#define line_validcount_update_check(ld, vc) line_validcount_update_check_impl(ld) +void line_check_reset(void); +void line_set_mapped(should_be_const line_t *line); +boolean line_is_mapped(should_be_const line_t *line); + +static inline int line_slopetype(const line_t *l) { + if (line_is_horiz(l)) return ST_HORIZONTAL; + if (line_is_vert(l)) return ST_VERTICAL; + + // todo graham this is horrible + //return FixedDiv(line_dy(l), line_dx(l)) > 0 ? ST_POSITIVE : ST_NEGATIVE; + // todo not quite the same thing, but probably what was meant... if we pre-calc we can do the exact thing +#if !PICO_ON_DEVICE + // todo graham little sanity check + int foo = (( line_dy_raw(l) ^ line_dx_raw(l)) >= 0); + int foo2 = FixedDiv(line_dy(l), line_dx(l)) > 0; + if (foo != foo2) { + printf("WLALA %d %d\n", line_dx_raw(l), line_dy_raw(l)); + } +#endif + return (( line_dy_raw(l) ^ line_dx_raw(l)) >= 0) ? ST_POSITIVE : ST_NEGATIVE; +} +#if !DOOM_SMALL // would already be defined +extern uint32_t *line_sector_check_bitmap; // shared by both line and sector checking - todo actually just alloc on the fly +#endif +extern uint32_t *line_mapped_bitmap; +extern uint32_t *line_special_cleared_bitmap; +#else +#define line_sidenum(l, s) ((l)->sidenum[s]) +#define line_onesided(l) (line_sidenum(l,1)==-1) +#define line_flags(l) ((l)->flags) +#define line_v1(l) ((l)->v1) +#define line_v2(l) ((l)->v2) +#define line_tag(l) ((l)->tag) +#define line_special(l) ((l)->special) +#define line_frontsector(l) ((l)->frontsector) +#define line_backsector(l) ((l)->backsector) +#define line_is_horiz(l) ((l)->slopetype == ST_HORIZONTAL) +#define line_is_vert(l) ((l)->slopetype == ST_VERTICAL) +#define clear_line_special(l) hack_rowad_p(line_t, l, special) = 0 +#define line_dy_gt_0(l) ((l)->dy > 0) +#define line_dy_lt_0(l) ((l)->dy < 0) +#define line_dx_lt_0(l) ((l)->dx < 0) +#define line_dx_gt_0(l) ((l)->dx > 0) +#define line_dx(l) ((l)->dx) +#define line_dy(l) ((l)->dy) +#define line_slopetype(l) ((l)->slopetype) +#define line_check_reset() ((void)0) +#define line_set_mapped(l) hack_rowad_p(line_t, l, flags) |= ML_MAPPED +#define line_is_mapped(l) (line_flags(l) & ML_MAPPED) + +static inline boolean line_validcount_update_check(should_be_const line_t *ld, int validcount) { + if (validcount == ld->validcount) { + return true; + } + hack_rowad_p(line_t, ld, validcount) = validcount; + return false; +} +#define line_next_step(l) 1 +#endif + +#if USE_RAW_MAPSEG +#include +#if !WHD_SUPER_TINY +#define seg_side(s) ((s)->side) +#define seg_linedef(s) (&lines[(s)->linedef]) +#define seg_v1(s) (&vertexes[(s)->v1]) +#define seg_v2(s) (&vertexes[(s)->v2]) +#define seg_angle(s) (((s)->angle) << FRACBITS) +#define seg_offset(s) (((s)->offset) << FRACBITS) +#define seg_next_step(s) 1 +#else +#define seg_linedef(s) (&lines[(s)[3] + ((s)[4] << 8)]) +#define seg_side(s) ((s)[0] >> 7) +#define seg_v1(s) (&vertexes[((s)[1] | (((s)[0]&0x07)<<8))]) +#define seg_v2(s) (&vertexes[((s)[2] | (((s)[0]&0x38)<<5))]) +static inline fixed_t seg_offset(const seg_t *s) { + if (!(s[0] & 64)) { + return 0; + } + if (s[5] < 128) { + return (s[5] * 4) << FRACBITS; + } else { + return (((s[5]<<23)|(s[6]<<17)))/2; + } +} + +static inline int seg_next_step(const seg_t *s) { + int size = 5; + if (s[0]&64) { + size += s[5]&128 ? 2 : 1; + } + return size; +} +#endif +#define seg_sidedef(s) (sidenum_to_side(line_sidenum(seg_linedef(s), seg_side(s)))) +static inline sector_t *seg_frontsector(const seg_t *seg) { + return side_sector(sidenum_to_side(line_sidenum(seg_linedef(seg), seg_side(seg)))); +} + +static inline sector_t *seg_backsector(const seg_t *seg) { + // todo this check might be redundent - see use in R_AddLine which could check flags instead + if (!(line_flags(seg_linedef(seg)) & ML_TWOSIDED)) return NULL; + return side_sector(sidenum_to_side(line_sidenum(seg_linedef(seg), seg_side(seg)^1))); +} +#else +#define seg_linedef(s) ((s)->linedef) +#define seg_sidedef(s) ((s)->sidedef) +#define seg_frontsector(s) ((s)->frontsector) +#define seg_backsector(s) ((s)->backsector) +#define seg_v1(s) ((s)->v1) +#define seg_v2(s) ((s)->v2) +#define seg_sidedef(s) ((s)->sidedef) +#define seg_angle(s) ((s)->angle) +#define seg_offset(s) ((s)->offset) +#define seg_next_step(s) 1 +#endif + +#if USE_WHD +#define subsector_firstline(ss) &segs[*(ss)] +#define subsector_sector(ss) side_sector(seg_sidedef(subsector_firstline(ss))) +#define subsector_linelimit(ss) &segs[(ss)[1]] +#else +#define subsector_firstline(ss) &segs[(ss)->firstline] +#define subsector_sector(ss) ((ss)->sector) +#define subsector_linelimit(ss) ((subsector_firstline(ss) + (ss)->numlines)) +#endif + +#if USE_INDEX_LINEBUFFER +#define sector_line(sector, n) &lines[linebuffer[(sector)->line_index + (n)]] +#else +#define sector_line(sector, n) (sector)->lines[n] +#endif +#endif diff --git a/src/doom/r_things.c b/src/doom/r_things.c index b9e879c1..3f55fe9e 100644 --- a/src/doom/r_things.c +++ b/src/doom/r_things.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -16,12 +17,22 @@ // Refresh of things, i.e. objects represented by sprites. // - - +#if NO_DRAW_SPRITES +int no_draw_sprites = 1; +#else +int no_draw_sprites; +#endif +#if NO_DRAW_PSPRITES +int no_draw_psprites = 1; +#else +int no_draw_psprites; +#endif #include #include - +#if DOOM_TINY +#include +#endif #include "deh_main.h" #include "doomdef.h" @@ -34,30 +45,32 @@ #include "r_local.h" #include "doomstat.h" +#if PICO_DOOM + +#include "picodoom.h" +#include "v_patch.h" +#endif - -#define MINZ (FRACUNIT*4) -#define BASEYCENTER (SCREENHEIGHT/2) +#define MINZ (FRACUNIT*4) +#define BASEYCENTER (SCREENHEIGHT/2) //void R_DrawColumn (void); //void R_DrawFuzzColumn (void); -typedef struct -{ - int x1; - int x2; - - int column; - int topclip; - int bottomclip; +typedef struct { + int x1; + int x2; + + int column; + int topclip; + int bottomclip; } maskdraw_t; - // // Sprite rotation 0 is facing the viewer, // rotation 1 is one angle turn CLOCKWISE around the axis. @@ -65,16 +78,19 @@ typedef struct // which increases counter clockwise (protractor). // There was a lot of stuff grabbed wrong, so I changed it... // -fixed_t pspritescale; -fixed_t pspriteiscale; +fixed_t pspritescale; +fixed_t pspriteiscale; -lighttable_t** spritelights; +#if !USE_LIGHTMAP_INDEXES +const lighttable_t** spritelights; +#else +int8_t *spritelights; +#endif // constant arrays // used for psprite clipping and initializing clipping -short negonearray[SCREENWIDTH]; -short screenheightarray[SCREENWIDTH]; - +floor_ceiling_clip_t minfloorceilingcliparray[SCREENWIDTH]; +floor_ceiling_clip_t maxfloorceilingcliparray[SCREENWIDTH]; // // INITIALIZATION FUNCTIONS @@ -82,77 +98,85 @@ short screenheightarray[SCREENWIDTH]; // variables used to look up // and range check thing_t sprites patches -spritedef_t* sprites; -int numsprites; - -spriteframe_t sprtemp[29]; -int maxframe; -const char *spritename; - - +#if !USE_WHD +spritedef_t *sprites; +#endif +cardinal_t +numsprites; +#if !USE_WHD +typedef struct sprite_init_state_s { + spriteframe_t sprtemp[29]; + int maxframe; + const char *spritename; +} sprite_init_state_t; // // R_InstallSpriteLump // Local function for R_InitSprites. // -void +static void R_InstallSpriteLump -( int lump, - unsigned frame, - unsigned rotation, - boolean flipped ) -{ - int r; - - if (frame >= 29 || rotation > 8) - I_Error("R_InstallSpriteLump: " - "Bad frame characters in lump %i", lump); - - if ((int)frame > maxframe) - maxframe = frame; - - if (rotation == 0) - { - // the lump should be used for all rotations - if (sprtemp[frame].rotate == false) - I_Error ("R_InitSprites: Sprite %s frame %c has " - "multip rot=0 lump", spritename, 'A'+frame); + (sprite_init_state_t *init, int lump, + unsigned frame, + unsigned rotation, + boolean flipped) { + int r; - if (sprtemp[frame].rotate == true) - I_Error ("R_InitSprites: Sprite %s frame %c has rotations " - "and a rot=0 lump", spritename, 'A'+frame); - - sprtemp[frame].rotate = false; - for (r=0 ; r<8 ; r++) - { - sprtemp[frame].lump[r] = lump - firstspritelump; - sprtemp[frame].flip[r] = (byte)flipped; - } - return; + if (frame >= 29 || rotation > 8) + I_Error("R_InstallSpriteLump: " + "Bad frame characters in lump %i", lump); + + if ((int) frame > init->maxframe) + init->maxframe = frame; + + if (rotation == 0) { + // the lump should be used for all rotations + if (init->sprtemp[frame].rotate == false) + I_Error("R_InitSprites: Sprite %s frame %c has " + "multip rot=0 lump", init->spritename, 'A' + frame); + + if (init->sprtemp[frame].rotate == true) + I_Error("R_InitSprites: Sprite %s frame %c has rotations " + "and a rot=0 lump", init->spritename, 'A' + frame); + + init->sprtemp[frame].rotate = false; +#if DOOM_SMALL + init->sprtemp[frame].flips = flipped ? 0xff : 0; +#endif + for (r = 0; r < 8; r++) { + init->sprtemp[frame].lump[r] = lump - firstspritelump; +#if !DOOM_SMALL + init->sprtemp[frame].flip[r] = (byte) flipped; +#endif + } + return; } - + // the lump is only used for one rotation - if (sprtemp[frame].rotate == false) - I_Error ("R_InitSprites: Sprite %s frame %c has rotations " - "and a rot=0 lump", spritename, 'A'+frame); - - sprtemp[frame].rotate = true; + if (init->sprtemp[frame].rotate == false) { + I_Error("R_InitSprites: Sprite %s frame %c has rotations " + "and a rot=0 lump", init->spritename, 'A' + frame); + } + + init->sprtemp[frame].rotate = true; // make 0 based - rotation--; - if (sprtemp[frame].lump[rotation] != -1) - I_Error ("R_InitSprites: Sprite %s : %c : %c " - "has two lumps mapped to it", - spritename, 'A'+frame, '1'+rotation); - - sprtemp[frame].lump[rotation] = lump - firstspritelump; - sprtemp[frame].flip[rotation] = (byte)flipped; + rotation--; + if (init->sprtemp[frame].lump[rotation] != -1) + I_Error("R_InitSprites: Sprite %s : %c : %c " + "has two lumps mapped to it", + init->spritename, 'A' + frame, '1' + rotation); + + init->sprtemp[frame].lump[rotation] = lump - firstspritelump; +#if !DOOM_SMALL + init->sprtemp[frame].flip[rotation] = (byte) flipped; +#else + if (flipped) init->sprtemp[frame].flips |= (1u << rotation); +#endif } - - // // R_InitSpriteDefs // Pass a null terminated list of sprite names @@ -168,164 +192,171 @@ R_InstallSpriteLump // letter/number appended. // The rotation character can be 0 to signify no rotations. // -void R_InitSpriteDefs(const char **namelist) -{ +void R_InitSpriteDefs(const char **namelist) { const char **check; - int i; - int l; - int frame; - int rotation; - int start; - int end; - int patched; - + int i; + int l; + int frame; + int rotation; + int start; + int end; + int patched; + + sprite_init_state_t init; + // count the number of sprite names check = namelist; while (*check != NULL) - check++; + check++; + + numsprites = check - namelist; - numsprites = check-namelist; - if (!numsprites) - return; - - sprites = Z_Malloc(numsprites *sizeof(*sprites), PU_STATIC, NULL); - - start = firstspritelump-1; - end = lastspritelump+1; - + return; + +// printf("SEG LOAD map %d segs x 0x%03x : size = %08x\n", numsegs, (int)sizeof(mapseg_t), numsegs*(int)sizeof(mapseg_t)); + printf("SPRITE LOAD alloc %d sprites x 0x%03x : size = %08x\n", numsprites, (int)sizeof(*sprites), numsprites*(int)sizeof(*sprites)); + sprites = Z_Malloc(numsprites * sizeof(*sprites), PU_STATIC, 0); + + start = firstspritelump - 1; + end = lastspritelump + 1; + + int totalframes=0; // scan all the lump names for each of the names, // noting the highest frame letter. // Just compare 4 characters as ints - for (i=0 ; iname, spritename, 4)) - { - frame = lumpinfo[l]->name[4] - 'A'; - rotation = lumpinfo[l]->name[5] - '0'; + for (i = 0; i < numsprites; i++) { + init.spritename = DEH_String(namelist[i]); + memset(init.sprtemp, -1, sizeof(init.sprtemp)); - if (modifiedgame) - patched = W_GetNumForName (lumpinfo[l]->name); - else - patched = l; + init.maxframe = -1; - R_InstallSpriteLump (patched, frame, rotation, false); +#if DOOM_SMALL + for(int j=0;j< count_of(init.sprtemp); j++) { + init.sprtemp[j].flips = 0; + } +#endif - if (lumpinfo[l]->name[6]) - { - frame = lumpinfo[l]->name[6] - 'A'; - rotation = lumpinfo[l]->name[7] - '0'; - R_InstallSpriteLump (l, frame, rotation, true); - } - } - } - - // check the frames that were found for completeness - if (maxframe == -1) - { - sprites[i].numframes = 0; - continue; - } - - maxframe++; - - for (frame = 0 ; frame < maxframe ; frame++) - { - switch ((int)sprtemp[frame].rotate) - { - case -1: - // no rotations were found for that frame at all - I_Error ("R_InitSprites: No patches found " - "for %s frame %c", spritename, frame+'A'); - break; - - case 0: - // only the first rotation is needed - break; - - case 1: - // must have all 8 frames - for (rotation=0 ; rotation<8 ; rotation++) - if (sprtemp[frame].lump[rotation] == -1) - I_Error ("R_InitSprites: Sprite %s frame %c " - "is missing rotations", - spritename, frame+'A'); - break; - } - } - - // allocate space for the frames present and copy sprtemp to it - sprites[i].numframes = maxframe; - sprites[i].spriteframes = - Z_Malloc (maxframe * sizeof(spriteframe_t), PU_STATIC, NULL); - memcpy (sprites[i].spriteframes, sprtemp, maxframe*sizeof(spriteframe_t)); + // scan the lumps, + // filling in the frames for whatever is found + for (l = start + 1; l < end; l++) { + should_be_const lumpinfo_t *info = lump_info(l); + if (!strncasecmp(info->name, init.spritename, 4)) { + frame = info->name[4] - 'A'; + rotation = info->name[5] - '0'; + + if (modifiedgame) + patched = W_GetNumForName(info->name); + else + patched = l; + + R_InstallSpriteLump(&init, patched, frame, rotation, false); + + if (info->name[6]) { + frame = info->name[6] - 'A'; + rotation = info->name[7] - '0'; + R_InstallSpriteLump(&init, l, frame, rotation, true); + } + } + } + + // check the frames that were found for completeness + if (init.maxframe == -1) { + sprites[i].numframes = 0; + continue; + } + + init.maxframe++; + + for (frame = 0; frame < init.maxframe; frame++) { + switch ((int) init.sprtemp[frame].rotate) { + case -1: + // no rotations were found for that frame at all + I_Error("R_InitSprites: No patches found " + "for %s frame %c", init.spritename, frame + 'A'); + break; + + case 0: + // only the first rotation is needed + break; + + case 1: + // must have all 8 frames + for (rotation = 0; rotation < 8; rotation++) + if (init.sprtemp[frame].lump[rotation] == -1) + I_Error("R_InitSprites: Sprite %s frame %c " + "is missing rotations", + init.spritename, frame + 'A'); + break; + } + } + + // allocate space for the frames present and copy init.sprtemp to it + sprites[i].numframes = init.maxframe; + sprites[i].spriteframes = + Z_Malloc(init.maxframe * sizeof(spriteframe_t), PU_STATIC, 0); + memcpy(sprites[i].spriteframes, init.sprtemp, init.maxframe * sizeof(spriteframe_t)); } - + printf("SPRITEFRAME LOAD alloc %d frames x 0x%03x : size = %08x\n", totalframes, (int)sizeof(spriteframe_t), totalframes*(int)sizeof(spriteframe_t)); } - - - +#endif // // GAME FUNCTIONS // -vissprite_t vissprites[MAXVISSPRITES]; -vissprite_t* vissprite_p; -int newvissprite; - +#if !NO_VISSPRITES +vissprite_t vissprites[MAXVISSPRITES]; +vissprite_t *vissprite_p; +int newvissprite; +#endif // // R_InitSprites // Called at program start. // -void R_InitSprites(const char **namelist) -{ - int i; - - for (i=0 ; itopdelta != 0xff ; ) - { - // calculate unclipped screen coordinates - // for post - topscreen = sprtopscreen + spryscale*column->topdelta; - bottomscreen = topscreen + spryscale*column->length; + for (; column->topdelta != 0xff;) { + // calculate unclipped screen coordinates + // for post + topscreen = sprtopscreen + spryscale * column->topdelta; + bottomscreen = topscreen + spryscale * column->length; - dc_yl = (topscreen+FRACUNIT-1)>>FRACBITS; - dc_yh = (bottomscreen-1)>>FRACBITS; - - if (dc_yh >= mfloorclip[dc_x]) - dc_yh = mfloorclip[dc_x]-1; - if (dc_yl <= mceilingclip[dc_x]) - dc_yl = mceilingclip[dc_x]+1; + dc_yl = (topscreen + FRACUNIT - 1) >> FRACBITS; + dc_yh = (bottomscreen - 1) >> FRACBITS; - if (dc_yl <= dc_yh) - { - dc_source = (byte *)column + 3; - dc_texturemid = basetexturemid - (column->topdelta<topdelta; +#if !NO_MASKED_FLOOR_CLIP + if (dc_yh >= mfloorclip[dc_x]) + dc_yh = mfloorclip[dc_x] - 1; + if (dc_yl <= mceilingclip[dc_x]) + dc_yl = mceilingclip[dc_x] + 1; +#else + if (dc_yl < 0) dc_yl = 0; + if (dc_yh >= viewheight) dc_yh = viewheight - 1; +#endif - // Drawn by either R_DrawColumn - // or (SHADOW) R_DrawFuzzColumn. - colfunc (); - } - column = (column_t *)( (byte *)column + column->length + 4); + if (dc_yl <= dc_yh) { + dc_source = (const byte *) column + 3; + dc_texturemid = basetexturemid - (column->topdelta << FRACBITS); + // dc_source = (byte *)column + 3 - column->topdelta; + + // Drawn by either R_DrawColumn + // or (SHADOW) R_DrawFuzzColumn. +#if PD_COLUMNS + pd_add_column(PDCOL_MASKED); +#endif +#if !NO_DRAW_MASKED + colfunc(); +#endif + } + column = (column_t *) ((byte *) column + column->length + 4); } - dc_texturemid = basetexturemid; -} +#else + if (column.real_id >= 0) { + panic_unsupported(); // handled earlier in r_segs. +// topscreen = sprtopscreen; +// bottomscreen = topscreen + spryscale * column.height; +// +// dc_yl = (topscreen + FRACUNIT - 1) >> FRACBITS; +// dc_yh = (bottomscreen - 1) >> FRACBITS; +// +// if (dc_yh >= mfloorclip[dc_x] - FLOOR_CEILING_CLIP_OFFSET) +// dc_yh = mfloorclip[dc_x] - FLOOR_CEILING_CLIP_OFFSET - 1; +// if (dc_yl <= mceilingclip[dc_x] - FLOOR_CEILING_CLIP_OFFSET) +// dc_yl = mceilingclip[dc_x] - FLOOR_CEILING_CLIP_OFFSET + 1; +// dc_source.tex = column.tex_or_patch; +// dc_source.col = column.col; +// // single masked column +// pd_add_column(PDCOL_MASKED); + } else { + patch_t *patch = W_CacheLumpNum(-(int)column.real_id, PU_CACHE); + assert(patch); + assert(column.col >=0 && column.col < patch_width(patch)); + dc_source = column; +#define MAX_SEGS 64 + uint8_t ysegs[MAX_SEGS*3]; + int seg_count = 0; + int height = patch_height(patch); + if (patch_fully_opaque(patch)) { + int yl, yh; + // calculate unclipped screen coordinates + // for post + topscreen = sprtopscreen; + bottomscreen = topscreen + spryscale * height; + yl = (topscreen + FRACUNIT - 1) >> FRACBITS; + yh = (bottomscreen - 1) >> FRACBITS; + +#if !NO_MASKED_FLOOR_CLIP + if (yh >= mfloorclip[dc_x]) + yh = mfloorclip[dc_x] - 1; + if (yl <= mceilingclip[dc_x]) + yl = mceilingclip[dc_x] + 1; +#else + if (yl < 0) yl = 0; + if (yh >= viewheight) yh = viewheight - 1; +#endif + + if (yl <= yh) { + ysegs[0] = yl; + ysegs[1] = yh; + ysegs[2] = 0; + seg_count++; + } + } else { + int yl, yh; + int last = -1; + th_backwards_bit_input rbi; + uint data_index = 3 + patch_has_extra(patch); + data_index += ((uint8_t*)patch)[data_index*2]; // skip over decoder metadata + uint16_t *col_offsets = &((uint16_t*)patch)[data_index]; + uint16_t col_offset = col_offsets[column.col]; + uint next_column; + if (0xff == (col_offset >> 8)) { + next_column = (col_offset & 0xff); + assert(next_column < patch_width(patch)); + next_column++; + } else { + next_column = column.col + 1; + } + col_offset = col_offsets[next_column]; // we work backwards from the next column + // we have to skip over any columns which aren't stored + while (0xff == (col_offset >> 8)) { + assert(next_column < patch_width(patch)); // note < and ++ afterwards; we are allowed to read one beyond width which is the "end" marker column + col_offset = col_offsets[++next_column]; + } + data_index = (data_index + patch_width(patch)) * 2 + 2; // + 2 because we have one extra col_data offset + if (patch_byte_addressed(patch)) { + th_backwards_bit_input_init(&rbi, patch + data_index + col_offset); // todo read off end potential + } else { + th_backwards_bit_input_init_bit_offset(&rbi, patch + data_index, col_offset); // todo read off end potential + } + int prev = 0; + int base_offset = 0; + do { + int top = prev + th_read_backwards_bits(&rbi, bitcount8(height - prev)); + if (top == height) break; + if (top > height) { + assert(false); + } + base_offset += (top - prev); + int size = th_read_backwards_bits(&rbi, bitcount8(height - top)); + assert(size >= 1); + prev = top + size; + + // calculate unclipped screen coordinates + // for post + topscreen = sprtopscreen + spryscale * top; + bottomscreen = topscreen + spryscale * size; + + yl = (topscreen + FRACUNIT - 1) >> FRACBITS; + yh = (bottomscreen - 1) >> FRACBITS; + + #if !NO_MASKED_FLOOR_CLIP + if (yh >= mfloorclip[dc_x]) + yh = mfloorclip[dc_x] - 1; + if (yl <= mceilingclip[dc_x]) + yl = mceilingclip[dc_x] + 1; + #else + if (yl < 0) yl = 0; + if (yh >= viewheight) yh = viewheight - 1; + #endif + + if (yl <= yh) { + + if (yl > last) { + ysegs[seg_count*3] = yl; + ysegs[seg_count*3+1] = yh; + assert(base_offset < 256); + ysegs[seg_count*3+2] = base_offset; + seg_count++; + } + last = yh; + } + } while (true); + } + assert(seg_count < MAX_SEGS); + if (seg_count) + pd_add_masked_columns(ysegs, seg_count); + } +#endif +} // @@ -387,596 +551,624 @@ void R_DrawMaskedColumn (column_t* column) // void R_DrawVisSprite -( vissprite_t* vis, - int x1, - int x2 ) -{ - column_t* column; - int texturecolumn; - fixed_t frac; - patch_t* patch; - - - patch = W_CacheLumpNum (vis->patch+firstspritelump, PU_CACHE); + (vissprite_t *vis, + int x1, + int x2) { + int texturecolumn; + fixed_t frac; + should_be_const patch_t *patch; + +#if !USE_WHD + patch = W_CacheLumpNum(vis->patch + firstspritelump, PU_CACHE); +#else + patch = NULL; +#endif + + byte fuzz; +#if !USE_LIGHTMAP_INDEXES dc_colormap = vis->colormap; - - if (!dc_colormap) - { - // NULL colormap = shadow draw - colfunc = fuzzcolfunc; + fuzz = !dc_colormap; +#else + fuzz = vis->colormap < 0; +#if !NO_USE_DC_COLORMAP + dc_colormap = vis->colormap < 0 ? NULL : colormaps + vis->colormap * 256; +#else + dc_colormap_index = vis->colormap; +#endif +#endif + +#if DOOM_TINY + dc_translation_index = 0; +#endif + if (fuzz) { + // NULL colormap = shadow draw +#if !PD_COLUMNS + colfunc = fuzzcolfunc; +#endif + } else if (vis->mobjflags & MF_TRANSLATION) { +#if !PD_COLUMNS + colfunc = transcolfunc; +#endif +#if !DOOM_TINY + dc_translation = translationtables - 256 + + ((vis->mobjflags & MF_TRANSLATION) >> (MF_TRANSSHIFT - 8)); +#else + dc_translation_index = (vis->mobjflags & MF_TRANSLATION) >> MF_TRANSSHIFT; +#endif } - else if (vis->mobjflags & MF_TRANSLATION) - { - colfunc = transcolfunc; - dc_translation = translationtables - 256 + - ( (vis->mobjflags & MF_TRANSLATION) >> (MF_TRANSSHIFT-8) ); - } - - dc_iscale = abs(vis->xiscale)>>detailshift; + + dc_iscale = abs(vis->xiscale) >> detailshift; dc_texturemid = vis->texturemid; frac = vis->startfrac; spryscale = vis->scale; - sprtopscreen = centeryfrac - FixedMul(dc_texturemid,spryscale); - - for (dc_x=vis->x1 ; dc_x<=vis->x2 ; dc_x++, frac += vis->xiscale) - { - texturecolumn = frac>>FRACBITS; -#ifdef RANGECHECK - if (texturecolumn < 0 || texturecolumn >= SHORT(patch->width)) - I_Error ("R_DrawSpriteRange: bad texturecolumn"); + sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); + +#if PD_SCALE_SORT + pd_scale = spryscale; #endif - column = (column_t *) ((byte *)patch + - LONG(patch->columnofs[texturecolumn])); - R_DrawMaskedColumn (column); + +#if USE_WHD + framedrawable_t *fd = lookup_patch(vis->patch); +#endif + for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, frac += vis->xiscale) { + texturecolumn = frac >> FRACBITS; +#ifdef RANGECHECK + if (patch && (texturecolumn < 0 || texturecolumn >= patch_width(patch))) + I_Error("R_DrawSpriteRange: bad texturecolumn"); +#endif +#if !USE_WHD + maskedcolumn_t column = R_GetPatchColumn(patch, texturecolumn); +#else + maskedcolumn_t column = R_GetPatchColumn(fd, texturecolumn); +#endif + R_DrawMaskedColumn(column); } +#if !PD_COLUMNS colfunc = basecolfunc; +#endif } - // // R_ProjectSprite // Generates a vissprite for a thing // if it might be visible. // -void R_ProjectSprite (mobj_t* thing) -{ - fixed_t tr_x; - fixed_t tr_y; - - fixed_t gxt; - fixed_t gyt; - - fixed_t tx; - fixed_t tz; +void R_ProjectSprite(mobj_t *thing) { + fixed_t tr_x; + fixed_t tr_y; - fixed_t xscale; - - int x1; - int x2; + fixed_t gxt; + fixed_t gyt; - spritedef_t* sprdef; - spriteframe_t* sprframe; - int lump; - - unsigned rot; - boolean flip; - - int index; + fixed_t tx; + fixed_t tz; + + fixed_t xscale; + + int x1; + int x2; + + spriteframeref_t sprframe; + int lump; + + unsigned rot; + boolean flip; + + int index; + + vissprite_t *vis; + + angle_t ang; + fixed_t iscale; - vissprite_t* vis; - - angle_t ang; - fixed_t iscale; - // transform the origin point - tr_x = thing->x - viewx; - tr_y = thing->y - viewy; - - gxt = FixedMul(tr_x,viewcos); - gyt = -FixedMul(tr_y,viewsin); - - tz = gxt-gyt; + tr_x = thing->xy.x - viewx; + tr_y = thing->xy.y - viewy; + + gxt = FixedMul(tr_x, viewcos); + gyt = -FixedMul(tr_y, viewsin); + + tz = gxt - gyt; // thing is behind view plane? if (tz < MINZ) - return; - + return; + xscale = FixedDiv(projection, tz); - - gxt = -FixedMul(tr_x,viewsin); - gyt = FixedMul(tr_y,viewcos); - tx = -(gyt+gxt); + + gxt = -FixedMul(tr_x, viewsin); + gyt = FixedMul(tr_y, viewcos); + tx = -(gyt + gxt); // too far off the side? - if (abs(tx)>(tz<<2)) - return; - + if (abs(tx) > (tz << 2)) + return; + // decide which patch to use for sprite relative to player #ifdef RANGECHECK - if ((unsigned int) thing->sprite >= (unsigned int) numsprites) - I_Error ("R_ProjectSprite: invalid sprite number %i ", - thing->sprite); + if ((unsigned int) mobj_sprite(thing) >= (unsigned int) numsprites) + I_Error("R_ProjectSprite: invalid sprite number %i ", + mobj_sprite(thing)); #endif - sprdef = &sprites[thing->sprite]; -#ifdef RANGECHECK - if ( (thing->frame&FF_FRAMEMASK) >= sprdef->numframes ) - I_Error ("R_ProjectSprite: invalid sprite frame %i : %i ", - thing->sprite, thing->frame); -#endif - sprframe = &sprdef->spriteframes[ thing->frame & FF_FRAMEMASK]; - if (sprframe->rotate) - { - // choose a different rotation based on player view - ang = R_PointToAngle (thing->x, thing->y); - rot = (ang-thing->angle+(unsigned)(ANG45/2)*9)>>29; - lump = sprframe->lump[rot]; - flip = (boolean)sprframe->flip[rot]; +#ifdef RANGECHECK + if ((mobj_frame(thing) & FF_FRAMEMASK) >= sprite_numframes(mobj_sprite(thing))) + I_Error("R_ProjectSprite: invalid sprite frame %i : %i ", + mobj_sprite(thing), mobj_frame(thing)); +#endif + sprframe = sprite_frame(mobj_sprite(thing), mobj_frame(thing) & FF_FRAMEMASK); + + if (spriteframe_rotates(sprframe)) { + // choose a different rotation based on player view + ang = R_PointToAngle(thing->xy.x, thing->xy.y); + rot = (ang - mobj_full(thing)->angle + (unsigned) (ANG45 / 2) * 9) >> 29; + lump = spriteframe_rotated_pic(sprframe, rot); + flip = (boolean) spriteframe_rotated_flipped(sprframe, rot); + } else { + // use single rotation for all views + lump = spriteframe_unrotated_pic(sprframe); + flip = (boolean) spriteframe_unrotated_flipped(sprframe); } - else - { - // use single rotation for all views - lump = sprframe->lump[0]; - flip = (boolean)sprframe->flip[0]; - } - + // calculate edges of the shape - tx -= spriteoffset[lump]; - x1 = (centerxfrac + FixedMul (tx,xscale) ) >>FRACBITS; + tx -= sprite_offset(lump); + x1 = (centerxfrac + FixedMul(tx, xscale)) >> FRACBITS; // off the right side? if (x1 > viewwidth) - return; - - tx += spritewidth[lump]; - x2 = ((centerxfrac + FixedMul (tx,xscale) ) >>FRACBITS) - 1; + return; + + tx += sprite_width(lump); + x2 = ((centerxfrac + FixedMul(tx, xscale)) >> FRACBITS) - 1; // off the left side if (x2 < 0) - return; - - // store information in a vissprite - vis = R_NewVisSprite (); - vis->mobjflags = thing->flags; - vis->scale = xscale<gx = thing->x; - vis->gy = thing->y; - vis->gz = thing->z; - vis->gzt = thing->z + spritetopoffset[lump]; - vis->texturemid = vis->gzt - viewz; - vis->x1 = x1 < 0 ? 0 : x1; - vis->x2 = x2 >= viewwidth ? viewwidth-1 : x2; - iscale = FixedDiv (FRACUNIT, xscale); + return; - if (flip) - { - vis->startfrac = spritewidth[lump]-1; - vis->xiscale = -iscale; - } - else - { - vis->startfrac = 0; - vis->xiscale = iscale; + // store information in a vissprite +#if NO_VISSPRITES + vissprite_t vis_a; + vis = &vis_a; +#else + vis = R_NewVisSprite(); +#endif + vis->mobjflags = thing->flags; + vis->scale = xscale << detailshift; +#if !DOOM_TINY + vis->gx = thing->xy.x; + vis->gy = thing->xy.y; + vis->gz = thing->z; + vis->gzt = thing->z + sprite_topoffset(lump); + vis->texturemid = vis->gzt - viewz; +#else + vis->texturemid = thing->z + sprite_topoffset(lump) - viewz; +#endif + vis->x1 = x1 < 0 ? 0 : x1; + vis->x2 = x2 >= viewwidth ? viewwidth - 1 : x2; + iscale = FixedDiv(FRACUNIT, xscale); + + if (flip) { + vis->startfrac = sprite_width(lump) - 1; + vis->xiscale = -iscale; + } else { + vis->startfrac = 0; + vis->xiscale = iscale; } if (vis->x1 > x1) - vis->startfrac += vis->xiscale*(vis->x1-x1); + vis->startfrac += vis->xiscale * (vis->x1 - x1); vis->patch = lump; - + // get light level - if (thing->flags & MF_SHADOW) - { - // shadow draw - vis->colormap = NULL; - } - else if (fixedcolormap) - { - // fixed map - vis->colormap = fixedcolormap; - } - else if (thing->frame & FF_FULLBRIGHT) - { - // full bright - vis->colormap = colormaps; - } - - else - { - // diminished light - index = xscale>>(LIGHTSCALESHIFT-detailshift); + if (thing->flags & MF_SHADOW) { + // shadow draw +#if !USE_LIGHTMAP_INDEXES + vis->colormap = NULL; +#else + vis->colormap = -1; +#endif + } else if (fixedcolormap) { + // fixed map + vis->colormap = fixedcolormap; + } else if (mobj_frame(thing) & FF_FULLBRIGHT) { + // full bright +#if !USE_LIGHTMAP_INDEXES + vis->colormap = colormaps; +#else + vis->colormap = 0; +#endif + } else { + // diminished light + index = xscale >> (LIGHTSCALESHIFT - detailshift); - if (index >= MAXLIGHTSCALE) - index = MAXLIGHTSCALE-1; + if (index >= MAXLIGHTSCALE) + index = MAXLIGHTSCALE - 1; - vis->colormap = spritelights[index]; - } + vis->colormap = spritelights[index]; + } + R_DrawSpriteEarly(vis); } - - // // R_AddSprites // During BSP traversal, this adds sprites by sector. // -void R_AddSprites (sector_t* sec) -{ - mobj_t* thing; - int lightnum; +void R_AddSprites(sector_t *sec) { + mobj_t *thing; + int lightnum; +#if !SPRITES_IN_SUBSECTORS // BSP is traversed by subsector. // A sector might have been split into several // subsectors during BSP building. // Thus we check whether its already added. - if (sec->validcount == validcount) - return; + if (sector_validcount_update_check(sec, validcount)) return; +#endif - // Well, now it will be done. - sec->validcount = validcount; - - lightnum = (sec->lightlevel >> LIGHTSEGSHIFT)+extralight; + lightnum = (sec->lightlevel >> LIGHTSEGSHIFT) + extralight; - if (lightnum < 0) - spritelights = scalelight[0]; + if (lightnum < 0) + spritelights = scalelight[0]; else if (lightnum >= LIGHTLEVELS) - spritelights = scalelight[LIGHTLEVELS-1]; + spritelights = scalelight[LIGHTLEVELS - 1]; else - spritelights = scalelight[lightnum]; + spritelights = scalelight[lightnum]; // Handle all things in sector. - for (thing = sec->thinglist ; thing ; thing = thing->snext) - R_ProjectSprite (thing); + for (thing = shortptr_to_mobj(sec->thinglist); thing; thing = mobj_snext(thing)) + R_ProjectSprite(thing); } // // R_DrawPSprite // -void R_DrawPSprite (pspdef_t* psp) -{ - fixed_t tx; - int x1; - int x2; - spritedef_t* sprdef; - spriteframe_t* sprframe; - int lump; - boolean flip; - vissprite_t* vis; - vissprite_t avis; - +void R_DrawPSprite(pspdef_t *psp) { + fixed_t tx; + int x1; + int x2; + spriteframeref_t sprframe; + int lump; + boolean flip; + vissprite_t *vis; + vissprite_t avis; + // decide which patch to use #ifdef RANGECHECK - if ( (unsigned)psp->state->sprite >= (unsigned int) numsprites) - I_Error ("R_ProjectSprite: invalid sprite number %i ", - psp->state->sprite); + if ((unsigned) psp->state->sprite >= (unsigned int) numsprites) + I_Error("R_ProjectSprite: invalid sprite number %i ", + psp->state->sprite); #endif - sprdef = &sprites[psp->state->sprite]; #ifdef RANGECHECK - if ( (psp->state->frame & FF_FRAMEMASK) >= sprdef->numframes) - I_Error ("R_ProjectSprite: invalid sprite frame %i : %i ", - psp->state->sprite, psp->state->frame); + if ((psp->state->frame & FF_FRAMEMASK) >= sprite_numframes(psp->state->sprite)) + I_Error("R_ProjectSprite: invalid sprite frame %i : %i ", + psp->state->sprite, psp->state->frame); #endif - sprframe = &sprdef->spriteframes[ psp->state->frame & FF_FRAMEMASK ]; + sprframe = sprite_frame(psp->state->sprite, psp->state->frame & FF_FRAMEMASK); + lump = spriteframe_unrotated_pic(sprframe); + flip = (boolean) spriteframe_unrotated_flipped(sprframe); - lump = sprframe->lump[0]; - flip = (boolean)sprframe->flip[0]; - // calculate edges of the shape - tx = psp->sx-(SCREENWIDTH/2)*FRACUNIT; - - tx -= spriteoffset[lump]; - x1 = (centerxfrac + FixedMul (tx,pspritescale) ) >>FRACBITS; + tx = psp->sx - (SCREENWIDTH / 2) * FRACUNIT; + + tx -= sprite_offset(lump); + x1 = (centerxfrac + FixedMul(tx, pspritescale)) >> FRACBITS; // off the right side if (x1 > viewwidth) - return; + return; - tx += spritewidth[lump]; - x2 = ((centerxfrac + FixedMul (tx, pspritescale) ) >>FRACBITS) - 1; + tx += sprite_width(lump); + x2 = ((centerxfrac + FixedMul(tx, pspritescale)) >> FRACBITS) - 1; // off the left side if (x2 < 0) - return; - + return; + // store information in a vissprite vis = &avis; vis->mobjflags = 0; - vis->texturemid = (BASEYCENTER<sy-spritetopoffset[lump]); + vis->texturemid = (BASEYCENTER << FRACBITS) + FRACUNIT / 2 - (psp->sy - sprite_topoffset(lump)); vis->x1 = x1 < 0 ? 0 : x1; - vis->x2 = x2 >= viewwidth ? viewwidth-1 : x2; - vis->scale = pspritescale<xiscale = -pspriteiscale; - vis->startfrac = spritewidth[lump]-1; + vis->x2 = x2 >= viewwidth ? viewwidth - 1 : x2; + vis->scale = pspritescale << detailshift; + + if (flip) { + vis->xiscale = -pspriteiscale; + vis->startfrac = sprite_width(lump) - 1; + } else { + vis->xiscale = pspriteiscale; + vis->startfrac = 0; } - else - { - vis->xiscale = pspriteiscale; - vis->startfrac = 0; - } - + if (vis->x1 > x1) - vis->startfrac += vis->xiscale*(vis->x1-x1); + vis->startfrac += vis->xiscale * (vis->x1 - x1); vis->patch = lump; - if (viewplayer->powers[pw_invisibility] > 4*32 - || viewplayer->powers[pw_invisibility] & 8) - { - // shadow draw - vis->colormap = NULL; + if (viewplayer->powers[pw_invisibility] > 4 * 32 + || viewplayer->powers[pw_invisibility] & 8) { + // shadow draw +#if !USE_LIGHTMAP_INDEXES + vis->colormap = NULL; +#else + vis->colormap = -1; +#endif + } else if (fixedcolormap) { + // fixed color + vis->colormap = fixedcolormap; + } else if (psp->state->frame & FF_FULLBRIGHT) { + // full bright +#if !USE_LIGHTMAP_INDEXES + vis->colormap = colormaps; +#else + vis->colormap = 0; +#endif + } else { + // local light + vis->colormap = spritelights[MAXLIGHTSCALE - 1]; } - else if (fixedcolormap) - { - // fixed color - vis->colormap = fixedcolormap; - } - else if (psp->state->frame & FF_FULLBRIGHT) - { - // full bright - vis->colormap = colormaps; - } - else - { - // local light - vis->colormap = spritelights[MAXLIGHTSCALE-1]; - } - - R_DrawVisSprite (vis, vis->x1, vis->x2); -} +#if PICO_DOOM + pd_flag |= 2; +#endif + R_DrawVisSprite(vis, vis->x1, vis->x2); +#if PICO_DOOM + pd_flag &= ~2; +#endif +} // // R_DrawPlayerSprites // -void R_DrawPlayerSprites (void) -{ - int i; - int lightnum; - pspdef_t* psp; - +void R_DrawPlayerSprites(void) { + int i; + int lightnum; + pspdef_t *psp; + // get light level lightnum = - (viewplayer->mo->subsector->sector->lightlevel >> LIGHTSEGSHIFT) - +extralight; + (mobj_sector(viewplayer->mo)->lightlevel >> LIGHTSEGSHIFT) + + extralight; - if (lightnum < 0) - spritelights = scalelight[0]; + if (lightnum < 0) + spritelights = scalelight[0]; else if (lightnum >= LIGHTLEVELS) - spritelights = scalelight[LIGHTLEVELS-1]; + spritelights = scalelight[LIGHTLEVELS - 1]; else - spritelights = scalelight[lightnum]; - + spritelights = scalelight[lightnum]; + // clip to screen bounds - mfloorclip = screenheightarray; - mceilingclip = negonearray; - + mfloorclip = maxfloorceilingcliparray; + mceilingclip = minfloorceilingcliparray; + // add all active psprites - for (i=0, psp=viewplayer->psprites; - istate) - R_DrawPSprite (psp); + for (i = 0, psp = viewplayer->psprites; + i < NUMPSPRITES; + i++, psp++) { + if (psp->state) + R_DrawPSprite(psp); } } - - // // R_SortVisSprites // -vissprite_t vsprsortedhead; +vissprite_t vsprsortedhead; -void R_SortVisSprites (void) -{ - int i; - int count; - vissprite_t* ds; - vissprite_t* best; - vissprite_t unsorted; - fixed_t bestscale; +#if !NO_VISSPRITES +void R_SortVisSprites(void) { + int i; + int count; + vissprite_t *ds; + vissprite_t *best; + vissprite_t unsorted; + fixed_t bestscale; count = vissprite_p - vissprites; - + unsorted.next = unsorted.prev = &unsorted; if (!count) - return; - - for (ds=vissprites ; dsnext = ds+1; - ds->prev = ds-1; + return; + + for (ds = vissprites; ds < vissprite_p; ds++) { + ds->next = ds + 1; + ds->prev = ds - 1; } - + vissprites[0].prev = &unsorted; unsorted.next = &vissprites[0]; - (vissprite_p-1)->next = &unsorted; - unsorted.prev = vissprite_p-1; - + (vissprite_p - 1)->next = &unsorted; + unsorted.prev = vissprite_p - 1; + // pull the vissprites out by scale vsprsortedhead.next = vsprsortedhead.prev = &vsprsortedhead; - for (i=0 ; inext) - { - if (ds->scale < bestscale) - { - bestscale = ds->scale; - best = ds; - } - } - best->next->prev = best->prev; - best->prev->next = best->next; - best->next = &vsprsortedhead; - best->prev = vsprsortedhead.prev; - vsprsortedhead.prev->next = best; - vsprsortedhead.prev = best; + for (ds = unsorted.next; ds != &unsorted; ds = ds->next) { + if (ds->scale < bestscale) { + bestscale = ds->scale; + best = ds; + } + } + best->next->prev = best->prev; + best->prev->next = best->next; + best->next = &vsprsortedhead; + best->prev = vsprsortedhead.prev; + vsprsortedhead.prev->next = best; + vsprsortedhead.prev = best; } } - - +#endif // // R_DrawSprite // -void R_DrawSprite (vissprite_t* spr) -{ - drawseg_t* ds; - short clipbot[SCREENWIDTH]; - short cliptop[SCREENWIDTH]; - int x; - int r1; - int r2; - fixed_t scale; - fixed_t lowscale; - int silhouette; - - for (x = spr->x1 ; x<=spr->x2 ; x++) - clipbot[x] = cliptop[x] = -2; - +void R_DrawSprite(vissprite_t *spr) { +#if !NO_DRAWSEGS + short clipbot[SCREENWIDTH]; + short cliptop[SCREENWIDTH]; + drawseg_t *ds; + int x; + int r1; + int r2; + fixed_t scale; + fixed_t lowscale; + int silhouette; + + for (x = spr->x1; x <= spr->x2; x++) + clipbot[x] = cliptop[x] = -2; + // Scan drawsegs from end to start for obscuring segs. // The first drawseg that has a greater scale // is the clip seg. - for (ds=ds_p-1 ; ds >= drawsegs ; ds--) - { - // determine if the drawseg obscures the sprite - if (ds->x1 > spr->x2 - || ds->x2 < spr->x1 - || (!ds->silhouette - && !ds->maskedtexturecol) ) - { - // does not cover sprite - continue; - } - - r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1; - r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2; + for (ds = ds_p - 1; ds >= drawsegs; ds--) { + // determine if the drawseg obscures the sprite + if (ds->x1 > spr->x2 + || ds->x2 < spr->x1 + || (!ds->silhouette + && !ds->maskedtexturecol)) { + // does not cover sprite + continue; + } - if (ds->scale1 > ds->scale2) - { - lowscale = ds->scale2; - scale = ds->scale1; - } - else - { - lowscale = ds->scale1; - scale = ds->scale2; - } - - if (scale < spr->scale - || ( lowscale < spr->scale - && !R_PointOnSegSide (spr->gx, spr->gy, ds->curline) ) ) - { - // masked mid texture? - if (ds->maskedtexturecol) - R_RenderMaskedSegRange (ds, r1, r2); - // seg is behind sprite - continue; - } + r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1; + r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2; - - // clip this piece of the sprite - silhouette = ds->silhouette; - - if (spr->gz >= ds->bsilheight) - silhouette &= ~SIL_BOTTOM; + if (ds->scale1 > ds->scale2) { + lowscale = ds->scale2; + scale = ds->scale1; + } else { + lowscale = ds->scale1; + scale = ds->scale2; + } + + if (scale < spr->scale + || (lowscale < spr->scale + && !R_PointOnSegSide(spr->gx, spr->gy, ds->curline))) { + // masked mid texture? +#if !PD_SCALE_SORT + if (ds->maskedtexturecol) + R_RenderMaskedSegRange(ds, r1, r2); +#endif + // seg is behind sprite + continue; + } + + + // clip this piece of the sprite + silhouette = ds->silhouette; + + if (spr->gz >= ds->bsilheight) + silhouette &= ~SIL_BOTTOM; + + if (spr->gzt <= ds->tsilheight) + silhouette &= ~SIL_TOP; + + if (silhouette == 1) { + // bottom sil + for (x = r1; x <= r2; x++) + if (clipbot[x] == -2) + clipbot[x] = ds->sprbottomclip[x]; + } else if (silhouette == 2) { + // top sil + for (x = r1; x <= r2; x++) + if (cliptop[x] == -2) + cliptop[x] = ds->sprtopclip[x]; + } else if (silhouette == 3) { + // both + for (x = r1; x <= r2; x++) { + if (clipbot[x] == -2) + clipbot[x] = ds->sprbottomclip[x]; + if (cliptop[x] == -2) + cliptop[x] = ds->sprtopclip[x]; + } + } - if (spr->gzt <= ds->tsilheight) - silhouette &= ~SIL_TOP; - - if (silhouette == 1) - { - // bottom sil - for (x=r1 ; x<=r2 ; x++) - if (clipbot[x] == -2) - clipbot[x] = ds->sprbottomclip[x]; - } - else if (silhouette == 2) - { - // top sil - for (x=r1 ; x<=r2 ; x++) - if (cliptop[x] == -2) - cliptop[x] = ds->sprtopclip[x]; - } - else if (silhouette == 3) - { - // both - for (x=r1 ; x<=r2 ; x++) - { - if (clipbot[x] == -2) - clipbot[x] = ds->sprbottomclip[x]; - if (cliptop[x] == -2) - cliptop[x] = ds->sprtopclip[x]; - } - } - } - + // all clipping has been performed, so draw the sprite // check for unclipped columns - for (x = spr->x1 ; x<=spr->x2 ; x++) - { - if (clipbot[x] == -2) - clipbot[x] = viewheight; + for (x = spr->x1; x <= spr->x2; x++) { + if (clipbot[x] == -2) + clipbot[x] = viewheight; - if (cliptop[x] == -2) - cliptop[x] = -1; + if (cliptop[x] == -2) + cliptop[x] = -1; } - + mfloorclip = clipbot; mceilingclip = cliptop; - R_DrawVisSprite (spr, spr->x1, spr->x2); +#else + mfloorclip = maxfloorceilingcliparray; + mceilingclip = minfloorceilingcliparray; +#endif + R_DrawVisSprite(spr, spr->x1, spr->x2); } - - // // R_DrawMasked // -void R_DrawMasked (void) -{ - vissprite_t* spr; - drawseg_t* ds; - - R_SortVisSprites (); +void R_DrawMasked(void) { - if (vissprite_p > vissprites) - { - // draw all vissprites back to front - for (spr = vsprsortedhead.next ; - spr != &vsprsortedhead ; - spr=spr->next) - { - - R_DrawSprite (spr); - } + if (!no_draw_sprites) { +#if !NO_VISSPRITES + vissprite_t *spr; + R_SortVisSprites(); + if (vissprite_p > vissprites) { + // draw all vissprites back to front + for (spr = vsprsortedhead.next; + spr != &vsprsortedhead; + spr = spr->next) { + + R_DrawSprite(spr); + } + } +#endif } - + + // todo graham - need to gather the masked seg ranges +#if !NO_DRAWSEGS + drawseg_t *ds; // render any remaining masked mid textures - for (ds=ds_p-1 ; ds >= drawsegs ; ds--) - if (ds->maskedtexturecol) - R_RenderMaskedSegRange (ds, ds->x1, ds->x2); - + for (ds = ds_p - 1; ds >= drawsegs; ds--) + if (ds->maskedtexturecol) + R_RenderMaskedSegRange(ds, ds->x1, ds->x2); +#endif + +#if !PICODOOM_RENDER_NEWHOPE // draw the psprites on top of everything // but does not draw on side views - if (!viewangleoffset) - R_DrawPlayerSprites (); + if (!viewangleoffset) + R_DrawPlayerSprites(); +#endif } +// +// R_DrawSprite +// +void R_DrawSpriteEarly(vissprite_t *spr) { +#if PICO_DOOM + pd_flag |= 1; +#if !NO_MASKED_FLOOR_CLIP + mfloorclip = floorclip; + mceilingclip = ceilingclip; +#endif + R_DrawVisSprite(spr, spr->x1, spr->x2); + pd_flag &= ~1; +#endif +} diff --git a/src/doom/r_things.h b/src/doom/r_things.h index fc51c34a..5d220528 100644 --- a/src/doom/r_things.h +++ b/src/doom/r_things.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -21,21 +22,24 @@ #define __R_THINGS__ - +#if !NO_VISSPRITES #define MAXVISSPRITES 128 extern vissprite_t vissprites[MAXVISSPRITES]; extern vissprite_t* vissprite_p; extern vissprite_t vsprsortedhead; +#endif // Constant arrays used for psprite clipping // and initializing clipping. -extern short negonearray[SCREENWIDTH]; -extern short screenheightarray[SCREENWIDTH]; +// todo graham remove these // vars for R_DrawMaskedColumn -extern short* mfloorclip; -extern short* mceilingclip; +extern floor_ceiling_clip_t minfloorceilingcliparray[SCREENWIDTH]; +extern floor_ceiling_clip_t maxfloorceilingcliparray[SCREENWIDTH]; +extern floor_ceiling_clip_t* mfloorclip; +extern floor_ceiling_clip_t* mceilingclip; + extern fixed_t spryscale; extern fixed_t sprtopscreen; @@ -43,7 +47,8 @@ extern fixed_t pspritescale; extern fixed_t pspriteiscale; -void R_DrawMaskedColumn (column_t* column); +void R_DrawMaskedColumn (maskedcolumn_t column); + void R_SortVisSprites (void); @@ -51,9 +56,16 @@ void R_SortVisSprites (void); void R_AddSprites (sector_t* sec); void R_AddPSprites (void); void R_DrawSprites (void); -void R_InitSprites(const char **namelist); +void R_DrawSpriteEarly (vissprite_t* spr); +void R_InitSprites(); void R_ClearSprites (void); void R_DrawMasked (void); +#if DOOM_TINY +void R_DrawVisSprite + (vissprite_t *vis, + int x1, + int x2); +#endif void R_ClipVisSprite diff --git a/src/doom/s_sound.c b/src/doom/s_sound.c index 9af5e07e..0779a1d4 100644 --- a/src/doom/s_sound.c +++ b/src/doom/s_sound.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -37,6 +38,11 @@ #include "w_wad.h" #include "z_zone.h" +#if INCLUDE_SOUND_C_IN_S_SOUND +#undef INCLUDE_SOUND_C_IN_S_SOUND +#include "sounds.c" +#endif + // when to clip out sounds // Does not fit the large outdoor areas. @@ -61,13 +67,14 @@ #define NORM_PRIORITY 64 #define NORM_SEP 128 +// todo graham this could be half the size or so, there are 8 of them typedef struct { // sound information (if null, channel avail.) - sfxinfo_t *sfxinfo; + should_be_const sfxinfo_t *sfxinfo; // origin of sound - mobj_t *origin; + xy_positioned_t *origin; // handle of the sound being played int handle; @@ -153,7 +160,11 @@ void S_Init(int sfxVolume, int musicVolume) // Note that sounds have not been cached (yet). for (i=1 ; isfxinfo->usefulness--; + sfx_mut(c->sfxinfo)->usefulness--; c->sfxinfo = NULL; c->origin = NULL; } @@ -263,7 +278,7 @@ void S_Start(void) S_ChangeMusic(mnum, true); } -void S_StopSound(mobj_t *origin) +void S_StopSound(xy_positioned_t *origin) { int cnum; @@ -282,7 +297,7 @@ void S_StopSound(mobj_t *origin) // If none available, return -1. Otherwise channel #. // -static int S_GetChannel(mobj_t *origin, sfxinfo_t *sfxinfo) +static int S_GetChannel(xy_positioned_t *origin, should_be_const sfxinfo_t *sfxinfo) { // channel number to use int cnum; @@ -343,7 +358,7 @@ static int S_GetChannel(mobj_t *origin, sfxinfo_t *sfxinfo) // Otherwise, modifies parameters and returns 1. // -static int S_AdjustSoundParams(mobj_t *listener, mobj_t *source, +static int S_AdjustSoundParams(mobj_t *listener, xy_positioned_t *source, int *vol, int *sep) { fixed_t approx_dist; @@ -353,8 +368,8 @@ static int S_AdjustSoundParams(mobj_t *listener, mobj_t *source, // calculate the distance to sound origin // and clip it if necessary - adx = abs(listener->x - source->x); - ady = abs(listener->y - source->y); + adx = abs(listener->xy.x - source->x); + ady = abs(listener->xy.y - source->y); // From _GG1_ p.428. Appox. eucledian distance fast. approx_dist = adx + ady - ((adx < ady ? adx : ady)>>1); @@ -365,24 +380,24 @@ static int S_AdjustSoundParams(mobj_t *listener, mobj_t *source, } // angle of source to listener - angle = R_PointToAngle2(listener->x, - listener->y, + angle = R_PointToAngle2(listener->xy.x, + listener->xy.y, source->x, source->y); - if (angle > listener->angle) + if (angle > mobj_full(listener)->angle) { - angle = angle - listener->angle; + angle = angle - mobj_full(listener)->angle; } else { - angle = angle + (0xffffffff - listener->angle); + angle = angle + (0xffffffff - mobj_full(listener)->angle); } angle >>= ANGLETOFINESHIFT; // stereo separation - *sep = 128 - (FixedMul(S_STEREO_SWING, finesine[angle]) >> FRACBITS); + *sep = 128 - (FixedMul(S_STEREO_SWING, finesine(angle)) >> FRACBITS); // volume calculation if (approx_dist < S_CLOSE_DIST) @@ -426,17 +441,27 @@ static int Clamp(int x) return x; } -void S_StartSound(void *origin_p, int sfx_id) +void S_StartObjSound(mobj_t *obj, int sfx_id) { + S_StartSound(obj ? &obj->xy : NULL, sfx_id); +} + +void S_StopObjSound(mobj_t *obj) { + S_StopSound(obj ? &obj->xy : NULL); +} + +void S_StartUnpositionedSound(int sfx_id) { + S_StartSound(NULL, sfx_id); +} + +void S_StartSound(xy_positioned_t *origin, int sfx_id) { - sfxinfo_t *sfx; - mobj_t *origin; + should_be_const sfxinfo_t *sfx; int rc; int sep; int pitch; int cnum; int volume; - origin = (mobj_t *) origin_p; volume = snd_SfxVolume; // check for bogus sound # @@ -446,6 +471,7 @@ void S_StartSound(void *origin_p, int sfx_id) } sfx = &S_sfx[sfx_id]; +// printf("Start sound %d %s\n", sfx_id, DEH_String(sfx->name)); // Initialize sound parameters pitch = NORM_PITCH; @@ -468,15 +494,15 @@ void S_StartSound(void *origin_p, int sfx_id) // Check to see if it is audible, // and if not, modify the params - if (origin && origin != players[consoleplayer].mo) + if (origin && origin != &players[consoleplayer].mo->xy) { rc = S_AdjustSoundParams(players[consoleplayer].mo, origin, &volume, &sep); - if (origin->x == players[consoleplayer].mo->x - && origin->y == players[consoleplayer].mo->y) + if (origin->x == players[consoleplayer].mo->xy.x + && origin->y == players[consoleplayer].mo->xy.y) { sep = NORM_SEP; } @@ -514,14 +540,14 @@ void S_StartSound(void *origin_p, int sfx_id) } // increase the usefulness - if (sfx->usefulness++ < 0) + if (sfx_mut(sfx)->usefulness++ < 0) { - sfx->usefulness = 1; + sfx_mut(sfx)->usefulness = 1; } - if (sfx->lumpnum < 0) + if (sfx_mut(sfx)->lumpnum < 0) { - sfx->lumpnum = I_GetSfxLumpNum(sfx); + sfx_mut(sfx)->lumpnum = I_GetSfxLumpNum(sfx); } channels[cnum].pitch = pitch; @@ -560,7 +586,7 @@ void S_UpdateSounds(mobj_t *listener) int cnum; int volume; int sep; - sfxinfo_t* sfx; + should_be_const sfxinfo_t* sfx; channel_t* c; I_UpdateSound(); @@ -594,7 +620,7 @@ void S_UpdateSounds(mobj_t *listener) // check non-local sounds for distance clipping // or modify their params - if (c->origin && listener != c->origin) + if (c->origin && &listener->xy != c->origin) { audible = S_AdjustSoundParams(listener, c->origin, @@ -690,9 +716,12 @@ void S_ChangeMusic(int musicnum, int looping) music->lumpnum = W_GetNumForName(namebuf); } +#if !DOOM_SMALL music->data = W_CacheLumpNum(music->lumpnum, PU_STATIC); - handle = I_RegisterSong(music->data, W_LumpLength(music->lumpnum)); +#else + handle = I_RegisterSong(W_CacheLumpNum(music->lumpnum, PU_STATIC), W_LumpLength(music->lumpnum)); +#endif music->handle = handle; I_PlaySong(handle, looping); @@ -716,8 +745,32 @@ void S_StopMusic(void) I_StopSong(); I_UnRegisterSong(mus_playing->handle); W_ReleaseLumpNum(mus_playing->lumpnum); +#if !DOOM_SMALL mus_playing->data = NULL; +#endif mus_playing = NULL; } } +#if 1 +void test_next_sound() { + static int snd_idx = 0; + do { + char buf[10]; + snd_idx++; + if (snd_idx == NUMSFX) snd_idx = 1; + should_be_const sfxinfo_t *sfx = &S_sfx[snd_idx]; + // Linked sfx lumps? Get the lump number for the sound linked to. + if (sfx->link != NULL) + { + sfx = sfx->link; + } + M_snprintf(buf, sizeof(buf), "ds%s", DEH_String(sfx->name)); + if (W_CheckNumForName(buf) > 0) { + break; + } + } while (true); + S_StartUnpositionedSound(snd_idx); +} +#endif + diff --git a/src/doom/s_sound.h b/src/doom/s_sound.h index bbd100a0..c8f28285 100644 --- a/src/doom/s_sound.h +++ b/src/doom/s_sound.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -51,11 +52,13 @@ void S_Start(void); // using from sounds.h // -void S_StartSound(void *origin, int sound_id); +void S_StartUnpositionedSound(int sound_id); +void S_StartSound(xy_positioned_t *origin, int sound_id); +void S_StartObjSound(mobj_t *origin, int sound_id); // Stop sound for thing at -void S_StopSound(mobj_t *origin); - +void S_StopSound(xy_positioned_t *origin); +void S_StopObjSound(mobj_t *origin); // Start music using from sounds.h void S_StartMusic(int music_id); diff --git a/src/doom/sounds.c b/src/doom/sounds.c index e976bc84..9ca06bc7 100644 --- a/src/doom/sounds.c +++ b/src/doom/sounds.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -17,23 +18,31 @@ // Kept as a sample, DOOM2 sounds. // - +#if !INCLUDE_SOUND_C_IN_S_SOUND #include +#include #include "doomtype.h" #include "sounds.h" - // // Information about all the music // -#define MUSIC(name) \ - { name, 0, NULL, NULL } +#if !DOOM_SMALL +#define MUSIC(name) { name, 0, NULL, NULL } +#else +#define MUSIC(name) { name, 0, NULL } +#endif +// todo graham big waste of space musicinfo_t S_music[] = { +#if !USE_CONST_SFX MUSIC(NULL), +#else + MUSIC(""), +#endif MUSIC("e1m1"), MUSIC("e1m2"), MUSIC("e1m3"), @@ -108,12 +117,27 @@ musicinfo_t S_music[] = // Information about all the sfx // -#define SOUND(name, priority) \ - { NULL, name, priority, NULL, -1, -1, 0, 0, -1, NULL } -#define SOUND_LINK(name, priority, link_id, pitch, volume) \ - { NULL, name, priority, &S_sfx[link_id], pitch, volume, 0, 0, -1, NULL } +#if !DOOM_ONLY +#define FIRST_ONE 0, +#define LAST_TWO -1, NULL +#else +#define FIRST_ONE +#define LAST_TWO +#endif -sfxinfo_t S_sfx[] = +#if !USE_CONST_SFX +#define SOUND(name, priority) \ + { FIRST_ONE name, priority, NULL, -1, -1, 0, 0, LAST_TWO } +#define SOUND_LINK(name, priority, link_id, pitch, volume) \ + { FIRST_ONE name, priority, &S_sfx[link_id], pitch, volume, 0, 0, LAST_TWO } +#else +#define SOUND(name, priority) \ + { FIRST_ONE name, priority, NULL, -1, -1, LAST_TWO } +#define SOUND_LINK(name, priority, link_id, pitch, volume) \ + { FIRST_ONE name, priority, &S_sfx[link_id], pitch, volume, LAST_TWO } +#endif + +sfxinfo_t S_sfx[NUM_SFX] = { // S_sfx[0] needs to be a dummy for odd reasons. SOUND("none", 0), @@ -227,3 +251,16 @@ sfxinfo_t S_sfx[] = SOUND("radio", 60), }; +#if USE_CONST_SFX +sfxinfo_mut_t S_sfx_mut[NUM_SFX]; + +sfxinfo_mut_t *get_mut_sfxinfo_t(const sfxinfo_t *sfxinfo) { + // todo graham maybe pass indexes around instead of pointers anyway + unsigned int index = sfxinfo - S_sfx; + assert(index < NUM_SFX); + return S_sfx_mut + index; +} + +#endif + +#endif \ No newline at end of file diff --git a/src/doom/sounds.h b/src/doom/sounds.h index 1e8afc40..fb2ef61d 100644 --- a/src/doom/sounds.h +++ b/src/doom/sounds.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -20,9 +21,11 @@ #ifndef __SOUNDS__ #define __SOUNDS__ +#include "doomdef.h" #include "i_sound.h" // the complete set of sound effects +#define NUM_SFX 110 extern sfxinfo_t S_sfx[]; // the complete set of music @@ -223,5 +226,7 @@ typedef enum sfx_radio, NUMSFX } sfxenum_t; +#include +static_assert(NUMSFX < 256, ""); // for mobj_info #endif diff --git a/src/doom/st_lib.c b/src/doom/st_lib.c index 64533658..ea50dadc 100644 --- a/src/doom/st_lib.c +++ b/src/doom/st_lib.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -16,7 +17,6 @@ // The status bar widget code. // - #include #include @@ -46,11 +46,11 @@ extern boolean automapactive; // Hack display negative frags. // Loads and store the stminus lump. // -patch_t* sttminus; +vpatch_handle_small_t sttminus; void STlib_init(void) { - sttminus = (patch_t *) W_CacheLumpName(DEH_String("STTMINUS"), PU_STATIC); + sttminus = VPATCH_HANDLE(VPATCH_NAME(STTMINUS)); } @@ -60,18 +60,22 @@ STlib_initNum ( st_number_t* n, int x, int y, - patch_t** pl, + vpatch_sequence_t pl, int* num, boolean* on, int width ) { n->x = x; n->y = y; +#if !DOOM_TINY n->oldnum = 0; +#else + n->cached = 9999; +#endif n->width = width; n->num = num; n->on = on; - n->p = pl; + n->p = pl; } @@ -88,14 +92,20 @@ STlib_drawNum int numdigits = n->width; int num = *n->num; - - int w = SHORT(n->p[0]->width); - int h = SHORT(n->p[0]->height); + + int w = vpatch_width(resolve_vpatch_handle(vpatch_n(n->p, 0))); + int h = vpatch_height(resolve_vpatch_handle(vpatch_n(n->p, 0))); int x = n->x; int neg; +#if !DOOM_TINY n->oldnum = *n->num; +#else + // stbar wipe support - we need to be able to redraw and old status bar value + if (refresh || n->cached == 9999) n->cached = (int16_t)num; + else num = n->cached; +#endif neg = num < 0; @@ -112,10 +122,12 @@ STlib_drawNum // clear the area x = n->x - numdigits*w; +#if !DOOM_TINY if (n->y - ST_Y < 0) I_Error("drawNum: n->y - ST_Y < 0"); V_CopyRect(x, n->y - ST_Y, st_backing_screen, w*numdigits, h, x, n->y); +#endif // if non-number, do not draw it if (num == 1994) @@ -125,14 +137,14 @@ STlib_drawNum // in the special case of 0, you draw 0 if (!num) - V_DrawPatch(x - w, n->y, n->p[ 0 ]); + V_DrawPatch(x - w, n->y, vpatch_n(n->p, 00)); // draw the new number while (num && numdigits--) { - x -= w; - V_DrawPatch(x, n->y, n->p[ num % 10 ]); - num /= 10; + x -= w; + V_DrawPatch(x, n->y, vpatch_n(n->p, num % 10)); + num /= 10; } // draw a minus sign if necessary @@ -157,10 +169,10 @@ STlib_initPercent ( st_percent_t* p, int x, int y, - patch_t** pl, + vpatch_sequence_t pl, int* num, boolean* on, - patch_t* percent ) + vpatch_handle_small_t percent ) { STlib_initNum(&p->n, x, y, pl, num, on, 3); p->p = percent; @@ -174,8 +186,13 @@ STlib_updatePercent ( st_percent_t* per, int refresh ) { +#if !DOOM_TINY if (refresh && *per->n.on) - V_DrawPatch(per->n.x, per->n.y, per->p); + V_DrawPatch(per->n.x, per->n.y, per->p); +#else + if (*per->n.on) + V_DrawPatch(per->n.x, per->n.y, per->p); +#endif STlib_updateNum(&per->n, refresh); } @@ -187,13 +204,17 @@ STlib_initMultIcon ( st_multicon_t* i, int x, int y, - patch_t** il, - int* inum, + vpatch_handle_small_t* il, + isb_int8_t * inum, boolean* on ) { i->x = x; i->y = y; +#if !DOOM_TINY i->oldinum = -1; +#else + i->cached = -1; +#endif i->inum = inum; i->on = on; i->p = il; @@ -211,16 +232,17 @@ STlib_updateMultIcon int x; int y; +#if !DOOM_TINY if (*mi->on && (mi->oldinum != *mi->inum || refresh) && (*mi->inum!=-1)) { if (mi->oldinum != -1) { - x = mi->x - SHORT(mi->p[mi->oldinum]->leftoffset); - y = mi->y - SHORT(mi->p[mi->oldinum]->topoffset); - w = SHORT(mi->p[mi->oldinum]->width); - h = SHORT(mi->p[mi->oldinum]->height); + x = mi->x - patch_leftoffset(mi->p[mi->oldinum]); + y = mi->y - patch_topoffset(mi->p[mi->oldinum]); + w = vpatch_width(resolve_vpatch_handle(mi->p[mi->oldinum])); + h = vpatch_height(resolve_vpatch_handle(mi->p[mi->oldinum])); if (y - ST_Y < 0) I_Error("updateMultIcon: y - ST_Y < 0"); @@ -230,6 +252,14 @@ STlib_updateMultIcon V_DrawPatch(mi->x, mi->y, mi->p[*mi->inum]); mi->oldinum = *mi->inum; } +#else + int8_t inum = *mi->inum; + // stbar wipe support - we need to be able to redraw and old status bar value + if (refresh || mi->cached==-1) mi->cached = (int8_t)*mi->inum; + else inum = mi->cached; + if (*mi->on && inum!=-1) + V_DrawPatch(mi->x, mi->y, mi->p[inum]); +#endif } @@ -239,13 +269,17 @@ STlib_initBinIcon ( st_binicon_t* b, int x, int y, - patch_t* i, + vpatch_handle_small_t i, boolean* val, boolean* on ) { b->x = x; b->y = y; +#if !DOOM_TINY b->oldval = false; +#else + b->cached = -1; +#endif b->val = val; b->on = on; b->p = i; @@ -263,13 +297,14 @@ STlib_updateBinIcon int w; int h; +#if !DOOM_TINY if (*bi->on && (bi->oldval != *bi->val || refresh)) { - x = bi->x - SHORT(bi->p->leftoffset); - y = bi->y - SHORT(bi->p->topoffset); - w = SHORT(bi->p->width); - h = SHORT(bi->p->height); + x = bi->x - patch_leftoffset(bi->p); + y = bi->y - patch_topoffset(bi->p); + w = patch_width(bi->p); + h = patch_height(bi->p); if (y - ST_Y < 0) I_Error("updateBinIcon: y - ST_Y < 0"); @@ -281,6 +316,14 @@ STlib_updateBinIcon bi->oldval = *bi->val; } +#else + boolean val = *bi->val; + // stbar wipe support - we need to be able to redraw and old status bar value + if (refresh || bi->cached==-1) bi->cached = (int8_t)*bi->val; + else val = bi->cached; + if (*bi->on && val) { + V_DrawPatch(bi->x, bi->y, bi->p); + } +#endif } - diff --git a/src/doom/st_lib.h b/src/doom/st_lib.h index 3a8f5212..d33ae1ab 100644 --- a/src/doom/st_lib.h +++ b/src/doom/st_lib.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -33,14 +34,16 @@ typedef struct { // upper right-hand corner // of the number (right-justified) - int x; - int y; + isb_int16_t x; + isb_int16_t y; // max # of digits in number - int width; + isb_int8_t width; +#if !DOOM_TINY // last number value int oldnum; +#endif // pointer to current value int* num; @@ -49,11 +52,14 @@ typedef struct // whether to update number boolean* on; - // list of patches for 0-9 - patch_t** p; - + vpatch_sequence_t p; +#if DOOM_TINY + int16_t cached; +#endif +#if !DOOM_TINY // user data int data; +#endif } st_number_t; @@ -67,7 +73,7 @@ typedef struct st_number_t n; // percent sign graphic - patch_t* p; + vpatch_handle_small_t p; } st_percent_t; @@ -77,56 +83,65 @@ typedef struct typedef struct { // center-justified location of icons - int x; - int y; + isb_int16_t x; + isb_int16_t y; +#if !DOOM_TINY // last icon number int oldinum; +#endif // pointer to current icon - int* inum; + isb_int8_t* inum; // pointer to boolean stating // whether to update icon boolean* on; // list of icons - patch_t** p; - + vpatch_handle_small_t* p; + +#if DOOM_TINY + int8_t cached; +#endif +#if !DOOM_TINY // user data int data; +#endif } st_multicon_t; - - - // Binary Icon widget typedef struct { // center-justified location of icon - int x; - int y; + isb_uint8_t x; + isb_uint8_t y; + vpatch_handle_small_t p; // icon +#if !DOOM_TINY // last icon value boolean oldval; +#endif // pointer to current icon status boolean* val; // pointer to boolean // stating whether to update icon - boolean* on; + boolean* on; +#if DOOM_TINY + int8_t cached; +#endif - patch_t* p; // icon +#if !DOOM_TINY int data; // user data - +#endif + } st_binicon_t; - - // // Widget creation, access, and update routines // @@ -145,7 +160,7 @@ STlib_initNum ( st_number_t* n, int x, int y, - patch_t** pl, + vpatch_sequence_t pl, int* num, boolean* on, int width ); @@ -162,10 +177,10 @@ STlib_initPercent ( st_percent_t* p, int x, int y, - patch_t** pl, + vpatch_sequence_t pl, int* num, boolean* on, - patch_t* percent ); + vpatch_handle_small_t percent ); void @@ -180,8 +195,8 @@ STlib_initMultIcon ( st_multicon_t* mi, int x, int y, - patch_t** il, - int* inum, + vpatch_handle_small_t* il, + isb_int8_t* inum, boolean* on ); @@ -197,7 +212,7 @@ STlib_initBinIcon ( st_binicon_t* b, int x, int y, - patch_t* i, + vpatch_handle_small_t i, boolean* val, boolean* on ); diff --git a/src/doom/st_stuff.c b/src/doom/st_stuff.c index dc72f1e6..350c4492 100644 --- a/src/doom/st_stuff.c +++ b/src/doom/st_stuff.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -260,16 +261,60 @@ #define ST_MAPHEIGHT 1 // graphics are drawn to a backing screen and blitted to the real screen +#if !DOOM_TINY pixel_t *st_backing_screen; +#endif // main player in game static player_t* plyr; +#if !DOOM_TINY // ST_Start() has just been called static boolean st_firsttime; +#endif +#if !USE_WHD // lump number for PLAYPAL static int lu_palette; +#endif + +static int st_palette = 0; +static boolean st_stopped = true; + +// whether in automap or first-person +static st_stateenum_t st_gamestate; + +cheatseq_t cheat_mus = CHEAT("idmus", 2); +cheatseq_t cheat_god = CHEAT("iddqd", 0); +cheatseq_t cheat_ammo = CHEAT("idkfa", 0); +cheatseq_t cheat_ammonokey = CHEAT("idfa", 0); +cheatseq_t cheat_noclip = CHEAT("idspispopd", 0); +cheatseq_t cheat_commercial_noclip = CHEAT("idclip", 0); +#if USE_FPS +boolean show_fps;//=1; +#endif + +cheatseq_t cheat_powerup[7] = + { + CHEAT("idbeholdv", 0), + CHEAT("idbeholds", 0), + CHEAT("idbeholdi", 0), + CHEAT("idbeholdr", 0), + CHEAT("idbeholda", 0), + CHEAT("idbeholdl", 0), + CHEAT("idbehold", 0), + }; + +cheatseq_t cheat_choppers = CHEAT("idchoppers", 0); +cheatseq_t cheat_clev = CHEAT("idclev", 2); +cheatseq_t cheat_mypos = CHEAT("idmypos", 0); + +// +// STATUS BAR CODE +// +void ST_Stop(void); + + // used for timing static unsigned int st_clock; @@ -280,9 +325,6 @@ static int st_msgcounter=0; // used when in chat static st_chatstateenum_t st_chatstate; -// whether in automap or first-person -static st_stateenum_t st_gamestate; - // whether left-side main status bar is active static boolean st_statusbaron; @@ -305,31 +347,42 @@ static boolean st_armson; static boolean st_fragson; // main bar left -static patch_t* sbar; +static vpatch_handle_small_t sbar; // 0-9, tall numbers -static patch_t* tallnum[10]; +#if !USE_WHD +static vpatch_handle_large_t tallnum[10]; +#else +#define tallnum ((vpatch_sequence_t)VPATCH_STTNUM0) +#endif // tall % sign -static patch_t* tallpercent; +static vpatch_handle_small_t tallpercent; // 0-9, short, yellow (,different!) numbers -static patch_t* shortnum[10]; +#if !USE_WHD +static vpatch_handle_large_t shortnum[10]; +#else +static vpatch_sequence_t shortnum; +#define shortnum ((vpatch_sequence_t)VPATCH_STYSNUM0) +#endif // 3 key-cards, 3 skulls -static patch_t* keys[NUMCARDS]; +static vpatch_handle_small_t keys[NUMCARDS]; // face status patches -static patch_t* faces[ST_NUMFACES]; +static vpatch_handle_small_t faces[ST_NUMFACES]; // face background -static patch_t* faceback; +#if !DOOM_TINY +static vpatch_handle_small_t faceback; +#endif // main bar right -static patch_t* armsbg; +static vpatch_handle_small_t armsbg; // weapon ownership patches -static patch_t* arms[6][2]; +static vpatch_handle_small_t arms[6][2]; // ready-weapon widget static st_number_t w_ready; @@ -337,6 +390,11 @@ static st_number_t w_ready; // in deathmatch only, summary of frags stats static st_number_t w_frags; +#if USE_FPS +static st_number_t w_fps; +int st_fps; +boolean st_fpson = true; +#endif // health widget static st_percent_t w_health; @@ -377,45 +435,19 @@ static boolean oldweaponsowned[NUMWEAPONS]; static int st_facecount = 0; // current face index, used by w_faces -static int st_faceindex = 0; +static isb_int8_t st_faceindex = 0; // holds key-type for each key box on bar -static int keyboxes[3]; +static isb_int8_t keyboxes[3]; // a random number per tick static int st_randomnumber; -cheatseq_t cheat_mus = CHEAT("idmus", 2); -cheatseq_t cheat_god = CHEAT("iddqd", 0); -cheatseq_t cheat_ammo = CHEAT("idkfa", 0); -cheatseq_t cheat_ammonokey = CHEAT("idfa", 0); -cheatseq_t cheat_noclip = CHEAT("idspispopd", 0); -cheatseq_t cheat_commercial_noclip = CHEAT("idclip", 0); - -cheatseq_t cheat_powerup[7] = -{ - CHEAT("idbeholdv", 0), - CHEAT("idbeholds", 0), - CHEAT("idbeholdi", 0), - CHEAT("idbeholdr", 0), - CHEAT("idbeholda", 0), - CHEAT("idbeholdl", 0), - CHEAT("idbehold", 0), -}; - -cheatseq_t cheat_choppers = CHEAT("idchoppers", 0); -cheatseq_t cheat_clev = CHEAT("idclev", 2); -cheatseq_t cheat_mypos = CHEAT("idmypos", 0); - - -// -// STATUS BAR CODE -// -void ST_Stop(void); +#if !NO_USE_ST void ST_refreshBackground(void) { - +#if !DOOM_TINY if (st_statusbaron) { V_UseBuffer(st_backing_screen); @@ -429,257 +461,9 @@ void ST_refreshBackground(void) V_CopyRect(ST_X, 0, st_backing_screen, ST_WIDTH, ST_HEIGHT, ST_X, ST_Y); } - +#endif } - -// Respond to keyboard input events, -// intercept cheats. -boolean -ST_Responder (event_t* ev) -{ - int i; - - // Filter automap on/off. - if (ev->type == ev_keyup - && ((ev->data1 & 0xffff0000) == AM_MSGHEADER)) - { - switch(ev->data1) - { - case AM_MSGENTERED: - st_gamestate = AutomapState; - st_firsttime = true; - break; - - case AM_MSGEXITED: - // fprintf(stderr, "AM exited\n"); - st_gamestate = FirstPersonState; - break; - } - } - - // if a user keypress... - else if (ev->type == ev_keydown) - { - if (!netgame && gameskill != sk_nightmare) - { - // 'dqd' cheat for toggleable god mode - if (cht_CheckCheat(&cheat_god, ev->data2)) - { - plyr->cheats ^= CF_GODMODE; - if (plyr->cheats & CF_GODMODE) - { - if (plyr->mo) - plyr->mo->health = 100; - - plyr->health = deh_god_mode_health; - plyr->message = DEH_String(STSTR_DQDON); - } - else - plyr->message = DEH_String(STSTR_DQDOFF); - } - // 'fa' cheat for killer fucking arsenal - else if (cht_CheckCheat(&cheat_ammonokey, ev->data2)) - { - plyr->armorpoints = deh_idfa_armor; - plyr->armortype = deh_idfa_armor_class; - - for (i=0;iweaponowned[i] = true; - - for (i=0;iammo[i] = plyr->maxammo[i]; - - plyr->message = DEH_String(STSTR_FAADDED); - } - // 'kfa' cheat for key full ammo - else if (cht_CheckCheat(&cheat_ammo, ev->data2)) - { - plyr->armorpoints = deh_idkfa_armor; - plyr->armortype = deh_idkfa_armor_class; - - for (i=0;iweaponowned[i] = true; - - for (i=0;iammo[i] = plyr->maxammo[i]; - - for (i=0;icards[i] = true; - - plyr->message = DEH_String(STSTR_KFAADDED); - } - // 'mus' cheat for changing music - else if (cht_CheckCheat(&cheat_mus, ev->data2)) - { - - char buf[3]; - int musnum; - - plyr->message = DEH_String(STSTR_MUS); - cht_GetParam(&cheat_mus, buf); - - // Note: The original v1.9 had a bug that tried to play back - // the Doom II music regardless of gamemode. This was fixed - // in the Ultimate Doom executable so that it would work for - // the Doom 1 music as well. - - if (gamemode == commercial || gameversion < exe_ultimate) - { - musnum = mus_runnin + (buf[0]-'0')*10 + buf[1]-'0' - 1; - - if (((buf[0]-'0')*10 + buf[1]-'0') > 35 - && gameversion >= exe_doom_1_8) - plyr->message = DEH_String(STSTR_NOMUS); - else - S_ChangeMusic(musnum, 1); - } - else - { - musnum = mus_e1m1 + (buf[0]-'1')*9 + (buf[1]-'1'); - - if (((buf[0]-'1')*9 + buf[1]-'1') > 31) - plyr->message = DEH_String(STSTR_NOMUS); - else - S_ChangeMusic(musnum, 1); - } - } - else if ( (logical_gamemission == doom - && cht_CheckCheat(&cheat_noclip, ev->data2)) - || (logical_gamemission != doom - && cht_CheckCheat(&cheat_commercial_noclip,ev->data2))) - { - // Noclip cheat. - // For Doom 1, use the idspipsopd cheat; for all others, use - // idclip - - plyr->cheats ^= CF_NOCLIP; - - if (plyr->cheats & CF_NOCLIP) - plyr->message = DEH_String(STSTR_NCON); - else - plyr->message = DEH_String(STSTR_NCOFF); - } - // 'behold?' power-up cheats - for (i=0;i<6;i++) - { - if (cht_CheckCheat(&cheat_powerup[i], ev->data2)) - { - if (!plyr->powers[i]) - P_GivePower( plyr, i); - else if (i!=pw_strength) - plyr->powers[i] = 1; - else - plyr->powers[i] = 0; - - plyr->message = DEH_String(STSTR_BEHOLDX); - } - } - - // 'behold' power-up menu - if (cht_CheckCheat(&cheat_powerup[6], ev->data2)) - { - plyr->message = DEH_String(STSTR_BEHOLD); - } - // 'choppers' invulnerability & chainsaw - else if (cht_CheckCheat(&cheat_choppers, ev->data2)) - { - plyr->weaponowned[wp_chainsaw] = true; - plyr->powers[pw_invulnerability] = true; - plyr->message = DEH_String(STSTR_CHOPPERS); - } - // 'mypos' for player position - else if (cht_CheckCheat(&cheat_mypos, ev->data2)) - { - static char buf[ST_MSGWIDTH]; - M_snprintf(buf, sizeof(buf), "ang=0x%x;x,y=(0x%x,0x%x)", - players[consoleplayer].mo->angle, - players[consoleplayer].mo->x, - players[consoleplayer].mo->y); - plyr->message = buf; - } - } - - // 'clev' change-level cheat - if (!netgame && cht_CheckCheat(&cheat_clev, ev->data2)) - { - char buf[3]; - int epsd; - int map; - - cht_GetParam(&cheat_clev, buf); - - if (gamemode == commercial) - { - epsd = 0; - map = (buf[0] - '0')*10 + buf[1] - '0'; - } - else - { - epsd = buf[0] - '0'; - map = buf[1] - '0'; - - // Chex.exe always warps to episode 1. - - if (gameversion == exe_chex) - { - if (epsd > 1) - { - epsd = 1; - } - if (map > 5) - { - map = 5; - } - } - } - - // Catch invalid maps. - if (gamemode != commercial) - { - if (epsd < 1) - { - return false; - } - if (epsd > 4) - { - return false; - } - if (epsd == 4 && gameversion < exe_ultimate) - { - return false; - } - if (map < 1) - { - return false; - } - if (map > 9) - { - return false; - } - } - else - { - if (map < 1) - { - return false; - } - if (map > 40) - { - return false; - } - } - - // So be it. - plyr->message = DEH_String(STSTR_CLEV); - G_DeferedInitNew(gameskill, epsd, map); - } - } - return false; -} - - - int ST_calcPainOffset(void) { int health; @@ -765,21 +549,21 @@ void ST_updateFaceWidget(void) } else { - badguyangle = R_PointToAngle2(plyr->mo->x, - plyr->mo->y, - plyr->attacker->x, - plyr->attacker->y); + badguyangle = R_PointToAngle2(plyr->mo->xy.x, + plyr->mo->xy.y, + plyr->attacker->xy.x, + plyr->attacker->xy.y); - if (badguyangle > plyr->mo->angle) + if (badguyangle > mobj_full(plyr->mo)->angle) { // whether right or left - diffang = badguyangle - plyr->mo->angle; + diffang = badguyangle - mobj_full(plyr->mo)->angle; i = diffang > ANG180; } else { // whether left or right - diffang = plyr->mo->angle - badguyangle; + diffang = mobj_full(plyr->mo)->angle - badguyangle; i = diffang <= ANG180; } // confusing, aint it? @@ -896,7 +680,9 @@ void ST_updateWidgets(void) // dir = 1; // tic++; // } +#if !DOOM_TINY // not sure what this is for, doesn't seem to be accessed w_ready.data = plyr->readyweapon; +#endif // if (*w_ready.on) // STlib_updateNum(&w_ready, true); @@ -949,73 +735,32 @@ void ST_Ticker (void) } -static int st_palette = 0; - -void ST_doPaletteStuff(void) -{ - - int palette; - byte* pal; - int cnt; - int bzc; - - cnt = plyr->damagecount; - - if (plyr->powers[pw_strength]) - { - // slowly fade the berzerk out - bzc = 12 - (plyr->powers[pw_strength]>>6); - - if (bzc > cnt) - cnt = bzc; +void ST_FpsDrawer(int fps) { +#if USE_FPS + if (show_fps) { + static uint8_t ms[16]; + static uint8_t ms_index; + static int total; + static int last_time_ms; + int time_ms = I_GetTimeMS(); + int delta_time_ms = time_ms - last_time_ms; + last_time_ms = time_ms; + if (delta_time_ms >=0 && delta_time_ms < 100) { + total -= ms[ms_index]; + ms[ms_index] = delta_time_ms; + total += ms[ms_index]; + ms_index = (ms_index + 1) & 0xf; + } + if (fps != -1) { + st_fps = fps; + STlib_updateNum(&w_fps, true); + } else if (total > 160 && total < 8000) { + st_fps = 16000 / total; + STlib_updateNum(&w_fps, true); + } } - - if (cnt) - { - palette = (cnt+7)>>3; - - if (palette >= NUMREDPALS) - palette = NUMREDPALS-1; - - palette += STARTREDPALS; - } - - else if (plyr->bonuscount) - { - palette = (plyr->bonuscount+7)>>3; - - if (palette >= NUMBONUSPALS) - palette = NUMBONUSPALS-1; - - palette += STARTBONUSPALS; - } - - else if ( plyr->powers[pw_ironfeet] > 4*32 - || plyr->powers[pw_ironfeet]&8) - palette = RADIATIONPAL; - else - palette = 0; - - // In Chex Quest, the player never sees red. Instead, the - // radiation suit palette is used to tint the screen green, - // as though the player is being covered in goo by an - // attacking flemoid. - - if (gameversion == exe_chex - && palette >= STARTREDPALS && palette < STARTREDPALS + NUMREDPALS) - { - palette = RADIATIONPAL; - } - - if (palette != st_palette) - { - st_palette = palette; - pal = (byte *) W_CacheLumpNum (lu_palette, PU_CACHE)+palette*768; - I_SetPalette (pal); - } - +#endif } - void ST_drawWidgets(boolean refresh) { int i; @@ -1024,8 +769,16 @@ void ST_drawWidgets(boolean refresh) st_armson = st_statusbaron && !deathmatch; // used by w_frags widget - st_fragson = deathmatch && st_statusbaron; + st_fragson = deathmatch && st_statusbaron; +#if DOOM_TINY + V_DrawPatch(0, ST_FACESY, sbar); +#if USE_PICO_NET + static vpatch_handle_small_t fb; + if (refresh) fb = netgame ? VPATCH_NAME(STFB0) + consoleplayer : 0; + if (fb) V_DrawPatch(ST_FX, ST_FACESY, fb); +#endif +#endif STlib_updateNum(&w_ready, refresh); for (i=0;i<4;i++) @@ -1051,42 +804,51 @@ void ST_drawWidgets(boolean refresh) } -void ST_doRefresh(void) +void ST_doRefresh(boolean refresh) { +#if !DOOM_TINY st_firsttime = false; // draw status bar background to off-screen buff ST_refreshBackground(); +#endif // and refresh all widgets - ST_drawWidgets(true); + ST_drawWidgets(refresh); } +#if !DOOM_TINY void ST_diffDraw(void) { // update all widgets ST_drawWidgets(false); } +#endif void ST_Drawer (boolean fullscreen, boolean refresh) { st_statusbaron = (!fullscreen) || automapactive; +#if !DOOM_TINY st_firsttime = st_firsttime || refresh; +#endif // Do red-/gold-shifts from damage/items ST_doPaletteStuff(); +#if DOOM_TINY + ST_doRefresh(refresh); +#else // If just after ST_Start(), refresh all - if (st_firsttime) ST_doRefresh(); + if (st_firsttime) ST_doRefresh(true); // Otherwise, update as little as possible else ST_diffDraw(); - +#endif } -typedef void (*load_callback_t)(const char *lumpname, patch_t **variable); +typedef void (*load_callback_t)(vpatchname_t name, vpatch_handle_small_t *variable); // Iterates through all graphics to be loaded or unloaded, along with // the variable they use, invoking the specified callback function. @@ -1101,6 +863,7 @@ static void ST_loadUnloadGraphics(load_callback_t callback) char namebuf[9]; // Load the numbers, tall and short +#if !USE_WHD for (i=0;i<10;i++) { DEH_snprintf(namebuf, 9, "STTNUM%d", i); @@ -1110,42 +873,57 @@ static void ST_loadUnloadGraphics(load_callback_t callback) callback(namebuf, &shortnum[i]); } +#endif + // Load percent key. //Note: why not load STMINUS here, too? - - callback(DEH_String("STTPRCNT"), &tallpercent); + callback(DEH_VPATCH_NAME(STTPRCNT), &tallpercent); // key cards for (i=0;iweaponowned[i]; - - for (i=0;i<3;i++) - keyboxes[i] = -1; - - STlib_init(); - -} - - void ST_createWidgets(void) { +#if DOOM_TINY + // this is todo with making wipe work (not doing so woud reset our caches), but there is no reason to recreate the widges anyway + static boolean initted; + if (!initted) { + initted = true; + } else { + return; + } +#endif int i; @@ -1256,8 +1026,10 @@ void ST_createWidgets(void) &st_statusbaron, ST_AMMOWIDTH ); +#if !DOOM_TINY // not sure what this is for, doesn't seem to be accessed // the last weapon type - w_ready.data = plyr->readyweapon; + w_ready.data = plyr->readyweapon; +#endif // health percentage STlib_initPercent(&w_health, @@ -1402,14 +1174,80 @@ void ST_createWidgets(void) } -static boolean st_stopped = true; +void ST_Init (void) +{ + ST_loadData(); +#if !DOOM_TINY + st_backing_screen = (pixel_t *) Z_Malloc(ST_WIDTH * ST_HEIGHT * sizeof(*st_backing_screen), PU_STATIC, 0); +#endif +#if USE_FPS + // frags sum + STlib_initNum(&w_fps, + 318, + 2, + shortnum, + &st_fps, + &st_fpson, + ST_FRAGSWIDTH*2); +#endif +#if DOOM_TINY + // do this early as we draw before ST_Start() is called to avoid a flicker in wipe (and this is one time init anyway) + plyr = &players[0]; + ST_createWidgets(); +#endif +} + +#else +void ST_Init (void) { + plyr = &players[consoleplayer]; +} +void ST_Ticker(void) {} +void ST_Drawer (boolean fullscreen, boolean refresh) { + ST_doPaletteStuff(); +} +void ST_createWidgets(void) {} +#endif + +void ST_initData(void) +{ + + int i; + +#if !DOOM_TINY + st_firsttime = true; +#endif + plyr = &players[consoleplayer]; + + st_clock = 0; + st_chatstate = StartChatState; + st_gamestate = FirstPersonState; + + st_statusbaron = true; + st_oldchat = st_chat = false; + st_cursoron = false; + + st_faceindex = 0; + st_palette = -1; + + st_oldhealth = -1; + + for (i=0;iweaponowned[i]; + + for (i=0;i<3;i++) + keyboxes[i] = -1; + + STlib_init(); + +} + void ST_Start (void) { if (!st_stopped) - ST_Stop(); + ST_Stop(); ST_initData(); ST_createWidgets(); @@ -1420,16 +1258,320 @@ void ST_Start (void) void ST_Stop (void) { if (st_stopped) - return; + return; +#if !USE_WHD I_SetPalette (W_CacheLumpNum (lu_palette, PU_CACHE)); +#else + I_SetPaletteNum(0); +#endif st_stopped = true; } -void ST_Init (void) +// Respond to keyboard input events, +// intercept cheats. +boolean +ST_Responder (event_t* ev) { - ST_loadData(); - st_backing_screen = (pixel_t *) Z_Malloc(ST_WIDTH * ST_HEIGHT * sizeof(*st_backing_screen), PU_STATIC, 0); + int i; + + // Filter automap on/off. + if (ev->type == ev_keyup + && ((ev->data1 & 0xffff0000) == AM_MSGHEADER)) + { + switch(ev->data1) + { + case AM_MSGENTERED: + st_gamestate = AutomapState; +#if !DOOM_TINY + st_firsttime = true; +#endif + break; + + case AM_MSGEXITED: + // fprintf(stderr, "AM exited\n"); + st_gamestate = FirstPersonState; + break; + } + } + + // if a user keypress... + else if (ev->type == ev_keydown) + { + if (!netgame && gameskill != sk_nightmare) { + // 'dqd' cheat for toggleable god mode + if (cht_CheckCheat(&cheat_god, ev->data2)) { + plyr->cheats ^= CF_GODMODE; + if (plyr->cheats & CF_GODMODE) { + if (plyr->mo) + mobj_full(plyr->mo)->health = 100; + + plyr->health = deh_god_mode_health; + plyr->message = DEH_String(STSTR_DQDON); + } else + plyr->message = DEH_String(STSTR_DQDOFF); + } + // 'fa' cheat for killer fucking arsenal + else if (cht_CheckCheat(&cheat_ammonokey, ev->data2)) { + plyr->armorpoints = deh_idfa_armor; + plyr->armortype = deh_idfa_armor_class; + + for (i = 0; i < NUMWEAPONS; i++) + plyr->weaponowned[i] = true; + + for (i = 0; i < NUMAMMO; i++) + plyr->ammo[i] = plyr->maxammo[i]; + + plyr->message = DEH_String(STSTR_FAADDED); + } + // 'kfa' cheat for key full ammo + else if (cht_CheckCheat(&cheat_ammo, ev->data2)) { + plyr->armorpoints = deh_idkfa_armor; + plyr->armortype = deh_idkfa_armor_class; + + for (i = 0; i < NUMWEAPONS; i++) + plyr->weaponowned[i] = true; + + for (i = 0; i < NUMAMMO; i++) + plyr->ammo[i] = plyr->maxammo[i]; + + for (i = 0; i < NUMCARDS; i++) + plyr->cards[i] = true; + + plyr->message = DEH_String(STSTR_KFAADDED); + } + // 'mus' cheat for changing music + else if (cht_CheckCheat(&cheat_mus, ev->data2)) { + + char buf[3]; + int musnum; + + plyr->message = DEH_String(STSTR_MUS); + cht_GetParam(&cheat_mus, buf); + + // Note: The original v1.9 had a bug that tried to play back + // the Doom II music regardless of gamemode. This was fixed + // in the Ultimate Doom executable so that it would work for + // the Doom 1 music as well. + + if (gamemode == commercial || gameversion < exe_ultimate) { + musnum = mus_runnin + (buf[0] - '0') * 10 + buf[1] - '0' - 1; + + if (((buf[0] - '0') * 10 + buf[1] - '0') > 35 + && gameversion >= exe_doom_1_8) + plyr->message = DEH_String(STSTR_NOMUS); + else + S_ChangeMusic(musnum, 1); + } else { + musnum = mus_e1m1 + (buf[0] - '1') * 9 + (buf[1] - '1'); + + if (((buf[0] - '1') * 9 + buf[1] - '1') > 31) + plyr->message = DEH_String(STSTR_NOMUS); + else + S_ChangeMusic(musnum, 1); + } + } else if ((logical_gamemission == doom + && cht_CheckCheat(&cheat_noclip, ev->data2)) + || (logical_gamemission != doom + && cht_CheckCheat(&cheat_commercial_noclip, ev->data2))) { + // Noclip cheat. + // For Doom 1, use the idspipsopd cheat; for all others, use + // idclip + + plyr->cheats ^= CF_NOCLIP; + + if (plyr->cheats & CF_NOCLIP) + plyr->message = DEH_String(STSTR_NCON); + else + plyr->message = DEH_String(STSTR_NCOFF); + } + // 'behold?' power-up cheats + for (i = 0; i < 6; i++) { + if (cht_CheckCheat(&cheat_powerup[i], ev->data2)) { + if (!plyr->powers[i]) + P_GivePower(plyr, i); + else if (i != pw_strength) + plyr->powers[i] = 1; + else + plyr->powers[i] = 0; + + plyr->message = DEH_String(STSTR_BEHOLDX); + } + } + + // 'behold' power-up menu + if (cht_CheckCheat(&cheat_powerup[6], ev->data2)) + { + plyr->message = DEH_String(STSTR_BEHOLD); + } + // 'choppers' invulnerability & chainsaw + else if (cht_CheckCheat(&cheat_choppers, ev->data2)) + { + plyr->weaponowned[wp_chainsaw] = true; + plyr->powers[pw_invulnerability] = true; + plyr->message = DEH_String(STSTR_CHOPPERS); + } + // 'mypos' for player position + else if (cht_CheckCheat(&cheat_mypos, ev->data2)) + { + static char buf[ST_MSGWIDTH]; + M_snprintf(buf, sizeof(buf), "ang=0x%x;x,y=(0x%x,0x%x)", + mobj_full(players[consoleplayer].mo)->angle, + players[consoleplayer].mo->xy.x, + players[consoleplayer].mo->xy.y); + plyr->message = buf; + } + } + + // 'clev' change-level cheat + if (!netgame && cht_CheckCheat(&cheat_clev, ev->data2)) + { + char buf[3]; + int epsd; + int map; + + cht_GetParam(&cheat_clev, buf); + + if (gamemode == commercial) + { + epsd = 0; + map = (buf[0] - '0')*10 + buf[1] - '0'; + } + else + { + epsd = buf[0] - '0'; + map = buf[1] - '0'; + + // Chex.exe always warps to episode 1. + + if (gameversion_is_chex(gameversion)) + { + if (epsd > 1) + { + epsd = 1; + } + if (map > 5) + { + map = 5; + } + } + } + + // Catch invalid maps. + if (gamemode != commercial) + { + if (epsd < 1) + { + return false; + } + if (epsd > 4) + { + return false; + } + if (epsd == 4 && gameversion < exe_ultimate) + { + return false; + } + if (map < 1) + { + return false; + } + if (map > 9) + { + return false; + } + } + else + { + if (map < 1) + { + return false; + } + if (map > 40) + { + return false; + } + } + + // So be it. + plyr->message = DEH_String(STSTR_CLEV); + G_DeferedInitNew(gameskill, epsd, map, false); + } + } + return false; } +void ST_doPaletteStuff() { +{ + + int palette; + byte* pal; + int cnt; + int bzc; + + cnt = plyr->damagecount; + + if (plyr->powers[pw_strength]) + { + // slowly fade the berzerk out + bzc = 12 - (plyr->powers[pw_strength]>>6); + + if (bzc > cnt) + cnt = bzc; + } + + if (cnt) + { + palette = (cnt+7)>>3; + + if (palette >= NUMREDPALS) + palette = NUMREDPALS-1; + + palette += STARTREDPALS; + } + + else if (plyr->bonuscount) + { + palette = (plyr->bonuscount+7)>>3; + + if (palette >= NUMBONUSPALS) + palette = NUMBONUSPALS-1; + + palette += STARTBONUSPALS; + } + + else if ( plyr->powers[pw_ironfeet] > 4*32 + || plyr->powers[pw_ironfeet]&8) + palette = RADIATIONPAL; + else + palette = 0; + + // In Chex Quest, the player never sees red. Instead, the + // radiation suit palette is used to tint the screen green, + // as though the player is being covered in goo by an + // attacking flemoid. + + if (gameversion_is_chex(gameversion) + && palette >= STARTREDPALS && palette < STARTREDPALS + NUMREDPALS) + { + palette = RADIATIONPAL; + } + + if (palette != st_palette) + { + st_palette = palette; +#if !USE_WHD + pal = (byte *) W_CacheLumpNum (lu_palette, PU_CACHE)+palette*768; + I_SetPalette (pal); +#else + I_SetPaletteNum(palette); +#endif + } + + static int last_health; + if (plyr->health != last_health) { + last_health = plyr->health; + } + } +} \ No newline at end of file diff --git a/src/doom/st_stuff.h b/src/doom/st_stuff.h index b01a5aed..d293651a 100644 --- a/src/doom/st_stuff.h +++ b/src/doom/st_stuff.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -45,13 +46,15 @@ void ST_Ticker (void); // Called by main loop. void ST_Drawer (boolean fullscreen, boolean refresh); +void ST_FpsDrawer(int fps); + // Called when the console player is spawned on each level. void ST_Start (void); // Called by startup code. void ST_Init (void); - +void ST_doPaletteStuff(void); // States for status bar code. typedef enum @@ -72,8 +75,12 @@ typedef enum } st_chatstateenum_t; - +#if USE_FPS +extern boolean show_fps; +#endif +#if !DOOM_TINY extern pixel_t *st_backing_screen; +#endif extern cheatseq_t cheat_mus; extern cheatseq_t cheat_god; extern cheatseq_t cheat_ammo; @@ -84,6 +91,7 @@ extern cheatseq_t cheat_powerup[7]; extern cheatseq_t cheat_choppers; extern cheatseq_t cheat_clev; extern cheatseq_t cheat_mypos; - - +#if DOOM_TINY +void ST_drawWidgets(boolean refresh); +#endif #endif diff --git a/src/doom/statdump.c b/src/doom/statdump.c index 5bacad67..da18cc88 100644 --- a/src/doom/statdump.c +++ b/src/doom/statdump.c @@ -1,6 +1,7 @@ /* Copyright(C) 2005-2014 Simon Howard + Copyright(C) 2021-2022 Graham Sanderson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -53,7 +54,7 @@ static const char *player_colors[] = static wbstartstruct_t captured_stats[MAX_CAPTURES]; static int num_captured_stats = 0; -static GameMission_t discovered_gamemission = none; +static GameMission_t discovered_gamemission = mission_none; /* Try to work out whether this is a Doom 1 or Doom 2 game, by looking * at the episode and map, and the par times. This is used to decide @@ -66,7 +67,7 @@ static void DiscoverGamemode(wbstartstruct_t *stats, int num_stats) int level; int i; - if (discovered_gamemission != none) + if (discovered_gamemission != mission_none) { return; } @@ -255,7 +256,7 @@ static void PrintLevelName(FILE *stream, int episode, int level) fprintf(stream, "MAP%02i\n", level + 1); break; default: - case none: + case mission_none: fprintf(stream, "E%iM%i / MAP%02i\n", episode + 1, level + 1, level + 1); break; @@ -320,6 +321,7 @@ void StatDump(void) // from statdump.exe (see ctrlapi.zip in the /idgames archive). // +#if !NO_USE_ARGS i = M_CheckParmWithArgs("-statdump", 1); if (i > 0) @@ -352,5 +354,6 @@ void StatDump(void) fclose(dumpfile); } } +#endif } diff --git a/src/doom/wi_stuff.c b/src/doom/wi_stuff.c index 1bfbcb2b..2e3ce5b2 100644 --- a/src/doom/wi_stuff.c +++ b/src/doom/wi_stuff.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -58,9 +59,16 @@ // This is supposedly ignored for commercial // release (aka DOOM II), which had 34 maps // in one episode. So there. +#if !DEMO1_ONLY #define NUMEPISODES 4 +#else +#define NUMEPISODES 1 +#endif #define NUMMAPS 9 +// todo cache values if this not good enough +#define rvpatch_width(p) vpatch_width(resolve_vpatch_handle(p)) +#define rvpatch_height(p) vpatch_height(resolve_vpatch_handle(p)) // in tics //U #define PAUSELEN (TICRATE*2) @@ -85,7 +93,7 @@ // NET GAME STUFF #define NG_STATSY 50 -#define NG_STATSX (32 + SHORT(star->width)/2 + 32*!dofrags) +#define NG_STATSX (32 + rvpatch_height(star)/2 + 32*!dofrags) #define NG_SPACINGX 64 @@ -114,61 +122,78 @@ typedef enum } animenum_t; + typedef struct { - int x; - int y; - +#if DEMO1_ONLY + isb_uint8_t x,y; +#else + isb_int16_t x,y; +#endif } point_t; +typedef isb_int8_t animframenum_t; +#define data_1(a) ((a)->data1) +#if DOOM_TINY +#define data_2(a) 0 +#else +#define data_2(a) ((a)->data2) +#endif // // Animation. // There is another anim_t used in p_spec. // +// todo graham separate out readonly typedef struct { - animenum_t type; + should_be_const animenum_t type; // period in tics between animations - int period; + should_be_const isb_uint8_t period; // number of animation frames - int nanims; + should_be_const animframenum_t nanims; // location of animation - point_t loc; + should_be_const point_t loc; // ALWAYS: n/a, // RANDOM: period deviation (<256), // LEVEL: level - int data1; + should_be_const isb_uint8_t data1; +#if !DOOM_TINY // ALWAYS: n/a, // RANDOM: random base period, // LEVEL: n/a - int data2; + should_be_const isb_uint8_t data2; +#endif // actual graphics for frames of animations - patch_t* p[3]; +#if !USE_WHD + vpatch_handle_large_t p[3]; +#else + vpatch_sequence_t p; +#endif // following must be initialized to zero before use! // next value of bcnt (used in conjunction with period) int nexttic; +#if !DOOM_TINY // last drawn animation frame - int lastdrawn; - + animframenum_t lastdrawn; +#endif // next frame number to animate - int ctr; - + animframenum_t ctr; + + // graham this does not seem to be the case so i removed it // used by RANDOM and LEVEL when animating - int state; - +// int state; } anim_t; - static point_t lnodes[NUMEPISODES][NUMMAPS] = { // Episode 0 World Map @@ -183,7 +208,7 @@ static point_t lnodes[NUMEPISODES][NUMMAPS] = { 135, 29 }, // location of level 7 (CJ) { 71, 24 } // location of level 8 (CJ) }, - +#if !DEMO1_ONLY // Episode 1 World Map should go here { { 254, 25 }, // location of level 0 (CJ) @@ -209,7 +234,7 @@ static point_t lnodes[NUMEPISODES][NUMMAPS] = { 140, 25 }, // location of level 7 (CJ) { 281, 136 } // location of level 8 (CJ) } - +#endif }; @@ -219,60 +244,73 @@ static point_t lnodes[NUMEPISODES][NUMMAPS] = // as they replace 320x200 full screen frames. // -#define ANIM(type, period, nanims, x, y, nexttic) \ - { (type), (period), (nanims), { (x), (y) }, (nexttic), \ - 0, { NULL, NULL, NULL }, 0, 0, 0, 0 } +#if !DOOM_TINY +#define ANIM(type, period, nanims, x, y, data1, vpatch) \ + { (type), (period), (nanims), { (x), (y) }, (data1), \ + 0, { NULL, NULL, NULL }, 0, 0, 0 } +#else +#define ANIM(type, period, nanims, x, y, data1, vpatch) \ + { (type), (period), (nanims), { (x), (y) }, (data1), \ + vpatch, 0, 0 } +#endif static anim_t epsd0animinfo[] = { - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 224, 104, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 184, 160, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 112, 136, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 72, 112, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 88, 96, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 64, 48, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 192, 40, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 136, 16, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 80, 16, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 64, 24, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 224, 104, 0, VPATCH_NAME(WIA00000)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 184, 160, 0, VPATCH_NAME(WIA00100)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 112, 136, 0, VPATCH_NAME(WIA00200)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 72, 112, 0, VPATCH_NAME(WIA00300)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 88, 96, 0, VPATCH_NAME(WIA00400)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 64, 48, 0, VPATCH_NAME(WIA00500)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 192, 40, 0, VPATCH_NAME(WIA00600)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 136, 16, 0, VPATCH_NAME(WIA00700)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 80, 16, 0, VPATCH_NAME(WIA00800)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 64, 24, 0, VPATCH_NAME(WIA00900)), }; +#if !DEMO1_ONLY + static anim_t epsd1animinfo[] = { - ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 1), - ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 2), - ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 3), - ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 4), - ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 5), - ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 6), - ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 7), - ANIM(ANIM_LEVEL, TICRATE/3, 3, 192, 144, 8), - ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 8), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 1, VPATCH_NAME(WIA10000)), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 2, VPATCH_NAME(WIA10100)), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 3, VPATCH_NAME(WIA10200)), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 4, VPATCH_NAME(WIA10300)), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 5, VPATCH_NAME(WIA10400)), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 6, VPATCH_NAME(WIA10500)), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 7, VPATCH_NAME(WIA10600)), + ANIM(ANIM_LEVEL, TICRATE/3, 3, 192, 144, 8, VPATCH_NAME(WIA10700)), + ANIM(ANIM_LEVEL, TICRATE/3, 1, 128, 136, 8, VPATCH_NAME(WIA10400)), }; static anim_t epsd2animinfo[] = { - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 104, 168, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 40, 136, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 160, 96, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 104, 80, 0), - ANIM(ANIM_ALWAYS, TICRATE/3, 3, 120, 32, 0), - ANIM(ANIM_ALWAYS, TICRATE/4, 3, 40, 0, 0), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 104, 168, 0, VPATCH_NAME(WIA20000)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 40, 136, 0, VPATCH_NAME(WIA20100)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 160, 96, 0, VPATCH_NAME(WIA20200)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 104, 80, 0, VPATCH_NAME(WIA20300)), + ANIM(ANIM_ALWAYS, TICRATE/3, 3, 120, 32, 0, VPATCH_NAME(WIA20400)), + ANIM(ANIM_ALWAYS, TICRATE/4, 3, 40, 0, 0, VPATCH_NAME(WIA20500)), }; +#endif -static int NUMANIMS[NUMEPISODES] = +static const isb_int8_t NUMANIMS[NUMEPISODES] = { arrlen(epsd0animinfo), +#if !DEMO1_ONLY arrlen(epsd1animinfo), arrlen(epsd2animinfo), +#endif }; static anim_t *anims[NUMEPISODES] = { epsd0animinfo, +#if !DEMO1_ONLY epsd1animinfo, epsd2animinfo +#endif }; @@ -300,10 +338,10 @@ static anim_t *anims[NUMEPISODES] = // used to accelerate or skip a stage -static int acceleratestage; +static isb_int8_t acceleratestage; // wbs->pnum -static int me; +static isb_int8_t me; // specifies current state static stateenum_t state; @@ -330,78 +368,115 @@ static int cnt_par; static int cnt_pause; // # of commercial levels -static int NUMCMAPS; +static isb_int8_t NUMCMAPS; // // GRAPHICS // -// You Are Here graphic -static patch_t* yah[3] = { NULL, NULL, NULL }; - -// splat -static patch_t* splat[2] = { NULL, NULL }; - +#if DOOM_TINY +#define wiminus VPATCH_NAME(WIMINUS) +#define percent VPATCH_NAME(WIPCNT) +#define finished VPATCH_NAME(WIF) +#define entering VPATCH_NAME(WIENTER) +#define kills VPATCH_NAME(WIOSTK) +#define secret VPATCH_NAME(WIOSTS) +#define sp_secret VPATCH_NAME(WISCRT2) +#define items VPATCH_NAME(WIOSTI) +#define ffrags VPATCH_NAME(WIFRGS) +#define colon VPATCH_NAME(WICOLON) +#define timepatch VPATCH_NAME(WITIME) +#define sucks VPATCH_NAME(WISUCKS) +#define par VPATCH_NAME(WIPAR) +#define killers VPATCH_NAME(WIKILRS) +#define victims VPATCH_NAME(WIVCTMS) +#define total VPATCH_NAME(WIMSTT) +#define num0 VPATCH_NAME(WINUM0) +#define num_patch(n) (num0 + (n)) +#else // %, : graphics -static patch_t* percent; -static patch_t* colon; - -// 0-9 graphic -static patch_t* num[10]; - +static vpatch_handle_large_t percent; +static vpatch_handle_large_t colon; // minus sign -static patch_t* wiminus; +static vpatch_handle_large_t wiminus; // "Finished!" graphics -static patch_t* finished; +static vpatch_handle_large_t finished; // "Entering" graphic -static patch_t* entering; +static vpatch_handle_large_t entering; // "secret" -static patch_t* sp_secret; +static vpatch_handle_large_t sp_secret; // "Kills", "Scrt", "Items", "Frags" -static patch_t* kills; -static patch_t* secret; -static patch_t* items; -static patch_t* frags; +static vpatch_handle_large_t kills; +static vpatch_handle_large_t secret; +static vpatch_handle_large_t items; +static vpatch_handle_large_t ffrags; // Time sucks. -static patch_t* timepatch; -static patch_t* par; -static patch_t* sucks; +static vpatch_handle_large_t timepatch; +static vpatch_handle_large_t par; +static vpatch_handle_large_t sucks; // "killers", "victims" -static patch_t* killers; -static patch_t* victims; +static vpatch_handle_large_t killers; +static vpatch_handle_large_t victims; // "Total", your face, your dead face -static patch_t* total; -static patch_t* star; -static patch_t* bstar; +static vpatch_handle_large_t total; +// 0-9 graphic +static vpatch_handle_large_t num[10]; +#define num_patch(n) num[n] +#endif +// You Are Here graphic +static vpatch_handle_small_t yah[3]; + +// splat +static vpatch_handle_small_t splat[2]; + + + +static vpatch_handle_small_t star; +static vpatch_handle_small_t bstar; + +#if !USE_WHD // "red P[1..MAXPLAYERS]" -static patch_t* p[MAXPLAYERS]; +static vpatch_handle_large_t p[MAXPLAYERS]; // "gray P[1..MAXPLAYERS]" -static patch_t* bp[MAXPLAYERS]; +static vpatch_handle_large_t bp[MAXPLAYERS]; +#else +// "red P[1..MAXPLAYERS]" +static vpatch_sequence_t p; - // Name graphics of each level (centered) -static patch_t** lnames; +// "gray P[1..MAXPLAYERS]" +static vpatch_sequence_t bp; +#endif +// Name graphics of each level (centered) +static vpatch_sequence_t lnames; +#define lname_patch(l) vpatch_n(lnames, l) // Buffer storing the backdrop -static patch_t *background; +#if !DOOM_TINY +should_be_const patch_t *background; +#else +lumpindex_t wi_background_patch_num; +#endif // // CODE // // slam background -void WI_slamBackground(void) +static void WI_slamBackground(void) { +#if !USE_WHD V_DrawPatch(0, 0, background); +#endif } // The ticker is used to detect keys @@ -411,7 +486,6 @@ boolean WI_Responder(event_t* ev) return false; } - // Draws " Finished!" void WI_drawLF(void) { @@ -420,18 +494,18 @@ void WI_drawLF(void) if (gamemode != commercial || wbs->last < NUMCMAPS) { // draw - V_DrawPatch((SCREENWIDTH - SHORT(lnames[wbs->last]->width))/2, - y, lnames[wbs->last]); + V_DrawPatch((SCREENWIDTH - rvpatch_width(lname_patch(wbs->last)))/2, + y, lname_patch(wbs->last)); // draw "Finished!" - y += (5*SHORT(lnames[wbs->last]->height))/4; + y += (5*rvpatch_height(lname_patch(wbs->last)))/4; - V_DrawPatch((SCREENWIDTH - SHORT(finished->width)) / 2, y, finished); + V_DrawPatch((SCREENWIDTH - rvpatch_width(finished)) / 2, y, finished); } else if (wbs->last == NUMCMAPS) { // MAP33 - draw "Finished!" only - V_DrawPatch((SCREENWIDTH - SHORT(finished->width)) / 2, y, finished); + V_DrawPatch((SCREENWIDTH - rvpatch_width(finished)) / 2, y, finished); } else if (wbs->last > NUMCMAPS) { @@ -440,38 +514,38 @@ void WI_drawLF(void) // bits of memory at this point, but let's try to be accurate // anyway. This deliberately triggers a V_DrawPatch error. +#if !USE_WHD // don't care about this behavior which causes compile warning with WHD patch_t tmp = { SCREENWIDTH, SCREENHEIGHT, 1, 1, { 0, 0, 0, 0, 0, 0, 0, 0 } }; V_DrawPatch(0, y, &tmp); +#endif } } - - // Draws "Entering " void WI_drawEL(void) { int y = WI_TITLEY; // draw "Entering" - V_DrawPatch((SCREENWIDTH - SHORT(entering->width))/2, + V_DrawPatch((SCREENWIDTH - rvpatch_width(entering))/2, y, entering); // draw level - y += (5*SHORT(lnames[wbs->next]->height))/4; + y += (5*rvpatch_height(lname_patch(wbs->next)))/4; - V_DrawPatch((SCREENWIDTH - SHORT(lnames[wbs->next]->width))/2, + V_DrawPatch((SCREENWIDTH - rvpatch_width(lname_patch(wbs->next)))/2, y, - lnames[wbs->next]); + lname_patch(wbs->next)); } void WI_drawOnLnode ( int n, - patch_t* c[] ) + vpatch_handle_small_t c[] ) { int i; @@ -484,10 +558,10 @@ WI_drawOnLnode i = 0; do { - left = lnodes[wbs->epsd][n].x - SHORT(c[i]->leftoffset); - top = lnodes[wbs->epsd][n].y - SHORT(c[i]->topoffset); - right = left + SHORT(c[i]->width); - bottom = top + SHORT(c[i]->height); + left = lnodes[wbs->epsd][n].x - patch_leftoffset(c[i]); + top = lnodes[wbs->epsd][n].y - patch_topoffset(c[i]); + right = left + rvpatch_width(c[i]); + bottom = top + rvpatch_height(c[i]); if (left >= 0 && right < SCREENWIDTH @@ -500,7 +574,7 @@ WI_drawOnLnode { i++; } - } while (!fits && i!=2 && c[i] != NULL); + } while (!fits && i!=2 && c[i]); if (fits && i<2) { @@ -539,7 +613,7 @@ void WI_initAnimatedBack(void) if (a->type == ANIM_ALWAYS) a->nexttic = bcnt + 1 + (M_Random()%a->period); else if (a->type == ANIM_RANDOM) - a->nexttic = bcnt + 1 + a->data2+(M_Random()%a->data1); + a->nexttic = bcnt + 1 + data_2(a)+(M_Random()%data_1(a)); else if (a->type == ANIM_LEVEL) a->nexttic = bcnt + 1; } @@ -575,7 +649,7 @@ void WI_updateAnimatedBack(void) if (a->ctr == a->nanims) { a->ctr = -1; - a->nexttic = bcnt+a->data2+(M_Random()%a->data1); + a->nexttic = bcnt+data_2(a)+(M_Random()%data_1(a)); } else a->nexttic = bcnt + a->period; break; @@ -583,7 +657,7 @@ void WI_updateAnimatedBack(void) case ANIM_LEVEL: // gawd-awful hack for level anims if (!(state == StatCount && i == 7) - && wbs->next == a->data1) + && wbs->next == data_1(a)) { a->ctr++; if (a->ctr == a->nanims) a->ctr--; @@ -612,8 +686,9 @@ void WI_drawAnimatedBack(void) { a = &anims[wbs->epsd][i]; - if (a->ctr >= 0) - V_DrawPatch(a->loc.x, a->loc.y, a->p[a->ctr]); + if (a->ctr >= 0) { + V_DrawPatch(a->loc.x, a->loc.y, vpatch_n(a->p, a->ctr)); + } } } @@ -633,7 +708,7 @@ WI_drawNum int digits ) { - int fontwidth = SHORT(num[0]->width); + int fontwidth = rvpatch_width(num_patch(0)); int neg; int temp; @@ -670,7 +745,7 @@ WI_drawNum while (digits--) { x -= fontwidth; - V_DrawPatch(x, y, num[ n % 10 ]); + V_DrawPatch(x, y, num_patch(n % 10)); n /= 10; } @@ -721,7 +796,7 @@ WI_drawTime do { n = (t / div) % 60; - x = WI_drawNum(x, y, n, 2) - SHORT(colon->width); + x = WI_drawNum(x, y, n, 2) - rvpatch_width(colon); div *= 60; // draw @@ -733,7 +808,7 @@ WI_drawTime else { // "sucks" - V_DrawPatch(x - SHORT(sucks->width), y, sucks); + V_DrawPatch(x - rvpatch_width(sucks), y, sucks); } } @@ -769,7 +844,6 @@ void WI_updateNoState(void) { static boolean snl_pointeron = false; - void WI_initShowNextLoc(void) { state = ShowNextLoc; @@ -923,7 +997,7 @@ void WI_updateDeathmatchStats(void) } - S_StartSound(0, sfx_barexp); + S_StartUnpositionedSound( sfx_barexp); dm_state = 4; } @@ -931,7 +1005,7 @@ void WI_updateDeathmatchStats(void) if (dm_state == 2) { if (!(bcnt&3)) - S_StartSound(0, sfx_pistol); + S_StartUnpositionedSound( sfx_pistol); stillticking = false; @@ -970,7 +1044,7 @@ void WI_updateDeathmatchStats(void) } if (!stillticking) { - S_StartSound(0, sfx_barexp); + S_StartUnpositionedSound( sfx_barexp); dm_state++; } @@ -979,7 +1053,7 @@ void WI_updateDeathmatchStats(void) { if (acceleratestage) { - S_StartSound(0, sfx_slop); + S_StartUnpositionedSound( sfx_slop); if ( gamemode == commercial) WI_initNoState(); @@ -1015,7 +1089,7 @@ void WI_drawDeathmatchStats(void) WI_drawLF(); // draw stat titles (top line) - V_DrawPatch(DM_TOTALSX-SHORT(total->width)/2, + V_DrawPatch(DM_TOTALSX-rvpatch_width(total)/2, DM_MATRIXY-WI_SPACINGY+10, total); @@ -1030,31 +1104,31 @@ void WI_drawDeathmatchStats(void) { if (playeringame[i]) { - V_DrawPatch(x-SHORT(p[i]->width)/2, + V_DrawPatch(x-rvpatch_width(vpatch_n(p,i))/2, DM_MATRIXY - WI_SPACINGY, - p[i]); + vpatch_n(p,i)); - V_DrawPatch(DM_MATRIXX-SHORT(p[i]->width)/2, + V_DrawPatch(DM_MATRIXX-rvpatch_width(vpatch_n(p,i))/2, y, - p[i]); + vpatch_n(p,i)); if (i == me) { - V_DrawPatch(x-SHORT(p[i]->width)/2, + V_DrawPatch(x-rvpatch_width(vpatch_n(p,i))/2, DM_MATRIXY - WI_SPACINGY, bstar); - V_DrawPatch(DM_MATRIXX-SHORT(p[i]->width)/2, + V_DrawPatch(DM_MATRIXX-rvpatch_width(vpatch_n(p,i))/2, y, star); } } else { - // V_DrawPatch(x-SHORT(bp[i]->width)/2, - // DM_MATRIXY - WI_SPACINGY, bp[i]); - // V_DrawPatch(DM_MATRIXX-SHORT(bp[i]->width)/2, - // y, bp[i]); + // V_DrawPatch(x-rvpatch_width(bvpatch_n(p,i))/2, + // DM_MATRIXY - WI_SPACINGY, bvpatch_n(p,i)); + // V_DrawPatch(DM_MATRIXX-rvpatch_width(bvpatch_n(p,i))/2, + // y, bvpatch_n(p,i)); } x += DM_SPACINGX; y += WI_SPACINGY; @@ -1062,7 +1136,7 @@ void WI_drawDeathmatchStats(void) // draw stats y = DM_MATRIXY+10; - w = SHORT(num[0]->width); + w = rvpatch_width(num_patch(0)); for (i=0 ; iwidth); + int pwidth = rvpatch_width(percent); WI_slamBackground(); @@ -1285,21 +1359,21 @@ void WI_drawNetgameStats(void) WI_drawLF(); // draw stat titles (top line) - V_DrawPatch(NG_STATSX+NG_SPACINGX-SHORT(kills->width), + V_DrawPatch(NG_STATSX+NG_SPACINGX-rvpatch_width(kills), NG_STATSY, kills); - V_DrawPatch(NG_STATSX+2*NG_SPACINGX-SHORT(items->width), + V_DrawPatch(NG_STATSX+2*NG_SPACINGX-rvpatch_width(items), NG_STATSY, items); - V_DrawPatch(NG_STATSX+3*NG_SPACINGX-SHORT(secret->width), + V_DrawPatch(NG_STATSX+3*NG_SPACINGX-rvpatch_width(secret), NG_STATSY, secret); if (dofrags) - V_DrawPatch(NG_STATSX+4*NG_SPACINGX-SHORT(frags->width), - NG_STATSY, frags); + V_DrawPatch(NG_STATSX+4*NG_SPACINGX-rvpatch_width(ffrags), + NG_STATSY, ffrags); // draw stats - y = NG_STATSY + SHORT(kills->height); + y = NG_STATSY + rvpatch_height(kills); for (i=0 ; iwidth), y, p[i]); + V_DrawPatch(x-rvpatch_width(vpatch_n(p,i)), y, vpatch_n(p,i)); if (i == me) - V_DrawPatch(x-SHORT(p[i]->width), y, star); + V_DrawPatch(x-rvpatch_width(vpatch_n(p,i)), y, star); x += NG_SPACINGX; WI_drawPercent(x-pwidth, y+10, cnt_kills[i]); x += NG_SPACINGX; @@ -1352,7 +1426,7 @@ void WI_updateStats(void) cnt_secret[0] = (plrs[me].ssecret * 100) / wbs->maxsecret; cnt_time = plrs[me].stime / TICRATE; cnt_par = wbs->partime / TICRATE; - S_StartSound(0, sfx_barexp); + S_StartUnpositionedSound( sfx_barexp); sp_state = 10; } @@ -1361,12 +1435,12 @@ void WI_updateStats(void) cnt_kills[0] += 2; if (!(bcnt&3)) - S_StartSound(0, sfx_pistol); + S_StartUnpositionedSound( sfx_pistol); if (cnt_kills[0] >= (plrs[me].skills * 100) / wbs->maxkills) { cnt_kills[0] = (plrs[me].skills * 100) / wbs->maxkills; - S_StartSound(0, sfx_barexp); + S_StartUnpositionedSound( sfx_barexp); sp_state++; } } @@ -1375,12 +1449,12 @@ void WI_updateStats(void) cnt_items[0] += 2; if (!(bcnt&3)) - S_StartSound(0, sfx_pistol); + S_StartUnpositionedSound( sfx_pistol); if (cnt_items[0] >= (plrs[me].sitems * 100) / wbs->maxitems) { cnt_items[0] = (plrs[me].sitems * 100) / wbs->maxitems; - S_StartSound(0, sfx_barexp); + S_StartUnpositionedSound( sfx_barexp); sp_state++; } } @@ -1389,12 +1463,12 @@ void WI_updateStats(void) cnt_secret[0] += 2; if (!(bcnt&3)) - S_StartSound(0, sfx_pistol); + S_StartUnpositionedSound( sfx_pistol); if (cnt_secret[0] >= (plrs[me].ssecret * 100) / wbs->maxsecret) { cnt_secret[0] = (plrs[me].ssecret * 100) / wbs->maxsecret; - S_StartSound(0, sfx_barexp); + S_StartUnpositionedSound( sfx_barexp); sp_state++; } } @@ -1402,7 +1476,7 @@ void WI_updateStats(void) else if (sp_state == 8) { if (!(bcnt&3)) - S_StartSound(0, sfx_pistol); + S_StartUnpositionedSound( sfx_pistol); cnt_time += 3; @@ -1417,7 +1491,7 @@ void WI_updateStats(void) if (cnt_time >= plrs[me].stime / TICRATE) { - S_StartSound(0, sfx_barexp); + S_StartUnpositionedSound( sfx_barexp); sp_state++; } } @@ -1426,7 +1500,7 @@ void WI_updateStats(void) { if (acceleratestage) { - S_StartSound(0, sfx_sgcock); + S_StartUnpositionedSound( sfx_sgcock); if (gamemode == commercial) WI_initNoState(); @@ -1448,9 +1522,9 @@ void WI_updateStats(void) void WI_drawStats(void) { // line height - int lh; + int lh; - lh = (3*SHORT(num[0]->height))/2; + lh = (3*rvpatch_height(num_patch(0)))/2; WI_slamBackground(); @@ -1549,10 +1623,12 @@ void WI_Ticker(void) WI_updateNoState(); break; } - +#if PICO_ON_DEVICE + I_UpdateSound(); +#endif } -typedef void (*load_callback_t)(const char *lumpname, patch_t **variable); +typedef void (*load_callback_t)(vpatchname_t lumpname, vpatch_handle_small_t *variable); // Common load/unload function. Iterates over all the graphics // lumps to be loaded/unloaded into memory. @@ -1563,153 +1639,160 @@ static void WI_loadUnloadData(load_callback_t callback) char name[9]; anim_t *a; - if (gamemode == commercial) - { - for (i=0 ; iepsd, i); + } +#else + lnames = VPATCH_CWILV00; +#endif + } else { +#if !DOOM_TINY + for (i = 0; i < NUMMAPS; i++) { + DEH_snprintf(name, 9, "WILV%d%d", wbs->epsd, i); callback(name, &lnames[i]); - } + } +#else + static_assert(NUMMAPS == 9, ""); + lnames = VPATCH_WILV00 + wbs->epsd * NUMMAPS; +#endif - // you are here - callback(DEH_String("WIURH0"), &yah[0]); + // you are here + callback(DEH_VPATCH_NAME(WIURH0), &yah[0]); - // you are here (alt.) - callback(DEH_String("WIURH1"), &yah[1]); + // you are here (alt.) + callback(DEH_VPATCH_NAME(WIURH1), &yah[1]); - // splat - callback(DEH_String("WISPLAT"), &splat[0]); + // splat + callback(DEH_VPATCH_NAME(WISPLAT), &splat[0]); - if (wbs->epsd < 3) - { - for (j=0;jepsd];j++) - { - a = &anims[wbs->epsd][j]; - for (i=0;inanims;i++) - { - // MONDO HACK! - if (wbs->epsd != 1 || j != 8) - { - // animations - DEH_snprintf(name, 9, "WIA%d%.2d%.2d", wbs->epsd, j, i); + if (wbs->epsd < 3) { +#if !DOOM_TINY + for (j = 0; j < NUMANIMS[wbs->epsd]; j++) { + a = &anims[wbs->epsd][j]; + for (i = 0; i < a->nanims; i++) { + // MONDO HACK! + if (wbs->epsd != 1 || j != 8) { + // animations + DEH_snprintf(name, 9, "WIA%d%.2d%.2d", wbs->epsd, j, i); callback(name, &a->p[i]); - } - else - { - // HACK ALERT! - a->p[i] = anims[1][4].p[i]; - } - } - } - } + } else { + // HACK ALERT! + a->p[i] = anims[1][4].p[i]; + } + } + } +#endif + } } +#if !DOOM_TINY // More hacks on minus sign. - callback(DEH_String("WIMINUS"), &wiminus); + callback(DEH_VPATCH_NAME(WIMINUS), &wiminus); - for (i=0;i<10;i++) - { - // numbers 0-9 - DEH_snprintf(name, 9, "WINUM%d", i); + for (i = 0; i < 10; i++) { + // numbers 0-9 + DEH_snprintf(name, 9, "WINUM%d", i); callback(name, &num[i]); } // percent sign - callback(DEH_String("WIPCNT"), &percent); + callback(DEH_VPATCH_NAME(WIPCNT), &percent); // "finished" - callback(DEH_String("WIF"), &finished); + callback(DEH_VPATCH_NAME(WIF), &finished); // "entering" - callback(DEH_String("WIENTER"), &entering); + callback(DEH_VPATCH_NAME(WIENTER), &entering); // "kills" - callback(DEH_String("WIOSTK"), &kills); + callback(DEH_VPATCH_NAME(WIOSTK), &kills); // "scrt" - callback(DEH_String("WIOSTS"), &secret); + callback(DEH_VPATCH_NAME(WIOSTS), &secret); - // "secret" - callback(DEH_String("WISCRT2"), &sp_secret); + // "secret" + callback(DEH_VPATCH_NAME(WISCRT2), &sp_secret); // french wad uses WIOBJ (?) - if (W_CheckNumForName(DEH_String("WIOBJ")) >= 0) - { - // "items" - if (netgame && !deathmatch) - callback(DEH_String("WIOBJ"), &items); - else - callback(DEH_String("WIOSTI"), &items); + if (W_CheckNumForName(DEH_String("WIOBJ")) >= 0) { + // "items" + if (netgame && !deathmatch) + callback(DEH_VPATCH_NAME(WIOBJ), &items); + else + callback(DEH_VPATCH_NAME(WIOSTI), &items); } else { - callback(DEH_String("WIOSTI"), &items); + callback(DEH_VPATCH_NAME(WIOSTI), &items); } // "frgs" - callback(DEH_String("WIFRGS"), &frags); + callback(DEH_VPATCH_NAME(WIFRGS), &ffrags); // ":" - callback(DEH_String("WICOLON"), &colon); + callback(DEH_VPATCH_NAME(WICOLON), &colon); // "time" - callback(DEH_String("WITIME"), &timepatch); + callback(DEH_VPATCH_NAME(WITIME), &timepatch); // "sucks" - callback(DEH_String("WISUCKS"), &sucks); + callback(DEH_VPATCH_NAME(WISUCKS), &sucks); // "par" - callback(DEH_String("WIPAR"), &par); + callback(DEH_VPATCH_NAME(WIPAR), &par); // "killers" (vertical) - callback(DEH_String("WIKILRS"), &killers); + callback(DEH_VPATCH_NAME(WIKILRS), &killers); // "victims" (horiz) - callback(DEH_String("WIVCTMS"), &victims); + callback(DEH_VPATCH_NAME(WIVCTMS), &victims); // "total" - callback(DEH_String("WIMSTT"), &total); + callback(DEH_VPATCH_NAME(WIMSTT), &total); +#endif - for (i=0 ; i= exe_ultimate && wbs->epsd == 3) - { + } else if (gameversion >= exe_ultimate && wbs->epsd == 3) { M_StringCopy(name, DEH_String("INTERPIC"), sizeof(name)); - } - else - { - DEH_snprintf(name, sizeof(name), "WIMAP%d", wbs->epsd); + } else { + DEH_snprintf(name, sizeof(name), "WIMAP%d", wbs->epsd); } // Draw backdrop and save to a temporary buffer +#if !DOOM_TINY callback(name, &background); +#else + wi_background_patch_num = W_GetNumForName(name); +#endif } -static void WI_loadCallback(const char *name, patch_t **variable) +static void WI_loadCallback(vpatchname_t name, vpatch_handle_small_t *variable) { +#if !DOOM_TINY *variable = W_CacheLumpName(name, PU_STATIC); +#else + *variable = name; +#endif } void WI_loadData(void) @@ -1717,13 +1800,17 @@ void WI_loadData(void) if (gamemode == commercial) { NUMCMAPS = 32; - lnames = (patch_t **) Z_Malloc(sizeof(patch_t*) * NUMCMAPS, - PU_STATIC, NULL); +#if !DOOM_TINY + lnames = (should_be_const patch_t **) Z_Malloc(sizeof(patch_t*) * NUMCMAPS, + PU_STATIC, 0); +#endif } else { - lnames = (patch_t **) Z_Malloc(sizeof(patch_t*) * NUMMAPS, - PU_STATIC, NULL); +#if !DOOM_TINY + lnames = (should_be_const patch_t **) Z_Malloc(sizeof(patch_t*) * NUMMAPS, + PU_STATIC, 0); +#endif } WI_loadUnloadData(WI_loadCallback); @@ -1732,16 +1819,18 @@ void WI_loadData(void) // them with the status bar code // your face - star = W_CacheLumpName(DEH_String("STFST01"), PU_STATIC); + star = VPATCH_HANDLE(VPATCH_NAME(STFST01)); // dead face - bstar = W_CacheLumpName(DEH_String("STFDEAD0"), PU_STATIC); + bstar = VPATCH_HANDLE(VPATCH_NAME(STFDEAD0)); } -static void WI_unloadCallback(const char *name, patch_t **variable) +static void WI_unloadCallback(vpatchname_t name, vpatch_handle_small_t *variable) { +#if !DOOM_TINY W_ReleaseLumpName(name); *variable = NULL; +#endif } void WI_unloadData(void) diff --git a/src/doom/wi_stuff.h b/src/doom/wi_stuff.h index 296571f7..da7e48fa 100644 --- a/src/doom/wi_stuff.h +++ b/src/doom/wi_stuff.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -45,4 +46,7 @@ void WI_Start(wbstartstruct_t* wbstartstruct); // Shut down the intermission screen void WI_End(void); +#if DOOM_TINY +extern lumpindex_t wi_background_patch_num; +#endif #endif diff --git a/src/doomtype.h b/src/doomtype.h index 8841a0cf..f6f0aa1f 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -34,6 +35,10 @@ #if !HAVE_DECL_STRCASECMP || !HAVE_DECL_STRNCASECMP #include +#if PICO_ON_DEVICE +extern int stricmp(const char *a, const char *b); +extern int strnicmp(const char *a, const char *b, size_t len); +#endif #if !HAVE_DECL_STRCASECMP #define strcasecmp stricmp #endif @@ -102,11 +107,14 @@ typedef bool boolean; #else -typedef enum -{ - false, - true -} boolean; +typedef uint8_t boolean; +#define false 0 +#define true 1 +//typedef enum +//{ +// false, +// true +//} boolean; #endif @@ -114,6 +122,15 @@ typedef uint8_t byte; typedef uint8_t pixel_t; typedef int16_t dpixel_t; +#if !DOOM_SMALL +typedef int isb_int8_t; +typedef int isb_int16_t; +typedef int isb_uint8_t; +#else +typedef int8_t isb_int8_t; +typedef int16_t isb_int16_t; +typedef uint8_t isb_uint8_t; +#endif #include #ifdef _WIN32 @@ -132,5 +149,97 @@ typedef int16_t dpixel_t; #define arrlen(array) (sizeof(array) / sizeof(*array)) +#if USE_FLAT_MAX_256 +typedef uint8_t flatnum_t; +#else +typedef int flatnum_t; +#endif + +#if !DOOM_SMALL +typedef int texnum_t; +#else +typedef short texnum_t; +#endif + +#if DOOM_CONST +#define should_be_const const +#else +#define should_be_const +#endif + +#if DOOM_SMALL +typedef uint8_t key_type_t; +typedef int8_t mouseb_type_t; // allow -1 +#else +typedef int key_type_t; +typedef int mouseb_type_t; +#endif + +#if DOOM_SMALL +typedef short lumpindex_t; +typedef uint16_t cardinal_t; +#else +typedef int lumpindex_t; +typedef int cardinal_t; +#endif +typedef const char * constcharstar; + +#if !FLOOR_CEILING_CLIP_8BIT +#define FLOOR_CEILING_CLIP_OFFSET 0 +typedef short floor_ceiling_clip_t; +#else +#define FLOOR_CEILING_CLIP_OFFSET 1 +typedef uint8_t floor_ceiling_clip_t; +#endif + +#if PICO_ON_DEVICE +#include +typedef uint16_t shortptr_t; +static inline void *shortptr_to_ptr(shortptr_t s) { + return s ? (void *)(0x20000000 + s * 4) : NULL; +} +static inline shortptr_t ptr_to_shortptr(void *p) { + if (!p) return 0; + uintptr_t v = (uintptr_t)p; + assert(v>=0x20000004 && v <= 0x20040000 && !(v&3)); + return (shortptr_t) ((v << 14u)>>16u); +} +#else +typedef void *shortptr_t; +#define shortptr_to_ptr(s) (s) +#define ptr_to_shortptr(s) (s) +#endif + +#if DOOM_TINY +#define stderr_print(...) printf(__VA_ARGS__) +#else +#define stderr_print(...) fprintf(stderr, __VA_ARGS__) +#endif + + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif + +#ifndef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) +#endif + +#if DOOM_SMALL && !defined(USE_ROWAD) +#define USE_ROWAD 1 +#endif + +#if USE_ROWAD +#define rowad_const const +#define hack_rowad_p(type, instance, member) ((type *)instance)->member +#else +#define rowad_const +#define hack_rowad_p(type, instance, member) (instance)->member +#endif + +#ifndef count_of +#define count_of(a) (sizeof(a)/sizeof((a)[0])) +#endif + #endif diff --git a/src/gusconf.c b/src/gusconf.c index c3d0c54e..1cc5dc6c 100644 --- a/src/gusconf.c +++ b/src/gusconf.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -30,6 +31,7 @@ #include "w_wad.h" #include "z_zone.h" +#if !NO_USE_GUS #define MAX_INSTRUMENTS 256 typedef struct @@ -40,7 +42,7 @@ typedef struct unsigned int count; } gus_config_t; -char *gus_patch_path = ""; +constcharstar gus_patch_path = ""; int gus_ram_kb = 1024; static unsigned int MappingIndex(void) @@ -217,7 +219,7 @@ static char *ReadDMXConfig(void) } len = W_LumpLength(lumpnum); - data = Z_Malloc(len + 1, PU_STATIC, NULL); + data = Z_Malloc(len + 1, PU_STATIC, 0); W_ReadLump(lumpnum, data); data[len] = '\0'; @@ -299,3 +301,4 @@ boolean GUS_WriteConfig(char *path) return result; } +#endif \ No newline at end of file diff --git a/src/gusconf.h b/src/gusconf.h index 4efbe995..c80b381b 100644 --- a/src/gusconf.h +++ b/src/gusconf.h @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -20,7 +21,7 @@ #include "doomtype.h" -extern char *gus_patch_path; +extern constcharstar gus_patch_path; extern int gus_ram_kb; boolean GUS_WriteConfig(char *path); diff --git a/src/heretic/CMakeLists.txt b/src/heretic/CMakeLists.txt index 9f12bde0..b221560c 100644 --- a/src/heretic/CMakeLists.txt +++ b/src/heretic/CMakeLists.txt @@ -1,4 +1,6 @@ -add_library(heretic STATIC +cmake_policy(SET CMP0076 NEW) +add_library(heretic INTERFACE) +target_sources(heretic INTERFACE am_data.h am_map.c am_map.h ct_chat.c ct_chat.h @@ -53,5 +55,5 @@ add_library(heretic STATIC sounds.c sounds.h s_sound.c s_sound.h) -target_include_directories(heretic PRIVATE "../" "${CMAKE_CURRENT_BINARY_DIR}/../../") -target_link_libraries(heretic textscreen SDL2::SDL2 SDL2::mixer SDL2::net) +target_include_directories(heretic INTERFACE "../" "${CMAKE_CURRENT_BINARY_DIR}/../../") +target_link_libraries(heretic INTERFACE textscreen SDL2::SDL2 SDL2::mixer SDL2::net) diff --git a/src/heretic/am_map.c b/src/heretic/am_map.c index be2b0dbb..55db5d98 100644 --- a/src/heretic/am_map.c +++ b/src/heretic/am_map.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1352,10 +1353,10 @@ void AM_rotate(fixed_t * x, fixed_t * y, angle_t a) { fixed_t tmpx; - tmpx = FixedMul(*x, finecosine[a >> ANGLETOFINESHIFT]) - - FixedMul(*y, finesine[a >> ANGLETOFINESHIFT]); - *y = FixedMul(*x, finesine[a >> ANGLETOFINESHIFT]) - + FixedMul(*y, finecosine[a >> ANGLETOFINESHIFT]); + tmpx = FixedMul(*x, finecosine(a >> ANGLETOFINESHIFT)) + - FixedMul(*y, finesine(a >> ANGLETOFINESHIFT)); + *y = FixedMul(*x, finesine(a >> ANGLETOFINESHIFT)) + + FixedMul(*y, finecosine(a >> ANGLETOFINESHIFT)); *x = tmpx; } diff --git a/src/heretic/d_net.c b/src/heretic/d_net.c index 68e12931..5892c328 100644 --- a/src/heretic/d_net.c +++ b/src/heretic/d_net.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -163,7 +164,7 @@ static void InitConnectData(net_connect_data_t *connect_data) // Game type fields: - connect_data->gamemode = gamemode; + connect_data->_gamemode = gamemode; connect_data->gamemission = heretic; // Are we recording a demo? Possibly set lowres turn mode diff --git a/src/heretic/g_game.c b/src/heretic/g_game.c index a2a0964e..39350417 100644 --- a/src/heretic/g_game.c +++ b/src/heretic/g_game.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1270,7 +1271,7 @@ boolean G_CheckSpot(int playernum, mapthing_t * mthing) ss = R_PointInSubsector(x, y); an = ((unsigned) ANG45 * (mthing->angle / 45)) >> ANGLETOFINESHIFT; - mo = P_SpawnMobj(x + 20 * finecosine[an], y + 20 * finesine[an], + mo = P_SpawnMobj(x + 20 * finecosine(an), y + 20 * finesine(an), ss->sector->floorheight + TELEFOGHEIGHT, MT_TFOG); if (players[consoleplayer].viewz != 1) diff --git a/src/heretic/mn_menu.c b/src/heretic/mn_menu.c index 92fa430a..51b43562 100644 --- a/src/heretic/mn_menu.c +++ b/src/heretic/mn_menu.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1301,7 +1302,7 @@ boolean MN_Responder(event_t * event) slottextloaded = false; //reload the slot text, when needed quicksave = -1; P_SetMessage(&players[consoleplayer], - "CHOOSE A QUICKSAVE SLOT", true); + "CHOOSE A QUICKSAVE SLOT_RENDER", true); } else { @@ -1347,7 +1348,7 @@ boolean MN_Responder(event_t * event) slottextloaded = false; //reload the slot text, when needed quickload = -1; P_SetMessage(&players[consoleplayer], - "CHOOSE A QUICKLOAD SLOT", true); + "CHOOSE A QUICKLOAD SLOT_RENDER", true); } else { diff --git a/src/heretic/p_enemy.c b/src/heretic/p_enemy.c index 882be721..83c9da72 100644 --- a/src/heretic/p_enemy.c +++ b/src/heretic/p_enemy.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -967,8 +968,8 @@ void A_ImpMsAttack(mobj_t * actor) S_StartSound(actor, actor->info->attacksound); A_FaceTarget(actor); an = actor->angle >> ANGLETOFINESHIFT; - actor->momx = FixedMul(12 * FRACUNIT, finecosine[an]); - actor->momy = FixedMul(12 * FRACUNIT, finesine[an]); + actor->momx = FixedMul(12 * FRACUNIT, finecosine(an)); + actor->momy = FixedMul(12 * FRACUNIT, finesine(an)); dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y); dist = dist / (12 * FRACUNIT); if (dist < 1) @@ -1636,8 +1637,8 @@ void A_MinotaurDecide(mobj_t * actor) actor->flags |= MF_SKULLFLY; A_FaceTarget(actor); angle = actor->angle >> ANGLETOFINESHIFT; - actor->momx = FixedMul(MNTR_CHARGE_SPEED, finecosine[angle]); - actor->momy = FixedMul(MNTR_CHARGE_SPEED, finesine[angle]); + actor->momx = FixedMul(MNTR_CHARGE_SPEED, finecosine(angle)); + actor->momy = FixedMul(MNTR_CHARGE_SPEED, finesine(angle)); actor->special1.i = 35 / 2; // Charge duration } else if (target->z == target->floorz @@ -1927,8 +1928,8 @@ void A_HeadIceImpact(mobj_t * ice) shard->target = ice->target; shard->angle = angle; angle >>= ANGLETOFINESHIFT; - shard->momx = FixedMul(shard->info->speed, finecosine[angle]); - shard->momy = FixedMul(shard->info->speed, finesine[angle]); + shard->momx = FixedMul(shard->info->speed, finecosine(angle)); + shard->momy = FixedMul(shard->info->speed, finesine(angle)); shard->momz = (fixed_t)(-.6 * FRACUNIT); P_CheckMissileSpawn(shard); } @@ -2521,8 +2522,8 @@ void A_VolcanoBlast(mobj_t * volcano) angle = P_Random() << 24; blast->angle = angle; angle >>= ANGLETOFINESHIFT; - blast->momx = FixedMul(1 * FRACUNIT, finecosine[angle]); - blast->momy = FixedMul(1 * FRACUNIT, finesine[angle]); + blast->momx = FixedMul(1 * FRACUNIT, finecosine(angle)); + blast->momy = FixedMul(1 * FRACUNIT, finesine(angle)); blast->momz = (fixed_t)(2.5 * FRACUNIT) + (P_Random() << 10); S_StartSound(blast, sfx_volsht); P_CheckMissileSpawn(blast); @@ -2556,8 +2557,8 @@ void A_VolcBallImpact(mobj_t * ball) angle = i * ANG90; tiny->angle = angle; angle >>= ANGLETOFINESHIFT; - tiny->momx = FixedMul((fixed_t)(FRACUNIT * .7), finecosine[angle]); - tiny->momy = FixedMul((fixed_t)(FRACUNIT * .7), finesine[angle]); + tiny->momx = FixedMul((fixed_t)(FRACUNIT * .7), finecosine(angle)); + tiny->momy = FixedMul((fixed_t)(FRACUNIT * .7), finesine(angle)); tiny->momz = FRACUNIT + (P_Random() << 9); P_CheckMissileSpawn(tiny); } diff --git a/src/heretic/p_inter.c b/src/heretic/p_inter.c index 78195687..0449e3cf 100644 --- a/src/heretic/p_inter.c +++ b/src/heretic/p_inter.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -969,8 +970,8 @@ void P_MinotaurSlam(mobj_t * source, mobj_t * target) angle = R_PointToAngle2(source->x, source->y, target->x, target->y); angle >>= ANGLETOFINESHIFT; thrust = 16 * FRACUNIT + (P_Random() << 10); - target->momx += FixedMul(thrust, finecosine[angle]); - target->momy += FixedMul(thrust, finesine[angle]); + target->momx += FixedMul(thrust, finecosine(angle)); + target->momy += FixedMul(thrust, finesine(angle)); P_DamageMobj(target, NULL, NULL, HITDICE(6)); if (target->player) { @@ -1365,8 +1366,8 @@ void P_DamageMobj && source->player->readyweapon == wp_staff) { // Staff power level 2 - target->momx += FixedMul(10 * FRACUNIT, finecosine[ang]); - target->momy += FixedMul(10 * FRACUNIT, finesine[ang]); + target->momx += FixedMul(10 * FRACUNIT, finecosine(ang)); + target->momy += FixedMul(10 * FRACUNIT, finesine(ang)); if (!(target->flags & MF_NOGRAVITY)) { target->momz += 5 * FRACUNIT; @@ -1374,8 +1375,8 @@ void P_DamageMobj } else { - target->momx += FixedMul(thrust, finecosine[ang]); - target->momy += FixedMul(thrust, finesine[ang]); + target->momx += FixedMul(thrust, finecosine(ang)); + target->momy += FixedMul(thrust, finesine(ang)); } } diff --git a/src/heretic/p_map.c b/src/heretic/p_map.c index 8afcf860..84b838b8 100644 --- a/src/heretic/p_map.c +++ b/src/heretic/p_map.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -701,7 +702,7 @@ void P_FakeZMovement(mobj_t * mo) if (mo->player && mo->flags2 & MF2_FLY && !(mo->z <= mo->floorz) && leveltime & 2) { - mo->z += finesine[(FINEANGLES / 20 * leveltime >> 2) & FINEMASK]; + mo->z += finesine((FINEANGLES / 20 * leveltime >> 2) & FINEMASK); } // @@ -990,9 +991,9 @@ void P_HitSlideLine(line_t * ld) deltaangle >>= ANGLETOFINESHIFT; movelen = P_AproxDistance(tmxmove, tmymove); - newlen = FixedMul(movelen, finecosine[deltaangle]); - tmxmove = FixedMul(newlen, finecosine[lineangle]); - tmymove = FixedMul(newlen, finesine[lineangle]); + newlen = FixedMul(movelen, finecosine(deltaangle)); + tmxmove = FixedMul(newlen, finecosine(lineangle)); + tmymove = FixedMul(newlen, finesine(lineangle)); } /* @@ -1397,8 +1398,8 @@ fixed_t P_AimLineAttack(mobj_t * t1, angle_t angle, fixed_t distance) angle >>= ANGLETOFINESHIFT; shootthing = t1; - x2 = t1->x + (distance >> FRACBITS) * finecosine[angle]; - y2 = t1->y + (distance >> FRACBITS) * finesine[angle]; + x2 = t1->x + (distance >> FRACBITS) * finecosine(angle); + y2 = t1->y + (distance >> FRACBITS) * finesine(angle); shootz = t1->z + (t1->height >> 1) + 8 * FRACUNIT; topslope = 100 * FRACUNIT / 160; // can't shoot outside view angles bottomslope = -100 * FRACUNIT / 160; @@ -1433,8 +1434,8 @@ void P_LineAttack(mobj_t * t1, angle_t angle, fixed_t distance, fixed_t slope, angle >>= ANGLETOFINESHIFT; shootthing = t1; la_damage = damage; - x2 = t1->x + (distance >> FRACBITS) * finecosine[angle]; - y2 = t1->y + (distance >> FRACBITS) * finesine[angle]; + x2 = t1->x + (distance >> FRACBITS) * finecosine(angle); + y2 = t1->y + (distance >> FRACBITS) * finesine(angle); shootz = t1->z + (t1->height >> 1) + 8 * FRACUNIT; if (t1->flags2 & MF2_FEETARECLIPPED) { @@ -1500,8 +1501,8 @@ void P_UseLines(player_t * player) angle = player->mo->angle >> ANGLETOFINESHIFT; x1 = player->mo->x; y1 = player->mo->y; - x2 = x1 + (USERANGE >> FRACBITS) * finecosine[angle]; - y2 = y1 + (USERANGE >> FRACBITS) * finesine[angle]; + x2 = x1 + (USERANGE >> FRACBITS) * finecosine(angle); + y2 = y1 + (USERANGE >> FRACBITS) * finesine(angle); P_PathTraverse(x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse); } diff --git a/src/heretic/p_mobj.c b/src/heretic/p_mobj.c index c0759848..18e173a4 100644 --- a/src/heretic/p_mobj.c +++ b/src/heretic/p_mobj.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -150,8 +151,8 @@ void P_FloorBounceMissile(mobj_t * mo) void P_ThrustMobj(mobj_t * mo, angle_t angle, fixed_t move) { angle >>= ANGLETOFINESHIFT; - mo->momx += FixedMul(move, finecosine[angle]); - mo->momy += FixedMul(move, finesine[angle]); + mo->momx += FixedMul(move, finecosine(angle)); + mo->momy += FixedMul(move, finesine(angle)); } //---------------------------------------------------------------------------- @@ -247,8 +248,8 @@ boolean P_SeekerMissile(mobj_t * actor, angle_t thresh, angle_t turnMax) actor->angle -= delta; } angle = actor->angle >> ANGLETOFINESHIFT; - actor->momx = FixedMul(actor->info->speed, finecosine[angle]); - actor->momy = FixedMul(actor->info->speed, finesine[angle]); + actor->momx = FixedMul(actor->info->speed, finecosine(angle)); + actor->momy = FixedMul(actor->info->speed, finesine(angle)); if (actor->z + actor->height < target->z || target->z + target->height < actor->z) { // Need to seek vertically @@ -508,7 +509,7 @@ void P_ZMovement(mobj_t * mo) if (mo->player && mo->flags2 & MF2_FLY && !(mo->z <= mo->floorz) && leveltime & 2) { - mo->z += finesine[(FINEANGLES / 20 * leveltime >> 2) & FINEMASK]; + mo->z += finesine((FINEANGLES / 20 * leveltime >> 2) & FINEMASK); } // @@ -1419,8 +1420,8 @@ mobj_t *P_SpawnMissile(mobj_t * source, mobj_t * dest, mobjtype_t type) } th->angle = an; an >>= ANGLETOFINESHIFT; - th->momx = FixedMul(th->info->speed, finecosine[an]); - th->momy = FixedMul(th->info->speed, finesine[an]); + th->momx = FixedMul(th->info->speed, finecosine(an)); + th->momy = FixedMul(th->info->speed, finesine(an)); dist = P_AproxDistance(dest->x - source->x, dest->y - source->y); dist = dist / th->info->speed; if (dist < 1) @@ -1473,8 +1474,8 @@ mobj_t *P_SpawnMissileAngle(mobj_t * source, mobjtype_t type, mo->target = source; // Originator mo->angle = angle; angle >>= ANGLETOFINESHIFT; - mo->momx = FixedMul(mo->info->speed, finecosine[angle]); - mo->momy = FixedMul(mo->info->speed, finesine[angle]); + mo->momx = FixedMul(mo->info->speed, finecosine(angle)); + mo->momy = FixedMul(mo->info->speed, finesine(angle)); mo->momz = momz; return (P_CheckMissileSpawn(mo) ? mo : NULL); } @@ -1527,9 +1528,9 @@ mobj_t *P_SpawnPlayerMissile(mobj_t * source, mobjtype_t type) MissileMobj->target = source; MissileMobj->angle = an; MissileMobj->momx = FixedMul(MissileMobj->info->speed, - finecosine[an >> ANGLETOFINESHIFT]); + finecosine(an >> ANGLETOFINESHIFT)); MissileMobj->momy = FixedMul(MissileMobj->info->speed, - finesine[an >> ANGLETOFINESHIFT]); + finesine(an >> ANGLETOFINESHIFT)); MissileMobj->momz = FixedMul(MissileMobj->info->speed, slope); if (MissileMobj->type == MT_BLASTERFX1) { // Ultra-fast ripper spawning missile @@ -1598,8 +1599,8 @@ mobj_t *P_SPMAngle(mobj_t * source, mobjtype_t type, angle_t angle) } th->target = source; th->angle = an; - th->momx = FixedMul(th->info->speed, finecosine[an >> ANGLETOFINESHIFT]); - th->momy = FixedMul(th->info->speed, finesine[an >> ANGLETOFINESHIFT]); + th->momx = FixedMul(th->info->speed, finecosine(an >> ANGLETOFINESHIFT)); + th->momy = FixedMul(th->info->speed, finesine(an >> ANGLETOFINESHIFT)); th->momz = FixedMul(th->info->speed, slope); return (P_CheckMissileSpawn(th) ? th : NULL); } diff --git a/src/heretic/p_pspr.c b/src/heretic/p_pspr.c index 7daeebc8..1118c96b 100644 --- a/src/heretic/p_pspr.c +++ b/src/heretic/p_pspr.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -370,10 +371,10 @@ void P_CalcSwing (player_t *player) swing = player->bob; angle = (FINEANGLES/70*leveltime)&FINEMASK; - swingx = FixedMul ( swing, finesine[angle]); + swingx = FixedMul ( swing, finesine(angle)); angle = (FINEANGLES/70*leveltime+FINEANGLES/2)&FINEMASK; - swingy = -FixedMul ( swingx, finesine[angle]); + swingy = -FixedMul ( swingx, finesine(angle)); } */ @@ -639,9 +640,9 @@ void A_WeaponReady(player_t * player, pspdef_t * psp) // Bob the weapon based on movement speed. angle = (128 * leveltime) & FINEMASK; - psp->sx = FRACUNIT + FixedMul(player->bob, finecosine[angle]); + psp->sx = FRACUNIT + FixedMul(player->bob, finecosine(angle)); angle &= FINEANGLES / 2 - 1; - psp->sy = WEAPONTOP + FixedMul(player->bob, finesine[angle]); + psp->sy = WEAPONTOP + FixedMul(player->bob, finesine(angle)); } //--------------------------------------------------------------------------- @@ -1080,9 +1081,9 @@ void A_FireMacePL1B(player_t * player, pspdef_t * psp) ball->z += (player->lookdir) << (FRACBITS - 4); angle >>= ANGLETOFINESHIFT; ball->momx = (pmo->momx >> 1) - + FixedMul(ball->info->speed, finecosine[angle]); + + FixedMul(ball->info->speed, finecosine(angle)); ball->momy = (pmo->momy >> 1) - + FixedMul(ball->info->speed, finesine[angle]); + + FixedMul(ball->info->speed, finesine(angle)); S_StartSound(ball, sfx_lobsht); P_CheckMissileSpawn(ball); } @@ -1139,8 +1140,8 @@ void A_MacePL1Check(mobj_t * ball) ball->special1.i = 0; ball->flags2 |= MF2_LOGRAV; angle = ball->angle >> ANGLETOFINESHIFT; - ball->momx = FixedMul(7 * FRACUNIT, finecosine[angle]); - ball->momy = FixedMul(7 * FRACUNIT, finesine[angle]); + ball->momx = FixedMul(7 * FRACUNIT, finecosine(angle)); + ball->momy = FixedMul(7 * FRACUNIT, finesine(angle)); ball->momz -= ball->momz >> 1; } @@ -1207,9 +1208,9 @@ void A_MaceBallImpact2(mobj_t * ball) tiny->angle = angle; angle >>= ANGLETOFINESHIFT; tiny->momx = (ball->momx >> 1) + FixedMul(ball->momz - FRACUNIT, - finecosine[angle]); + finecosine(angle)); tiny->momy = (ball->momy >> 1) + FixedMul(ball->momz - FRACUNIT, - finesine[angle]); + finesine(angle)); tiny->momz = ball->momz; P_CheckMissileSpawn(tiny); @@ -1219,9 +1220,9 @@ void A_MaceBallImpact2(mobj_t * ball) tiny->angle = angle; angle >>= ANGLETOFINESHIFT; tiny->momx = (ball->momx >> 1) + FixedMul(ball->momz - FRACUNIT, - finecosine[angle]); + finecosine(angle)); tiny->momy = (ball->momy >> 1) + FixedMul(ball->momz - FRACUNIT, - finesine[angle]); + finesine(angle)); tiny->momz = ball->momz; P_CheckMissileSpawn(tiny); } @@ -1308,8 +1309,8 @@ void A_DeathBallImpact(mobj_t * ball) { ball->angle = angle; angle >>= ANGLETOFINESHIFT; - ball->momx = FixedMul(ball->info->speed, finecosine[angle]); - ball->momy = FixedMul(ball->info->speed, finesine[angle]); + ball->momx = FixedMul(ball->info->speed, finecosine(angle)); + ball->momy = FixedMul(ball->info->speed, finesine(angle)); } P_SetMobjState(ball, ball->info->spawnstate); S_StartSound(ball, sfx_pstop); @@ -1341,8 +1342,8 @@ void A_SpawnRippers(mobj_t * actor) ripper->target = actor->target; ripper->angle = angle; angle >>= ANGLETOFINESHIFT; - ripper->momx = FixedMul(ripper->info->speed, finecosine[angle]); - ripper->momy = FixedMul(ripper->info->speed, finesine[angle]); + ripper->momx = FixedMul(ripper->info->speed, finecosine(angle)); + ripper->momy = FixedMul(ripper->info->speed, finesine(angle)); P_CheckMissileSpawn(ripper); } } @@ -1620,8 +1621,8 @@ void A_FirePhoenixPL1(player_t * player, pspdef_t * psp) //P_SpawnPlayerMissile(player->mo, MT_MNTRFX2); angle = player->mo->angle + ANG180; angle >>= ANGLETOFINESHIFT; - player->mo->momx += FixedMul(4 * FRACUNIT, finecosine[angle]); - player->mo->momy += FixedMul(4 * FRACUNIT, finesine[angle]); + player->mo->momx += FixedMul(4 * FRACUNIT, finecosine(angle)); + player->mo->momy += FixedMul(4 * FRACUNIT, finesine(angle)); } //---------------------------------------------------------------------------- @@ -1639,14 +1640,14 @@ void A_PhoenixPuff(mobj_t * actor) puff = P_SpawnMobj(actor->x, actor->y, actor->z, MT_PHOENIXPUFF); angle = actor->angle + ANG90; angle >>= ANGLETOFINESHIFT; - puff->momx = FixedMul((fixed_t)(FRACUNIT * 1.3), finecosine[angle]); - puff->momy = FixedMul((fixed_t)(FRACUNIT * 1.3), finesine[angle]); + puff->momx = FixedMul((fixed_t)(FRACUNIT * 1.3), finecosine(angle)); + puff->momy = FixedMul((fixed_t)(FRACUNIT * 1.3), finesine(angle)); puff->momz = 0; puff = P_SpawnMobj(actor->x, actor->y, actor->z, MT_PHOENIXPUFF); angle = actor->angle - ANG90; angle >>= ANGLETOFINESHIFT; - puff->momx = FixedMul((fixed_t)(FRACUNIT * 1.3), finecosine[angle]); - puff->momy = FixedMul((fixed_t)(FRACUNIT * 1.3), finesine[angle]); + puff->momx = FixedMul((fixed_t)(FRACUNIT * 1.3), finecosine(angle)); + puff->momy = FixedMul((fixed_t)(FRACUNIT * 1.3), finesine(angle)); puff->momz = 0; } @@ -1708,9 +1709,9 @@ void A_FirePhoenixPL2(player_t * player, pspdef_t * psp) mo->target = pmo; mo->angle = angle; mo->momx = pmo->momx + FixedMul(mo->info->speed, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = pmo->momy + FixedMul(mo->info->speed, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->momz = FixedMul(mo->info->speed, slope); if (!player->refire || !(leveltime % 38)) { diff --git a/src/heretic/p_telept.c b/src/heretic/p_telept.c index 640cee50..8c87d18b 100644 --- a/src/heretic/p_telept.c +++ b/src/heretic/p_telept.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -82,8 +83,8 @@ boolean P_Teleport(mobj_t * thing, fixed_t x, fixed_t y, angle_t angle) fog = P_SpawnMobj(oldx, oldy, oldz + fogDelta, MT_TFOG); S_StartSound(fog, sfx_telept); an = angle >> ANGLETOFINESHIFT; - fog = P_SpawnMobj(x + 20 * finecosine[an], - y + 20 * finesine[an], thing->z + fogDelta, MT_TFOG); + fog = P_SpawnMobj(x + 20 * finecosine(an), + y + 20 * finesine(an), thing->z + fogDelta, MT_TFOG); S_StartSound(fog, sfx_telept); if (thing->player && !thing->player->powers[pw_weaponlevel2]) { // Freeze player for about .5 sec @@ -102,8 +103,8 @@ boolean P_Teleport(mobj_t * thing, fixed_t x, fixed_t y, angle_t angle) if (thing->flags & MF_MISSILE) { angle >>= ANGLETOFINESHIFT; - thing->momx = FixedMul(thing->info->speed, finecosine[angle]); - thing->momy = FixedMul(thing->info->speed, finesine[angle]); + thing->momx = FixedMul(thing->info->speed, finecosine(angle)); + thing->momy = FixedMul(thing->info->speed, finesine(angle)); } else { diff --git a/src/heretic/p_user.c b/src/heretic/p_user.c index 9c22d0b7..7b349161 100644 --- a/src/heretic/p_user.c +++ b/src/heretic/p_user.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -63,18 +64,18 @@ void P_Thrust(player_t * player, angle_t angle, fixed_t move) angle >>= ANGLETOFINESHIFT; if (player->powers[pw_flight] && !(player->mo->z <= player->mo->floorz)) { - player->mo->momx += FixedMul(move, finecosine[angle]); - player->mo->momy += FixedMul(move, finesine[angle]); + player->mo->momx += FixedMul(move, finecosine(angle)); + player->mo->momy += FixedMul(move, finesine(angle)); } else if (player->mo->subsector->sector->special == 15) // Friction_Low { - player->mo->momx += FixedMul(move >> 2, finecosine[angle]); - player->mo->momy += FixedMul(move >> 2, finesine[angle]); + player->mo->momx += FixedMul(move >> 2, finecosine(angle)); + player->mo->momy += FixedMul(move >> 2, finesine(angle)); } else { - player->mo->momx += FixedMul(move, finecosine[angle]); - player->mo->momy += FixedMul(move, finesine[angle]); + player->mo->momx += FixedMul(move, finecosine(angle)); + player->mo->momy += FixedMul(move, finesine(angle)); } } @@ -119,7 +120,7 @@ void P_CalcHeight(player_t * player) } angle = (FINEANGLES / 20 * leveltime) & FINEMASK; - bob = FixedMul(player->bob / 2, finesine[angle]); + bob = FixedMul(player->bob / 2, finesine(angle)); // // move viewheight @@ -517,8 +518,8 @@ boolean P_UndoPlayerChicken(player_t * player) player->health = mo->health = MAXHEALTH; player->mo = mo; angle >>= ANGLETOFINESHIFT; - fog = P_SpawnMobj(x + 20 * finecosine[angle], - y + 20 * finesine[angle], z + TELEFOGHEIGHT, MT_TFOG); + fog = P_SpawnMobj(x + 20 * finecosine(angle), + y + 20 * finesine(angle), z + TELEFOGHEIGHT, MT_TFOG); S_StartSound(fog, sfx_telept); P_PostChickenWeapon(player, weapon); return (true); @@ -988,8 +989,8 @@ boolean P_UseArtifact(player_t * player, artitype_t arti) // (player->mo->flags2 & (MF2_FEETARECLIPPED != 0)), // Which simplifies to: // (player->mo->flags2 & 1), - mo = P_SpawnMobj(player->mo->x + 24 * finecosine[angle], - player->mo->y + 24 * finesine[angle], + mo = P_SpawnMobj(player->mo->x + 24 * finecosine(angle), + player->mo->y + 24 * finesine(angle), player->mo->z - 15 * FRACUNIT * (player->mo->flags2 & 1), MT_FIREBOMB); diff --git a/src/heretic/r_main.c b/src/heretic/r_main.c index d54bb051..cb8df599 100644 --- a/src/heretic/r_main.c +++ b/src/heretic/r_main.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -186,6 +187,34 @@ int R_PointOnSegSide(fixed_t x, fixed_t y, seg_t * line) return 1; // back side } +// to get a global angle from cartesian coordinates, the coordinates are +// flipped until they are in the first octant of the coordinate system, then +// the y (<=x) is scaled and divided by x to get a tangent (slope) value +// which is looked up in the tantoangle[] table. The +1 size is to handle +// the case when x==y without additional checking. + +static int SlopeDiv(unsigned int num, unsigned int den) +{ + unsigned ans; + + if (den < 512) + { + return SLOPERANGE; + } + else + { + ans = (num << 3) / (den >> 8); + + if (ans <= SLOPERANGE) + { + return ans; + } + else + { + return SLOPERANGE; + } + } +} /* =============================================================================== @@ -270,7 +299,7 @@ fixed_t R_PointToDist(fixed_t x, fixed_t y) angle = (tantoangle[FixedDiv(dy, dx) >> DBITS] + ANG90) >> ANGLETOFINESHIFT; - dist = FixedDiv(dx, finesine[angle]); // use as cosine + dist = FixedDiv(dx, finesine(angle)); // use as cosine return dist; } @@ -328,9 +357,9 @@ fixed_t R_ScaleFromGlobalAngle(angle_t visangle) fixed_t dist, z; fixed_t sinv, cosv; - sinv = finesine[(visangle - rw_normalangle) >> ANGLETOFINESHIFT]; + sinv = finesine((visangle - rw_normalangle) >> ANGLETOFINESHIFT); dist = FixedDiv(rw_distance, sinv); - cosv = finecosine[(viewangle - visangle) >> ANGLETOFINESHIFT]; + cosv = finecosine((viewangle - visangle) >> ANGLETOFINESHIFT); z = abs(FixedMul(dist, cosv)); scale = FixedDiv(projection, z); return scale; @@ -340,8 +369,8 @@ fixed_t R_ScaleFromGlobalAngle(angle_t visangle) anglea = ANG90 + (visangle - viewangle); angleb = ANG90 + (visangle - rw_normalangle); // bothe sines are allways positive - sinea = finesine[anglea >> ANGLETOFINESHIFT]; - sineb = finesine[angleb >> ANGLETOFINESHIFT]; + sinea = finesine(anglea >> ANGLETOFINESHIFT); + sineb = finesine(angleb >> ANGLETOFINESHIFT); num = FixedMul(projection, sineb) << detailshift; den = FixedMul(rw_distance, sinea); if (den > num >> 16) @@ -395,7 +424,7 @@ void R_InitTables(void) // OPTIMIZE: mirror... a = (i + 0.5) * PI * 2 / FINEANGLES; t = FRACUNIT * sin(a); - finesine[i] = t; + finesine(i) = t; } #endif @@ -424,17 +453,17 @@ void R_InitTextureMapping(void) // // calc focallength so FIELDOFVIEW angles covers SCREENWIDTH focallength = - FixedDiv(centerxfrac, finetangent[FINEANGLES / 4 + FIELDOFVIEW / 2]); + FixedDiv(centerxfrac, finetangent(FINEANGLES / 4 + FIELDOFVIEW / 2)); for (i = 0; i < FINEANGLES / 2; i++) { - if (finetangent[i] > FRACUNIT * 2) + if (finetangent(i) > FRACUNIT * 2) t = -1; - else if (finetangent[i] < -FRACUNIT * 2) + else if (finetangent(i) < -FRACUNIT * 2) t = viewwidth + 1; else { - t = FixedMul(finetangent[i], focallength); + t = FixedMul(finetangent(i), focallength); t = (centerxfrac - t + FRACUNIT - 1) >> FRACBITS; if (t < -1) t = -1; @@ -461,7 +490,7 @@ void R_InitTextureMapping(void) // for (i = 0; i < FINEANGLES / 2; i++) { - t = FixedMul(finetangent[i], focallength); + t = FixedMul(finetangent(i), focallength); t = centerx - t; if (viewangletox[i] == -1) viewangletox[i] = 0; @@ -614,7 +643,7 @@ void R_ExecuteSetViewSize(void) for (i = 0; i < viewwidth; i++) { - cosadj = abs(finecosine[xtoviewangle[i] >> ANGLETOFINESHIFT]); + cosadj = abs(finecosine(xtoviewangle[i] >> ANGLETOFINESHIFT)); distscale[i] = FixedDiv(FRACUNIT, cosadj); } @@ -731,8 +760,8 @@ void R_SetupFrame(player_t * player) tableAngle = viewangle >> ANGLETOFINESHIFT; if (player->chickenTics && player->chickenPeck) { // Set chicken attack view position - viewx = player->mo->x + player->chickenPeck * finecosine[tableAngle]; - viewy = player->mo->y + player->chickenPeck * finesine[tableAngle]; + viewx = player->mo->x + player->chickenPeck * finecosine(tableAngle); + viewy = player->mo->y + player->chickenPeck * finesine(tableAngle); } else { // Normal view position @@ -754,8 +783,8 @@ void R_SetupFrame(player_t * player) FRACUNIT / 2)); } } - viewsin = finesine[tableAngle]; - viewcos = finecosine[tableAngle]; + viewsin = finesine(tableAngle); + viewcos = finecosine(tableAngle); sscount = 0; if (player->fixedcolormap) { diff --git a/src/heretic/r_plane.c b/src/heretic/r_plane.c index 0015e978..6cb88de7 100644 --- a/src/heretic/r_plane.c +++ b/src/heretic/r_plane.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -149,8 +150,8 @@ void R_MapPlane(int y, int x1, int x2) length = FixedMul(distance, distscale[x1]); angle = (viewangle + xtoviewangle[x1]) >> ANGLETOFINESHIFT; - ds_xfrac = viewx + FixedMul(finecosine[angle], length); - ds_yfrac = -viewy - FixedMul(finesine[angle], length); + ds_xfrac = viewx + FixedMul(finecosine(angle), length); + ds_yfrac = -viewy - FixedMul(finesine(angle), length); if (fixedcolormap) ds_colormap = fixedcolormap; @@ -204,8 +205,8 @@ void R_ClearPlanes(void) angle = (viewangle - ANG90) >> ANGLETOFINESHIFT; // left to right mapping // scale will be unit scale at SCREENWIDTH/2 distance - basexscale = FixedDiv(finecosine[angle], centerxfrac); - baseyscale = -FixedDiv(finesine[angle], centerxfrac); + basexscale = FixedDiv(finecosine(angle), centerxfrac); + baseyscale = -FixedDiv(finesine(angle), centerxfrac); } diff --git a/src/heretic/r_segs.c b/src/heretic/r_segs.c index 9326e1f4..2133a4c2 100644 --- a/src/heretic/r_segs.c +++ b/src/heretic/r_segs.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -231,7 +232,7 @@ void R_RenderSegLoop(void) // calculate texture offset angle = (rw_centerangle + xtoviewangle[rw_x]) >> ANGLETOFINESHIFT; texturecolumn = - rw_offset - FixedMul(finetangent[angle], rw_distance); + rw_offset - FixedMul(finetangent(angle), rw_distance); texturecolumn >>= FRACBITS; // calculate lighting index = rw_scale >> LIGHTSCALESHIFT; @@ -361,7 +362,7 @@ void R_StoreWallRange(int start, int stop) offsetangle = ANG90; distangle = ANG90 - offsetangle; hyp = R_PointToDist(curline->v1->x, curline->v1->y); - sineval = finesine[distangle >> ANGLETOFINESHIFT]; + sineval = finesine(distangle >> ANGLETOFINESHIFT); rw_distance = FixedMul(hyp, sineval); @@ -553,7 +554,7 @@ void R_StoreWallRange(int start, int stop) offsetangle = -offsetangle; if (offsetangle > ANG90) offsetangle = ANG90; - sineval = finesine[offsetangle >> ANGLETOFINESHIFT]; + sineval = finesine(offsetangle >> ANGLETOFINESHIFT); rw_offset = FixedMul(hyp, sineval); if (rw_normalangle - rw_angle1 < ANG180) rw_offset = -rw_offset; diff --git a/src/hexen/CMakeLists.txt b/src/hexen/CMakeLists.txt index 6b4beed2..ef5c1ea3 100644 --- a/src/hexen/CMakeLists.txt +++ b/src/hexen/CMakeLists.txt @@ -1,4 +1,6 @@ -add_library(hexen STATIC +cmake_policy(SET CMP0076 NEW) +add_library(hexen INTERFACE) +target_sources(hexen INTERFACE a_action.c am_data.h am_map.c am_map.h @@ -54,5 +56,5 @@ add_library(hexen STATIC textdefs.h xddefs.h) -target_include_directories(hexen PRIVATE "../" "${CMAKE_CURRENT_BINARY_DIR}/../../") -target_link_libraries(hexen SDL2::SDL2 SDL2::mixer SDL2::net) +target_include_directories(hexen INTERFACE "../" "${CMAKE_CURRENT_BINARY_DIR}/../../") +target_link_libraries(hexen INTERFACE SDL2::SDL2 SDL2::mixer SDL2::net) diff --git a/src/hexen/a_action.c b/src/hexen/a_action.c index 5755d04d..0f8def11 100644 --- a/src/hexen/a_action.c +++ b/src/hexen/a_action.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -366,8 +367,8 @@ void GenerateOrbitTable(void) for (angle=0; angle<256; angle++) { - orbitTableX[angle] = FixedMul(ORBIT_RADIUS, finecosine[angle<<5]); - orbitTableY[angle] = FixedMul(ORBIT_RADIUS, finesine[angle<<5]); + orbitTableX[angle] = FixedMul(ORBIT_RADIUS, finecosine(angle<<5)); + orbitTableY[angle] = FixedMul(ORBIT_RADIUS, finesine(angle<<5)); } printf("int orbitTableX[256]=\n{\n"); @@ -738,8 +739,8 @@ void A_FogMove(mobj_t * actor) } angle = actor->angle >> ANGLETOFINESHIFT; - actor->momx = FixedMul(speed, finecosine[angle]); - actor->momy = FixedMul(speed, finesine[angle]); + actor->momx = FixedMul(speed, finecosine(angle)); + actor->momy = FixedMul(speed, finesine(angle)); } //=========================================================================== @@ -1038,8 +1039,8 @@ void P_SpawnDirt(mobj_t * actor, fixed_t radius) angle_t angle; angle = P_Random() << 5; // <<24 >>19 - x = actor->x + FixedMul(radius, finecosine[angle]); - y = actor->y + FixedMul(radius, finesine[angle]); + x = actor->x + FixedMul(radius, finecosine(angle)); + y = actor->y + FixedMul(radius, finesine(angle)); // x = actor->x + (P_SubRandom()%radius)<y + ((P_SubRandom()<z + (P_Random() << 9) + FRACUNIT; @@ -1308,8 +1309,8 @@ void A_BatMove(mobj_t * actor) // Adjust momentum vector to new direction newangle >>= ANGLETOFINESHIFT; speed = FixedMul(actor->info->speed, P_Random() << 10); - actor->momx = FixedMul(speed, finecosine[newangle]); - actor->momy = FixedMul(speed, finesine[newangle]); + actor->momx = FixedMul(speed, finecosine(newangle)); + actor->momy = FixedMul(speed, finesine(newangle)); if (P_Random() < 15) S_StartSound(actor, SFX_BAT_SCREAM); diff --git a/src/hexen/am_map.c b/src/hexen/am_map.c index d124105e..db45d677 100644 --- a/src/hexen/am_map.c +++ b/src/hexen/am_map.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1238,10 +1239,10 @@ void AM_rotate(fixed_t * x, fixed_t * y, angle_t a) { fixed_t tmpx; - tmpx = FixedMul(*x, finecosine[a >> ANGLETOFINESHIFT]) - - FixedMul(*y, finesine[a >> ANGLETOFINESHIFT]); - *y = FixedMul(*x, finesine[a >> ANGLETOFINESHIFT]) - + FixedMul(*y, finecosine[a >> ANGLETOFINESHIFT]); + tmpx = FixedMul(*x, finecosine(a >> ANGLETOFINESHIFT)) + - FixedMul(*y, finesine(a >> ANGLETOFINESHIFT)); + *y = FixedMul(*x, finesine(a >> ANGLETOFINESHIFT)) + + FixedMul(*y, finecosine(a >> ANGLETOFINESHIFT)); *x = tmpx; } diff --git a/src/hexen/d_net.c b/src/hexen/d_net.c index a2bfb5a6..6ca9bac0 100644 --- a/src/hexen/d_net.c +++ b/src/hexen/d_net.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -174,7 +175,7 @@ static void InitConnectData(net_connect_data_t *connect_data) // Game type fields: - connect_data->gamemode = gamemode; + connect_data->_gamemode = gamemode; connect_data->gamemission = hexen; // Are we recording a demo? Possibly set lowres turn mode diff --git a/src/hexen/g_game.c b/src/hexen/g_game.c index 87d1e5d9..039d5b6f 100644 --- a/src/hexen/g_game.c +++ b/src/hexen/g_game.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1278,7 +1279,7 @@ boolean G_CheckSpot(int playernum, mapthing_t * mthing) ss = R_PointInSubsector(x, y); an = ((unsigned) ANG45 * (mthing->angle / 45)) >> ANGLETOFINESHIFT; - mo = P_SpawnMobj(x + 20 * finecosine[an], y + 20 * finesine[an], + mo = P_SpawnMobj(x + 20 * finecosine(an), y + 20 * finesine(an), ss->sector->floorheight + TELEFOGHEIGHT, MT_TFOG); if (players[consoleplayer].viewz != 1) S_StartSound(mo, SFX_TELEPORT); // don't start sound on first frame diff --git a/src/hexen/mn_menu.c b/src/hexen/mn_menu.c index 059f45b3..795eca0b 100644 --- a/src/hexen/mn_menu.c +++ b/src/hexen/mn_menu.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -131,7 +132,7 @@ boolean MenuActive; int InfoType; int messageson = true; boolean mn_SuicideConsole; -boolean demoextend; // from h2def.h +//boolean demoextend; // from h2def.h // PRIVATE DATA DEFINITIONS ------------------------------------------------ @@ -1413,7 +1414,7 @@ boolean MN_Responder(event_t * event) slottextloaded = false; //reload the slot text quicksave = -1; P_SetMessage(&players[consoleplayer], - "CHOOSE A QUICKSAVE SLOT", true); + "CHOOSE A QUICKSAVE SLOT_RENDER", true); } else { @@ -1464,7 +1465,7 @@ boolean MN_Responder(event_t * event) slottextloaded = false; // reload the slot text quickload = -1; P_SetMessage(&players[consoleplayer], - "CHOOSE A QUICKLOAD SLOT", true); + "CHOOSE A QUICKLOAD SLOT_RENDER", true); } else { diff --git a/src/hexen/p_enemy.c b/src/hexen/p_enemy.c index ff265c8e..73a0c94d 100644 --- a/src/hexen/p_enemy.c +++ b/src/hexen/p_enemy.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1352,8 +1353,8 @@ void A_MinotaurDecide(mobj_t * actor) actor->flags |= MF_SKULLFLY; A_FaceTarget(actor); angle = actor->angle >> ANGLETOFINESHIFT; - actor->momx = FixedMul(MNTR_CHARGE_SPEED, finecosine[angle]); - actor->momy = FixedMul(MNTR_CHARGE_SPEED, finesine[angle]); + actor->momx = FixedMul(MNTR_CHARGE_SPEED, finecosine(angle)); + actor->momy = FixedMul(MNTR_CHARGE_SPEED, finesine(angle)); actor->args[4] = 35 / 2; // Charge duration } else if (target->z == target->floorz @@ -2556,9 +2557,9 @@ void A_CentaurDropStuff(mobj_t * actor) angle = actor->angle + ANG90; mo->momz = FRACUNIT * 8 + (P_Random() << 10); mo->momx = FixedMul(((P_Random() - 128) << 11) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul(((P_Random() - 128) << 11) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; } mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT, @@ -2568,9 +2569,9 @@ void A_CentaurDropStuff(mobj_t * actor) angle = actor->angle - ANG90; mo->momz = FRACUNIT * 8 + (P_Random() << 10); mo->momx = FixedMul(((P_Random() - 128) << 11) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul(((P_Random() - 128) << 11) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; } } @@ -2653,13 +2654,13 @@ void A_BishopMissileWeave(mobj_t * actor) weaveXY = actor->special2.i >> 16; weaveZ = actor->special2.i & 0xFFFF; angle = (actor->angle + ANG90) >> ANGLETOFINESHIFT; - newX = actor->x - FixedMul(finecosine[angle], + newX = actor->x - FixedMul(finecosine(angle), FloatBobOffsets[weaveXY] << 1); - newY = actor->y - FixedMul(finesine[angle], + newY = actor->y - FixedMul(finesine(angle), FloatBobOffsets[weaveXY] << 1); weaveXY = (weaveXY + 2) & 63; - newX += FixedMul(finecosine[angle], FloatBobOffsets[weaveXY] << 1); - newY += FixedMul(finesine[angle], FloatBobOffsets[weaveXY] << 1); + newX += FixedMul(finecosine(angle), FloatBobOffsets[weaveXY] << 1); + newY += FixedMul(finesine(angle), FloatBobOffsets[weaveXY] << 1); P_TryMove(actor, newX, newY); actor->z -= FloatBobOffsets[weaveZ]; weaveZ = (weaveZ + 2) & 63; @@ -2855,8 +2856,8 @@ static void DragonSeek(mobj_t * actor, angle_t thresh, angle_t turnMax) actor->angle -= delta; } angle = actor->angle >> ANGLETOFINESHIFT; - actor->momx = FixedMul(actor->info->speed, finecosine[angle]); - actor->momy = FixedMul(actor->info->speed, finesine[angle]); + actor->momx = FixedMul(actor->info->speed, finecosine(angle)); + actor->momy = FixedMul(actor->info->speed, finesine(angle)); if (actor->z + actor->height < target->z || target->z + target->height < actor->z) { @@ -3150,9 +3151,9 @@ void A_DemonDeath(mobj_t * actor) angle = actor->angle + ANG90; mo->momz = 8 * FRACUNIT; mo->momx = FixedMul((P_Random() << 10) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul((P_Random() << 10) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; } mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT, @@ -3162,9 +3163,9 @@ void A_DemonDeath(mobj_t * actor) angle = actor->angle - ANG90; mo->momz = 8 * FRACUNIT; mo->momx = FixedMul((P_Random() << 10) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul((P_Random() << 10) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; } mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT, @@ -3174,9 +3175,9 @@ void A_DemonDeath(mobj_t * actor) angle = actor->angle - ANG90; mo->momz = 8 * FRACUNIT; mo->momx = FixedMul((P_Random() << 10) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul((P_Random() << 10) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; } mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT, @@ -3186,9 +3187,9 @@ void A_DemonDeath(mobj_t * actor) angle = actor->angle - ANG90; mo->momz = 8 * FRACUNIT; mo->momx = FixedMul((P_Random() << 10) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul((P_Random() << 10) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; } mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT, @@ -3198,9 +3199,9 @@ void A_DemonDeath(mobj_t * actor) angle = actor->angle - ANG90; mo->momz = 8 * FRACUNIT; mo->momx = FixedMul((P_Random() << 10) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul((P_Random() << 10) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; } } @@ -3223,9 +3224,9 @@ void A_Demon2Death(mobj_t * actor) angle = actor->angle + ANG90; mo->momz = 8 * FRACUNIT; mo->momx = FixedMul((P_Random() << 10) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul((P_Random() << 10) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; } mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT, @@ -3235,9 +3236,9 @@ void A_Demon2Death(mobj_t * actor) angle = actor->angle - ANG90; mo->momz = 8 * FRACUNIT; mo->momx = FixedMul((P_Random() << 10) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul((P_Random() << 10) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; } mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT, @@ -3247,9 +3248,9 @@ void A_Demon2Death(mobj_t * actor) angle = actor->angle - ANG90; mo->momz = 8 * FRACUNIT; mo->momx = FixedMul((P_Random() << 10) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul((P_Random() << 10) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; } mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT, @@ -3259,9 +3260,9 @@ void A_Demon2Death(mobj_t * actor) angle = actor->angle - ANG90; mo->momz = 8 * FRACUNIT; mo->momx = FixedMul((P_Random() << 10) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul((P_Random() << 10) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; } mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT, @@ -3271,9 +3272,9 @@ void A_Demon2Death(mobj_t * actor) angle = actor->angle - ANG90; mo->momz = 8 * FRACUNIT; mo->momx = FixedMul((P_Random() << 10) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul((P_Random() << 10) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; } } @@ -3430,9 +3431,9 @@ void A_WraithFX2(mobj_t * actor) } mo->momz = 0; mo->momx = FixedMul((P_Random() << 7) + FRACUNIT, - finecosine[angle >> ANGLETOFINESHIFT]); + finecosine(angle >> ANGLETOFINESHIFT)); mo->momy = FixedMul((P_Random() << 7) + FRACUNIT, - finesine[angle >> ANGLETOFINESHIFT]); + finesine(angle >> ANGLETOFINESHIFT)); mo->target = actor; mo->floorclip = 10 * FRACUNIT; } @@ -3693,8 +3694,8 @@ void A_FiredChase(mobj_t * actor) else ang -= ANG90; ang >>= ANGLETOFINESHIFT; - actor->momx = FixedMul(8 * FRACUNIT, finecosine[ang]); - actor->momy = FixedMul(8 * FRACUNIT, finesine[ang]); + actor->momx = FixedMul(8 * FRACUNIT, finecosine(ang)); + actor->momy = FixedMul(8 * FRACUNIT, finesine(ang)); actor->special2.i = 3; // strafe time } } @@ -3771,8 +3772,8 @@ void A_IceGuyLook(mobj_t * actor) dist = ((P_Random() - 128) * actor->radius) >> 7; an = (actor->angle + ANG90) >> ANGLETOFINESHIFT; - P_SpawnMobj(actor->x + FixedMul(dist, finecosine[an]), - actor->y + FixedMul(dist, finesine[an]), + P_SpawnMobj(actor->x + FixedMul(dist, finecosine(an)), + actor->y + FixedMul(dist, finesine(an)), actor->z + 60 * FRACUNIT, MT_ICEGUY_WISP1 + (P_Random() & 1)); } @@ -3796,8 +3797,8 @@ void A_IceGuyChase(mobj_t * actor) dist = ((P_Random() - 128) * actor->radius) >> 7; an = (actor->angle + ANG90) >> ANGLETOFINESHIFT; - mo = P_SpawnMobj(actor->x + FixedMul(dist, finecosine[an]), - actor->y + FixedMul(dist, finesine[an]), + mo = P_SpawnMobj(actor->x + FixedMul(dist, finecosine(an)), + actor->y + FixedMul(dist, finesine(an)), actor->z + 60 * FRACUNIT, MT_ICEGUY_WISP1 + (P_Random() & 1)); if (mo) @@ -3826,14 +3827,14 @@ void A_IceGuyAttack(mobj_t * actor) } an = (actor->angle + ANG90) >> ANGLETOFINESHIFT; P_SpawnMissileXYZ(actor->x + FixedMul(actor->radius >> 1, - finecosine[an]), - actor->y + FixedMul(actor->radius >> 1, finesine[an]), + finecosine(an)), + actor->y + FixedMul(actor->radius >> 1, finesine(an)), actor->z + 40 * FRACUNIT, actor, actor->target, MT_ICEGUY_FX); an = (actor->angle - ANG90) >> ANGLETOFINESHIFT; P_SpawnMissileXYZ(actor->x + FixedMul(actor->radius >> 1, - finecosine[an]), - actor->y + FixedMul(actor->radius >> 1, finesine[an]), + finecosine(an)), + actor->y + FixedMul(actor->radius >> 1, finesine(an)), actor->z + 40 * FRACUNIT, actor, actor->target, MT_ICEGUY_FX); S_StartSound(actor, actor->info->attacksound); @@ -4113,8 +4114,8 @@ void A_SorcBallOrbit(mobj_t * actor) S_StartSound(actor, SFX_SORCERER_BALLWOOSH); } actor->special1.i = angle; // Set previous angle - x = parent->x + FixedMul(dist, finecosine[angle]); - y = parent->y + FixedMul(dist, finesine[angle]); + x = parent->x + FixedMul(dist, finecosine(angle)); + y = parent->y + FixedMul(dist, finesine(angle)); actor->x = x; actor->y = y; actor->z = parent->z - parent->floorclip + parent->info->height; @@ -4329,7 +4330,7 @@ void A_SorcOffense2(mobj_t * actor) index = actor->args[4] << 5; actor->args[4] += 15; - delta = (finesine[index]) * SORCFX4_SPREAD_ANGLE; + delta = (finesine(index)) * SORCFX4_SPREAD_ANGLE; delta = (delta >> FRACBITS) * ANG1; ang1 = actor->angle + delta; mo = P_SpawnMissileAngle(parent, MT_SORCFX4, ang1, 0); @@ -4364,8 +4365,8 @@ void A_SpawnFizzle(mobj_t * actor) mobj_t *mo; int ix; - x = actor->x + FixedMul(dist, finecosine[angle]); - y = actor->y + FixedMul(dist, finesine[angle]); + x = actor->x + FixedMul(dist, finecosine(angle)); + y = actor->y + FixedMul(dist, finesine(angle)); z = actor->z - actor->floorclip + (actor->height >> 1); for (ix = 0; ix < 5; ix++) { @@ -4373,8 +4374,8 @@ void A_SpawnFizzle(mobj_t * actor) if (mo) { rangle = angle + ((P_Random() % 5) << 1); - mo->momx = FixedMul(P_Random() % speed, finecosine[rangle]); - mo->momy = FixedMul(P_Random() % speed, finesine[rangle]); + mo->momx = FixedMul(P_Random() % speed, finecosine(rangle)); + mo->momy = FixedMul(P_Random() % speed, finesine(rangle)); mo->momz = FRACUNIT * 2; } } @@ -4457,10 +4458,10 @@ void A_SorcFX2Orbit(mobj_t * actor) { actor->special1.i += ANG1 * 10; angle = ((angle_t) actor->special1.i) >> ANGLETOFINESHIFT; - x = parent->x + FixedMul(dist, finecosine[angle]); - y = parent->y + FixedMul(dist, finesine[angle]); + x = parent->x + FixedMul(dist, finecosine(angle)); + y = parent->y + FixedMul(dist, finesine(angle)); z = parent->z - parent->floorclip + SORC_DEFENSE_HEIGHT * FRACUNIT; - z += FixedMul(15 * FRACUNIT, finecosine[angle]); + z += FixedMul(15 * FRACUNIT, finecosine(angle)); // Spawn trailer P_SpawnMobj(x, y, z, MT_SORCFX2_T1); } @@ -4468,10 +4469,10 @@ void A_SorcFX2Orbit(mobj_t * actor) { actor->special1.i -= ANG1 * 10; angle = ((angle_t) actor->special1.i) >> ANGLETOFINESHIFT; - x = parent->x + FixedMul(dist, finecosine[angle]); - y = parent->y + FixedMul(dist, finesine[angle]); + x = parent->x + FixedMul(dist, finecosine(angle)); + y = parent->y + FixedMul(dist, finesine(angle)); z = parent->z - parent->floorclip + SORC_DEFENSE_HEIGHT * FRACUNIT; - z += FixedMul(20 * FRACUNIT, finesine[angle]); + z += FixedMul(20 * FRACUNIT, finesine(angle)); // Spawn trailer P_SpawnMobj(x, y, z, MT_SORCFX2_T1); } @@ -4674,8 +4675,8 @@ void A_FastChase(mobj_t * actor) else ang -= ANG90; ang >>= ANGLETOFINESHIFT; - actor->momx = FixedMul(13 * FRACUNIT, finecosine[ang]); - actor->momy = FixedMul(13 * FRACUNIT, finesine[ang]); + actor->momx = FixedMul(13 * FRACUNIT, finecosine(ang)); + actor->momy = FixedMul(13 * FRACUNIT, finesine(ang)); actor->special2.i = 3; // strafe time } } @@ -5152,8 +5153,8 @@ void A_KoraxCommand(mobj_t * actor) // Shoot stream of lightning to ceiling ang = (actor->angle - ANG90) >> ANGLETOFINESHIFT; - x = actor->x + FixedMul(KORAX_COMMAND_OFFSET, finecosine[ang]); - y = actor->y + FixedMul(KORAX_COMMAND_OFFSET, finesine[ang]); + x = actor->x + FixedMul(KORAX_COMMAND_OFFSET, finecosine(ang)); + y = actor->y + FixedMul(KORAX_COMMAND_OFFSET, finesine(ang)); z = actor->z + KORAX_COMMAND_HEIGHT; P_SpawnMobj(x, y, z, MT_KORAX_BOLT); @@ -5223,8 +5224,8 @@ void KoraxFire1(mobj_t * actor, int type) fixed_t x, y, z; ang = (actor->angle - KORAX_DELTAANGLE) >> ANGLETOFINESHIFT; - x = actor->x + FixedMul(KORAX_ARM_EXTENSION_SHORT, finecosine[ang]); - y = actor->y + FixedMul(KORAX_ARM_EXTENSION_SHORT, finesine[ang]); + x = actor->x + FixedMul(KORAX_ARM_EXTENSION_SHORT, finecosine(ang)); + y = actor->y + FixedMul(KORAX_ARM_EXTENSION_SHORT, finesine(ang)); z = actor->z - actor->floorclip + KORAX_ARM1_HEIGHT; P_SpawnKoraxMissile(x, y, z, actor, actor->target, type); } @@ -5237,8 +5238,8 @@ void KoraxFire2(mobj_t * actor, int type) fixed_t x, y, z; ang = (actor->angle - KORAX_DELTAANGLE) >> ANGLETOFINESHIFT; - x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine[ang]); - y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine[ang]); + x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine(ang)); + y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine(ang)); z = actor->z - actor->floorclip + KORAX_ARM2_HEIGHT; P_SpawnKoraxMissile(x, y, z, actor, actor->target, type); } @@ -5250,8 +5251,8 @@ void KoraxFire3(mobj_t * actor, int type) fixed_t x, y, z; ang = (actor->angle - KORAX_DELTAANGLE) >> ANGLETOFINESHIFT; - x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine[ang]); - y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine[ang]); + x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine(ang)); + y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine(ang)); z = actor->z - actor->floorclip + KORAX_ARM3_HEIGHT; P_SpawnKoraxMissile(x, y, z, actor, actor->target, type); } @@ -5263,8 +5264,8 @@ void KoraxFire4(mobj_t * actor, int type) fixed_t x, y, z; ang = (actor->angle + KORAX_DELTAANGLE) >> ANGLETOFINESHIFT; - x = actor->x + FixedMul(KORAX_ARM_EXTENSION_SHORT, finecosine[ang]); - y = actor->y + FixedMul(KORAX_ARM_EXTENSION_SHORT, finesine[ang]); + x = actor->x + FixedMul(KORAX_ARM_EXTENSION_SHORT, finecosine(ang)); + y = actor->y + FixedMul(KORAX_ARM_EXTENSION_SHORT, finesine(ang)); z = actor->z - actor->floorclip + KORAX_ARM4_HEIGHT; P_SpawnKoraxMissile(x, y, z, actor, actor->target, type); } @@ -5276,8 +5277,8 @@ void KoraxFire5(mobj_t * actor, int type) fixed_t x, y, z; ang = (actor->angle + KORAX_DELTAANGLE) >> ANGLETOFINESHIFT; - x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine[ang]); - y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine[ang]); + x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine(ang)); + y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine(ang)); z = actor->z - actor->floorclip + KORAX_ARM5_HEIGHT; P_SpawnKoraxMissile(x, y, z, actor, actor->target, type); } @@ -5289,8 +5290,8 @@ void KoraxFire6(mobj_t * actor, int type) fixed_t x, y, z; ang = (actor->angle + KORAX_DELTAANGLE) >> ANGLETOFINESHIFT; - x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine[ang]); - y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine[ang]); + x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine(ang)); + y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine(ang)); z = actor->z - actor->floorclip + KORAX_ARM6_HEIGHT; P_SpawnKoraxMissile(x, y, z, actor, actor->target, type); } @@ -5305,13 +5306,13 @@ void A_KSpiritWeave(mobj_t * actor) weaveXY = actor->special2.i >> 16; weaveZ = actor->special2.i & 0xFFFF; angle = (actor->angle + ANG90) >> ANGLETOFINESHIFT; - newX = actor->x - FixedMul(finecosine[angle], + newX = actor->x - FixedMul(finecosine(angle), FloatBobOffsets[weaveXY] << 2); - newY = actor->y - FixedMul(finesine[angle], + newY = actor->y - FixedMul(finesine(angle), FloatBobOffsets[weaveXY] << 2); weaveXY = (weaveXY + (P_Random() % 5)) & 63; - newX += FixedMul(finecosine[angle], FloatBobOffsets[weaveXY] << 2); - newY += FixedMul(finesine[angle], FloatBobOffsets[weaveXY] << 2); + newX += FixedMul(finecosine(angle), FloatBobOffsets[weaveXY] << 2); + newY += FixedMul(finesine(angle), FloatBobOffsets[weaveXY] << 2); P_TryMove(actor, newX, newY); actor->z -= FloatBobOffsets[weaveZ] << 1; weaveZ = (weaveZ + (P_Random() % 5)) & 63; @@ -5352,8 +5353,8 @@ void A_KSpiritSeeker(mobj_t * actor, angle_t thresh, angle_t turnMax) actor->angle -= delta; } angle = actor->angle >> ANGLETOFINESHIFT; - actor->momx = FixedMul(actor->info->speed, finecosine[angle]); - actor->momy = FixedMul(actor->info->speed, finesine[angle]); + actor->momx = FixedMul(actor->info->speed, finecosine(angle)); + actor->momy = FixedMul(actor->info->speed, finesine(angle)); if (!(leveltime & 15) || actor->z > target->z + (target->info->height) diff --git a/src/hexen/p_inter.c b/src/hexen/p_inter.c index 8edb3ac7..c83884cb 100644 --- a/src/hexen/p_inter.c +++ b/src/hexen/p_inter.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1520,8 +1521,8 @@ void P_MinotaurSlam(mobj_t * source, mobj_t * target) angle = R_PointToAngle2(source->x, source->y, target->x, target->y); angle >>= ANGLETOFINESHIFT; thrust = 16 * FRACUNIT + (P_Random() << 10); - target->momx += FixedMul(thrust, finecosine[angle]); - target->momy += FixedMul(thrust, finesine[angle]); + target->momx += FixedMul(thrust, finecosine(angle)); + target->momy += FixedMul(thrust, finesine(angle)); P_DamageMobj(target, NULL, source, HITDICE(4)); if (target->player) { @@ -1924,8 +1925,8 @@ void P_DamageMobj thrust *= 4; } ang >>= ANGLETOFINESHIFT; - target->momx += FixedMul(thrust, finecosine[ang]); - target->momy += FixedMul(thrust, finesine[ang]); + target->momx += FixedMul(thrust, finecosine(ang)); + target->momy += FixedMul(thrust, finesine(ang)); } // diff --git a/src/hexen/p_map.c b/src/hexen/p_map.c index 4fa3d5e9..31add98b 100644 --- a/src/hexen/p_map.c +++ b/src/hexen/p_map.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -946,7 +947,7 @@ void P_FakeZMovement(mobj_t * mo) if (mo->player && mo->flags2 & MF2_FLY && !(mo->z <= mo->floorz) && leveltime & 2) { - mo->z += finesine[(FINEANGLES / 20 * leveltime >> 2) & FINEMASK]; + mo->z += finesine((FINEANGLES / 20 * leveltime >> 2) & FINEMASK); } // @@ -1291,9 +1292,9 @@ void P_HitSlideLine(line_t * ld) deltaangle >>= ANGLETOFINESHIFT; movelen = P_AproxDistance(tmxmove, tmymove); - newlen = FixedMul(movelen, finecosine[deltaangle]); - tmxmove = FixedMul(newlen, finecosine[lineangle]); - tmymove = FixedMul(newlen, finesine[lineangle]); + newlen = FixedMul(movelen, finecosine(deltaangle)); + tmxmove = FixedMul(newlen, finecosine(lineangle)); + tmymove = FixedMul(newlen, finesine(lineangle)); } /* @@ -1546,8 +1547,8 @@ void P_BounceWall(mobj_t * mo) movelen = FixedMul(movelen, 0.75 * FRACUNIT); // friction if (movelen < FRACUNIT) movelen = 2 * FRACUNIT; - mo->momx = FixedMul(movelen, finecosine[deltaangle]); - mo->momy = FixedMul(movelen, finesine[deltaangle]); + mo->momx = FixedMul(movelen, finecosine(deltaangle)); + mo->momy = FixedMul(movelen, finesine(deltaangle)); } @@ -1815,8 +1816,8 @@ fixed_t P_AimLineAttack(mobj_t * t1, angle_t angle, fixed_t distance) angle >>= ANGLETOFINESHIFT; shootthing = t1; - x2 = t1->x + (distance >> FRACBITS) * finecosine[angle]; - y2 = t1->y + (distance >> FRACBITS) * finesine[angle]; + x2 = t1->x + (distance >> FRACBITS) * finecosine(angle); + y2 = t1->y + (distance >> FRACBITS) * finesine(angle); shootz = t1->z + (t1->height >> 1) + 8 * FRACUNIT; topslope = 100 * FRACUNIT / 160; // can't shoot outside view angles bottomslope = -100 * FRACUNIT / 160; @@ -1851,8 +1852,8 @@ void P_LineAttack(mobj_t * t1, angle_t angle, fixed_t distance, fixed_t slope, angle >>= ANGLETOFINESHIFT; shootthing = t1; la_damage = damage; - x2 = t1->x + (distance >> FRACBITS) * finecosine[angle]; - y2 = t1->y + (distance >> FRACBITS) * finesine[angle]; + x2 = t1->x + (distance >> FRACBITS) * finecosine(angle); + y2 = t1->y + (distance >> FRACBITS) * finesine(angle); shootz = t1->z + (t1->height >> 1) + 8 * FRACUNIT; shootz -= t1->floorclip; attackrange = distance; @@ -1982,8 +1983,8 @@ void P_UseLines(player_t * player) angle = player->mo->angle >> ANGLETOFINESHIFT; x1 = player->mo->x; y1 = player->mo->y; - x2 = x1 + (USERANGE >> FRACBITS) * finecosine[angle]; - y2 = y1 + (USERANGE >> FRACBITS) * finesine[angle]; + x2 = x1 + (USERANGE >> FRACBITS) * finecosine(angle); + y2 = y1 + (USERANGE >> FRACBITS) * finesine(angle); P_PathTraverse(x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse); } @@ -2094,8 +2095,8 @@ boolean P_UsePuzzleItem(player_t * player, int itemType) angle = player->mo->angle >> ANGLETOFINESHIFT; x1 = player->mo->x; y1 = player->mo->y; - x2 = x1 + (USERANGE >> FRACBITS) * finecosine[angle]; - y2 = y1 + (USERANGE >> FRACBITS) * finesine[angle]; + x2 = x1 + (USERANGE >> FRACBITS) * finecosine(angle); + y2 = y1 + (USERANGE >> FRACBITS) * finesine(angle); P_PathTraverse(x1, y1, x2, y2, PT_ADDLINES | PT_ADDTHINGS, PTR_PuzzleItemTraverse); return PuzzleActivated; diff --git a/src/hexen/p_mobj.c b/src/hexen/p_mobj.c index 1f5db12f..f6c9b7c4 100644 --- a/src/hexen/p_mobj.c +++ b/src/hexen/p_mobj.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -244,8 +245,8 @@ void P_FloorBounceMissile(mobj_t * mo) void P_ThrustMobj(mobj_t * mo, angle_t angle, fixed_t move) { angle >>= ANGLETOFINESHIFT; - mo->momx += FixedMul(move, finecosine[angle]); - mo->momy += FixedMul(move, finesine[angle]); + mo->momx += FixedMul(move, finecosine(angle)); + mo->momy += FixedMul(move, finesine(angle)); } //---------------------------------------------------------------------------- @@ -340,8 +341,8 @@ boolean P_SeekerMissile(mobj_t * actor, angle_t thresh, angle_t turnMax) actor->angle -= delta; } angle = actor->angle >> ANGLETOFINESHIFT; - actor->momx = FixedMul(actor->info->speed, finecosine[angle]); - actor->momy = FixedMul(actor->info->speed, finesine[angle]); + actor->momx = FixedMul(actor->info->speed, finecosine(angle)); + actor->momy = FixedMul(actor->info->speed, finesine(angle)); if (actor->z + actor->height < target->z || target->z + target->height < actor->z) { // Need to seek vertically @@ -494,8 +495,8 @@ void P_XYMovement(mobj_t * mo) speed = FixedMul(speed, 0.75 * FRACUNIT); mo->angle = angle; angle >>= ANGLETOFINESHIFT; - mo->momx = FixedMul(speed, finecosine[angle]); - mo->momy = FixedMul(speed, finesine[angle]); + mo->momx = FixedMul(speed, finecosine(angle)); + mo->momy = FixedMul(speed, finesine(angle)); if (mo->info->seesound) { S_StartSound(mo, mo->info->seesound); @@ -559,9 +560,9 @@ void P_XYMovement(mobj_t * mo) mo->angle = angle; angle >>= ANGLETOFINESHIFT; mo->momx = - FixedMul(mo->info->speed >> 1, finecosine[angle]); + FixedMul(mo->info->speed >> 1, finecosine(angle)); mo->momy = - FixedMul(mo->info->speed >> 1, finesine[angle]); + FixedMul(mo->info->speed >> 1, finesine(angle)); // mo->momz = -mo->momz; if (mo->flags2 & MF2_SEEKERMISSILE) { @@ -734,7 +735,7 @@ void P_ZMovement(mobj_t * mo) if (mo->player && mo->flags2 & MF2_FLY && !(mo->z <= mo->floorz) && leveltime & 2) { - mo->z += finesine[(FINEANGLES / 20 * leveltime >> 2) & FINEMASK]; + mo->z += finesine((FINEANGLES / 20 * leveltime >> 2) & FINEMASK); } // @@ -2073,8 +2074,8 @@ mobj_t *P_SpawnMissile(mobj_t * source, mobj_t * dest, mobjtype_t type) } th->angle = an; an >>= ANGLETOFINESHIFT; - th->momx = FixedMul(th->info->speed, finecosine[an]); - th->momy = FixedMul(th->info->speed, finesine[an]); + th->momx = FixedMul(th->info->speed, finecosine(an)); + th->momy = FixedMul(th->info->speed, finesine(an)); dist = P_AproxDistance(dest->x - source->x, dest->y - source->y); dist = dist / th->info->speed; if (dist < 1) @@ -2115,8 +2116,8 @@ mobj_t *P_SpawnMissileXYZ(fixed_t x, fixed_t y, fixed_t z, } th->angle = an; an >>= ANGLETOFINESHIFT; - th->momx = FixedMul(th->info->speed, finecosine[an]); - th->momy = FixedMul(th->info->speed, finesine[an]); + th->momx = FixedMul(th->info->speed, finecosine(an)); + th->momy = FixedMul(th->info->speed, finesine(an)); dist = P_AproxDistance(dest->x - source->x, dest->y - source->y); dist = dist / th->info->speed; if (dist < 1) @@ -2169,8 +2170,8 @@ mobj_t *P_SpawnMissileAngle(mobj_t * source, mobjtype_t type, mo->target = source; // Originator mo->angle = angle; angle >>= ANGLETOFINESHIFT; - mo->momx = FixedMul(mo->info->speed, finecosine[angle]); - mo->momy = FixedMul(mo->info->speed, finesine[angle]); + mo->momx = FixedMul(mo->info->speed, finecosine(angle)); + mo->momy = FixedMul(mo->info->speed, finesine(angle)); mo->momz = momz; return (P_CheckMissileSpawn(mo) ? mo : NULL); } @@ -2200,8 +2201,8 @@ mobj_t *P_SpawnMissileAngleSpeed(mobj_t * source, mobjtype_t type, mo->target = source; // Originator mo->angle = angle; angle >>= ANGLETOFINESHIFT; - mo->momx = FixedMul(speed, finecosine[angle]); - mo->momy = FixedMul(speed, finesine[angle]); + mo->momx = FixedMul(speed, finecosine(angle)); + mo->momy = FixedMul(speed, finesine(angle)); mo->momz = momz; return (P_CheckMissileSpawn(mo) ? mo : NULL); } @@ -2266,9 +2267,9 @@ mobj_t *P_SpawnPlayerMissile(mobj_t * source, mobjtype_t type) MissileMobj->target = source; MissileMobj->angle = an; MissileMobj->momx = FixedMul(MissileMobj->info->speed, - finecosine[an >> ANGLETOFINESHIFT]); + finecosine(an >> ANGLETOFINESHIFT)); MissileMobj->momy = FixedMul(MissileMobj->info->speed, - finesine[an >> ANGLETOFINESHIFT]); + finesine(an >> ANGLETOFINESHIFT)); MissileMobj->momz = FixedMul(MissileMobj->info->speed, slope); if (MissileMobj->type == MT_MWAND_MISSILE || MissileMobj->type == MT_CFLAME_MISSILE) @@ -2307,8 +2308,8 @@ mobj_t *P_SpawnPlayerMinotaur(mobj_t *source, mobjtype_t type) fixed_t dist=0 *FRACUNIT; an = source->angle; - x = source->x + FixedMul(dist, finecosine[an>>ANGLETOFINESHIFT]); - y = source->y + FixedMul(dist, finesine[an>>ANGLETOFINESHIFT]); + x = source->x + FixedMul(dist, finecosine(an>>ANGLETOFINESHIFT)); + y = source->y + FixedMul(dist, finesine(an>>ANGLETOFINESHIFT)); z = source->z + 4*8*FRACUNIT+((source->player->lookdir)<floorclip; MissileMobj = P_SpawnMobj(x, y, z, type); @@ -2319,9 +2320,9 @@ mobj_t *P_SpawnPlayerMinotaur(mobj_t *source, mobjtype_t type) MissileMobj->target = source; MissileMobj->angle = an; MissileMobj->momx = FixedMul(MissileMobj->info->speed, - finecosine[an>>ANGLETOFINESHIFT]); + finecosine(an>>ANGLETOFINESHIFT)); MissileMobj->momy = FixedMul(MissileMobj->info->speed, - finesine[an>>ANGLETOFINESHIFT]); + finesine(an>>ANGLETOFINESHIFT)); MissileMobj->momz = 0; // MissileMobj->x += (MissileMobj->momx>>3); @@ -2381,8 +2382,8 @@ mobj_t *P_SPMAngle(mobj_t * source, mobjtype_t type, angle_t angle) // } th->target = source; th->angle = an; - th->momx = FixedMul(th->info->speed, finecosine[an >> ANGLETOFINESHIFT]); - th->momy = FixedMul(th->info->speed, finesine[an >> ANGLETOFINESHIFT]); + th->momx = FixedMul(th->info->speed, finecosine(an >> ANGLETOFINESHIFT)); + th->momy = FixedMul(th->info->speed, finesine(an >> ANGLETOFINESHIFT)); th->momz = FixedMul(th->info->speed, slope); return (P_CheckMissileSpawn(th) ? th : NULL); } @@ -2429,8 +2430,8 @@ mobj_t *P_SPMAngleXYZ(mobj_t * source, fixed_t x, fixed_t y, // } th->target = source; th->angle = an; - th->momx = FixedMul(th->info->speed, finecosine[an >> ANGLETOFINESHIFT]); - th->momy = FixedMul(th->info->speed, finesine[an >> ANGLETOFINESHIFT]); + th->momx = FixedMul(th->info->speed, finecosine(an >> ANGLETOFINESHIFT)); + th->momy = FixedMul(th->info->speed, finesine(an >> ANGLETOFINESHIFT)); th->momz = FixedMul(th->info->speed, slope); return (P_CheckMissileSpawn(th) ? th : NULL); } @@ -2456,8 +2457,8 @@ mobj_t *P_SpawnKoraxMissile(fixed_t x, fixed_t y, fixed_t z, } th->angle = an; an >>= ANGLETOFINESHIFT; - th->momx = FixedMul(th->info->speed, finecosine[an]); - th->momy = FixedMul(th->info->speed, finesine[an]); + th->momx = FixedMul(th->info->speed, finecosine(an)); + th->momy = FixedMul(th->info->speed, finesine(an)); dist = P_AproxDistance(dest->x - x, dest->y - y); dist = dist / th->info->speed; if (dist < 1) diff --git a/src/hexen/p_pspr.c b/src/hexen/p_pspr.c index 9ab2a752..c372d175 100644 --- a/src/hexen/p_pspr.c +++ b/src/hexen/p_pspr.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -312,10 +313,10 @@ void P_CalcSwing (player_t *player) swing = player->bob; angle = (FINEANGLES/70*leveltime)&FINEMASK; - swingx = FixedMul ( swing, finesine[angle]); + swingx = FixedMul ( swing, finesine(angle)); angle = (FINEANGLES/70*leveltime+FINEANGLES/2)&FINEMASK; - swingy = -FixedMul ( swingx, finesine[angle]); + swingy = -FixedMul ( swingx, finesine(angle)); } */ @@ -527,9 +528,9 @@ void A_WeaponReady(player_t * player, pspdef_t * psp) { // Bob the weapon based on movement speed. angle = (128 * leveltime) & FINEMASK; - psp->sx = FRACUNIT + FixedMul(player->bob, finecosine[angle]); + psp->sx = FRACUNIT + FixedMul(player->bob, finecosine(angle)); angle &= FINEANGLES / 2 - 1; - psp->sy = WEAPONTOP + FixedMul(player->bob, finesine[angle]); + psp->sy = WEAPONTOP + FixedMul(player->bob, finesine(angle)); } } @@ -1233,13 +1234,13 @@ void A_MStaffWeave(mobj_t * actor) weaveXY = actor->special2.i >> 16; weaveZ = actor->special2.i & 0xFFFF; angle = (actor->angle + ANG90) >> ANGLETOFINESHIFT; - newX = actor->x - FixedMul(finecosine[angle], + newX = actor->x - FixedMul(finecosine(angle), FloatBobOffsets[weaveXY] << 2); - newY = actor->y - FixedMul(finesine[angle], + newY = actor->y - FixedMul(finesine(angle), FloatBobOffsets[weaveXY] << 2); weaveXY = (weaveXY + 6) & 63; - newX += FixedMul(finecosine[angle], FloatBobOffsets[weaveXY] << 2); - newY += FixedMul(finesine[angle], FloatBobOffsets[weaveXY] << 2); + newX += FixedMul(finecosine(angle), FloatBobOffsets[weaveXY] << 2); + newY += FixedMul(finesine(angle), FloatBobOffsets[weaveXY] << 2); P_TryMove(actor, newX, newY); actor->z -= FloatBobOffsets[weaveZ] << 1; weaveZ = (weaveZ + 3) & 63; @@ -1609,11 +1610,11 @@ void A_CStaffMissileSlither(mobj_t * actor) weaveXY = actor->special2.i; angle = (actor->angle + ANG90) >> ANGLETOFINESHIFT; - newX = actor->x - FixedMul(finecosine[angle], FloatBobOffsets[weaveXY]); - newY = actor->y - FixedMul(finesine[angle], FloatBobOffsets[weaveXY]); + newX = actor->x - FixedMul(finecosine(angle), FloatBobOffsets[weaveXY]); + newY = actor->y - FixedMul(finesine(angle), FloatBobOffsets[weaveXY]); weaveXY = (weaveXY + 3) & 63; - newX += FixedMul(finecosine[angle], FloatBobOffsets[weaveXY]); - newY += FixedMul(finesine[angle], FloatBobOffsets[weaveXY]); + newX += FixedMul(finecosine(angle), FloatBobOffsets[weaveXY]); + newY += FixedMul(finesine(angle), FloatBobOffsets[weaveXY]); P_TryMove(actor, newX, newY); actor->special2.i = weaveXY; } @@ -1704,28 +1705,28 @@ void A_CFlameMissile(mobj_t * actor) for (i = 0; i < 4; i++) { an = (i * ANG45) >> ANGLETOFINESHIFT; - mo = P_SpawnMobj(BlockingMobj->x + FixedMul(dist, finecosine[an]), - BlockingMobj->y + FixedMul(dist, finesine[an]), + mo = P_SpawnMobj(BlockingMobj->x + FixedMul(dist, finecosine(an)), + BlockingMobj->y + FixedMul(dist, finesine(an)), BlockingMobj->z + 5 * FRACUNIT, MT_CIRCLEFLAME); if (mo) { mo->angle = an << ANGLETOFINESHIFT; mo->target = actor->target; mo->momx = mo->special1.i = - FixedMul(FLAMESPEED, finecosine[an]); - mo->momy = mo->special2.i = FixedMul(FLAMESPEED, finesine[an]); + FixedMul(FLAMESPEED, finecosine(an)); + mo->momy = mo->special2.i = FixedMul(FLAMESPEED, finesine(an)); mo->tics -= P_Random() & 3; } - mo = P_SpawnMobj(BlockingMobj->x - FixedMul(dist, finecosine[an]), - BlockingMobj->y - FixedMul(dist, finesine[an]), + mo = P_SpawnMobj(BlockingMobj->x - FixedMul(dist, finecosine(an)), + BlockingMobj->y - FixedMul(dist, finesine(an)), BlockingMobj->z + 5 * FRACUNIT, MT_CIRCLEFLAME); if (mo) { mo->angle = ANG180 + (an << ANGLETOFINESHIFT); mo->target = actor->target; mo->momx = mo->special1.i = FixedMul(-FLAMESPEED, - finecosine[an]); - mo->momy = mo->special2.i = FixedMul(-FLAMESPEED, finesine[an]); + finecosine(an)); + mo->momy = mo->special2.i = FixedMul(-FLAMESPEED, finesine(an)); mo->tics -= P_Random() & 3; } } @@ -1783,27 +1784,27 @@ void A_CFlameAttack(player_t *player, pspdef_t *psp) { an = (i*ANG45)>>ANGLETOFINESHIFT; an90 = (i*ANG45+ANG90)>>ANGLETOFINESHIFT; - mo = P_SpawnMobj(linetarget->x+FixedMul(dist, finecosine[an]), - linetarget->y+FixedMul(dist, finesine[an]), + mo = P_SpawnMobj(linetarget->x+FixedMul(dist, finecosine(an)), + linetarget->y+FixedMul(dist, finesine(an)), linetarget->z+5*FRACUNIT, MT_CIRCLEFLAME); if(mo) { mo->angle = an<target = pmo; - mo->momx = mo->special1.i = FixedMul(FLAMESPEED, finecosine[an]); - mo->momy = mo->special2.i = FixedMul(FLAMESPEED, finesine[an]); + mo->momx = mo->special1.i = FixedMul(FLAMESPEED, finecosine(an)); + mo->momy = mo->special2.i = FixedMul(FLAMESPEED, finesine(an)); mo->tics -= P_Random()&3; } - mo = P_SpawnMobj(linetarget->x-FixedMul(dist, finecosine[an]), - linetarget->y-FixedMul(dist, finesine[an]), + mo = P_SpawnMobj(linetarget->x-FixedMul(dist, finecosine(an)), + linetarget->y-FixedMul(dist, finesine(an)), linetarget->z+5*FRACUNIT, MT_CIRCLEFLAME); if(mo) { mo->angle = ANG180+(an<target = pmo; mo->momx = mo->special1.i = FixedMul(-FLAMESPEED, - finecosine[an]); - mo->momy = mo->special2.i = FixedMul(-FLAMESPEED, finesine[an]); + finecosine(an)); + mo->momy = mo->special2.i = FixedMul(-FLAMESPEED, finesine(an)); mo->tics -= P_Random()&3; } } @@ -1829,8 +1830,8 @@ void A_CFlameRotate(mobj_t * actor) int an; an = (actor->angle + ANG90) >> ANGLETOFINESHIFT; - actor->momx = actor->special1.i + FixedMul(FLAMEROTSPEED, finecosine[an]); - actor->momy = actor->special2.i + FixedMul(FLAMEROTSPEED, finesine[an]); + actor->momx = actor->special1.i + FixedMul(FLAMEROTSPEED, finecosine(an)); + actor->momy = actor->special2.i + FixedMul(FLAMEROTSPEED, finesine(an)); actor->angle += ANG90 / 15; } @@ -2029,8 +2030,8 @@ static void CHolySeekerMissile(mobj_t * actor, angle_t thresh, actor->angle -= delta; } angle = actor->angle >> ANGLETOFINESHIFT; - actor->momx = FixedMul(actor->info->speed, finecosine[angle]); - actor->momy = FixedMul(actor->info->speed, finesine[angle]); + actor->momx = FixedMul(actor->info->speed, finecosine(angle)); + actor->momy = FixedMul(actor->info->speed, finesine(angle)); if (!(leveltime & 15) || actor->z > target->z + (target->height) || actor->z + actor->height < target->z) @@ -2074,13 +2075,13 @@ static void CHolyWeave(mobj_t * actor) weaveXY = actor->special2.i >> 16; weaveZ = actor->special2.i & 0xFFFF; angle = (actor->angle + ANG90) >> ANGLETOFINESHIFT; - newX = actor->x - FixedMul(finecosine[angle], + newX = actor->x - FixedMul(finecosine(angle), FloatBobOffsets[weaveXY] << 2); - newY = actor->y - FixedMul(finesine[angle], + newY = actor->y - FixedMul(finesine(angle), FloatBobOffsets[weaveXY] << 2); weaveXY = (weaveXY + (P_Random() % 5)) & 63; - newX += FixedMul(finecosine[angle], FloatBobOffsets[weaveXY] << 2); - newY += FixedMul(finesine[angle], FloatBobOffsets[weaveXY] << 2); + newX += FixedMul(finecosine(angle), FloatBobOffsets[weaveXY] << 2); + newY += FixedMul(finesine(angle), FloatBobOffsets[weaveXY] << 2); P_TryMove(actor, newX, newY); actor->z -= FloatBobOffsets[weaveZ] << 1; weaveZ = (weaveZ + (P_Random() % 5)) & 63; @@ -2138,8 +2139,8 @@ static void CHolyTailFollow(mobj_t * actor, fixed_t dist) oldDistance = P_AproxDistance(child->x - actor->x, child->y - actor->y); if (P_TryMove - (child, actor->x + FixedMul(dist, finecosine[an]), - actor->y + FixedMul(dist, finesine[an]))) + (child, actor->x + FixedMul(dist, finecosine(an)), + actor->y + FixedMul(dist, finesine(an)))) { newDistance = P_AproxDistance(child->x - actor->x, child->y - actor->y) - FRACUNIT; @@ -2203,13 +2204,13 @@ void A_CHolyTail(mobj_t * actor) return; } else if (P_TryMove(actor, parent->x - FixedMul(14 * FRACUNIT, - finecosine[parent-> + finecosine(parent-> angle >> - ANGLETOFINESHIFT]), + ANGLETOFINESHIFT)), parent->y - FixedMul(14 * FRACUNIT, - finesine[parent-> + finesine(parent-> angle >> - ANGLETOFINESHIFT]))) + ANGLETOFINESHIFT)))) { actor->z = parent->z - 5 * FRACUNIT; } diff --git a/src/hexen/p_telept.c b/src/hexen/p_telept.c index ba5416b2..56640ffc 100644 --- a/src/hexen/p_telept.c +++ b/src/hexen/p_telept.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -108,8 +109,8 @@ boolean P_Teleport(mobj_t * thing, fixed_t x, fixed_t y, angle_t angle, fog = P_SpawnMobj(oldx, oldy, oldz + fogDelta, MT_TFOG); S_StartSound(fog, SFX_TELEPORT); an = angle >> ANGLETOFINESHIFT; - fog = P_SpawnMobj(x + 20 * finecosine[an], - y + 20 * finesine[an], thing->z + fogDelta, + fog = P_SpawnMobj(x + 20 * finecosine(an), + y + 20 * finesine(an), thing->z + fogDelta, MT_TFOG); S_StartSound(fog, SFX_TELEPORT); if (thing->player && !thing->player->powers[pw_speed]) @@ -133,8 +134,8 @@ boolean P_Teleport(mobj_t * thing, fixed_t x, fixed_t y, angle_t angle, if (thing->flags & MF_MISSILE) { angle >>= ANGLETOFINESHIFT; - thing->momx = FixedMul(thing->info->speed, finecosine[angle]); - thing->momy = FixedMul(thing->info->speed, finesine[angle]); + thing->momx = FixedMul(thing->info->speed, finecosine(angle)); + thing->momy = FixedMul(thing->info->speed, finesine(angle)); } else if (useFog) // no fog doesn't alter the player's momentums { diff --git a/src/hexen/p_things.c b/src/hexen/p_things.c index 12ec8547..11ca5eac 100644 --- a/src/hexen/p_things.c +++ b/src/hexen/p_things.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -194,8 +195,8 @@ boolean EV_ThingProjectile(byte * args, boolean gravity) } newMobj->target = mobj; // Originator newMobj->angle = angle; - newMobj->momx = FixedMul(speed, finecosine[fineAngle]); - newMobj->momy = FixedMul(speed, finesine[fineAngle]); + newMobj->momx = FixedMul(speed, finecosine(fineAngle)); + newMobj->momy = FixedMul(speed, finesine(fineAngle)); newMobj->momz = vspeed; newMobj->flags2 |= MF2_DROPPED; // Don't respawn if (gravity == true) diff --git a/src/hexen/p_user.c b/src/hexen/p_user.c index c7fa7a3f..7667b3f6 100644 --- a/src/hexen/p_user.c +++ b/src/hexen/p_user.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -78,18 +79,18 @@ void P_Thrust(player_t * player, angle_t angle, fixed_t move) angle >>= ANGLETOFINESHIFT; if (player->powers[pw_flight] && !(player->mo->z <= player->mo->floorz)) { - player->mo->momx += FixedMul(move, finecosine[angle]); - player->mo->momy += FixedMul(move, finesine[angle]); + player->mo->momx += FixedMul(move, finecosine(angle)); + player->mo->momy += FixedMul(move, finesine(angle)); } else if (P_GetThingFloorType(player->mo) == FLOOR_ICE) // Friction_Low { - player->mo->momx += FixedMul(move >> 1, finecosine[angle]); - player->mo->momy += FixedMul(move >> 1, finesine[angle]); + player->mo->momx += FixedMul(move >> 1, finecosine(angle)); + player->mo->momy += FixedMul(move >> 1, finesine(angle)); } else { - player->mo->momx += FixedMul(move, finecosine[angle]); - player->mo->momy += FixedMul(move, finesine[angle]); + player->mo->momx += FixedMul(move, finecosine(angle)); + player->mo->momy += FixedMul(move, finesine(angle)); } } @@ -134,7 +135,7 @@ void P_CalcHeight(player_t * player) } angle = (FINEANGLES / 20 * leveltime) & FINEMASK; - bob = FixedMul(player->bob / 2, finesine[angle]); + bob = FixedMul(player->bob / 2, finesine(angle)); // // move viewheight @@ -582,8 +583,8 @@ boolean P_UndoPlayerMorph(player_t * player) player->mo = mo; player->class = PlayerClass[playerNum]; angle >>= ANGLETOFINESHIFT; - fog = P_SpawnMobj(x + 20 * finecosine[angle], - y + 20 * finesine[angle], z + TELEFOGHEIGHT, MT_TFOG); + fog = P_SpawnMobj(x + 20 * finecosine(angle), + y + 20 * finesine(angle), z + TELEFOGHEIGHT, MT_TFOG); S_StartSound(fog, SFX_TELEPORT); P_PostMorphWeapon(player, weapon); return (true); @@ -1110,8 +1111,8 @@ void P_BlastMobj(mobj_t * source, mobj_t * victim, fixed_t strength) angle >>= ANGLETOFINESHIFT; if (strength < BLAST_FULLSTRENGTH) { - victim->momx = FixedMul(strength, finecosine[angle]); - victim->momy = FixedMul(strength, finesine[angle]); + victim->momx = FixedMul(strength, finecosine(angle)); + victim->momy = FixedMul(strength, finesine(angle)); if (victim->player) { // Players handled automatically @@ -1149,14 +1150,14 @@ void P_BlastMobj(mobj_t * source, mobj_t * victim, fixed_t strength) victim->target = source; } } - victim->momx = FixedMul(BLAST_SPEED, finecosine[angle]); - victim->momy = FixedMul(BLAST_SPEED, finesine[angle]); + victim->momx = FixedMul(BLAST_SPEED, finecosine(angle)); + victim->momy = FixedMul(BLAST_SPEED, finesine(angle)); // Spawn blast puff ang = R_PointToAngle2(victim->x, victim->y, source->x, source->y); ang >>= ANGLETOFINESHIFT; - x = victim->x + FixedMul(victim->radius + FRACUNIT, finecosine[ang]); - y = victim->y + FixedMul(victim->radius + FRACUNIT, finesine[ang]); + x = victim->x + FixedMul(victim->radius + FRACUNIT, finecosine(ang)); + y = victim->y + FixedMul(victim->radius + FRACUNIT, finesine(ang)); z = victim->z - victim->floorclip + (victim->height >> 1); mo = P_SpawnMobj(x, y, z, MT_BLASTEFFECT); if (mo) @@ -1520,8 +1521,8 @@ boolean P_UseArtifact(player_t * player, artitype_t arti) angle = player->mo->angle >> ANGLETOFINESHIFT; if (player->class == PCLASS_CLERIC) { - mo = P_SpawnMobj(player->mo->x + 16 * finecosine[angle], - player->mo->y + 24 * finesine[angle], + mo = P_SpawnMobj(player->mo->x + 16 * finecosine(angle), + player->mo->y + 24 * finesine(angle), player->mo->z - player->mo->floorclip + 8 * FRACUNIT, MT_POISONBAG); if (mo) @@ -1531,8 +1532,8 @@ boolean P_UseArtifact(player_t * player, artitype_t arti) } else if (player->class == PCLASS_MAGE) { - mo = P_SpawnMobj(player->mo->x + 16 * finecosine[angle], - player->mo->y + 24 * finesine[angle], + mo = P_SpawnMobj(player->mo->x + 16 * finecosine(angle), + player->mo->y + 24 * finesine(angle), player->mo->z - player->mo->floorclip + 8 * FRACUNIT, MT_FIREBOMB); if (mo) diff --git a/src/hexen/po_man.c b/src/hexen/po_man.c index 2661a8bb..a97c68b0 100644 --- a/src/hexen/po_man.c +++ b/src/hexen/po_man.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -229,8 +230,8 @@ void T_MovePoly(polyevent_t * pe) if (pe->dist < absSpeed) { pe->speed = pe->dist * (pe->speed < 0 ? -1 : 1); - pe->xSpeed = FixedMul(pe->speed, finecosine[pe->angle]); - pe->ySpeed = FixedMul(pe->speed, finesine[pe->angle]); + pe->xSpeed = FixedMul(pe->speed, finecosine(pe->angle)); + pe->ySpeed = FixedMul(pe->speed, finesine(pe->angle)); } } } @@ -281,8 +282,8 @@ boolean EV_MovePoly(line_t * line, byte * args, boolean timesEight, boolean an = args[2] * (ANG90 / 64); pe->angle = an >> ANGLETOFINESHIFT; - pe->xSpeed = FixedMul(pe->speed, finecosine[pe->angle]); - pe->ySpeed = FixedMul(pe->speed, finesine[pe->angle]); + pe->xSpeed = FixedMul(pe->speed, finecosine(pe->angle)); + pe->ySpeed = FixedMul(pe->speed, finesine(pe->angle)); SN_StartSequence((mobj_t *) & poly->startSpot, SEQ_DOOR_STONE + poly->seqType); @@ -309,8 +310,8 @@ boolean EV_MovePoly(line_t * line, byte * args, boolean timesEight, boolean pe->speed = args[1] * (FRACUNIT / 8); an = an + ANG180; // reverse the angle pe->angle = an >> ANGLETOFINESHIFT; - pe->xSpeed = FixedMul(pe->speed, finecosine[pe->angle]); - pe->ySpeed = FixedMul(pe->speed, finesine[pe->angle]); + pe->xSpeed = FixedMul(pe->speed, finecosine(pe->angle)); + pe->ySpeed = FixedMul(pe->speed, finesine(pe->angle)); polyNum = mirror; SN_StartSequence((mobj_t *) & poly->startSpot, SEQ_DOOR_STONE + poly->seqType); @@ -485,8 +486,8 @@ boolean EV_OpenPolyDoor(line_t * line, byte * args, podoortype_t type) pd->dist = pd->totalDist; an = args[2] * (ANG90 / 64); pd->direction = an >> ANGLETOFINESHIFT; - pd->xSpeed = FixedMul(pd->speed, finecosine[pd->direction]); - pd->ySpeed = FixedMul(pd->speed, finesine[pd->direction]); + pd->xSpeed = FixedMul(pd->speed, finecosine(pd->direction)); + pd->ySpeed = FixedMul(pd->speed, finesine(pd->direction)); SN_StartSequence((mobj_t *) & poly->startSpot, SEQ_DOOR_STONE + poly->seqType); } @@ -525,8 +526,8 @@ boolean EV_OpenPolyDoor(line_t * line, byte * args, podoortype_t type) pd->dist = pd->totalDist; an = an + ANG180; // reverse the angle pd->direction = an >> ANGLETOFINESHIFT; - pd->xSpeed = FixedMul(pd->speed, finecosine[pd->direction]); - pd->ySpeed = FixedMul(pd->speed, finesine[pd->direction]); + pd->xSpeed = FixedMul(pd->speed, finecosine(pd->direction)); + pd->ySpeed = FixedMul(pd->speed, finesine(pd->direction)); SN_StartSequence((mobj_t *) & poly->startSpot, SEQ_DOOR_STONE + poly->seqType); } @@ -633,8 +634,8 @@ static void ThrustMobj(mobj_t * mobj, seg_t * seg, polyobj_t * po) force = FRACUNIT; } - thrustX = FixedMul(force, finecosine[thrustAngle]); - thrustY = FixedMul(force, finesine[thrustAngle]); + thrustX = FixedMul(force, finecosine(thrustAngle)); + thrustY = FixedMul(force, finesine(thrustAngle)); mobj->momx += thrustX; mobj->momy += thrustY; if (po->crush) @@ -821,12 +822,12 @@ static void RotatePt(int an, fixed_t * x, fixed_t * y, fixed_t startSpotX, trx = *x; try = *y; - gxt = FixedMul(trx, finecosine[an]); - gyt = FixedMul(try, finesine[an]); + gxt = FixedMul(trx, finecosine(an)); + gyt = FixedMul(try, finesine(an)); *x = (gxt - gyt) + startSpotX; - gxt = FixedMul(trx, finesine[an]); - gyt = FixedMul(try, finecosine[an]); + gxt = FixedMul(trx, finesine(an)); + gyt = FixedMul(try, finecosine(an)); *y = (gyt + gxt) + startSpotY; } diff --git a/src/hexen/r_main.c b/src/hexen/r_main.c index b551a153..277296f7 100644 --- a/src/hexen/r_main.c +++ b/src/hexen/r_main.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -196,6 +197,28 @@ int R_PointOnSegSide(fixed_t x, fixed_t y, seg_t * line) */ #define DBITS (FRACBITS-SLOPEBITS) +static int SlopeDiv(unsigned int num, unsigned int den) +{ + unsigned ans; + + if (den < 512) + { + return SLOPERANGE; + } + else + { + ans = (num << 3) / (den >> 8); + + if (ans <= SLOPERANGE) + { + return ans; + } + else + { + return SLOPERANGE; + } + } +} angle_t R_PointToAngle(fixed_t x, fixed_t y) { @@ -272,7 +295,7 @@ fixed_t R_PointToDist(fixed_t x, fixed_t y) angle = (tantoangle[FixedDiv(dy, dx) >> DBITS] + ANG90) >> ANGLETOFINESHIFT; - dist = FixedDiv(dx, finesine[angle]); // use as cosine + dist = FixedDiv(dx, finesine(angle)); // use as cosine return dist; } @@ -330,9 +353,9 @@ fixed_t R_ScaleFromGlobalAngle(angle_t visangle) fixed_t dist, z; fixed_t sinv, cosv; - sinv = finesine[(visangle - rw_normalangle) >> ANGLETOFINESHIFT]; + sinv = finesine((visangle - rw_normalangle) >> ANGLETOFINESHIFT); dist = FixedDiv(rw_distance, sinv); - cosv = finecosine[(viewangle - visangle) >> ANGLETOFINESHIFT]; + cosv = finecosine((viewangle - visangle) >> ANGLETOFINESHIFT); z = abs(FixedMul(dist, cosv)); scale = FixedDiv(projection, z); return scale; @@ -342,8 +365,8 @@ fixed_t R_ScaleFromGlobalAngle(angle_t visangle) anglea = ANG90 + (visangle - viewangle); angleb = ANG90 + (visangle - rw_normalangle); // bothe sines are allways positive - sinea = finesine[anglea >> ANGLETOFINESHIFT]; - sineb = finesine[angleb >> ANGLETOFINESHIFT]; + sinea = finesine(anglea >> ANGLETOFINESHIFT); + sineb = finesine(angleb >> ANGLETOFINESHIFT); num = FixedMul(projection, sineb) << detailshift; den = FixedMul(rw_distance, sinea); if (den > num >> 16) @@ -397,7 +420,7 @@ void R_InitTables(void) // OPTIMIZE: mirror... a = (i + 0.5) * PI * 2 / FINEANGLES; t = FRACUNIT * sin(a); - finesine[i] = t; + finesine(i) = t; } #endif @@ -426,17 +449,17 @@ void R_InitTextureMapping(void) // // calc focallength so FIELDOFVIEW angles covers SCREENWIDTH focallength = - FixedDiv(centerxfrac, finetangent[FINEANGLES / 4 + FIELDOFVIEW / 2]); + FixedDiv(centerxfrac, finetangent(FINEANGLES / 4 + FIELDOFVIEW / 2)); for (i = 0; i < FINEANGLES / 2; i++) { - if (finetangent[i] > FRACUNIT * 2) + if (finetangent(i) > FRACUNIT * 2) t = -1; - else if (finetangent[i] < -FRACUNIT * 2) + else if (finetangent(i) < -FRACUNIT * 2) t = viewwidth + 1; else { - t = FixedMul(finetangent[i], focallength); + t = FixedMul(finetangent(i), focallength); t = (centerxfrac - t + FRACUNIT - 1) >> FRACBITS; if (t < -1) t = -1; @@ -463,7 +486,7 @@ void R_InitTextureMapping(void) // for (i = 0; i < FINEANGLES / 2; i++) { - t = FixedMul(finetangent[i], focallength); + t = FixedMul(finetangent(i), focallength); t = centerx - t; if (viewangletox[i] == -1) viewangletox[i] = 0; @@ -616,7 +639,7 @@ void R_ExecuteSetViewSize(void) for (i = 0; i < viewwidth; i++) { - cosadj = abs(finecosine[xtoviewangle[i] >> ANGLETOFINESHIFT]); + cosadj = abs(finecosine(xtoviewangle[i] >> ANGLETOFINESHIFT)); distscale[i] = FixedDiv(FRACUNIT, cosadj); } @@ -746,8 +769,8 @@ void R_SetupFrame(player_t * player) FRACUNIT / 2)); } } - viewsin = finesine[tableAngle]; - viewcos = finecosine[tableAngle]; + viewsin = finesine(tableAngle); + viewcos = finecosine(tableAngle); sscount = 0; if (player->fixedcolormap) { diff --git a/src/hexen/r_plane.c b/src/hexen/r_plane.c index 8dd34083..837a6de7 100644 --- a/src/hexen/r_plane.c +++ b/src/hexen/r_plane.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -162,8 +163,8 @@ void R_MapPlane(int y, int x1, int x2) length = FixedMul(distance, distscale[x1]); angle = (viewangle + xtoviewangle[x1]) >> ANGLETOFINESHIFT; - ds_xfrac = viewx + FixedMul(finecosine[angle], length); - ds_yfrac = -viewy - FixedMul(finesine[angle], length); + ds_xfrac = viewx + FixedMul(finecosine(angle), length); + ds_yfrac = -viewy - FixedMul(finesine(angle), length); if (fixedcolormap) { @@ -213,8 +214,8 @@ void R_ClearPlanes(void) memset(cachedheight, 0, sizeof(cachedheight)); angle = (viewangle - ANG90) >> ANGLETOFINESHIFT; // left to right mapping // Scale will be unit scale at SCREENWIDTH/2 distance - basexscale = FixedDiv(finecosine[angle], centerxfrac); - baseyscale = -FixedDiv(finesine[angle], centerxfrac); + basexscale = FixedDiv(finecosine(angle), centerxfrac); + baseyscale = -FixedDiv(finesine(angle), centerxfrac); } //========================================================================== diff --git a/src/hexen/r_segs.c b/src/hexen/r_segs.c index 60e9c521..07e4185f 100644 --- a/src/hexen/r_segs.c +++ b/src/hexen/r_segs.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -223,7 +224,7 @@ void R_RenderSegLoop(void) // calculate texture offset angle = (rw_centerangle + xtoviewangle[rw_x]) >> ANGLETOFINESHIFT; texturecolumn = - rw_offset - FixedMul(finetangent[angle], rw_distance); + rw_offset - FixedMul(finetangent(angle), rw_distance); texturecolumn >>= FRACBITS; // calculate lighting index = rw_scale >> LIGHTSCALESHIFT; @@ -353,7 +354,7 @@ void R_StoreWallRange(int start, int stop) offsetangle = ANG90; distangle = ANG90 - offsetangle; hyp = R_PointToDist(curline->v1->x, curline->v1->y); - sineval = finesine[distangle >> ANGLETOFINESHIFT]; + sineval = finesine(distangle >> ANGLETOFINESHIFT); rw_distance = FixedMul(hyp, sineval); @@ -546,7 +547,7 @@ void R_StoreWallRange(int start, int stop) offsetangle = -offsetangle; if (offsetangle > ANG90) offsetangle = ANG90; - sineval = finesine[offsetangle >> ANGLETOFINESHIFT]; + sineval = finesine(offsetangle >> ANGLETOFINESHIFT); rw_offset = FixedMul(hyp, sineval); if (rw_normalangle - rw_angle1 < ANG180) rw_offset = -rw_offset; diff --git a/src/i_endoom.c b/src/i_endoom.c index 9beacc90..3928a85d 100644 --- a/src/i_endoom.c +++ b/src/i_endoom.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -30,8 +31,8 @@ // // Displays the text mode ending screen after the game quits // - -void I_Endoom(byte *endoom_data) +#if !NO_USE_ENDDOOM +void I_Endoom(should_be_const byte *endoom_data) { unsigned char *screendata; int y; @@ -76,4 +77,4 @@ void I_Endoom(byte *endoom_data) TXT_Shutdown(); } - +#endif \ No newline at end of file diff --git a/src/i_endoom.h b/src/i_endoom.h index 8c8ff457..c9edf753 100644 --- a/src/i_endoom.h +++ b/src/i_endoom.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -23,7 +24,7 @@ // Display the Endoom screen on shutdown. Pass a pointer to the // ENDOOM lump. -void I_Endoom(byte *data); +void I_Endoom(should_be_const byte *data); #endif diff --git a/src/i_input.c b/src/i_input.c index e91564cf..58a546a2 100644 --- a/src/i_input.c +++ b/src/i_input.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -262,6 +263,13 @@ void I_HandleKeyboardEvent(SDL_Event *sdlevent) case SDL_KEYUP: event.type = ev_keyup; +#if DOOM_SMALL && !DOOM_TINY + if (sdlevent->key.keysym.scancode == SDL_SCANCODE_F15) { + extern int pd_frame; + extern int pd_dump_frame; + pd_dump_frame = pd_frame + 1; + } +#endif event.data1 = TranslateKey(&sdlevent->key.keysym); // data2/data3 are initialized to zero for ev_keyup. diff --git a/src/i_input.h b/src/i_input.h index 8e3b2f1f..af1f47ee 100644 --- a/src/i_input.h +++ b/src/i_input.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -38,5 +39,10 @@ void I_StartTextInput(int x1, int y1, int x2, int y2); // I_StopTextInput finishes text input, deactivating the on-screen keyboard // (if one is used). void I_StopTextInput(void); +#if DOOM_TINY +void I_GetEvent(void); +void I_GetEventTimeout(int); +int GetTypedChar(int scancode, boolean shiftdown); +#endif #endif diff --git a/src/i_joystick.c b/src/i_joystick.c index d29c2fe6..7edd9ff7 100644 --- a/src/i_joystick.c +++ b/src/i_joystick.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -31,6 +32,7 @@ #include "m_config.h" #include "m_misc.h" +#if !NO_USE_JOYSTICK // When an axis is within the dead zone, it is set to zero. // This is 5% of the full range: @@ -45,7 +47,7 @@ static SDL_Joystick *joystick = NULL; static int usejoystick = 0; // SDL GUID and index of the joystick to use. -static char *joystick_guid = ""; +static const char *joystick_guid = ""; static int joystick_index = -1; // Which joystick axis to use for horizontal movement, and whether to @@ -401,3 +403,4 @@ void I_BindJoystickVariables(void) } } +#endif \ No newline at end of file diff --git a/src/i_main.c b/src/i_main.c index 22c46180..7e7ca595 100644 --- a/src/i_main.c +++ b/src/i_main.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -18,10 +19,23 @@ #include "config.h" +#include #include +#if !LIB_PICO_STDLIB #include "SDL.h" - +#else +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "pico/sem.h" +#include "pico/multicore.h" +#if PICO_ON_DEVICE +#include "hardware/vreg.h" +#endif +#endif +#if USE_PICO_NET +#include "piconet.h" +#endif #include "doomtype.h" #include "i_system.h" #include "m_argv.h" @@ -34,14 +48,43 @@ void D_DoomMain (void); +#if PICO_ON_DEVICE +#include "pico/binary_info.h" +bi_decl(bi_3pins_with_names(PICO_AUDIO_I2S_DATA_PIN, "I2S DIN", PICO_AUDIO_I2S_CLOCK_PIN_BASE, "I2S BCK", PICO_AUDIO_I2S_CLOCK_PIN_BASE+1, "I2S LRCK")); +#endif + int main(int argc, char **argv) { // save arguments - +#if !NO_USE_ARGS myargc = argc; myargv = argv; - - //! +#endif +#if PICO_ON_DEVICE + vreg_set_voltage(VREG_VOLTAGE_1_30); + // todo pause? is this the cause of the cold start isue? + set_sys_clock_khz(270000, true); +#if !USE_PICO_NET + // debug ? +// gpio_debug_pins_init(); +#endif +#ifdef PICO_SMPS_MODE_PIN + gpio_init(PICO_SMPS_MODE_PIN); + gpio_set_dir(PICO_SMPS_MODE_PIN, GPIO_OUT); + gpio_put(PICO_SMPS_MODE_PIN, 1); +#endif +#endif +#if LIB_PICO_STDIO + stdio_init_all(); +#endif +#if PICO_BUILD + I_Init(); +#endif +#if USE_PICO_NET + // do init early to set pulls + piconet_init(); +#endif +//! // Print the program version and exit. // if (M_ParmExists("-version") || M_ParmExists("--version")) { @@ -49,7 +92,9 @@ int main(int argc, char **argv) exit(0); } +#if !NO_USE_ARGS M_FindResponseFile(); +#endif #ifdef SDL_HINT_NO_SIGNAL_HANDLERS SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); diff --git a/src/i_musicpack.c b/src/i_musicpack.c index a8a21fba..04f9f0ed 100644 --- a/src/i_musicpack.c +++ b/src/i_musicpack.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -100,7 +101,7 @@ static boolean music_initialized = false; static boolean sdl_was_initialized = false; -char *music_pack_path = ""; +constcharstar music_pack_path = ""; // If true, we are playing a substitute digital track rather than in-WAD // MIDI/MUS track, and file_metadata contains loop metadata. @@ -622,7 +623,7 @@ static void ReadLoopPoints(const char *filename, file_metadata_t *metadata) // Given a MUS lump, look up a substitute MUS file to play instead // (or NULL to just use normal MIDI playback). -static const char *GetSubstituteMusicFile(void *data, size_t data_len) +static const char *GetSubstituteMusicFile(should_be_const void *data, size_t data_len) { sha1_context_t context; sha1_digest_t hash; @@ -983,6 +984,7 @@ static void LoadSubstituteConfigs(void) free(musicdir); } +#if !DOOM_SMALL // Returns true if the given lump number is a music lump that should // be included in substitute configs. @@ -991,7 +993,7 @@ static void LoadSubstituteConfigs(void) static boolean IsMusicLump(int lumpnum) { - byte *data; + should_be_const byte *data; boolean result; if (W_LumpLength(lumpnum) < 4) @@ -1017,7 +1019,7 @@ static void DumpSubstituteConfig(char *filename) sha1_context_t context; sha1_digest_t digest; char name[9]; - byte *data; + should_be_const byte *data; FILE *fs; unsigned int lumpnum; size_t h; @@ -1065,6 +1067,7 @@ static void DumpSubstituteConfig(char *filename) printf("Substitute MIDI config file written to %s.\n", filename); I_Quit(); } +#endif // Shutdown music @@ -1104,6 +1107,7 @@ static boolean I_MP_InitMusic(void) { int i; +#if !DOOM_SMALL //! // @category obscure // @arg @@ -1117,6 +1121,7 @@ static boolean I_MP_InitMusic(void) { DumpSubstituteConfig(myargv[i + 1]); } +#endif // If we're in GENMIDI mode, try to load sound packs. LoadSubstituteConfigs(); @@ -1258,7 +1263,7 @@ static void I_MP_UnRegisterSong(void *handle) Mix_FreeMusic(music); } -static void *I_MP_RegisterSong(void *data, int len) +static void *I_MP_RegisterSong(should_be_const void *data, int len) { const char *filename; Mix_Music *music; diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 1a42aef2..d6a2914a 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -108,51 +109,75 @@ typedef struct typedef struct opl_voice_s opl_voice_t; +#if DOOM_SMALL +typedef int8_t opl_index_t; +typedef int8_t opl_op_t; +typedef uint8_t opl_voice_index_t; +typedef uint8_t opl_key_t; +typedef uint8_t opl_vol_t; +typedef uint8_t opl_note_t; +typedef uint8_t opl_pan_t; +typedef uint8_t opl_priority_t; +typedef uint16_t opl_array_t; +typedef uint16_t opl_freq_t; +#else +typedef int opl_index_t; +typedef int opl_op_t; +typedef int opl_array_t; +typedef unsigned int opl_voice_index_t; +typedef unsigned int opl_key_t; +typedef unsigned int opl_vol_t; +typedef unsigned int opl_note_t; +typedef unsigned int opl_pan_t; +typedef unsigned int opl_priority_t; +typedef unsigned int opl_freq_t; +#endif struct opl_voice_s { // Index of this voice: - int index; + opl_index_t index; // The operators used by this voice: - int op1, op2; - - // Array used by voice: - int array; - - // Currently-loaded instrument data - genmidi_instr_t *current_instr; + opl_op_t op1, op2; // The voice number in the instrument to use. // This is normally set to zero; if this is a double voice // instrument, it may be one. - unsigned int current_instr_voice; - - // The channel currently using this voice. - opl_channel_data_t *channel; + opl_voice_index_t current_instr_voice; // The midi key that this voice is playing. - unsigned int key; + opl_key_t key; // The note being played. This is normally the same as // the key, but if the instrument is a fixed pitch // instrument, it is different. - unsigned int note; - - // The frequency value being used. - unsigned int freq; + opl_note_t note; // The volume of the note being played on this channel. - unsigned int note_volume; + opl_vol_t note_volume; // The current volume (register value) that has been set for this channel. - unsigned int car_volume; - unsigned int mod_volume; + opl_vol_t car_volume; + opl_vol_t mod_volume; // Pan. - unsigned int reg_pan; + opl_pan_t reg_pan; // Priority. - unsigned int priority; + opl_priority_t priority; + + // Array used by voice: + opl_array_t array; // todo grahm this is just a small number << 8 + + // The frequency value being used. + opl_freq_t freq; + + // todo graham could convert these back to indexes + // Currently-loaded instrument data + genmidi_instr_t *current_instr; + + // The channel currently using this voice. + opl_channel_data_t *channel; }; // Operators used by the different voices. @@ -282,7 +307,7 @@ static const unsigned short frequency_curve[] = { // Mapping from MIDI volume level to OPL level value. -static const unsigned int volume_mapping_table[] = { +static const unsigned char volume_mapping_table[] = { 0, 1, 3, 5, 6, 8, 10, 11, 13, 14, 16, 17, 19, 20, 22, 23, 25, 26, 27, 29, 30, 32, 33, 34, @@ -301,7 +326,11 @@ static const unsigned int volume_mapping_table[] = { 124, 124, 125, 125, 126, 126, 127, 127 }; +#if DOOM_TINY +//const +#endif static opl_driver_ver_t opl_drv_ver = opl_doom_1_9; + static boolean music_initialized = false; //static boolean musicpaused = false; @@ -322,8 +351,13 @@ static opl_voice_t *voice_free_list[OPL_NUM_VOICES * 2]; static opl_voice_t *voice_alloced_list[OPL_NUM_VOICES * 2]; static int voice_free_num; static int voice_alloced_num; +#if !DOOM_TINY static int opl_opl3mode; static int num_opl_voices; +#else +#define opl_opl3mode 0 +#define num_opl_voices OPL_NUM_VOICES +#endif // Data for each channel. @@ -349,8 +383,8 @@ static unsigned int last_perc_count; // Configuration file variable, containing the port number for the // adlib chip. -char *snd_dmxoption = ""; -int opl_io_port = 0x388; +should_be_const constcharstar snd_dmxoption = ""; +isb_int16_t opl_io_port = 0x388; // If true, OPL sound channels are reversed to their correct arrangement // (as intended by the MIDI standard) rather than the backwards one @@ -362,7 +396,7 @@ static boolean opl_stereo_correct = false; static boolean LoadInstrumentTable(void) { - byte *lump; + should_be_const byte *lump; lump = W_CacheLumpName(DEH_String("genmidi"), PU_STATIC); @@ -459,7 +493,7 @@ static void ReleaseVoice(int index) // Load data to the specified operator static void LoadOperatorData(int operator, genmidi_op_t *data, - boolean max_level, unsigned int *volume) + boolean max_level, opl_vol_t *volume) { int level; @@ -535,6 +569,7 @@ static void SetVoiceInstrument(opl_voice_t *voice, + 0x0f - (data->carrier.sustain & 0x0f); } +#include static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) { genmidi_voice_t *opl_voice; @@ -543,6 +578,7 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) unsigned int car_volume; unsigned int mod_volume; + assert(volume < 256); voice->note_volume = volume; opl_voice = &voice->current_instr->voices[voice->current_instr_voice]; @@ -595,6 +631,7 @@ static void SetVoicePan(opl_voice_t *voice, unsigned int pan) { genmidi_voice_t *opl_voice; + assert(pan < 256); voice->reg_pan = pan; opl_voice = &voice->current_instr->voices[voice->current_instr_voice];; @@ -638,6 +675,9 @@ static void I_OPL_SetMusicVolume(int volume) { unsigned int i; +#if DUMPO + volume = 127; +#endif if (current_music_volume == volume) { return; @@ -1211,7 +1251,7 @@ static void ControllerEvent(opl_track_data_t *track, midi_event_t *event) default: #ifdef OPL_MIDI_DEBUG - fprintf(stderr, "Unknown MIDI controller type: %i\n", controller); + stderr_print( "Unknown MIDI controller type: %i\n", controller); #endif break; } @@ -1263,7 +1303,7 @@ static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event) static void MetaSetTempo(unsigned int tempo) { - OPL_AdjustCallbacks((float) us_per_beat / tempo); + OPL_AdjustCallbacks(us_per_beat, tempo); us_per_beat = tempo; } @@ -1304,7 +1344,7 @@ static void MetaEvent(opl_track_data_t *track, midi_event_t *event) default: #ifdef OPL_MIDI_DEBUG - fprintf(stderr, "Unknown MIDI meta event type: %i\n", + stderr_print( "Unknown MIDI meta event type: %i\n", event->data.meta.type); #endif break; @@ -1349,7 +1389,7 @@ static void ProcessEvent(opl_track_data_t *track, midi_event_t *event) default: #ifdef OPL_MIDI_DEBUG - fprintf(stderr, "Unknown MIDI event type %i\n", event->event_type); + stderr_print( "Unknown MIDI event type %i\n", event->event_type); #endif break; } @@ -1360,8 +1400,22 @@ static void InitChannel(opl_channel_data_t *channel); // Restart a song from the beginning. -static void RestartSong(void *unused) +#if DOOM_TINY +// bit 0 means we can't restart because we are too deep in stack +// bit 1 means we need to restart +uint8_t restart_song_state; +#endif + +void RestartSong(void *unused) { +#if DOOM_TINY + if (restart_song_state & 1) { + // need to defer restart of song (because of stack) + restart_song_state |= 2; + return; + } + restart_song_state &= ~2; +#endif unsigned int i; running_tracks = num_tracks; @@ -1383,7 +1437,7 @@ static void RestartSong(void *unused) // Callback function invoked when another event needs to be read from // a track. -static void TrackTimerCallback(void *arg) +void TrackTimerCallback(void *arg) { opl_track_data_t *track = arg; midi_event_t *event; @@ -1394,7 +1448,7 @@ static void TrackTimerCallback(void *arg) { return; } - +// printf("MIDIEVENT %d %d %d %d\n", event->event_type, event->data.channel.channel, event->data.channel.param1, event->data.channel.param2); ProcessEvent(track, event); // End of track? @@ -1432,6 +1486,7 @@ static void ScheduleTrack(opl_track_data_t *track) // Get the number of microseconds until the next event. nticks = MIDI_GetDeltaTime(track->iter); +// printf("DELTA TICK %d\n", nticks); us = ((uint64_t) nticks * us_per_beat) / ticks_per_beat; // Set a timer to be invoked when the next event is @@ -1607,12 +1662,13 @@ static void I_OPL_UnRegisterSong(void *handle) // Determine whether memory block is a .mid file -static boolean IsMid(byte *mem, int len) +static boolean IsMid(should_be_const byte *mem, int len) { return len > 4 && !memcmp(mem, "MThd", 4); } -static boolean ConvertMus(byte *musdata, int len, char *filename) +#if !USE_MUSX +static boolean ConvertMus(should_be_const byte *musdata, int len, char *filename) { MEMFILE *instream; MEMFILE *outstream; @@ -1637,11 +1693,11 @@ static boolean ConvertMus(byte *musdata, int len, char *filename) return result; } +#endif -static void *I_OPL_RegisterSong(void *data, int len) +static void *I_OPL_RegisterSong(should_be_const void *data, int len) { midi_file_t *result; - char *filename; if (!music_initialized) { @@ -1651,6 +1707,14 @@ static void *I_OPL_RegisterSong(void *data, int len) // MUS files begin with "MUS" // Reject anything which doesnt have this signature +#if USE_DIRECT_MIDI_LUMP +#if !USE_MUSX + result = MIDI_LoadRaw(data, len); +#else + result = MUSX_LoadRaw(data, len); +#endif +#else + char *filename; filename = M_TempFile("doom.mid"); if (IsMid(data, len) && len < MAXMIDLENGTH) @@ -1664,17 +1728,40 @@ static void *I_OPL_RegisterSong(void *data, int len) ConvertMus(data, len, filename); } + int free_file = 1; +#if 0 + static int foo = -1; + if (foo == 0) filename = "/home/graham/dev/MCWhereIsMyMind.mid"; + if (foo == 1) filename = "/home/graham/dev/moonlight.mid"; + if (foo == 2) filename = "/home/graham/dev/Compound_quadruple_drum_pattern.mid"; + foo++; + + free_file = 0; +#endif result = MIDI_LoadFile(filename); +#if USE_MIDI_DUMP_FILE + for(int i=0;imem == data) { + char out_filename[32]; + sprintf(out_filename, "%s.midx", lumpinfo[i]->name); + MIDI_DumpFile(result, out_filename); + break; + } + } +#endif if (result == NULL) { - fprintf(stderr, "I_OPL_RegisterSong: Failed to load MID.\n"); + stderr_print( "I_OPL_RegisterSong: Failed to load MID.\n"); } // remove file now - remove(filename); - free(filename); + if (free_file) { + remove(filename); + free(filename); + } +#endif return result; } @@ -1715,11 +1802,10 @@ static void I_OPL_ShutdownMusic(void) static boolean I_OPL_InitMusic(void) { - char *dmxoption; +#if !DOOM_TINY + const char *dmxoption; opl_init_result_t chip_type; - OPL_SetSampleRate(snd_samplerate); - chip_type = OPL_Init(opl_io_port); if (chip_type == OPL_INIT_NONE) { @@ -1749,6 +1835,9 @@ static boolean I_OPL_InitMusic(void) // Secret, undocumented DMXOPTION that reverses the stereo channels // into their correct orientation. opl_stereo_correct = strstr(dmxoption, "-reverse") != NULL; +#else + OPL_Init(opl_io_port); +#endif // Initialize all registers. @@ -1771,13 +1860,13 @@ static boolean I_OPL_InitMusic(void) return true; } -static snddevice_t music_opl_devices[] = +static const snddevice_t music_opl_devices[] = { SNDDEVICE_ADLIB, SNDDEVICE_SB, }; -music_module_t music_opl_module = +const music_module_t music_opl_module = { music_opl_devices, arrlen(music_opl_devices), diff --git a/src/i_pcsound.c b/src/i_pcsound.c index ff5e0b0e..4860397a 100644 --- a/src/i_pcsound.c +++ b/src/i_pcsound.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -35,8 +36,8 @@ static boolean pcs_initialized = false; static SDL_mutex *sound_lock; static boolean use_sfx_prefix; -static uint8_t *current_sound_lump = NULL; -static uint8_t *current_sound_pos = NULL; +static should_be_const uint8_t *current_sound_lump = NULL; +static should_be_const uint8_t *current_sound_pos = NULL; static unsigned int current_sound_remaining = 0; static int current_sound_handle = 0; static int current_sound_lump_num = -1; @@ -103,7 +104,7 @@ static void PCSCallbackFunc(int *duration, int *freq) SDL_UnlockMutex(sound_lock); } -static boolean CachePCSLump(sfxinfo_t *sfxinfo) +static boolean CachePCSLump(should_be_const sfxinfo_t *sfxinfo) { int lumplen; int headerlen; @@ -118,8 +119,8 @@ static boolean CachePCSLump(sfxinfo_t *sfxinfo) // Load from WAD - current_sound_lump = W_CacheLumpNum(sfxinfo->lumpnum, PU_STATIC); - lumplen = W_LumpLength(sfxinfo->lumpnum); + current_sound_lump = W_CacheLumpNum(sfx_mut(sfxinfo)->lumpnum, PU_STATIC); + lumplen = W_LumpLength(sfx_mut(sfxinfo)->lumpnum); // Read header @@ -139,7 +140,7 @@ static boolean CachePCSLump(sfxinfo_t *sfxinfo) current_sound_remaining = headerlen; current_sound_pos = current_sound_lump + 4; - current_sound_lump_num = sfxinfo->lumpnum; + current_sound_lump_num = sfx_mut(sfxinfo)->lumpnum; return true; } @@ -148,7 +149,7 @@ static boolean CachePCSLump(sfxinfo_t *sfxinfo) // Heretic source code, where there are remnants of this left over // from Doom. -static boolean IsDisabledSound(sfxinfo_t *sfxinfo) +static boolean IsDisabledSound(should_be_const sfxinfo_t *sfxinfo) { int i; const char *disabled_sounds[] = { @@ -171,7 +172,7 @@ static boolean IsDisabledSound(sfxinfo_t *sfxinfo) return false; } -static int I_PCS_StartSound(sfxinfo_t *sfxinfo, +static int I_PCS_StartSound(should_be_const sfxinfo_t *sfxinfo, int channel, int vol, int sep, @@ -240,7 +241,7 @@ static void I_PCS_StopSound(int handle) // for a given SFX name. // -static int I_PCS_GetSfxLumpNum(sfxinfo_t* sfx) +static int I_PCS_GetSfxLumpNum(should_be_const sfxinfo_t* sfx) { char namebuf[9]; diff --git a/src/i_sdlmusic.c b/src/i_sdlmusic.c index 35c50275..68c0b953 100644 --- a/src/i_sdlmusic.c +++ b/src/i_sdlmusic.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -56,7 +57,7 @@ static boolean sdl_was_initialized = false; static boolean musicpaused = false; static int current_music_volume; -char *timidity_cfg_path = ""; +constcharstar timidity_cfg_path = ""; static char *temp_timidity_cfg = NULL; @@ -101,7 +102,11 @@ void I_InitTimidityConfig(void) if (snd_musicdevice == SNDDEVICE_GUS) { +#if !NO_USE_GUS success = GUS_WriteConfig(temp_timidity_cfg); +#else + success = false; +#endif } else { @@ -177,11 +182,11 @@ static boolean I_SDL_InitMusic(void) { if (SDL_Init(SDL_INIT_AUDIO) < 0) { - fprintf(stderr, "Unable to set up sound.\n"); + stderr_print( "Unable to set up sound.\n"); } else if (Mix_OpenAudio(snd_samplerate, AUDIO_S16SYS, 2, 1024) < 0) { - fprintf(stderr, "Error initializing SDL_mixer: %s\n", + stderr_print( "Error initializing SDL_mixer: %s\n", Mix_GetError()); SDL_QuitSubSystem(SDL_INIT_AUDIO); } @@ -348,12 +353,12 @@ static void I_SDL_UnRegisterSong(void *handle) // Determine whether memory block is a .mid file -static boolean IsMid(byte *mem, int len) +static boolean IsMid(should_be_const byte *mem, int len) { return len > 4 && !memcmp(mem, "MThd", 4); } -static boolean ConvertMus(byte *musdata, int len, const char *filename) +static boolean ConvertMus(should_be_const byte *musdata, int len, const char *filename) { MEMFILE *instream; MEMFILE *outstream; @@ -379,7 +384,7 @@ static boolean ConvertMus(byte *musdata, int len, const char *filename) return result; } -static void *I_SDL_RegisterSong(void *data, int len) +static void *I_SDL_RegisterSong(should_be_const void *data, int len) { char *filename; Mix_Music *music; @@ -417,7 +422,7 @@ static void *I_SDL_RegisterSong(void *data, int len) music = NULL; if (!I_MidiPipe_RegisterSong(filename)) { - fprintf(stderr, "Error loading midi: %s\n", + stderr_print( "Error loading midi: %s\n", "Could not communicate with midiproc."); } } @@ -428,7 +433,7 @@ static void *I_SDL_RegisterSong(void *data, int len) if (music == NULL) { // Failed to load - fprintf(stderr, "Error loading midi: %s\n", Mix_GetError()); + stderr_print( "Error loading midi: %s\n", Mix_GetError()); } // Remove the temporary MIDI file; however, when using an external diff --git a/src/i_sdlsound.c b/src/i_sdlsound.c index 4f256b88..8dd1214d 100644 --- a/src/i_sdlsound.c +++ b/src/i_sdlsound.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard // Copyright(C) 2008 David Flater +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -49,7 +50,7 @@ typedef struct allocated_sound_s allocated_sound_t; struct allocated_sound_s { - sfxinfo_t *sfxinfo; + should_be_const sfxinfo_t *sfxinfo; Mix_Chunk chunk; int use_count; int pitch; @@ -64,8 +65,8 @@ static int mixer_freq; static Uint16 mixer_format; static int mixer_channels; static boolean use_sfx_prefix; -static boolean (*ExpandSoundData)(sfxinfo_t *sfxinfo, - byte *data, +static boolean (*ExpandSoundData)(should_be_const sfxinfo_t *sfxinfo, + should_be_const byte *data, int samplerate, int length) = NULL; @@ -73,6 +74,11 @@ static boolean (*ExpandSoundData)(sfxinfo_t *sfxinfo, // When a sound is played, it is moved to the head, so that the oldest // sounds not used recently are at the tail. +#define MUNGE_AUDIO 0 +#if MUNGE_AUDIO +static uint8_t *munge_audio(const uint8_t *data, int length, const char *filename); +#endif + static allocated_sound_t *allocated_sounds_head = NULL; static allocated_sound_t *allocated_sounds_tail = NULL; static int allocated_sounds_size = 0; @@ -195,7 +201,7 @@ static void ReserveCacheSpace(size_t len) // Allocate a block for a new sound effect. -static allocated_sound_t *AllocateSound(sfxinfo_t *sfxinfo, size_t len) +static allocated_sound_t *AllocateSound(should_be_const sfxinfo_t *sfxinfo, size_t len) { allocated_sound_t *snd; @@ -274,7 +280,7 @@ static void UnlockAllocatedSound(allocated_sound_t *snd) // Search through the list of allocated sounds and return the one that matches // the supplied sfxinfo entry and pitch level. -static allocated_sound_t * GetAllocatedSoundBySfxInfoAndPitch(sfxinfo_t *sfxinfo, int pitch) +static allocated_sound_t * GetAllocatedSoundBySfxInfoAndPitch(should_be_const sfxinfo_t *sfxinfo, int pitch) { allocated_sound_t * p = allocated_sounds_head; @@ -594,8 +600,8 @@ static void WriteWAV(char *filename, byte *data, // Generic sound expansion function for any sample rate. // Returns number of clipped samples (always 0). -static boolean ExpandSoundData_SDL(sfxinfo_t *sfxinfo, - byte *data, +static boolean ExpandSoundData_SDL(should_be_const sfxinfo_t *sfxinfo, + should_be_const byte *data, int samplerate, int length) { @@ -711,17 +717,17 @@ static boolean ExpandSoundData_SDL(sfxinfo_t *sfxinfo, // Load and convert a sound effect // Returns true if successful -static boolean CacheSFX(sfxinfo_t *sfxinfo) +static boolean CacheSFX(should_be_const sfxinfo_t *sfxinfo) { int lumpnum; unsigned int lumplen; int samplerate; unsigned int length; - byte *data; + should_be_const byte *data; // need to load the sound - lumpnum = sfxinfo->lumpnum; + lumpnum = sfx_mut(sfxinfo)->lumpnum; data = W_CacheLumpNum(lumpnum, PU_STATIC); lumplen = W_LumpLength(lumpnum); @@ -762,10 +768,43 @@ static boolean CacheSFX(sfxinfo_t *sfxinfo) // Sample rate conversion - if (!ExpandSoundData(sfxinfo, data + 8, samplerate, length)) +#if MUNGE_AUDIO + char filename[16]; + allocated_sound_t * snd; + + M_snprintf(filename, sizeof(filename), "%s.d2", + DEH_String(sfxinfo->name)); + + uint8_t *d2 = munge_audio(data, (int)length, filename); + if (1) { + char filename[16]; + allocated_sound_t * snd; + + M_snprintf(filename, sizeof(filename), "%s.dff", + DEH_String(sfxinfo->name)); + uint8_t *d = malloc(length); + d[0] = data[0]; + for(int s=1;s lumplen - 8 || length <= 48) + { + printf("Invalid %s\n", DEH_String(sounds[i].name)); + continue; + } + + // The DMX sound library seems to skip the first 16 and last 16 + // bytes of the lump - reason unknown. + + data += 16; + length -= 32; + printf("%s %d %d\n", DEH_String(sounds[i].name), samplerate, length); +#if 0 +#if 0 + uint8_t predict = data[0]; + for(int s=1;slumpnum = W_CheckNumForName(namebuf); - if (sounds[i].lumpnum != -1) + if (sfx_mut(&sounds[i])->lumpnum != -1) { CacheSFX(&sounds[i]); } @@ -847,9 +977,74 @@ static void I_SDL_PrecacheSounds(sfxinfo_t *sounds, int num_sounds) printf("\n"); } +#if MUNGE_AUDIO +static uint8_t *munge_audio(const uint8_t *data, int length, const char *filename) { + int16_t *raw = calloc(2, length); + for (int s = 0; s < length; s++) { + raw[s] = (int16_t) ((((int) data[s]) - 128)) << 8; + } + int32_t average_deltas[2]; + int i; + + average_deltas[0] = average_deltas[1] = 0; + + for (i = 255; i--;) { + average_deltas[0] -= average_deltas[0] >> 3; + average_deltas[0] += abs((int32_t) raw[i] - raw[i - 1]); + } + + average_deltas[0] >>= 3; + average_deltas[1] >>= 3; + + void *ctx = adpcm_create_context(1, 3, NOISE_SHAPING_DYNAMIC, average_deltas); +#define MUNGE_BLOCK_SIZE 256 +#define MUNGE_ENCODED_BLOCK_SIZE (MUNGE_BLOCK_SIZE / 2) + + int block_size = (MUNGE_BLOCK_SIZE - 1) / (1 ^ 3) + (1 * 4); + printf("block size should be %d\n", block_size); + // todo not correct + uint8_t *enc = malloc(((length + MUNGE_BLOCK_SIZE - 1) / 2) & ~(MUNGE_ENCODED_BLOCK_SIZE-1)); + uint8_t *encout = enc; + for (int off = 0; off < length; off += MUNGE_BLOCK_SIZE) { + int local_len = length - off; + if (local_len > MUNGE_BLOCK_SIZE) local_len = MUNGE_BLOCK_SIZE; + size_t outbufsize = 0; + adpcm_encode_block(ctx, encout, &outbufsize, raw + off, local_len); +// printf("ENCODE %p + %04x -> %p + %04x\n", raw + off, local_len * 2, encout, (int)outbufsize); + encout += outbufsize; + printf("OB SIZE %d\n", (int)outbufsize); + } + if (filename) { + FILE *out = fopen(filename, "wb"); + fwrite(enc, 1, encout-enc, out); + fclose(out); + } + + const uint8_t *src = enc; + int16_t *raw2 = calloc(2, length); + for (int off = 0; off < length; off += MUNGE_BLOCK_SIZE) { + int local_len = length - off; + if (local_len > MUNGE_BLOCK_SIZE) local_len = MUNGE_BLOCK_SIZE; + int samps = adpcm_decode_block(raw2 + off, src, local_len / 2, 1); + printf("%d\n", samps); +// printf("DDECODE %p + %04x <- %p + %04x\n", raw2 + off, local_len * 2, src, local_len/2); + src += MUNGE_ENCODED_BLOCK_SIZE; + } + free(enc); + uint8_t *data2 = malloc(length); + for (int s = 0; s < length; s++) { + data2[s] = (raw2[s] >> 8) ^ 0x80; + } + free(raw2); + adpcm_free_context(ctx); + free(raw); + return data2; +} +#endif + #else -static void I_SDL_PrecacheSounds(sfxinfo_t *sounds, int num_sounds) +static void I_SDL_PrecacheSounds(should_be_const sfxinfo_t *sounds, int num_sounds) { // no-op } @@ -858,7 +1053,7 @@ static void I_SDL_PrecacheSounds(sfxinfo_t *sounds, int num_sounds) // Load a SFX chunk into memory and ensure that it is locked. -static boolean LockSound(sfxinfo_t *sfxinfo) +static boolean LockSound(should_be_const sfxinfo_t *sfxinfo) { // If the sound isn't loaded, load it now if (GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH) == NULL) @@ -879,7 +1074,7 @@ static boolean LockSound(sfxinfo_t *sfxinfo) // for a given SFX name. // -static int I_SDL_GetSfxLumpNum(sfxinfo_t *sfx) +static int I_SDL_GetSfxLumpNum(should_be_const sfxinfo_t *sfx) { char namebuf[9]; @@ -921,7 +1116,7 @@ static void I_SDL_UpdateSoundParams(int handle, int vol, int sep) // is set, but currently not used by mixing. // -static int I_SDL_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch) +static int I_SDL_StartSound(should_be_const sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch) { allocated_sound_t *snd; diff --git a/src/i_sound.c b/src/i_sound.c index 468b2da7..face8cbb 100644 --- a/src/i_sound.c +++ b/src/i_sound.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -18,7 +19,11 @@ #include #include -#include "SDL_mixer.h" +#if PICO_BUILD +#include "i_picosound.h" +#endif + +//#include "SDL_mixer.h" #include "config.h" #include "doomtype.h" @@ -30,6 +35,7 @@ #include "m_config.h" // Sound sample rate to use for digital output (Hz) +#if !DOOM_TINY int snd_samplerate = 44100; @@ -45,76 +51,101 @@ int snd_maxslicetime_ms = 28; // External command to invoke to play back music. -char *snd_musiccmd = ""; +should_be_const constcharstar snd_musiccmd = ""; +#endif // Whether to vary the pitch of sound effects // Each game will set the default differently -int snd_pitchshift = -1; +isb_int8_t snd_pitchshift = -1; +#if !DOOM_TINY int snd_musicdevice = SNDDEVICE_SB; int snd_sfxdevice = SNDDEVICE_SB; +#endif // Low-level sound and music modules we are using -static sound_module_t *sound_module; -static music_module_t *music_module; +static const sound_module_t *sound_module; +static const music_module_t *music_module; // If true, the music pack module was successfully initialized. +#if !NO_USE_MUSIC_PACKS static boolean music_packs_active = false; // This is either equal to music_module or &music_pack_module, // depending on whether the current track is substituted. -static music_module_t *active_music_module; +static const music_module_t *active_music_module; +#else +#define active_music_module music_module +#endif // Sound modules +#if !NO_USE_TIMIDITY extern void I_InitTimidityConfig(void); +#endif extern sound_module_t sound_sdl_module; extern sound_module_t sound_pcsound_module; extern music_module_t music_sdl_module; -extern music_module_t music_opl_module; +extern const music_module_t music_opl_module; extern music_module_t music_pack_module; +#if PICO_BUILD +extern sound_module_t sound_pico_module; +#endif // For OPL module: - +#if !DOOM_SMALL extern opl_driver_ver_t opl_drv_ver; extern int opl_io_port; +#endif // For native music module: -extern char *music_pack_path; -extern char *timidity_cfg_path; +#if !NO_USE_MUSIC_PACKS +extern constcharstar music_pack_path; +#endif +#if !NO_USE_TIMIDITY +extern constcharstar timidity_cfg_path; +#endif // DOS-specific options: These are unused but should be maintained // so that the config file can be shared between chocolate // doom and doom.exe +#if !DOOM_SMALL static int snd_sbport = 0; static int snd_sbirq = 0; static int snd_sbdma = 0; static int snd_mport = 0; +#endif // Compiled-in sound modules: -static sound_module_t *sound_modules[] = +static sound_module_t *sound_modules[] = { +#if PICO_BUILD + &sound_pico_module, +#else &sound_sdl_module, &sound_pcsound_module, +#endif NULL, }; // Compiled-in music modules: -static music_module_t *music_modules[] = +static const music_module_t *music_modules[] = { +#if !PICO_BUILD &music_sdl_module, +#endif &music_opl_module, NULL, }; // Check if a sound device is in the given list of devices -static boolean SndDeviceInList(snddevice_t device, snddevice_t *list, +static boolean SndDeviceInList(snddevice_t device, const snddevice_t *list, int len) { int i; @@ -135,6 +166,7 @@ static boolean SndDeviceInList(snddevice_t device, snddevice_t *list, static void InitSfxModule(boolean use_sfx_prefix) { +#if !DOOM_TINY int i; sound_module = NULL; @@ -157,12 +189,18 @@ static void InitSfxModule(boolean use_sfx_prefix) } } } +#else + if (sound_modules[0] && sound_modules[0]->Init(use_sfx_prefix)) { + sound_module = sound_modules[0]; + } +#endif } // Initialize music according to snd_musicdevice. static void InitMusicModule(void) { +#if !DOOM_TINY int i; music_module = NULL; @@ -185,6 +223,12 @@ static void InitMusicModule(void) } } } +#else + if (music_modules[0] && music_modules[0]->Init()) + { + music_module = music_modules[0]; + } +#endif } // @@ -195,7 +239,10 @@ static void InitMusicModule(void) void I_InitSound(boolean use_sfx_prefix) { - boolean nosound, nosfx, nomusic, nomusicpacks; + boolean nosound, nosfx, nomusic; +#if !NO_USE_MUSIC_PACKS + boolean nomusicpacks; +#endif //! // @vanilla @@ -221,6 +268,7 @@ void I_InitSound(boolean use_sfx_prefix) nomusic = M_CheckParm("-nomusic") > 0; +#if !NO_USE_MUSIC_PACKS //! // // Disable substitution music packs. @@ -230,6 +278,7 @@ void I_InitSound(boolean use_sfx_prefix) // Auto configure the music pack directory. M_SetMusicPackDir(); +#endif // Initialize the sound and music subsystems. @@ -239,12 +288,14 @@ void I_InitSound(boolean use_sfx_prefix) // the TIMIDITY_CFG environment variable here before SDL_mixer // is opened. +#if !NO_USE_TIMIDITY if (!nomusic && (snd_musicdevice == SNDDEVICE_GENMIDI || snd_musicdevice == SNDDEVICE_GUS)) { I_InitTimidityConfig(); } +#endif if (!nosfx) { @@ -257,11 +308,13 @@ void I_InitSound(boolean use_sfx_prefix) active_music_module = music_module; } +#if !NO_USE_MUSIC_PACKS // We may also have substitute MIDIs we can load. if (!nomusicpacks && music_module != NULL) { music_packs_active = music_pack_module.Init(); } +#endif } } @@ -272,10 +325,12 @@ void I_ShutdownSound(void) sound_module->Shutdown(); } +#if !NO_USE_MUSIC_PACKS if (music_packs_active) { music_pack_module.Shutdown(); } +#endif if (music_module != NULL) { @@ -283,7 +338,7 @@ void I_ShutdownSound(void) } } -int I_GetSfxLumpNum(sfxinfo_t *sfxinfo) +int I_GetSfxLumpNum(should_be_const sfxinfo_t *sfxinfo) { if (sound_module != NULL) { @@ -338,7 +393,7 @@ void I_UpdateSoundParams(int channel, int vol, int sep) } } -int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch) +int I_StartSound(should_be_const sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch) { if (sound_module != NULL) { @@ -371,7 +426,7 @@ boolean I_SoundIsPlaying(int channel) } } -void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds) +void I_PrecacheSounds(should_be_const sfxinfo_t *sounds, int num_sounds) { if (sound_module != NULL && sound_module->CacheSounds != NULL) { @@ -412,8 +467,9 @@ void I_ResumeSong(void) } } -void *I_RegisterSong(void *data, int len) +void *I_RegisterSong(should_be_const void *data, int len) { +#if !NO_USE_MUSIC_PACKS // If the music pack module is active, check to see if there is a // valid substitution for this track. If there is, we set the // active_music_module pointer to the music pack module for the @@ -432,6 +488,7 @@ void *I_RegisterSong(void *data, int len) // No substitution for this track, so use the main module. active_music_module = music_module; +#endif if (active_music_module != NULL) { return active_music_module->RegisterSong(data, len); @@ -480,30 +537,46 @@ boolean I_MusicIsPlaying(void) void I_BindSoundVariables(void) { - extern char *snd_dmxoption; + extern should_be_const constcharstar snd_dmxoption; +#if !NO_USE_LIBSAMPLERATE extern int use_libsamplerate; extern float libsamplerate_scale; +#endif +#if !DOOM_TINY M_BindIntVariable("snd_musicdevice", &snd_musicdevice); M_BindIntVariable("snd_sfxdevice", &snd_sfxdevice); +#endif +#if !DOOM_SMALL M_BindIntVariable("snd_sbport", &snd_sbport); M_BindIntVariable("snd_sbirq", &snd_sbirq); M_BindIntVariable("snd_sbdma", &snd_sbdma); M_BindIntVariable("snd_mport", &snd_mport); +#endif +#if !DOOM_TINY M_BindIntVariable("snd_maxslicetime_ms", &snd_maxslicetime_ms); M_BindStringVariable("snd_musiccmd", &snd_musiccmd); M_BindStringVariable("snd_dmxoption", &snd_dmxoption); M_BindIntVariable("snd_samplerate", &snd_samplerate); M_BindIntVariable("snd_cachesize", &snd_cachesize); +#endif +#if !DOOM_SMALL M_BindIntVariable("opl_io_port", &opl_io_port); +#endif M_BindIntVariable("snd_pitchshift", &snd_pitchshift); +#if !NO_USE_MUSIC_PACKS M_BindStringVariable("music_pack_path", &music_pack_path); +#endif +#if !NO_USE_TIMIDITY M_BindStringVariable("timidity_cfg_path", &timidity_cfg_path); +#endif +#if !NO_USE_GUS M_BindStringVariable("gus_patch_path", &gus_patch_path); M_BindIntVariable("gus_ram_kb", &gus_ram_kb); - +#endif +#if !NO_USE_LIBSAMPLERATE M_BindIntVariable("use_libsamplerate", &use_libsamplerate); M_BindFloatVariable("libsamplerate_scale", &libsamplerate_scale); -} - +#endif +} \ No newline at end of file diff --git a/src/i_sound.h b/src/i_sound.h index 30580626..5088fa19 100644 --- a/src/i_sound.h +++ b/src/i_sound.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -28,12 +29,18 @@ // // SoundFX struct. // +#if USE_CONST_SFX +typedef const struct sfxinfo_struct sfxinfo_t; +#else typedef struct sfxinfo_struct sfxinfo_t; +#endif struct sfxinfo_struct { +#if !DOOM_ONLY // tag name, used for hexen. const char *tagname; +#endif // lump name. If we are running with use_sfx_prefix=true, a // 'DS' (or 'DP' for PC speaker sounds) is prepended to this. @@ -41,7 +48,11 @@ struct sfxinfo_struct char name[9]; // Sfx priority +#if DOOM_ONLY + uint8_t priority; +#else int priority; +#endif // referenced sound if a link sfxinfo_t *link; @@ -52,35 +63,56 @@ struct sfxinfo_struct // volume if a link int volume; +#if !USE_CONST_SFX // this is checked every second to see if sound // can be thrown out (if 0, then decrement, if -1, // then throw out, if > 0, then it is in use) int usefulness; // lump number of sfx - int lumpnum; - - // Maximum number of channels that the sound can be played on + lumpindex_t lumpnum; +#endif +#if !DOOM_ONLY + // Maximum number of channels that the sound can be played on // (Heretic) int numchannels; // data used by the low level code void *driver_data; +#endif }; +#if USE_CONST_SFX +struct sfxinfo_mut_struct { + // lump number of sfx + lumpindex_t lumpnum; + + // todo graham size probably dosn't need to be a 32 bit value + // this is checked every second to see if sound + // can be thrown out (if 0, then decrement, if -1, + // then throw out, if > 0, then it is in use) + int usefulness; +}; +#endif // // MusicInfo struct. // typedef struct { // up to 6-character name +#if !USE_CONST_MUSIC const char *name; +#else + const char name[7]; +#endif // lump number of music - int lumpnum; + lumpindex_t lumpnum; +#if !USE_CONST_MUSIC // music data void *data; +#endif // music handle once registered void *handle; @@ -122,7 +154,7 @@ typedef struct // Returns the lump index of the given sound. - int (*GetSfxLumpNum)(sfxinfo_t *sfxinfo); + int (*GetSfxLumpNum)(should_be_const sfxinfo_t *sfxinfo); // Called periodically to update the subsystem. @@ -135,7 +167,7 @@ typedef struct // Start a sound on a given channel. Returns the channel id // or -1 on failure. - int (*StartSound)(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch); + int (*StartSound)(should_be_const sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch); // Stop the sound playing on the given channel. @@ -147,19 +179,19 @@ typedef struct // Called on startup to precache sound effects (if necessary) - void (*CacheSounds)(sfxinfo_t *sounds, int num_sounds); + void (*CacheSounds)(should_be_const sfxinfo_t *sounds, int num_sounds); } sound_module_t; void I_InitSound(boolean use_sfx_prefix); void I_ShutdownSound(void); -int I_GetSfxLumpNum(sfxinfo_t *sfxinfo); +int I_GetSfxLumpNum(should_be_const sfxinfo_t *sfxinfo); void I_UpdateSound(void); void I_UpdateSoundParams(int channel, int vol, int sep); -int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch); +int I_StartSound(should_be_const sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch); void I_StopSound(int channel); boolean I_SoundIsPlaying(int channel); -void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds); +void I_PrecacheSounds(should_be_const sfxinfo_t *sounds, int num_sounds); // Interface for music modules @@ -167,7 +199,7 @@ typedef struct { // List of sound devices that this music module is used for. - snddevice_t *sound_devices; + const snddevice_t *sound_devices; int num_sound_devices; // Initialise the music subsystem @@ -193,7 +225,7 @@ typedef struct // Register a song handle from data // Returns a handle that can be used to play the song - void *(*RegisterSong)(void *data, int len); + void *(*RegisterSong)(should_be_const void *data, int len); // Un-register (free) song data @@ -221,19 +253,24 @@ void I_ShutdownMusic(void); void I_SetMusicVolume(int volume); void I_PauseSong(void); void I_ResumeSong(void); -void *I_RegisterSong(void *data, int len); +void *I_RegisterSong(should_be_const void *data, int len); void I_UnRegisterSong(void *handle); void I_PlaySong(void *handle, boolean looping); void I_StopSong(void); boolean I_MusicIsPlaying(void); -extern int snd_sfxdevice; +#if !DOOM_TINY extern int snd_musicdevice; +extern int snd_sfxdevice; +#else +#define snd_sfxdevice SNDDEVICE_SB +#define snd_musicdevice SNDDEVICE_SB +#endif extern int snd_samplerate; extern int snd_cachesize; extern int snd_maxslicetime_ms; -extern char *snd_musiccmd; -extern int snd_pitchshift; +extern should_be_const constcharstar snd_musiccmd; +extern isb_int8_t snd_pitchshift; void I_BindSoundVariables(void); @@ -246,5 +283,15 @@ typedef enum { void I_SetOPLDriverVer(opl_driver_ver_t ver); +#if USE_CONST_SFX +typedef struct sfxinfo_mut_struct sfxinfo_mut_t; +sfxinfo_mut_t *get_mut_sfxinfo_t(const sfxinfo_t *sfxinfo); +extern sfxinfo_mut_t S_sfx_mut[]; +#define sfx_mut(s) (get_mut_sfxinfo_t(s)) +#else +#define sfx_mut(s) (s) +#endif + + #endif diff --git a/src/i_swap.h b/src/i_swap.h index 3dee8e3f..cc2cd3a9 100644 --- a/src/i_swap.h +++ b/src/i_swap.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -20,7 +21,17 @@ #ifndef __I_SWAP__ #define __I_SWAP__ +#if !PICO_ON_DEVICE #include "SDL_endian.h" +#else +#define SDL_SwapLE16(x) (x) +#define SDL_SwapLE32(x) (x) +#define SDL_SwapBE16(x) __builtin_bswap16(x) +#define SDL_SwapBE32(x) __builtin_bswap32(x) +#define SDL_BYTEORDER 0 +#define SDL_LITTLE_ENDIAN 0 +#define SDL_BIG_ENDIAN 1 +#endif // Endianess handling. // WAD files are stored little endian. diff --git a/src/i_system.c b/src/i_system.c index 6a2e6969..b987983d 100644 --- a/src/i_system.c +++ b/src/i_system.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -110,6 +111,12 @@ static byte *AutoAllocMemory(int *size, int default_ram, int min_ram) *size = default_ram * 1024 * 1024; +#if DOOM_SMALL +// *size = (384+64) * 1024; +#if PICO_ON_DEVICE + *size = 16 *1024; +#endif +#endif zonemem = malloc(*size); // Failed to allocate? Reduce zone size until we reach a size @@ -137,6 +144,7 @@ byte *I_ZoneBase (int *size) // Specify the heap size, in MiB (default 16). // +#if !NO_USE_ARGS p = M_CheckParmWithArgs("-mb", 1); if (p > 0) @@ -145,6 +153,7 @@ byte *I_ZoneBase (int *size) min_ram = default_ram; } else +#endif { default_ram = DEFAULT_RAM; min_ram = MIN_RAM; @@ -398,6 +407,7 @@ boolean I_GetMemoryValue(unsigned int offset, void *value, int size) // The default is to emulate DOS 7.1 (Windows 98). // +#if !NO_USE_ARGS p = M_CheckParmWithArgs("-setmem", 1); if (p > 0) @@ -432,6 +442,7 @@ boolean I_GetMemoryValue(unsigned int offset, void *value, int size) dos_mem_dump = mem_dump_custom; } } +#endif } switch (size) diff --git a/src/i_system.h b/src/i_system.h index 78376f3c..16095263 100644 --- a/src/i_system.h +++ b/src/i_system.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -51,8 +52,24 @@ ticcmd_t* I_BaseTiccmd (void); // Called by M_Responder when quit is selected. // Clean exit, displays sell blurb. void I_Quit (void) NORETURN; +#if DOOM_TINY +extern void handle_exit_key_down(int scancode, boolean shift, uint8_t *kb_buffer, int kb_len); +extern int8_t at_exit_screen; +extern uint8_t *exit_screen_kb_buffer_80; +#endif +#if !NO_IERROR || !PICO_ON_DEVICE void I_Error (const char *error, ...) NORETURN PRINTF_ATTR(1, 2); +#else +#include "pico.h" +//#define I_Error(args...) ((void)0) +#define I_Error(args...) __breakpoint(); +#endif + +#if DOOM_TINY +#include "pico/sem.h" +extern semaphore_t render_frame_ready, display_frame_freed; +#endif void I_Tactile (int on, int off, int total); @@ -82,5 +99,8 @@ void I_PrintBanner(const char *text); void I_PrintDivider(void); +#if USE_ZONE_FOR_MALLOC +extern boolean disallow_core1_malloc; +#endif #endif diff --git a/src/i_video.c b/src/i_video.c index 9070896d..a8fb0f70 100644 --- a/src/i_video.c +++ b/src/i_video.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -17,6 +18,8 @@ // +#include +#include #include "SDL.h" #include "SDL_opengl.h" @@ -98,11 +101,11 @@ int png_screenshots = 0; // SDL video driver name -char *video_driver = ""; +should_be_const constcharstar video_driver = ""; // Window position: -char *window_position = "center"; +should_be_const constcharstar window_position = "center"; // SDL display number on which to run. @@ -194,13 +197,18 @@ static unsigned int last_resize_time; // Gamma correction level to use -int usegamma = 0; +isb_int8_t usegamma = 0; +#ifndef NO_USE_JOYSTICK // Joystick/gamepad hysteresis unsigned int joywait = 0; +#endif static boolean MouseShouldBeGrabbed() { +#if DOOM_SMALL + return false; +#endif // never grab the mouse when in screensaver mode if (screensaver_mode) @@ -482,10 +490,12 @@ void I_StartTic (void) I_ReadMouse(); } +#if !NO_USE_JOYSTICK if (joywait < I_GetTime()) { I_UpdateJoystick(); } +#endif } @@ -816,7 +826,7 @@ void I_ReadScreen (pixel_t* scr) // // I_SetPalette // -void I_SetPalette (byte *doompalette) +void I_SetPalette (should_be_const byte *doompalette) { int i; @@ -973,6 +983,7 @@ void I_GraphicsCheckCommandLine(void) // Specify the screen width, in pixels. Implies -window. // +#if !NO_USE_ARGS i = M_CheckParmWithArgs("-width", 1); if (i > 0) @@ -1017,6 +1028,7 @@ void I_GraphicsCheckCommandLine(void) fullscreen = false; } } +#endif //! // @category video @@ -1090,7 +1102,7 @@ static void CenterWindow(int *x, int *y, int w, int h) if (SDL_GetDisplayBounds(video_display, &bounds) < 0) { - fprintf(stderr, "CenterWindow: Failed to read display bounds " + stderr_print( "CenterWindow: Failed to read display bounds " "for display #%d!\n", video_display); return; } @@ -1105,7 +1117,7 @@ void I_GetWindowPosition(int *x, int *y, int w, int h) // and if it doesn't, reset it. if (video_display < 0 || video_display >= SDL_GetNumVideoDisplays()) { - fprintf(stderr, + stderr_print( "I_GetWindowPosition: We were configured to run on display #%d, " "but it no longer exists (max %d). Moving to display 0.\n", video_display, SDL_GetNumVideoDisplays() - 1); @@ -1339,7 +1351,7 @@ static void SetVideoMode(void) void I_InitGraphics(void) { SDL_Event dummy; - byte *doompal; + should_be_const byte *doompal; char *env; // Pass through the XSCREENSAVER_WINDOW environment variable to @@ -1395,6 +1407,20 @@ void I_InitGraphics(void) // Set the palette doompal = W_CacheLumpName(DEH_String("PLAYPAL"), PU_CACHE); +#if PRINT_PALETTE + int size = W_LumpLength(W_GetNumForName(DEH_String("PLAYPAL"))); + assert(!(size % 768)); + printf("\nconst uint8_t doompal[%d * 768] = {\n", size / 768); + for(int i=0;iformat->palette, palette, 0, 256); diff --git a/src/i_video.h b/src/i_video.h index 0e0ee4ec..0a43434d 100644 --- a/src/i_video.h +++ b/src/i_video.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -26,6 +27,9 @@ #define SCREENWIDTH 320 #define SCREENHEIGHT 200 +#if DOOM_TINY +#define MAIN_VIEWHEIGHT (SCREENHEIGHT - 32 /* ST_HEIGHT */) +#endif // Screen height used when aspect_ratio_correct=true. @@ -43,7 +47,11 @@ void I_GraphicsCheckCommandLine(void); void I_ShutdownGraphics(void); // Takes full 8 bit values. -void I_SetPalette (byte* palette); +#if !USE_WHD +void I_SetPalette (should_be_const byte* palette); +#else +void I_SetPaletteNum(int num); +#endif int I_GetPaletteIndex(int r, int g, int b); void I_UpdateNoBlit (void); @@ -78,12 +86,16 @@ void I_StartTic (void); void I_EnableLoadingDisk(int xoffs, int yoffs); -extern char *video_driver; +extern should_be_const constcharstar video_driver; extern boolean screenvisible; +#if !USE_VANILLA_KEYBOARD_MAPPING_ONLY extern int vanilla_keyboard_mapping; +#else +#define vanilla_keyboard_mapping true +#endif extern boolean screensaver_mode; -extern int usegamma; +extern isb_int8_t usegamma; extern pixel_t *I_VideoBuffer; extern int screen_width; @@ -94,10 +106,31 @@ extern int integer_scaling; extern int vga_porch_flash; extern int force_software_renderer; -extern char *window_position; +extern should_be_const constcharstar window_position; void I_GetWindowPosition(int *x, int *y, int w, int h); // Joystic/gamepad hysteresis extern unsigned int joywait; +#if DOOM_TINY +enum { + VIDEO_TYPE_NONE, // note order is important as we compare (also there is an array of handlers) + VIDEO_TYPE_TEXT, + VIDEO_TYPE_SAVING, + VIDEO_TYPE_DOUBLE, + VIDEO_TYPE_SINGLE, + VIDEO_TYPE_WIPE, +}; +#define FIRST_VIDEO_TYPE_WITH_OVERLAYS VIDEO_TYPE_DOUBLE + +extern uint8_t next_video_type; +extern uint8_t next_frame_index; // next frame_index to be picked up by the diplsau +extern uint8_t next_overlay_index; +#if !DEMO1_ONLY +extern uint8_t *next_video_scroll; +#endif +extern int16_t *wipe_yoffsets_raw; // work area for y offsets +extern uint8_t *wipe_yoffsets; // position of start of y in each column (clipped 0->200) +extern uint32_t *wipe_linelookup; // offset of front image from start of screenbuffer (actually address of in PICO_ON_DEVICE) +#endif #endif diff --git a/src/image_decoder.c b/src/image_decoder.c new file mode 100644 index 00000000..52832493 --- /dev/null +++ b/src/image_decoder.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#if PICO_BUILD +#include "pico.h" +#else +#define __not_in_flash_func(x) x +#endif +#include "image_decoder.h" +#pragma GCC push_options +#if PICO_ON_DEVICE +#pragma GCC optimize("O3") +#endif + +uint16_t *__not_in_flash_func(read_raw_pixels_decoder)(th_bit_input *bi, uint16_t *buffer, uint buffer_size, uint8_t *tmp_buf, uint tmp_buf_size) { + uint min_cl = th_read_bits(bi, 4); + uint max_cl = th_read_bits(bi, 4); + uint bit_count = 32 - __builtin_clz(max_cl - min_cl); + uint count = 0; + for (uint p = 0; p < 32; p++) { + if (th_bit(bi)) { + for (uint bit = 0; bit < 8; bit++) { + if (th_bit(bi)) { + uint length; + if (min_cl == max_cl) { + length = min_cl; + } else { + assert(min_cl < max_cl); + length = min_cl + th_read_bits(bi, bit_count); + } + assert(count * 2 + 1 < tmp_buf_size); + tmp_buf[count * 2] = (p << 3) | bit; + tmp_buf[count * 2 + 1] = length; + count++; + } + } + } + } + assert(buffer_size >= th_decoder_size(count, max_cl)); + return th_create_decoder(buffer, tmp_buf, count, max_cl); +} + +uint16_t *__not_in_flash_func(read_raw_pixels_decoder_c3)(th_bit_input *bi, uint16_t *buffer, uint buffer_size, uint8_t *tmp_buf, uint tmp_buf_size) { + uint min_cl = th_read_bits(bi, 4); + uint max_cl = th_read_bits(bi, 4); +// printf(" Code length %d->%d\n", min_cl, max_cl); + uint bit_count = 32 - __builtin_clz(max_cl - min_cl); + uint count = 0; + for (uint p = 0; p < 32; p++) { + if (th_bit(bi)) { + for (uint bit = 0; bit < 8; bit++) { + if (th_bit(bi)) { + uint length; + if (min_cl == max_cl) { + length = min_cl; + } else { + length = min_cl + th_read_bits(bi, bit_count); + } + assert(count * 3 + 2 < tmp_buf_size); + tmp_buf[count * 3] = (p << 3) | bit; +// printf("0,%d = %d\n", (p << 3) | bit, length); + tmp_buf[count * 3 + 1] = 0; + tmp_buf[count * 3 + 2] = length; + count++; + } + } + } + } + bit_count = 32 - __builtin_clz(1 + max_cl - min_cl); + for(uint8_t i=0;i<7;i++) { + uint length = th_read_bits(bi, bit_count); + if (length != (1u << bit_count)-1) { + length += min_cl; + assert(count * 3 + 2 < tmp_buf_size); + tmp_buf[count * 3] = i; +// printf("1,%d = %d\n", i, length); + tmp_buf[count * 3 + 1] = 1; + tmp_buf[count * 3 + 2] = length; + count++; + } + } + + assert(buffer_size >= th_decoder_size(count, max_cl)); + return th_create_decoder_16(buffer, tmp_buf, count, max_cl); +} + +#pragma GCC pop_options + +void decode_data(uint8_t *dest, uint len, th_bit_input *bi, uint16_t *buffer, uint buffer_size, uint8_t *tmp_buf, uint tmp_buf_size) { + uint16_t *pos; + if (th_bit(bi)) { + pos = th_read_simple_decoder(bi, buffer, buffer_size, tmp_buf, tmp_buf_size); + } else { + pos = read_raw_pixels_decoder(bi, buffer, buffer_size, tmp_buf, tmp_buf_size); + } + th_make_prefix_length_table(buffer, tmp_buf); + assert(pos < buffer + buffer_size); + for(int i=0;iparameter_buf[cht->param_chars_read] = key; +#else + parameter_buf[cht->param_chars_read] = key; +#endif ++cht->param_chars_read; } @@ -83,7 +92,11 @@ cht_GetParam ( cheatseq_t* cht, char* buffer ) { +#if !DOOM_TINY memcpy(buffer, cht->parameter_buf, cht->parameter_chars); +#else + memcpy(buffer, parameter_buf, cht->parameter_chars); +#endif } diff --git a/src/m_cheat.h b/src/m_cheat.h index 6775e709..43b371f0 100644 --- a/src/m_cheat.h +++ b/src/m_cheat.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -26,8 +27,13 @@ // declaring a cheat +#if !DOOM_TINY #define CHEAT(value, parameters) \ { value, sizeof(value) - 1, parameters, 0, 0, "" } +#else +#define CHEAT(value, parameters) \ +{ value, sizeof(value) - 1, parameters, 0, 0 } +#endif #define MAX_CHEAT_LEN 25 #define MAX_CHEAT_PARAMS 5 @@ -36,15 +42,18 @@ typedef struct { // settings for this cheat - char sequence[MAX_CHEAT_LEN]; - size_t sequence_len; - int parameter_chars; + //char sequence[MAX_CHEAT_LEN]; + should_be_const char *sequence; + isb_uint8_t sequence_len; + isb_uint8_t parameter_chars; // state used during the game - size_t chars_read; - int param_chars_read; + isb_uint8_t chars_read; + isb_int8_t param_chars_read; +#ifndef DOOM_TINY // just use a shared one - don't think multiple cheats could be into parameters at the same time char parameter_buf[MAX_CHEAT_PARAMS]; +#endif } cheatseq_t; int diff --git a/src/m_config.c b/src/m_config.c index db4a9298..c38795d6 100644 --- a/src/m_config.c +++ b/src/m_config.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -17,6 +18,7 @@ // Configuration file interface. // +#if !NO_USE_BOUND_CONFIG #include #include @@ -25,7 +27,9 @@ #include #include +#if !PICO_ON_DEVICE #include "SDL_filesystem.h" +#endif #include "config.h" @@ -47,7 +51,7 @@ const char *configdir; -static char *autoload_path = ""; +static const char *autoload_path = ""; // Default filenames for configuration files. @@ -61,6 +65,8 @@ typedef enum DEFAULT_STRING, DEFAULT_FLOAT, DEFAULT_KEY, + DEFAULT_MOUSEB, + DEFAULT_INT_ISB8, } default_type_t; typedef struct @@ -71,8 +77,11 @@ typedef struct // Pointer to the location in memory of the variable union { int *i; - char **s; + isb_int8_t *isb8; + constcharstar *s; float *f; + key_type_t *key; + mouseb_type_t *mouseb; } location; // Type of the variable @@ -106,6 +115,8 @@ typedef struct #define CONFIG_VARIABLE_KEY(name) \ CONFIG_VARIABLE_GENERIC(name, DEFAULT_KEY) +#define CONFIG_VARIABLE_MOUSEB(name) \ + CONFIG_VARIABLE_GENERIC(name, DEFAULT_MOUSEB) #define CONFIG_VARIABLE_INT(name) \ CONFIG_VARIABLE_GENERIC(name, DEFAULT_INT) #define CONFIG_VARIABLE_INT_HEX(name) \ @@ -114,6 +125,8 @@ typedef struct CONFIG_VARIABLE_GENERIC(name, DEFAULT_FLOAT) #define CONFIG_VARIABLE_STRING(name) \ CONFIG_VARIABLE_GENERIC(name, DEFAULT_STRING) +#define CONFIG_VARIABLE_INT_ISB8(name) \ + CONFIG_VARIABLE_GENERIC(name, DEFAULT_INT_ISB8) //! @begin_config_file default @@ -128,7 +141,7 @@ static default_t doom_defaults_list[] = // the game to crash when entering the options menu. // - CONFIG_VARIABLE_INT(mouse_sensitivity), + CONFIG_VARIABLE_INT_ISB8(mouse_sensitivity), //! // Volume of sound effects, range 0-15. @@ -167,7 +180,7 @@ static default_t doom_defaults_list[] = // are not displayed. // - CONFIG_VARIABLE_INT(show_messages), + CONFIG_VARIABLE_INT_ISB8(show_messages), //! // Keyboard key to turn right. @@ -426,20 +439,20 @@ static default_t doom_defaults_list[] = // Mouse button to fire the currently selected weapon. // - CONFIG_VARIABLE_INT(mouseb_fire), + CONFIG_VARIABLE_MOUSEB(mouseb_fire), //! // Mouse button to turn on strafing. When held down, the player // will strafe left and right instead of turning left and right. // - CONFIG_VARIABLE_INT(mouseb_strafe), + CONFIG_VARIABLE_MOUSEB(mouseb_strafe), //! // Mouse button to move forward. // - CONFIG_VARIABLE_INT(mouseb_forward), + CONFIG_VARIABLE_MOUSEB(mouseb_forward), //! // @game hexen strife @@ -447,7 +460,7 @@ static default_t doom_defaults_list[] = // Mouse button to jump. // - CONFIG_VARIABLE_INT(mouseb_jump), + CONFIG_VARIABLE_MOUSEB(mouseb_jump), //! // If non-zero, joystick input is enabled. @@ -502,7 +515,7 @@ static default_t doom_defaults_list[] = // status bar displayed. // - CONFIG_VARIABLE_INT(screenblocks), + CONFIG_VARIABLE_INT_ISB8(screenblocks), //! // @game strife @@ -523,7 +536,7 @@ static default_t doom_defaults_list[] = // a non-zero value gives "low detail" mode. // - CONFIG_VARIABLE_INT(detaillevel), + CONFIG_VARIABLE_INT_ISB8(detaillevel), //! // Number of sounds that will be played simultaneously. @@ -810,7 +823,7 @@ static default_t extra_defaults_list[] = // game. If zero, the ENDOOM screen is not displayed. // - CONFIG_VARIABLE_INT(show_endoom), + CONFIG_VARIABLE_INT_ISB8(show_endoom), //! // @game doom strife @@ -960,7 +973,7 @@ static default_t extra_defaults_list[] = // the size of savegames. // - CONFIG_VARIABLE_INT(vanilla_savegame_limit), + CONFIG_VARIABLE_INT_ISB8(vanilla_savegame_limit), //! // @game doom strife @@ -971,7 +984,7 @@ static default_t extra_defaults_list[] = // limit to the size of demos. // - CONFIG_VARIABLE_INT(vanilla_demo_limit), + CONFIG_VARIABLE_INT_ISB8(vanilla_demo_limit), //! // If non-zero, the game behaves like Vanilla Doom, always assuming @@ -1024,37 +1037,37 @@ static default_t extra_defaults_list[] = // Mouse button to strafe left. // - CONFIG_VARIABLE_INT(mouseb_strafeleft), + CONFIG_VARIABLE_MOUSEB(mouseb_strafeleft), //! // Mouse button to strafe right. // - CONFIG_VARIABLE_INT(mouseb_straferight), + CONFIG_VARIABLE_MOUSEB(mouseb_straferight), //! // Mouse button to "use" an object, eg. a door or switch. // - CONFIG_VARIABLE_INT(mouseb_use), + CONFIG_VARIABLE_MOUSEB(mouseb_use), //! // Mouse button to move backwards. // - CONFIG_VARIABLE_INT(mouseb_backward), + CONFIG_VARIABLE_MOUSEB(mouseb_backward), //! // Mouse button to cycle to the previous weapon. // - CONFIG_VARIABLE_INT(mouseb_prevweapon), + CONFIG_VARIABLE_MOUSEB(mouseb_prevweapon), //! // Mouse button to cycle to the next weapon. // - CONFIG_VARIABLE_INT(mouseb_nextweapon), + CONFIG_VARIABLE_MOUSEB(mouseb_nextweapon), //! // If non-zero, double-clicking a mouse button acts like pressing @@ -1719,6 +1732,7 @@ static const int scantokey[128] = static void SaveDefaultCollection(default_collection_t *collection) { +#if !NO_USE_SAVE_CONFIG default_t *defaults; int i, v; FILE *f; @@ -1757,7 +1771,7 @@ static void SaveDefaultCollection(default_collection_t *collection) // the possibility of screwing up the user's config // file - v = *defaults[i].location.i; + v = *defaults[i].location.key; if (v == KEY_RSHIFT) { @@ -1801,6 +1815,14 @@ static void SaveDefaultCollection(default_collection_t *collection) fprintf(f, "%i", *defaults[i].location.i); break; + case DEFAULT_INT_ISB8: + fprintf(f, "%i", *defaults[i].location.isb8); + break; + + case DEFAULT_MOUSEB: + fprintf(f, "%i", *defaults[i].location.mouseb); + break; + case DEFAULT_INT_HEX: fprintf(f, "0x%x", *defaults[i].location.i); break; @@ -1818,6 +1840,7 @@ static void SaveDefaultCollection(default_collection_t *collection) } fclose (f); +#endif } // Parses integer values in the configuration file @@ -1850,6 +1873,12 @@ static void SetVariable(default_t *def, const char *value) case DEFAULT_INT_HEX: *def->location.i = ParseIntParameter(value); break; + case DEFAULT_INT_ISB8: + *def->location.isb8 = ParseIntParameter(value); + break; + case DEFAULT_MOUSEB: + *def->location.mouseb = ParseIntParameter(value); + break; case DEFAULT_KEY: @@ -1868,7 +1897,7 @@ static void SetVariable(default_t *def, const char *value) } def->original_translated = intparm; - *def->location.i = intparm; + *def->location.key = intparm; break; case DEFAULT_FLOAT: @@ -1879,6 +1908,7 @@ static void SetVariable(default_t *def, const char *value) static void LoadDefaultCollection(default_collection_t *collection) { +#if !NO_FILE_ACCESS FILE *f; default_t *def; char defname[80]; @@ -1919,7 +1949,7 @@ static void LoadDefaultCollection(default_collection_t *collection) // Strip off trailing non-printable characters (\r characters // from DOS text files) - while (strlen(strparm) > 0 && !isprint(strparm[strlen(strparm)-1])) + while (strlen(strparm) > 0 && !isprint((uint)strparm[strlen(strparm)-1])) { strparm[strlen(strparm)-1] = '\0'; } @@ -1936,6 +1966,7 @@ static void LoadDefaultCollection(default_collection_t *collection) } fclose (f); +#endif } // Set the default filenames to use for configuration files. @@ -1952,14 +1983,17 @@ void M_SetConfigFilenames(const char *main_config, const char *extra_config) void M_SaveDefaults (void) { +#if !NO_USE_BOUND_CONFIG SaveDefaultCollection(&doom_defaults); SaveDefaultCollection(&extra_defaults); +#endif } // // Save defaults to alternate filenames // +#if !NO_USE_BOUND_CONFIG void M_SaveDefaultsAlternate(const char *main, const char *extra) { const char *orig_main; @@ -1980,6 +2014,7 @@ void M_SaveDefaultsAlternate(const char *main, const char *extra) doom_defaults.filename = orig_main; extra_defaults.filename = orig_extra; } +#endif // // M_LoadDefaults @@ -2002,6 +2037,8 @@ void M_LoadDefaults (void) // default. // +#if !NO_USE_BOUND_CONFIG +#if !NO_USE_ARGS i = M_CheckParmWithArgs("-config", 1); if (i) @@ -2010,12 +2047,13 @@ void M_LoadDefaults (void) printf (" default file: %s\n",doom_defaults.filename); } else +#endif { doom_defaults.filename = M_StringJoin(configdir, default_main_config, NULL); } - printf("saving config in %s\n", doom_defaults.filename); +// printf("saving config in %s\n", doom_defaults.filename); //! // @arg @@ -2024,6 +2062,7 @@ void M_LoadDefaults (void) // the default. // +#if !NO_USE_ARGS i = M_CheckParmWithArgs("-extraconfig", 1); if (i) @@ -2033,6 +2072,7 @@ void M_LoadDefaults (void) extra_defaults.filename); } else +#endif { extra_defaults.filename = M_StringJoin(configdir, default_extra_config, NULL); @@ -2040,8 +2080,10 @@ void M_LoadDefaults (void) LoadDefaultCollection(&doom_defaults); LoadDefaultCollection(&extra_defaults); +#endif } +#if !NO_USE_BOUND_CONFIG // Get a configuration file variable by its name static default_t *GetDefaultForName(const char *name) @@ -2078,12 +2120,47 @@ void M_BindIntVariable(const char *name, int *location) variable = GetDefaultForName(name); assert(variable->type == DEFAULT_INT || variable->type == DEFAULT_INT_HEX + || variable->type == DEFAULT_INT_ISB8 || variable->type == DEFAULT_KEY); variable->location.i = location; variable->bound = true; } +void M_BindIntISB8Variable(const char *name, isb_int8_t *location) +{ + default_t *variable; + + variable = GetDefaultForName(name); + assert(variable->type == DEFAULT_INT_ISB8); + + variable->location.isb8 = location; + variable->bound = true; +} + + +void M_BindKeyVariable(const char *name, key_type_t *location) +{ + default_t *variable; + + variable = GetDefaultForName(name); + assert(variable->type == DEFAULT_KEY); + + variable->location.key = location; + variable->bound = true; +} + +void M_BindMouseBVariable(const char *name, mouseb_type_t *location) +{ + default_t *variable; + + variable = GetDefaultForName(name); + assert(variable->type == DEFAULT_MOUSEB); + variable->location.mouseb = location; + variable->bound = true; +} + + void M_BindFloatVariable(const char *name, float *location) { default_t *variable; @@ -2095,7 +2172,7 @@ void M_BindFloatVariable(const char *name, float *location) variable->bound = true; } -void M_BindStringVariable(const char *name, char **location) +void M_BindStringVariable(const char *name, constcharstar *location) { default_t *variable; @@ -2171,12 +2248,14 @@ float M_GetFloatVariable(const char *name) return *variable->location.f; } +#endif // Get the path to the default configuration dir to use, if NULL // is passed to M_SetConfigDir. static char *GetDefaultConfigDir(void) { +#if !PICO_ON_DEVICE #if !defined(_WIN32) || defined(_WIN32_WCE) // Configuration settings are stored in an OS-appropriate path @@ -2192,16 +2271,16 @@ static char *GetDefaultConfigDir(void) return result; } #endif /* #ifndef _WIN32 */ +#endif return M_StringDuplicate(""); } -// +// // SetConfigDir: // // Sets the location of the configuration directory, where configuration // files are stored - default.cfg, chocolate-doom.cfg, savegames, etc. // - void M_SetConfigDir(const char *dir) { // Use the directory that was passed, or find the default. @@ -2221,9 +2300,11 @@ void M_SetConfigDir(const char *dir) } // Make the directory if it doesn't already exist: - +#if !NO_FILE_ACCESS M_MakeDirectory(configdir); +#endif } +#if !NO_FILE_ACCESS #define MUSIC_PACK_README \ "Extract music packs into this directory in .flac or .ogg format;\n" \ @@ -2236,6 +2317,7 @@ void M_SetConfigDir(const char *dir) // the directory if necessary. void M_SetMusicPackDir(void) { +#if !NO_USE_MUSIC_PACKS const char *current_path; char *prefdir, *music_pack_path, *readme_path; @@ -2262,6 +2344,7 @@ void M_SetMusicPackDir(void) free(readme_path); free(music_pack_path); free(prefdir); +#endif } // @@ -2282,6 +2365,7 @@ char *M_GetSaveGameDir(const char *iwadname) // does not exist then it will automatically be created. // +#if !NO_USE_SAVE p = M_CheckParmWithArgs("-savedir", 1); if (p) { @@ -2327,10 +2411,12 @@ char *M_GetSaveGameDir(const char *iwadname) free(topdir); } +#endif return savegamedir; } +#if !DOOM_TINY // // Calculate the path to the directory for autoloaded WADs/DEHs. // Creates the directory as necessary. @@ -2357,3 +2443,6 @@ char *M_GetAutoloadDir(const char *iwadname) return result; } +#endif +#endif +#endif \ No newline at end of file diff --git a/src/m_config.h b/src/m_config.h index 5939b6be..e901bff3 100644 --- a/src/m_config.h +++ b/src/m_config.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -22,22 +23,44 @@ #include "doomtype.h" + +#if !NO_FILE_ACCESS +void M_SetMusicPackDir(void); +char *M_GetSaveGameDir(const char *iwadname); +char *M_GetAutoloadDir(const char *iwadname); +extern const char *configdir; +#endif +#if !NO_USE_BOUND_CONFIG void M_LoadDefaults(void); void M_SaveDefaults(void); void M_SaveDefaultsAlternate(const char *main, const char *extra); -void M_SetConfigDir(const char *dir); -void M_SetMusicPackDir(void); void M_BindIntVariable(const char *name, int *variable); +void M_BindIntISB8Variable(const char *name, isb_int8_t *location); +void M_BindKeyVariable(const char *name, key_type_t *variable); +void M_BindMouseBVariable(const char *name, mouseb_type_t *variable); void M_BindFloatVariable(const char *name, float *variable); -void M_BindStringVariable(const char *name, char **variable); +void M_BindStringVariable(const char *name, constcharstar *variable); boolean M_SetVariable(const char *name, const char *value); int M_GetIntVariable(const char *name); const char *M_GetStringVariable(const char *name); float M_GetFloatVariable(const char *name); void M_SetConfigFilenames(const char *main_config, const char *extra_config); -char *M_GetSaveGameDir(const char *iwadname); -char *M_GetAutoloadDir(const char *iwadname); - -extern const char *configdir; +void M_SetConfigDir(const char *dir); +#else +#define M_BindIntVariable(n, v) ((void)0) +#define M_BindIntISB8Variable(n, v) ((void)0) +#define M_BindKeyVariable(n, v) ((void)0) +#define M_BindMouseBVariable(n, v) ((void)0) +#define M_BindFloatVariable(n, v) ((void)0) +#define M_BindStringVariable(n, v) ((void)0) +#define M_GetIntVariable failure_xxx +#define M_GetFloatVariable failure_xxx +#define M_GetStringVariable failure_xxx +#define M_LoadDefaults() ((void)0) +#define M_SaveDefaults() ((void)0) +#define M_SaveDefaultsAlternate(a,b) ((void)0) +#define M_SetConfigFilenames(a,b) ((void)0) +#define M_SetConfigDir(a) ((void)0) +#endif #endif diff --git a/src/m_controls.c b/src/m_controls.c index 7225647c..176585f4 100644 --- a/src/m_controls.c +++ b/src/m_controls.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -21,53 +22,54 @@ #include "m_config.h" #include "m_misc.h" +#include "m_controls.h" // // Keyboard controls // -int key_right = KEY_RIGHTARROW; -int key_left = KEY_LEFTARROW; +key_type_t key_right = KEY_RIGHTARROW; +key_type_t key_left = KEY_LEFTARROW; -int key_up = KEY_UPARROW; -int key_down = KEY_DOWNARROW; -int key_strafeleft = ','; -int key_straferight = '.'; -int key_fire = KEY_RCTRL; -int key_use = ' '; -int key_strafe = KEY_RALT; -int key_speed = KEY_RSHIFT; +key_type_t key_up = KEY_UPARROW; +key_type_t key_down = KEY_DOWNARROW; +key_type_t key_strafeleft = ','; +key_type_t key_straferight = '.'; +key_type_t key_fire = KEY_RCTRL; +key_type_t key_use = ' '; +key_type_t key_strafe = KEY_RALT; +key_type_t key_speed = KEY_RSHIFT; // // Heretic keyboard controls // -int key_flyup = KEY_PGUP; -int key_flydown = KEY_INS; -int key_flycenter = KEY_HOME; +key_type_t key_flyup = KEY_PGUP; +key_type_t key_flydown = KEY_INS; +key_type_t key_flycenter = KEY_HOME; -int key_lookup = KEY_PGDN; -int key_lookdown = KEY_DEL; -int key_lookcenter = KEY_END; +key_type_t key_lookup = KEY_PGDN; +key_type_t key_lookdown = KEY_DEL; +key_type_t key_lookcenter = KEY_END; -int key_invleft = '['; -int key_invright = ']'; -int key_useartifact = KEY_ENTER; +key_type_t key_invleft = '['; +key_type_t key_invright = ']'; +key_type_t key_useartifact = KEY_ENTER; // // Hexen key controls // -int key_jump = '/'; +key_type_t key_jump = '/'; -int key_arti_all = KEY_BACKSPACE; -int key_arti_health = '\\'; -int key_arti_poisonbag = '0'; -int key_arti_blastradius = '9'; -int key_arti_teleport = '8'; -int key_arti_teleportother = '7'; -int key_arti_egg = '6'; -int key_arti_invulnerability = '5'; +key_type_t key_arti_all = KEY_BACKSPACE; +key_type_t key_arti_health = '\\'; +key_type_t key_arti_poisonbag = '0'; +key_type_t key_arti_blastradius = '9'; +key_type_t key_arti_teleport = '8'; +key_type_t key_arti_teleportother = '7'; +key_type_t key_arti_egg = '6'; +key_type_t key_arti_invulnerability = '5'; // // Strife key controls @@ -78,102 +80,102 @@ int key_arti_invulnerability = '5'; // Note: Strife also uses key_invleft, key_invright, key_jump, key_lookup, and // key_lookdown, but with different default values. -int key_usehealth = 'h'; -int key_invquery = 'q'; -int key_mission = 'w'; -int key_invpop = 'z'; -int key_invkey = 'k'; -int key_invhome = KEY_HOME; -int key_invend = KEY_END; -int key_invuse = KEY_ENTER; -int key_invdrop = KEY_BACKSPACE; +key_type_t key_usehealth = 'h'; +key_type_t key_invquery = 'q'; +key_type_t key_mission = 'w'; +key_type_t key_invpop = 'z'; +key_type_t key_invkey = 'k'; +key_type_t key_invhome = KEY_HOME; +key_type_t key_invend = KEY_END; +key_type_t key_invuse = KEY_ENTER; +key_type_t key_invdrop = KEY_BACKSPACE; // // Mouse controls // -int mousebfire = 0; -int mousebstrafe = 1; -int mousebforward = 2; +mouseb_type_t mousebfire = 0; +mouseb_type_t mousebstrafe = 1; +mouseb_type_t mousebforward = 2; -int mousebjump = -1; +mouseb_type_t mousebjump = -1; -int mousebstrafeleft = -1; -int mousebstraferight = -1; -int mousebbackward = -1; -int mousebuse = -1; +mouseb_type_t mousebstrafeleft = -1; +mouseb_type_t mousebstraferight = -1; +mouseb_type_t mousebbackward = -1; +mouseb_type_t mousebuse = -1; -int mousebprevweapon = -1; -int mousebnextweapon = -1; +mouseb_type_t mousebprevweapon = -1; +mouseb_type_t mousebnextweapon = -1; - -int key_message_refresh = KEY_ENTER; -int key_pause = KEY_PAUSE; -int key_demo_quit = 'q'; -int key_spy = KEY_F12; +key_type_t key_message_refresh = KEY_ENTER; +key_type_t key_pause = KEY_PAUSE; +key_type_t key_demo_quit = 'q'; +key_type_t key_spy = KEY_F12; // Multiplayer chat keys: -int key_multi_msg = 't'; -int key_multi_msgplayer[8]; +key_type_t key_multi_msg = 't'; +key_type_t key_multi_msgplayer[8]; // Weapon selection keys: -int key_weapon1 = '1'; -int key_weapon2 = '2'; -int key_weapon3 = '3'; -int key_weapon4 = '4'; -int key_weapon5 = '5'; -int key_weapon6 = '6'; -int key_weapon7 = '7'; -int key_weapon8 = '8'; -int key_prevweapon = 0; -int key_nextweapon = 0; +key_type_t key_weapon1 = '1'; +key_type_t key_weapon2 = '2'; +key_type_t key_weapon3 = '3'; +key_type_t key_weapon4 = '4'; +key_type_t key_weapon5 = '5'; +key_type_t key_weapon6 = '6'; +key_type_t key_weapon7 = '7'; +key_type_t key_weapon8 = '8'; +key_type_t key_prevweapon = 0; +key_type_t key_nextweapon = 0; // Map control keys: -int key_map_north = KEY_UPARROW; -int key_map_south = KEY_DOWNARROW; -int key_map_east = KEY_RIGHTARROW; -int key_map_west = KEY_LEFTARROW; -int key_map_zoomin = '='; -int key_map_zoomout = '-'; -int key_map_toggle = KEY_TAB; -int key_map_maxzoom = '0'; -int key_map_follow = 'f'; -int key_map_grid = 'g'; -int key_map_mark = 'm'; -int key_map_clearmark = 'c'; +key_type_t key_map_north = KEY_UPARROW; +key_type_t key_map_south = KEY_DOWNARROW; +key_type_t key_map_east = KEY_RIGHTARROW; +key_type_t key_map_west = KEY_LEFTARROW; +key_type_t key_map_zoomin = '='; +key_type_t key_map_zoomout = '-'; +key_type_t key_map_toggle = KEY_TAB; +key_type_t key_map_maxzoom = '0'; +key_type_t key_map_follow = 'f'; +key_type_t key_map_grid = 'g'; +key_type_t key_map_mark = 'm'; +key_type_t key_map_clearmark = 'c'; // menu keys: -int key_menu_activate = KEY_ESCAPE; -int key_menu_up = KEY_UPARROW; -int key_menu_down = KEY_DOWNARROW; -int key_menu_left = KEY_LEFTARROW; -int key_menu_right = KEY_RIGHTARROW; -int key_menu_back = KEY_BACKSPACE; -int key_menu_forward = KEY_ENTER; -int key_menu_confirm = 'y'; -int key_menu_abort = 'n'; +key_type_t key_menu_activate = KEY_ESCAPE; +key_type_t key_menu_up = KEY_UPARROW; +key_type_t key_menu_down = KEY_DOWNARROW; +key_type_t key_menu_left = KEY_LEFTARROW; +key_type_t key_menu_right = KEY_RIGHTARROW; +key_type_t key_menu_back = KEY_BACKSPACE; +key_type_t key_menu_forward = KEY_ENTER; +key_type_t key_menu_confirm = 'y'; +key_type_t key_menu_abort = 'n'; -int key_menu_help = KEY_F1; -int key_menu_save = KEY_F2; -int key_menu_load = KEY_F3; -int key_menu_volume = KEY_F4; -int key_menu_detail = KEY_F5; -int key_menu_qsave = KEY_F6; -int key_menu_endgame = KEY_F7; -int key_menu_messages = KEY_F8; -int key_menu_qload = KEY_F9; -int key_menu_quit = KEY_F10; -int key_menu_gamma = KEY_F11; +key_type_t key_menu_help = KEY_F1; +key_type_t key_menu_save = KEY_F2; +key_type_t key_menu_load = KEY_F3; +key_type_t key_menu_volume = KEY_F4; +key_type_t key_menu_detail = KEY_F5; +key_type_t key_menu_qsave = KEY_F6; +key_type_t key_menu_endgame = KEY_F7; +key_type_t key_menu_messages = KEY_F8; +key_type_t key_menu_qload = KEY_F9; +key_type_t key_menu_quit = KEY_F10; +key_type_t key_menu_gamma = KEY_F11; -int key_menu_incscreen = KEY_EQUALS; -int key_menu_decscreen = KEY_MINUS; -int key_menu_screenshot = 0; +key_type_t key_menu_incscreen = KEY_EQUALS; +key_type_t key_menu_decscreen = KEY_MINUS; +key_type_t key_menu_screenshot = 0; +#if !NO_USE_JOYSTICK // // Joystick controls // @@ -193,11 +195,12 @@ int joybnextweapon = -1; int joybmenu = -1; int joybautomap = -1; +#endif // Control whether if a mouse button is double clicked, it acts like // "use" has been pressed -int dclick_use = 1; +isb_int8_t dclick_use = 1; // // Bind all of the common controls used by Doom and all other games. @@ -205,21 +208,22 @@ int dclick_use = 1; void M_BindBaseControls(void) { - M_BindIntVariable("key_right", &key_right); - M_BindIntVariable("key_left", &key_left); - M_BindIntVariable("key_up", &key_up); - M_BindIntVariable("key_down", &key_down); - M_BindIntVariable("key_strafeleft", &key_strafeleft); - M_BindIntVariable("key_straferight", &key_straferight); - M_BindIntVariable("key_fire", &key_fire); - M_BindIntVariable("key_use", &key_use); - M_BindIntVariable("key_strafe", &key_strafe); - M_BindIntVariable("key_speed", &key_speed); + M_BindKeyVariable("key_right", &key_right); + M_BindKeyVariable("key_left", &key_left); + M_BindKeyVariable("key_up", &key_up); + M_BindKeyVariable("key_down", &key_down); + M_BindKeyVariable("key_strafeleft", &key_strafeleft); + M_BindKeyVariable("key_straferight", &key_straferight); + M_BindKeyVariable("key_fire", &key_fire); + M_BindKeyVariable("key_use", &key_use); + M_BindKeyVariable("key_strafe", &key_strafe); + M_BindKeyVariable("key_speed", &key_speed); - M_BindIntVariable("mouseb_fire", &mousebfire); - M_BindIntVariable("mouseb_strafe", &mousebstrafe); - M_BindIntVariable("mouseb_forward", &mousebforward); + M_BindMouseBVariable("mouseb_fire", &mousebfire); + M_BindMouseBVariable("mouseb_strafe", &mousebstrafe); + M_BindMouseBVariable("mouseb_forward", &mousebforward); +#if !NO_USE_JOYSTICK M_BindIntVariable("joyb_fire", &joybfire); M_BindIntVariable("joyb_strafe", &joybstrafe); M_BindIntVariable("joyb_use", &joybuse); @@ -232,44 +236,48 @@ void M_BindBaseControls(void) M_BindIntVariable("joyb_strafeleft", &joybstrafeleft); M_BindIntVariable("joyb_straferight", &joybstraferight); - M_BindIntVariable("mouseb_strafeleft", &mousebstrafeleft); - M_BindIntVariable("mouseb_straferight", &mousebstraferight); - M_BindIntVariable("mouseb_use", &mousebuse); - M_BindIntVariable("mouseb_backward", &mousebbackward); +#endif + + M_BindMouseBVariable("mouseb_strafeleft", &mousebstrafeleft); + M_BindMouseBVariable("mouseb_straferight", &mousebstraferight); + M_BindMouseBVariable("mouseb_use", &mousebuse); + M_BindMouseBVariable("mouseb_backward", &mousebbackward); M_BindIntVariable("dclick_use", &dclick_use); - M_BindIntVariable("key_pause", &key_pause); - M_BindIntVariable("key_message_refresh", &key_message_refresh); + M_BindKeyVariable("key_pause", &key_pause); + M_BindKeyVariable("key_message_refresh", &key_message_refresh); } void M_BindHereticControls(void) { - M_BindIntVariable("key_flyup", &key_flyup); - M_BindIntVariable("key_flydown", &key_flydown); - M_BindIntVariable("key_flycenter", &key_flycenter); + M_BindKeyVariable("key_flyup", &key_flyup); + M_BindKeyVariable("key_flydown", &key_flydown); + M_BindKeyVariable("key_flycenter", &key_flycenter); - M_BindIntVariable("key_lookup", &key_lookup); - M_BindIntVariable("key_lookdown", &key_lookdown); - M_BindIntVariable("key_lookcenter", &key_lookcenter); + M_BindKeyVariable("key_lookup", &key_lookup); + M_BindKeyVariable("key_lookdown", &key_lookdown); + M_BindKeyVariable("key_lookcenter", &key_lookcenter); - M_BindIntVariable("key_invleft", &key_invleft); - M_BindIntVariable("key_invright", &key_invright); - M_BindIntVariable("key_useartifact", &key_useartifact); + M_BindKeyVariable("key_invleft", &key_invleft); + M_BindKeyVariable("key_invright", &key_invright); + M_BindKeyVariable("key_useartifact", &key_useartifact); } void M_BindHexenControls(void) { - M_BindIntVariable("key_jump", &key_jump); - M_BindIntVariable("mouseb_jump", &mousebjump); + M_BindKeyVariable("key_jump", &key_jump); + M_BindMouseBVariable("mouseb_jump", &mousebjump); +#if !NO_USE_JOYSTICK M_BindIntVariable("joyb_jump", &joybjump); +#endif - M_BindIntVariable("key_arti_all", &key_arti_all); - M_BindIntVariable("key_arti_health", &key_arti_health); - M_BindIntVariable("key_arti_poisonbag", &key_arti_poisonbag); - M_BindIntVariable("key_arti_blastradius", &key_arti_blastradius); - M_BindIntVariable("key_arti_teleport", &key_arti_teleport); - M_BindIntVariable("key_arti_teleportother", &key_arti_teleportother); - M_BindIntVariable("key_arti_egg", &key_arti_egg); - M_BindIntVariable("key_arti_invulnerability", &key_arti_invulnerability); + M_BindKeyVariable("key_arti_all", &key_arti_all); + M_BindKeyVariable("key_arti_health", &key_arti_health); + M_BindKeyVariable("key_arti_poisonbag", &key_arti_poisonbag); + M_BindKeyVariable("key_arti_blastradius", &key_arti_blastradius); + M_BindKeyVariable("key_arti_teleport", &key_arti_teleport); + M_BindKeyVariable("key_arti_teleportother", &key_arti_teleportother); + M_BindKeyVariable("key_arti_egg", &key_arti_egg); + M_BindKeyVariable("key_arti_invulnerability", &key_arti_invulnerability); } void M_BindStrifeControls(void) @@ -284,95 +292,99 @@ void M_BindStrifeControls(void) key_invleft = KEY_INS; key_invright = KEY_DEL; - M_BindIntVariable("key_jump", &key_jump); - M_BindIntVariable("key_lookUp", &key_lookup); - M_BindIntVariable("key_lookDown", &key_lookdown); - M_BindIntVariable("key_invLeft", &key_invleft); - M_BindIntVariable("key_invRight", &key_invright); + M_BindKeyVariable("key_jump", &key_jump); + M_BindKeyVariable("key_lookUp", &key_lookup); + M_BindKeyVariable("key_lookDown", &key_lookdown); + M_BindKeyVariable("key_invLeft", &key_invleft); + M_BindKeyVariable("key_invRight", &key_invright); // Custom Strife-only Keys: - M_BindIntVariable("key_useHealth", &key_usehealth); - M_BindIntVariable("key_invquery", &key_invquery); - M_BindIntVariable("key_mission", &key_mission); - M_BindIntVariable("key_invPop", &key_invpop); - M_BindIntVariable("key_invKey", &key_invkey); - M_BindIntVariable("key_invHome", &key_invhome); - M_BindIntVariable("key_invEnd", &key_invend); - M_BindIntVariable("key_invUse", &key_invuse); - M_BindIntVariable("key_invDrop", &key_invdrop); + M_BindKeyVariable("key_useHealth", &key_usehealth); + M_BindKeyVariable("key_invquery", &key_invquery); + M_BindKeyVariable("key_mission", &key_mission); + M_BindKeyVariable("key_invPop", &key_invpop); + M_BindKeyVariable("key_invKey", &key_invkey); + M_BindKeyVariable("key_invHome", &key_invhome); + M_BindKeyVariable("key_invEnd", &key_invend); + M_BindKeyVariable("key_invUse", &key_invuse); + M_BindKeyVariable("key_invDrop", &key_invdrop); // Strife also supports jump on mouse and joystick, and in the exact same // manner as Hexen! - M_BindIntVariable("mouseb_jump", &mousebjump); + M_BindMouseBVariable("mouseb_jump", &mousebjump); +#if !NO_USE_JOYSTICK M_BindIntVariable("joyb_jump", &joybjump); +#endif } void M_BindWeaponControls(void) { - M_BindIntVariable("key_weapon1", &key_weapon1); - M_BindIntVariable("key_weapon2", &key_weapon2); - M_BindIntVariable("key_weapon3", &key_weapon3); - M_BindIntVariable("key_weapon4", &key_weapon4); - M_BindIntVariable("key_weapon5", &key_weapon5); - M_BindIntVariable("key_weapon6", &key_weapon6); - M_BindIntVariable("key_weapon7", &key_weapon7); - M_BindIntVariable("key_weapon8", &key_weapon8); + M_BindKeyVariable("key_weapon1", &key_weapon1); + M_BindKeyVariable("key_weapon2", &key_weapon2); + M_BindKeyVariable("key_weapon3", &key_weapon3); + M_BindKeyVariable("key_weapon4", &key_weapon4); + M_BindKeyVariable("key_weapon5", &key_weapon5); + M_BindKeyVariable("key_weapon6", &key_weapon6); + M_BindKeyVariable("key_weapon7", &key_weapon7); + M_BindKeyVariable("key_weapon8", &key_weapon8); - M_BindIntVariable("key_prevweapon", &key_prevweapon); - M_BindIntVariable("key_nextweapon", &key_nextweapon); + M_BindKeyVariable("key_prevweapon", &key_prevweapon); + M_BindKeyVariable("key_nextweapon", &key_nextweapon); +#if !NO_USE_JOYSTICK M_BindIntVariable("joyb_prevweapon", &joybprevweapon); M_BindIntVariable("joyb_nextweapon", &joybnextweapon); +#endif - M_BindIntVariable("mouseb_prevweapon", &mousebprevweapon); - M_BindIntVariable("mouseb_nextweapon", &mousebnextweapon); + M_BindMouseBVariable("mouseb_prevweapon", &mousebprevweapon); + M_BindMouseBVariable("mouseb_nextweapon", &mousebnextweapon); } void M_BindMapControls(void) { - M_BindIntVariable("key_map_north", &key_map_north); - M_BindIntVariable("key_map_south", &key_map_south); - M_BindIntVariable("key_map_east", &key_map_east); - M_BindIntVariable("key_map_west", &key_map_west); - M_BindIntVariable("key_map_zoomin", &key_map_zoomin); - M_BindIntVariable("key_map_zoomout", &key_map_zoomout); - M_BindIntVariable("key_map_toggle", &key_map_toggle); - M_BindIntVariable("key_map_maxzoom", &key_map_maxzoom); - M_BindIntVariable("key_map_follow", &key_map_follow); - M_BindIntVariable("key_map_grid", &key_map_grid); - M_BindIntVariable("key_map_mark", &key_map_mark); - M_BindIntVariable("key_map_clearmark", &key_map_clearmark); + M_BindKeyVariable("key_map_north", &key_map_north); + M_BindKeyVariable("key_map_south", &key_map_south); + M_BindKeyVariable("key_map_east", &key_map_east); + M_BindKeyVariable("key_map_west", &key_map_west); + M_BindKeyVariable("key_map_zoomin", &key_map_zoomin); + M_BindKeyVariable("key_map_zoomout", &key_map_zoomout); + M_BindKeyVariable("key_map_toggle", &key_map_toggle); + M_BindKeyVariable("key_map_maxzoom", &key_map_maxzoom); + M_BindKeyVariable("key_map_follow", &key_map_follow); + M_BindKeyVariable("key_map_grid", &key_map_grid); + M_BindKeyVariable("key_map_mark", &key_map_mark); + M_BindKeyVariable("key_map_clearmark", &key_map_clearmark); } void M_BindMenuControls(void) { - M_BindIntVariable("key_menu_activate", &key_menu_activate); - M_BindIntVariable("key_menu_up", &key_menu_up); - M_BindIntVariable("key_menu_down", &key_menu_down); - M_BindIntVariable("key_menu_left", &key_menu_left); - M_BindIntVariable("key_menu_right", &key_menu_right); - M_BindIntVariable("key_menu_back", &key_menu_back); - M_BindIntVariable("key_menu_forward", &key_menu_forward); - M_BindIntVariable("key_menu_confirm", &key_menu_confirm); - M_BindIntVariable("key_menu_abort", &key_menu_abort); + M_BindKeyVariable("key_menu_activate", &key_menu_activate); + M_BindKeyVariable("key_menu_up", &key_menu_up); + M_BindKeyVariable("key_menu_down", &key_menu_down); + M_BindKeyVariable("key_menu_left", &key_menu_left); + M_BindKeyVariable("key_menu_right", &key_menu_right); + M_BindKeyVariable("key_menu_back", &key_menu_back); + M_BindKeyVariable("key_menu_forward", &key_menu_forward); + M_BindKeyVariable("key_menu_confirm", &key_menu_confirm); + M_BindKeyVariable("key_menu_abort", &key_menu_abort); - M_BindIntVariable("key_menu_help", &key_menu_help); - M_BindIntVariable("key_menu_save", &key_menu_save); - M_BindIntVariable("key_menu_load", &key_menu_load); - M_BindIntVariable("key_menu_volume", &key_menu_volume); - M_BindIntVariable("key_menu_detail", &key_menu_detail); - M_BindIntVariable("key_menu_qsave", &key_menu_qsave); - M_BindIntVariable("key_menu_endgame", &key_menu_endgame); - M_BindIntVariable("key_menu_messages", &key_menu_messages); - M_BindIntVariable("key_menu_qload", &key_menu_qload); - M_BindIntVariable("key_menu_quit", &key_menu_quit); - M_BindIntVariable("key_menu_gamma", &key_menu_gamma); + M_BindKeyVariable("key_menu_help", &key_menu_help); + M_BindKeyVariable("key_menu_save", &key_menu_save); + M_BindKeyVariable("key_menu_load", &key_menu_load); + M_BindKeyVariable("key_menu_volume", &key_menu_volume); + M_BindKeyVariable("key_menu_detail", &key_menu_detail); + M_BindKeyVariable("key_menu_qsave", &key_menu_qsave); + M_BindKeyVariable("key_menu_endgame", &key_menu_endgame); + M_BindKeyVariable("key_menu_messages", &key_menu_messages); + M_BindKeyVariable("key_menu_qload", &key_menu_qload); + M_BindKeyVariable("key_menu_quit", &key_menu_quit); + M_BindKeyVariable("key_menu_gamma", &key_menu_gamma); - M_BindIntVariable("key_menu_incscreen", &key_menu_incscreen); - M_BindIntVariable("key_menu_decscreen", &key_menu_decscreen); - M_BindIntVariable("key_menu_screenshot",&key_menu_screenshot); - M_BindIntVariable("key_demo_quit", &key_demo_quit); - M_BindIntVariable("key_spy", &key_spy); + M_BindKeyVariable("key_menu_incscreen", &key_menu_incscreen); + M_BindKeyVariable("key_menu_decscreen", &key_menu_decscreen); + M_BindKeyVariable("key_menu_screenshot",&key_menu_screenshot); + M_BindKeyVariable("key_demo_quit", &key_demo_quit); + M_BindKeyVariable("key_spy", &key_spy); } void M_BindChatControls(unsigned int num_players) @@ -380,12 +392,12 @@ void M_BindChatControls(unsigned int num_players) char name[32]; // haleyjd: 20 not large enough - Thank you, come again! unsigned int i; // haleyjd: signedness conflict - M_BindIntVariable("key_multi_msg", &key_multi_msg); + M_BindKeyVariable("key_multi_msg", &key_multi_msg); for (i=0; i> FRACBITS; +#else + return FixedMulInline(a,b); +#endif } diff --git a/src/m_fixed.h b/src/m_fixed.h index 733b2901..0da8cc16 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -26,14 +27,45 @@ // // Fixed point, 32bit as 16.16. // +#ifndef FRACBITS #define FRACBITS 16 +#endif #define FRACUNIT (1< +static inline fixed_t FixedMulInline(fixed_t a, fixed_t b) { +#if PICO_ON_DEVICE + uint32_t tmp1, tmp2, tmp3; + __asm__ volatile ( + ".syntax unified\n" + "asrs %[r_tmp1], %[r_b], #16 \n" // r_tmp1 = BH + "uxth %[r_tmp2], %[r_a] \n" // r_tmp2 = AL + "muls %[r_tmp2], %[r_tmp1] \n" // r_tmp2 = BH * AL + "asrs %[r_tmp3], %[r_a], #16 \n" // r_tmp3 = AH + "muls %[r_tmp1], %[r_tmp3] \n" // r_tmp1 = BH * AH + "uxth %[r_b], %[r_b] \n" // r_b = BL + "uxth %[r_a], %[r_a] \n" // r_a = AL + "muls %[r_a], %[r_b] \n" // r_a = AL * BL + "muls %[r_b], %[r_tmp3] \n" // r_b = BL * AH + "add %[r_b], %[r_tmp2] \n" // r_b = BL * AH + BH * AL + "lsls %[r_tmp1], #16 \n" // r_tmp1 = (BH * AH) L << 16 + "lsrs %[r_a], #16 \n" // r_a = (AL & BL) H + "add %[r_a], %[r_b] \n" + "add %[r_a], %[r_tmp1] \n" + : [r_a] "+l" (a), [r_b] "+l" (b), [r_tmp1] "=&l" (tmp1), [r_tmp2] "=&l" (tmp2), [r_tmp3] "=&l" (tmp3) + : + ); + return a; +#else + return (((uint64_t)a) * b) >> FRACBITS; +#endif +} +#endif fixed_t FixedMul (fixed_t a, fixed_t b); fixed_t FixedDiv (fixed_t a, fixed_t b); - #endif diff --git a/src/m_misc.c b/src/m_misc.c index 1f8117be..2037f4d9 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -48,6 +49,7 @@ #include "w_wad.h" #include "z_zone.h" +#if !NO_FILE_ACCESS // // Create a directory // @@ -56,6 +58,7 @@ void M_MakeDirectory(const char *path) { #ifdef _WIN32 mkdir(path); +#elif PICO_ON_DEVICE #else mkdir(path, 0755); #endif @@ -217,7 +220,7 @@ int M_ReadFile(const char *name, byte **buffer) length = M_FileLength(handle); - buf = Z_Malloc (length + 1, PU_STATIC, NULL); + buf = Z_Malloc (length + 1, PU_STATIC, 0); count = fread(buf, 1, length, handle); fclose (handle); @@ -257,14 +260,6 @@ char *M_TempFile(const char *s) return M_StringJoin(tempdir, DIR_SEPARATOR_S, s, NULL); } -boolean M_StrToInt(const char *str, int *result) -{ - return sscanf(str, " 0x%x", result) == 1 - || sscanf(str, " 0X%x", result) == 1 - || sscanf(str, " 0%o", result) == 1 - || sscanf(str, " %d", result) == 1; -} - // Returns the directory portion of the given path, without the trailing // slash separator character. If no directory is described in the path, // the string "." is returned. In either case, the result is newly allocated @@ -315,7 +310,7 @@ void M_ExtractFileBase(const char *path, char *dest) // back up until a \ or the start while (src != path && *(src - 1) != DIR_SEPARATOR) { - src--; + src--; } filename = src; @@ -337,9 +332,19 @@ void M_ExtractFileBase(const char *path, char *dest) break; } - dest[length++] = toupper((int)*src++); + dest[length++] = toupper((int)*src++); } } +#endif + +boolean M_StrToInt(const char *str, int *result) +{ + return sscanf(str, " 0x%x", result) == 1 + || sscanf(str, " 0X%x", result) == 1 + || sscanf(str, " 0%o", result) == 1 + || sscanf(str, " %d", result) == 1; +} + //--------------------------------------------------------------------------- // diff --git a/src/m_misc.h b/src/m_misc.h index 90855d18..2597cc36 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -25,6 +26,7 @@ #include "doomtype.h" +#if !NO_FILE_ACCESS boolean M_WriteFile(const char *name, const void *source, int length); int M_ReadFile(const char *name, byte **buffer); void M_MakeDirectory(const char *dir); @@ -32,10 +34,11 @@ char *M_TempFile(const char *s); boolean M_FileExists(const char *file); char *M_FileCaseExists(const char *file); long M_FileLength(FILE *handle); -boolean M_StrToInt(const char *str, int *result); char *M_DirName(const char *path); const char *M_BaseName(const char *path); void M_ExtractFileBase(const char *path, char *dest); +#endif +boolean M_StrToInt(const char *str, int *result); void M_ForceUppercase(char *text); void M_ForceLowercase(char *text); const char *M_StrCaseStr(const char *haystack, const char *needle); diff --git a/src/memio.c b/src/memio.c index 3cc769a3..30249ddb 100644 --- a/src/memio.c +++ b/src/memio.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -39,7 +40,7 @@ struct _MEMFILE { // Open a memory area for reading -MEMFILE *mem_fopen_read(void *buf, size_t buflen) +MEMFILE *mem_fopen_read(const void *buf, size_t buflen) { MEMFILE *file; diff --git a/src/memio.h b/src/memio.h index 03706a31..a11fd0c4 100644 --- a/src/memio.h +++ b/src/memio.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -25,7 +26,7 @@ typedef enum MEM_SEEK_END, } mem_rel_t; -MEMFILE *mem_fopen_read(void *buf, size_t buflen); +MEMFILE *mem_fopen_read(const void *buf, size_t buflen); size_t mem_fread(void *buf, size_t size, size_t nmemb, MEMFILE *stream); MEMFILE *mem_fopen_write(void); size_t mem_fwrite(const void *ptr, size_t size, size_t nmemb, MEMFILE *stream); diff --git a/src/midifile.c b/src/midifile.c index c042b225..d1097c97 100644 --- a/src/midifile.c +++ b/src/midifile.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -25,6 +26,10 @@ #include "i_system.h" #include "midifile.h" +#if USE_MUSX +#include "musx_decoder.h" +#endif + #define HEADER_CHUNK_ID "MThd" #define TRACK_CHUNK_ID "MTrk" #define MAX_BUFFER_SIZE 0x10000 @@ -53,8 +58,25 @@ typedef PACKED_STRUCT ( #pragma pack(pop) #endif +#if USE_DIRECT_MIDI_LUMP || USE_MIDI_DUMP_FILE +typedef struct { + midi_header_t header; + uint16_t pad; +} raw_midi_t; +static_assert(sizeof(raw_midi_t) == 16, ""); + +typedef struct { + // Time between the previous event and this event. + uint32_t delta_time; + uint8_t event; + uint8_t param[3]; +} raw_midi_event_t; +static_assert(sizeof(raw_midi_event_t) == 8, ""); +#endif + typedef struct { +#if !USE_DIRECT_MIDI_LUMP // Length in bytes: unsigned int data_len; @@ -62,28 +84,53 @@ typedef struct // Events in this track: midi_event_t *events; +#else +#if !USE_MUSX + raw_midi_event_t *raw_events; +#else + const byte *buffer; + uint32_t buffer_size; +#endif +#endif int num_events; } midi_track_t; -struct midi_track_iter_s -{ - midi_track_t *track; - unsigned int position; -}; - struct midi_file_s { +#if !USE_MUSX midi_header_t header; // All tracks in this file: midi_track_t *tracks; unsigned int num_tracks; - +#endif +#if !USE_DIRECT_MIDI_LUMP // Data buffer used to store data read for SysEx or meta events: byte *buffer; unsigned int buffer_size; +#endif +#if USE_MUSX + midi_track_t tracks[1]; +#endif }; +struct midi_track_iter_s +{ + midi_track_t *track; + unsigned int position; +#if USE_MUSX + midi_event_t events[2]; + int peek_index; + th_bit_input bit_input; // note we mark end of stream reached by NULLing this out + musx_decoder decoder; + uint16_t decoder_space[MUSX_MAX_DECODER_SPACE]; +#endif +}; + + +#if !USE_DIRECT_MIDI_LUMP + + // Check the header of a chunk: static boolean CheckChunkHeader(chunk_header_t *chunk, @@ -95,7 +142,7 @@ static boolean CheckChunkHeader(chunk_header_t *chunk, if (!result) { - fprintf(stderr, "CheckChunkHeader: Expected '%s' chunk header, " + stderr_print( "CheckChunkHeader: Expected '%s' chunk header, " "got '%c%c%c%c'\n", expected_id, chunk->chunk_id[0], chunk->chunk_id[1], @@ -115,7 +162,7 @@ static boolean ReadByte(byte *result, FILE *stream) if (c == EOF) { - fprintf(stderr, "ReadByte: Unexpected end of file\n"); + stderr_print( "ReadByte: Unexpected end of file\n"); return false; } else @@ -139,7 +186,7 @@ static boolean ReadVariableLength(unsigned int *result, FILE *stream) { if (!ReadByte(&b, stream)) { - fprintf(stderr, "ReadVariableLength: Error while reading " + stderr_print( "ReadVariableLength: Error while reading " "variable-length value\n"); return false; } @@ -157,7 +204,7 @@ static boolean ReadVariableLength(unsigned int *result, FILE *stream) } } - fprintf(stderr, "ReadVariableLength: Variable-length value too " + stderr_print( "ReadVariableLength: Variable-length value too " "long: maximum of four bytes\n"); return false; } @@ -176,7 +223,7 @@ static void *ReadByteSequence(unsigned int num_bytes, FILE *stream) if (result == NULL) { - fprintf(stderr, "ReadByteSequence: Failed to allocate buffer\n"); + stderr_print( "ReadByteSequence: Failed to allocate buffer\n"); return NULL; } @@ -186,7 +233,7 @@ static void *ReadByteSequence(unsigned int num_bytes, FILE *stream) { if (!ReadByte(&result[i], stream)) { - fprintf(stderr, "ReadByteSequence: Error while reading byte %u\n", + stderr_print( "ReadByteSequence: Error while reading byte %u\n", i); free(result); return NULL; @@ -215,7 +262,7 @@ static boolean ReadChannelEvent(midi_event_t *event, if (!ReadByte(&b, stream)) { - fprintf(stderr, "ReadChannelEvent: Error while reading channel " + stderr_print( "ReadChannelEvent: Error while reading channel " "event parameters\n"); return false; } @@ -228,12 +275,14 @@ static boolean ReadChannelEvent(midi_event_t *event, { if (!ReadByte(&b, stream)) { - fprintf(stderr, "ReadChannelEvent: Error while reading channel " + stderr_print( "ReadChannelEvent: Error while reading channel " "event parameters\n"); return false; } event->data.channel.param2 = b; + } else { + event->data.channel.param2 = 0; } return true; @@ -248,7 +297,7 @@ static boolean ReadSysExEvent(midi_event_t *event, int event_type, if (!ReadVariableLength(&event->data.sysex.length, stream)) { - fprintf(stderr, "ReadSysExEvent: Failed to read length of " + stderr_print( "ReadSysExEvent: Failed to read length of " "SysEx block\n"); return false; } @@ -259,7 +308,7 @@ static boolean ReadSysExEvent(midi_event_t *event, int event_type, if (event->data.sysex.data == NULL) { - fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n"); + stderr_print( "ReadSysExEvent: Failed while reading SysEx event\n"); return false; } @@ -278,7 +327,7 @@ static boolean ReadMetaEvent(midi_event_t *event, FILE *stream) if (!ReadByte(&b, stream)) { - fprintf(stderr, "ReadMetaEvent: Failed to read meta event type\n"); + stderr_print( "ReadMetaEvent: Failed to read meta event type\n"); return false; } @@ -288,7 +337,7 @@ static boolean ReadMetaEvent(midi_event_t *event, FILE *stream) if (!ReadVariableLength(&event->data.meta.length, stream)) { - fprintf(stderr, "ReadSysExEvent: Failed to read length of " + stderr_print( "ReadSysExEvent: Failed to read length of " "SysEx block\n"); return false; } @@ -299,7 +348,7 @@ static boolean ReadMetaEvent(midi_event_t *event, FILE *stream) if (event->data.meta.data == NULL) { - fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n"); + stderr_print( "ReadSysExEvent: Failed while reading SysEx event\n"); return false; } @@ -313,13 +362,13 @@ static boolean ReadEvent(midi_event_t *event, unsigned int *last_event_type, if (!ReadVariableLength(&event->delta_time, stream)) { - fprintf(stderr, "ReadEvent: Failed to read event timestamp\n"); + stderr_print( "ReadEvent: Failed to read event timestamp\n"); return false; } if (!ReadByte(&event_type, stream)) { - fprintf(stderr, "ReadEvent: Failed to read event type\n"); + stderr_print( "ReadEvent: Failed to read event type\n"); return false; } @@ -334,7 +383,7 @@ static boolean ReadEvent(midi_event_t *event, unsigned int *last_event_type, if (fseek(stream, -1, SEEK_CUR) < 0) { - fprintf(stderr, "ReadEvent: Unable to seek in stream\n"); + stderr_print( "ReadEvent: Unable to seek in stream\n"); return false; } } @@ -381,7 +430,7 @@ static boolean ReadEvent(midi_event_t *event, unsigned int *last_event_type, break; } - fprintf(stderr, "ReadEvent: Unknown MIDI event type: 0x%x\n", event_type); + stderr_print( "ReadEvent: Unknown MIDI event type: 0x%x\n", event_type); return false; } @@ -542,7 +591,7 @@ static boolean ReadFileHeader(midi_file_t *file, FILE *stream) if (!CheckChunkHeader(&file->header.chunk_header, HEADER_CHUNK_ID) || SDL_SwapBE32(file->header.chunk_header.chunk_size) != 6) { - fprintf(stderr, "ReadFileHeader: Invalid MIDI chunk header! " + stderr_print( "ReadFileHeader: Invalid MIDI chunk header! " "chunk_size=%i\n", SDL_SwapBE32(file->header.chunk_header.chunk_size)); return false; @@ -554,31 +603,34 @@ static boolean ReadFileHeader(midi_file_t *file, FILE *stream) if ((format_type != 0 && format_type != 1) || file->num_tracks < 1) { - fprintf(stderr, "ReadFileHeader: Only type 0/1 " + stderr_print( "ReadFileHeader: Only type 0/1 " "MIDI files supported!\n"); return false; } return true; } +#endif void MIDI_FreeFile(midi_file_t *file) { - int i; +#if !USE_DIRECT_MIDI_LUMP if (file->tracks != NULL) { + int i; for (i=0; inum_tracks; ++i) { FreeTrack(&file->tracks[i]); } - free(file->tracks); } +#endif free(file); } +#if !USE_DIRECT_MIDI_LUMP midi_file_t *MIDI_LoadFile(char *filename) { midi_file_t *file; @@ -602,7 +654,7 @@ midi_file_t *MIDI_LoadFile(char *filename) if (stream == NULL) { - fprintf(stderr, "MIDI_LoadFile: Failed to open '%s'\n", filename); + stderr_print( "MIDI_LoadFile: Failed to open '%s'\n", filename); MIDI_FreeFile(file); return NULL; } @@ -629,26 +681,31 @@ midi_file_t *MIDI_LoadFile(char *filename) return file; } +#endif // Get the number of tracks in a MIDI file. unsigned int MIDI_NumTracks(midi_file_t *file) { - return file->num_tracks; + return midifile_numtracks(file); } // Start iterating over the events in a track. +#ifdef TEST +static void PrintTrack(midi_track_t *track); +#endif midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track) { midi_track_iter_t *iter; - assert(track < file->num_tracks); + assert(track < midifile_numtracks(file)); +// printf("Begin iterating track %d\n", track); +// PrintTrack(&file->tracks[track]); iter = malloc(sizeof(*iter)); iter->track = &file->tracks[track]; - iter->position = 0; - + MIDI_RestartIterator(iter); return iter; } @@ -661,40 +718,79 @@ void MIDI_FreeIterator(midi_track_iter_t *iter) unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter) { +#if USE_MUSX + return iter->events[iter->peek_index^1].delta_time; +#else if (iter->position < iter->track->num_events) { +#if USE_DIRECT_MIDI_LUMP + raw_midi_event_t *next_event; + next_event = &iter->track->raw_events[iter->position]; +#else midi_event_t *next_event; - next_event = &iter->track->events[iter->position]; - + return next_event->delta_time; +#endif return next_event->delta_time; } else { return 0; } +#endif } // Get a pointer to the next MIDI event. +#if USE_MUSX +void peek_event(midi_track_iter_t *iter); +#endif + +#if USE_DIRECT_MIDI_LUMP && !USE_MUSX +static void MIDI_DecodeEvent(raw_midi_event_t *raw_event, midi_event_t *event) { + event->delta_time = raw_event->delta_time; + if (raw_event->event == MIDI_EVENT_META) { + event->event_type = raw_event->event; + event->data.meta.type = MIDI_META_SET_TEMPO; + event->data.meta.data = raw_event->param; + event->data.meta.length = 3; + } else { + event->event_type = raw_event->event & 0xf0; + event->data.channel.channel = raw_event->event & 0x0f; + event->data.channel.param1 = raw_event->param[0]; + event->data.channel.param2 = raw_event->param[1]; + } +} +#endif int MIDI_GetNextEvent(midi_track_iter_t *iter, midi_event_t **event) { +#if USE_MUSX + *event = &iter->events[iter->peek_index]; + peek_event(iter); + return 1; +#else if (iter->position < iter->track->num_events) { - *event = &iter->track->events[iter->position]; +#if USE_DIRECT_MIDI_LUMP + *event = &iter->track->current_event; + MIDI_DecodeEvent(&iter->track->raw_events[iter->position], *event); + ++iter->position; +#else + *event = &iter->track->events[iter->position]; +#endif ++iter->position; - return 1; } else { return 0; } +#endif } unsigned int MIDI_GetFileTimeDivision(midi_file_t *file) { - short result = SDL_SwapBE16(file->header.time_division); + short result = midifile_timedivision(file); // Negative time division indicates SMPTE time and must be handled // differently. @@ -712,8 +808,17 @@ unsigned int MIDI_GetFileTimeDivision(midi_file_t *file) void MIDI_RestartIterator(midi_track_iter_t *iter) { iter->position = 0; +#if USE_MUSX + iter->peek_index = 0; + iter->events[iter->peek_index].delta_time = 0; // time before first event + uint8_t tmp_buf[512]; // todo get tem[ workspace if stack not big enough + th_sized_bit_input_init(&iter->bit_input, iter->track->buffer, iter->track->buffer_size); + musx_decoder_init(&iter->decoder, &iter->bit_input, iter->decoder_space, count_of(iter->decoder_space), tmp_buf, sizeof(tmp_buf)); + peek_event(iter); +#endif } +//#define TEST #ifdef TEST static char *MIDI_EventTypeToString(midi_event_type_t event_type) @@ -753,7 +858,13 @@ void PrintTrack(midi_track_t *track) for (i=0; inum_events; ++i) { +#if USE_DIRECT_MIDI_LUMP + midi_event_t the_event; + MIDI_DecodeEvent(&track->raw_events[i], &the_event); + event = &the_event; +#else event = &track->events[i]; +#endif if (event->delta_time > 0) { @@ -790,7 +901,9 @@ void PrintTrack(midi_track_t *track) } } } +#endif +#ifdef TEST int main(int argc, char *argv[]) { midi_file_t *file; @@ -806,11 +919,11 @@ int main(int argc, char *argv[]) if (file == NULL) { - fprintf(stderr, "Failed to open %s\n", argv[1]); + stderr_print( "Failed to open %s\n", argv[1]); exit(1); } - for (i=0; inum_tracks; ++i) + for (i=0; iheader.chunk_header.chunk_id, "MidX", 4) != 0) { + stderr_print( "MIDI_LoadRAW Data wasn't RAW MID.\n"); + return NULL; + } + midi_file_t *file = malloc(sizeof(midi_file_t)); + if (file == NULL) + { + return NULL; + } + file->header = raw->header; + file->num_tracks = SDL_SwapBE16(file->header.num_tracks); + file->tracks = calloc(file->num_tracks, sizeof(midi_track_t)); + if (!file->tracks) { + free(file); + return NULL; + } + const void *event_data = data + sizeof(raw_midi_t); + for(int i=0;inum_tracks;i++) { + file->tracks[i].num_events = *(int32_t*)event_data; + event_data += 4; + file->tracks[i].raw_events = (raw_midi_event_t *)event_data; + event_data += file->tracks[i].num_events * 8; + } + return file; +} +#else + +midi_file_t *MUSX_LoadRaw(const void *data, int len) { +#if !MUSX_COMPRESSED + if (memcmp(data, "MUS2", 4) != 0) { + stderr_print( "MUSX_LoadRAW Data wasn't uncompressed MUS2.\n"); + return NULL; + } +#else + if (memcmp(data, "MUSX", 4) != 0) { + stderr_print( "MUSX_LoadRAW Data wasn't compressed MUSX.\n"); + return NULL; + } +#endif + midi_file_t *file = malloc(sizeof(midi_file_t)); + if (file == NULL) + { + return NULL; + } + file->tracks[0].buffer_size = *(uint32_t *)(data+4); + assert(file->tracks[0].buffer_size == len - 8); + file->tracks[0].buffer = data + 8; + return file; +} + +static const byte controller_map[] = { + 0x00, 0x20, 0x01, 0x07, 0x0A, 0x0B, 0x5B, 0x5D, + 0x40, 0x43, 0x78, 0x7B, 0x7E, 0x7F, 0x79 +}; + +void peek_event(midi_track_iter_t *iter) { + iter->peek_index ^= 1; + midi_event_t *me = &iter->events[iter->peek_index]; + + musx_decoder *d = &iter->decoder; + th_bit_input *bi = &iter->bit_input; + + if (bi->cur) { + if (!d->group_remaining) { + d->group_remaining = th_decode(d->decoders + d->group_size_idx, bi); + } + uint8_t ec = th_decode(d->decoders + d->channel_event_idx, bi); + uint channel = me->data.channel.channel = ec >> 4; + me->data.channel.param1 = me->data.channel.param2 = 0; // not sure if necessary + switch (ec & 0xf) { + case change_controller: { + uint8_t controller = th_read_bits(bi, 4); + uint8_t value = th_read_bits(bi, 8); + if (controller == 0) { + me->event_type = MIDI_EVENT_PROGRAM_CHANGE; + me->data.channel.param1 = value; + } else { + assert(controller >= 1 && controller <= 9); + me->event_type = MIDI_EVENT_CONTROLLER; + me->data.channel.param1 = controller_map[controller]; + me->data.channel.param2 = value; + } + break; + } + case delta_volume: { + int delta = from_zig(th_decode(d->decoders + d->delta_volume_idx, bi)); + d->channel_last_volume[channel] += delta; + me->event_type = MIDI_EVENT_CONTROLLER; + me->data.channel.param1 = controller_map[3]; + me->data.channel.param2 = d->channel_last_volume[channel]; +// printf("delta volume %d, so %d\n", delta, d->channel_last_volume[channel]); + break; + } + case delta_pitch: { + int delta = from_zig(th_decode(d->decoders + d->delta_pitch_idx, bi)); + d->channel_last_wheel[channel] += delta; + uint wheel = d->channel_last_wheel[channel] * 64; + me->event_type = MIDI_EVENT_PITCH_BEND; + me->data.channel.param1 = wheel & 0x7fu; + me->data.channel.param2 = (wheel >> 7u) & 0x7fu; + // printf("delta pitch %d, so %d\n", delta, d->channel_last_wheel[channel]); + break; + } + case delta_vibrato: { + uint delta = from_zig(th_decode(d->decoders + d->delta_vibrato_idx, bi)); + d->channel_last_vibrato[channel] += delta; + me->event_type = MIDI_EVENT_CONTROLLER; + me->data.channel.param1 = controller_map[2]; + me->data.channel.param2 = d->channel_last_vibrato[channel]; +// printf("delta vibrato %d, so %d\n", delta, d->channel_last_vibrato[channel]); + break; + } + case press_key: { + int note = th_decode(d->decoders + (channel == 9 ? d->press_note9_idx : d->press_note_idx), bi); + int vol = th_decode(d->decoders + d->press_volume_idx, bi); + // printf("press key %d vol %d", note, vol); + if (vol == vol_last_global) { + vol = d->channel_last_volume[channel] = d->channel_last_press_volume[channel] = d->last_volume; + } else if (vol == vol_last_channel) { + vol = d->channel_last_press_volume[channel]; + } else { + d->last_volume = d->channel_last_volume[channel] = d->channel_last_press_volume[channel] = vol; + } + // printf(" so %d\n", vol); + musx_record_note_on(d, channel, note); + me->event_type = MIDI_EVENT_NOTE_ON; + me->data.channel.param1 = note; + me->data.channel.param2 = vol; + break; + } + case release_key: { + assert(d->channel_note_count[channel]); + uint8_t dist = 0; + if (d->channel_note_count[channel] > 1) { + dist = th_decode(d->decoders + d->release_dist_idx[d->channel_note_count[channel]], bi); + } + uint8_t note = musx_record_note_off(d, channel, dist); + me->event_type = MIDI_EVENT_NOTE_OFF; + me->data.channel.param1 = note; + // printf("release key dist %d note %d\n", dist, note); + break; + } + case system_event: { + uint controller = th_read_bits(bi, 3) + 10; + me->event_type = MIDI_EVENT_CONTROLLER; + me->data.channel.param1 = controller_map[controller]; + // printf("system event %d\n", controller); + break; + } + case score_end: + me->event_type = MIDI_EVENT_META; + me->data.meta.type = MIDI_META_END_OF_TRACK; + bi->cur = NULL; + break; + default: + assert(false); + } + int gap = 0; + if (bi->cur) { + if (!--d->group_remaining) { + int lgap; + do { + lgap = th_decode(d->decoders + d->gap_idx, bi); + gap += lgap; + } while (lgap == MUSX_GAP_MAX); + } + } + me->delta_time = gap; +// printf("%d MIDI %02x %d %02x %02x\n", d->pos++, me->event_type, me->data.channel.channel, me->data.channel.param1, me->data.channel.param2); + } else { + // already at score end; should we fill in? + } +} +#endif +#endif + +#if USE_MIDI_DUMP_FILE +static int filter_event(const midi_event_t *event) { + midi_event_type_t type = event->event_type; + if (type == MIDI_EVENT_SYSEX || type == MIDI_EVENT_SYSEX_SPLIT) { + return false; + } + if (type == MIDI_EVENT_META) { + if (event->data.meta.type != MIDI_META_SET_TEMPO) { + printf("Skipping meta 0x%02x\n", event->data.meta.type); + return false; + } + if (event->data.meta.length != 3) { + printf("EXPECTED LENGTH 3\n"); + return false; + } + } + return true; +} + +void MIDI_DumpFile(midi_file_t *file, const char *filename) { + FILE *out = fopen(filename, "wb"); + if (!out) return; + raw_midi_t rm; + rm.header = file->header; + memcpy(rm.header.chunk_header.chunk_id, "MidX", 4); + fwrite(&rm, sizeof(rm), 1, out); + for(int i=0;inum_tracks;i++) { + const midi_track_t *track = &file->tracks[i]; + int32_t event_count = 0; + for(int j=0;jnum_events;j++) { + const midi_event_t *event = &track->events[j]; + if (filter_event(event)) event_count++; + } + fwrite(&event_count, 4, 1, out); + int ecount = 0; + for(int j=0;jnum_events;j++) { + const midi_event_t *event = &track->events[j]; + raw_midi_event_t raw_event; + raw_event.delta_time = event->delta_time; + raw_event.event = event->event_type; + if (filter_event(event)) { + if (event->event_type == MIDI_EVENT_META) { + assert(event->data.meta.length == 3); + assert(event->data.meta.type == MIDI_META_SET_TEMPO); + memcpy(raw_event.param, event->data.meta.data, 3); + } else { + raw_event.event |= event->data.channel.channel; + raw_event.param[0] = event->data.channel.param1; + raw_event.param[1] = event->data.channel.param2; + raw_event.param[2] = 0; + } + ecount++; + static_assert(sizeof(raw_event) == 8, ""); + fwrite(&raw_event, sizeof(raw_event), 1, out); + } + } + assert(ecount == event_count); + } + fclose(out); +} +#endif \ No newline at end of file diff --git a/src/midifile.h b/src/midifile.h index 490c2a0a..c1b55acb 100644 --- a/src/midifile.h +++ b/src/midifile.h @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -21,6 +22,14 @@ typedef struct midi_file_s midi_file_t; typedef struct midi_track_iter_s midi_track_iter_t; +#if !USE_MUSX +#define midifile_numtracks(f) ((f)->num_tracks) +#define midifile_timedivision(f) SDL_SwapBE16((f)->header.time_division) +#else +#define midifile_numtracks(m) 1 +#define midifile_timedivision(f) 0x46 // seems to be 0x46 always in mus2mid +#endif + #define MIDI_CHANNELS_PER_TRACK 16 typedef enum @@ -116,7 +125,7 @@ typedef struct typedef struct { // Time between the previous event and this event. - unsigned int delta_time; + uint32_t delta_time; // Type of event: midi_event_type_t event_type; @@ -133,6 +142,17 @@ typedef struct midi_file_t *MIDI_LoadFile(char *filename); +#if USE_DIRECT_MIDI_LUMP +#if !USE_MUSX +midi_file_t *MIDI_LoadRaw(const void *data, int len); +#else +midi_file_t *MUSX_LoadRaw(const void *data, int len); +#endif +#endif +#if USE_MIDI_DUMP_FILE +void MIDI_DumpFile(midi_file_t *file, const char *filename); +#endif + // Free a MIDI file. void MIDI_FreeFile(midi_file_t *file); diff --git a/src/mus2mid.c b/src/mus2mid.c index 3029fa5a..7ee59c85 100644 --- a/src/mus2mid.c +++ b/src/mus2mid.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard // Copyright(C) 2006 Ben Ryves 2006 +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -24,6 +25,7 @@ #include "memio.h" #include "mus2mid.h" +#if !USE_MUSX #define NUM_CHANNELS 16 #define MIDI_PERCUSSION_CHAN 9 @@ -733,3 +735,4 @@ int main(int argc, char *argv[]) #endif +#endif \ No newline at end of file diff --git a/src/musx_decoder.c b/src/musx_decoder.c new file mode 100644 index 00000000..07f18114 --- /dev/null +++ b/src/musx_decoder.c @@ -0,0 +1,78 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "musx_decoder.h" +#include +#include + +uint16_t *read_channel_event_decoder(th_bit_input *bi, uint16_t *buffer, uint buffer_size, uint8_t *tmp_buf, uint tmp_buf_size) { + uint min_cl = th_read_bits(bi, 4); + uint max_cl = th_read_bits(bi, 4); +// printf(" Code length %d->%d\n", min_cl, max_cl); + uint bit_count = 32 - __builtin_clz(max_cl - min_cl); + uint count = 0; + for (uint ch = 0; ch < MUSX_CHANNEL_COUNT; ch++) { + if (th_bit(bi)) { + for (uint bit = 0; bit < 8; bit++) { + if (th_bit(bi)) { + uint length; + if (min_cl == max_cl) { + length = min_cl; + } else { + length = min_cl + th_read_bits(bi, bit_count); + } + assert(count < tmp_buf_size); + tmp_buf[count * 2] = (ch << 4) | bit; + tmp_buf[count * 2 + 1] = length; + count++; + } + } + } + } + assert(buffer_size >= th_decoder_size(count, max_cl)); + return th_create_decoder(buffer, tmp_buf, count, max_cl); +} + +uint musx_decoder_init(musx_decoder *d, th_bit_input *bi, uint16_t *decoder_buffer, uint decoder_buffer_size, uint8_t *tmp_buf, + uint tmp_buf_size) { + uint16_t idx = 0; + memset(d, 0, sizeof(*d)); + d->decoders = decoder_buffer; +#define READ_DECODER(name, func) d->name = idx, idx = func(bi, d->decoders + idx, decoder_buffer_size - idx, tmp_buf, tmp_buf_size) - d->decoders + READ_DECODER(channel_event_idx, read_channel_event_decoder); + READ_DECODER(delta_volume_idx, th_read_simple_decoder); + READ_DECODER(delta_pitch_idx, th_read_simple_decoder); + READ_DECODER(delta_vibrato_idx, th_read_simple_decoder); + READ_DECODER(press_note_idx, th_read_simple_decoder); + READ_DECODER(press_note9_idx, th_read_simple_decoder); + READ_DECODER(press_volume_idx, th_read_simple_decoder); + READ_DECODER(group_size_idx, th_read_simple_decoder); + uint last_ne = th_read_bits(bi, 4); + for (int i = 2; i <= last_ne; i++) { + READ_DECODER(release_dist_idx[i], th_read_simple_decoder); + } + READ_DECODER(gap_idx, th_read_simple_decoder); + // build free note linked list + int i; + for(i=0;ion_notes[i*2] = (int8_t)(i+1); + } + d->on_notes[i*2] = -1; // end of list +#if MUSX_INITIAL_CHANNEL_VOLUME != 0 + memset(d->channel_last_volume, MUSX_INITIAL_CHANNEL_VOLUME, sizeof(d->channel_last_volume)); // empty lists + memset(d->channel_last_press_volume, MUSX_INITIAL_CHANNEL_VOLUME, sizeof(d->channel_last_press_volume)); // empty lists +#endif +#if MUSX_INITIAL_CHANNEL_WHEEL != 0 + memset(d->channel_last_wheel, MUSX_INITIAL_CHANNEL_WHEEL, sizeof(d->channel_last_wheel)); // empty lists +#endif +#if MUSX_INITIAL_CHANNEL_VIBRATO != 0 + memset(d->channel_last_vibrato, MUSX_INITIAL_CHANNEL_VIBRATO, sizeof(d->channel_last_vibrato)); // empty lists +#endif +#ifndef NDEBUG + memset(d->channel_note_tail, -1, sizeof(d->channel_note_head)); // empty lists + memset(d->channel_note_head, -1, sizeof(d->channel_note_head)); // empty lists +#endif + return idx; +} \ No newline at end of file diff --git a/src/musx_decoder.h b/src/musx_decoder.h new file mode 100644 index 00000000..42648029 --- /dev/null +++ b/src/musx_decoder.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#pragma once +#include "tiny_huff.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "assert.h" + +#define MUSX_CHANNEL_COUNT 16 +#define MUSX_RELEASE_DIST_COUNT 16 +#define MUSX_NOTE_LIMIT 24 + +// todo this should be dynamic per WAD +#define MUSX_MAX_DECODER_SPACE 384 + +#define MUSX_INITIAL_CHANNEL_VOLUME 100 +#define MUSX_INITIAL_CHANNEL_WHEEL 128 +#define MUSX_INITIAL_CHANNEL_VIBRATO 0 + +#define MUSX_GAP_MAX 255 + +static_assert(MUSX_NOTE_LIMIT < 128, ""); + +// MUSX event codes +enum seq_event { + system_event = 0x0, + score_end = 0x1, + release_key = 0x2, + press_key = 0x3, + change_controller = 0x4, + delta_pitch = 0x5, + delta_volume = 0x6, + delta_vibrato = 0x7 +}; + +typedef enum { + vol_last_channel = 128, + vol_last_global = 129, +} note_volume; + + +typedef struct { + th_bit_input *bi; + uint8_t group_remaining; + int8_t channel_note_free; + uint8_t last_volume; + uint16_t *decoders; + uint16_t channel_event_idx; + uint16_t delta_volume_idx; + uint16_t delta_pitch_idx; + uint16_t delta_vibrato_idx; + uint16_t press_note_idx; + uint16_t press_note9_idx; + uint16_t press_volume_idx; + uint16_t group_size_idx; + uint16_t gap_idx; + uint16_t release_dist_idx[MUSX_RELEASE_DIST_COUNT]; + uint8_t channel_last_volume[MUSX_CHANNEL_COUNT]; + uint8_t channel_last_press_volume[MUSX_CHANNEL_COUNT]; // distinct because "mus" last refers only to this + uint8_t channel_last_wheel[MUSX_CHANNEL_COUNT]; + uint8_t channel_last_vibrato[MUSX_CHANNEL_COUNT]; + uint8_t channel_note_count[MUSX_CHANNEL_COUNT]; + int8_t channel_note_head[MUSX_CHANNEL_COUNT]; + int8_t channel_note_tail[MUSX_CHANNEL_COUNT]; + int8_t on_notes[MUSX_NOTE_LIMIT*2]; +#ifndef NDEBEUG + int pos; +#endif +} musx_decoder; + +uint musx_decoder_init(musx_decoder *d, th_bit_input *bi, uint16_t *decoder_buffer, uint decoder_buffer_size, uint8_t *tmp_buf, uint tmp_buf_size); + +#include + +// only inline as it is only needed once +static inline void musx_record_note_on(musx_decoder *d, uint8_t channel, uint8_t note) { + assert(d->channel_note_free >= 0); + int8_t index = d->channel_note_free; + d->channel_note_free = d->on_notes[index*2]; + if (!d->channel_note_count[channel]++) { + assert(d->channel_note_head[channel]==-1); + assert(d->channel_note_tail[channel]==-1); + d->channel_note_head[channel] = d->channel_note_tail[channel] = index; + } else { + d->on_notes[d->channel_note_tail[channel]*2] = index; + d->channel_note_tail[channel] = index; + } +#ifndef NDEBUG + d->on_notes[index*2] = -1; +#endif + d->on_notes[index*2+1] = (int8_t)note; +#if 0 && !defined(NDEBUG) + printf("ON %d,%d: ", channel, note); + int p = (int)d->channel_note_head[channel]; + int last_p = p; + while (p>=0) { + printf("%d ", d->on_notes[p*2+1]); + p = (int)d->on_notes[p*2]; + if (p >= 0) last_p = p; + } + assert(last_p == d->channel_note_tail[channel]); + printf("\n"); +#endif +} + +static inline uint8_t musx_record_note_off(musx_decoder *d, uint8_t channel, uint8_t dist) { + assert(dist < d->channel_note_count[channel]); + d->channel_note_count[channel]--; + int8_t freed_index; + if (!dist) { + freed_index = d->channel_note_head[channel]; + assert(freed_index >= 0); + d->channel_note_head[channel] = d->on_notes[freed_index*2]; +#ifndef NDEBUG + if (d->channel_note_head[channel] < 0) { + d->channel_note_tail[channel] = -1; + } +#endif + } else { + assert(d->channel_note_head[channel]>=0); + assert(d->channel_note_tail[channel]>=0); + int8_t prev_index = d->channel_note_head[channel]; + for(int i=dist; i>1; i--) { + prev_index = d->on_notes[prev_index*2]; + assert(prev_index >= 0); // note only set to -1 at all in NDEBUG, but should always be positive + } + freed_index = d->on_notes[prev_index*2]; + assert(freed_index >= 0); // note only set to -1 at all in NDEBUG, but should always be positive + if (freed_index == d->channel_note_tail[channel]) { + d->channel_note_tail[channel] = prev_index; + } + d->on_notes[prev_index*2] = d->on_notes[freed_index*2]; + } +#if 0 && !defined(NDEBUG) + printf("OFF %d,%d: ", channel, dist); + int p = (int)d->channel_note_head[channel]; + int last_p = p; + while (p>=0) { + printf("%d ", d->on_notes[p*2+1]); + p = (int)d->on_notes[p*2]; + if (p >= 0) last_p = p; + } + assert(last_p == d->channel_note_tail[channel]); + printf("\n"); +#endif + d->on_notes[freed_index*2] = d->channel_note_free; + d->channel_note_free = freed_index; + return d->on_notes[freed_index*2+1]; +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/net_client.c b/src/net_client.c index c3b2517f..753faffc 100644 --- a/src/net_client.c +++ b/src/net_client.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -40,6 +41,11 @@ #include "w_checksum.h" #include "w_wad.h" +// Connected but not participating in the game (observer) + +boolean drone = false; + +#if !NO_USE_NET extern void D_ReceiveTic(ticcmd_t *ticcmds, boolean *playeringame); typedef enum @@ -124,11 +130,7 @@ boolean net_waiting_for_launch = false; // Name that we send to the server -char *net_player_name = NULL; - -// Connected but not participating in the game (observer) - -boolean drone = false; +const char *net_player_name = NULL; // The last ticcmd constructed @@ -1202,3 +1204,4 @@ void NET_BindVariables(void) { M_BindStringVariable("player_name", &net_player_name); } +#endif \ No newline at end of file diff --git a/src/net_client.h b/src/net_client.h index 37ba0198..21b45e33 100644 --- a/src/net_client.h +++ b/src/net_client.h @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -22,6 +23,7 @@ #include "sha1.h" #include "net_defs.h" +#if !NO_USE_NET boolean NET_CL_Connect(net_addr_t *addr, net_connect_data_t *data); void NET_CL_Disconnect(void); void NET_CL_Run(void); @@ -39,7 +41,7 @@ extern boolean net_client_received_wait_data; extern net_waitdata_t net_client_wait_data; extern char *net_client_reject_reason; extern boolean net_waiting_for_launch; -extern char *net_player_name; +extern const char *net_player_name; extern sha1_digest_t net_server_wad_sha1sum; extern sha1_digest_t net_server_deh_sha1sum; @@ -48,6 +50,9 @@ extern sha1_digest_t net_local_wad_sha1sum; extern sha1_digest_t net_local_deh_sha1sum; extern unsigned int net_local_is_freedoom; +#elif !USE_PICO_NET +#define net_client_connected false +#endif extern boolean drone; #endif /* #ifndef NET_CLIENT_H */ diff --git a/src/net_common.c b/src/net_common.c index 08eeb0ee..ff1b2b52 100644 --- a/src/net_common.c +++ b/src/net_common.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -495,6 +496,7 @@ void NET_OpenLog(void) { int p; +#if !NO_USE_ARGS p = M_CheckParmWithArgs("-netlog", 1); if (p > 0) { @@ -505,6 +507,7 @@ void NET_OpenLog(void) } I_AtExit(CloseLog, true); } +#endif } void NET_Log(const char *fmt, ...) diff --git a/src/net_dedicated.c b/src/net_dedicated.c index dcb9f8fa..157f3019 100644 --- a/src/net_dedicated.c +++ b/src/net_dedicated.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -29,6 +30,7 @@ #include "net_sdl.h" #include "net_server.h" +#if !NO_USE_NET // // People can become confused about how dedicated servers work. Game // options are specified to the controlling player who is the first to @@ -78,3 +80,4 @@ void NET_DedicatedServer(void) } } +#endif \ No newline at end of file diff --git a/src/net_defs.h b/src/net_defs.h index bf6a264e..870a5a70 100644 --- a/src/net_defs.h +++ b/src/net_defs.h @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -28,21 +29,33 @@ // NET_MAXPLAYERS, as there may be observers that are not participating // (eg. left/right monitors) -#define MAXNETNODES 16 - // The maximum number of players, multiplayer/networking. // This is the maximum supported by the networking code; individual games // have their own values for MAXPLAYERS that can be smaller. +#if !DOOM_SMALL #define NET_MAXPLAYERS 8 +#define MAXNETNODES 16 +#else +#define NET_MAXPLAYERS 4 +#define MAXNETNODES 4 +#endif // Maximum length of a player's name. +#if !DOOM_TINY #define MAXPLAYERNAME 30 - +#else +#define MAXPLAYERNAME 18 // really just based on the entry field size +#endif // Networking and tick handling related. +#if DOOM_TINY +// todo graham figure out what we actually need (for multiplayer maybe more, for single player possibly only 1) +#define BACKUPTICS 5 +#else #define BACKUPTICS 128 +#endif typedef struct _net_module_s net_module_t; typedef struct _net_packet_s net_packet_t; @@ -169,7 +182,7 @@ typedef enum typedef struct { - int gamemode; + int _gamemode; int gamemission; int lowres_turn; int drone; diff --git a/src/net_gui.c b/src/net_gui.c index 84622248..6ee37caf 100644 --- a/src/net_gui.c +++ b/src/net_gui.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -37,6 +38,7 @@ #include "textscreen.h" +#if !NO_USE_NET static txt_window_t *window; static int old_max_players; static txt_label_t *player_labels[NET_MAXPLAYERS]; @@ -429,3 +431,4 @@ void NET_WaitForLaunch(void) TXT_Shutdown(); } +#endif \ No newline at end of file diff --git a/src/net_query.c b/src/net_query.c index da292b50..67c711b5 100644 --- a/src/net_query.c +++ b/src/net_query.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -32,6 +33,7 @@ #include "net_structrw.h" #include "net_sdl.h" +#if !NO_USE_NET // DNS address of the Internet master server. #define MASTER_SERVER_ADDRESS "master.chocolate-doom.org:2342" @@ -678,6 +680,7 @@ static const char *GameDescription(GameMode_t mode, GameMission_t mission) return "tnt"; case pack_plut: return "plutonia"; +#if !DOOM_ONLY case pack_chex: return "chex"; case pack_hacx: @@ -688,6 +691,7 @@ static const char *GameDescription(GameMode_t mode, GameMission_t mission) return "hexen"; case strife: return "strife"; +#endif default: return "?"; } @@ -951,3 +955,4 @@ char *NET_EndSecureDemo(sha1_digest_t demo_hash) return signature; } +#endif \ No newline at end of file diff --git a/src/net_sdl.c b/src/net_sdl.c index b889c9c9..e8180fee 100644 --- a/src/net_sdl.c +++ b/src/net_sdl.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -29,6 +30,8 @@ #include "net_sdl.h" #include "z_zone.h" +#if !NO_USE_NET + // // NETWORKING // @@ -375,3 +378,4 @@ net_module_t net_sdl_module = NET_SDL_ResolveAddress, }; +#endif \ No newline at end of file diff --git a/src/net_server.c b/src/net_server.c index c9dd3d9b..8d36e098 100644 --- a/src/net_server.c +++ b/src/net_server.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -39,6 +40,7 @@ #include "net_sdl.h" #include "net_structrw.h" +#if !NO_USE_NET // How often to refresh our registration with the master server. #define MASTER_REFRESH_PERIOD 20 * 60 /* 20 minutes */ @@ -656,12 +658,12 @@ static void NET_SV_ParseSYN(net_packet_t *packet, net_client_t *client, return; } - if (!D_ValidGameMode(data.gamemission, data.gamemode) + if (!D_ValidGameMode(data.gamemission, data._gamemode) || data.max_players > NET_MAXPLAYERS) { NET_Log("server: error: invalid connect data, max_players=%d, " "gamemission=%d, gamemode=%d", - data.max_players, data.gamemission, data.gamemode); + data.max_players, data.gamemission, data._gamemode); return; } @@ -705,7 +707,7 @@ static void NET_SV_ParseSYN(net_packet_t *packet, net_client_t *client, // Adopt the game mode and mission of the first connecting client: if (num_players == 0 && !data.drone) { - sv_gamemode = data.gamemode; + sv_gamemode = data._gamemode; sv_gamemission = data.gamemission; NET_Log("server: new game, mode=%d, mission=%d", sv_gamemode, sv_gamemission); @@ -713,17 +715,17 @@ static void NET_SV_ParseSYN(net_packet_t *packet, net_client_t *client, // Check the connecting client is playing the same game as all // the other clients - if (data.gamemode != sv_gamemode || data.gamemission != sv_gamemission) + if (data._gamemode != sv_gamemode || data.gamemission != sv_gamemission) { char msg[128]; NET_Log("server: wrong mode/mission, %d != %d || %d != %d", - data.gamemode, sv_gamemode, data.gamemission, sv_gamemission); + data._gamemode, sv_gamemode, data.gamemission, sv_gamemission); M_snprintf(msg, sizeof(msg), "Game mismatch: server is %s (%s), client is %s (%s)", D_GameMissionString(sv_gamemission), D_GameModeString(sv_gamemode), D_GameMissionString(data.gamemission), - D_GameModeString(data.gamemode)); + D_GameModeString(data._gamemode)); NET_SV_SendReject(addr, msg); return; @@ -2006,3 +2008,4 @@ void NET_SV_Shutdown(void) I_Sleep(1); } } +#endif \ No newline at end of file diff --git a/src/net_structrw.c b/src/net_structrw.c index 437bc71a..b113ada4 100644 --- a/src/net_structrw.c +++ b/src/net_structrw.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -36,7 +37,7 @@ static struct void NET_WriteConnectData(net_packet_t *packet, net_connect_data_t *data) { - NET_WriteInt8(packet, data->gamemode); + NET_WriteInt8(packet, data->_gamemode); NET_WriteInt8(packet, data->gamemission); NET_WriteInt8(packet, data->lowres_turn); NET_WriteInt8(packet, data->drone); @@ -49,7 +50,7 @@ void NET_WriteConnectData(net_packet_t *packet, net_connect_data_t *data) boolean NET_ReadConnectData(net_packet_t *packet, net_connect_data_t *data) { - return NET_ReadInt8(packet, (unsigned int *) &data->gamemode) + return NET_ReadInt8(packet, (unsigned int *) &data->_gamemode) && NET_ReadInt8(packet, (unsigned int *) &data->gamemission) && NET_ReadInt8(packet, (unsigned int *) &data->lowres_turn) && NET_ReadInt8(packet, (unsigned int *) &data->drone) @@ -201,6 +202,7 @@ void NET_WriteTiccmdDiff(net_packet_t *packet, net_ticdiff_t *diff, NET_WriteInt8(packet, diff->cmd.consistancy); if (diff->diff & NET_TICDIFF_CHATCHAR) NET_WriteInt8(packet, diff->cmd.chatchar); +#if !DOOM_ONLY if (diff->diff & NET_TICDIFF_RAVEN) { NET_WriteInt8(packet, diff->cmd.lookfly); @@ -211,6 +213,7 @@ void NET_WriteTiccmdDiff(net_packet_t *packet, net_ticdiff_t *diff, NET_WriteInt8(packet, diff->cmd.buttons2); NET_WriteInt16(packet, diff->cmd.inventory); } +#endif } boolean NET_ReadTiccmdDiff(net_packet_t *packet, net_ticdiff_t *diff, @@ -277,6 +280,7 @@ boolean NET_ReadTiccmdDiff(net_packet_t *packet, net_ticdiff_t *diff, diff->cmd.chatchar = val; } +#if !DOOM_ONLY if (diff->diff & NET_TICDIFF_RAVEN) { if (!NET_ReadInt8(packet, &val)) @@ -298,6 +302,7 @@ boolean NET_ReadTiccmdDiff(net_packet_t *packet, net_ticdiff_t *diff, return false; diff->cmd.inventory = val; } +#endif return true; } @@ -320,6 +325,7 @@ void NET_TiccmdDiff(ticcmd_t *tic1, ticcmd_t *tic2, net_ticdiff_t *diff) if (tic2->chatchar != 0) diff->diff |= NET_TICDIFF_CHATCHAR; +#if !DOOM_ONLY // Heretic/Hexen-specific if (tic1->lookfly != tic2->lookfly || tic2->arti != 0) @@ -329,6 +335,7 @@ void NET_TiccmdDiff(ticcmd_t *tic1, ticcmd_t *tic2, net_ticdiff_t *diff) if (tic1->buttons2 != tic2->buttons2 || tic2->inventory != 0) diff->diff |= NET_TICDIFF_STRIFE; +#endif } void NET_TiccmdPatch(ticcmd_t *src, net_ticdiff_t *diff, ticcmd_t *dest) @@ -353,6 +360,7 @@ void NET_TiccmdPatch(ticcmd_t *src, net_ticdiff_t *diff, ticcmd_t *dest) else dest->chatchar = 0; +#if !DOOM_ONLY // Heretic/Hexen specific: if (diff->diff & NET_TICDIFF_RAVEN) @@ -376,6 +384,7 @@ void NET_TiccmdPatch(ticcmd_t *src, net_ticdiff_t *diff, ticcmd_t *dest) { dest->inventory = 0; } +#endif } // diff --git a/src/pd_render.cpp b/src/pd_render.cpp new file mode 100644 index 00000000..7efefb5e --- /dev/null +++ b/src/pd_render.cpp @@ -0,0 +1,3107 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// This is a rationalization of pd_render into something which does the sort on insert, but without all the memory +// chicanery we'll need from the final version + +#include "picodoom.h" +#include "pico/sem.h" +#include "hardware/gpio.h" +#include "pico/divider.h" +#include "image_decoder.h" +#include +extern "C" { +#include "doom/d_main.h" +#include "doom/r_data.h" +#include "doom/r_sky.h" +#include "doom/r_things.h" +#include "doom/m_menu.h" +#include "doom/doomstat.h" +#include "doom/am_map.h" +#include "doom/m_menu.h" +#include "doom/st_stuff.h" +#include "doom/wi_stuff.h" +#include "doom/hu_stuff.h" +#include "doom/f_wipe.h" +#include "doom/f_finale.h" +#include "v_video.h" +#include "i_video.h" +} +// todo compare with and without +#define USE_XIPCPY 0 +#if PICO_ON_DEVICE +#define USE_CORE1_FOR_FLATS 1 +#endif +#define USE_CORE1_FOR_REGULAR 1 +#ifdef PICO_SPINLOCK_ID_OS2 +#define RENDER_SPIN_LOCK PICO_SPINLOCK_ID_OS2 +#else +#define RENDER_SPIN_LOCK 15 +#endif +//#define DEBUG_DECODER 1 +//#define DEBUG_DECODER_BUFFERS 1 +//#define DEBUG_COMPOSITE 1 +// we wake up core1 during rendering whole rendering part of game loop.. during +// this time it can do sound updates (since the main core is clearly not) +semaphore_t core1_wake, core0_done, core1_done; + +#if USE_CORE1_FOR_FLATS +semaphore_t core1_do_flats; +int16_t core1_fr_list; +#endif +#if USE_CORE1_FOR_REGULAR +semaphore_t core1_do_regular; +#endif +pre_wipe_state_t pre_wipe_state; +static int16_t sub_gamestate; +// todo look at using scratch RAM for local linked lists/buffers (we sort of have this with stack) + +CU_REGISTER_DEBUG_PINS(flat_decode, patch_decode, full_render, render_thing, render_flat, start_end) +extern uint8_t restart_song_state; + +//CU_SELECT_DEBUG_PINS(patch_decode) +//CU_SELECT_DEBUG_PINS(render_thing) +//CU_SELECT_DEBUG_PINS(full_render) +//CU_SELECT_DEBUG_PINS(start_end) + +#if !PICO_ON_DEVICE +#include "../whd_gen/statsomizer.h" + +std::set textures, patches; +statsomizer tex_count("texcount"), patch_count("patchcount"); +statsomizer patch_decoder_size("patch decoder size"); +#endif + +#if PICO_ON_DEVICE + +#include "hardware/interp.h" + +#endif + +extern "C" { +#include "doomtype.h" +#include "doom/r_local.h" +#include "i_system.h" +#include "w_wad.h" +#include "z_zone.h" +#include "doom/r_plane.h" +void I_UpdateSound(void); +} +void draw_cast_sprite(int sprite_lump); +#pragma GCC push_options +#if PICO_ON_DEVICE +#pragma GCC optimize("O3") +#endif + +wipestate_t wipestate; +static uint8_t post_wipecount; + +// todo these are only needed temporarily, so stack or "tmp buffer" +static uint16_t flat_decoder_buf[WHD_FLAT_DECODER_MAX_SIZE]; +static uint8_t flat_decoder_tmp[WHD_FLAT_DECODER_MAX_SIZE]; +#define PATCH_DECODER_HASH_SIZE 128 +static_assert(__builtin_popcount(PATCH_DECODER_HASH_SIZE)==1, ""); +static int16_t patch_hash_offsets[PATCH_DECODER_HASH_SIZE]; +#define PATCH_DECODER_CIRCULAR_BUFFER_SIZE (2048-256) +static uint16_t patch_decoder_circular_buf[PATCH_DECODER_CIRCULAR_BUFFER_SIZE]; +static uint16_t patch_decoder_circular_buf_write_pos; +static uint16_t patch_decoder_circular_buf_write_limit; +// this is used when decoding decoders, but also as a cache for up to 4 decoder tables (each of which are 256 bytes big) +static uint8_t patch_decoder_tmp[256 * WHD_MAX_COL_UNIQUE_PATCHES]; +// which patch decoder table (or 0 if none) is stored in each of the 256 byte areas in patch_decoder_tmp +static uint16_t patch_decoder_tmp_table_patch_numbers[WHD_MAX_COL_UNIQUE_PATCHES]; +// in case we max out columns during regular rendering, we will be left with gaps in the screen +// so we keep a bit set for each 4 columns (no harm in clearing columns a word wide) +static uint32_t not_fully_covered_cols[(SCREENWIDTH/4 + 31)/32]; +static uint8_t not_fully_covered_yl, not_fully_covered_yh; +static int16_t fd_heads[MAX_FRAME_DRAWABLES]; // frame drawable linked lists +vpatchlists_t *vpatchlists; +int16_t visplane_heads[MAXVISPLANES]; +int8_t flatnum_next[MAXVISPLANES]; + +#if USE_XIPCPY +#define FLAT_SOURCE_Z_SIZE 2400 +static uint32_t flat_sourcez[FLAT_SOURCE_Z_SIZE/4]; +#endif + +#define xcolormaps colormaps + +#define FUZZTABLE 50 +#define FUZZOFF (SCREENWIDTH) + +const int16_t fuzzoffset[FUZZTABLE] = + { + FUZZOFF, -FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, + FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, + FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, + FUZZOFF, -FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, + FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, -FUZZOFF, FUZZOFF, + FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, + FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF + }; + +#include + +#if PICO_ON_DEVICE +//extern fixed_t FastFixedMul(fixed_t a, fixed_t b); +#define FastFixedMul FixedMulInline +#else +#define FastFixedMul FixedMul +#endif + +#define USE_INTERP PICO_ON_DEVICE +#if USE_INTERP +#define span_interp interp1_hw +#endif + +#if !PICO_ON_DEVICE +#define DUMP_SORTING 1 +#endif +// seems to work +typedef unsigned int uint; + +int pd_frame; +int pd_flag; +fixed_t pd_scale; + +extern uint8_t __aligned(4) frame_buffer[2][SCREENWIDTH * MAIN_VIEWHEIGHT]; +static uint8_t __aligned(4) visplane_bit[(SCREENWIDTH / 8) * MAIN_VIEWHEIGHT]; // this is also used for patch decoding in core1 (since flats are done by then) +static int8_t flatnum_first[256]; + +static uint8_t *render_frame_buffer; +static uint8_t render_frame_index; +static uint8_t render_overlay_index; + +static_assert(NO_USE_DC_COLORMAP, ""); +static_assert(USE_ROWAD, ""); // don't want things moving! + +#define SHOW_COLUMN_STATS 0 +#define SHOW_COSTLY_DATA_STATS 0 + +// we always want to collapse range of iscale and dda +#define SHIFT 7 +#define DOWN_SHIFT(x) ((x) >> SHIFT) // only used for texturemid +#define MINI (-512*65536) +#define MAXI (512*65536-1 - (1 << SHIFT)) +#define TEXTUREMID_PLANE (DOWN_SHIFT(MAXI)+1) +static_assert(TEXTUREMID_PLANE == 0x3ffff, ""); + +#define UP_SHIFT(x) ((x) << SHIFT) // only used for texturemid +#define CLAMP(x) (((x)<<13)>>13) + +#define DDA_MIN 0 +#define DDA_SHIFT 0 +#define DDA_DOWN_SHIFT(x) ((x)>>DDA_SHIFT) +// note max is slightly over this, but the texture is likely unintelligable at this point anyway +#define DDA_MAX 0xfffff +#define DDA_CLAMP(x) (((uint)((x)<<12))>>12) +#define DDA_UP_SHIFT(x) ((x) << DDA_SHIFT) + +bool next_frame_pause; + +int pd_dump_frame = -1; + +// =========================================================== +// LETS LOOK AT COLUMNS AGAIN +// SORT COL +// 19: texture_mid - need markers for fuzzy, and (floor/ceiling) +// 5: cmap_index +// 8: len + +// 22: (6:16) pd_scale (not sure we need this many fractional bits) +// 10: graphic (maybe 1 bit of y) + +// 8: y +// 8: x +// 16: next + +// ENCODED NON SPAN: +// <19,texture_mid:10.9> <5,cmap_index> <8, len> +// <20,iscale:4.16> <12, col_index> +// ENCODED SPAN: +// <19,0x4000> <5,cmap_index> <8, len> +// <24,0x000000> <8, visplane_idx> + +struct pd_column { +#if PD_COLUMN_DEBUG + pd_column_type type; +#endif + int16_t next; + uint8_t yl; + uint8_t yh; + + uint32_t scale: 24; // ? 22 + yh = 29 + uint8_t colormap_index: 5; + + uint8_t col_hi: 3; + uint32_t col_lo: 5; + + int32_t texturemid: 19; // 0x7ffff for flat + + union { + uint8_t plane; + uint8_t fd_num; + uint8_t x; + }; +}; + +#define TO_COL_HI(c) ((c)>>5) +#define TO_COL_LO(c) ((c)&0x1f) +#define FROM_COL_HI_LO(h,l) (((h)<<5)|(l)) + +struct __packed __aligned(2) flat_run { + int16_t next; + uint32_t x_start: 12; + uint32_t x_end: 12; + uint32_t y: 8; +}; + +static_assert(sizeof(flat_run) == 6, ""); +static_assert(sizeof(pd_column) == 12, ""); +static_assert(sizeof(pd_column) == sizeof(flat_run) * 2, ""); // we pack two into the space of the other + +static __aligned(4) int16_t column_heads[SCREENWIDTH * 2]; +#define fuzzy_column_heads (&column_heads[SCREENWIDTH]) + +static void SafeUpdateSound() { + boolean save = false; + if (get_core_num()) { + save = interp_in_use; + // setting this causes the scanline code to save/restore the interp settings + interp_in_use = true; + } + I_UpdateSound(); + if (get_core_num()) { + interp_in_use = save; + } +} + +static bool column_is_psprite(const pd_column &c) { + return c.scale == 0; +} + +static bool column_is_nil(const pd_column &c) { + return c.yl > c.yh || c.yh == 255; +} + +const char *type_name(pd_column column) { +#if PD_COLUMN_DEBUG + switch (column.type) { + case PDCOL_MASKED: + if (column_is_fuzzy(column)) return "fuzz"; + if (column_is_psprite(column)) return "player"; + return "masked"; + case PDCOL_BOTTOM: + return "bottom"; + case PDCOL_CEILING: + return "ceiling"; + case PDCOL_FLOOR: + return "floor"; + case PDCOL_MID: + return "mid"; + case PDCOL_SKY: + return "sky"; + case PDCOL_TOP: + return "top"; + default: + return ""; + } +#else + if (column_is_psprite(column)) { + return "player"; + } + return ""; +#endif +} + +#define RENDER_COL_MAX 3600 +static uint8_t __aligned(4) list_buffer[RENDER_COL_MAX * sizeof(pd_column) + 64*64]; // extra 64*64 is for one flat +static uint8_t *last_list_buffer_limit = list_buffer + sizeof(list_buffer); +//static_assert(text_font_cpy > list_buffer, ""); +#define MAX_CACHED_FLATS (sizeof(list_buffer) / 4096) +static uint8_t cached_flat_picnum[MAX_CACHED_FLATS]; +static uint8_t cached_flat_slots; +static uint8_t *cached_flat0; +static int16_t render_col_count; +#define render_cols ((pd_column *)list_buffer) +#define flat_runs ((flat_run *)list_buffer) +static int16_t render_col_free; + +static int16_t alloc_pd_column(int x) { + if (render_col_free < 0) { + if (render_col_count == RENDER_COL_MAX) { + assert(x>=0 && x= 0); + render_cols[rc_index].next = render_col_free; + render_col_free = rc_index; +} + +#if DUMP_SORTING + +const char *column_desc(int index) { + static char buf[128]; + pd_column &pd = render_cols[index]; + sprintf(buf, "%d %s %d->%d at %08x", index, type_name(pd), pd.yl, pd.yh, pd.scale); + return buf; +} + +static void dump_column_list(int x, const char *prefix, int i) { + printf("%d: %s:\n", x, prefix); + int last = -1; + bool bad = false; + for (; i != -1; i = render_cols[i].next) { + printf(" %s\n", column_desc(i)); + if (render_cols[i].yl <= last) { + bad = true; + } + last = render_cols[i].yh; + } + if (bad && x < SCREENWIDTH) { + printf("oops\n"); + } +} + +static void dump_column(int x, const char *prefix) { + dump_column_list(x, prefix, column_heads[x]); +} + +#endif + +// new_index can be a (non overlapping) linked list (in ascending y order) +static void push_down_x_guts(int x, int16_t new_index) { + int16_t *prev_existing_ptr = &column_heads[x]; + int16_t existing_index = *prev_existing_ptr; +// dump_column(x, "before"); +// dump_column_list(x, "Want to insert", new_index); +// if (pd_frame == 176 && x == 174) { +// printf("FUZZ %d\n", column_is_fuzzy(render_cols[new_index])); +// printf("SPSP\n"); +// } + do { + assert(existing_index == *prev_existing_ptr); + + pd_column &new_col = render_cols[new_index]; + assert(!column_is_nil(new_col)); + + // both lists (new, existing) are y sorted and non overlapping, so skip past anything existing + // that is above the new columns + while (existing_index >= 0 && new_col.yl > render_cols[existing_index].yh) { + prev_existing_ptr = &render_cols[existing_index].next; + existing_index = *prev_existing_ptr; + } + if (existing_index < 0) { + // all new columns are below the existing ones; append rest as is + assert(*prev_existing_ptr == -1); + *prev_existing_ptr = new_index; + break; + } + + pd_column &existing_col = render_cols[existing_index]; + assert(!column_is_nil(existing_col)); + + if (new_col.yh < existing_col.yl) { + // new column is before the existing col, so just insert it + *prev_existing_ptr = new_index; + new_index = new_col.next; + new_col.next = existing_index; + if (new_index < 0) break; // loop condition (only checked when we insert our new column) + prev_existing_ptr = &new_col.next; + *prev_existing_ptr = existing_index; + // and move on to the next new column + continue; + } + // there is some overlap + assert(existing_col.yl <= new_col.yh); + if (new_col.scale < existing_col.scale) { + // new column is in front of existing column + const auto &cf = new_col; + auto &cb = existing_col; + + if (cf.yl <= cb.yl) { + if (cf.yh >= cb.yh) { + // new column fully obscures existing column, so unlink existing column as well as freeing it + *prev_existing_ptr = existing_col.next; + free_pd_column(existing_index); + existing_index = *prev_existing_ptr; + } else { + // new column obscures the top of existing column, so clip the existing + cb.yl = cf.yh + 1; + assert(cb.yl <= cb.yh); + // and now we can insert the new column, and + // move on to the next new column if any, since the one we + // were dealing with cannot interact with any future existing columns + // since we didn't go past the one we just clipped (and the existing + // items don't overlap) + *prev_existing_ptr = new_index; + new_index = new_col.next; + new_col.next = existing_index; + if (new_index < 0) break; // loop condition (only checked when we insert our new column) + prev_existing_ptr = &new_col.next; + *prev_existing_ptr = existing_index; + } + } else { + if (cf.yh >= cb.yh) { + // new column obscures the bottom of existing column cb, so clip existing column + cb.yh = cf.yl - 1; + // ... and keep going with the same new column with the next existing column + prev_existing_ptr = &existing_col.next; + existing_index = *prev_existing_ptr; + } else { + // new column splits existing in two (it is in the middle). we can clip the top half of the existing column, + // then insert the new column (which is also in the right place) and then add a new bottom part for the existing one + + // bottom part + int16_t extra_index = alloc_pd_column(x); + if (extra_index >= 0) { + render_cols[extra_index] = cb; + render_cols[extra_index].yl = cf.yh + 1; + + // top part is just clipped + cb.yh = cf.yl - 1; + + // so + // prev->existing->xxx with N1->N2 + // becomes + // prev->existing(clipped)->N1->e2->xxx with N2 + // + // and we continue with e2 and N2 + + render_cols[extra_index].next = existing_col.next; + existing_col.next = new_index; + new_index = new_col.next; + new_col.next = extra_index; + + if (new_index < 0) break; // loop condition (only checked when we insert our new column) + prev_existing_ptr = &new_col.next; + existing_index = extra_index; + } else { + // no more room, so just clip the existing column to be just the top part + + cb.yh = cf.yl - 1; + + // we can move onto tne next new item since it finished during the existing item and we're + // moving onto the next item below + new_index = new_col.next; + new_col.next = extra_index; + + if (new_index < 0) break; // loop condition (only checked when we insert our new column) + + // ... and keep going with the same new column with the next existing column + prev_existing_ptr = &existing_col.next; + existing_index = *prev_existing_ptr; + } + } + } + } else { + // new column is behind existing column + const auto &cf = existing_col; + auto &cb = new_col; + if (cf.yl <= cb.yl) { + if (cf.yh >= cb.yh) { + // new column fully obscured by existing column, so free and move on + int16_t to_free_index = new_index; + new_index = new_col.next; + free_pd_column(to_free_index); + if (new_index < 0) break; // loop condition + } else { + // existing column obscures the top of new column, so clip our new column + cb.yl = cf.yh + 1; + assert(cb.yl <= cb.yh); + // ... and keep going with the same new column with the next existing column + prev_existing_ptr = &existing_col.next; + existing_index = *prev_existing_ptr; + } + } else { + if (cf.yh >= cb.yh) { + // existing column obscures the bottom of new column, so clip + cb.yh = cf.yl - 1; + // and then insert what is left (it is in the right place) + *prev_existing_ptr = new_index; + new_index = new_col.next; + new_col.next = existing_index; + if (new_index < 0) break; // loop condition (only checked when we insert our new column) + prev_existing_ptr = &new_col.next; + *prev_existing_ptr = existing_index; + } else { + // existing column splits new column in two (it is in the middle) + // we clip the new column for the top half (and insert it as it is in the right place), + // and make a second new column for the bottom half. + + // bottom part + int16_t extra_index = alloc_pd_column(x); + if (extra_index >= 0) { + render_cols[extra_index] = cb; + render_cols[extra_index].yl = cf.yh + 1; + + // top part + cb.yh = cf.yl - 1; + + // so + // prev->existing->xxx with N1->N2 + // becomes + // prev->N1->existing->xxx with N1b->N2 + // + // and we continue with xxx and N1b + *prev_existing_ptr = new_index; + render_cols[extra_index].next = new_col.next; + new_col.next = existing_index; + new_index = extra_index; + + prev_existing_ptr = &existing_col.next; + existing_index = *prev_existing_ptr; + } else { + // pretend existing column obscures the bottom of new column, so clip + cb.yh = cf.yl - 1; + // and then insert what is left (it is in the right place) + *prev_existing_ptr = new_index; + new_index = new_col.next; + new_col.next = existing_index; + if (new_index < 0) break; // loop condition (only checked when we insert our new column) + prev_existing_ptr = &new_col.next; + *prev_existing_ptr = existing_index; + } + } + } + } + } while (true); +// dump_column(x, "after"); +} + +// new_index can be a (non overlapping) linked list (in ascending y order) +// for fuzzy columns we're just clipping the new columns against the existing stuff +static void push_down_x_fuzzy(int x, int16_t new_index) { + int16_t existing_index = column_heads[x]; +// dump_column(x, "before"); +// dump_column(x+SCREENWIDTH, "before fuzz"); +// if (x == 478 - SCREENWIDTH && new_index == 246) { +// printf("VANAR\n"); +// } +// dump_column_list(x+SCREENWIDTH, "Want to insert", new_index); + // if (pd_frame == 176 && x == 174) { + // printf("FUZZ %d\n", column_is_fuzzy(render_cols[new_index])); + // printf("SPSP\n"); + // } + do { + pd_column &new_col = render_cols[new_index]; + assert(!column_is_nil(new_col)); + + // both lists (new, existing) are y sorted and non overlapping, so skip past anything existing + // that is above the new columns + while (existing_index >= 0 && new_col.yl > render_cols[existing_index].yh) { + existing_index = render_cols[existing_index].next; + } + if (existing_index < 0) { + // remaining new list can be prepended + break; + } + + pd_column &existing_col = render_cols[existing_index]; + assert(!column_is_nil(existing_col)); + + if (new_col.yh < existing_col.yl) { + // fuzzy column is before the existing col, so just insert it + int16_t tmp = new_index; + new_index = new_col.next; + new_col.next = fuzzy_column_heads[x]; + fuzzy_column_heads[x] = tmp; + if (new_index < 0) break; + continue; + } + // there is some overlap + assert(existing_col.yl <= new_col.yh); + if (new_col.scale < existing_col.scale) { + // fuzzy column is in front of existing column + const auto &cf = new_col; + auto &cb = existing_col; + + if (cf.yl <= cb.yl) { + if (cf.yh >= cb.yh) { + // fuzzy column fully obscures existing column, so just move onto next existing one which may also overlap + existing_index = existing_col.next; + } else { + // fuzzy column obscures the top of existing column, so the fuzzy column cannot be obscured, so insert it then move on + int16_t tmp = new_index; + new_index = new_col.next; + new_col.next = fuzzy_column_heads[x]; + fuzzy_column_heads[x] = tmp; + if (new_index < 0) break; // loop condition + } + } else { + if (cf.yh >= cb.yh) { + // fuzzy column obscures the bottom of an existing column, so move on with the next existing column + existing_index = existing_col.next; + } else { + // fuzzy column splits existing in two (it is in the middle), so we can insert the fuzzy column which + // cannot be obscured + + // new column obscures the top of existing column, so insert the new column which is not obscured, and move onto the next existing column + int16_t tmp = new_index; + new_index = new_col.next; + new_col.next = fuzzy_column_heads[x]; + fuzzy_column_heads[x] = tmp; + if (new_index < 0) break; // loop condition + } + } + } else { + // fuzzy column is behind existing column + const auto &cf = existing_col; + auto &cb = new_col; + if (cf.yl <= cb.yl) { + if (cf.yh >= cb.yh) { + // fuzzy column fully obscured by existing column, so free and move on + int16_t to_free_index = new_index; + new_index = new_col.next; + free_pd_column(to_free_index); + if (new_index < 0) break; // loop condition + } else { + // existing column obscures the top of fuzzy column, so clip our fuzzy column + cb.yl = cf.yh + 1; + assert(cb.yl <= cb.yh); + // ... and keep going with the same fuzzy column with the next existing column + existing_index = existing_col.next; + } + } else { + if (cf.yh >= cb.yh) { + // existing column obscures the bottom of fuzzy column, so clip fuzzy column + cb.yh = cf.yl - 1; + // and then insert what is left (it is not obscured) + int16_t tmp = new_index; + new_index = new_col.next; + new_col.next = fuzzy_column_heads[x]; + fuzzy_column_heads[x] = tmp; + if (new_index < 0) break; // loop condition (only checked when we insert our new column) + } else { + // existing column splits new fuzzy in two (it is in the middle) + // we clip the fuzzy column for the top half (and insert it as it is now not obscured), + // and make a second new column for the bottom half. + + // bottom part + int16_t extra_index = alloc_pd_column(x); + if (extra_index >= 0) { + render_cols[extra_index] = cb; + render_cols[extra_index].yl = cf.yh + 1; + + // top part + cb.yh = cf.yl - 1; + + render_cols[extra_index].next = new_col.next; + new_col.next = fuzzy_column_heads[x]; + fuzzy_column_heads[x] = new_index; + new_index = extra_index; + + existing_index = existing_col.next; + } else { + // pretend existing column obscures the bottom of new column, so clip + cb.yh = cf.yl - 1; + // and then insert what is left (it is not obscured) + int16_t tmp = new_index; + new_index = new_col.next; + new_col.next = fuzzy_column_heads[x]; + fuzzy_column_heads[x] = tmp; + if (new_index < 0) break; // loop condition (only checked when we insert our new column) + } + } + } + } + } while (true); + if (new_index >= 0) { + int tmp = new_index; + while (render_cols[tmp].next >= 0) tmp = render_cols[tmp].next; + render_cols[tmp].next = fuzzy_column_heads[x]; + fuzzy_column_heads[x] = new_index; + } +// dump_column(x+SCREENWIDTH, "after"); +} + +static void push_down_x(int x, int new_index) { +#if DUMP_SORTING + // if (x == 196 && render_cols[new_index].yl == 94 && render_cols[new_index].yh == 95) { + // printf("Claraa\n"); + // } + if (0 && !x) { + dump_column(x, "Before insert"); + dump_column_list(x, "Want to insert", new_index); + if (x >= 145 && render_cols[new_index].next != -1) { + printf("VLARN\n"); + } + } +#endif +#if PICO_ON_DEVICE + // gpio_put(22, 1); +#endif + push_down_x_guts(x, new_index); +#if PICO_ON_DEVICE + // gpio_put(22, 0); +#endif + +#if DUMP_SORTING + if (0 && !x) { + dump_column(x, "After insert"); + printf("\n"); + } +#endif +} + +void pd_begin_frame() { + DEBUG_PINS_SET(start_end, 1); + if (gamestate == GS_LEVEL) { +// render_frame_index ^= 1; + } + render_frame_buffer = nullptr; +#if 0 && !PICO_ON_DEVICE + printf("BEGIN FRAME %d rfb %p\n", render_frame_index, render_frame_buffer); +#endif + sem_release(&core1_wake); + + reset_framedrawables(); +#if !PICO_ON_DEVICE + textures.clear(); + patches.clear(); +#endif +#if DUMP_SORTING + if (0) printf("BEGIN FRAME\n"); +#endif + // new + memset(column_heads, -1, sizeof(column_heads)); + memset(visplane_bit, 0, sizeof(visplane_bit)); // todo could do this with dma + for(uint i=0;iframebuffer->header.max = count_of(vpatchlists->framebuffer); + vpatchlists->overlays[0]->header.max = vpatchlists->overlays[1]->header.max = count_of(vpatchlists->overlays[0]); +#if USE_CORE1_FOR_FLATS + sem_init(&core1_do_flats, 0, 1); +#endif +#if USE_CORE1_FOR_REGULAR + sem_init(&core1_do_regular, 0, 1); +#endif + memset(patch_hash_offsets, -1, sizeof(patch_hash_offsets)); + patch_decoder_circular_buf_write_limit = PATCH_DECODER_CIRCULAR_BUFFER_SIZE; +} + +void pd_add_span() { +} + +#define FORCE_ISCALE 1 + +void pd_add_column(pd_column_type type) { + // --- VALIDATION AND CLAMPING + int count = dc_yh - dc_yl; + if (count < 0) + return; + + fixed_t iscale; +#if FORCE_ISCALE + if (true || type != PDCOL_SKY) { + iscale = hw_divider_u32_quotient_inlined(0xffffffff, pd_scale); + } else { + iscale = 0x7fffffff; + } +#else + iscale = dc_iscale; +#endif + // todo record these + if (iscale < DDA_MIN) { + I_Error("dc_iscale %d\n", iscale); + } + if (iscale > DDA_MAX) { + iscale = DDA_MAX; + } + fixed_t texturemid = dc_texturemid; + if (texturemid < MINI) texturemid = MINI; + if (texturemid > MAXI) texturemid = MAXI; + // -------- + + // todo it would be nice to defer filling this in in case we are entirely clipped - let's get some stats on that + int rc_index = alloc_pd_column(dc_x); + if (rc_index < 0) return; + render_cols[rc_index].yl = dc_yl; + render_cols[rc_index].yh = dc_yh; + assert(render_cols[rc_index].yl >= 0 && render_cols[rc_index].yl < MAIN_VIEWHEIGHT && render_cols[rc_index].yh >= 0 && render_cols[rc_index].yh < MAIN_VIEWHEIGHT); + assert(!(pd_flag & 2)); // don't think this can happen + render_cols[rc_index].scale = pd_flag & 2 ? 0 : iscale; + render_cols[rc_index].colormap_index = dc_colormap_index; + render_cols[rc_index].next = -1; + render_cols[rc_index].texturemid = DOWN_SHIFT(texturemid); +#ifndef NDEBUG + #warning surpising off + // todo i wonder if we can fix this with a s small displacement? gather min/max +#if 0 + if (dc_iscale > 0x7fffff || dc_iscale < -0x800000) { + printf("pd_column fracs surprising iscale %08x\n", dc_iscale); + } + if (dc_texturemid > 0x1ffffff) { + printf("pd_column fracs surprising texturemid %08x\n", dc_texturemid); + } +#endif +#endif + + render_cols[rc_index].fd_num = dc_source.fd_num; + render_cols[rc_index].col_hi = TO_COL_HI(dc_source.col); + render_cols[rc_index].col_lo = TO_COL_LO(dc_source.col); +#if !PICO_ON_DEVICE + if (dc_source.real_id < 0) { + // patch + patches.insert(-dc_source.real_id); + } else { + assert(dc_source.real_id < numtextures); + textures.insert(dc_source.real_id); + } +#endif + push_down_x(dc_x, rc_index); + +#if SHOW_COLUMN_STATS + fixed_t frac; + fixed_t fracstep; + + auto &p = unique_columns[dc_source]; + column_count++; + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + // dest = ylookup[dc_yl] + columnofs[dc_x]; + + // Determine scaling, + // which is the only mapping to be done. + fracstep = dc_iscale; + fixed_t frac0 = frac = dc_texturemid + (dc_yl - centery) * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. + do { +#if SHOW_COSTLY_DATA_STATS + unique_column_source_pixels.insert(&dc_source[(frac >> FRACBITS) & 127]); +#endif + frac += fracstep; + } while (count--); + + if (frac < frac0) { + assert(false); + std::swap(frac, frac0); + } + + if (p.first == 0 && p.second == 0) { + p.first = frac0; + p.second = frac; + } else { + p.first = std::min(p.first, frac0); + p.second = std::max(p.second, frac); + } +#endif +} + +void pd_add_masked_columns(uint8_t *ys, int seg_count) { + // --- VALIDATION AND CLAMPING + fixed_t iscale; +#if FORCE_ISCALE + iscale = hw_divider_u32_quotient_inlined(0xffffffff, pd_scale); +#else + iscale = dc_iscale; +#endif + // todo record these + if (iscale < DDA_MIN) { + I_Error("dc_iscale %d\n", iscale); + } + if (iscale > DDA_MAX) { + iscale = DDA_MAX; + } + // -------- + + // todo it would be nice to defer filling this in in case we are entirely clipped - let's get some stats on that + int rc_index = alloc_pd_column(dc_x); + if (rc_index < 0) return; + render_cols[rc_index].yl = ys[0]; + render_cols[rc_index].yh = ys[1]; + assert(render_cols[rc_index].yl >= 0 && render_cols[rc_index].yl < MAIN_VIEWHEIGHT && render_cols[rc_index].yh >= 0 && render_cols[rc_index].yh < MAIN_VIEWHEIGHT); + + assert(ys[1] >= ys[0]); + render_cols[rc_index].scale = pd_flag & 2 ? 0 : iscale; + render_cols[rc_index].colormap_index = dc_colormap_index; +#if !FORCE_ISCALE + if (type != PDCOL_SKY) { + uint32_t foo = hw_divider_u32_quotient_inlined(0xffffffff, pd_scale); + if (foo != dc_iscale && foo != dc_iscale - 1 && foo != dc_iscale + 1) { + printf("BOO %d : %d %d\b", foo, pd_scale, dc_iscale); + } + } else { + printf("FLARK\n"); + } +#endif + // if (dc_yl < 0 || dc_yl > SCREENHEIGHT || dc_yh < 0 || dc_yh > SCREENHEIGHT) { + // I_Error("pants %d %d", dc_yl, dc_yh); + // } +#ifndef NDEBUG + if (dc_iscale > 0x7fffff || dc_iscale < -0x800000 || dc_texturemid > 0x1ffffff) { + printf("pd_column fracs surprising %08x %08x\n", dc_iscale, dc_texturemid); + } +#endif + + render_cols[rc_index].fd_num = dc_source.fd_num; + render_cols[rc_index].col_hi = TO_COL_HI(dc_source.col); + render_cols[rc_index].col_lo = TO_COL_LO(dc_source.col); +#if !PICO_ON_DEVICE + if (dc_source.real_id < 0) { + // patch + patches.insert(-dc_source.real_id); + } else { + assert(dc_source.real_id < numtextures); + textures.insert(dc_source.real_id); + } +#endif + fixed_t texturemid = dc_texturemid - (ys[2] << FRACBITS); + if (texturemid < MINI) texturemid = MINI; + if (texturemid > MAXI) texturemid = MAXI; + render_cols[rc_index].texturemid = DOWN_SHIFT(texturemid); + int first_index = rc_index; + for (int i = 1; i < seg_count; i++) { + int new_rc_index = alloc_pd_column(dc_x); + if (new_rc_index < 0) break; + render_cols[new_rc_index] = render_cols[rc_index]; + render_cols[rc_index].next = new_rc_index; + rc_index = new_rc_index; + assert(ys[i * 3 + 1] >= ys[i * 3]); + render_cols[rc_index].yl = ys[i * 3]; + render_cols[rc_index].yh = ys[i * 3 + 1]; + assert(render_cols[rc_index].yl >= 0 && render_cols[rc_index].yl < MAIN_VIEWHEIGHT && render_cols[rc_index].yh >= 0 && render_cols[rc_index].yh < MAIN_VIEWHEIGHT); + texturemid = dc_texturemid - (ys[i*3 + 2] << FRACBITS); + if (texturemid < MINI) texturemid = MINI; + if (texturemid > MAXI) texturemid = MAXI; + render_cols[rc_index].texturemid = DOWN_SHIFT(texturemid); + } + render_cols[rc_index].next = -1; + if (dc_colormap_index < 0) { + push_down_x_fuzzy(dc_x, first_index); + } else { + push_down_x(dc_x, first_index); + } + +#if SHOW_COLUMN_STATS + fixed_t frac; + fixed_t fracstep; + + auto &p = unique_columns[dc_source]; + column_count++; + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + // dest = ylookup[dc_yl] + columnofs[dc_x]; + + // Determine scaling, + // which is the only mapping to be done. + fracstep = dc_iscale; + fixed_t frac0 = frac = dc_texturemid + (dc_yl - centery) * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. + do { +#if SHOW_COSTLY_DATA_STATS + unique_column_source_pixels.insert(&dc_source[(frac >> FRACBITS) & 127]); +#endif + frac += fracstep; + } while (count--); + + if (frac < frac0) { + assert(false); + std::swap(frac, frac0); + } + + if (p.first == 0 && p.second == 0) { + p.first = frac0; + p.second = frac; + } else { + p.first = std::min(p.first, frac0); + p.second = std::max(p.second, frac); + } +#endif +} + +static int lightlevel(int visplane) { + int light = (visplanes[visplane].lightlevel >> LIGHTSEGSHIFT) + extralight; + if (light >= LIGHTLEVELS) + light = LIGHTLEVELS - 1; + if (light < 0) + light = 0; + return light; +} + +void pd_add_plane_column(int x, int yl, int yh, fixed_t scale, int floor, int fd_num) { + int rc_index = alloc_pd_column(x); + if (rc_index < 0) return; + int iscale = hw_divider_u32_quotient_inlined(0xffffffff, pd_scale); + if (yh < yl) { + return; + } + render_cols[rc_index].yl = yl; + render_cols[rc_index].yh = yh; + assert(render_cols[rc_index].yl >= 0 && render_cols[rc_index].yl < MAIN_VIEWHEIGHT && render_cols[rc_index].yh >= 0 && render_cols[rc_index].yh < MAIN_VIEWHEIGHT); + render_cols[rc_index].scale = pd_flag & 2 ? 0 : iscale; + render_cols[rc_index].colormap_index = dc_colormap_index; + render_cols[rc_index].texturemid = TEXTUREMID_PLANE; + render_cols[rc_index].fd_num = fd_num; + render_cols[rc_index].next = -1; + push_down_x(x, rc_index); +} + +static void reclip_fuzz_columns() { + // re-add the fuzzy columns so they are correctly clipped + for (int x = 0; x < SCREENWIDTH; x++) { + int16_t cur = fuzzy_column_heads[x]; + fuzzy_column_heads[x] = -1; + while (cur >= 0) { + int16_t next = render_cols[cur].next; + render_cols[cur].next = -1; + push_down_x_fuzzy(x, cur); + cur = next; + } + } +} + +static int16_t predraw_visplanes() { + int16_t free_list = -1; + for (int x = 0; x < SCREENWIDTH; x++) { + uint8_t *screen_col = render_frame_buffer + x; + uint8_t *visplane_bit_col = visplane_bit + x / 8; + uint8_t visplane_bit_bit = 1u << (x & 7u); + int16_t *last = &column_heads[x]; + int16_t i = *last; + while (i >= 0) { + auto &c = render_cols[i]; + uint8_t *p = screen_col + c.yl * SCREENWIDTH; + uint8_t color = c.plane; +// printf("%d: %d -> %d %02x %d\n", x, c.yl, c.yh, color, c.texturemid == TEXTUREMID_PLANE); + if (c.texturemid == TEXTUREMID_PLANE) { + uint8_t *vp = visplane_bit_col + c.yl * SCREENWIDTH / 8; + for (int y = c.yl; y <= c.yh; y++) { + *p = color; + p += SCREENWIDTH; + *vp |= visplane_bit_bit; + vp += SCREENWIDTH / 8; + } + *last = c.next; + // we replace c with two elements in the new free list of plane runs + flat_run *fr = (flat_run *) (&c); + i *= 2; + fr[0].next = (int16_t) (i + 1); + fr[1].next = free_list; + free_list = i; + i = *last; + } else { + last = &c.next; + i = c.next; + } + } + } + return free_list; +} + +static void re_sort_regular_columns_by_fd_num() { + memset(fd_heads, -1, sizeof(fd_heads)); + for (int x = 0; x < SCREENWIDTH; x++) { + int16_t i = column_heads[x]; + while (i >= 0) { + auto &c = render_cols[i]; + assert(c.texturemid != TEXTUREMID_PLANE); + int16_t fd_next = fd_heads[c.fd_num]; + // link is index with top bit set if x >= 256... note -1 would conflict with i == 0x7fff, x>=256 + // which we don't care about because i would never be that high + fd_heads[c.fd_num] = (x >> 8) ? (i | 0x8000) : i; + // loop over old list + i = c.next; + // replace fd_num with 8 low bits of x + c.x = x; + // and link remainder of old chain + c.next = fd_next; + } + } +} + +static void clip_columns(int yl, int yh) { + memset(fd_heads, -1, sizeof(fd_heads)); + // we will need to clip "not fully covered" columns too + not_fully_covered_yl = yl; + not_fully_covered_yh = yh; + for (int x = 0; x < SCREENWIDTH; x++) { + int16_t *prev = &column_heads[x]; + while (*prev >= 0) { + auto &c = render_cols[*prev]; + if (c.yh < yl || c.yl > yh) { + *prev = c.next; + } else { + if (c.yl < yl) c.yl = yl; + if (c.yh > yh) c.yh = yh; + prev = &c.next; + } + } + } +} + +static int translate_picnum(int picnum) { + if (whd_flattospecial[picnum] != 0xff) { + picnum = whd_specialtoflat[whd_flattranslation[whd_flattospecial[picnum]]]; + } + return picnum; +} + +static uint8_t *decode_flat_to_slot(int cache_slot, int picnum) { + uint8_t *flat_data = cached_flat0 - cache_slot * 4096; + DEBUG_PINS_SET(flat_decode, 1); + uint16_t *pos = flat_decoder_buf; + uint pos_size = count_of(flat_decoder_buf); + uint16_t *rp_decoder = flat_decoder_buf; + const uint32_t *sourcez = (const uint32_t *) W_CacheLumpNum(firstflat + picnum, PU_STATIC); + th_bit_input bi; +#if USE_XIPCPY + xip_copy_start(flat_sourcez, sourcez, (W_LumpLength(firstflat + picnum) + 3) / 4); +#define wait_for_input(amount) while (xip_copy_unfinished() && (bi.cur + amount) > ((uint8_t*)flat_sourcez) + xip_copy_progress() * 4) + th_bit_input_init(&bi, (const uint8_t *)flat_sourcez); +#else +#define wait_for_input(x) ((void)0) + th_bit_input_init(&bi, (const uint8_t *) sourcez); +#endif + wait_for_input(512); // guess + if (th_bit(&bi)) { + pos = th_read_simple_decoder(&bi, pos, pos_size, flat_decoder_tmp, count_of(flat_decoder_tmp)); + } else { + pos = read_raw_pixels_decoder(&bi, pos, pos_size, flat_decoder_tmp, count_of(flat_decoder_tmp)); + } + assert(pos < flat_decoder_buf + count_of(flat_decoder_buf)); + th_make_prefix_length_table(rp_decoder, flat_decoder_tmp); + wait_for_input(100000); + bool have_same = th_bit(&bi); + if (!have_same) { + uint8_t *p = flat_data; + for (int y = 0; y < 4096; y++) { + *p++ = th_decode_table_special(rp_decoder, flat_decoder_tmp, &bi); + } + } else { + for (int x = 0; x < 64; x++) { + uint8_t *p = &flat_data[x * 64]; + if (th_bit(&bi)) { + uint xf = th_read_bits(&bi, bitcount8(x)); + assert(xf < (uint) x); + uint32_t *a = (uint32_t *) p; + uint32_t *b = a + 16 * (int) (xf - x); + for (int i = 0; i < 16; i++) { + a[i] = b[i]; + } + } else { + for (int y = 0; y < 64; y++) { + *p++ = th_decode_table_special(rp_decoder, flat_decoder_tmp, &bi); + } + } + + } + } +// printf("Pass %d, caching slot %d pic (%d)\n", pass, cache_slot, picnum); + cached_flat_picnum[cache_slot] = picnum; + DEBUG_PINS_CLR(flat_decode, 1); + return flat_data; +} + +static void flush_visplanes(int8_t *flatnum_next, int numvisplanes) { +// printf("FRAME %d %d\n", pd_frame, numvisplanes); + angle_t angle = (viewangle + x_to_viewangle(0)) >> ANGLETOFINESHIFT; + fixed_t viewcosangle = finecosine(angle); + fixed_t viewsinangle = finesine(angle); +#if MERGE_DISTSCALE0_INTO_VIEWCOSSINANGLE + const fixed_t distscale0 = distscale(0); //0x00016a75; // todo i guess this is screen size based + viewcosangle = FixedMul(distscale0, viewcosangle); + viewsinangle = FixedMul(distscale0, viewsinangle); +#endif + // two passes; first pass we try to reuse flats we have decoded + int cache_slot = 0; + for(int pass=0;pass<2;pass++) { + for (int i = 0; i < numvisplanes; i++) { + int picnum = translate_picnum(visplanes[i].picnum); + bool any = false; + // just realized our list of visplanes includes those that may have been fully clipped away, so duh.. we need + // to skip such things (this also includes the sky flat it turns out which shows up in visplanes) + for(int vpcheck = flatnum_first[picnum]; vpcheck != -1; vpcheck = flatnum_next[vpcheck]) { + if (visplane_heads[vpcheck] != -1) { + any = true; + break; + } + } + if (any) { +#if PICO_ON_DEVICE + restart_song_state |= 1; // we may not restart a song during this call because it may blow the stack + SafeUpdateSound(); + restart_song_state &= ~1; + interp_init(); +#endif +#if 0 + source = (const uint8_t *) W_CacheLumpNum(firstflat + picnum, PU_STATIC); +#else + uint8_t *flat_data = nullptr; + if (!pass) { + for(cache_slot=0;cache_slot= cached_flat_slots) { + cache_slot = 0; + } + } + if (!flat_data) { + flat_data = decode_flat_to_slot(cache_slot, picnum); + } +#endif + DEBUG_PINS_SET(render_flat, 2); + DEBUG_PINS_SET(render_thing, 2); + int8_t vp = flatnum_first[picnum]; + flatnum_first[picnum] = -1; +#if USE_INTERP + span_interp->base[2] = (uintptr_t) flat_data;//0x20020000;//(uintptr_t)W_CacheLumpNum(firstflat + pl->picnum, PU_STATIC); +#endif + do { + visplane_t *pl = &visplanes[vp]; + + int last_y = -1; + fixed_t rel_height = abs(pl->height - viewz); + int startmap = ((LIGHTLEVELS - 1 - lightlevel(vp)) * 2) * NUMCOLORMAPS / LIGHTLEVELS; +// int last_x_end = 0; + + const lighttable_t *colormap; + fixed_t xstep; + fixed_t ystep; + fixed_t xfrac; + fixed_t yfrac; +#if !USE_INTERP + uint32_t step; + uint32_t position; +#endif + for (int16_t fr = visplane_heads[vp]; fr != -1; fr = flat_runs[fr].next) { + int delta; + if (flat_runs[fr].y != last_y) { + // abs rel height? + // todo get rid of yslope? + fixed_t distance = FastFixedMul(rel_height, yslope[flat_runs[fr].y]); + xstep = FastFixedMul(distance, basexscale); + ystep = FastFixedMul(distance, baseyscale); + // mved into viewcosangle/sinangle +#if !MERGE_DISTSCALE0_INTO_VIEWCOSSINANGLE + const fixed_t distscale0 = 0x00016a75; // todo i guess this is screen size based + fixed_t length = FastFixedMul(distance, distscale0); + xfrac = viewx + FastFixedMul(viewcosangle, length); + yfrac = -viewy - FastFixedMul(viewsinangle, length); +#else + xfrac = viewx + FastFixedMul(viewcosangle, distance); + yfrac = -viewy - FastFixedMul(viewsinangle, distance); +#endif + int8_t colormap_index; + if (fixedcolormap) { + colormap_index = fixedcolormap; + } else { + unsigned index = distance >> LIGHTZSHIFT; +#if !NO_USE_ZLIGHT + if (index >= MAXLIGHTZ) + index = MAXLIGHTZ - 1; + const int8_t *planezlight = &grs.zlight[pl->lightlevel * MAXLIGHTZ]; + colormap_index = planezlight[index]; +#else + // NOTE: we assume we have no IRQs on this core using the divider + fixed_t scale = hw_divider_s32_quotient_inlined((SCREENWIDTH / 4), (index + 1)); + //fixed_t scale = (SCREENWIDTH / 4) / (index + 1); + int level = startmap - scale; + + if (level < 0) + level = 0; + + if (level >= NUMCOLORMAPS) + level = NUMCOLORMAPS - 1; + colormap_index = level; +#endif + } + colormap = xcolormaps + colormap_index * 256; + delta = flat_runs[fr].x_start; + last_y = flat_runs[fr].y; + } else { + // we are in reverse x order + delta = flat_runs[fr].x_start;// - last_x_end; + } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + +#if USE_INTERP + uint32_t position = ((yfrac << 10) & 0xffff0000) + | ((xfrac >> 6) & 0x0000ffff); + uint32_t step = ((ystep << 10) & 0xffff0000) + | ((xstep >> 6) & 0x0000ffff); + span_interp->accum[0] = position; + span_interp->base[0] = step; +#else + position = ((yfrac << 10) & 0xffff0000) + | ((xfrac >> 6) & 0x0000ffff); + step = ((ystep << 10) & 0xffff0000) + | ((xstep >> 6) & 0x0000ffff); +#endif +#pragma GCC diagnostic pop +#if USE_INTERP + span_interp->add_raw[0] = delta * span_interp->base[0]; +#else + position += delta * step; +#endif + // printf("partial\n"); + uint8_t *p = render_frame_buffer + flat_runs[fr].y * SCREENWIDTH + flat_runs[fr].x_start; + uint8_t *p_end = p + flat_runs[fr].x_end - flat_runs[fr].x_start; + while (p < p_end) { +#if USE_INTERP + const uint8_t *texel = (const uint8_t *) span_interp->pop[2]; +#else + // Calculate current texture index in u,v. + uint32_t xtemp = (position >> 4) & 0x0fc0; + uint32_t ytemp = (position >> 26); + uint32_t spot = xtemp | ytemp; + position += step; + const uint8_t *texel = &flat_data[spot]; +#endif +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + *p++ = colormap[*texel]; +#pragma GCC diagnostic pop + } +// last_x_end = flat_runs[fr].x_end; + } + vp = flatnum_next[vp]; + } while (vp != -1); + DEBUG_PINS_CLR(render_thing, 2); + DEBUG_PINS_CLR(render_flat, 2); + } + } + } +} + +static void draw_visplanes(int16_t fr_list) { + if (!lastvisplane) return; + int numvisplanes = lastvisplane - visplanes; + + memset(visplane_heads, -1, numvisplanes * sizeof(visplane_heads[0])); + memset(flatnum_first, -1, sizeof(flatnum_first)); + memset(flatnum_next, -1, numvisplanes * sizeof(flatnum_next[0])); + for (int i = 0; i < numvisplanes; i++) { + static_assert(sizeof(visplanes[i].picnum) == 1, ""); + uint8_t picnum = translate_picnum(visplanes[i].picnum); + flatnum_next[i] = flatnum_first[picnum]; + flatnum_first[picnum] = (int8_t) i; + } +#if USE_INTERP + // - visplanes - +// todo only necessary on audio core + interp_config c = interp_default_config(); + interp_config_set_add_raw(&c, true); + interp_config_set_shift(&c, 4); + interp_config_set_mask(&c, 6, 11); + interp_set_config(span_interp, 0, &c); + + c = interp_default_config(); + interp_config_set_cross_input(&c, true); + interp_config_set_shift(&c, 26); + interp_config_set_mask(&c, 0, 5); + interp_set_config(span_interp, 1, &c); +#endif + + // first sort by visplane + int16_t fr_pos = fr_list; + for (int y = 0; y < viewheight; y++) { + uint8_t *vp = &visplane_bit[y * (SCREENWIDTH / 8)]; + uint8_t *vp_end = vp + SCREENWIDTH / 8; + uint bit = 0; + uint8_t *p0 = &render_frame_buffer[y * SCREENWIDTH]; + uint8_t *p = p0; + while (vp != vp_end) { +// printf("y %d, x %d + %d = %02x\n", y, (int)(vp- &visplane_bit[y * (SCREENWIDTH / 8)]), bit, *vp); + assert(bit < 8); + assert(0 == (*vp & ((1u << (bit)) - 1))); + if (!*vp) { + vp++; + p += 8 - bit; + bit = 0; + continue; + } + // new span starts within the 8 pixels + while (!(*vp & (1u << bit))) bit++, p++; // todo ctz table? +// printf("skipped ot y %d, x %d + %d plane=%d\n", y, (int)(vp- &visplane_bit[y * (SCREENWIDTH / 8)]), bit, *p); + assert(bit < 8); + assert(*p != 255); + uint x_start = p - p0; + uint8_t plane_num = *p; +// if (plane_num >= numvisplanes) { +// printf("WAHAHF x=%d y=%d %d %d *vp %02x bit %d %p\n", x_start, y, plane_num, numvisplanes, *vp, bit, p); +// } + assert(plane_num < numvisplanes); + while (bit < 8 && *p == plane_num && *vp & (1u << bit)) { + p++; + bit++; + } + if (bit == 8) { + vp++; + bit = 0; + if (*vp == 255 && *p == plane_num) { + uint fourx = plane_num * 0x1010101; + assert(!(((uintptr_t) p) & 0x3)); + while (vp < vp_end && *vp == 255 && ((uint32_t *) p)[0] == fourx && ((uint32_t *) p)[1] == fourx) { + p += 8; + vp++; + } + } + if (vp < vp_end) { + while (bit < 8 && *p == plane_num && *vp & (1u << bit)) { + p++; + bit++; + } + assert(bit != 8); // should have been handled above by 8x case + *vp &= ~((1u << bit) - 1); + } + } else { + *vp &= ~((1u << bit) - 1); + } + if (fr_pos == -1) { + flush_visplanes(flatnum_next, numvisplanes); + memset(visplane_heads, -1, MAXVISPLANES * 2); + fr_pos = fr_list; + break; + } + auto &fr = flat_runs[fr_pos]; + fr.x_start = x_start; + fr.x_end = p - p0; + fr.y = y; + int16_t tmp = fr.next; + fr.next = visplane_heads[plane_num]; + visplane_heads[plane_num] = fr_pos; + fr_pos = tmp; + } + } + if (fr_pos != fr_list) { + flush_visplanes(flatnum_next, numvisplanes); + } +} + +static inline void col_render(uint8_t *dest, uint count, const uint8_t *source, fixed_t frac, fixed_t fracstep, const lighttable_t* colormap) { +#if 0 && PICO_ON_DEVICE + col_interp->accum[0] = xs->col.frac; + col_interp->base[0] = xs->col.fracstep; + col_interp->base[2] = (uintptr_t)xs->col.source; + + static_assert(BAND_HEIGHT == 8, ""); // needs code fixup + count = __fast_mul((BAND_HEIGHT - 1 - count), 14); // 12 is size of FULL_COL_PIXEL + 2 for add + uint32_t tmp; + __asm__ volatile ( + ".syntax unified\n" + + "add pc, %[r_count]\n" + "nop\n" // because adding 0 to pc above will jump 4 bytes, and this way we save an extra instruction in control flow for count -= 2 + + FULL_COL_PIXEL + "add %[r_dest], %[r_deststep]\n" + FULL_COL_PIXEL + "add %[r_dest], %[r_deststep]\n" + FULL_COL_PIXEL + "add %[r_dest], %[r_deststep]\n" + FULL_COL_PIXEL + "add %[r_dest], %[r_deststep]\n" + FULL_COL_PIXEL + "add %[r_dest], %[r_deststep]\n" + FULL_COL_PIXEL + "add %[r_dest], %[r_deststep]\n" + FULL_COL_PIXEL + "add %[r_dest], %[r_deststep]\n" + + : [ r_dest] "+l" (dest), + [ r_tmp] "=&l" (tmp) + : [ r_interp] "l" (col_interp), + [ r_colormap] "l" (colormap), + [ r_palette] "l" (palette), + [ POP_FULL_OFFSET] "n" (SIO_INTERP0_POP_FULL_OFFSET - SIO_INTERP0_ACCUM0_OFFSET), + [ r_deststep ]"r" (SCREENWIDTH * 2), + [r_count] "r" (count) + : + ); + xs->col.frac = col_interp->accum[0]; +#else + do { + *dest = colormap[source[(frac >> FRACBITS) & 127]]; + dest += SCREENWIDTH; + frac += fracstep; + } while (count--); +#endif +} + +struct patch_hash_entry_header { + uint16_t patch_num; + int16_t next; + uint16_t size:15; + uint16_t encoding:1; +}; + +struct patch_decode_info { + const patch_t *patch; + const uint16_t *col_offsets; + const uint16_t *decoder; + uint data_index; // offset in patch of start of data (col_offsets relative to this) + patch_hash_entry_header header; + uint16_t w; +}; + +#define PATCH_HASH_ENTRY_HEADER_HWORDS 3 +static_assert(sizeof(struct patch_hash_entry_header)==2 * PATCH_HASH_ENTRY_HEADER_HWORDS, ""); +static inline int patch_hash(int patch_num) { + // todo better hash + return patch_num & (PATCH_DECODER_HASH_SIZE - 1); +} + +// returns positive for existing slot, inverted for where to put +static inline int patch_offset_or_inverse_slot(int patch_num) { + int slot = patch_hash(patch_num); + int offset = patch_hash_offsets[slot]; + while (offset != -1) { + patch_hash_entry_header *header = (patch_hash_entry_header *)(patch_decoder_circular_buf + offset); + assert(slot == patch_hash(header->patch_num)); + if (header->patch_num == patch_num) return offset; + offset = header->next; + } + return ~slot; +} + +static void get_patch_decoder(int patch_num, patch_decode_info* pdis, int pdi_pos = 0, int pdi_count = 1) { + auto& pdi = pdis[pdi_pos]; + pdi.patch = (patch_t *) W_CacheLumpNum(patch_num, PU_CACHE); + bool simple_path = get_core_num(); + int offset_or_inverse_slot = patch_offset_or_inverse_slot(patch_num); + uint data_index = 3 + patch_has_extra(pdi.patch); + if (!simple_path && offset_or_inverse_slot >= 0) { + data_index += ((uint8_t *) pdi.patch)[data_index * 2]; // skip over decoder metadata + patch_hash_entry_header *header = (patch_hash_entry_header *)(patch_decoder_circular_buf + offset_or_inverse_slot); + assert(header->patch_num == patch_num); + pdi.decoder = patch_decoder_circular_buf + offset_or_inverse_slot + PATCH_HASH_ENTRY_HEADER_HWORDS; + pdi.header = *header; +#if DECODER_DECODER_BUFFERS + printf("found patch=%d at offset %d\n", patch_num, offset_or_inverse_slot); +#endif + } else { + DEBUG_PINS_SET(patch_decode, 1); + int space_needed = patch_decoder_size_needed(pdi.patch) + PATCH_HASH_ENTRY_HEADER_HWORDS; +#if DEBUG_DECODER_BUFFERS + printf("Need slot of size %d\n", space_needed); +#endif + uint16_t *pos; + patch_hash_entry_header *header; + if (simple_path) { + pos = flat_decoder_buf; + header = (patch_hash_entry_header*)pos; + } else { + while (patch_decoder_circular_buf_write_pos >= + patch_decoder_circular_buf_write_limit - space_needed) { + if (patch_decoder_circular_buf_write_limit == PATCH_DECODER_CIRCULAR_BUFFER_SIZE) { + // we have wrapped + patch_decoder_circular_buf_write_pos = patch_decoder_circular_buf_write_limit = 0; + } else { + // we need to advance + patch_hash_entry_header *header = (patch_hash_entry_header *) (patch_decoder_circular_buf + + patch_decoder_circular_buf_write_limit); + if (header->patch_num) { + // free the patch we now encounter +#if DEBUG_DECODER_BUFFERS + printf("Freeing slot (%d) at %08x->%08x\n", header->patch_num, patch_decoder_circular_buf_write_limit, patch_decoder_circular_buf_write_limit + header->size); +#endif + uint slot = patch_hash(header->patch_num); + int16_t *last = &patch_hash_offsets[slot]; + while (*last != -1 && *last != patch_decoder_circular_buf_write_limit) { + last = &((patch_hash_entry_header *) (patch_decoder_circular_buf + *last))->next; + } + assert(*last != -1); + *last = ((patch_hash_entry_header *) (patch_decoder_circular_buf + *last))->next; + for (int p = 0; p < pdi_count; p++) { + if (pdis[p].header.patch_num == header->patch_num) { + pdis[p].decoder = nullptr; + } + } + patch_decoder_circular_buf_write_limit += header->size; + } else { + // we've reached the end of what was there + patch_decoder_circular_buf_write_limit = PATCH_DECODER_CIRCULAR_BUFFER_SIZE; + } + } + } + pos = patch_decoder_circular_buf + patch_decoder_circular_buf_write_pos; + uint slot = ~offset_or_inverse_slot; +#if DEBUG_DECODER_BUFFERS + printf("Allocate slot %d (%d) at %08x limit %08x\n", slot, patch_num, patch_decoder_circular_buf_write_pos, patch_decoder_circular_buf_write_limit); +#endif + uint slot_offset = patch_decoder_circular_buf_write_pos; + header = (patch_hash_entry_header *)(patch_decoder_circular_buf + slot_offset); + assert( slot >=0 && slot < PATCH_DECODER_HASH_SIZE); + header->next = patch_hash_offsets[slot]; + patch_hash_offsets[slot] = slot_offset; + } + header->patch_num = patch_num; + pos += PATCH_HASH_ENTRY_HEADER_HWORDS; + pdi.decoder = pos; + const uint8_t *sourcez = pdi.patch + data_index * 2 + 1; + data_index += ((uint8_t *) pdi.patch)[data_index * 2]; // skip over decoder metadata + th_bit_input bi; + th_bit_input_init(&bi, (const uint8_t *) sourcez); + header->encoding = th_read_bits(&bi, 1); + // todo make this correct for each core +#define effective_decoder_buf_size sizeof(patch_decoder_tmp) + static_assert(effective_decoder_buf_size <= count_of(patch_decoder_tmp), ""); + //static_assert(effective_decoder_buf_size <= count_of(flat_decoder_tmp), ""); + uint8_t *decoder_tmp = simple_path ? flat_decoder_tmp : patch_decoder_tmp; + int decoder_tmp_use_estimate; // this is a very ugly hack, but saves us polluting the decoder stuff since none of the other users care + switch (header->encoding) { + case 0: + if (th_bit(&bi)) { + pos = th_read_simple_decoder(&bi, pos, space_needed, decoder_tmp, effective_decoder_buf_size); + } else { + pos = read_raw_pixels_decoder(&bi, pos, space_needed, decoder_tmp, effective_decoder_buf_size); + } + // pos-pdi.decoder_size (X) is equal to max_code_length * 2 + (count + 1) / 2 where count is elements used in decoder_tmp + // (count + 1) /2 < X + // count < 2X + // use_estimate = 2*count (as each element is 2 bytes) + // use_estimate < X + decoder_tmp_use_estimate = pos - pdi.decoder; + break; + case 1: + pos = read_raw_pixels_decoder_c3(&bi, pos, space_needed, decoder_tmp, effective_decoder_buf_size); + // pos-pdi.decoder_size (X) is equal to max_code_length * 2 + count where count is elements used in decoder_tmp + // count < X + // use_estimate = 3*count (as each element is 3 bytes) + // use_estimate < 3X + decoder_tmp_use_estimate = (pos - pdi.decoder) * 3; + break; + default: + assert(false); + } + assert(pos - pdi.decoder <= space_needed - PATCH_HASH_ENTRY_HEADER_HWORDS); + assert(pos <= patch_decoder_circular_buf + patch_decoder_circular_buf_write_limit); + if (!simple_path) { + // we may have trashed some of our decoder tmp tables + patch_decoder_tmp_table_patch_numbers[0] = 0; + if (decoder_tmp_use_estimate > 256) { + patch_decoder_tmp_table_patch_numbers[1] = 0; + if (decoder_tmp_use_estimate > 512) { + patch_decoder_tmp_table_patch_numbers[2] = 0; + if (decoder_tmp_use_estimate > 768) { + patch_decoder_tmp_table_patch_numbers[3] = 0; + } + } + } + assert(pos <= patch_decoder_circular_buf + PATCH_DECODER_CIRCULAR_BUFFER_SIZE - 1); + header->size = pos + PATCH_HASH_ENTRY_HEADER_HWORDS - pdi.decoder; +#if !PICO_ON_DEVICE + patch_decoder_size.record(header->size); +#endif + patch_decoder_circular_buf_write_pos += header->size; + patch_decoder_circular_buf[patch_decoder_circular_buf_write_pos] = 0; // we need a zero patch number to follow + } + pdi.header = *header; + DEBUG_PINS_CLR(patch_decode, 1); + } + pdi.col_offsets = &((uint16_t*)pdi.patch)[data_index]; + uint w = patch_width(pdi.patch); + assert(w > 0 && w <= 320); + pdi.data_index = (data_index + w) * 2 + 2; // + 2 because we have one extra col_data offset + pdi.w = (uint16_t) w; +} + +const uint8_t *get_patch_decoder_table(uint patch_num, const uint16_t *decoder) { + if (get_core_num()) { + th_make_prefix_length_table(decoder, + flat_decoder_tmp); // the table is large and quick to generate, so we don't cache + return flat_decoder_tmp; + } else { + for (int i = 0; i < WHD_MAX_COL_UNIQUE_PATCHES; i++) { + if (patch_num == patch_decoder_tmp_table_patch_numbers[i]) return patch_decoder_tmp + i * 256; + } + th_make_prefix_length_table(decoder, + patch_decoder_tmp); // the table is large and quick to generate, so we don't cache + patch_decoder_tmp_table_patch_numbers[0] = patch_num; + return patch_decoder_tmp; + } +} + +const uint8_t *get_patch_decoder_table(uint patch_num, const uint16_t *decoder, int pos) { + if (patch_num == patch_decoder_tmp_table_patch_numbers[pos]) return patch_decoder_tmp + pos * 256; +#if DEBUG_DECODER + printf("Get decoder %d table pos %d\n", patch_num, pos); +#endif + th_make_prefix_length_table(decoder, patch_decoder_tmp + pos * 256); // the table is large and quick to generate, so we don't cache + patch_decoder_tmp_table_patch_numbers[pos] = patch_num; + return patch_decoder_tmp + pos * 256; +} + +static void draw_patch_columns(int patch_num, int patch_head, int16_t *col_heads, uint8_t *col_height, int translated) { + // fix up the sky scale (we had to preserve the original scale for column clipping/sorting) + // note: we do this as a rare edge case here, rather than checking in loops + if (patch_num == skytexture_patch) { + for(int j=patch_head; j != -1;) { + auto &c = render_cols[j & 0x7fffu]; + c.scale = 0x10000; + j = c.next; + } + } + patch_decode_info pdi; + get_patch_decoder(patch_num, &pdi); + assert(pdi.w <= WHD_PATCH_MAX_WIDTH); + // moved these to the caller because not enough stack on core 1, core 0 can use a fixed 256 size since it seems to have enough stack (it isn't calling audio from here) +// int16_t col_heads[pdi.w]; +// uint8_t col_height[pdi.w]; + memset(col_heads, -1, pdi.w * 2); + int i = patch_head; + assert(i != -1); + int h = patch_height(pdi.patch); + do { + auto &c = render_cols[i & 0x7fffu]; + uint col = FROM_COL_HI_LO(c.col_hi, c.col_lo); + assert(col < pdi.w); + int16_t tmp = col_heads[col]; + col_heads[col] = (int16_t)i; + i = c.next; + c.next = tmp; + + // we actually don't know how many pixels are in the column without looking at the run data, however + // we can see the last pixel we've used, which means, as a bonus, we won't decode all of a column when we only + // use the top + fixed_t texturemid = UP_SHIFT(c.texturemid); + fixed_t fracstep = DDA_UP_SHIFT(c.scale); + if (!fracstep) fracstep = 0x10000; + fixed_t start = (texturemid + (c.yl - centery) * fracstep) >> FRACBITS; + fixed_t end = (texturemid + (c.yh - centery) * fracstep) >> FRACBITS; + if (start < -1) { +// printf("Awooga\n"); + end = 128; // todo gate on texture only (it should not happen otherwise) + } +// printf("tm %08x fracstep %08x yl %d yh %d centery %d flan %08x end %d\n", texturemid, fracstep, c.yl, c.yh, centery, (texturemid + (c.yh - centery) * fracstep), end); + if (end < 0) end = 0; +// assert(end >= 0 && end < 256); + if (end >= h) end = h-1; + if (tmp == -1 || end > col_height[col]) col_height[col] = (uint8_t)end; + } while (i != -1); + + const uint16_t *col_offsets = pdi.col_offsets; + const uint8_t *patch_decoder_table = get_patch_decoder_table(patch_num, pdi.decoder); + for(int col = 0; col < pdi.w; col++) { + i = col_heads[col]; + if (!(col & 63) && get_core_num()) { + restart_song_state |= 1; // we may not restart a song during this call because it may blow the stack + SafeUpdateSound(); + restart_song_state &= ~1; + } + if (i != -1) { + uint16_t col_offset = col_offsets[col]; + if (0xff == (col_offset >> 8)) { + assert((col_offset&0xff)=0); + assert(1 == p >> 8); + p &= 0xff; + assert(p<7); + pixels[j] = pixels[prev] + p - 3; + } + } + } +#if USE_PICO_NET + // bit of a waste of time mostly. would be cheaper to change the decoder tables, but then again + // that is a lot of dealing with polluting caches, so unless this causes marked slowdown, go with this + if (translated) { + int base = 0x80 - translated * 0x20; + for (int j = 0; j <= col_height[col]; j++) { + if ((pixels[j] >> 4) == 7) { + pixels[j] = base + (pixels[j]&0xf); + } + } + } +#endif + if (h < 127) { + pixels[127] = pixels[0]; + pixels[h] = pixels[h-1]; + } + + if (fixedcolormap || patch_num == skytexture_patch) { +#if NO_USE_DC_COLORMAP + should_be_const lighttable_t *dc_colormap = colormaps + 256 * (patch_num == 1203 ? 0 : fixedcolormap); +#endif + do { + const auto &c = render_cols[i & 0x7fffu]; + uint8_t *p = render_frame_buffer + __mul_instruction(c.yl, SCREENWIDTH) + c.x + + ((i & 0x8000u) >> 7u); + assert (c.texturemid != TEXTUREMID_PLANE); + fixed_t fracstep = DDA_UP_SHIFT(c.scale); + if (!fracstep) fracstep = 0x10000; + fixed_t frac = UP_SHIFT(c.texturemid) + (c.yl - centery) * fracstep; + col_render(p, c.yh - c.yl, pixels, frac, fracstep, dc_colormap); + i = c.next; + } while (i != -1); + } else { + do { + const auto &c = render_cols[i & 0x7fffu]; + uint8_t *p = render_frame_buffer + __mul_instruction(c.yl, SCREENWIDTH) + c.x + + ((i & 0x8000u) >> 7u); +#if NO_USE_DC_COLORMAP + should_be_const lighttable_t *dc_colormap = colormaps + 256 * c.colormap_index; +#endif + assert (c.texturemid != TEXTUREMID_PLANE); + fixed_t fracstep = DDA_UP_SHIFT(c.scale); + if (!fracstep) fracstep = 0x10000; + fixed_t frac = UP_SHIFT(c.texturemid) + (c.yl - centery) * fracstep; +// if (get_core_num()) { +// for(int yy=c.yl;yy<=c.yh;yy++) { +// p[0] = 0xfc; +// p += SCREENWIDTH; +// } +// } else { +// for(int yy=c.yl;yy<=c.yh;yy++) { +// p[0] = 0x5; +// p += SCREENWIDTH; +// } +// } + col_render(p, c.yh - c.yl, pixels, frac, fracstep, dc_colormap); + i = c.next; + } while (i != -1); + } + } + } +} + +static void draw_composite_columns(int texture_num, int tex_head) { + uint w = texture_width(texture_num); + int16_t col_heads[w]; + memset(col_heads, -1, sizeof(col_heads)); + int i = tex_head; + assert(i != -1); + // todo not sure this is beneficial + int min = 128; + int max = 0; +// printf("DCC tex=%d\n", texture_num); + do { + auto &c = render_cols[i & 0x7fffu]; + uint col = FROM_COL_HI_LO(c.col_hi, c.col_lo); + assert(col < w); + int16_t tmp = col_heads[col]; + col_heads[col] = (int16_t)i; + i = c.next; + c.next = tmp; + + // we actaully don't know how many pixels are in the column without looking at the run data, however + // we can see the last pixel we've used, which means, as a bonus, we won't decode all of a column when we only + // use the top + fixed_t texturemid = UP_SHIFT(c.texturemid); + fixed_t fracstep = DDA_UP_SHIFT(c.scale); + if (!fracstep) fracstep = 0x10000; + fixed_t start = (texturemid + (c.yl - centery) * fracstep) >> FRACBITS; + fixed_t end = (texturemid + (c.yh - centery) * fracstep) >> FRACBITS; + if (start < -1 || end > 128) { + min = 0; + max = 128; + } else { + min = std::min(min, start); + max = std::max(max, end); + } + } while (i != -1); + +#if DEBUG_COMPOSITE + printf("Texture %d h=%d, %d->%d\n", texture_num, texture_height(texture_num)>>FRACBITS, min, max); +#endif + int pc = whd_textures[texture_num].patch_count; + assert(pc); + uint8_t *patch_table = &((uint8_t *)whd_textures)[whd_textures[texture_num].metdata_offset]; + uint8_t *metadata = patch_table + pc * 2; + // skip over the non composite column metadata (always pretty small) + uint xx=0; + while (xx < w) { + uint b = *metadata++; + xx += (b&0x7f)+1; + if (b&0x80) metadata+=2; + } + patch_decode_info pdis[WHD_MAX_COL_UNIQUE_PATCHES]; + for(uint p=0;p%d skip %d\n", texture_num, base, limit, col == limit); + #endif + if (col != limit) { + struct { + uint8_t y; + uint8_t count; + uint8_t pdi_index; // 0xff for memcpy + uint8_t col; + uint8_t src_offset; // for memcpy this is the source + } runs[WHD_MAX_COL_SEGS]; + int run_count = 0; + int y = 0; + uint used_this_time = 0; + do { + int local_patch = metadata[0]; + int m1 = metadata[1]; + if (local_patch & WHD_COL_SEG_EXPLICIT_Y) { + y = metadata[2]; + metadata++; + } + int length = 1 + (m1 & 0x7f); + if (local_patch & WHD_COL_SEG_MEMCPY) { + // note y < max, because we only copy from above, and indeed having y > max in non local_patch & 0x80 causes us to not know how many pixels to draw + if (y <= max && (y + length > min || (local_patch & WHD_COL_SEG_MEMCPY_SOURCE))) { // 0x20 means used for memcpy + assert(run_count < WHD_MAX_COL_SEGS); + runs[run_count].pdi_index = local_patch & WHD_COL_SEG_MEMCPY_IS_BACKWARDS ? 0xff : 0xfe; + // todo we could clip more, but barely seems worth it + runs[run_count].y = y; + runs[run_count].src_offset = metadata[2]; + runs[run_count].count = std::min(1 + max - y, length); +#if DEBUG_COMPOSITE + printf(" memcpy %d <- %d + %d\n", y, runs[run_count].src_offset, length); +#endif + run_count++; + } + metadata += 3; + } else { + // note y < max, because we only copy from above, and indeed having y > max in non local_patch & 0x80 causes us to not know how many pixels to draw + if (y <= max && (y + length > min || (local_patch & 0x20))) { // 0x20 means used for memcpy + // todo bitfields here + // 0qzy xxxx include y start : (if y ) (if z ) + // q means needed for memcpy + // 1yyy yyyy memcpy : (y is start y) + // 1111 1111 single columns (ignore) _ eqqivalent to memcpy from 0x7f which would be silly + // todo local_patch_mapping? probably unnecessary as we don't often have the same patch in a column + local_patch &= 0xf; + int patch_num = patch_table[local_patch * 2] | (patch_table[local_patch * 2 + 1] << 8); + #if DEBUG_DECODER + printf(" looking for local patch %d (%d) offset %d,%d\n", local_patch, patch_num, (int8_t)metadata[2], (int8_t)metadata[3]); + #endif + uint p; + for (p = 0; p < count_of(pdis); p++) { + if (pdis[p].header.patch_num == patch_num) { + break; + } + } + if (p == count_of(pdis)) { + assert(used_this_time < 15); // simply for this crappy table + static const int lookup[15] = { + 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 1, 1, 0 + }; + p = lookup[used_this_time]; + #if DEBUG_DECODER + printf(" not found, inserting at index %d\n", p); + #endif + get_patch_decoder(patch_num, pdis, p, count_of(pdis)); + } else { + #if DEBUG_DECODER + printf(" found at index %d\n", p); + #endif + } + used_this_time |= (1u << p); + assert(run_count <= WHD_MAX_COL_SEGS); + runs[run_count].pdi_index = p; + // todo we could clip more, but barely seems worth it + runs[run_count].y = y; + runs[run_count].col = metadata[2] - base; + runs[run_count].src_offset = metadata[3]; + runs[run_count].count = std::min(1 + max - y, length); +#if DEBUG_COMPOSITE + printf(" patch %d, %d + %d\n", patch_num, y, length); +#endif + run_count++; + } + metadata += 4; + } + y += length; + if (m1 >= 128) break; + } while (true); + const uint8_t *decoder_tables[count_of(pdis)]; + for (uint p = 0; p < count_of(pdis); p++) { + if (used_this_time & (1u << p)) { + // unfortunate state... one of our (cached) decoders got deleted during the loop allocating new ones + // note we do this test here rather than in a separate loop above as it is rare, and it saves us another loop in the common case + if (!pdis[p].decoder) { + get_patch_decoder(pdis[p].header.patch_num, pdis, p, count_of(pdis)); + p = -1; continue; // start loop over (we may have freed something from the previous iteration + // note we assume that we can always actually fit the decoders for all count_of(pdis) (4) patches so this loop will terminate + } + decoder_tables[p] = get_patch_decoder_table(pdis[p].header.patch_num, pdis[p].decoder, p); + } + } + #if 1 + for (col = base; col < limit; col++) { + i = col_heads[col]; + if (i != -1) { + #if 1 + uint8_t pixels[129]; + for(int r=0;r= 0xfe) { + if (run.pdi_index == 0xfe) { + for (int yy = 0; yy < run.count; yy++) { + pixels[run.y + yy] = pixels[run.src_offset + yy]; + } +// memset(pixels+run.y, 0xfc, run.count); + } else { + for (int yy = run.count-1; yy >= 0; yy--) { + pixels[run.y + yy] = pixels[run.src_offset + yy]; + } +// memset(pixels+run.y, 0xfe, run.count); + } + } else { + const auto &pdi = pdis[run.pdi_index]; + const uint16_t *col_offsets = pdi.col_offsets; + const uint8_t *patch_decoder_table = decoder_tables[run.pdi_index]; + uint8_t pcol = col + run.col; + assert(pcol < patch_width(pdi.patch)); + uint16_t col_offset = col_offsets[pcol]; + if (0xff == (col_offset >> 8)) { + assert((col_offset & 0xff) < w); + col_offset = col_offsets[col_offset & 0xff]; + } + th_bit_input bi; + if (patch_byte_addressed(pdi.patch)) { + th_bit_input_init(&bi, pdi.patch + pdi.data_index + + col_offset); // todo read off end potential + } else { + th_bit_input_init_bit_offset(&bi, pdi.patch + pdi.data_index, + col_offset); // todo read off end potential + } + uint8_t *lp = pixels + run.y; + if (!pdi.header.encoding) { + for (int j = 0; j < run.src_offset; j++) { + th_decode_table_special(pdi.decoder, patch_decoder_table, &bi); + } + for (int j = 0; j < run.count; j++) { + lp[j] = th_decode_table_special(pdi.decoder, patch_decoder_table, &bi); + } + } else { + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + uint8_t prev_pixel; + #pragma GCC diagnostic pop + for (int j = 0; j < run.src_offset; j++) { + // uint16_t p = th_decode_16(rp_decoder, &bi); + uint16_t p = th_decode_table_special_16(pdi.decoder, patch_decoder_table, &bi); + if (p < 256) { + prev_pixel = p; + } else { + p &= 0xff; + assert(p < 7); + prev_pixel = prev_pixel + p - 3; + } + } + for (int j = 0; j < run.count; j++) { + // uint16_t p = th_decode_16(rp_decoder, &bi); + uint16_t p = th_decode_table_special_16(pdi.decoder, patch_decoder_table, &bi); + if (p < 256) { + lp[j] = p; + } else { + p &= 0xff; + assert(p < 7); + lp[j] = prev_pixel + p - 3; + } + prev_pixel = lp[j]; + } + } + } + } + uint hh = texture_height(texture_num) >> FRACBITS; + if (hh != 128) { + pixels[127] = pixels[0]; + pixels[hh] = pixels[hh - 1]; + } + if (fixedcolormap) { + #if NO_USE_DC_COLORMAP + should_be_const lighttable_t *dc_colormap = colormaps + 256 * fixedcolormap; + #endif + do { + const auto &c = render_cols[i & 0x7fffu]; + uint8_t *p = render_frame_buffer + __mul_instruction(c.yl, SCREENWIDTH) + c.x + + ((i & 0x8000u) >> 7u); + assert (c.texturemid != TEXTUREMID_PLANE); + fixed_t fracstep = DDA_UP_SHIFT(c.scale); + if (!fracstep) fracstep = 0x10000; + fixed_t frac = UP_SHIFT(c.texturemid) + (c.yl - centery) * fracstep; + col_render(p, c.yh - c.yl, pixels, frac, fracstep, dc_colormap); + i = c.next; + } while (i != -1); + } else { + do { + const auto &c = render_cols[i & 0x7fffu]; + uint8_t *p = render_frame_buffer + __mul_instruction(c.yl, SCREENWIDTH) + c.x + + ((i & 0x8000u) >> 7u); + #if NO_USE_DC_COLORMAP + should_be_const lighttable_t *dc_colormap = colormaps + 256 * c.colormap_index; + #endif + assert (c.texturemid != TEXTUREMID_PLANE); + fixed_t fracstep = DDA_UP_SHIFT(c.scale); + if (!fracstep) fracstep = 0x10000; + fixed_t frac = UP_SHIFT(c.texturemid) + (c.yl - centery) * fracstep; + col_render(p, c.yh - c.yl, pixels, frac, fracstep, dc_colormap); + i = c.next; + } while (i != -1); + } + #else + uint8_t color = texture_num & 0xff; + do { + const auto &c = render_cols[i & 0x7fffu]; + uint8_t *p = + render_frame_buffer + __mul_instruction(c.yl, SCREENWIDTH) + c.x + ((i & 0x8000u) >> 7u); + assert (c.texturemid != TEXTUREMID_PLANE); + #if NO_USE_DC_COLORMAP + should_be_const lighttable_t *dc_colormap = colormaps + 256 * c.colormap_index; + #endif + uint8_t lcolor = dc_colormap[color]; + // fixed_t texturemid = UP_SHIFT(c.texturemid); + // fixed_t fracstep = DDA_UP_SHIFT(c.scale); + // fixed_t start = (texturemid + (c.yl - centery) * fracstep) >> FRACBITS; + // fixed_t end = (texturemid + (c.yh - centery) * fracstep) >> FRACBITS; + // if (end > 128) lcolor = 0xfc; + for (int y = 0; y <= c.yh - c.yl; y++) { + *p = lcolor; + p += SCREENWIDTH; + } + i = c.next; + } while (i != -1); + + #endif + + } + } + #endif + } else { +#if DEBUG_COMPOSITE + printf(" no cols though\n"); +#endif + int last; + do { + last = metadata[1] & 0x80; + bool has_y = metadata[0] & WHD_COL_SEG_EXPLICIT_Y; + if (metadata[0] & 0x80) { + metadata += 3 + has_y; + } else { + metadata += 4 + has_y; + } + } while (!last); + } + } else { +#if DEBUG_COMPOSITE + printf("single patch, limit %d\n", limit); +#endif + metadata += 2; + } + base = limit; + } +} + +// noinline as it uses alloca +static void __noinline draw_regular_columns(int core) { + if (!core) { + // on core 0 draw the textures first + for(int fd_num=0; fd_num < num_framedrawables; fd_num++) { + int i = fd_heads[fd_num]; + if (i != -1 && framedrawables[fd_num].real_id > 0) { + DEBUG_PINS_SET(render_thing, 1<= WHD_PATCH_MAX_WIDTH, ""); + // visplane_bit is no longer used on core 1 as we've already drawn + buffer = visplane_bit; + } else { + // on core 0 we can use the stack + buffer = (uint8_t *)__builtin_alloca(WHD_PATCH_MAX_WIDTH * 3); + } + for(int fd_num=0; fd_num < num_framedrawables; fd_num++) { + int i = fd_heads[fd_num]; + if (i != -1) { + uint32_t save = spin_lock_blocking(lock); + int id = framedrawables[fd_num].real_id; + if (id < 0) { + framedrawables[fd_num].real_id = 0; // mark as done + } + spin_unlock(lock, save); + if (id < 0) { + DEBUG_PINS_SET(render_thing, 1<= 0) { + const auto &c = render_cols[i]; + uint8_t *screen_col = render_frame_buffer + x; + int yl = c.yl; + int yh = c.yh; + assert(yl <= MAIN_VIEWHEIGHT); + assert(yh <= MAIN_VIEWHEIGHT); + if (yl == 0) yl = 1; + if (yh >= MAIN_VIEWHEIGHT - 1) yh = MAIN_VIEWHEIGHT - 2; + + if (yl <= yh) { + uint8_t *p = screen_col + yl * SCREENWIDTH; + for (int y = 0; y <= yh - yl; y++) { + *p = darken_map[p[fuzzoffset[fuzzpos]]]; + + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) + fuzzpos = 0; + p += SCREENWIDTH; + } + } + i = c.next; + } + } +} + +static void draw_splash(int patch_num, int top, int bottom, uint8_t *dest, int single_col = -1) { + patch_decode_info pdi; + get_patch_decoder(patch_num, &pdi); + int w = patch_width(pdi.patch); + int h = patch_height(pdi.patch); + assert(w == 320 && h == 200); + const uint16_t *col_offsets = pdi.col_offsets; + const uint8_t *patch_decoder_table = get_patch_decoder_table(patch_num, pdi.decoder); + uint32_t delta = (bottom - top) * SCREENWIDTH - 1; + int col; + int stride; + if (single_col < 0) { + col = 0; + stride = SCREENWIDTH; + } else { + // hack to draw only one column + col = single_col; + w = col + 1; + stride = 1; + } + for (; col < w; col++) { + uint16_t col_offset = col_offsets[col]; + if (0xff == (col_offset >> 8)) { + assert((col_offset & 0xff) < pdi.w); + col_offset = col_offsets[col_offset & 0xff]; + } + th_bit_input bi; + if (patch_byte_addressed(pdi.patch)) { + th_bit_input_init(&bi, pdi.patch + pdi.data_index + col_offset); // todo read off end potential + } else { + th_bit_input_init_bit_offset(&bi, pdi.patch + pdi.data_index, col_offset); // todo read off end potential + } + if (!pdi.header.encoding) { + assert(false); // we don't have this +// for (int j = 0; j <= h; j++) { +// pixels[j] = th_decode_table_special(pdi.decoder, patch_decoder_table, &bi); +// } + } else { + int y; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + uint8_t prev_pixel; +#pragma GCC diagnostic pop + for (y = 0; y < top; y++) { + uint16_t p = th_decode_table_special_16(pdi.decoder, patch_decoder_table, &bi); + if (p < 256) { + prev_pixel = p; + } else { + p &= 0xff; + assert(p < 7); + prev_pixel = prev_pixel + p - 3; + } + } + for (; y < bottom; y++) { + uint16_t p = th_decode_table_special_16(pdi.decoder, patch_decoder_table, &bi); + if (p < 256) { + *dest = p; + } else { + p &= 0xff; + assert(p < 7); + *dest = prev_pixel + p - 3; + } + prev_pixel = *dest; + dest += stride; + } + dest -= delta; + } + } +} + +extern "C" int M_Random(); + +void maybe_draw_single_screen(int patch_num) { + if (sub_gamestate == 0) { + next_video_type = VIDEO_TYPE_SINGLE; + draw_splash(patch_num, 0, MAIN_VIEWHEIGHT, frame_buffer[render_frame_index]); + sub_gamestate = 1; + } else if (sub_gamestate == 1) { + draw_splash(patch_num, MAIN_VIEWHEIGHT, SCREENHEIGHT, + frame_buffer[render_frame_index^1] + (MAIN_VIEWHEIGHT - 32) * SCREENWIDTH); + sub_gamestate = 2; + } +} + +void draw_stbar_on_framebuffer(int frame, boolean refresh) { + V_BeginPatchList(vpatchlists->framebuffer); + // we call ST_drawwidgets directly as we don't want to mess with palette stuff (we call this during startup when not initialized) +// ST_Drawer(false, refresh); + ST_drawWidgets(refresh); + // draw the status bar onto the bottom (now non visible part of the top buffer) + I_VideoBuffer = frame_buffer[frame] - 32 * SCREENWIDTH; + V_RestoreBuffer(); + V_DrawPatchList(vpatchlists->framebuffer); + I_VideoBuffer = render_frame_buffer; +} + +static void draw_framebuffer_patches_fullscreen() { + V_RestoreBuffer(); + vpatch_clip_bottom = MAIN_VIEWHEIGHT; + V_DrawPatchList(vpatchlists->framebuffer); + I_VideoBuffer = frame_buffer[render_frame_index^1] - 32 * SCREENWIDTH; + V_RestoreBuffer(); + vpatch_clip_top = SCREENHEIGHT-32; + vpatch_clip_bottom = SCREENHEIGHT; + V_DrawPatchList(vpatchlists->framebuffer); + vpatch_clip_top = 0; + I_VideoBuffer = frame_buffer[render_frame_index]; +} + +void draw_fullscreen_background(int top, int bottom) { + assert(top < bottom); + assert((top < MAIN_VIEWHEIGHT && bottom <= MAIN_VIEWHEIGHT) || + (top >= MAIN_VIEWHEIGHT && bottom > MAIN_VIEWHEIGHT)); + int patch_num = 0; + byte *top_pixel = top < MAIN_VIEWHEIGHT ? render_frame_buffer + top * SCREENWIDTH : + frame_buffer[render_frame_index^1] + (top - 32) * SCREENWIDTH; + switch (gamestate) { + case GS_INTERMISSION: + patch_num = wi_background_patch_num; + break; + case GS_DEMOSCREEN: + patch_num = W_GetNumForName(pagename); + break; + case GS_FINALE: + if (finalestage == F_STAGE_TEXT) { + int picnum = W_GetNumForName(finaleflat); + if (picnum) { + int cache_slot; + uint8_t *flat_data = 0; + for (cache_slot = 0; cache_slot < cached_flat_slots; cache_slot++) { + if (cached_flat_picnum[cache_slot] == picnum) { + flat_data = cached_flat0 - cache_slot * 4096; + } + } + if (!flat_data) { + // just use slot 0 + assert(cached_flat_slots); + cache_slot = 0; + flat_data = decode_flat_to_slot(cache_slot, picnum - firstflat); // note this uses core1's data area, but it is not drawing flats at the moment + } + // todo is this rotated 90 degress + for (int y = top; y < bottom; y++) { + uint8_t *src = flat_data + (y & 63); + for(int i=0;iframebuffer); + WI_Drawer(); + if (top >= MAIN_VIEWHEIGHT) { + I_VideoBuffer = frame_buffer[render_frame_index ^ 1] - 32 * SCREENWIDTH; + } + V_RestoreBuffer(); + vpatch_clip_top = top; + vpatch_clip_bottom = bottom; + V_DrawPatchList(vpatchlists->framebuffer); + vpatch_clip_top = 0; + vpatch_clip_bottom = SCREENHEIGHT; + I_VideoBuffer = render_frame_buffer; + } +} + +static void uh_oh_discard_columns(int render_col_limit) { +// memset(render_frame_buffer, 0xfc, SCREENWIDTH * MAIN_VIEWHEIGHT); // not quite right in clip + for (int x = 0; x < SCREENWIDTH * 2; x++) { // >= SCREENWIDTH these are fuzzy columns + int16_t *last = &column_heads[x]; + int16_t i = *last; + while (i >= 0) { + auto &c = render_cols[i]; + if (i >= render_col_limit) { + // we have to throw it out, but we will draw something - black is as good as anything i guess + if (x < SCREENWIDTH) { + uint8_t *dest = render_frame_buffer + x + c.yl * SCREENWIDTH; + for(int y = c.yh -c.yl; y >= 0; y--, dest += SCREENWIDTH) { + *dest = 0; + } + } + *last = c.next; + i = *last; + } else { + last = &c.next; + i = c.next; + } + } + } +} +void pd_end_frame(int wipe_start) { + DEBUG_PINS_SET(start_end, 2); +#if !PICO_ON_DEVICE +// tex_count.record_print(textures.size()); +// patch_count.record_print(patches.size()); +// patch_decoder_size.print_summary(); +// patch_decoder_size.reset(); +#endif + // these were only clipped as they were inserted (so may be more obscured) + reclip_fuzz_columns(); +#if PICO_ON_DEVICE +// gpio_put(22, 1); + while (!sem_available(&display_frame_freed)) { + I_UpdateSound(); + } +// gpio_put(22, 0); +#endif + sem_acquire_blocking(&display_frame_freed); + bool showing_help = inhelpscreens; + static boolean was_in_help; + if (gamestate == GS_LEVEL) { + if (!wipestate && (!showing_help || !was_in_help)) render_frame_index ^= 1; + } else { + // we expect all the rendering code to be a no-op + assert(render_col_count == 0); + } + render_frame_buffer = frame_buffer[render_frame_index]; +#if 0 && !PICO_ON_DEVICE + printf("END FRAME %d %p ws %d cols %d\n", render_frame_index, render_frame_buffer, wipe_start, render_col_count); +#endif + + DEBUG_PINS_SET(full_render, 1); + + uint8_t *list_buffer_limit = list_buffer + count_of(list_buffer); + if (!inhelpscreens) { + if (was_in_help) { +// // todo graham, wtf this was a complete guess - why should this be necessary, and if so why +// // not for splash screens +// memset(patch_decoder_tmp_table_patch_numbers, 0, sizeof(patch_decoder_tmp_table_patch_numbers)); + next_video_type = gamestate == GS_LEVEL ? VIDEO_TYPE_DOUBLE : VIDEO_TYPE_SINGLE; + wipestate = WIPESTATE_NONE; + post_wipecount = 0; // this causes us to redraw intermission screens + sub_gamestate = 0; // and splash screens + } + switch (wipestate) { + case WIPESTATE_NONE: { + if (wipe_start) { + if (gamestate != GS_LEVEL) { + render_frame_index ^= 1; + render_frame_buffer = frame_buffer[render_frame_index]; + I_VideoBuffer = render_frame_buffer; + draw_fullscreen_background(0, MAIN_VIEWHEIGHT - 32); + } + if (next_video_type == VIDEO_TYPE_DOUBLE) { + // coming from level already, so draw statusbar + draw_stbar_on_framebuffer(render_frame_index, false); // argh it is the wrong status bar + } + clip_columns(0, MAIN_VIEWHEIGHT - 32 - + 1); // note this is a noop in non GS_LEVEL so don't bother to add if + next_video_type = VIDEO_TYPE_WIPE; + // steal space for our wipe data structures + memset(cached_flat_picnum, -1, sizeof(cached_flat_picnum)); + wipe_yoffsets_raw = (int16_t *) (list_buffer_limit - 4096); + wipe_yoffsets = list_buffer_limit - 4096 + SCREENWIDTH * 2; + + memset(wipe_yoffsets, 0, SCREENWIDTH); + wipe_yoffsets_raw[0] = -6;//-(M_Random() % 12); + for (int i = 1; i < SCREENWIDTH; i++) { + int r = (M_Random() % 3) - 1; + wipe_yoffsets_raw[i] = wipe_yoffsets_raw[i - 1] + r; + if (wipe_yoffsets_raw[i] > 0) wipe_yoffsets_raw[i] = 0; + else if (wipe_yoffsets_raw[i] == -12) wipe_yoffsets_raw[i] = -11; + } + wipe_linelookup = (uint32_t *) (wipe_yoffsets + SCREENWIDTH); + uint screen_front = render_frame_index ^ 1; // what was currently displayed + uint32_t base; +#if PICO_ON_DEVICE + base = (uintptr_t) &frame_buffer[0][0]; +#else + base = 0; +#endif + for (int i = 0; i < SCREENHEIGHT; i++) { + if (i < MAIN_VIEWHEIGHT) + wipe_linelookup[i] = base + screen_front * SCREENWIDTH * MAIN_VIEWHEIGHT + i * SCREENWIDTH; + else + wipe_linelookup[i] = + base + (screen_front ^ 1) * SCREENWIDTH * MAIN_VIEWHEIGHT + (i - 32) * SCREENWIDTH; + } + wipestate = WIPESTATE_SKIP1; + wipe_min = 0; + } + break; + } + case WIPESTATE_SKIP1: { + if (wipe_min > 32) wipestate = WIPESTATE_REDRAW1; + break; + } + case WIPESTATE_REDRAW1: { + // we need to render the bottom of the screen + clip_columns(MAIN_VIEWHEIGHT - 32, + MAIN_VIEWHEIGHT - 1); // note this is a noop in non GS_LEVEL so don't bother to add if + if (gamestate != GS_LEVEL) { + draw_fullscreen_background(MAIN_VIEWHEIGHT - 32, MAIN_VIEWHEIGHT); + } + wipestate = WIPESTATE_SKIP2; + break; + } + case WIPESTATE_SKIP2: { + if (wipe_min > 64) wipestate = WIPESTATE_REDRAW2; + break; + } + case WIPESTATE_REDRAW2: { + if (gamestate == GS_LEVEL) { + draw_stbar_on_framebuffer(render_frame_index ^ 1, true); + } else { + draw_fullscreen_background(MAIN_VIEWHEIGHT, SCREENHEIGHT); + } + wipestate = WIPESTATE_SKIP3; + break; + } + case WIPESTATE_SKIP3: { + // todo check we are on the right frame before exiting + if (wipe_min >= 200) { + next_video_type = gamestate == GS_LEVEL ? VIDEO_TYPE_DOUBLE : VIDEO_TYPE_SINGLE; + wipestate = WIPESTATE_NONE; + post_wipecount = 0; + } + break; + } + } + } + I_VideoBuffer = render_frame_buffer; + if (wipestate) list_buffer_limit -= 4096; + // we need to use the lower limit of this frame and the last since the final wipe frame may still be using the data + uint8_t *this_time_limit = std::min(list_buffer_limit, last_list_buffer_limit); + if (cached_flat0 != this_time_limit - 4096) { + // this should only happen coming in and out of wipe, so we trash all our flash slots + cached_flat0 = this_time_limit - 4096; + memset(cached_flat_picnum, -1, sizeof(cached_flat_picnum)); + } +// printf("CF0 %p ll %p ttl %p overall %p\n", cached_flat0, list_buffer_limit, this_time_limit, list_buffer + sizeof(list_buffer)); + last_list_buffer_limit = list_buffer_limit; + + int new_cache_flat_slots = 1 + ((int)(cached_flat0 - list_buffer - render_col_count * sizeof(pd_column))) / 4096; + if (new_cache_flat_slots < 1) { + // flat 0 - list_buffer - render_col_count * 12 == 4096 + int render_col_limit = (cached_flat0 - list_buffer ) / sizeof(pd_column); +// printf("THIS IS A PROBLEM LIMIT TO %d cols\n", render_col_limit); + new_cache_flat_slots = 1; + uh_oh_discard_columns(render_col_limit); + } else if (render_col_count == RENDER_COL_MAX) { + static int foo; +// printf("OOPS MAXXED OUT %d\n", foo++); + } + for(int i=cached_flat_slots; iframebuffer); + F_Drawer(); + draw_framebuffer_patches_fullscreen(); + } + V_BeginPatchList(vpatchlists->overlays[render_overlay_index]); + if (!showing_help) { + was_in_help = false; + switch (gamestate) { + case GS_LEVEL: +// if (!gametic) +// break; + if (!wipestate) { + if (automapactive) + AM_Drawer(); + // goes into overlay set above + ST_Drawer(false, !pre_wipe_state); + sub_gamestate = 0; + next_video_type = VIDEO_TYPE_DOUBLE; + } + break; + + case GS_INTERMISSION: { + static int16_t *wipe_yoffsets_raw; + if (!wipestate && post_wipecount < 2) { + // todo we don't need to check wi_background_patch_num + if (post_wipecount == 1 && wi_background_patch_num) { + // at this point the text should be drawn by overlay, so we must erease it from the background + draw_splash(wi_background_patch_num, 0, MAIN_VIEWHEIGHT, frame_buffer[render_frame_index]); + draw_splash(wi_background_patch_num, MAIN_VIEWHEIGHT, SCREENHEIGHT, + frame_buffer[render_frame_index ^ 1] + (MAIN_VIEWHEIGHT - 32) * SCREENWIDTH); + } + post_wipecount++; + } + // todo we should draw static stuff except the numbers on the background above, which also would be we might need to refresh the first time here + if (pre_wipe_state) { + // to framebuffer (otherwise it goes to the overlay) + V_BeginPatchList(vpatchlists->framebuffer); + } + if (!wipestate) WI_Drawer(); + if (pre_wipe_state) { + draw_framebuffer_patches_fullscreen(); + } + break; + } + case GS_FINALE: { +#if !DEMO1_ONLY + if (finalestage==F_STAGE_ARTSCREEN && !F_ArtScreenLumpName() && !wipestate) { + static uint16_t last_scroll; + int scroll = SCREENWIDTH - F_BunnyScrollPos(); + if (last_scroll > scroll) last_scroll = 0; + if (scroll > last_scroll) { + // i think using the list buffer here as scratch space is fine ... you'll get a garbage column if you start a new game half way thru the scroll!! i don't think i care! + next_video_scroll = list_buffer + SCREENHEIGHT * render_frame_index; + int patch_num = W_GetNumForName("PFUB2"); + if (scroll != SCREENWIDTH) { + draw_splash(patch_num, 0, SCREENHEIGHT, next_video_scroll, SCREENWIDTH - 1 - scroll); + } + last_scroll++; + } else { + next_video_scroll = nullptr; + } + F_BunnyDrawPatches(); + } else { + next_video_scroll = nullptr; + } +#endif + break; + } + case GS_DEMOSCREEN: { + //assert(num_framedrawables == 0); + if (!wipestate) { + static bool warmup_done; + if (!warmup_done) { + // hack alert: we draw the status bar (to be immediately overdrawn) as a first thing so that we don't have a cold cache + // when we draw it in the middle of the first wipe where it causes a cache fight with the video_newhope scanline stuff + draw_stbar_on_framebuffer(render_frame_index, false); + warmup_done = true; + } + int pnum = W_GetNumForName(pagename); + assert(pnum); + maybe_draw_single_screen(pnum); + } + break; + } + } + } else { + static const char *last_name; + if (!was_in_help) { + next_video_type = VIDEO_TYPE_SINGLE; + last_name = NULL; + was_in_help = true; + } + if (gamestate == GS_LEVEL) { + ST_doPaletteStuff(); + } + const char *name = nullptr; + switch (inhelpscreens & 127) { + case 1: + name = "HELP1"; + break; + case 2: + name = "HELP2"; + break; + case 3: + name = "HELP"; + break; + } + if (name) { + if (last_name != name) { + sub_gamestate = 0; // redraw + last_name = name; + } + int pnum = W_GetNumForName(name); + maybe_draw_single_screen(pnum); + } + } +// ST_FpsDrawer(render_col_count); +// ST_FpsDrawer(cached_flat_slots); + ST_FpsDrawer(-1); + + // todo this might not be right + // advance demo is set on the last frame of a demo, pre_wipe_state is set for last frame of gameplay in other state changes (by g_game) + // inhelpscreens has skull which is in an iconvenient place + bool render_menu_etc_to_fb = !advancedemo && !pre_wipe_state && next_video_type == VIDEO_TYPE_DOUBLE && !inhelpscreens; + if (render_menu_etc_to_fb) { + // render menu/hu to framebuffer (otherwise it goes to the overlay) + V_BeginPatchList(vpatchlists->framebuffer); + } + + if (!pre_wipe_state && !wipestate && gamestate == GS_LEVEL && gametic && !inhelpscreens) { + HU_Drawer(); + } +#if !DEMO1_ONLY + if (gamestate == GS_FINALE && finalestage == F_STAGE_CAST && !wipestate) { + F_CastDrawer(); // just draw the text + } +#endif + M_Drawer(); + + if (render_menu_etc_to_fb) { + // render menu/hu to framebuffer + V_RestoreBuffer(); + V_DrawPatchList(vpatchlists->framebuffer); + } + if (pre_wipe_state == PRE_WIPE_EXTRA_FRAME_NEEDED) { + pre_wipe_state = PRE_WIPE_EXTRA_FRAME_DONE; + } + + next_frame_index = render_frame_index; + next_overlay_index = render_overlay_index; + render_overlay_index ^= 1; +#if !DEMO1_ONLY + if (next_video_type == VIDEO_TYPE_SINGLE && gamestate != GS_FINALE) { + next_video_scroll = nullptr; + } +#endif +#if 0 && !PICO_ON_DEVICE + printf("GS %d vt %d fi %d\n", gamestate, next_video_type, next_frame_index); +#endif + sem_release(&render_frame_ready); + DEBUG_PINS_CLR(start_end, 2); +} + +void pd_core1_loop() { +#if PICO_ON_DEVICE + sem_acquire_blocking(&core1_wake); +#if USE_CORE1_FOR_FLATS + while (!sem_acquire_timeout_ms(&core1_do_flats, 1)) { + SafeUpdateSound(); + } + interp_in_use = true; + draw_visplanes(core1_fr_list); + interp_in_use = false; +#if USE_CORE1_FOR_REGULAR + while (!sem_acquire_timeout_ms(&core1_do_regular, 1)) { + SafeUpdateSound(); + } + draw_regular_columns(1); +#endif +#endif + while (!sem_acquire_timeout_ms(&core0_done, 1)) { + SafeUpdateSound(); + } +#endif + sem_release(&core1_done); +} + +#if PICO_ON_DEVICE +extern "C" { +#include "i_picosound.h" +} +static uint8_t old_video_type; +void pd_start_save_pause(void) { + I_PicoSoundFade(false); + while (!sem_available(&display_frame_freed) || I_PicoSoundFading()) { + I_UpdateSound(); + } + sem_acquire_blocking(&display_frame_freed); + old_video_type = next_video_type; + // this should be the case + if (old_video_type == VIDEO_TYPE_DOUBLE) { + draw_stbar_on_framebuffer(render_frame_index ^ 1, false); + } + next_video_type = VIDEO_TYPE_SAVING; + sem_release(&render_frame_ready); + // need to be sure we've picked up the change + while (!sem_available(&display_frame_freed)) { + I_UpdateSound(); + } + sem_acquire_blocking(&display_frame_freed); +} + +void pd_end_save_pause(void) { + next_video_type = old_video_type; + sem_release(&render_frame_ready); + I_PicoSoundFade(true); + while (I_PicoSoundFading()) { + I_UpdateSound(); + } +} + +#endif + +#pragma GCC pop_options + +void th_bit_overrun(th_bit_input *bi) { + panic("BIT OVERRUN"); +} + +uint8_t *pd_get_work_area(uint32_t *size) { + *size = last_list_buffer_limit - list_buffer; + return list_buffer; +} + +#if !DEMO1_ONLY +void draw_cast_sprite(int sprite_lump) { + // drawing the sprites here is somewhat painful... f_finale uses V_DrawPatch but in Pico Doom we + // vpatches are different from patches. the simplest thing for us to do (to avoid duplicating + // decompressing code) is to pass the sprite similarly to the regular game, giving us some columns + // to draw via draw_patch_columns + boolean flip; + if (sprite_lump < 0) { + sprite_lump = -1 - sprite_lump; + flip = true; + } else { + flip = false; + } + vissprite_t avis; + vissprite_t *vis = &avis; + vis->mobjflags = 0; + // note that f_finale uses patch offsets (which we do not store), however the sprite offsets seem to be the same (or not noticeably different) + // save for a different y offset + + // fortunately and entirely coincidentally the maximum height we need is EXACTLY 168-32 (which is how much offscreen buffer we have left) + // 145 puts the bottom of the graphics at the bottom of that + int ypos = (145 << FRACBITS) - sprite_topoffset(sprite_lump); + vis->texturemid = ((SCREENHEIGHT / 2) << FRACBITS) + FRACUNIT / 2 - ypos; + vis->x1 = SCREENWIDTH / 2 - (sprite_offset(sprite_lump) >> FRACBITS); + vis->x2 = vis->x1 + ((sprite_width(sprite_lump) - 1) >> FRACBITS); + vis->scale = pspritescale << detailshift; + + if (flip) { + vis->xiscale = -pspriteiscale; + vis->startfrac = sprite_width(sprite_lump) - 1; + } else { + vis->xiscale = pspriteiscale; + vis->startfrac = 0; + } + +// if (vis->x1 > x1) +// vis->startfrac += vis->xiscale * (vis->x1 - x1); + + vis->patch = sprite_lump; + vis->colormap = 0; + + pd_flag |= 2; + R_DrawVisSprite(vis, 0, 0); // vis->x1, vis->x2); the params are ignored + pd_flag &= ~2; + + // sort into correct lists + uint8_t buffer[WHD_PATCH_MAX_WIDTH * 3]; + int16_t head = -1; + const int height = MAIN_VIEWHEIGHT - 32; + for (int x = 0; x < SCREENWIDTH; x++) { + int16_t i = column_heads[x]; + while (i >= 0) { + auto &c = render_cols[i]; + int16_t fd_next = head; + // link is index with top bit set if x >= 256... note -1 would conflict with i == 0x7fff, x>=256 + // which we don't care about because i would never be that high + head = (x >> 8) ? (i | 0x8000) : i; + // loop over old list + i = c.next; + // replace fd_num with 8 low bits of x + c.x = x; + if (c.yh>167-32) c.yh = 167-32; // may as well clip + // and link remainder of old chain + c.next = fd_next; + } + } + // to save having a new overlay mode, we'll render to an off screen buffer and attempt to copy this avoiding the scanline (or not - perhaps we don't really care) + render_frame_buffer = frame_buffer[render_frame_index ^ 1]; + const int top = 41; // window top -> top + height is the window we care about + // draw the bit of the background we need + draw_splash(W_GetNumForName("BOSSBACK"), top, top + height, render_frame_buffer); + // draw the stuff over the top + draw_patch_columns(firstspritelump+sprite_lump, head, (int16_t *) buffer, buffer + WHD_PATCH_MAX_WIDTH * 2, 0); + + // now blit to the screen (could have waited for a vsync here but i don't see any flickering) + static_assert(top + height > MAIN_VIEWHEIGHT, ""); // just to check we need to split this copy into two bits + memcpy(frame_buffer[render_frame_index] + top * SCREENWIDTH, render_frame_buffer, (MAIN_VIEWHEIGHT - top) * SCREENWIDTH); + // bottom bit goes on the last 32 pixels of the other buffer + memcpy(render_frame_buffer + (MAIN_VIEWHEIGHT-32) * SCREENWIDTH, render_frame_buffer + (MAIN_VIEWHEIGHT - top) * SCREENWIDTH, (top + height - MAIN_VIEWHEIGHT) * SCREENWIDTH); +} +#endif \ No newline at end of file diff --git a/src/pico/CMakeLists.txt b/src/pico/CMakeLists.txt new file mode 100644 index 00000000..20dd6407 --- /dev/null +++ b/src/pico/CMakeLists.txt @@ -0,0 +1,45 @@ +add_library(common_pico INTERFACE) +target_sources(common_pico INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/i_glob.c + ${CMAKE_CURRENT_LIST_DIR}/i_input.c + ${CMAKE_CURRENT_LIST_DIR}/i_system.c + ${CMAKE_CURRENT_LIST_DIR}/piconet.c + ${CMAKE_CURRENT_LIST_DIR}/i_timer.c + ${CMAKE_CURRENT_LIST_DIR}/i_video.c + ${CMAKE_CURRENT_LIST_DIR}/stubs.c + + ${CMAKE_CURRENT_LIST_DIR}/i_picosound.c +) +if (PICO_ON_DEVICE) + target_sources(common_pico INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/blit.S + ${CMAKE_CURRENT_LIST_DIR}/picoflash.c + ) + pico_wrap_function(common_pico malloc) + pico_wrap_function(common_pico calloc) + pico_wrap_function(common_pico free) + target_compile_definitions(common_pico INTERFACE + PICO_HEAP_SIZE=0 + USE_ZONE_FOR_MALLOC=1 + ) + target_link_libraries(common_pico INTERFACE hardware_i2c) +endif() +target_include_directories(common_pico INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. + ${CMAKE_CURRENT_LIST_DIR}/../../textscreen + ) + +target_compile_definitions(common_pico INTERFACE + NO_USE_MOUSE=1 + PICO_AUDIO_I2S_PIO=1 + PICO_AUDIO_I2S_DMA_IRQ=1 + ) + +pico_generate_pio_header(common_pico ${CMAKE_CURRENT_LIST_DIR}/video_doom.pio) +target_link_libraries(common_pico INTERFACE pico_stdlib pico_multicore pico_scanvideo_dpi) + +add_library(pico_cd INTERFACE) +if (TARGET tinyusb_host) + target_link_libraries(pico_cd INTERFACE tinyusb_host) +endif() \ No newline at end of file diff --git a/src/pico/blit.S b/src/pico/blit.S new file mode 100644 index 00000000..3d7f2c9a --- /dev/null +++ b/src/pico/blit.S @@ -0,0 +1,96 @@ +// +// Copyright (c) 20222 Graham Sanderson +// +// SPDX-License-Identifier: BSD-3-Clause +// + +#include "pico/asm_helper.S" +#include "hardware/regs/addressmap.h" +#include "hardware/regs/sio.h" +.syntax unified +.cpu cortex-m0plus +.thumb + +#define INTERP_OFFSET0(x) ((x) - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP_OFFSET1(x) (INTERP_OFFSET0(x) + SIO_INTERP1_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) + +.section .time_critical.blit + +.global palette8to16 +.type palette8to16,%function +.thumb_func +// palette8to16(dest, src, pixels) // note src, dest, and pixels must be 4 aligned +palette8to16: + push {r4, r5, lr} + add r2, r1 + mov ip, r2 + ldr r5, =#SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET +1: + ldmia r1!, {r2} + str r2, [r5, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)] + str r2, [r5, #INTERP_OFFSET0(SIO_INTERP1_ACCUM0_OFFSET)] + ldr r2, [r5, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldrh r2, [r2, r2] + ldr r3, [r5, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE1_OFFSET)] + ldrh r3, [r3, r3] + lsls r3, #16 + orrs r3, r2 + ldr r2, [r5, #INTERP_OFFSET0(SIO_INTERP1_PEEK_LANE0_OFFSET)] + ldrh r2, [r2, r2] + ldr r4, [r5, #INTERP_OFFSET0(SIO_INTERP1_PEEK_LANE1_OFFSET)] + ldrh r4, [r4, r4] + lsls r4, #16 + orrs r4, r2 + stmia r0!, {r3, r4} + cmp r1, ip + bne 1b + pop {r4, r5, pc} + + +.global palette4to16 +.type palette4to16,%function +.thumb_func +// palette4to16(dest, src, pixels) // note src, dest, and pixels must be 4 aligned +palette4to16: + push {r4-r6, lr} + add r2, r1 + mov ip, r2 + ldr r5, =#SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET +1: + ldmia r1!, {r6} + str r6, [r5, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)] + str r6, [r5, #INTERP_OFFSET0(SIO_INTERP1_ACCUM0_OFFSET)] + ldr r2, [r5, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldrh r2, [r2, r2] + ldr r3, [r5, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE1_OFFSET)] + ldrh r3, [r3, r3] + lsls r3, #16 + orrs r3, r2 + ldr r2, [r5, #INTERP_OFFSET0(SIO_INTERP1_PEEK_LANE0_OFFSET)] + ldrh r2, [r2, r2] + ldr r4, [r5, #INTERP_OFFSET0(SIO_INTERP1_PEEK_LANE1_OFFSET)] + ldrh r4, [r4, r4] + lsls r4, #16 + orrs r4, r2 + stmia r0!, {r3, r4} + lsrs r6, #16 + str r6, [r5, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)] + str r6, [r5, #INTERP_OFFSET0(SIO_INTERP1_ACCUM0_OFFSET)] + ldr r2, [r5, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldrh r2, [r2, r2] + ldr r3, [r5, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE1_OFFSET)] + ldrh r3, [r3, r3] + lsls r3, #16 + orrs r3, r2 + ldr r2, [r5, #INTERP_OFFSET0(SIO_INTERP1_PEEK_LANE0_OFFSET)] + ldrh r2, [r2, r2] + ldr r4, [r5, #INTERP_OFFSET0(SIO_INTERP1_PEEK_LANE1_OFFSET)] + ldrh r4, [r4, r4] + lsls r4, #16 + orrs r4, r2 + stmia r0!, {r3, r4} + + cmp r1, ip + bne 1b + pop {r4-r6, pc} + diff --git a/src/pico/i_glob.c b/src/pico/i_glob.c new file mode 100644 index 00000000..39014cdb --- /dev/null +++ b/src/pico/i_glob.c @@ -0,0 +1,364 @@ +// +// Copyright(C) 2018 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// File globbing API. This allows the contents of the filesystem +// to be interrogated. +// + +#include +#include +#include + +#include "i_glob.h" +#include "m_misc.h" +#include "config.h" + +#if PICO_ON_DEVICE +#define NO_DIRENT_IMPLEMENTATION +#else +#include +#include +#endif + +#ifndef NO_DIRENT_IMPLEMENTATION + +// Only the fields d_name and (as an XSI extension) d_ino are specified +// in POSIX.1. Other than Linux, the d_type field is available mainly +// only on BSD systems. The remaining fields are available on many, but +// not all systems. +static boolean IsDirectory(char *dir, struct dirent *de) +{ +#if defined(_DIRENT_HAVE_D_TYPE) + if (de->d_type != DT_UNKNOWN && de->d_type != DT_LNK) + { + return de->d_type == DT_DIR; + } + else +#endif + { + char *filename; + struct stat sb; + int result; + + filename = M_StringJoin(dir, DIR_SEPARATOR_S, de->d_name, NULL); + result = stat(filename, &sb); + free(filename); + + if (result != 0) + { + return false; + } + + return S_ISDIR(sb.st_mode); + } +} + +struct glob_s +{ + char **globs; + int num_globs; + int flags; + DIR *dir; + char *directory; + char *last_filename; + // These fields are only used when the GLOB_FLAG_SORTED flag is set: + char **filenames; + int filenames_len; + int next_index; +}; + +static void FreeStringList(char **globs, int num_globs) +{ + int i; + for (i = 0; i < num_globs; ++i) + { + free(globs[i]); + } + free(globs); +} + +glob_t *I_StartMultiGlob(const char *directory, int flags, + const char *glob, ...) +{ + char **globs; + int num_globs; + glob_t *result; + va_list args; + + globs = malloc(sizeof(char *)); + if (globs == NULL) + { + return NULL; + } + globs[0] = M_StringDuplicate(glob); + num_globs = 1; + + va_start(args, glob); + for (;;) + { + const char *arg = va_arg(args, const char *); + char **new_globs; + + if (arg == NULL) + { + break; + } + + new_globs = realloc(globs, sizeof(char *) * (num_globs + 1)); + if (new_globs == NULL) + { + FreeStringList(globs, num_globs); + } + globs = new_globs; + globs[num_globs] = M_StringDuplicate(arg); + ++num_globs; + } + va_end(args); + + result = malloc(sizeof(glob_t)); + if (result == NULL) + { + FreeStringList(globs, num_globs); + return NULL; + } + + result->dir = opendir(directory); + if (result->dir == NULL) + { + FreeStringList(globs, num_globs); + free(result); + return NULL; + } + + result->directory = M_StringDuplicate(directory); + result->globs = globs; + result->num_globs = num_globs; + result->flags = flags; + result->last_filename = NULL; + result->filenames = NULL; + result->filenames_len = 0; + result->next_index = -1; + return result; +} + +glob_t *I_StartGlob(const char *directory, const char *glob, int flags) +{ + return I_StartMultiGlob(directory, flags, glob, NULL); +} + +void I_EndGlob(glob_t *glob) +{ + if (glob == NULL) + { + return; + } + + FreeStringList(glob->globs, glob->num_globs); + FreeStringList(glob->filenames, glob->filenames_len); + + free(glob->directory); + free(glob->last_filename); + (void) closedir(glob->dir); + free(glob); +} + +static boolean MatchesGlob(const char *name, const char *glob, int flags) +{ + int n, g; + + while (*glob != '\0') + { + n = *name; + g = *glob; + + if ((flags & GLOB_FLAG_NOCASE) != 0) + { + n = tolower(n); + g = tolower(g); + } + + if (g == '*') + { + // To handle *-matching we skip past the * and recurse + // to check each subsequent character in turn. If none + // match then the whole match is a failure. + while (*name != '\0') + { + if (MatchesGlob(name, glob + 1, flags)) + { + return true; + } + ++name; + } + return glob[1] == '\0'; + } + else if (g != '?' && n != g) + { + // For normal characters the name must match the glob, + // but for ? we don't care what the character is. + return false; + } + + ++name; + ++glob; + } + + // Match successful when glob and name end at the same time. + return *name == '\0'; +} + +static boolean MatchesAnyGlob(const char *name, glob_t *glob) +{ + int i; + + for (i = 0; i < glob->num_globs; ++i) + { + if (MatchesGlob(name, glob->globs[i], glob->flags)) + { + return true; + } + } + return false; +} + +static char *NextGlob(glob_t *glob) +{ + struct dirent *de; + + do + { + de = readdir(glob->dir); + if (de == NULL) + { + return NULL; + } + } while (IsDirectory(glob->directory, de) + || !MatchesAnyGlob(de->d_name, glob)); + + // Return the fully-qualified path, not just the bare filename. + return M_StringJoin(glob->directory, DIR_SEPARATOR_S, de->d_name, NULL); +} + +static void ReadAllFilenames(glob_t *glob) +{ + char *name; + + glob->filenames = NULL; + glob->filenames_len = 0; + glob->next_index = 0; + + for (;;) + { + name = NextGlob(glob); + if (name == NULL) + { + break; + } + glob->filenames = realloc(glob->filenames, + (glob->filenames_len + 1) * sizeof(char *)); + glob->filenames[glob->filenames_len] = name; + ++glob->filenames_len; + } +} + +static void SortFilenames(char **filenames, int len, int flags) +{ + char *pivot, *tmp; + int i, left_len, cmp; + + if (len <= 1) + { + return; + } + pivot = filenames[len - 1]; + left_len = 0; + for (i = 0; i < len-1; ++i) + { + if ((flags & GLOB_FLAG_NOCASE) != 0) + { + cmp = strcasecmp(filenames[i], pivot); + } + else + { + cmp = strcmp(filenames[i], pivot); + } + + if (cmp < 0) + { + tmp = filenames[i]; + filenames[i] = filenames[left_len]; + filenames[left_len] = tmp; + ++left_len; + } + } + filenames[len - 1] = filenames[left_len]; + filenames[left_len] = pivot; + + SortFilenames(filenames, left_len, flags); + SortFilenames(&filenames[left_len + 1], len - left_len - 1, flags); +} + +const char *I_NextGlob(glob_t *glob) +{ + const char *result; + + if (glob == NULL) + { + return NULL; + } + + // In unsorted mode we just return the filenames as we read + // them back from the system API. + if ((glob->flags & GLOB_FLAG_SORTED) == 0) + { + free(glob->last_filename); + glob->last_filename = NextGlob(glob); + return glob->last_filename; + } + + // In sorted mode we read the whole list of filenames into memory, + // sort them and return them one at a time. + if (glob->next_index < 0) + { + ReadAllFilenames(glob); + SortFilenames(glob->filenames, glob->filenames_len, glob->flags); + } + if (glob->next_index >= glob->filenames_len) + { + return NULL; + } + result = glob->filenames[glob->next_index]; + ++glob->next_index; + return result; +} + +#else /* #ifdef NO_DIRENT_IMPLEMENTATION */ + +glob_t *I_StartGlob(const char *directory, const char *glob, int flags) +{ + return NULL; +} + +void I_EndGlob(glob_t *glob) +{ +} + +const char *I_NextGlob(glob_t *glob) +{ + return ""; +} + +#endif /* #ifdef NO_DIRENT_IMPLEMENTATION */ + diff --git a/src/pico/i_input.c b/src/pico/i_input.c new file mode 100644 index 00000000..1ac1e1f1 --- /dev/null +++ b/src/pico/i_input.c @@ -0,0 +1,825 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// SDL implementation of system-specific input interface. +// + + +//#include "SDL.h" +//#include "SDL_keycode.h" +#include +#include +#include "pico.h" +#include "doomkeys.h" +#include "doomtype.h" +#include "d_event.h" +#include "i_input.h" +#include "i_system.h" +#include "i_video.h" +#include "m_argv.h" +#include "m_config.h" +#include "hardware/uart.h" +#include +#if USB_SUPPORT +#include "pico/binary_info.h" +#include "tusb.h" +#include "hardware/irq.h" +bi_decl(bi_program_feature("USB keyboard support")); +#endif + +static const int scancode_translate_table[] = SCANCODE_TO_KEYS_ARRAY; + +// Lookup table for mapping ASCII characters to their equivalent when +// shift is pressed on a US layout keyboard. This is the original table +// as found in the Doom sources, comments and all. +static const char shiftxform[] = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, ' ', '!', '"', '#', '$', '%', '&', + '"', // shift-' + '(', ')', '*', '+', + '<', // shift-, + '_', // shift-- + '>', // shift-. + '?', // shift-/ + ')', // shift-0 + '!', // shift-1 + '@', // shift-2 + '#', // shift-3 + '$', // shift-4 + '%', // shift-5 + '^', // shift-6 + '&', // shift-7 + '*', // shift-8 + '(', // shift-9 + ':', + ':', // shift-; + '<', + '+', // shift-= + '>', '?', '@', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '[', // shift-[ + '!', // shift-backslash - OH MY GOD DOES WATCOM SUCK + ']', // shift-] + '"', '_', + '\'', // shift-` + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '{', '|', '}', '~', 127 + }; + +// If true, I_StartTextInput() has been called, and we are populating +// the data3 field of ev_keydown events. +static boolean text_input_enabled = true; + +// Bit mask of mouse button state. +static unsigned int mouse_button_state = 0; + +// Disallow mouse and joystick movement to cause forward/backward +// motion. Specified with the '-novert' command line parameter. +// This is an int to allow saving to config file +int novert = 0; + +// If true, keyboard mapping is ignored, like in Vanilla Doom. +// The sensible thing to do is to disable this if you have a non-US +// keyboard. + +#if !USE_VANILLA_KEYBOARD_MAPPING_ONLY +int vanilla_keyboard_mapping = true; +#endif + +// Mouse acceleration +// +// This emulates some of the behavior of DOS mouse drivers by increasing +// the speed when the mouse is moved fast. +// +// The mouse input values are input directly to the game, but when +// the values exceed the value of mouse_threshold, they are multiplied +// by mouse_acceleration to increase the speed. +#if !NO_USE_MOUSE +float mouse_acceleration = 2.0; +int mouse_threshold = 10; +#endif + +enum { + SDL_SCANCODE_SPACE = 44, + SDL_SCANCODE_LCTRL = 224, + SDL_SCANCODE_LSHIFT = 225, + SDL_SCANCODE_LALT = 226, /**< alt, option */ + SDL_SCANCODE_LGUI = 227, /**< windows, command (apple), meta */ + SDL_SCANCODE_RCTRL = 228, + SDL_SCANCODE_RSHIFT = 229, + SDL_SCANCODE_RALT = 230, /**< alt gr, option */ + SDL_SCANCODE_RGUI = 231, /**< windows, command (apple), meta */ +}; + +// Translates the SDL key to a value of the type found in doomkeys.h +int TranslateKey(int scancode) +{ + switch (scancode) + { + case SDL_SCANCODE_LCTRL: + case SDL_SCANCODE_RCTRL: + return KEY_RCTRL; + + case SDL_SCANCODE_LSHIFT: + case SDL_SCANCODE_RSHIFT: + return KEY_RSHIFT; + + case SDL_SCANCODE_LALT: + return KEY_LALT; + + case SDL_SCANCODE_RALT: + return KEY_RALT; + + default: + if (scancode >= 0 && scancode < arrlen(scancode_translate_table)) + { + return scancode_translate_table[scancode]; + } + else + { + return 0; + } + } +} + +// Get the localized version of the key press. This takes into account the +// keyboard layout, but does not apply any changes due to modifiers, (eg. +// shift-, alt-, etc.) +static int GetLocalizedKey(int scancode) +{ + // When using Vanilla mapping, we just base everything off the scancode + // and always pretend the user is using a US layout keyboard. + if (vanilla_keyboard_mapping) + { + return TranslateKey(scancode); + } + else + { + assert(false); return 0; +// int result = sym->sym; +// +// if (result < 0 || result >= 128) +// { +// result = 0; +// } +// +// return sym_= 0 && result < arrlen(shiftxform)) + { + result = shiftxform[result]; + } + + return result; + } + else + { +#if 0 + SDL_Event next_event; + + // Special cases, where we always return a fixed value. + switch (sym->sym) + { + case SDLK_BACKSPACE: return KEY_BACKSPACE; + case SDLK_RETURN: return KEY_ENTER; + default: + break; + } + + // The following is a gross hack, but I don't see an easier way + // of doing this within the SDL2 API (in SDL1 it was easier). + // We want to get the fully transformed input character associated + // with this keypress - correct keyboard layout, appropriately + // transformed by any modifier keys, etc. So peek ahead in the SDL + // event queue and see if the key press is immediately followed by + // an SDL_TEXTINPUT event. If it is, it's reasonable to assume the + // key press and the text input are connected. Technically the SDL + // API does not guarantee anything of the sort, but in practice this + // is what happens and I've verified it through manual inspect of + // the SDL source code. + // + // In an ideal world we'd split out ev_keydown into a separate + // ev_textinput event, as SDL2 has done. But this doesn't work + // (I experimented with the idea), because lots of Doom's code is + // based around different responders "eating" events to stop them + // being passed on to another responder. If code is listening for + // a text input, it cannot block the corresponding keydown events + // which can affect other responders. + // + // So we're stuck with this as a rather fragile alternative. + + if (SDL_PeepEvents(&next_event, 1, SDL_PEEKEVENT, + SDL_FIRSTEVENT, SDL_LASTEVENT) == 1 + && next_event.type == SDL_TEXTINPUT) + { + // If an SDL_TEXTINPUT event is found, we always assume it + // matches the key press. The input text must be a single + // ASCII character - if it isn't, it's possible the input + // char is a Unicode value instead; better to send a null + // character than the unshifted key. + if (strlen(next_event.text.text) == 1 + && (next_event.text.text[0] & 0x80) == 0) + { + return next_event.text.text[0]; + } + } +#else + assert(false); +#endif + + // Failed to find anything :/ + return 0; + } +} + +void I_StartTextInput(int x1, int y1, int x2, int y2) +{ + text_input_enabled = true; + + if (!vanilla_keyboard_mapping) + { +#if !USE_VANILLA_KEYBOARD_MAPPING_ONLY + // SDL2-TODO: SDL_SetTextInputRect(...); + SDL_StartTextInput(); +#endif + } +} + +void I_StopTextInput(void) +{ + text_input_enabled = false; + + if (!vanilla_keyboard_mapping) + { +#if !USE_VANILLA_KEYBOARD_MAPPING_ONLY + SDL_StopTextInput(); +#endif + } +} + +#if !NO_USE_MOUSE +static void UpdateMouseButtonState(unsigned int button, boolean on) +{ + static event_t event; + + if (button < SDL_BUTTON_LEFT || button > MAX_MOUSE_BUTTONS) + { + return; + } + + // Note: button "0" is left, button "1" is right, + // button "2" is middle for Doom. This is different + // to how SDL sees things. + + switch (button) + { + case SDL_BUTTON_LEFT: + button = 0; + break; + + case SDL_BUTTON_RIGHT: + button = 1; + break; + + case SDL_BUTTON_MIDDLE: + button = 2; + break; + + default: + // SDL buttons are indexed from 1. + --button; + break; + } + + // Turn bit representing this button on or off. + + if (on) + { + mouse_button_state |= (1 << button); + } + else + { + mouse_button_state &= ~(1 << button); + } + + // Post an event with the new button state. + + event.type = ev_mouse; + event.data1 = mouse_button_state; + event.data2 = event.data3 = 0; + D_PostEvent(&event); +} + +static void MapMouseWheelToButtons(SDL_MouseWheelEvent *wheel) +{ + // SDL2 distinguishes button events from mouse wheel events. + // We want to treat the mouse wheel as two buttons, as per + // SDL1 + static event_t up, down; + int button; + + if (wheel->y <= 0) + { // scroll down + button = 4; + } + else + { // scroll up + button = 3; + } + + // post a button down event + mouse_button_state |= (1 << button); + down.type = ev_mouse; + down.data1 = mouse_button_state; + down.data2 = down.data3 = 0; + D_PostEvent(&down); + + // post a button up event + mouse_button_state &= ~(1 << button); + up.type = ev_mouse; + up.data1 = mouse_button_state; + up.data2 = up.data3 = 0; + D_PostEvent(&up); +} + +void I_HandleMouseEvent(SDL_Event *sdlevent) +{ + switch (sdlevent->type) + { + case SDL_MOUSEBUTTONDOWN: + UpdateMouseButtonState(sdlevent->button.button, true); + break; + + case SDL_MOUSEBUTTONUP: + UpdateMouseButtonState(sdlevent->button.button, false); + break; + + case SDL_MOUSEWHEEL: + MapMouseWheelToButtons(&(sdlevent->wheel)); + break; + + default: + break; + } +} + +static int AccelerateMouse(int val) +{ + if (val < 0) + return -AccelerateMouse(-val); + + if (val > mouse_threshold) + { + return (int)((val - mouse_threshold) * mouse_acceleration + mouse_threshold); + } + else + { + return val; + } +} + +// +// Read the change in mouse state to generate mouse motion events +// +// This is to combine all mouse movement for a tic into one mouse +// motion event. +void I_ReadMouse(void) +{ + int x, y; + event_t ev; + + SDL_GetRelativeMouseState(&x, &y); + + if (x != 0 || y != 0) + { + ev.type = ev_mouse; + ev.data1 = mouse_button_state; + ev.data2 = AccelerateMouse(x); + + if (!novert) + { + ev.data3 = -AccelerateMouse(y); + } + else + { + ev.data3 = 0; + } + + // XXX: undefined behaviour since event is scoped to + // this function + D_PostEvent(&ev); + } +} +#endif + +// Bind all variables controlling input options. +void I_BindInputVariables(void) +{ +#if !NO_USE_MOUSE + M_BindFloatVariable("mouse_acceleration", &mouse_acceleration); + M_BindIntVariable("mouse_threshold", &mouse_threshold); +#endif +#if !USE_VANILLA_KEYBOARD_MAPPING_ONLY + M_BindIntVariable("vanilla_keyboard_mapping", &vanilla_keyboard_mapping); +#endif + M_BindIntVariable("novert", &novert); +} + +#if PICO_NO_HARDWARE +#include "pico/scanvideo.h" +#else +#define WITH_SHIFT 0x8000 +#endif + +static void pico_key_down(int scancode, int keysym, int modifiers) { + event_t event; + event.type = ev_keydown; + event.data1 = TranslateKey(scancode); + event.data2 = GetLocalizedKey(scancode); + event.data3 = GetTypedChar(scancode, modifiers & WITH_SHIFT ? 1 : 0); + + if (at_exit_screen) { + handle_exit_key_down(scancode, modifiers & WITH_SHIFT ? 1 : 0, exit_screen_kb_buffer_80, 80); + return; + } + if (event.data1 != 0) + { + D_PostEvent(&event); + } +} + +static void pico_key_up(int scancode, int keysym, int modifiers) { + event_t event; + event.type = ev_keyup; + event.data1 = TranslateKey(scancode); + // data2/data3 are initialized to zero for ev_keyup. + // For ev_keydown it's the shifted Unicode character + // that was typed, but if something wants to detect + // key releases it should do so based on data1 + // (key ID), not the printable char. + event.data2 = 0; + event.data3 = 0; + if (event.data1 != 0) + { + D_PostEvent(&event); + } +} + +#if PICO_NO_HARDWARE +static void pico_quit(void) { + exit(0); +} +#endif + +void I_InputInit(void) { +#if PICO_NO_HARDWARE + platform_key_down = pico_key_down; + platform_key_up = pico_key_up; + platform_quit = pico_quit; +#elif USB_SUPPORT + tusb_init(); + irq_set_priority(USBCTRL_IRQ, 0xc0); +#endif +} + +void I_GetEvent() { +#if USB_SUPPORT + tuh_task(); +#endif + return I_GetEventTimeout(50); +} + +void I_GetEventTimeout(int key_timeout) { +#if PICO_ON_DEVICE && !NO_USE_UART + if (uart_is_readable(uart_default)) { + char c = uart_getc(uart_default); + if (c == 26 && uart_is_readable_within_us(uart_default, key_timeout)) { + c = uart_getc(uart_default); + static int modifiers = 0; + switch (c) { + case 0: + if (uart_is_readable_within_us(uart_default, key_timeout)) { + uint scancode = (uint8_t) uart_getc(uart_default); + if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT) { + modifiers |= WITH_SHIFT; + } + pico_key_down(scancode, 0, modifiers); + } + return; + case 1: + if (uart_is_readable_within_us(uart_default, key_timeout)) { + uint scancode = (uint8_t) uart_getc(uart_default); + if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT) { + modifiers &= ~WITH_SHIFT; + } + pico_key_up(scancode, 0, modifiers); + } + return; + case 2: + case 3: + case 5: + if (uart_is_readable_within_us(uart_default, key_timeout)) { + uint __unused scancode = (uint8_t) uart_getc(uart_default); + } + return; + case 4: + if (uart_is_readable_within_us(uart_default, key_timeout)) { + uint __unused scancode = (uint8_t) uart_getc(uart_default); + } + if (uart_is_readable_within_us(uart_default, key_timeout)) { + uint __unused scancode = (uint8_t) uart_getc(uart_default); + } + return; + } + } + } +#endif +} + +#if USB_SUPPORT + +#define MAX_REPORT 4 +#define debug_printf(fmt,...) ((void)0) + +// Each HID instance can has multiple reports +static struct +{ + uint8_t report_count; + tuh_hid_report_info_t report_info[MAX_REPORT]; +}hid_info[CFG_TUH_HID]; + +static void process_kbd_report(hid_keyboard_report_t const *report); +static void process_mouse_report(hid_mouse_report_t const * report); +static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len); + +// Invoked when device with hid interface is mounted +// Report descriptor is also available for use. tuh_hid_parse_report_descriptor() +// can be used to parse common/simple enough descriptor. +// Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped +// therefore report_desc = NULL, desc_len = 0 +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) +{ + debug_printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); + + // Interface protocol (hid_interface_protocol_enum_t) + const char* protocol_str[] = { "None", "Keyboard", "Mouse" }; + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + debug_printf("HID Interface Protocol = %s\r\n", protocol_str[itf_protocol]); +// printf("%d USB: device %d connected, protocol %s\n", time_us_32() - t0 , dev_addr, protocol_str[itf_protocol]); + + // By default host stack will use activate boot protocol on supported interface. + // Therefore for this simple example, we only need to parse generic report descriptor (with built-in parser) + if ( itf_protocol == HID_ITF_PROTOCOL_NONE ) + { + hid_info[instance].report_count = tuh_hid_parse_report_descriptor(hid_info[instance].report_info, MAX_REPORT, desc_report, desc_len); + debug_printf("HID has %u reports \r\n", hid_info[instance].report_count); + } + + // request to receive report + // tuh_hid_report_received_cb() will be invoked when report is available + if ( !tuh_hid_receive_report(dev_addr, instance) ) + { + debug_printf("Error: cannot request to receive report\r\n"); + } +} + +// Invoked when device with hid interface is un-mounted +void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) +{ + debug_printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); + printf("USB: device %d disconnected\n", dev_addr); +} + +// Invoked when received report from device via interrupt endpoint +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) +{ + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + + switch (itf_protocol) + { + case HID_ITF_PROTOCOL_KEYBOARD: + TU_LOG2("HID receive boot keyboard report\r\n"); + process_kbd_report( (hid_keyboard_report_t const*) report ); + break; + +#if !NO_USE_MOUSE + case HID_ITF_PROTOCOL_MOUSE: + TU_LOG2("HID receive boot mouse report\r\n"); + process_mouse_report( (hid_mouse_report_t const*) report ); + break; +#endif + + default: + // Generic report requires matching ReportID and contents with previous parsed report info + process_generic_report(dev_addr, instance, report, len); + break; + } + + // continue to request to receive report + if ( !tuh_hid_receive_report(dev_addr, instance) ) + { + debug_printf("Error: cannot request to receive report\r\n"); + } +} + +//--------------------------------------------------------------------+ +// Keyboard +//--------------------------------------------------------------------+ + +// look up new key in previous keys +static inline bool find_key_in_report(hid_keyboard_report_t const *report, uint8_t keycode) +{ + for(uint8_t i=0; i<6; i++) + { + if (report->keycode[i] == keycode) return true; + } + + return false; +} + +static void check_mod(int mod, int prev_mod, int mask, int scancode) { + if ((mod^prev_mod)&mask) { + if (mod & mask) + pico_key_down(scancode, 0, 0); + else + pico_key_up(scancode, 0, 0); + } +} + +static void process_kbd_report(hid_keyboard_report_t const *report) +{ + static hid_keyboard_report_t prev_report = { 0, 0, {0} }; // previous report to check key released + + //------------- example code ignore control (non-printable) key affects -------------// + for(uint8_t i=0; i<6; i++) + { + if ( report->keycode[i] ) + { + if ( find_key_in_report(&prev_report, report->keycode[i]) ) + { + // exist in previous report means the current key is holding + }else + { + // not existed in previous report means the current key is pressed + bool const is_shift = report->modifier & (KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT); + pico_key_down(report->keycode[i], 0, is_shift ? WITH_SHIFT : 0); + } + } + // Check for key depresses (i.e. was present in prev report but not here) + if (prev_report.keycode[i]) { + // If not present in the current report then depressed + if (!find_key_in_report(report, prev_report.keycode[i])) + { + bool const is_shift = report->modifier & (KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT); + pico_key_up(prev_report.keycode[i], 0, is_shift ? WITH_SHIFT : 0); + } + } + } + // synthesize events for modifier keys + static const uint8_t mods[] = { + KEYBOARD_MODIFIER_LEFTCTRL, SDL_SCANCODE_LCTRL, + KEYBOARD_MODIFIER_RIGHTCTRL, SDL_SCANCODE_RCTRL, + KEYBOARD_MODIFIER_LEFTALT, SDL_SCANCODE_LALT, + KEYBOARD_MODIFIER_RIGHTALT, SDL_SCANCODE_RALT, + KEYBOARD_MODIFIER_LEFTSHIFT, SDL_SCANCODE_LSHIFT, + KEYBOARD_MODIFIER_RIGHTSHIFT, SDL_SCANCODE_RSHIFT, + }; + for(int i=0;imodifier, prev_report.modifier, mods[i], mods[i+1]); + } + prev_report = *report; +} + +//--------------------------------------------------------------------+ +// Mouse +//--------------------------------------------------------------------+ + +#if !NO_USE_MOUSE +static void process_mouse_report(hid_mouse_report_t const * report) +{ + static hid_mouse_report_t prev_report = { 0 }; + + uint8_t button_changed_mask = report->buttons ^ prev_report.buttons; + if ( button_changed_mask & report->buttons) + { + debug_printf(" %c%c%c ", + report->buttons & MOUSE_BUTTON_LEFT ? 'L' : '-', + report->buttons & MOUSE_BUTTON_MIDDLE ? 'M' : '-', + report->buttons & MOUSE_BUTTON_RIGHT ? 'R' : '-'); + } + +// cursor_movement(report->x, report->y, report->wheel); +} +#endif + +//--------------------------------------------------------------------+ +// Generic Report +//--------------------------------------------------------------------+ +static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) +{ + (void) dev_addr; + + uint8_t const rpt_count = hid_info[instance].report_count; + tuh_hid_report_info_t* rpt_info_arr = hid_info[instance].report_info; + tuh_hid_report_info_t* rpt_info = NULL; + + if ( rpt_count == 1 && rpt_info_arr[0].report_id == 0) + { + // Simple report without report ID as 1st byte + rpt_info = &rpt_info_arr[0]; + }else + { + // Composite report, 1st byte is report ID, data starts from 2nd byte + uint8_t const rpt_id = report[0]; + + // Find report id in the arrray + for(uint8_t i=0; iusage_page == HID_USAGE_PAGE_DESKTOP ) + { + switch (rpt_info->usage) + { + case HID_USAGE_DESKTOP_KEYBOARD: + TU_LOG1("HID receive keyboard report\r\n"); + // Assume keyboard follow boot report layout + process_kbd_report( (hid_keyboard_report_t const*) report ); + break; + +#if !NO_USE_MOUSE + case HID_USAGE_DESKTOP_MOUSE: + TU_LOG1("HID receive mouse report\r\n"); + // Assume mouse follow boot report layout + process_mouse_report( (hid_mouse_report_t const*) report ); + break; +#endif + + default: break; + } + } +} + +#endif \ No newline at end of file diff --git a/src/pico/i_picosound.c b/src/pico/i_picosound.c new file mode 100644 index 00000000..5b00701b --- /dev/null +++ b/src/pico/i_picosound.c @@ -0,0 +1,491 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2008 David Flater +// Copyright(C) 2021-2022 Graham Sanderson +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System interface for sound. +// + +#include "config.h" + +#include +#include +#include +#include + +#include "deh_str.h" +#include "i_sound.h" +#include "m_misc.h" +#include "w_wad.h" + +#include "doomtype.h" +#include "i_picosound.h" +#include "pico/audio_i2s.h" +#include "pico/binary_info.h" +#include "hardware/gpio.h" + +#define ADPCM_BLOCK_SIZE 128 +#define ADPCM_SAMPLES_PER_BLOCK_SIZE 249 +#define LOW_PASS_FILTER +#define MIX_MAX_VOLUME 128 +typedef struct channel_s channel_t; + +static volatile enum { + FS_NONE, + FS_FADE_OUT, + FS_FADE_IN, + FS_SILENT, +} fade_state; +#define FADE_STEP 8 // must be power of 2 +uint16_t fade_level; + +struct channel_s +{ + const uint8_t *data; + const uint8_t *data_end; + uint32_t offset; + uint32_t step; + uint8_t left, right; // 0-255 + uint8_t decompressed_size; +#if SOUND_LOW_PASS + uint8_t alpha256; +#endif + int8_t decompressed[ADPCM_SAMPLES_PER_BLOCK_SIZE]; +}; + +static struct audio_buffer_pool *producer_pool; + +static struct audio_format audio_format = { + .format = AUDIO_BUFFER_FORMAT_PCM_S16, + .sample_freq = PICO_SOUND_SAMPLE_FREQ, + .channel_count = 2, +}; + +static struct audio_buffer_format producer_format = { + .format = &audio_format, + .sample_stride = 4 +}; + +// ====== FROM ADPCM-LIB ===== +#define CLIP(data, min, max) \ +if ((data) > (max)) data = max; \ +else if ((data) < (min)) data = min; + +/* step table */ +static const uint16_t step_table[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, + 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, + 32767 +}; + +/* step index tables */ +static const int index_table[] = { + /* adpcm data size is 4 */ + -1, -1, -1, -1, 2, 4, 6, 8 +}; +// ============================= + +static void (*music_generator)(audio_buffer_t *buffer); + +static boolean sound_initialized = false; +static channel_t channels[NUM_SOUND_CHANNELS]; + +static boolean use_sfx_prefix; + +static inline bool is_channel_playing(int channel) { + return channels[channel].decompressed_size != 0; +} + +static inline void stop_channel(int channel) { + channels[channel].decompressed_size = 0; +} + +static bool check_and_init_channel(int channel) { + return sound_initialized && ((uint)channel) < NUM_SOUND_CHANNELS; +} + +int adpcm_decode_block_s8(int8_t *outbuf, const uint8_t *inbuf, int inbufsize) +{ +#if 1 + int samples = 1, chunks; + + if (inbufsize < 4) + return 0; + + int32_t pcmdata = (int16_t) (inbuf [0] | (inbuf [1] << 8)); + *outbuf++ = pcmdata>>8u; + int index = inbuf[2]; + + if (index < 0 || index > 88 || inbuf [3]) // sanitize the input a little... + return 0; + + inbufsize -= 4; + inbuf += 4; + + chunks = inbufsize / 4; + samples += chunks * 8; + + while (chunks--) { + for (int i = 0; i < 4; ++i) { + int step = step_table[index], delta = step >> 3; + + if (*inbuf & 1) delta += (step >> 2); + if (*inbuf & 2) delta += (step >> 1); + if (*inbuf & 4) delta += step; + if (*inbuf & 8) delta = -delta; + + pcmdata += delta; + index += index_table [*inbuf & 0x7]; + CLIP(index, 0, 88); + CLIP(pcmdata, -32768, 32767); + outbuf [i * 2] = pcmdata>>8u; + + step = step_table[index], delta = step >> 3; + + if (*inbuf & 0x10) delta += (step >> 2); + if (*inbuf & 0x20) delta += (step >> 1); + if (*inbuf & 0x40) delta += step; + if (*inbuf & 0x80) delta = -delta; + + pcmdata += delta; + index += index_table[(*inbuf >> 4) & 0x7]; + CLIP(index, 0, 88); + CLIP(pcmdata, -32768, 32767); + outbuf [i * 2 + 1] = pcmdata>>8u; + inbuf++; + } + + outbuf += 8; + } + + return samples; +#else + extern int adpcm_decode_block (int16_t *outbuf, const uint8_t *inbuf, size_t inbufsize, int channels); + static int16_t tmp[ADPCM_SAMPLES_PER_BLOCK_SIZE]; + int samples = adpcm_decode_block(tmp, inbuf, inbufsize, 1); + for(int s=0;sdata == channel->data_end) { + channel->decompressed_size = 0; + } else { + int block_size = MIN(ADPCM_BLOCK_SIZE, channel->data_end - channel->data); + channel->decompressed_size = adpcm_decode_block_s8(channel->decompressed, channel->data, block_size); + assert(channel->decompressed_size && channel->decompressed_size <= sizeof(channel->decompressed)); + channel->data += block_size; + } +} + +static boolean init_channel_for_sfx(channel_t *ch, const sfxinfo_t *sfxinfo, int pitch) +{ + int lumpnum = sfx_mut(sfxinfo)->lumpnum; + int lumplen = W_LumpLength(lumpnum); + + const uint8_t *data = W_CacheLumpNum(lumpnum, PU_STATIC); // we don't track because we assume in ROWAD anyway + + if (lumplen < 8 || data[0] != 0x03 || data[1] != 0x80) // note 0x80 i.e. only support compressed right now + { + return false; + } + + // 16 bit sample rate field, 32 bit length field + +// int length = (data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4]; +// length -= 40; // 8 for header, 32 because we didn't updated it in lump converter (which cuts of unused 16 bit leading/leadout) +// if (length <= 0) { +// return false; +// } + int length = lumplen - 8; +// printf("channel %d lump %d size %d at %p len2 %d\n", (int)(ch-channels), lumpnum, lumplen, data, length); + + ch->data = data + 8; + ch->data_end = ch->data + length; + + uint32_t sample_freq = (data[3] << 8) | data[2]; + if (pitch == NORM_PITCH) + ch->step = sample_freq * 65536 / PICO_SOUND_SAMPLE_FREQ; + else + ch->step = (uint32_t)((sample_freq * pitch) * 65536ull / (PICO_SOUND_SAMPLE_FREQ * pitch)); + + decompress_buffer(ch); // we need non-zero decompressed size if playing + ch->offset = 0; + +#if SOUND_LOW_PASS +// const float dt = 1.0f / PICO_SOUND_SAMPLE_FREQ; +// const float rc = 1.0f / (3.14f * sample_freq); +// const float alpha = dt / (rc + dt); +// ch->alpha256 = (int)(256*alpha); + ch->alpha256 = 256u * 201u * sample_freq / (201u * sample_freq + 64u * (uint)PICO_SOUND_SAMPLE_FREQ); +#endif + return true; +} + +static void GetSfxLumpName(const sfxinfo_t *sfx, char *buf, size_t buf_len) +{ + // Linked sfx lumps? Get the lump number for the sound linked to. + if (sfx->link != NULL) + { + sfx = sfx->link; + } + + // Doom adds a DS* prefix to sound lumps; Heretic and Hexen don't + // do this. + + if (use_sfx_prefix) + { + M_snprintf(buf, buf_len, "ds%s", DEH_String(sfx->name)); + } + else + { + M_StringCopy(buf, DEH_String(sfx->name), buf_len); + } +} + +static void I_Pico_PrecacheSounds(should_be_const sfxinfo_t *sounds, int num_sounds) +{ + // no-op +} + +static int I_Pico_GetSfxLumpNum(should_be_const sfxinfo_t *sfx) +{ + char namebuf[9]; + GetSfxLumpName(sfx, namebuf, sizeof(namebuf)); + return W_GetNumForName(namebuf); +} + +static void I_Pico_UpdateSoundParams(int handle, int vol, int sep) +{ + int left, right; + + if (!sound_initialized || handle < 0 || handle >= NUM_SOUND_CHANNELS) + { + return; + } + + // todo graham seems unnecessary + left = ((254 - sep) * vol) / 127; + right = ((sep) * vol) / 127; + + if (left < 0) left = 0; + else if ( left > 255) left = 255; + if (right < 0) right = 0; + else if (right > 255) right = 255; + + channels[handle].left = left; + channels[handle].right = right; +} + +static int I_Pico_StartSound(should_be_const sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch) +{ + if (!check_and_init_channel(channel)) return -1; + + stop_channel(channel); + channel_t *ch = &channels[channel]; + if (!init_channel_for_sfx(ch, sfxinfo, pitch)) { + assert(!is_channel_playing(channel)); // don't expect to have to mark it sotpped + } + I_Pico_UpdateSoundParams(channel, vol, sep); + return channel; +} + +static void I_Pico_StopSound(int channel) +{ + if (check_and_init_channel(channel)) { + + } +} + +static boolean I_Pico_SoundIsPlaying(int channel) +{ + if (!check_and_init_channel(channel)) return false; + return is_channel_playing(channel); +} + +static void I_Pico_UpdateSound(void) +{ + if (!sound_initialized) return; + + // todo note this is called from D_Main around the game loop, which is fast enough now but may not be. + // we can either poll more frequently, or use IRQ but then we have to be careful with threading (both OPL and channels) + // todo hopefully at least we can run the AI fast enough. + audio_buffer_t *buffer = take_audio_buffer(producer_pool, false); + if (buffer) { + if (music_generator) { + // todo think about volume; this already has a (<< 3) in it + music_generator(buffer); + } else { + memset(buffer->buffer->bytes, 0, buffer->buffer->size); + } + for(int ch=0; ch < NUM_SOUND_CHANNELS; ch++) { + if (is_channel_playing(ch)) { + channel_t *channel = &channels[ch]; + assert(channel->decompressed_size); + int voll = channel->left/2; + int volr = channel->right/2; + uint offset_end = channel->decompressed_size * 65536; + assert(channel->offset < offset_end); + int16_t *samples = (int16_t *)buffer->buffer->bytes; +#if SOUND_LOW_PASS + int alpha256 = channel->alpha256; + int beta256 = 256 - alpha256; + int sample = channel->decompressed[channel->offset >> 16]; +#endif + for(int s=0;smax_sample_count;s++) { +#if !SOUND_LOW_PASS + int sample = channel->decompressed[channel->offset >> 16]; +#else + // todo graham, note that since we are all at the same frequency (and it isn't the end + // of the world anyway, we could do this across all channels at once) + sample = (beta256 * sample + alpha256 * channel->decompressed[channel->offset >> 16]) / 256; +#endif + *samples++ += sample * voll; + *samples++ += sample * volr; + channel->offset += channel->step; + if (channel->offset >= offset_end) { + channel->offset -= offset_end; + decompress_buffer(channel); + offset_end = channel->decompressed_size * 65536; + if (channel->offset >= offset_end) { + stop_channel(ch); + break; + } + } + } + } + } + buffer->sample_count = buffer->max_sample_count; + if (fade_state == FS_SILENT) { + memset(buffer->buffer->bytes, 0, buffer->buffer->size); + } else if (fade_state != FS_NONE) { + int16_t *samples = (int16_t *)buffer->buffer->bytes; + int fade_step = fade_state == FS_FADE_IN ? FADE_STEP : -FADE_STEP; + int i; + for(i=0;isample_count * 2 && fade_level;i+=2) { + samples[i] = (samples[i] * (int)fade_level) >> 16; + samples[i+1] = (samples[i+1] * (int)fade_level) >> 16; + fade_level += fade_step; + } + if (!fade_level) { + if (fade_state == FS_FADE_OUT) { + for(;isample_count * 2;i++) { + samples[i] = 0; + } + fade_state = FS_SILENT; + } else { + fade_state = FS_NONE; + } + } + } + give_audio_buffer(producer_pool, buffer); + } +} + +static void I_Pico_ShutdownSound(void) +{ + if (!sound_initialized) + { + return; + } + sound_initialized = false; +} + +static boolean I_Pico_InitSound(boolean _use_sfx_prefix) +{ + int i; + use_sfx_prefix = _use_sfx_prefix; + + // todo this will likely need adjustment - maybe with IRQs/double buffer & pull from audio we can make it quite small + producer_pool = audio_new_producer_pool(&producer_format, 2, 1024); // todo correct size + + struct audio_i2s_config config = { + .data_pin = PICO_AUDIO_I2S_DATA_PIN, + .clock_pin_base = PICO_AUDIO_I2S_CLOCK_PIN_BASE, + .dma_channel = 6, + .pio_sm = 0, + }; + + const struct audio_format *output_format; + output_format = audio_i2s_setup(&audio_format, &config); + if (!output_format) { + panic("PicoAudio: Unable to open audio device.\n"); + } + +#if INCREASE_I2S_DRIVE_STRENGTH + bi_decl(bi_program_feature("12mA I2S")); + gpio_set_drive_strength(PICO_AUDIO_I2S_DATA_PIN, GPIO_DRIVE_STRENGTH_12MA); + gpio_set_drive_strength(PICO_AUDIO_I2S_CLOCK_PIN_BASE, GPIO_DRIVE_STRENGTH_12MA); + gpio_set_drive_strength(PICO_AUDIO_I2S_CLOCK_PIN_BASE+1, GPIO_DRIVE_STRENGTH_12MA); +#endif + // we want to pass thr + bool ok = audio_i2s_connect_extra(producer_pool, false, 0, 0, NULL); + assert(ok); + audio_i2s_set_enabled(true); + + sound_initialized = true; + return true; +} + +static snddevice_t sound_pico_devices[] = +{ + SNDDEVICE_SB, +}; + +sound_module_t sound_pico_module = +{ + sound_pico_devices, + arrlen(sound_pico_devices), + I_Pico_InitSound, + I_Pico_ShutdownSound, + I_Pico_GetSfxLumpNum, + I_Pico_UpdateSound, + I_Pico_UpdateSoundParams, + I_Pico_StartSound, + I_Pico_StopSound, + I_Pico_SoundIsPlaying, + I_Pico_PrecacheSounds, +}; + +bool I_PicoSoundIsInitialized(void) { + return sound_initialized; +} + +void I_PicoSoundSetMusicGenerator(void (*generator)(audio_buffer_t *buffer)) { + music_generator = generator; +} + +#if PICO_ON_DEVICE +void I_PicoSoundFade(bool in) { + fade_state = in ? FS_FADE_IN : FS_FADE_OUT; + fade_level = in ? FADE_STEP : 0x10000 - FADE_STEP; +} + +bool I_PicoSoundFading(void) { + return fade_state == FS_FADE_IN || fade_state == FS_FADE_OUT; +} +#endif diff --git a/src/pico/i_picosound.h b/src/pico/i_picosound.h new file mode 100644 index 00000000..1328f83b --- /dev/null +++ b/src/pico/i_picosound.h @@ -0,0 +1,41 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2008 David Flater +// Copyright(C) 2021-2022 Graham Sanderson +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System interface for sound. + +#ifndef __I_PICO_SOUND__ +#define __I_PICO_SOUND__ + +#include "pico.h" +typedef struct audio_buffer audio_buffer_t; + +#if USE_EMU8950_OPL +#define PICO_SOUND_SAMPLE_FREQ 49716 +#else +#define PICO_SOUND_SAMPLE_FREQ 44100 +#endif + +#ifndef NUM_SOUND_CHANNELS +// this is the defaul tin game not 16 +#define NUM_SOUND_CHANNELS 8 +#endif + +void I_PicoSoundSetMusicGenerator(void (*generator)(audio_buffer_t *buffer)); +bool I_PicoSoundIsInitialized(void); +void I_PicoSoundFade(bool in); +bool I_PicoSoundFading(void); +#endif diff --git a/src/pico/i_system.c b/src/pico/i_system.c new file mode 100644 index 00000000..a1ef4e82 --- /dev/null +++ b/src/pico/i_system.c @@ -0,0 +1,652 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// + +#include "pico.h" +#include "pico/stdio.h" +#if PICO_ON_DEVICE +#include "hardware/watchdog.h" +#include "doomtype.h" +#include "doom/p_saveg.h" +#endif +extern void I_InputInit(); + +#include +#include +#include + +#include + +#include +#include + +#include "config.h" + +#include "deh_str.h" +#include "doomtype.h" +#include "m_argv.h" +#include "m_config.h" +#include "m_misc.h" +#include "i_joystick.h" +#include "i_sound.h" +#include "i_timer.h" +#include "i_video.h" + +#include "i_system.h" +#include "i_input.h" +#include "doomkeys.h" + +#include "w_wad.h" +#include "z_zone.h" +#include "picodoom.h" + +#define DEFAULT_RAM 16 /* MiB */ +#define MIN_RAM 4 /* MiB */ + + +typedef struct atexit_listentry_s atexit_listentry_t; + +struct atexit_listentry_s +{ + atexit_func_t func; + boolean run_on_error; + atexit_listentry_t *next; +}; + +static atexit_listentry_t *exit_funcs = NULL; + +void I_AtExit(atexit_func_t func, boolean run_on_error) +{ + atexit_listentry_t *entry; + + entry = malloc(sizeof(*entry)); + + entry->func = func; + entry->run_on_error = run_on_error; + entry->next = exit_funcs; + exit_funcs = entry; +} + +// Tactile feedback function, probably used for the Logitech Cyberman + +void I_Tactile(int on, int off, int total) +{ +} + +// Zone memory auto-allocation function that allocates the zone size +// by trying progressively smaller zone sizes until one is found that +// works. + +static byte *AutoAllocMemory(int *size, int default_ram, int min_ram) +{ + byte *zonemem; + + // Allocate the zone memory. This loop tries progressively smaller + // zone sizes until a size is found that can be allocated. + // If we used the -mb command line parameter, only the parameter + // provided is accepted. + + zonemem = NULL; + + while (zonemem == NULL) + { + // We need a reasonable minimum amount of RAM to start. + + if (default_ram < min_ram) + { + I_Error("Unable to allocate %i MiB of RAM for zone", default_ram); + } + +#if !USE_ZONE_FOR_MALLOC + // Try to allocate the zone memory. + + *size = default_ram * 1024 * 1024; + +#if DOOM_SMALL +// *size = (384+64) * 1024; +#if DOOM_TINY +#if PICO_ON_DEVICE +#if PICODOOM_RENDER_BABY + // todo temp since we put the buffers here + *size = 160 *1024; +#else + *size = 40 * 1024; +#endif +#else +// *size = 384 *1024; + *size = 256 *1024; +#endif +#endif +#endif + zonemem = malloc(*size); +#else +#if PICO_ON_DEVICE + // we have set heap size to 0, so __HeapLimit is a good value + extern char __HeapLimit; + zonemem = (uint8_t *)(((uintptr_t)&__HeapLimit)&~3); + *size = ((uint8_t *)SRAM4_BASE) - zonemem; +#else +#error use zone for malloc only on device +#endif +// zonemem = static_zone_mem; +// *size = count_of(static_zone_mem); +#endif + // Failed to allocate? Reduce zone size until we reach a size + // that is acceptable. + + if (zonemem == NULL) + { + default_ram -= 1; + } + } + + return zonemem; +} + +byte *I_ZoneBase (int *size) +{ + byte *zonemem; + int min_ram, default_ram; + int p; + + //! + // @category obscure + // @arg + // + // Specify the heap size, in MiB (default 16). + // + +#if !NO_USE_ARGS + p = M_CheckParmWithArgs("-mb", 1); + if (p > 0) + { + default_ram = atoi(myargv[p+1]); + min_ram = default_ram; + } + else +#endif + { + default_ram = DEFAULT_RAM; + min_ram = MIN_RAM; + } + + zonemem = AutoAllocMemory(size, default_ram, min_ram); + + printf("zone memory: %p, %x allocated for zone\n", + zonemem, *size); + + return zonemem; +} + +void I_PrintBanner(const char *msg) +{ + int i; + int spaces = 35 - (strlen(msg) / 2); + + for (i=0; i= 'a' && *foo <= 'z') *foo -= 'a' - 'A'; + foo++; + } + *foo++ = 0; + if (!strcmp((char*)cmd, "DOOM") || !strcmp((char*)cmd, "DOOM.EXE")) restart(); + else if (!strcmp((char*)kb_buffer, "CLS")) { + memset(text_screen_data, 0, 80 * 25 * 2); + entry_line = 0; +#if PICO_NO_HARDWARE + } else if (!strcmp((char*)kb_buffer, "exit")) { + exit(0); +#endif + } else if (!strcmp((char*)cmd, "CD")) { + while (foo < kb_buffer + l && *foo == ' ') foo++; + if (foo >= kb_buffer + l || !*foo) { + write_text_line("A:\\"); + } else if (*foo == '.' && (!foo[1] || foo[1]==' ')) { + } else { + write_text_line("Invalid directory"); + } + scroll_line(); + scroll_line(); + } else if (!strcmp((char*)cmd, "DIR")) { + write_text_line("Directory of A:\\"); + scroll_line(); + char buf[80]; + strcpy(buf, "11/02/1994 01:20 AM DOOM.EXE"); + int binsize; +#if PICO_ON_DEVICE + extern char __flash_binary_start; + extern char __flash_binary_end; + binsize = &__flash_binary_end - &__flash_binary_start; +#else + binsize = 2428760; +#endif +#if PICO_ON_DEVICE + int disksize = (int)(get_end_of_flash() - (const uint8_t *)XIP_BASE); +#else + int disksize = 1u << (32 - __builtin_clz(binsize + whdheader->size)); +#endif + write_num(binsize, buf + 27, 9); + write_text_line(buf); + sprintf(buf, "11/02/1994 07:03 PM %s", whdheader->name); + write_num(whdheader->size, buf + 27, 9); + write_text_line(buf); + int dsg_size = 0; +#define SHOW_SLOTS 1 +#if PICO_ON_DEVICE && SHOW_SLOTS + flash_slot_info_t slots[7]; + P_SaveGameGetExistingFlashSlotAddresses(slots, 7); + int filecount = 2; + for(int i=0;i<7;i++) { + if (slots[i].data) { + sprintf(buf, "12/11/2021 04:21 PM %d.DSG", i); + write_num(slots[i].size + 8, buf + 27, 9); + write_text_line(buf); + dsg_size += slots[i].size + 8; + filecount++; + } + } + int filesize = whd_map_base + whdheader->size - (uint8_t *)XIP_BASE; + scroll_line(); + sprintf(buf, " %d File(s) bytes", filecount); +#else + int filesize = ((binsize + 2047) & ~2047) + ((whdheader->size + 2047) & ~2047); + scroll_line(); + strcpy(buf, " 2 File(s) bytes"); +#endif + write_num(binsize + whdheader->size + dsg_size, buf + 23, 9); + write_text_line(buf); + strcpy(buf, " 0 Dir(s) bytes free"); + int remaining = disksize - filesize - dsg_size; + if (remaining < 0) remaining = 0; + write_num(remaining, buf + 23, 9); + write_text_line(buf); + scroll_line(); + } else { + if (l) { + write_text_line("Bad command or file name"); + scroll_line(); + } + scroll_line(); + } + kb_buffer[0] = 0; + } else if (scancode == SCANCODE_ESCAPE) { + kb_buffer[0] = 0; + } else if (scancode == SCANCODE_BACKSPACE) { + if (l) kb_buffer[l - 1] = 0; + } else if (scancode == SCANCODE_TAB) { + for(int max = (l + 8) & ~7; l < max && l < 80 - 4; l++) { + kb_buffer[l] = 32; + } + kb_buffer[l+1] = 0; + } else { + int key = GetTypedChar(scancode, shift); + if (key && key < 127 && l < 80 - 4) { + kb_buffer[l] = key; + kb_buffer[l+1] = 0; + } + } + uint8_t *line = text_screen_data + 160 * entry_line; + memcpy(line, "A\007:\007\\\007", 6); + int i = 6; + l = strlen((char*)kb_buffer); + for(int j=0;jdebug +// -d 0:0 +// +// DOS 6.22: +// 0000:0000 (57 92 19 00) F4 06 70 00-(16 00) +// DOS 7.1: +// 0000:0000 (9E 0F C9 00) 65 04 70 00-(16 00) +// Win98: +// 0000:0000 (9E 0F C9 00) 65 04 70 00-(16 00) +// DOSBox under XP: +// 0000:0000 (00 00 00 F1) ?? ?? ?? 00-(07 00) + +#define DOS_MEM_DUMP_SIZE 10 + +static const unsigned char mem_dump_dos622[DOS_MEM_DUMP_SIZE] = { + 0x57, 0x92, 0x19, 0x00, 0xF4, 0x06, 0x70, 0x00, 0x16, 0x00}; +static const unsigned char mem_dump_win98[DOS_MEM_DUMP_SIZE] = { + 0x9E, 0x0F, 0xC9, 0x00, 0x65, 0x04, 0x70, 0x00, 0x16, 0x00}; +static const unsigned char mem_dump_dosbox[DOS_MEM_DUMP_SIZE] = { + 0x00, 0x00, 0x00, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00}; +static unsigned char mem_dump_custom[DOS_MEM_DUMP_SIZE]; + +static const unsigned char *dos_mem_dump = mem_dump_dos622; + +boolean I_GetMemoryValue(unsigned int offset, void *value, int size) +{ + static boolean firsttime = true; + + if (firsttime) + { + int p, i, val; + + firsttime = false; + i = 0; + + //! + // @category compat + // @arg + // + // Specify DOS version to emulate for NULL pointer dereference + // emulation. Supported versions are: dos622, dos71, dosbox. + // The default is to emulate DOS 7.1 (Windows 98). + // + +#if !NO_USE_ARGS + p = M_CheckParmWithArgs("-setmem", 1); + + if (p > 0) + { + if (!strcasecmp(myargv[p + 1], "dos622")) + { + dos_mem_dump = mem_dump_dos622; + } + if (!strcasecmp(myargv[p + 1], "dos71")) + { + dos_mem_dump = mem_dump_win98; + } + else if (!strcasecmp(myargv[p + 1], "dosbox")) + { + dos_mem_dump = mem_dump_dosbox; + } + else + { + for (i = 0; i < DOS_MEM_DUMP_SIZE; ++i) + { + ++p; + + if (p >= myargc || myargv[p][0] == '-') + { + break; + } + + M_StrToInt(myargv[p], &val); + mem_dump_custom[i++] = (unsigned char) val; + } + + dos_mem_dump = mem_dump_custom; + } + } +#endif + } + + switch (size) + { + case 1: + *((unsigned char *) value) = dos_mem_dump[offset]; + return true; + case 2: + *((unsigned short *) value) = dos_mem_dump[offset] + | (dos_mem_dump[offset + 1] << 8); + return true; + case 4: + *((unsigned int *) value) = dos_mem_dump[offset] + | (dos_mem_dump[offset + 1] << 8) + | (dos_mem_dump[offset + 2] << 16) + | (dos_mem_dump[offset + 3] << 24); + return true; + } + + return false; +} + +#if PICO_ON_DEVICE + +#if USE_ZONE_FOR_MALLOC +boolean disallow_core1_malloc; +void *__wrap_malloc(size_t size) { + hard_assert(!(get_core_num() && disallow_core1_malloc)); // there should be no allocation from core 1 after startup + void * rc = Z_MallocNoUser((int)size, PU_STATIC); + return rc; +} + +void *__wrap_calloc(size_t count, size_t size) { + hard_assert(!(get_core_num() && disallow_core1_malloc)); // there should be no allocation from core 1 after startup + void * rc = Z_MallocNoUser((int)(count * size), PU_STATIC); + memset(rc, 0, count * size); + return rc; +} + +void __wrap_free(void *mem) { + hard_assert(!(get_core_num() && disallow_core1_malloc)); // there should be no allocation from core 1 after startup + Z_Free(mem); +} +#endif +#endif \ No newline at end of file diff --git a/src/pico/i_timer.c b/src/pico/i_timer.c new file mode 100644 index 00000000..e22699f6 --- /dev/null +++ b/src/pico/i_timer.c @@ -0,0 +1,61 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Timer functions. +// + +#include "pico/time.h" + +#include "i_timer.h" +#include "doomtype.h" + +// +// I_GetTime +// returns time in 1/35th second tics +// + +int I_GetTime (void) +{ + return TICRATE * (uint32_t)(time_us_64() / 1000); +} + +// +// Same as I_GetTime, but returns time in milliseconds +// + +int I_GetTimeMS(void) +{ + return (int)(time_us_64() / 1000); +} + +// Sleep for a specified number of ms + +void I_Sleep(int ms) +{ + sleep_ms(ms); +} + +void I_WaitVBL(int count) +{ + // todo + I_Sleep((count * 1000) / 70); +} + + +void I_InitTimer(void) +{ +} + diff --git a/src/pico/i_video.c b/src/pico/i_video.c new file mode 100644 index 00000000..3a5bc244 --- /dev/null +++ b/src/pico/i_video.c @@ -0,0 +1,1497 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// DOOM graphics stuff for Pico. +// + +#if PICODOOM_RENDER_NEWHOPE +#include +#include +#include +#include +#include "doom/f_wipe.h" +#include "pico.h" + +#include "config.h" +#include "d_loop.h" +#include "deh_str.h" +#include "doomtype.h" +#include "i_input.h" +#include "i_joystick.h" +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" +#include "m_argv.h" +#include "m_config.h" +#include "m_misc.h" +#include "tables.h" +#include "v_diskicon.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +#include "pico/scanvideo.h" +#include "pico/scanvideo/composable_scanline.h" +#include "pico/multicore.h" +#include "pico/sync.h" +#include "pico/time.h" +#include "hardware/gpio.h" +#include "picodoom.h" +#include "video_doom.pio.h" +#include "image_decoder.h" +#if PICO_ON_DEVICE +#include "hardware/dma.h" +#include "hardware/structs/xip_ctrl.h" +#endif + +#define YELLOW_SUBMARINE 0 +#define SUPPORT_TEXT 1 +#if SUPPORT_TEXT +typedef struct __packed { + const char * const name; + const uint8_t * const data; + const uint8_t w; + const uint8_t h; +} txt_font_t; +#define TXT_SCREEN_W 80 +#include "fonts/normal.h" + +static uint16_t ega_colors[] = { + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0x00, 0x00, 0x00), // 0: Black + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0x00, 0x00, 0xa8), // 1: Blue + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0x00, 0xa8, 0x00), // 2: Green + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0x00, 0xa8, 0xa8), // 3: Cyan + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0xa8, 0x00, 0x00), // 4: Red + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0xa8, 0x00, 0xa8), // 5: Magenta + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0xa8, 0x54, 0x00), // 6: Brown + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0xa8, 0xa8, 0xa8), // 7: Grey + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0x54, 0x54, 0x54), // 8: Dark grey + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0x54, 0x54, 0xfe), // 9: Bright blue + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0x54, 0xfe, 0x54), // 10: Bright green + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0x54, 0xfe, 0xfe), // 11: Bright cyan + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0xfe, 0x54, 0x54), // 12: Bright red + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0xfe, 0x54, 0xfe), // 13: Bright magenta + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0xfe, 0xfe, 0x54), // 14: Yellow + PICO_SCANVIDEO_PIXEL_FROM_RGB8(0xfe, 0xfe, 0xfe), // 15: Bright white +}; +#endif + +// todo temproarly turned this off because it causes a seeming bug in scanvideo (perhaps only with the new callback stuff) where the last repeated scanline of a pixel line is freed while shown +// note it may just be that this happens anyway, but usually we are writing slower than the beam? +#define USE_INTERP PICO_ON_DEVICE +#if USE_INTERP +#include "hardware/interp.h" +#endif + +CU_REGISTER_DEBUG_PINS(scanline_copy) +//CU_SELECT_DEBUG_PINS(scanline_copy) + +static const patch_t *stbar; + +volatile uint8_t interp_in_use; + +#define USE_1280x1024x60 1 + +// display has been set up? + +static boolean initialized = false; + +boolean screenvisible = true; + +//int vga_porch_flash = false; + +//static int startup_delay = 1000; + +// The screen buffer; this is modified to draw things to the screen +//pixel_t *I_VideoBuffer = NULL; +// Gamma correction level to use + +boolean screensaver_mode = false; + +isb_int8_t usegamma = 0; + +// Joystick/gamepad hysteresis +unsigned int joywait = 0; + +pixel_t *I_VideoBuffer; // todo can't have this + +uint8_t __aligned(4) frame_buffer[2][SCREENWIDTH*MAIN_VIEWHEIGHT]; +static uint16_t palette[256]; +static uint16_t __scratch_x("shared_pal") shared_pal[NUM_SHARED_PALETTES][16]; +static int8_t next_pal=-1; + +semaphore_t render_frame_ready, display_frame_freed; +semaphore_t core1_launch; + +uint8_t *text_screen_data; +static uint32_t *text_scanline_buffer_start; +static uint8_t *text_screen_cpy; +static uint8_t *text_font_cpy; + +#if USE_1280x1024x60 +//static uint32_t missing_scanline_data[] = { +// video_doom_offset_raw_1p | (0 << 16u), +// video_doom_offset_end_of_scanline_skip_ALIGN +//}; + +static uint32_t missing_scanline_data[] = + { +#if YELLOW_SUBMARINE + video_doom_offset_color_run | (PICO_SCANVIDEO_PIXEL_FROM_RGB8(255,255,0) << 16u), + 120 | (video_doom_offset_raw_1p << 16u), +#endif + 0u | (video_doom_offset_end_of_scanline_ALIGN << 16u) + }; + +#if PICO_ON_DEVICE +bool video_doom_adapt_for_mode(const struct scanvideo_pio_program *program, const struct scanvideo_mode *mode, + struct scanvideo_scanline_buffer *missing_scanvideo_scanline_buffer, uint16_t *modifiable_instructions); +pio_sm_config video_doom_configure_pio(pio_hw_t *pio, uint sm, uint offset); +#endif +#define VIDEO_DOOM_PROGRAM_NAME "doom" +const struct scanvideo_pio_program video_doom = { +#if PICO_ON_DEVICE + .program = &video_doom_program, + .adapt_for_mode = video_doom_adapt_for_mode, + .configure_pio = video_doom_configure_pio, +#else + .id = VIDEO_DOOM_PROGRAM_NAME +#endif +}; + +const scanvideo_timing_t vga_timing_1280x1000_60_default = // same as 1280x1024_60 standard just with some 12 blank lines at the top and bottom + { + .clock_freq = 108000000, + + .h_active = 1280, + .v_active = 1024 - 24, + + .h_front_porch = 48, + .h_pulse = 112, + .h_total = 1688, + .h_sync_polarity = 0, + + .v_front_porch = 1 + 24 - 12, // center our slightly short screen + .v_pulse = 3, + .v_total = 1066, + .v_sync_polarity = 0, + }; + +const scanvideo_timing_t vga_timing_640x1000_60_default = // same as 1280x1024_60 standard just with some 12 blank lines at the top and bottom + { + .clock_freq = 108000000 / 2, + +#if PICO_ON_DEVICE + .h_active = 1280 / 2, +#else + .h_active = 1280, +#endif + .v_active = 1024 - 24, + + .h_front_porch = 48 / 2, + .h_pulse = 112 / 2, + .h_total = 1688 / 2, + .h_sync_polarity = 0, + + .v_front_porch = 1 + 24 - 12, // center our slightly short screen + .v_pulse = 3, + .v_total = 1066, + .v_sync_polarity = 0, + }; + +const scanvideo_mode_t vga_mode_320x200 = + { + .default_timing = &vga_timing_640x1000_60_default, + .pio_program = &video_doom, +#if PICO_ON_DEVICE + .width = 320, +#else + .width = 640, +#endif + .height = 200, + .xscale = 2, + .yscale = 5, + }; +#define VGA_MODE vga_mode_320x200 +#elif USE_320x240x60 +#define VGA_MODE vga_mode_320x240_60 +#else +const scanvideo_mode_t vga_mode_320x200_60 = + { + .default_timing = &vga_timing_1280x1024_60_default, + .pio_program = &video_24mhz_composable, + .width = 320, + .height = 204, + .xscale = 4, + .yscale = 5, + }; + +#define VGA_MODE vga_mode_320x200_60 +#endif + +#if USE_INTERP +static interp_hw_save_t interp0_save, interp1_save; +static boolean interp_updated; +static boolean need_save; + +static inline void interp_save_static(interp_hw_t *interp, interp_hw_save_t *saver) { + saver->accum[0] = interp->accum[0]; + saver->accum[1] = interp->accum[1]; + saver->base[0] = interp->base[0]; + saver->base[1] = interp->base[1]; + saver->base[2] = interp->base[2]; + saver->ctrl[0] = interp->ctrl[0]; + saver->ctrl[1] = interp->ctrl[1]; +} + +static inline void interp_restore_static(interp_hw_t *interp, interp_hw_save_t *saver) { + interp->accum[0] = saver->accum[0]; + interp->accum[1] = saver->accum[1]; + interp->base[0] = saver->base[0]; + interp->base[1] = saver->base[1]; + interp->base[2] = saver->base[2]; + interp->ctrl[0] = saver->ctrl[0]; + interp->ctrl[1] = saver->ctrl[1]; +} +#endif + +void I_ShutdownGraphics(void) +{ +} + +// +// I_StartFrame +// +void I_StartFrame (void) +{ + // er? +} + +// +// Set the window title +// + +void I_SetWindowTitle(const char *title) +{ +// window_title = title; +} + +// +// I_SetPalette +// +void I_SetPaletteNum(int doompalette) +{ + next_pal = doompalette; +} + +// +// I_FinishUpdate +// +void I_FinishUpdate (void) +{ +} + +uint8_t display_frame_index; +uint8_t display_overlay_index; +uint8_t display_video_type; + +typedef void (*scanline_func)(uint32_t *dest, int scanline); + +static void scanline_func_none(uint32_t *dest, int scanline); +static void scanline_func_double(uint32_t *dest, int scanline); +static void scanline_func_single(uint32_t *dest, int scanline); +static void scanline_func_wipe(uint32_t *dest, int scanline); + +scanline_func scanline_funcs[] = { + scanline_func_none, // VIDEO_TYPE_NONE + NULL, // VIDEO_TYPE_TEXT + scanline_func_single, // VIDEO_TYPE_SAVING + scanline_func_double, // VIDEO_TYPE_DOUBLE + scanline_func_single, // VIDEO_TYPE_SINGLE + scanline_func_wipe, // VIDEO_TYPE_WIPE +}; + +uint8_t *wipe_yoffsets; // position of start of y in each column +int16_t *wipe_yoffsets_raw; +uint32_t *wipe_linelookup; // offset of each line from start of screenbuffer (can be negative for FB 1 to FB 0) +uint8_t next_video_type; +uint8_t next_frame_index; // todo combine with video type? +uint8_t next_overlay_index; +#if !DEMO1_ONLY +uint8_t *next_video_scroll; +uint8_t *video_scroll; +#endif +volatile uint8_t wipe_min; +uint32_t *saved_scanline_buffer_ptrs[PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT]; + +#pragma GCC push_options +#if PICO_ON_DEVICE +#pragma GCC optimize("O3") +#endif + +static inline void palette_convert_scanline(uint32_t *dest, const uint8_t *src) { +#if USE_INTERP + if (interp_updated != 1) { + if (need_save) { + interp_save_static(interp0, &interp0_save); + interp_save_static(interp1, &interp1_save); + } + interp_config c = interp_default_config(); + interp_config_set_shift(&c, 0); + interp_config_set_mask(&c, 0, 7); + interp_set_config(interp0, 0, &c); + interp_config_set_shift(&c, 16); + interp_set_config(interp1, 0, &c); + interp_config_set_shift(&c, 8); + interp_config_set_cross_input(&c, true); + interp_set_config(interp0, 1, &c); + interp_config_set_shift(&c, 24); + interp_set_config(interp1, 1, &c); + uint32_t palette_div2 = ((uintptr_t)palette) >> 1; + interp0->base[0] = palette_div2; + interp0->base[1] = palette_div2; + interp1->base[0] = palette_div2; + interp1->base[1] = palette_div2; + interp_updated = 1; + } + extern void palette8to16(uint32_t *dest, const uint8_t *src, uint words); + palette8to16(dest, src, SCREENWIDTH); +// dest[4] = (255-scanline) * 0x2000; + dest += SCREENWIDTH / 2; +// dest[-4] = (255-scanline) * 0x10001; +#else + for (int i = 0; i < SCREENWIDTH; i += 2) { + uint32_t val = palette[*src++]; + val |= (palette[*src++]) << 16; + *dest++ = val; + } +#endif +} +static void scanline_func_none(uint32_t *dest, int scanline) { + memset(dest, 0, SCREENWIDTH * 2); +} + +#if SUPPORT_TEXT +void check_text_buffer(scanvideo_scanline_buffer_t *buffer) { +#if PICO_ON_DEVICE + if (buffer->data < text_scanline_buffer_start || buffer->data >= text_scanline_buffer_start + TEXT_SCANLINE_BUFFER_TOTAL_WORDS) { + // is an original scanvideo allocated buffer, we need to use a larger one + int i; + for(i=0;idata; + buffer->data = text_scanline_buffer_start + i * TEXT_SCANLINE_BUFFER_WORDS; + } +#endif +} + +static void finish_text_buffer(scanvideo_scanline_buffer_t *buffer) { + uint16_t * p = (uint16_t *)buffer->data; + p[0] = video_doom_offset_raw_run_half; + p[1] = p[2]; + p[2] = SCREENWIDTH*2 - 3; + buffer->data[SCREENWIDTH + 1] = video_doom_offset_raw_1p; + buffer->data[SCREENWIDTH + 2] = video_doom_offset_end_of_scanline_skip_ALIGN; + buffer->data_used = SCREENWIDTH + 3; +} + +static void __not_in_flash_func(render_text_mode_half_scanline)(scanvideo_scanline_buffer_t *buffer, const uint8_t *text_data, int yoffset) { + uint16_t * p = (uint16_t *)(buffer->data + 1); +// memset(buffer->data + 1, 0, 1280); +// uint x = scanline * 2 + yoffset; +// buffer->data[1 + (x/2)] = x&1 ? 0xffff0000 : 0xffff; +#if 1 + uint blink = scanvideo_frame_number(buffer->scanline_id) & 16; + // not going to change so just hard code +// assert(normal_font.w == 8); +// assert(normal_font.h == 16); + const uint8_t *font_base = text_font_cpy + yoffset; + for(uint i=0;i<80;i++) { + uint fg = text_data[1] & 0xf; + uint bg = (text_data[1] >> 4) & 0xf; + if (bg & 0x8) { + bg &= ~0x8; + // blinking + if (blink) fg = bg; + } + // probably user error but this wasn't working correctly with the inline asm on the stack + static uint16_t colors[2]; + colors[0] = ega_colors[bg]; + colors[1] = ega_colors[fg]; + uint bits8 = font_base[text_data[0] * 16]; +#if PICO_ON_DEVICE + // todo use interpolator? + uint tmp1, tmp2, tmp3; + __asm__ volatile ( + ".syntax unified\n" + + "movs %[r_tmp3], #2\n" + "lsls %[r_tmp1],%[r_bits8],#1\n" + "ands %[r_tmp1],%[r_tmp3]\n" + "ldrh %[r_tmp1],[%[r_colors],%[r_tmp1]]\n" + + "movs %[r_tmp2],%[r_bits8]\n" + "ands %[r_tmp2],%[r_tmp3]\n" + "ldrh %[r_tmp2],[%[r_colors],%[r_tmp2]]\n" + + "lsls %[r_tmp2], #16\n" + "orrs %[r_tmp1], %[r_tmp2]\n" + "stmia %[r_p]!, {%[r_tmp1]}\n" + + "lsrs %[r_tmp1],%[r_bits8],#1\n" + "ands %[r_tmp1],%[r_tmp3]\n" + "ldrh %[r_tmp1],[%[r_colors],%[r_tmp1]]\n" + + "lsrs %[r_tmp2],%[r_bits8],#2\n" + "ands %[r_tmp2],%[r_tmp3]\n" + "ldrh %[r_tmp2],[%[r_colors],%[r_tmp2]]\n" + + "lsls %[r_tmp2], #16\n" + "orrs %[r_tmp1], %[r_tmp2]\n" + "stmia %[r_p]!, {%[r_tmp1]}\n" + + "lsrs %[r_tmp1],%[r_bits8],#3\n" + "ands %[r_tmp1],%[r_tmp3]\n" + "ldrh %[r_tmp1],[%[r_colors],%[r_tmp1]]\n" + + "lsrs %[r_tmp2],%[r_bits8],#4\n" + "ands %[r_tmp2],%[r_tmp3]\n" + "ldrh %[r_tmp2],[%[r_colors],%[r_tmp2]]\n" + + "lsls %[r_tmp2], #16\n" + "orrs %[r_tmp1], %[r_tmp2]\n" + "stmia %[r_p]!, {%[r_tmp1]}\n" + + "lsrs %[r_tmp1],%[r_bits8],#5\n" + "ands %[r_tmp1],%[r_tmp3]\n" + "ldrh %[r_tmp1],[%[r_colors],%[r_tmp1]]\n" + + "lsrs %[r_tmp2],%[r_bits8],#6\n" + "ands %[r_tmp2],%[r_tmp3]\n" + "ldrh %[r_tmp2],[%[r_colors],%[r_tmp2]]\n" + + "lsls %[r_tmp2], #16\n" + "orrs %[r_tmp1], %[r_tmp2]\n" + "stmia %[r_p]!, {%[r_tmp1]}\n" + + : [ r_p] "+l" (p), + [ r_tmp1] "=&l" (tmp1), + [ r_tmp2] "=&l" (tmp2), + [ r_tmp3] "=&l" (tmp3) + + : [ r_bits8] "l" (bits8), + [ r_colors] "l" (colors) + : + ); +#else + p[0] = colors[bits8&1]; + p[1] = colors[(bits8>>1)&1]; + p[2] = colors[(bits8>>2)&1]; + p[3] = colors[(bits8>>3)&1]; + p[4] = colors[(bits8>>4)&1]; + p[5] = colors[(bits8>>5)&1]; + p[6] = colors[(bits8>>6)&1]; + p[7] = colors[(bits8>>7)&1]; + p+=8; +#endif + text_data+=2; + } +#endif +} + +static void __noinline render_text_mode_scanline(scanvideo_scanline_buffer_t *buffer, int scanline) { +#if 1 + const uint8_t *text_data = text_screen_data; + assert(text_data); + text_data += TXT_SCREEN_W * 2 * (scanline/8); + check_text_buffer(buffer); + render_text_mode_half_scanline(buffer, text_data, (scanline & 7u)*2 ); + finish_text_buffer(buffer); + if (buffer->link) { + buffer->link_after = 2; + buffer->link->link_after = 0; + check_text_buffer(buffer->link); + render_text_mode_half_scanline(buffer->link, text_data, (scanline & 7u)*2 + 1); + finish_text_buffer(buffer->link); + } +#else + uint16_t *p = (uint16_t *)buffer->data; + p[0] = video_doom_offset_raw_run; + p[1] = p[2]; + p[2] = SCREENWIDTH - 3; + memset(buffer->data+1, 0x1f * ((scanline + 8) / 8), SCREENWIDTH * 2); + if (buffer->link) { + buffer->link_after = 2; + scanvideo_scanline_buffer_t *buffer2 = buffer->link; + memset(buffer2->data+1, 0xf1 * ((scanline + 8) / 8), SCREENWIDTH * 2); + p = (uint16_t *)buffer2->data; + p[0] = video_doom_offset_raw_run; + p[1] = p[2]; + p[2] = SCREENWIDTH - 3; + buffer2->data[SCREENWIDTH / 2 + 1] = video_doom_offset_raw_1p; + buffer2->data[SCREENWIDTH / 2 + 2] = video_doom_offset_end_of_scanline_skip_ALIGN; + buffer2->data_used = SCREENWIDTH / 2 + 3; + } + buffer->data[SCREENWIDTH / 2 + 1] = video_doom_offset_raw_1p; + buffer->data[SCREENWIDTH / 2 + 2] = video_doom_offset_end_of_scanline_skip_ALIGN; + buffer->data_used = SCREENWIDTH / 2 + 3; +#endif +} +#endif + +static void __scratch_x("scanlines") scanline_func_double(uint32_t *dest, int scanline) { + if (scanline < MAIN_VIEWHEIGHT) { + const uint8_t *src = frame_buffer[display_frame_index] + scanline * SCREENWIDTH; +// if (scanline == 100) { +// printf("SL %d %p\n", display_frame_index, &frame_buffer[display_frame_index]); +// } + palette_convert_scanline(dest, src); + } else { + // we expect everything to be overdrawn by statusbar so we do nothing + } +} + +static void __not_in_flash_func(scanline_func_single)(uint32_t *dest, int scanline) { + uint8_t *src; + if (scanline < MAIN_VIEWHEIGHT) { + src = frame_buffer[display_frame_index] + scanline * SCREENWIDTH; + } else { + src = frame_buffer[display_frame_index^1] + (scanline - 32) * SCREENWIDTH; + } +#if !DEMO1_ONLY + if (video_scroll) { + for(int i=SCREENWIDTH-1;i>0;i--) { + src[i] = src[i-1]; + } + src[0] = video_scroll[scanline]; + } +#endif + palette_convert_scanline(dest, src); +} + +static void scanline_func_wipe(uint32_t *dest, int scanline) { + const uint8_t *src; +#if 0 + if (scanline < MAIN_VIEWHEIGHT) { + src = frame_buffer[display_frame_index^1] + scanline * SCREENWIDTH; + } else { + src = frame_buffer[display_frame_index] + (scanline - 32) * SCREENWIDTH; + } + palette_convert_scanline(dest, src); + return; +#endif + if (scanline < MAIN_VIEWHEIGHT) { + src = frame_buffer[display_frame_index]; + } else { + src = frame_buffer[display_frame_index^1] - 32 * SCREENWIDTH; + } + assert(wipe_yoffsets && wipe_linelookup); + uint16_t *d = (uint16_t *)dest; + src += scanline * SCREENWIDTH; + for (int i = 0; i < SCREENWIDTH; i++) { + int rel = scanline - wipe_yoffsets[i]; + if (rel < 0) { + d[i] = palette[src[i]]; + } else { + const uint8_t *flip; +#if PICO_ON_DEVICE + flip = (const uint8_t *)wipe_linelookup[rel]; +#else + flip = &frame_buffer[0][0] + wipe_linelookup[rel]; +#endif + // todo better protection here + if (flip >= &frame_buffer[0][0] && flip < &frame_buffer[0][0] + 2 * SCREENWIDTH * MAIN_VIEWHEIGHT) { + d[i] = palette[flip[i]]; + } + } + } +} + +static inline uint draw_vpatch(uint16_t *dest, patch_t *patch, vpatchlist_t *vp, uint off) { + int repeat = vp->entry.repeat; + dest += vp->entry.x; + int w = vpatch_width(patch); + const uint8_t *data0 = vpatch_data(patch); + const uint8_t *data = data0 + off; + if (!vpatch_has_shared_palette(patch)) { + const uint8_t *pal = vpatch_palette(patch); + switch (vpatch_type(patch)) { + case vp4_runs: { + uint16_t *p = dest; + uint16_t *pend = dest + w; + uint8_t gap; + while (0xff != (gap = *data++)) { + p += gap; + int len = *data++; + for (int i = 1; i < len; i += 2) { + uint v = *data++; + *p++ = palette[pal[v & 0xf]]; + *p++ = palette[pal[v >> 4]]; + } + if (len & 1) { + *p++ = palette[pal[(*data++) & 0xf]]; + } + assert(p <= pend); + if (p == pend) break; + } + break; + } + case vp4_alpha: { + uint16_t *p = dest; + for (int i = 0; i < w / 2; i++) { + uint v = *data++; + if (v & 0xf) p[0] = palette[pal[v & 0xf]]; + if (v >> 4) p[1] = palette[pal[v >> 4]]; + p += 2; + } + if (w & 1) { + uint v = *data++; + if (v & 0xf) p[0] = palette[pal[v & 0xf]]; + } + break; + } + case vp4_solid: { + uint16_t *p = dest; + for (int i = 0; i < w / 2; i++) { + uint v = *data++; + p[0] = palette[pal[v & 0xf]]; + p[1] = palette[pal[v >> 4]]; + p += 2; + } + if (w & 1) { + uint v = *data++; + p[0] = palette[pal[v & 0xf]]; + } + break; + } + case vp6_runs: { + uint16_t *p = dest; + uint16_t *pend = dest + w; + uint8_t gap; + while (0xff != (gap = *data++)) { + p += gap; + int len = *data++; + for (int i = 3; i < len; i += 4) { + uint v = *data++; + v |= (*data++) << 8; + v |= (*data++) << 16; + *p++ = palette[pal[v & 0x3f]]; + *p++ = palette[pal[(v >> 6) & 0x3f]]; + *p++ = palette[pal[(v >> 12) & 0x3f]]; + *p++ = palette[pal[(v >> 18) & 0x3f]]; + } + len &= 3; + if (len--) { + uint v = *data++; + *p++ = palette[pal[v & 0x3f]]; + if (len--) { + v >>= 6; + v |= (*data++) << 2; + *p++ = palette[pal[v & 0x3f]]; + if (len--) { + v >>= 6; + v |= (*data++) << 4; + *p++ = palette[pal[v & 0x3f]]; + assert(!len); + } + } + } + assert(p <= pend); + if (p == pend) break; + } + break; + } + case vp8_runs: { + uint16_t *p = dest; + uint16_t *pend = dest + w; + uint8_t gap; + while (0xff != (gap = *data++)) { + p += gap; + int len = *data++; + for (int i = 0; i < len; i++) { + *p++ = palette[pal[*data++]]; + } + assert(p <= pend); + if (p == pend) break; + } + break; + } + case vp_border: { + dest[0] = palette[*data++]; + uint16_t col = palette[*data++]; + for (int i = 1; i < w - 1; i++) dest[i] = col; + dest[w-1] = palette[*data++]; + break; + } + default: + assert(false); + break; + } + } else { + uint sp = vpatch_shared_palette(patch); + uint16_t *pal16 = shared_pal[sp]; + assert(sp < NUM_SHARED_PALETTES); + switch (vpatch_type(patch)) { + case vp4_solid: { +#if PICO_ON_DEVICE + if (patch == stbar) { + static const uint8_t *cached_data; + static uint32_t __scratch_x("data_cache") data_cache[41]; + int i = 0; + uint32_t *d = (uint32_t *) dest; +#define DMA_CHANNEL 11 + if (cached_data == data) { + const uint8_t *source = (const uint8_t *) data_cache; + // we need to correct for the misalignment of data, because the XIP copy ignores the low 2 bits... + // the raw bitmap data is always misaligned by 3 (the size of the header in the case of stbar) + source += 3; + for (; source < (const uint8_t *) dma_hw->ch[DMA_CHANNEL].al1_write_addr; source++) { + uint32_t val = pal16[source[0] & 0xf]; + val |= (pal16[source[0] >> 4]) << 16; + *d++ = val; + } + source -= 3; + i = (source - (const uint8_t *) data_cache); + } + if (true) { + // once = true; + xip_ctrl_hw->stream_ctr = 0; + // workaround yucky bug + (void) *(io_rw_32 *) XIP_NOCACHE_NOALLOC_BASE; + xip_ctrl_hw->stream_fifo; + dma_channel_abort(DMA_CHANNEL); + dma_channel_config c = dma_channel_get_default_config(DMA_CHANNEL); + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, true); + channel_config_set_dreq(&c, DREQ_XIP_STREAM); + dma_channel_set_read_addr(DMA_CHANNEL, (void *) XIP_AUX_BASE, false); + dma_channel_set_config(DMA_CHANNEL, &c, false); + cached_data = data + SCREENWIDTH / 2; + xip_ctrl_hw->stream_addr = (uintptr_t) cached_data; + xip_ctrl_hw->stream_ctr = 41; + __compiler_memory_barrier(); + dma_channel_transfer_to_buffer_now(DMA_CHANNEL, data_cache, 41); + } + for (; i < SCREENWIDTH / 2; i++) { + uint32_t val = pal16[data[i] & 0xf]; + val |= (pal16[data[i] >> 4]) << 16; + *d++ = val; + } + data += SCREENWIDTH / 2; + break; // early break from switch + } +#endif + if (((uintptr_t)dest)&3) { + uint16_t *p = dest; + for (int i = 0; i < w / 2; i++) { + uint v = *data++; + p[0] = pal16[v & 0xf]; + p[1] = pal16[v >> 4]; + p += 2; + } + } else { + uint32_t *wide = (uint32_t *) dest; + for (int i = 0; i < w / 2; i++) { + uint v = *data++; + wide[i] = pal16[v & 0xf] | (pal16[v >> 4] << 16); + } + } + if (w & 1) { + uint v = *data++; + dest[w-1] = pal16[v & 0xf]; + } + break; + } + case vp4_alpha: { + uint16_t *p = dest; + for (int i = 0; i < w / 2; i++) { + uint v = *data++; + if (v & 0xf) p[0] = pal16[v & 0xf]; + if (v >> 4) p[1] = pal16[v >> 4]; + p += 2; + } + if (w & 1) { + uint v = *data++; + if (v & 0xf) p[0] = pal16[v & 0xf]; + } + break; + } + default: + assert(false); + } + } + if (repeat) { + // we need them to be solid... which they are, but if not you'll just get some visual funk + //assert(vpatch_type(patch) == vp4_solid); + if (vp->entry.patch_handle == VPATCH_M_THERMM) w--; // hackity hack + for(int i=0;i= FIRST_VIDEO_TYPE_WITH_OVERLAYS) { + memset(vpatchlists->vpatch_next, 0, sizeof(vpatchlists->vpatch_next)); + memset(vpatchlists->vpatch_starters, 0, sizeof(vpatchlists->vpatch_starters)); + memset(vpatchlists->vpatch_doff, 0, sizeof(vpatchlists->vpatch_doff)); + vpatchlist_t *overlays = vpatchlists->overlays[display_overlay_index]; + // do it in reverse so our linked lists are in ascending order + for (int i = overlays->header.size - 1; i > 0; i--) { + assert(overlays[i].entry.y < count_of(vpatchlists->vpatch_starters)); + vpatchlists->vpatch_next[i] = vpatchlists->vpatch_starters[overlays[i].entry.y]; + vpatchlists->vpatch_starters[overlays[i].entry.y] = i; + } + if (next_pal != -1) { + static const uint8_t *playpal; + static bool calculate_palettes; + if (!playpal) { + lumpindex_t l = W_GetNumForName("PLAYPAL"); + playpal = W_CacheLumpNum(l, PU_STATIC); + calculate_palettes = W_LumpLength(l) == 768; + } + if (!calculate_palettes || !next_pal) { + const uint8_t *doompalette = playpal + next_pal * 768; + for (int i = 0; i < 256; i++) { + int r = *doompalette++; + int g = *doompalette++; + int b = *doompalette++; + if (usegamma) { + r = gammatable[usegamma-1][r]; + g = gammatable[usegamma-1][g]; + b = gammatable[usegamma-1][b]; + } + palette[i] = PICO_SCANVIDEO_PIXEL_FROM_RGB8(r, g, b); + } + } else { + int mul, r0, g0, b0; + if (next_pal < 9) { + mul = next_pal * 65536 / 9; + r0 = 255; g0 = b0 = 0; + } else if (next_pal < 13) { + mul = (next_pal - 8) * 65536 / 8; + r0 = 215; g0 = 186; b0 = 69; + } else { + mul = 65536 / 8; + r0 = b0 = 0; g0 = 256; + } + const uint8_t *doompalette = playpal; + for (int i = 0; i < 256; i++) { + int r = *doompalette++; + int g = *doompalette++; + int b = *doompalette++; + r += ((r0 - r) * mul) >> 16; + g += ((g0 - g) * mul) >> 16; + b += ((b0 - b) * mul) >> 16; + palette[i] = PICO_SCANVIDEO_PIXEL_FROM_RGB8(r, g, b); + } + } + next_pal = -1; + assert(vpatch_type(stbar) == vp4_solid); // no transparent, no runs, 4 bpp + for (int i = 0; i < NUM_SHARED_PALETTES; i++) { + patch_t *patch = resolve_vpatch_handle(vpatch_for_shared_palette[i]); + assert(vpatch_colorcount(patch) <= 16); + assert(vpatch_has_shared_palette(patch)); + for (int j = 0; j < 16; j++) { + shared_pal[i][j] = palette[vpatch_palette(patch)[j]]; + } + } + } + if (display_video_type == VIDEO_TYPE_WIPE) { +// printf("WIPEMIN %d\n", wipe_min); + if (wipe_min <= 200) { + bool regular = display_overlay_index; // just happens to toggle every frame + int new_wipe_min = 200; + for (int i = 0; i < SCREENWIDTH; i++) { + int v; + if (wipe_yoffsets_raw[i] < 0) { + if (regular) { + wipe_yoffsets_raw[i]++; + } + v = 0; + } else { + int dy = (wipe_yoffsets_raw[i] < 16) ? (1 + wipe_yoffsets_raw[i] + regular) / 2 : 4; + if (wipe_yoffsets_raw[i] + dy > 200) { + v = 200; + } else { + wipe_yoffsets_raw[i] += dy; + v = wipe_yoffsets_raw[i]; + } + } + wipe_yoffsets[i] = v; + if (v < new_wipe_min) new_wipe_min = v; + } + assert(new_wipe_min >= wipe_min); + wipe_min = new_wipe_min; + } + } + } +} + +// this method moved out of scratchx because we didn't have quite enough space for core1 stack +void __no_inline_not_in_flash_func(new_frame_stuff)() { + // this part of the per frame code is in RAM as it is needed during save + if (sem_available(&render_frame_ready)) { + sem_acquire_blocking(&render_frame_ready); + display_video_type = next_video_type; + display_frame_index = next_frame_index; + display_overlay_index = next_overlay_index; +#if !DEMO1_ONLY + video_scroll = next_video_scroll; // todo does this waste too much space +#endif + sem_release(&display_frame_freed); + } else { +#if !DEMO1_ONLY + video_scroll = NULL; +#endif + } + if (display_video_type != VIDEO_TYPE_SAVING) { + // this stuff is large (so in flash) and not needed in save move + new_frame_init_overlays_palette_and_wipe(); + } +} + +void __scratch_x("scanlines") fill_scanlines() { +#if SUPPORT_TEXT + struct scanvideo_scanline_buffer *buffer = scanvideo_begin_scanline_generation_linked(display_video_type == VIDEO_TYPE_TEXT ? 2 : 1, false); +#else + struct scanvideo_scanline_buffer *buffer = scanvideo_begin_scanline_generation(false); +#endif +#if USE_INTERP + need_save = interp_in_use; + interp_updated = 0; +#endif + + while (buffer) { + static int8_t last_frame_number = -1; + int frame = scanvideo_frame_number(buffer->scanline_id); + int scanline = scanvideo_scanline_number(buffer->scanline_id); + if ((int8_t) frame != last_frame_number) { + last_frame_number = frame; + new_frame_stuff(); + } + + DEBUG_PINS_SET(scanline_copy, 1); + if (display_video_type != VIDEO_TYPE_TEXT) { + // we don't have text mode -> normal transition yet, but we may for network game, so leaving this here - we would need to put the buffer pointers back + assert (buffer->data < text_scanline_buffer_start || buffer->data >= text_scanline_buffer_start + TEXT_SCANLINE_BUFFER_TOTAL_WORDS); + scanline_funcs[display_video_type](buffer->data+1, scanline); + if (display_video_type >= FIRST_VIDEO_TYPE_WITH_OVERLAYS) { + assert(scanline < count_of(vpatchlists->vpatch_starters)); + int prev = 0; + for (int vp = vpatchlists->vpatch_starters[scanline]; vp;) { + int next = vpatchlists->vpatch_next[vp]; + while (vpatchlists->vpatch_next[prev] && vpatchlists->vpatch_next[prev] < vp) { + prev = vpatchlists->vpatch_next[prev]; + } + assert(prev != vp); + assert(vpatchlists->vpatch_next[prev] != vp); + vpatchlists->vpatch_next[vp] = vpatchlists->vpatch_next[prev]; + vpatchlists->vpatch_next[prev] = vp; + prev = vp; + vp = next; + } + vpatchlist_t *overlays = vpatchlists->overlays[display_overlay_index]; + prev = 0; + for (int vp = vpatchlists->vpatch_next[prev]; vp; vp = vpatchlists->vpatch_next[prev]) { + patch_t *patch = resolve_vpatch_handle(overlays[vp].entry.patch_handle); + int yoff = scanline - overlays[vp].entry.y; + if (yoff < vpatch_height(patch)) { + vpatchlists->vpatch_doff[vp] = draw_vpatch((uint16_t*)(buffer->data + 1), patch, &overlays[vp], + vpatchlists->vpatch_doff[vp]); + prev = vp; + } else { + vpatchlists->vpatch_next[prev] = vpatchlists->vpatch_next[vp]; + } + } + } + uint16_t *p = (uint16_t *) buffer->data; + p[0] = video_doom_offset_raw_run; + p[1] = p[2]; + p[2] = SCREENWIDTH - 3; + buffer->data[SCREENWIDTH / 2 + 1] = video_doom_offset_raw_1p; + buffer->data[SCREENWIDTH / 2 + 2] = video_doom_offset_end_of_scanline_skip_ALIGN; + buffer->data_used = SCREENWIDTH / 2 + 3; + DEBUG_PINS_CLR(scanline_copy, 1); + } else { +#if SUPPORT_TEXT + render_text_mode_scanline(buffer, scanline); +#else + memset(buffer->data + 1, 0, SCREENWIDTH * 2); + p[0] = video_doom_offset_raw_run; + p[1] = p[2]; + p[2] = SCREENWIDTH - 3; + buffer->data[SCREENWIDTH / 2 + 1] = video_doom_offset_raw_1p; + buffer->data[SCREENWIDTH / 2 + 2] = video_doom_offset_end_of_scanline_skip_ALIGN; + buffer->data_used = SCREENWIDTH / 2 + 3; +#endif + } + scanvideo_end_scanline_generation(buffer); +#if SUPPORT_TEXT + buffer = scanvideo_begin_scanline_generation_linked(display_video_type == VIDEO_TYPE_TEXT ? 2 : 1, false); +#else + buffer = scanvideo_begin_scanline_generation(false); +#endif + } +#if USE_INTERP + if (interp_updated && need_save) { + interp_restore_static(interp0, &interp0_save); + interp_restore_static(interp1, &interp1_save); + } +#endif +} +#pragma GCC pop_options + +#if PICO_ON_DEVICE +#define LOW_PRIO_IRQ 31 +#include "hardware/irq.h" + +static void __not_in_flash_func(free_buffer_callback)() { +// irq_set_pending(LOW_PRIO_IRQ); + // ^ is in flash by default + *((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ISPR_OFFSET)) = 1u << LOW_PRIO_IRQ; +} +#endif + +//static semaphore_t init_sem; +static void core1() { +#if !PICO_ON_DEVICE + void simulate_video_pio_video_doom(const uint32_t *dma_data, uint32_t dma_data_size, + uint16_t *pixel_buffer, int32_t max_pixels, int32_t expected_width, bool overlay); + scanvideo_set_simulate_scanvideo_pio_fn(VIDEO_DOOM_PROGRAM_NAME, simulate_video_pio_video_doom); +#endif + scanvideo_setup(&VGA_MODE); +// sem_release(&init_sem); +#if PICO_ON_DEVICE + irq_set_exclusive_handler(LOW_PRIO_IRQ, fill_scanlines); + irq_set_enabled(LOW_PRIO_IRQ, true); + scanvideo_set_scanline_release_fn(free_buffer_callback); +#endif + scanvideo_timing_enable(true); +#if PICO_ON_DEVICE + irq_set_pending(LOW_PRIO_IRQ); +#endif + sem_release(&core1_launch); + while (true) { + pd_core1_loop(); +#if PICO_ON_DEVICE + tight_loop_contents(); +#else + fill_scanlines(); +#endif + } +} + +void I_InitGraphics(void) +{ + stbar = resolve_vpatch_handle(VPATCH_STBAR); + sem_init(&render_frame_ready, 0, 2); + sem_init(&display_frame_freed, 1, 2); + sem_init(&core1_launch, 0, 1); + pd_init(); + multicore_launch_core1(core1); + // wait for core1 launch as it may do malloc and we have no mutex around that + sem_acquire_blocking(&core1_launch); +#if USE_ZONE_FOR_MALLOC + disallow_core1_malloc = true; +#endif + initialized = true; +} + +// Bind all variables controlling video options into the configuration +// file system. +void I_BindVideoVariables(void) +{ +// M_BindIntVariable("use_mouse", &usemouse); +// M_BindIntVariable("fullscreen", &fullscreen); +// M_BindIntVariable("video_display", &video_display); +// M_BindIntVariable("aspect_ratio_correct", &aspect_ratio_correct); +// M_BindIntVariable("integer_scaling", &integer_scaling); +// M_BindIntVariable("vga_porch_flash", &vga_porch_flash); +// M_BindIntVariable("startup_delay", &startup_delay); +// M_BindIntVariable("fullscreen_width", &fullscreen_width); +// M_BindIntVariable("fullscreen_height", &fullscreen_height); +// M_BindIntVariable("force_software_renderer", &force_software_renderer); +// M_BindIntVariable("max_scaling_buffer_pixels", &max_scaling_buffer_pixels); +// M_BindIntVariable("window_width", &window_width); +// M_BindIntVariable("window_height", &window_height); +// M_BindIntVariable("grabmouse", &grabmouse); +// M_BindStringVariable("video_driver", &video_driver); +// M_BindStringVariable("window_position", &window_position); +// M_BindIntVariable("usegamma", &usegamma); +// M_BindIntVariable("png_screenshots", &png_screenshots); +} + +// +// I_StartTic +// +void I_StartTic (void) +{ + if (!initialized) + { + return; + } + + I_GetEvent(); +// +// if (usemouse && !nomouse && window_focused) +// { +// I_ReadMouse(); +// } +// +// if (joywait < I_GetTime()) +// { +// I_UpdateJoystick(); +// } +} + + +// +// I_UpdateNoBlit +// +void I_UpdateNoBlit (void) +{ + // what is this? +} + +int I_GetPaletteIndex(int r, int g, int b) +{ + return 0; +} + +#if !NO_USE_ENDDOOM +void I_Endoom(byte *endoom_data) { + uint32_t size; + uint8_t *wa = pd_get_work_area(&size); + assert(size >=TEXT_SCANLINE_BUFFER_TOTAL_WORDS * 4 + 80*25*2 + 4096); + text_screen_cpy = wa; + text_font_cpy = text_screen_cpy + 80 * 25 * 2; + text_scanline_buffer_start = (uint32_t *) (text_font_cpy + 4096); +#if 0 + static_assert(sizeof(normal_font_data) == 4096, ""); + memcpy(text_font_cpy, normal_font_data, sizeof(normal_font_data)); + memcpy(text_screen_cpy, endoom_data, 80 * 25 * 2); +#else + static_assert(TEXT_SCANLINE_BUFFER_TOTAL_WORDS * 4 > 1024 + 512, ""); + uint8_t *tmp_buf = (uint8_t *)text_scanline_buffer_start; + uint16_t *decoder = (uint16_t *)(tmp_buf + 512); + th_bit_input bi; + th_bit_input_init(&bi, normal_font_data_z); + decode_data(text_font_cpy, 4096, &bi, decoder, 512, tmp_buf, 512); + th_bit_input_init(&bi, endoom_data); + // text + decode_data(text_screen_cpy, 80*25, &bi, decoder, 512, tmp_buf, 512); + // attr + decode_data(text_screen_cpy+80*25, 80*25, &bi, decoder, 512, tmp_buf, 512); + static_assert(TEXT_SCANLINE_BUFFER_TOTAL_WORDS * 4 > 80*25*2, ""); + // re-interlace the text & attr + memcpy(tmp_buf, text_screen_cpy, 80*25*2); + for(int i=0;i<80*25;i++) { + text_screen_cpy[i*2] = tmp_buf[i]; + text_screen_cpy[i*2+1] = tmp_buf[80*25 + i]; + } +#endif + text_screen_data = text_screen_cpy; +} +#endif + +void I_GraphicsCheckCommandLine(void) +{ +// int i; +// +// //! +// // @category video +// // @vanilla +// // +// // Disable blitting the screen. +// // +// +// noblit = M_CheckParm ("-noblit"); +// +// //! +// // @category video +// // +// // Don't grab the mouse when running in windowed mode. +// // +// +// nograbmouse_override = M_ParmExists("-nograbmouse"); +// +// // default to fullscreen mode, allow override with command line +// // nofullscreen because we love prboom +// +// //! +// // @category video +// // +// // Run in a window. +// // +// +// if (M_CheckParm("-window") || M_CheckParm("-nofullscreen")) +// { +// fullscreen = false; +// } +// +// //! +// // @category video +// // +// // Run in fullscreen mode. +// // +// +// if (M_CheckParm("-fullscreen")) +// { +// fullscreen = true; +// } +// +// //! +// // @category video +// // +// // Disable the mouse. +// // +// +// nomouse = M_CheckParm("-nomouse") > 0; +// +// //! +// // @category video +// // @arg +// // +// // Specify the screen width, in pixels. Implies -window. +// // +// +// i = M_CheckParmWithArgs("-width", 1); +// +// if (i > 0) +// { +// window_width = atoi(myargv[i + 1]); +// fullscreen = false; +// } +// +// //! +// // @category video +// // @arg +// // +// // Specify the screen height, in pixels. Implies -window. +// // +// +// i = M_CheckParmWithArgs("-height", 1); +// +// if (i > 0) +// { +// window_height = atoi(myargv[i + 1]); +// fullscreen = false; +// } +// +// //! +// // @category video +// // @arg +// // +// // Specify the dimensions of the window. Implies -window. +// // +// +// i = M_CheckParmWithArgs("-geometry", 1); +// +// if (i > 0) +// { +// int w, h, s; +// +// s = sscanf(myargv[i + 1], "%ix%i", &w, &h); +// if (s == 2) +// { +// window_width = w; +// window_height = h; +// fullscreen = false; +// } +// } +// +// //! +// // @category video +// // +// // Don't scale up the screen. Implies -window. +// // +// +// if (M_CheckParm("-1")) +// { +// SetScaleFactor(1); +// } +// +// //! +// // @category video +// // +// // Double up the screen to 2x its normal size. Implies -window. +// // +// +// if (M_CheckParm("-2")) +// { +// SetScaleFactor(2); +// } +// +// //! +// // @category video +// // +// // Double up the screen to 3x its normal size. Implies -window. +// // +// +// if (M_CheckParm("-3")) +// { +// SetScaleFactor(3); +// } +} + +// Check if we have been invoked as a screensaver by xscreensaver. + +void I_CheckIsScreensaver(void) +{ +} + +void I_DisplayFPSDots(boolean dots_on) +{ +} + +#if PICO_ON_DEVICE +bool video_doom_adapt_for_mode(const struct scanvideo_pio_program *program, const struct scanvideo_mode *mode, + struct scanvideo_scanline_buffer *missing_scanvideo_scanline_buffer, uint16_t *modifiable_instructions) { + missing_scanvideo_scanline_buffer->data = missing_scanline_data; + missing_scanvideo_scanline_buffer->data_used = missing_scanvideo_scanline_buffer->data_max = sizeof(missing_scanline_data) / 4; + return true; +} + +pio_sm_config video_doom_configure_pio(pio_hw_t *pio, uint sm, uint offset) { + pio_sm_config config = video_24mhz_composable_default_program_get_default_config(offset); + scanvideo_default_configure_pio(pio, sm, offset, &config, false); + return config; +} +#else +void simulate_video_pio_video_doom(const uint32_t *dma_data, uint32_t dma_data_size, + uint16_t *pixel_buffer, int32_t max_pixels, int32_t expected_width, bool overlay) { + const uint16_t *it = (uint16_t *) dma_data; + assert(!(3u & (uintptr_t) dma_data)); + const uint16_t *const __unused dma_data_end = (uint16_t *) (dma_data + dma_data_size); + const uint16_t *const pixels_end = (uint16_t *) (pixel_buffer + max_pixels); + uint16_t *pixels = pixel_buffer; + bool __unused ok = false; + bool done = false; + bool __unused last_was_black = true; // in case no pixels + const uint16_t display_enable_bit = PICO_SCANVIDEO_ALPHA_MASK; // for now + do { + uint16_t cmd = *it++; + switch (cmd) { + case video_doom_offset_nop_raw: + break; + case video_doom_offset_end_of_scanline_skip_ALIGN: + it++; + // fall thru + case video_doom_offset_end_of_scanline_ALIGN: + done = ok = true; + break; + case video_doom_offset_raw_run_half: { + assert(pixels < pixels_end); + uint16_t c = *it++; + if (!overlay || (c & display_enable_bit)) + *pixels++ = c; + else + pixels++; + uint16_t len = *it++; + for (int i = 0; i < len + 2; i++) { + assert(pixels < pixels_end); + c = *it++; + if (!overlay || (c & display_enable_bit)) + *pixels++ = c; + else + pixels++; + } + last_was_black = !c; + break; + } + case video_doom_offset_raw_1p_half: { + uint16_t c; + if (pixels == pixels_end) { + c = *it++; + assert(!c); // must end with black + } else { + assert(pixels < pixels_end); + c = *it++; + if (!overlay || (c & display_enable_bit)) + *pixels++ = c; + else + pixels++; + } + last_was_black = !c; + break; + } + case video_doom_offset_raw_run: { + assert(pixels < pixels_end); + uint16_t c = *it++; + if (!overlay || (c & display_enable_bit)) + *pixels++ = c, *pixels++ = c; + else + pixels+=2; + uint16_t len = *it++; + for (int i = 0; i < len + 2; i++) { + assert(pixels < pixels_end); + c = *it++; + if (!overlay || (c & display_enable_bit)) + *pixels++ = c, *pixels++ = c; + else + pixels+=2; + } + last_was_black = !c; + break; + } + case video_doom_offset_raw_1p: { + uint16_t c; + if (pixels == pixels_end) { + c = *it++; + assert(!c); // must end with black + } else { + assert(pixels < pixels_end); + c = *it++; + if (!overlay || (c & display_enable_bit)) + *pixels++ = c, *pixels++ = c; + else + pixels += 2; + } + last_was_black = !c; + break; + } + default: + assert(cmd < 32); + assert(false); + done = true; + } + } while (!done); + assert(ok); + assert(it == dma_data_end); + assert(!(3u & (uintptr_t) (it))); // should end on dword boundary +#if 0 + // should probably have this back ignored for now because of overlays which don't bother + if (!overlay) { + assert(!expected_width || pixels == pixel_buffer + + expected_width); // with the correct number of pixels (one more because we stick a black pixel on the end) + } +#else + if (expected_width && pixels < pixel_buffer + expected_width) { + // black out rest of line + if (!overlay) memset(pixels, 0, (expected_width - (pixels - pixel_buffer)) * sizeof(uint16_t)); + } +#endif + assert(last_was_black); +} +#endif + +#endif \ No newline at end of file diff --git a/src/pico/memmap_doom.ld b/src/pico/memmap_doom.ld new file mode 100644 index 00000000..07d5812d --- /dev/null +++ b/src/pico/memmap_doom.ld @@ -0,0 +1,252 @@ +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k + SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k +} + +ENTRY(_entry_point) + +SECTIONS +{ + /* Second stage bootloader is prepended to the image. It must be 256 bytes big + and checksummed. It is usually built by the boot_stage2 target + in the Raspberry Pi Pico SDK + */ + + .flash_begin : { + __flash_binary_start = .; + } > FLASH + + .boot2 : { + __boot2_start__ = .; + KEEP (*(.boot2)) + __boot2_end__ = .; + } > FLASH + + ASSERT(__boot2_end__ - __boot2_start__ == 256, + "ERROR: Pico second stage bootloader must be 256 bytes in size") + + /* The second stage will always enter the image at the start of .text. + The debugger will use the ELF entry point, which is the _entry_point + symbol if present, otherwise defaults to start of .text. + This can be used to transfer control back to the bootrom on debugger + launches only, to perform proper flash setup. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ + *(.init) + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > FLASH + __binary_info_end = .; + . = ALIGN(4); + + /* End of .text-like segments */ + __etext = .; + + .ram_vector_table (COPY): { + *(.ram_vector_table) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + + *(.jcr) + . = ALIGN(4); + /* All data end */ + __data_end__ = .; + } > RAM AT> FLASH + + .uninitialized_data (COPY): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + /* Start and end symbols must be word-aligned */ + .scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > FLASH + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > FLASH + __scratch_y_source__ = LOADADDR(.scratch_y); + + .bss : { + . = ALIGN(4); + __bss_start__ = .; + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (COPY): + { + __end__ = .; + end = __end__; + *(.heap*) + __HeapLimit = .; + } > RAM + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (COPY): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (COPY): + { + *(.stack*) + } > SCRATCH_Y + + .flash_end : { + __flash_binary_end = .; + } > FLASH + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = ORIGIN(RAM) + LENGTH(RAM); + __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); + __StackBottom = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop); + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") + + ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") + /* todo assert on extra code */ +} + diff --git a/src/pico/picoflash.c b/src/pico/picoflash.c new file mode 100644 index 00000000..4757200c --- /dev/null +++ b/src/pico/picoflash.c @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// this is modified from of the Pico SDK hardware/flash.c but modified to use a user buffer for boot2_copyout and combine to a single sector erase/write function + +#include "picoflash.h" +#include "pico/bootrom.h" + +#include "hardware/structs/ssi.h" +#include "hardware/structs/ioqspi.h" + +#define FLASH_BLOCK_ERASE_CMD 0xd8 + +//----------------------------------------------------------------------------- +// Infrastructure for reentering XIP mode after exiting for programming (take +// a copy of boot2 before XIP exit). Calling boot2 as a function works because +// it accepts a return vector in LR (and doesn't trash r4-r7). Bootrom passes +// NULL in LR, instructing boot2 to enter flash vector table's reset handler. + +#define BOOT2_SIZE_WORDS 64 + +static void __no_inline_not_in_flash_func(flash_init_boot2_copyout)(uint32_t boot2_copyout[BOOT2_SIZE_WORDS]) { + for (int i = 0; i < BOOT2_SIZE_WORDS; ++i) + boot2_copyout[i] = ((uint32_t *)XIP_BASE)[i]; + __compiler_memory_barrier(); +} + +static void __no_inline_not_in_flash_func(flash_enable_xip_via_boot2)(const uint32_t boot2_copyout[BOOT2_SIZE_WORDS]) { + ((void (*)(void))boot2_copyout+1)(); +} + +#define FLASH_BLOCK_SIZE (1u << 16) + +void __no_inline_not_in_flash_func(picoflash_sector_program)(uint32_t flash_offs, const uint8_t *data) { + rom_connect_internal_flash_fn connect_internal_flash = (rom_connect_internal_flash_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + rom_flash_exit_xip_fn flash_exit_xip = (rom_flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + rom_flash_range_program_fn flash_range_program = (rom_flash_range_program_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_PROGRAM); + rom_flash_flush_cache_fn flash_flush_cache = (rom_flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + rom_flash_range_erase_fn flash_range_erase = (rom_flash_range_erase_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_ERASE); + assert(connect_internal_flash && flash_exit_xip && flash_range_program && flash_flush_cache); + uint32_t boot2_copyout[BOOT2_SIZE_WORDS]; + flash_init_boot2_copyout(boot2_copyout); + + __compiler_memory_barrier(); + + connect_internal_flash(); + flash_exit_xip(); + flash_range_erase(flash_offs, FLASH_SECTOR_SIZE, FLASH_BLOCK_SIZE, FLASH_BLOCK_ERASE_CMD); + flash_range_program(flash_offs, data, FLASH_SECTOR_SIZE); + flash_flush_cache(); // Note this is needed to remove CSn IO force as well as cache flushing + flash_enable_xip_via_boot2(boot2_copyout); +} diff --git a/src/pico/picoflash.h b/src/pico/picoflash.h new file mode 100644 index 00000000..44082c9a --- /dev/null +++ b/src/pico/picoflash.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#pragma once + +#include "pico.h" + +#define FLASH_SECTOR_SIZE (1u << 12) + +// erase and write a 4K sector +void picoflash_sector_program(uint32_t flash_offs, const uint8_t *data); diff --git a/src/pico/piconet.c b/src/pico/piconet.c new file mode 100644 index 00000000..09471011 --- /dev/null +++ b/src/pico/piconet.c @@ -0,0 +1,936 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "piconet.h" + +boolean net_client_connected; + +#if PICO_ON_DEVICE +#include "hardware/irq.h" +#include "hardware/dma.h" +#include "hardware/timer.h" +#include "hardware/i2c.h" +#include "hardware/gpio.h" +#include "pico/sync.h" +#include "pico/binary_info.h" +#if DOOM_DEBUG_INFO +#define piconet_assert(x) ({ if (!(x)) panic("DOH "__STRING(x)"\n");}) +#define piconet_warning printf +#define piconet_info printf +#else +#define piconet_assert(x) ((void)0) +static inline void piconet_warning(const char *fmt, ...) {} +static inline void piconet_info(const char *fmt, ...) {} +#endif + +#define PICONET_VERSION 1 + +// todo ditch client that causes abort (after a bit) + +#define PERIODIC_ALARM_NUM 1 +#define I2C_DMA_CHANNEL_READ 9 +#define I2C_DMA_CHANNEL_WRITE 10 +#define TO_HOST_LENGTH 32 // fixed size +#define TO_CLIENT_LENGTH 128 + +#define CLIENT_TIMEOUT_SHORT_US 100000 // long enough for an entire poll round +#define CLIENT_TIMEOUT_LONG_US 1000000 + +#include "d_loop.h" +#include "doom/doomstat.h" + + +typedef enum { + piconet_msg_none, + piconet_msg_client_lobby, + piconet_msg_host_lobby, + piconet_msg_game_full, + piconet_msg_game_already_started, + piconet_msg_client_tic, + piconet_msg_host_tic, + piconet_msg_game_not_compatible, +} piconet_msg_type; + +// master uses this to queue stuff to send, clients use it to queue stuff from server +// note that it is a bit of an overlap with d_loop ticcmds, however combining the two seems like a fair amount of effort + +// local view of the shared state +static struct +{ + enum + { + in_none, + in_lobby, + in_game + } status; + // this was going to be a union, but we don't have anythuing for the other states, and keeping it around always makes game start simpler + lobby_state_t lobby; +} synced_state; + +typedef struct { + uint32_t id; + uint32_t last_acked_by_client_seq; + uint32_t last_rx_time; + int last_acked_by_client_tic; + int last_sent_by_client_tic; + uint8_t addr; + uint8_t abort_count; // not sure this really helps +} remote_client_t; + +typedef struct +{ + enum + { + ps_begin, + ps_recv_clients, + ps_recv_poll, + ps_send_clients, + ps_send_poll, + ps_end + } protocol_state; + // client number we are currently communicating iwth in m_recv_clients or m_send_clients + int8_t client_num; + // rotating poll address (looking for new clients) - used in game too so we can inform new clients what's going on + uint8_t poll_addr; + // client id we got from a polling client + uint32_t poll_client_id; + // highest tic which has data from all clients + int last_complete_tic; + int limit_tic; // tic we cannot receive data beyond or write over our local state + // if non zero, error code for polling clients (they don't get to join) + piconet_msg_type poll_error_response; + remote_client_t clients[NET_MAXPLAYERS]; +} host_state_t; + +typedef struct +{ + // related to i2c transfers + uint32_t last_tx_time; + uint32_t last_rx_time; + uint8_t rx_pos; + uint8_t rx_length; + int8_t tx_pos; + int8_t player_num; + int last_server_acked_tic; + int last_local_tic; + int last_received_from_server_tic; + int limit_tic; +} client_state_t; + +static enum { + role_none, + role_host, + role_client +} role; + +static union +{ + host_state_t host; + client_state_t client; +} local_state; + +typedef struct { + piconet_msg_type msg_type; + uint32_t client_id; +} host_packet_header_t; + +typedef struct { + uint8_t msg_type; + uint32_t compat_hash:24; + uint32_t client_id; +} client_packet_header_t; + +// todo we need different buffer sizes depending on role, but just do max for now +static __aligned(4) uint16_t small_buffer_16[TO_HOST_LENGTH]; +static __aligned(4) uint16_t large_buffer_16[TO_CLIENT_LENGTH+1]; + +#define small_buffer_8 ((uint8_t*)small_buffer_16) +#define large_buffer_8 ((uint8_t*)large_buffer_16) +static critical_section_t critsec; +#define I2C_IRQ __CONCAT(I2C, __CONCAT(PICO_DEFAULT_I2C, _IRQ)) + +#define FAST 1 + +#define ADDR_LOW 0x20 +#if !FAST +#define piconet_debug piconet_info +#define PERIOD_MS 1000 +#define ADDR_COUNT 8 +#else +#define piconet_debug(fmt, ...) ((void)0) +#define PERIOD_MS 10 +#define ADDR_COUNT 0x40 +#endif +#define IN_GAME_CLIENT_ABORT_MAX 16 + +static struct { + uint32_t client_id; + enum + { + i2c_none, + i2c_send, + i2c_receive, + i2c_send_dma, + i2c_receive_dma, + } activity; + bool dma_active; + uint8_t client_addr; +} i2c_state; + +static void i2c_locked_cancel_dma() { + if (i2c_state.dma_active) { + dma_hw->abort = I2C_DMA_CHANNEL_READ | I2C_DMA_CHANNEL_WRITE; + while (dma_hw->abort & (I2C_DMA_CHANNEL_READ | I2C_DMA_CHANNEL_WRITE)) tight_loop_contents(); + i2c_state.dma_active = false; + } +} + +static void i2c_dma_read(uint8_t *buffer, int len) { + dma_channel_config c = dma_channel_get_default_config(I2C_DMA_CHANNEL_READ); + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, true); + channel_config_set_transfer_data_size(&c, DMA_SIZE_8); + channel_config_set_dreq(&c, i2c_get_dreq(i2c_default, false)); + i2c_state.dma_active = true; + dma_channel_configure(I2C_DMA_CHANNEL_READ, &c, + buffer, &i2c_get_hw(i2c_default)->data_cmd, len, true); +} + +static void i2c_dma_write(uint16_t *buffer, int len) { + dma_channel_config c = dma_channel_get_default_config(I2C_DMA_CHANNEL_WRITE); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + channel_config_set_transfer_data_size(&c, DMA_SIZE_16); + channel_config_set_dreq(&c, i2c_get_dreq(i2c_default, true)); + i2c_state.dma_active = true; + dma_channel_configure(I2C_DMA_CHANNEL_WRITE, &c, + &i2c_get_hw(i2c_default)->data_cmd, buffer, len, true); +} + +static void host_advance_protocol_state_locked(); +static void host_check_tic_advance_locked(); +typedef struct { + client_packet_header_t hdr; + uint32_t last_rx_seq; + char player_name[MAXPLAYERNAME]; +} client_lobby_msg_t; +static_assert(sizeof(client_lobby_msg_t) <= TO_HOST_LENGTH, ""); + +typedef struct { + client_packet_header_t hdr; + int last_rx_tic; // last received from the server + int enclosed_tic; // -1 for none + ticcmd_t ticcmd; +} client_tic_msg_t; +static_assert(sizeof(client_tic_msg_t) <= TO_HOST_LENGTH, ""); + +typedef struct { + host_packet_header_t hdr; + lobby_state_t lobby_state; +} host_lobby_msg_t; +static_assert(sizeof(host_lobby_msg_t) <= count_of(large_buffer_16), ""); + +typedef struct { + client_packet_header_t hdr; + int client_ack_tic; // last received from client + int enclosed_tic; // -1 for none +} host_tic_msg_hdr_t; + +// if hdr.enclosed_tic != -1 then use this structure +typedef struct { + host_tic_msg_hdr_t hdr; + ticcmd_set_t cmds; +} host_tic_msg_t; + +static_assert(sizeof(host_tic_msg_t) <= count_of(large_buffer_16), ""); + +#if DOOM_DEBUG_INFO +int foo_receive_count; +#endif + +static uint32_t get_compat_hash() { + return (PICONET_VERSION * 31 + whdheader->hash)&0xffffff; +} + + +void i2c_irq_handler() { + uint32_t status = i2c_get_hw(i2c_default)->intr_stat; + piconet_debug("STATUS %04x %04x %04x\n", status, i2c_get_hw(i2c_default)->raw_intr_stat, i2c_get_hw(i2c_default)->intr_mask); + critical_section_enter_blocking(&critsec); + if (status & I2C_IC_INTR_STAT_R_TX_ABRT_BITS) { + piconet_debug("ABORTED %d %08x!\n", time_us_32() - i2c_state.t0, i2c_get_hw(i2c_default)->tx_abrt_source); + i2c_locked_cancel_dma(); + i2c_get_hw(i2c_default)->clr_tx_abrt; + if (role == role_host) { + if (i2c_state.activity == i2c_none) { + // actually we can get this from our own abort + //printf("WHAT? got abort when not doing anything\n"); + } else { + if (local_state.host.protocol_state == ps_recv_clients) { + piconet_info("*** I2C ABORT RECEIVING FROM %d (%02x) ***\n", local_state.host.client_num, local_state.host.clients[local_state.host.client_num].addr); + if (synced_state.status == in_lobby) { + // move any below players up + for(int i=local_state.host.client_num; i < NET_MAXPLAYERS - 1; i++) { + synced_state.lobby.players[i] = synced_state.lobby.players[i+1]; + local_state.host.clients[i] = local_state.host.clients[i+1]; + } + // update the lobby + synced_state.lobby.nplayers--; + synced_state.lobby.players[NET_MAXPLAYERS-1].client_id = 0; + synced_state.lobby.seq++; + // and remove the client from our tracking + local_state.host.clients[NET_MAXPLAYERS-1].id = 0; + local_state.host.clients[NET_MAXPLAYERS-1].addr = 0; + // we don't want to skip the next up client + local_state.host.client_num--; + } else { + if (++local_state.host.clients[local_state.host.client_num].abort_count > IN_GAME_CLIENT_ABORT_MAX) { + piconet_info("Lost client %d\n", local_state.host.client_num); + local_state.host.clients[local_state.host.client_num].id = 0; + local_state.host.clients[local_state.host.client_num].addr = 0; + host_check_tic_advance_locked(); + } + } + } + i2c_state.activity = i2c_none; + host_advance_protocol_state_locked(); + } + } else { + piconet_debug("CLIENT ABORTED\n"); + } + i2c_state.activity = i2c_none; + } else if (status & I2C_IC_INTR_STAT_R_RD_REQ_BITS) { + // we are a client and need to be sending + piconet_assert(role == role_client); + if (i2c_get_hw(i2c_default)->raw_intr_stat & I2C_IC_RAW_INTR_STAT_START_DET_BITS) { + // this is the first byte of a new read + piconet_debug("NEW RX TRANSMISSION\n"); + i2c_get_hw(i2c_default)->clr_start_det; + + i2c_state.activity = i2c_send; + local_state.client.tx_pos = 0; + client_packet_header_t *pkt = (client_packet_header_t *)large_buffer_8; + pkt->client_id = i2c_state.client_id; + pkt->compat_hash = get_compat_hash(); + if (synced_state.status == in_lobby) { + client_lobby_msg_t *lobby_msg = (client_lobby_msg_t *)pkt; + pkt->msg_type = piconet_msg_client_lobby; + lobby_msg->last_rx_seq = synced_state.lobby.seq; + memcpy(lobby_msg->player_name, player_name, MAXPLAYERNAME); + } else if (synced_state.status == in_game) { + client_tic_msg_t *tic_msg = (client_tic_msg_t *)pkt; + pkt->msg_type = piconet_msg_client_tic; + tic_msg->last_rx_tic = local_state.client.last_received_from_server_tic; + if (local_state.client.last_server_acked_tic < local_state.client.last_local_tic) { + tic_msg->enclosed_tic = local_state.client.last_server_acked_tic + 1; + tic_msg->ticcmd = ticdata[tic_msg->enclosed_tic % BACKUPTICS].cmds[consoleplayer]; +// printf("SEND TICK %d to server\n", tic_msg->enclosed_tic); + } else { + tic_msg->enclosed_tic = -1; + } + } else { + pkt->msg_type = piconet_msg_none; + } + } + i2c_get_hw(i2c_default)->clr_rd_req; + while (i2c_get_write_available(i2c_default) && local_state.client.tx_pos < TO_HOST_LENGTH) { + i2c_get_hw(i2c_default)->data_cmd = large_buffer_8[local_state.client.tx_pos++]; + } + } else if (status & I2C_IC_INTR_STAT_R_RX_FULL_BITS) { + // we have incoming date (from the host) + piconet_assert(role == role_client); + int val = i2c_get_hw(i2c_default)->data_cmd; + if (val & I2C_IC_DATA_CMD_FIRST_DATA_BYTE_BITS) { + // first byte is size + i2c_state.activity = i2c_receive; + local_state.client.rx_pos = 0; + local_state.client.rx_length = val & 0xff; + piconet_debug("RX START, length = %d\n", val & 0xff); + } else { + if (local_state.client.rx_pos < local_state.client.rx_length) { + large_buffer_8[local_state.client.rx_pos++] = val; + piconet_debug("RX %d/%d %04x\n", count, length, val); +// if (val & I2C_IC_DATA_CMD_STOP_BITS) { +// piconet_debug("STOP BIT\n"); +// i2c_state.activity = i2c_none; +// } + } else { + piconet_warning("RX out of band %04x\n", val); + } + } + } else if (status & I2C_IC_INTR_STAT_R_STOP_DET_BITS) { + // end of a transmission + i2c_get_hw(i2c_default)->clr_stop_det; + if (role == role_host) { + int dma_count = 0; + if (i2c_state.activity == i2c_receive_dma) { + dma_count = dma_hw->ch[I2C_DMA_CHANNEL_READ].write_addr - (uintptr_t)large_buffer_8; + if (dma_count == TO_HOST_LENGTH) { // (hopefully) valid message + client_packet_header_t *pkt = (client_packet_header_t *)large_buffer_8; + if (pkt->msg_type == piconet_msg_client_lobby) { + client_lobby_msg_t *lobby_msg = (client_lobby_msg_t *) pkt; + if (local_state.host.protocol_state == ps_recv_poll) { + // this is data from a polled client + local_state.host.poll_client_id = pkt->client_id; + if (synced_state.status == in_lobby) { + if (pkt->compat_hash == get_compat_hash()) { + if (synced_state.lobby.nplayers < 4) { + piconet_info("I2C RECORDING CLIENT AT %d %02x\n", synced_state.lobby.nplayers, + local_state.host.poll_addr); + local_state.host.clients[synced_state.lobby.nplayers].id = lobby_msg->hdr.client_id; + local_state.host.clients[synced_state.lobby.nplayers].addr = local_state.host.poll_addr; + // update the lobby + synced_state.lobby.players[synced_state.lobby.nplayers].client_id = lobby_msg->hdr.client_id; + memcpy(synced_state.lobby.players[synced_state.lobby.nplayers].name, + lobby_msg->player_name, MAXPLAYERNAME); + synced_state.lobby.nplayers++; + synced_state.lobby.seq++; + } else { + local_state.host.poll_error_response = piconet_msg_game_full; + } + } else { + local_state.host.poll_error_response = piconet_msg_game_not_compatible; + } + } else { + local_state.host.poll_error_response = piconet_msg_game_already_started; + } + } else if (local_state.host.protocol_state == ps_recv_clients) { + // this is data from a current client + if (synced_state.status == in_lobby) { + // record what the client last received + local_state.host.clients[local_state.host.client_num].last_rx_time = time_us_32(); + local_state.host.clients[local_state.host.client_num].last_acked_by_client_seq = lobby_msg->last_rx_seq; + } + } + } else if (pkt->msg_type == piconet_msg_client_tic) { + client_tic_msg_t *tic_msg = (client_tic_msg_t *)pkt; + local_state.host.clients[local_state.host.client_num].abort_count = 0; + if (local_state.host.protocol_state != ps_recv_clients || synced_state.status != in_game) { + piconet_warning("unexpected client tic ps %d ss %d\n", local_state.host.protocol_state, synced_state.status); + } else { +// printf("CLIENT TIC enclosed %d ack %d\n", tic_msg->enclosed_tic, tic_msg->last_rx_tic); + local_state.host.clients[local_state.host.client_num].last_rx_time = time_us_32(); + local_state.host.clients[local_state.host.client_num].last_acked_by_client_tic = tic_msg->last_rx_tic; + if (tic_msg->enclosed_tic != -1) { + // + if (tic_msg->enclosed_tic > local_state.host.last_complete_tic && + tic_msg->enclosed_tic < local_state.host.limit_tic) { +// printf("GOT TIC DATA FOR %d from client %d\n", tic_msg->enclosed_tic, local_state.host.client_num); +// printf("ticdata %d (slot %d) from %d %02x\n", tic_msg->enclosed_tic, tic_msg->enclosed_tic % BACKUPTICS, local_state.host.client_num, tic_msg->ticcmd.consistancy); + ticdata[tic_msg->enclosed_tic % BACKUPTICS].cmds[local_state.host.client_num] = tic_msg->ticcmd; + local_state.host.clients[local_state.host.client_num].last_sent_by_client_tic = tic_msg->enclosed_tic; + host_check_tic_advance_locked(); + } else { + piconet_warning("CLIENT TIC %d out of window %d->%d\n", tic_msg->enclosed_tic, + local_state.host.last_complete_tic, + local_state.host.limit_tic); + } + } + } + } else { + piconet_warning("unexpected msg type %d from client\n", pkt->msg_type); + } + } else { + piconet_warning("msg received from client is too short %d\n", dma_count); + } +#if DOOM_DEBUG_INFO + foo_receive_count++; +#endif + } else if (i2c_state.activity == i2c_send_dma) { + dma_count = (uint16_t *)dma_hw->ch[I2C_DMA_CHANNEL_WRITE].read_addr - large_buffer_16; + piconet_debug("HOST SEND DONE %d %d\n", time_us_32() - i2c_state.t0, dma_count); + } + i2c_state.activity = i2c_none; + host_advance_protocol_state_locked(); + } else if (role == role_client) { + if (i2c_state.activity == i2c_send) { + piconet_debug("SEND DONE %d\n", client_tx_pos); + local_state.client.last_tx_time = time_us_32(); + } else if (i2c_state.activity == i2c_receive) { + piconet_debug("RECEIVE DONE %d\n", client_rx_count); + if (local_state.client.rx_pos == local_state.client.rx_length) { + host_packet_header_t *hdr = (host_packet_header_t*)large_buffer_8; + if (i2c_state.client_id == hdr->client_id) { + local_state.client.last_rx_time = time_us_32(); + if (hdr->msg_type == piconet_msg_host_lobby) { + if (synced_state.status == in_lobby) { + host_lobby_msg_t *lobby_msg = (host_lobby_msg_t *) hdr; + synced_state.lobby = lobby_msg->lobby_state; + // update our player number + local_state.client.player_num = -1; + for (int i = 0; i < NET_MAXPLAYERS; i++) { + if (synced_state.lobby.players[i].client_id == i2c_state.client_id) { + local_state.client.player_num = (int8_t)i; + } + } + } + } else if (hdr->msg_type == piconet_msg_host_tic) { + synced_state.status = in_game; // cause auto launch + host_tic_msg_t *tic_msg = (host_tic_msg_t *) hdr; + if (tic_msg->hdr.enclosed_tic != -1) { + local_state.client.last_server_acked_tic = tic_msg->hdr.client_ack_tic; + if (tic_msg->hdr.enclosed_tic == local_state.client.last_received_from_server_tic + 1) { + if (tic_msg->hdr.enclosed_tic < local_state.client.limit_tic) { +// printf(" storing\n"); + local_state.client.last_received_from_server_tic = tic_msg->hdr.enclosed_tic; + ticdata[tic_msg->hdr.enclosed_tic % BACKUPTICS] = tic_msg->cmds; +// printf("ticdata %d (slot %d) %02x %02x\n", tic_msg->hdr.enclosed_tic, tic_msg->hdr.enclosed_tic % BACKUPTICS, tic_msg->cmds.cmds[0].consistancy, tic_msg->cmds.cmds[1].consistancy); + } else { + // probably should not happen! + piconet_warning("no room for host TIC %d ack %d\n", tic_msg->hdr.enclosed_tic, tic_msg->hdr.client_ack_tic); + } + } else { + piconet_warning(" expected tic %d but got %d\n", local_state.client.last_received_from_server_tic + 1, tic_msg->hdr.enclosed_tic); + } + } + } else if (synced_state.status == in_lobby) { + if (hdr->msg_type == piconet_msg_game_already_started) { + synced_state.lobby.status = lobby_game_started; + } else if (hdr->msg_type == piconet_msg_game_not_compatible) { + synced_state.lobby.status = lobby_game_not_compatible; + } + } + } + } + } + i2c_state.activity = i2c_none; + } + } + critical_section_exit(&critsec); +} + +static void host_send_locked(int addr, int len) { +#if DOOM_DEBUG_INFO + if (i2c_state.activity != i2c_none) { + piconet_info("MSEND: BADSTATE: was in %d foo %d\n", i2c_state.activity, foo_receive_count); + } +#endif + i2c_state.activity = i2c_send_dma; + // todo can we release the critsec earlier? + assert(len < count_of(large_buffer_16)); + // backwards to allow use of the same buffer + for(int i=len-1;i>=0;i--) { + large_buffer_16[i+1] = large_buffer_8[i]; + } + large_buffer_16[0] = I2C_IC_DATA_CMD_RESTART_BITS | len; + large_buffer_16[len] |= I2C_IC_DATA_CMD_STOP_BITS; + + i2c_get_hw(i2c_default)->enable = 0; + i2c_get_hw(i2c_default)->tar = addr; + i2c_get_hw(i2c_default)->enable = 1; + i2c_dma_write(large_buffer_16, len+1); +} + +static void host_receive_locked(int addr) { + if (i2c_state.activity != i2c_none) { + piconet_info("MREC: BADSTATE: was in %d\n", i2c_state.activity); + } + i2c_state.activity = i2c_receive_dma; + // todo can we release the critsec earlier? + for(int i=0;ienable = 0; + i2c_get_hw(i2c_default)->tar = addr; + i2c_get_hw(i2c_default)->enable = 1; + i2c_dma_read(large_buffer_8, TO_HOST_LENGTH); + i2c_dma_write(small_buffer_16, TO_HOST_LENGTH); +} + +static void host_advance_protocol_state_locked() { + if (local_state.host.protocol_state == ps_begin) { + local_state.host.protocol_state = ps_recv_clients; + local_state.host.client_num = 0; // note we want to start at 1 and we pre-increment later + } + if (local_state.host.protocol_state == ps_recv_clients) { + local_state.host.client_num++; + for(; local_state.host.client_num < NET_MAXPLAYERS; local_state.host.client_num++) { + if (local_state.host.clients[local_state.host.client_num].addr) { + piconet_debug("TRY RECV FROM %02x\n", local_state.host.clients[local_state.host.client_num].addr); + host_receive_locked(local_state.host.clients[local_state.host.client_num].addr); + return; + } + } + local_state.host.protocol_state = ps_recv_poll; + local_state.host.poll_error_response = piconet_msg_none; + piconet_debug("POLL %02x\n", host_protocol_state.poll_addr); + host_receive_locked(local_state.host.poll_addr); + return; + } + if (local_state.host.protocol_state == ps_recv_poll) { + local_state.host.protocol_state = ps_send_clients; + local_state.host.client_num = 0; // note we want to start at 1 and we pre-increment later + } + if (local_state.host.protocol_state == ps_send_clients) { + local_state.host.client_num++; + for(; local_state.host.client_num < NET_MAXPLAYERS; local_state.host.client_num++) { + if (local_state.host.clients[local_state.host.client_num].id) { + piconet_debug("SEND TO %02x\n", local_state.host.clients[local_state.host.client_num].addr); + host_packet_header_t *hdr = (host_packet_header_t *)large_buffer_8; + hdr->client_id = local_state.host.clients[local_state.host.client_num].id; + int len = 0; + if (synced_state.status == in_lobby || local_state.host.clients[local_state.host.client_num].last_sent_by_client_tic == -1) { + // note we send lobby message until the client acknowledges the game start (by sending a tic) + if (local_state.host.clients[local_state.host.client_num].last_acked_by_client_seq != synced_state.lobby.seq) { + hdr->msg_type = piconet_msg_host_lobby; + ((host_lobby_msg_t *) hdr)->lobby_state = synced_state.lobby; +// printf("SENDING UPDATED LOBBY (%d) TO CLIENT %d\n", (int) synced_state.lobby.seq, +// local_state.host.client_num); + len = sizeof(host_lobby_msg_t); + } else { + // just send dummy message + } + } else if (synced_state.status == in_game) { + hdr->msg_type = piconet_msg_host_tic; + host_tic_msg_hdr_t *tichdr = (host_tic_msg_hdr_t *)hdr; + tichdr->client_ack_tic = local_state.host.clients[local_state.host.client_num].last_sent_by_client_tic; + if (local_state.host.clients[local_state.host.client_num].last_acked_by_client_tic < local_state.host.last_complete_tic) { + tichdr->enclosed_tic = local_state.host.clients[local_state.host.client_num].last_acked_by_client_tic + 1; + ((host_tic_msg_t *)tichdr)->cmds = ticdata[tichdr->enclosed_tic % BACKUPTICS]; +// printf("SENDING TIC %d to client %d\n", tichdr->enclosed_tic, local_state.host.client_num); + len = sizeof(host_tic_msg_t); + } else { +// printf("NOT SENDING TIC to client %d acked = %d complete = %d\n", local_state.host.client_num, local_state.host.clients[local_state.host.client_num].last_acked_by_client_tic, local_state.host.last_complete_tic); + tichdr->enclosed_tic = -1; + len = sizeof(host_tic_msg_hdr_t); + } + } + if (!len) { + hdr->msg_type = piconet_msg_none; + len = sizeof(host_packet_header_t); + } + host_send_locked(local_state.host.clients[local_state.host.client_num].addr, len); + return; + } + } + local_state.host.protocol_state = ps_send_poll; + if (local_state.host.poll_error_response) { + piconet_debug("POLL RESPONSE %02x\n", local_state.host.poll_addr); + host_packet_header_t *hdr = (host_packet_header_t *)large_buffer_8; + hdr->msg_type = local_state.host.poll_error_response; + hdr->client_id = local_state.host.poll_client_id; + host_send_locked(local_state.host.poll_addr, sizeof(host_packet_header_t)); + local_state.host.poll_error_response = 0; + return; + } + } + if (local_state.host.protocol_state == ps_send_poll) { + local_state.host.protocol_state = ps_end; + } +} + +static void periodic_tick(uint timer) { +// printf("TICK\n"); + critical_section_enter_blocking(&critsec); + if (i2c_state.activity != i2c_none) { + piconet_warning("OOPS BAD STATE %d %08x\n", i2c_state.activity, (int)i2c_get_hw(i2c_default)->raw_intr_stat); + i2c_state.activity = i2c_none; + hw_set_bits(&i2c_get_hw(i2c_default)->enable, I2C_IC_ENABLE_ABORT_BITS); + } + do { + local_state.host.poll_addr++; + if (local_state.host.poll_addr == ADDR_LOW + ADDR_COUNT) local_state.host.poll_addr = ADDR_LOW; + bool found = false; + for(int i=0;iints & 1u << PERIODIC_ALARM_NUM); +} + + +void piconet_init() { + bi_decl_if_func_used(bi_program_feature("I2C multi-player")); + bi_decl_if_func_used(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); + + critical_section_init(&critsec); + i2c_init(i2c_default, 800 * 1000); + gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); + gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); + irq_set_exclusive_handler(I2C_IRQ, i2c_irq_handler); + hardware_alarm_set_callback(PERIODIC_ALARM_NUM, periodic_tick); + irq_set_priority(TIMER_IRQ_0 + PERIODIC_ALARM_NUM, 0xc0); // don't want timer pre-empting the other IRQs + irq_set_enabled(TIMER_IRQ_0 + PERIODIC_ALARM_NUM, true); // no harm turning it on +} + +static void clear_state() { + memset(&i2c_state, 0, sizeof(i2c_state)); + memset(&local_state, 0, sizeof(local_state)); + memset(&synced_state, 0, sizeof(synced_state)); +} + +void piconet_start_host(int8_t deathmatch, int8_t epi, int8_t skill) { + critical_section_enter_blocking(&critsec); + hardware_alarm_cancel(PERIODIC_ALARM_NUM); +#if PICO_DOOM_INFO + printf("START HOST\n"); +#endif + clear_state(); + role = role_host; + // host is client 0 + memcpy(synced_state.lobby.players[0].name, player_name, MAXPLAYERNAME); + uint32_t client_id = time_us_32(); + synced_state.status = in_lobby; + synced_state.lobby.players[0].client_id = client_id; + synced_state.lobby.status = lobby_waiting_for_start; + synced_state.lobby.nplayers = 1; + synced_state.lobby.deathmatch = deathmatch; + synced_state.lobby.epi = epi; + synced_state.lobby.skill = skill; + synced_state.lobby.seq = 1; + + local_state.host.poll_addr = ADDR_LOW; + local_state.host.clients[0].id = client_id; + local_state.host.last_complete_tic = -1; + for(int i=0;ienable = 0; + i2c_set_slave_mode(i2c_default, false, 0); + i2c_get_hw(i2c_default)->intr_mask = I2C_IC_INTR_MASK_M_STOP_DET_BITS | I2C_IC_INTR_STAT_R_TX_ABRT_BITS; + i2c_get_hw(i2c_default)->enable = 1; + critical_section_exit(&critsec); + + hardware_alarm_set_target(PERIODIC_ALARM_NUM, make_timeout_time_ms(PERIOD_MS)); + irq_set_enabled(I2C_IRQ, true); +} + +static void client_new_random_addr_locked() { + i2c_state.client_addr = ADDR_LOW + time_us_32()%ADDR_COUNT; + i2c_state.client_id = time_us_32(); + piconet_info("CLIENT ADDR %02x\n", i2c_state.client_addr); +} + +void piconet_start_client() { + critical_section_enter_blocking(&critsec); + hardware_alarm_cancel(PERIODIC_ALARM_NUM); + clear_state(); + synced_state.status = in_lobby; + role = role_client; + local_state.client.last_local_tic = local_state.client.last_server_acked_tic = local_state.client.last_received_from_server_tic = -1; + client_new_random_addr_locked(); + i2c_set_slave_mode(i2c_default, true, i2c_state.client_addr); + i2c_get_hw(i2c_default)->enable = 0; + hw_set_bits(&i2c_get_hw(i2c_default)->con, I2C_IC_CON_STOP_DET_IFADDRESSED_BITS); + i2c_get_hw(i2c_default)->intr_mask = I2C_IC_INTR_MASK_M_STOP_DET_BITS | I2C_IC_INTR_STAT_R_RD_REQ_BITS | I2C_IC_INTR_STAT_R_TX_ABRT_BITS | I2C_IC_INTR_STAT_R_RX_FULL_BITS; + i2c_get_hw(i2c_default)->enable = 1; + critical_section_exit(&critsec); + irq_set_enabled(I2C_IRQ, true); +} + +bool piconet_client_check_for_dropped_connection() { + bool rc = false; + critical_section_enter_blocking(&critsec); + if (role == role_client && i2c_state.activity == i2c_none) { + // use long timeout if we aren't accepted as the server will only be talking to us once per round of polling + if (time_us_32() - local_state.client.last_tx_time > (synced_state.lobby.status == lobby_waiting_for_start ? CLIENT_TIMEOUT_SHORT_US : CLIENT_TIMEOUT_LONG_US)) { + client_new_random_addr_locked(); + if (synced_state.status == in_lobby) { + synced_state.lobby.status = lobby_no_connection; + synced_state.lobby.seq = 0; + } + // todo leave game + i2c_get_hw(i2c_default)->enable = false; + i2c_get_hw(i2c_default)->sar = i2c_state.client_addr; + i2c_get_hw(i2c_default)->enable = true; + rc = true; + } + } + critical_section_exit(&critsec); + return rc; +} + +void piconet_stop() { + critical_section_enter_blocking(&critsec); + if (role != role_none) { + piconet_info("I2C STOP\n"); + i2c_locked_cancel_dma(); + hardware_alarm_cancel(PERIODIC_ALARM_NUM); + irq_set_enabled(I2C_IRQ, false); + i2c_get_hw(i2c_default)->enable = 0; + role = role_none; + } + critical_section_exit(&critsec); +} + +int piconet_get_lobby_state(lobby_state_t *state) { + critical_section_enter_blocking(&critsec); + int rc; + if (role == role_client) { + rc = local_state.client.player_num; + } else if (role == role_host) { + rc = 0; + } else { + rc = -1; + } + memcpy(state, &synced_state.lobby, sizeof(lobby_state_t)); + critical_section_exit(&critsec); + return rc; +} + +void piconet_start_game() { + critical_section_enter_blocking(&critsec); + piconet_assert(synced_state.status == in_lobby); + synced_state.status = in_game; + synced_state.lobby.status = lobby_game_started; + if (role == role_host) { + synced_state.lobby.seq++; + } + critical_section_exit(&critsec); +} + +void piconet_new_local_tic(int tic) { + critical_section_enter_blocking(&critsec); + piconet_assert(synced_state.status == in_game); +// printf("NEWTIC %d\n", tic); + if (role == role_client) { + piconet_assert(tic == local_state.client.last_local_tic + 1); + local_state.client.last_local_tic = tic; + } else { + local_state.host.clients[0].last_sent_by_client_tic = tic; + host_check_tic_advance_locked(); + } + critical_section_exit(&critsec); +} + +int piconet_maybe_recv_tic(int fromtic) { + critical_section_enter_blocking(&critsec); + if (role == role_host) { + if (fromtic < local_state.host.last_complete_tic) { + fromtic++; +#if DEBUG_CONSISTENCY + printf("Accept tic %d %d,%d,%d:%02x %d,%d,%d:%02x limit was %d\n", fromtic, + ticdata[fromtic%BACKUPTICS].cmds[0].angleturn, ticdata[fromtic%BACKUPTICS].cmds[0].forwardmove, ticdata[fromtic%BACKUPTICS].cmds[0].buttons, ticdata[fromtic%BACKUPTICS].cmds[0].consistancy, + ticdata[fromtic%BACKUPTICS].cmds[1].angleturn, ticdata[fromtic%BACKUPTICS].cmds[1].forwardmove, ticdata[fromtic%BACKUPTICS].cmds[1].buttons, ticdata[fromtic%BACKUPTICS].cmds[1].consistancy, + local_state.host.limit_tic); +#endif + } else { + uint32_t now = time_us_32(); + for(int i=1;i CLIENT_TIMEOUT_SHORT_US) { + piconet_info("KICKING CLIENT %d out\n", i); + local_state.host.clients[i].id = local_state.host.clients[i].addr = 0; + host_check_tic_advance_locked(); + } + } + } + local_state.host.limit_tic = fromtic + BACKUPTICS - 1; + } else if (role == role_client) { + if (fromtic < local_state.client.last_received_from_server_tic) { + fromtic++; +#if DEBUG_CONSISTENCY + printf("Accept tic %d %d,%d,%d:%02x %d,%d,%d:%02x limit was %d\n", fromtic, + ticdata[fromtic%BACKUPTICS].cmds[0].angleturn, ticdata[fromtic%BACKUPTICS].cmds[0].forwardmove, ticdata[fromtic%BACKUPTICS].cmds[0].buttons, ticdata[fromtic%BACKUPTICS].cmds[0].consistancy, + ticdata[fromtic%BACKUPTICS].cmds[1].angleturn, ticdata[fromtic%BACKUPTICS].cmds[1].forwardmove, ticdata[fromtic%BACKUPTICS].cmds[1].buttons, ticdata[fromtic%BACKUPTICS].cmds[1].consistancy, + local_state.client.limit_tic); +#endif + } else if (time_us_32() - local_state.client.last_rx_time > CLIENT_TIMEOUT_SHORT_US) { + net_client_connected = false; + for(int i=0;i fromtic) fromtic++; + return fromtic; +} + +// returns which player you are +int piconet_get_lobby_state(lobby_state_t *ls) { + memset(ls, 0, sizeof(lobby_state_t)); + if (state == host || state == host_game) { + ls->status = state == host ? lobby_waiting_for_start : lobby_game_started; + ls->players[0].client_id = 1; + ls->nplayers = 1; + memcpy(ls->players[0].name, player_name, MAXPLAYERNAME); + return 0; + } + ls->status = lobby_no_connection; + return -1; +} + +#endif \ No newline at end of file diff --git a/src/pico/piconet.h b/src/pico/piconet.h new file mode 100644 index 00000000..7cc1afd6 --- /dev/null +++ b/src/pico/piconet.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include "pico.h" +#include "net_defs.h" + +typedef struct { + uint32_t client_id; + char name[MAXPLAYERNAME]; +} lobby_player_t; + +typedef enum { + lobby_no_connection, + lobby_waiting_for_start, + lobby_game_started, + lobby_game_not_compatible, +} piconet_lobby_status_t; + +typedef struct { + uint32_t compat_hash; // version and whd hash + uint32_t seq; + piconet_lobby_status_t status; + uint8_t nplayers; + int8_t deathmatch; + int8_t epi; + int8_t skill; + lobby_player_t players[NET_MAXPLAYERS]; +} lobby_state_t; + +// one time initialization (set pulls etc) +void piconet_init(); +void piconet_start_host(int8_t deathmatch, int8_t epi, int8_t skill); +void piconet_start_client(); +void piconet_stop(); +// periodically poll to check connection hasn't dropped +bool piconet_client_check_for_dropped_connection(); +void piconet_start_game(); +// returns which player you are +int piconet_get_lobby_state(lobby_state_t *state); +void piconet_new_local_tic(int tic); +int piconet_maybe_recv_tic(int fromtic); + +extern char player_name[MAXPLAYERNAME]; + +#if USE_PICO_NET +// same var as used by regular networking +extern boolean net_client_connected; // basically whether events are sync-ing amongst the players +#endif \ No newline at end of file diff --git a/src/pico/stubs.c b/src/pico/stubs.c new file mode 100644 index 00000000..a83cbd58 --- /dev/null +++ b/src/pico/stubs.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "pico.h" + +#include + +#if PICO_ON_DEVICE +static inline char to_lower(char c) { + if (c >= 'A' && c <= 'Z') c += 'a' - 'A'; + return c; +} + +int strnicmp(const char *a, const char *b, size_t len) { + int diff = 0; + for(uint i=0; i | + out pins, bpp [2] + out x, bpp +pixel_loop: + out pins, bpp [2] + jmp x-- pixel_loop +public raw_1p: ; | jmp raw_1p | color | + out pins, bpp [2] + out pc, bpp + +public raw_run_half: ; | jmp raw_run | color | n | | + out pins, bpp + out x, bpp +pixel_loop_half: + out pins, bpp + jmp x-- pixel_loop_half +public raw_1p_half: ; | jmp raw_1p | color | + out pins, bpp + out pc, bpp + +public color_run: ; | jmp color_run | color | count-3 | + out pins, bpp [2] + out x, bpp +color_loop: + jmp x-- color_loop [3] + out pc, bpp [3] diff --git a/src/pico/w_file_static.c b/src/pico/w_file_static.c new file mode 100644 index 00000000..c4192748 --- /dev/null +++ b/src/pico/w_file_static.c @@ -0,0 +1,174 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// WAD I/O functions. +// + +#include "config.h" + +#if !PICO_ON_DEVICE +#ifdef HAVE_MMAP + +#include +#include +#include +#include +#include + +#include "m_misc.h" +#include "w_file.h" +#include "z_zone.h" + +typedef struct +{ + wad_file_t wad; + int handle; +} posix_wad_file_t; + +extern wad_file_class_t posix_wad_file; + +static void MapFile(posix_wad_file_t *wad, const char *filename) +{ + void *result; + int protection; + int flags; + + // Mapped area can be read and written to. Ideally + // this should be read-only, as none of the Doom code should + // change the WAD files after being read. However, there may + // be code lurking in the source that does. + + protection = PROT_READ|PROT_WRITE; + + // Writes to the mapped area result in private changes that are + // *not* written to disk. + + flags = MAP_PRIVATE; + + result = mmap(NULL, wad->wad.length, + protection, flags, + wad->handle, 0); + + wad->wad.mapped = result; + + if (result == NULL) + { + stderr_print( "W_POSIX_OpenFile: Unable to mmap() %s - %s\n", + filename, strerror(errno)); + } +} + +unsigned int GetFileLength(int handle) +{ + return lseek(handle, 0, SEEK_END); +} + +static wad_file_t *W_POSIX_OpenFile(const char *path) +{ + posix_wad_file_t *result; + int handle; + + handle = open(path, 0); + + if (handle < 0) + { + return NULL; + } + + // Create a new posix_wad_file_t to hold the file handle. + + result = Z_Malloc(sizeof(posix_wad_file_t), PU_STATIC, 0); + result->wad.file_class = &posix_wad_file; + result->wad.length = GetFileLength(handle); + result->wad.path = M_StringDuplicate(path); + result->handle = handle; + + // Try to map the file into memory with mmap: + + MapFile(result, path); + + return &result->wad; +} + +static void W_POSIX_CloseFile(wad_file_t *wad) +{ + posix_wad_file_t *posix_wad; + + posix_wad = (posix_wad_file_t *) wad; + + // If mapped, unmap it. + + // Close the file + + close(posix_wad->handle); + Z_Free(posix_wad); +} + +// Read data from the specified position in the file into the +// provided buffer. Returns the number of bytes read. + +size_t W_POSIX_Read(wad_file_t *wad, unsigned int offset, + void *buffer, size_t buffer_len) +{ + posix_wad_file_t *posix_wad; + byte *byte_buffer; + size_t bytes_read; + int result; + + posix_wad = (posix_wad_file_t *) wad; + + // Jump to the specified position in the file. + + lseek(posix_wad->handle, offset, SEEK_SET); + + // Read into the buffer. + + bytes_read = 0; + byte_buffer = buffer; + + while (buffer_len > 0) { + result = read(posix_wad->handle, byte_buffer, buffer_len); + + if (result < 0) { + perror("W_POSIX_Read"); + break; + } else if (result == 0) { + break; + } + + // Successfully read some bytes + + byte_buffer += result; + buffer_len -= result; + bytes_read += result; + } + + return bytes_read; +} + + +wad_file_class_t posix_wad_file = +{ + W_POSIX_OpenFile, + W_POSIX_CloseFile, + W_POSIX_Read, +}; + + +#endif /* #ifdef HAVE_MMAP */ + +#else +#endif \ No newline at end of file diff --git a/src/picodoom.h b/src/picodoom.h new file mode 100644 index 00000000..d04e040d --- /dev/null +++ b/src/picodoom.h @@ -0,0 +1,41 @@ +#ifndef _PICODOOM_H +#define _PICODOOM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "m_fixed.h" + +typedef enum { + PDCOL_NONE = 0, + PDCOL_TOP, + PDCOL_MID, + PDCOL_BOTTOM, + PDCOL_SKY, + PDCOL_MASKED, + PDCOL_FLOOR, + PDCOL_CEILING, +} pd_column_type; + +extern volatile uint8_t interp_in_use; +void pd_init(); +void pd_core1_loop(); +void pd_begin_frame(); +void pd_add_column(pd_column_type type); +void pd_add_masked_columns(uint8_t *ys, int seg_count); +void pd_add_plane_column(int x, int yl, int yh, fixed_t scale, int floor, int fd_num); +void pd_end_frame(int wipe_start); +uint8_t *pd_get_work_area(uint32_t *size); +#if PICO_ON_DEVICE +void pd_start_save_pause(void); +void pd_end_save_pause(void); +const uint8_t *get_end_of_flash(void); +#endif +extern int pd_flag; +extern fixed_t pd_scale; +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/setup/display.c b/src/setup/display.c index 763ab4ed..00cd3704 100644 --- a/src/setup/display.c +++ b/src/setup/display.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -259,8 +260,8 @@ void BindDisplayVariables(void) M_BindIntVariable("window_width", &window_width); M_BindIntVariable("window_height", &window_height); M_BindIntVariable("startup_delay", &startup_delay); - M_BindStringVariable("video_driver", &video_driver); - M_BindStringVariable("window_position", &window_position); + M_BindStringVariable("video_driver", (constcharstar *)&video_driver); + M_BindStringVariable("window_position", (constcharstar *)&window_position); M_BindIntVariable("usegamma", &usegamma); M_BindIntVariable("png_screenshots", &png_screenshots); M_BindIntVariable("vga_porch_flash", &vga_porch_flash); diff --git a/src/setup/display.h b/src/setup/display.h index 0c20c51a..9f1b920c 100644 --- a/src/setup/display.h +++ b/src/setup/display.h @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -19,7 +20,7 @@ void ConfigDisplay(void *widget, void *user_data); void SetDisplayDriver(void); void BindDisplayVariables(void); -extern int show_endoom; +extern isb_int8_t show_endoom; extern int graphical_startup; extern int png_screenshots; diff --git a/src/setup/joystick.c b/src/setup/joystick.c index 07388735..2aca5ad1 100644 --- a/src/setup/joystick.c +++ b/src/setup/joystick.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1089,7 +1090,7 @@ void BindJoystickVariables(void) int i; M_BindIntVariable("use_joystick", &usejoystick); - M_BindStringVariable("joystick_guid", &joystick_guid); + M_BindStringVariable("joystick_guid", (constcharstar *)&joystick_guid); M_BindIntVariable("joystick_index", &joystick_index); M_BindIntVariable("joystick_x_axis", &joystick_x_axis); M_BindIntVariable("joystick_y_axis", &joystick_y_axis); diff --git a/src/setup/mode.c b/src/setup/mode.c index dbe667df..f88cd628 100644 --- a/src/setup/mode.c +++ b/src/setup/mode.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -107,9 +108,9 @@ static int detailLevel = 0; static char *savedir = NULL; static char *executable = NULL; static const char *game_title = "Doom"; -static char *back_flat = "F_PAVE01"; +static const char *back_flat = "F_PAVE01"; static int comport = 0; -static char *nickname = NULL; +static const char *nickname = NULL; static void BindMiscVariables(void) { @@ -121,7 +122,7 @@ static void BindMiscVariables(void) if (gamemission == hexen) { - M_BindStringVariable("savedir", &savedir); + M_BindStringVariable("savedir", (constcharstar *)&savedir); M_BindIntVariable("messageson", &showMessages); // Hexen has a variable to control the savegame directory diff --git a/src/setup/mouse.c b/src/setup/mouse.c index e25cd990..87af06d4 100644 --- a/src/setup/mouse.c +++ b/src/setup/mouse.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -36,7 +37,7 @@ static int grabmouse = 1; int novert = 0; -static int *all_mouse_buttons[] = { +static mouseb_type_t *all_mouse_buttons[] = { &mousebfire, &mousebstrafe, &mousebforward, @@ -67,7 +68,7 @@ static void MouseSetCallback(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(variable)) } } -static void AddMouseControl(TXT_UNCAST_ARG(table), const char *label, int *var) +static void AddMouseControl(TXT_UNCAST_ARG(table), const char *label, mouseb_type_t *var) { TXT_CAST_ARG(txt_table_t, table); txt_mouse_input_t *mouse_input; diff --git a/src/setup/multiplayer.c b/src/setup/multiplayer.c index 3faaaca1..2a982b3c 100644 --- a/src/setup/multiplayer.c +++ b/src/setup/multiplayer.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -119,8 +120,8 @@ static const char *strife_gamemodes[] = "Items respawn", // (altdeath) }; -static char *net_player_name; -static char *chat_macros[10]; +static const char *net_player_name; +static const char *chat_macros[10]; static char *wads[NUM_WADS]; static char *extra_params[NUM_EXTRA_PARAMS]; @@ -1135,7 +1136,7 @@ void MultiplayerConfig(TXT_UNCAST_ARG(widget), void *user_data) TXT_AddWidgets(window, TXT_NewStrut(0, 1), TXT_NewHorizBox(TXT_NewLabel("Player name: "), - TXT_NewInputBox(&net_player_name, 25), + TXT_NewInputBox((char **)&net_player_name, 25), NULL), TXT_NewStrut(0, 1), TXT_NewSeparator("Chat macros"), @@ -1152,7 +1153,7 @@ void MultiplayerConfig(TXT_UNCAST_ARG(widget), void *user_data) TXT_AddWidgets(table, label, - TXT_NewInputBox(&chat_macros[(i + 1) % 10], 40), + TXT_NewInputBox((char **)&chat_macros[(i + 1) % 10], 40), NULL); } diff --git a/src/setup/sound.c b/src/setup/sound.c index cae4ff39..40d262df 100644 --- a/src/setup/sound.c +++ b/src/setup/sound.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -51,9 +52,9 @@ int snd_samplerate = 44100; int opl_io_port = 0x388; int snd_cachesize = 64 * 1024 * 1024; int snd_maxslicetime_ms = 28; -char *snd_musiccmd = ""; +const char *snd_musiccmd = ""; int snd_pitchshift = 0; -char *snd_dmxoption = ""; +const char *snd_dmxoption = ""; static int numChannels = 8; static int sfxVolume = 8; @@ -63,9 +64,9 @@ static int show_talk = 0; static int use_libsamplerate = 0; static float libsamplerate_scale = 0.65; -static char *music_pack_path = NULL; -static char *timidity_cfg_path = NULL; -static char *gus_patch_path = NULL; +static const char *music_pack_path = NULL; +static const char *timidity_cfg_path = NULL; +static const char *gus_patch_path = NULL; static int gus_ram_kb = 1024; // DOS specific variables: these are unused but should be maintained @@ -181,7 +182,7 @@ void ConfigSound(TXT_UNCAST_ARG(widget), void *user_data) TXT_NewStrut(4, 0), TXT_NewLabel("Path to patch files: "), TXT_NewStrut(4, 0), - TXT_NewFileSelector(&gus_patch_path, 34, + TXT_NewFileSelector((char **)&gus_patch_path, 34, "Select directory containing GUS patches", TXT_DIRECTORY), NULL)), @@ -192,7 +193,7 @@ void ConfigSound(TXT_UNCAST_ARG(widget), void *user_data) TXT_NewStrut(4, 0), TXT_NewLabel("Timidity configuration file: "), TXT_NewStrut(4, 0), - TXT_NewFileSelector(&timidity_cfg_path, 34, + TXT_NewFileSelector((char **)&timidity_cfg_path, 34, "Select Timidity config file", cfg_extension), NULL)), diff --git a/src/setup/sound.h b/src/setup/sound.h index d0c47dc8..b6740584 100644 --- a/src/setup/sound.h +++ b/src/setup/sound.h @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -20,6 +21,6 @@ void ConfigSound(void *widget, void *user_data); void BindSoundVariables(void); -extern char *snd_dmxoption; +extern const char *snd_dmxoption; #endif /* #ifndef SETUP_SOUND_H */ diff --git a/src/sha1.c b/src/sha1.c index 8504134a..fb70faf4 100644 --- a/src/sha1.c +++ b/src/sha1.c @@ -54,7 +54,7 @@ void SHA1_Init(sha1_context_t *hd) /**************** * Transform the message X which consists of 16 32-bit-words */ -static void Transform(sha1_context_t *hd, byte *data) +static void Transform(sha1_context_t *hd, should_be_const byte *data) { uint32_t a,b,c,d,e,tm; uint32_t x[16]; @@ -197,7 +197,7 @@ static void Transform(sha1_context_t *hd, byte *data) /* Update the message digest with the contents * of INBUF with length INLEN. */ -void SHA1_Update(sha1_context_t *hd, byte *inbuf, size_t inlen) +void SHA1_Update(sha1_context_t *hd, should_be_const byte *inbuf, size_t inlen) { if (hd->count == 64) { diff --git a/src/sha1.h b/src/sha1.h index 249571be..862fcf89 100644 --- a/src/sha1.h +++ b/src/sha1.h @@ -31,7 +31,7 @@ struct sha1_context_s { }; void SHA1_Init(sha1_context_t *context); -void SHA1_Update(sha1_context_t *context, byte *buf, size_t len); +void SHA1_Update(sha1_context_t *context, should_be_const byte *buf, size_t len); void SHA1_Final(sha1_digest_t digest, sha1_context_t *context); void SHA1_UpdateInt32(sha1_context_t *context, unsigned int val); void SHA1_UpdateString(sha1_context_t *context, char *str); diff --git a/src/strife/CMakeLists.txt b/src/strife/CMakeLists.txt index 06b17a6b..89c06b54 100644 --- a/src/strife/CMakeLists.txt +++ b/src/strife/CMakeLists.txt @@ -1,4 +1,6 @@ -set(STRIFE_SOURCES +cmake_policy(SET CMP0076 NEW) +add_library(strife INTERFACE) +target_sources(strife INTERFACE am_map.c am_map.h deh_ammo.c deh_cheat.c @@ -67,7 +69,5 @@ set(STRIFE_SOURCES st_stuff.c st_stuff.h wi_stuff.c wi_stuff.h) -add_library(strife STATIC ${STRIFE_SOURCES}) - -target_include_directories(strife PRIVATE "../" "../../win32/" "${CMAKE_CURRENT_BINARY_DIR}/../../") -target_link_libraries(strife textscreen SDL2::SDL2 SDL2::mixer SDL2::net) +target_include_directories(strife INTERFACE "../" "../../win32/" "${CMAKE_CURRENT_BINARY_DIR}/../../") +target_link_libraries(strife INTERFACE textscreen SDL2::SDL2 SDL2::mixer SDL2::net) diff --git a/src/strife/am_map.c b/src/strife/am_map.c index 22913c39..bc70d928 100644 --- a/src/strife/am_map.c +++ b/src/strife/am_map.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1213,12 +1214,12 @@ AM_rotate fixed_t tmpx; tmpx = - FixedMul(*x,finecosine[a>>ANGLETOFINESHIFT]) - - FixedMul(*y,finesine[a>>ANGLETOFINESHIFT]); + FixedMul(*x,finecosine(a>>ANGLETOFINESHIFT)) + - FixedMul(*y,finesine(a>>ANGLETOFINESHIFT)); *y = - FixedMul(*x,finesine[a>>ANGLETOFINESHIFT]) - + FixedMul(*y,finecosine[a>>ANGLETOFINESHIFT]); + FixedMul(*x,finesine(a>>ANGLETOFINESHIFT)) + + FixedMul(*y,finecosine(a>>ANGLETOFINESHIFT)); *x = tmpx; } diff --git a/src/strife/d_net.c b/src/strife/d_net.c index 5b05e304..b7c12dfc 100644 --- a/src/strife/d_net.c +++ b/src/strife/d_net.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -197,7 +198,7 @@ static void InitConnectData(net_connect_data_t *connect_data) // Game type fields: - connect_data->gamemode = gamemode; + connect_data->_gamemode = gamemode; connect_data->gamemission = gamemission; // Are we recording a demo? Possibly set lowres turn mode diff --git a/src/strife/g_game.c b/src/strife/g_game.c index c29ca4e6..37fba5b3 100644 --- a/src/strife/g_game.c +++ b/src/strife/g_game.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1238,7 +1239,7 @@ G_CheckSpot ss = R_PointInSubsector (x,y); an = ( ANG45 * (((unsigned int) mthing->angle)/45) ) >> ANGLETOFINESHIFT; - mo = P_SpawnMobj (x+20*finecosine[an], y+20*finesine[an] + mo = P_SpawnMobj (x+20*finecosine(an), y+20*finesine(an) , ss->sector->floorheight , MT_TFOG); diff --git a/src/strife/m_menu.c b/src/strife/m_menu.c index c3724986..d2bb80c1 100644 --- a/src/strife/m_menu.c +++ b/src/strife/m_menu.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -119,7 +120,7 @@ int saveStringEnter; int saveSlot; // which slot to save in int saveCharIndex; // which char we're editing // old save description before edit -char saveOldString[SAVESTRINGSIZE]; +char stringEntryOldString[SAVESTRINGSIZE]; boolean inhelpscreens; boolean menuactive; @@ -768,7 +769,7 @@ void M_SaveSelect(int choice) quickSaveSlot = choice; //saveSlot = choice; - M_StringCopy(saveOldString, savegamestrings[choice], sizeof(saveOldString)); + M_StringCopy(stringEntryOldString, savegamestrings[choice], sizeof(stringEntryOldString)); if (!strcmp(savegamestrings[choice], DEH_String(EMPTYSTRING))) savegamestrings[choice][0] = 0; saveCharIndex = strlen(savegamestrings[choice]); @@ -1859,7 +1860,7 @@ boolean M_Responder (event_t* ev) case KEY_ESCAPE: saveStringEnter = 0; I_StopTextInput(); - M_StringCopy(savegamestrings[quickSaveSlot], saveOldString, + M_StringCopy(savegamestrings[quickSaveSlot], stringEntryOldString, sizeof(savegamestrings[quickSaveSlot])); break; diff --git a/src/strife/p_enemy.c b/src/strife/p_enemy.c index 09d27001..10e73a02 100644 --- a/src/strife/p_enemy.c +++ b/src/strife/p_enemy.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1321,8 +1322,8 @@ void A_SentinelAttack(mobj_t* actor) { for(i = 8; i > 1; i--) { - x = mo->x + FixedMul(mobjinfo[MT_L_LASER].radius * i, finecosine[an]); - y = mo->y + FixedMul(mobjinfo[MT_L_LASER].radius * i, finesine[an]); + x = mo->x + FixedMul(mobjinfo[MT_L_LASER].radius * i, finecosine(an)); + y = mo->y + FixedMul(mobjinfo[MT_L_LASER].radius * i, finesine(an)); z = mo->z + i * (mo->momz >> 2); mo2 = P_SpawnMobj(x, y, z, MT_R_LASER); mo2->target = actor; @@ -1680,8 +1681,8 @@ void A_InqTakeOff(mobj_t* actor) an = actor->angle >> ANGLETOFINESHIFT; - actor->momx = FixedMul(finecosine[an], speed); - actor->momy = FixedMul(finesine[an], speed); + actor->momx = FixedMul(finecosine(an), speed); + actor->momy = FixedMul(finesine(an), speed); dist = P_AproxDistance(actor->target->x - actor->x, actor->target->y - actor->y); @@ -2175,8 +2176,8 @@ void A_Tracer (mobj_t* actor) } exact = actor->angle>>ANGLETOFINESHIFT; - actor->momx = FixedMul (actor->info->speed, finecosine[exact]); - actor->momy = FixedMul (actor->info->speed, finesine[exact]); + actor->momx = FixedMul (actor->info->speed, finecosine(exact)); + actor->momy = FixedMul (actor->info->speed, finesine(exact)); // change slope dist = P_AproxDistance (dest->x - actor->x, @@ -2520,8 +2521,8 @@ void A_SpawnEntity(mobj_t* actor) void P_ThrustMobj(mobj_t *actor, angle_t angle, fixed_t force) { angle_t an = angle >> ANGLETOFINESHIFT; - actor->momx += FixedMul(finecosine[an], force); - actor->momy += FixedMul(finesine[an], force); + actor->momx += FixedMul(finecosine(an), force); + actor->momy += FixedMul(finesine(an), force); } // @@ -2542,8 +2543,8 @@ void A_EntityDeath(mobj_t* actor) // Subentity One an = actor->angle >> ANGLETOFINESHIFT; - subentity = P_SpawnMobj(FixedMul(finecosine[an], dist) + entity_pos_x, - FixedMul(finesine[an], dist) + entity_pos_y, + subentity = P_SpawnMobj(FixedMul(finecosine(an), dist) + entity_pos_x, + FixedMul(finesine(an), dist) + entity_pos_y, entity_pos_z, MT_SUBENTITY); subentity->target = actor->target; A_FaceTarget(subentity); @@ -2551,8 +2552,8 @@ void A_EntityDeath(mobj_t* actor) // Subentity Two an = (actor->angle + ANG90) >> ANGLETOFINESHIFT; - subentity = P_SpawnMobj(FixedMul(finecosine[an], dist) + entity_pos_x, - FixedMul(finesine[an], dist) + entity_pos_y, + subentity = P_SpawnMobj(FixedMul(finecosine(an), dist) + entity_pos_x, + FixedMul(finesine(an), dist) + entity_pos_y, entity_pos_z, MT_SUBENTITY); subentity->target = actor->target; P_ThrustMobj(subentity, actor->angle + ANG90, 4); @@ -2560,8 +2561,8 @@ void A_EntityDeath(mobj_t* actor) // Subentity Three an = (actor->angle - ANG90) >> ANGLETOFINESHIFT; - subentity = P_SpawnMobj(FixedMul(finecosine[an], dist) + entity_pos_x, - FixedMul(finesine[an], dist) + entity_pos_y, + subentity = P_SpawnMobj(FixedMul(finecosine(an), dist) + entity_pos_x, + FixedMul(finesine(an), dist) + entity_pos_y, entity_pos_z, MT_SUBENTITY); subentity->target = actor->target; P_ThrustMobj(subentity, actor->angle - ANG90, 4); @@ -3200,8 +3201,8 @@ void A_TeleportBeacon(mobj_t* actor) P_SetMobjState(mobj, mobj->info->seestate); mobj->angle = actor->angle; - fog_x = mobj->x + FixedMul(20*FRACUNIT, finecosine[actor->angle>>ANGLETOFINESHIFT]); - fog_y = mobj->y + FixedMul(20*FRACUNIT, finesine[actor->angle>>ANGLETOFINESHIFT]); + fog_x = mobj->x + FixedMul(20*FRACUNIT, finecosine(actor->angle>>ANGLETOFINESHIFT)); + fog_y = mobj->y + FixedMul(20*FRACUNIT, finesine(actor->angle>>ANGLETOFINESHIFT)); fog = P_SpawnMobj(fog_x, fog_y, mobj->z, MT_TFOG); S_StartSound(fog, sfx_telept); @@ -3234,8 +3235,8 @@ void A_BodyParts(mobj_t* actor) an = (P_Random() << 13) / 255; mo->angle = an << ANGLETOFINESHIFT; - mo->momx = FixedMul(finecosine[an], (P_Random() & 0x0f) << FRACBITS); - mo->momy = FixedMul(finesine[an], (P_Random() & 0x0f) << FRACBITS); + mo->momx = FixedMul(finecosine(an), (P_Random() & 0x0f) << FRACBITS); + mo->momy = FixedMul(finesine(an), (P_Random() & 0x0f) << FRACBITS); mo->momz = (P_Random() & 0x0f) << FRACBITS; } diff --git a/src/strife/p_inter.c b/src/strife/p_inter.c index b92cacfd..41975627 100644 --- a/src/strife/p_inter.c +++ b/src/strife/p_inter.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1146,8 +1147,8 @@ void P_DamageMobj(mobj_t* target, mobj_t* inflictor, mobj_t* source, int damage) source->x, source->y) >> ANGLETOFINESHIFT; - target->momx += FixedMul(finecosine[ang], (12750*FRACUNIT) / target->info->mass); - target->momy += FixedMul(finesine[ang], (12750*FRACUNIT) / target->info->mass); + target->momx += FixedMul(finecosine(ang), (12750*FRACUNIT) / target->info->mass); + target->momy += FixedMul(finesine(ang), (12750*FRACUNIT) / target->info->mass); target->reactiontime += 10; temp = P_AproxDistance(target->x - source->x, target->y - source->y); @@ -1234,8 +1235,8 @@ void P_DamageMobj(mobj_t* target, mobj_t* inflictor, mobj_t* source, int damage) } ang >>= ANGLETOFINESHIFT; - target->momx += FixedMul (thrust, finecosine[ang]); - target->momy += FixedMul (thrust, finesine[ang]); + target->momx += FixedMul (thrust, finecosine(ang)); + target->momy += FixedMul (thrust, finesine(ang)); } // player specific diff --git a/src/strife/p_map.c b/src/strife/p_map.c index 03b57edd..68714629 100644 --- a/src/strife/p_map.c +++ b/src/strife/p_map.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard, Andrey Budko +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -769,10 +770,10 @@ void P_HitSlideLine (line_t* ld) deltaangle >>= ANGLETOFINESHIFT; movelen = P_AproxDistance (tmxmove, tmymove); - newlen = FixedMul (movelen, finecosine[deltaangle]); + newlen = FixedMul (movelen, finecosine(deltaangle)); - tmxmove = FixedMul (newlen, finecosine[lineangle]); - tmymove = FixedMul (newlen, finesine[lineangle]); + tmxmove = FixedMul (newlen, finecosine(lineangle)); + tmymove = FixedMul (newlen, finesine(lineangle)); } @@ -1227,8 +1228,8 @@ P_AimLineAttack angle >>= ANGLETOFINESHIFT; shootthing = t1; - x2 = t1->x + (distance>>FRACBITS)*finecosine[angle]; - y2 = t1->y + (distance>>FRACBITS)*finesine[angle]; + x2 = t1->x + (distance>>FRACBITS)*finecosine(angle); + y2 = t1->y + (distance>>FRACBITS)*finesine(angle); shootz = t1->z + (t1->height>>1) + 8*FRACUNIT; // can't shoot outside view angles @@ -1277,8 +1278,8 @@ P_LineAttack angle >>= ANGLETOFINESHIFT; shootthing = t1; la_damage = damage; - x2 = t1->x + (distance>>FRACBITS)*finecosine[angle]; - y2 = t1->y + (distance>>FRACBITS)*finesine[angle]; + x2 = t1->x + (distance>>FRACBITS)*finecosine(angle); + y2 = t1->y + (distance>>FRACBITS)*finesine(angle); shootz = t1->z + (t1->height>>1) + 8*FRACUNIT; attackrange = distance; aimslope = slope; @@ -1355,8 +1356,8 @@ void P_UseLines (player_t* player) x1 = player->mo->x; y1 = player->mo->y; - x2 = x1 + (USERANGE>>FRACBITS)*finecosine[angle]; - y2 = y1 + (USERANGE>>FRACBITS)*finesine[angle]; + x2 = x1 + (USERANGE>>FRACBITS)*finecosine(angle); + y2 = y1 + (USERANGE>>FRACBITS)*finesine(angle); P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse ); } diff --git a/src/strife/p_mobj.c b/src/strife/p_mobj.c index 919c4a7b..550dff4f 100644 --- a/src/strife/p_mobj.c +++ b/src/strife/p_mobj.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -1190,8 +1191,8 @@ P_SpawnMissile th->angle = an; an >>= ANGLETOFINESHIFT; - th->momx = FixedMul (th->info->speed, finecosine[an]); - th->momy = FixedMul (th->info->speed, finesine[an]); + th->momx = FixedMul (th->info->speed, finecosine(an)); + th->momy = FixedMul (th->info->speed, finesine(an)); dist = P_AproxDistance (dest->x - source->x, dest->y - source->y); dist = dist / th->info->speed; @@ -1240,8 +1241,8 @@ mobj_t* P_SpawnFacingMissile(mobj_t* source, mobj_t* target, mobjtype_t type) } an >>= ANGLETOFINESHIFT; - th->momx = FixedMul (th->info->speed, finecosine[an]); - th->momy = FixedMul (th->info->speed, finesine[an]); + th->momx = FixedMul (th->info->speed, finecosine(an)); + th->momy = FixedMul (th->info->speed, finesine(an)); dist = P_AproxDistance (target->x - source->x, target->y - source->y); dist = dist / th->info->speed; @@ -1318,9 +1319,9 @@ mobj_t* P_SpawnPlayerMissile(mobj_t* source, mobjtype_t type) th->target = source; th->angle = an; th->momx = FixedMul( th->info->speed, - finecosine[an>>ANGLETOFINESHIFT]); + finecosine(an>>ANGLETOFINESHIFT)); th->momy = FixedMul( th->info->speed, - finesine[an>>ANGLETOFINESHIFT]); + finesine(an>>ANGLETOFINESHIFT)); th->momz = FixedMul( th->info->speed, slope); P_CheckMissileSpawn (th); @@ -1348,8 +1349,8 @@ mobj_t* P_SpawnMortar(mobj_t *source, mobjtype_t type) an >>= ANGLETOFINESHIFT; // haleyjd 20110203: corrected order of function calls - th->momx = FixedMul(th->info->speed, finecosine[an]); - th->momy = FixedMul(th->info->speed, finesine[an]); + th->momx = FixedMul(th->info->speed, finecosine(an)); + th->momy = FixedMul(th->info->speed, finesine(an)); P_CheckMissileSpawn(th); diff --git a/src/strife/p_pspr.c b/src/strife/p_pspr.c index 4295c977..711ca5a4 100644 --- a/src/strife/p_pspr.c +++ b/src/strife/p_pspr.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -295,9 +296,9 @@ void A_WeaponReady( player_t* player, pspdef_t* psp) // bob the weapon based on movement speed angle = (128*leveltime)&FINEMASK; - psp->sx = FRACUNIT + FixedMul (player->bob, finecosine[angle]); + psp->sx = FRACUNIT + FixedMul (player->bob, finecosine(angle)); angle &= FINEANGLES/2-1; - psp->sy = WEAPONTOP + FixedMul (player->bob, finesine[angle]); + psp->sy = WEAPONTOP + FixedMul (player->bob, finesine(angle)); } @@ -581,8 +582,8 @@ void A_FireGrenade(player_t* player, pspdef_t* pspr) radius = mobjinfo[type].radius + player->mo->info->radius; an = (player->mo->angle >> ANGLETOFINESHIFT); - mo->x += FixedMul(finecosine[an], radius + (4*FRACUNIT)); - mo->y += FixedMul(finesine[an], radius + (4*FRACUNIT)); + mo->x += FixedMul(finecosine(an), radius + (4*FRACUNIT)); + mo->y += FixedMul(finesine(an), radius + (4*FRACUNIT)); // shoot grenade from left or right side? if(&states[weaponinfo[player->readyweapon].atkstate] == pspr->state) @@ -590,8 +591,8 @@ void A_FireGrenade(player_t* player, pspdef_t* pspr) else an = (player->mo->angle + ANG90) >> ANGLETOFINESHIFT; - mo->x += FixedMul((15*FRACUNIT), finecosine[an]); - mo->y += FixedMul((15*FRACUNIT), finesine[an]); + mo->x += FixedMul((15*FRACUNIT), finecosine(an)); + mo->y += FixedMul((15*FRACUNIT), finesine(an)); // set bounce flag mo->flags |= MF_BOUNCE; @@ -805,8 +806,8 @@ void A_FireSigil(player_t* player, pspdef_t* pspr) an = player->mo->angle>>ANGLETOFINESHIFT; mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SIGIL_A_GROUND); - mo->momx += FixedMul((28*FRACUNIT), finecosine[an]); - mo->momy += FixedMul((28*FRACUNIT), finesine[an]); + mo->momx += FixedMul((28*FRACUNIT), finecosine(an)); + mo->momy += FixedMul((28*FRACUNIT), finesine(an)); } mo->health = -1; mo->target = player->mo; @@ -842,8 +843,8 @@ void A_FireSigil(player_t* player, pspdef_t* pspr) { an = player->mo->angle >> ANGLETOFINESHIFT; mo = P_SpawnPlayerMissile(player->mo, MT_SIGIL_D_SHOT); - mo->momx += FixedMul(mo->info->speed, finecosine[an]); - mo->momy += FixedMul(mo->info->speed, finesine[an]); + mo->momx += FixedMul(mo->info->speed, finecosine(an)); + mo->momy += FixedMul(mo->info->speed, finesine(an)); } mo->health = -1; break; @@ -855,7 +856,7 @@ void A_FireSigil(player_t* player, pspdef_t* pspr) if(!linetarget) { an = (unsigned int)player->pitch >> ANGLETOFINESHIFT; - mo->momz += FixedMul(finesine[an], mo->info->speed); + mo->momz += FixedMul(finesine(an), mo->info->speed); } break; diff --git a/src/strife/p_switch.c b/src/strife/p_switch.c index 849ab2bc..3191459c 100644 --- a/src/strife/p_switch.c +++ b/src/strife/p_switch.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -225,8 +226,8 @@ static void P_SpawnBrokenGlass(line_t* line) an = ((P_Random() << 13) / 255); glass->angle = (an << ANGLETOFINESHIFT); - glass->momx = FixedMul(finecosine[an], (P_Random() & 3) << FRACBITS); - glass->momy = FixedMul(finesine[an], (P_Random() & 3) << FRACBITS); + glass->momx = FixedMul(finecosine(an), (P_Random() & 3) << FRACBITS); + glass->momy = FixedMul(finesine(an), (P_Random() & 3) << FRACBITS); glass->momz = (P_Random() & 7) << FRACBITS; glass->tics += (P_Random() + 7) & 7; } diff --git a/src/strife/p_telept.c b/src/strife/p_telept.c index 544c2336..d0d88b4a 100644 --- a/src/strife/p_telept.c +++ b/src/strife/p_telept.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -132,7 +133,7 @@ EV_Teleport an = m->angle >> ANGLETOFINESHIFT; if(!(flags & TF_NODSTFOG)) - fog = P_SpawnMobj (m->x+20*finecosine[an], m->y+20*finesine[an], + fog = P_SpawnMobj (m->x+20*finecosine(an), m->y+20*finesine(an), thing->z, MT_TFOG); if(!(flags & TF_NODSTSND)) S_StartSound (fog, sfx_telept); diff --git a/src/strife/p_user.c b/src/strife/p_user.c index bf082e99..2f919578 100644 --- a/src/strife/p_user.c +++ b/src/strife/p_user.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -73,8 +74,8 @@ P_Thrust { angle >>= ANGLETOFINESHIFT; - player->mo->momx += FixedMul(move,finecosine[angle]); - player->mo->momy += FixedMul(move,finesine[angle]); + player->mo->momx += FixedMul(move,finecosine(angle)); + player->mo->momy += FixedMul(move,finesine(angle)); } @@ -122,7 +123,7 @@ void P_CalcHeight (player_t* player) } angle = (FINEANGLES/20*leveltime)&FINEMASK; - bob = FixedMul ( player->bob/2, finesine[angle]); + bob = FixedMul ( player->bob/2, finesine(angle)); // move viewheight if (player->playerstate == PST_LIVE) @@ -705,8 +706,8 @@ void P_DropInventoryItem(player_t* player, int sprite) mo = player->mo; dist = mobjinfo[type].radius + mo->info->radius + (4*FRACUNIT); - x = mo->x + FixedMul(finecosine[angle], dist); - y = mo->y + FixedMul(finesine[angle], dist); + x = mo->x + FixedMul(finecosine(angle), dist); + y = mo->y + FixedMul(finesine(angle), dist); z = mo->z + (10*FRACUNIT); mobjitem = P_SpawnMobj(x, y, z, type); mobjitem->flags |= (MF_SPECIAL|MF_DROPPED); @@ -714,8 +715,8 @@ void P_DropInventoryItem(player_t* player, int sprite) if(P_CheckPosition(mobjitem, x, y)) { mobjitem->angle = (angle << ANGLETOFINESHIFT); - mobjitem->momx = FixedMul(finecosine[angle], (5*FRACUNIT)) + mo->momx; - mobjitem->momy = FixedMul(finesine[angle], (5*FRACUNIT)) + mo->momy; + mobjitem->momx = FixedMul(finecosine(angle), (5*FRACUNIT)) + mo->momx; + mobjitem->momy = FixedMul(finesine(angle), (5*FRACUNIT)) + mo->momy; mobjitem->momz = FRACUNIT; P_RemoveInventoryItem(player, invslot, amount); @@ -756,8 +757,8 @@ boolean P_TossDegninOre(player_t* player) mo = player->mo; dist = mobjinfo[MT_DEGNINORE].radius + mo->info->radius + (4*FRACUNIT); - x = mo->x + FixedMul(finecosine[angle], dist); - y = mo->y + FixedMul(finesine[angle], dist); + x = mo->x + FixedMul(finecosine(angle), dist); + y = mo->y + FixedMul(finesine(angle), dist); z = mo->z + (10*FRACUNIT); ore = P_SpawnMobj(x, y, z, MT_DEGNINORE); @@ -765,8 +766,8 @@ boolean P_TossDegninOre(player_t* player) { ore->target = mo; ore->angle = (angle << ANGLETOFINESHIFT); - ore->momx = FixedMul(finecosine[angle], (5*FRACUNIT)); - ore->momy = FixedMul(finesine[angle], (5*FRACUNIT)); + ore->momx = FixedMul(finecosine(angle), (5*FRACUNIT)); + ore->momy = FixedMul(finesine(angle), (5*FRACUNIT)); ore->momz = FRACUNIT; return true; } @@ -812,8 +813,8 @@ boolean P_SpawnTeleportBeacon(player_t* player) mo = player->mo; dist = mobjinfo[MT_BEACON].radius + mo->info->radius + (4*FRACUNIT); - x = mo->x + FixedMul(finecosine[angle], dist); - y = mo->y + FixedMul(finesine[angle], dist); + x = mo->x + FixedMul(finecosine(angle), dist); + y = mo->y + FixedMul(finesine(angle), dist); z = mo->z + (10*FRACUNIT); beacon = P_SpawnMobj(x, y, z, MT_BEACON); @@ -822,8 +823,8 @@ boolean P_SpawnTeleportBeacon(player_t* player) beacon->target = mo; beacon->miscdata = (byte)(player->allegiance); beacon->angle = (angle << ANGLETOFINESHIFT); - beacon->momx = FixedMul(finecosine[angle], (5*FRACUNIT)); - beacon->momy = FixedMul(finesine[angle], (5*FRACUNIT)); + beacon->momx = FixedMul(finecosine(angle), (5*FRACUNIT)); + beacon->momy = FixedMul(finesine(angle), (5*FRACUNIT)); beacon->momz = FRACUNIT; P_SetMobjState(beacon, beacon->info->seestate); return true; diff --git a/src/strife/r_main.c b/src/strife/r_main.c index faaf4b07..ff4b2c4d 100644 --- a/src/strife/r_main.c +++ b/src/strife/r_main.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -273,6 +274,28 @@ R_PointOnSegSide // +static int SlopeDiv(unsigned int num, unsigned int den) +{ + unsigned ans; + + if (den < 512) + { + return SLOPERANGE; + } + else + { + ans = (num << 3) / (den >> 8); + + if (ans <= SLOPERANGE) + { + return ans; + } + else + { + return SLOPERANGE; + } + } +} angle_t @@ -411,7 +434,7 @@ R_PointToDist angle = (tantoangle[frac>>DBITS]+ANG90) >> ANGLETOFINESHIFT; // use as cosine - dist = FixedDiv (dx, finesine[angle] ); + dist = FixedDiv (dx, finesine(angle) ); return dist; } @@ -467,9 +490,9 @@ fixed_t R_ScaleFromGlobalAngle (angle_t visangle) fixed_t sinv; fixed_t cosv; - sinv = finesine[(visangle-rw_normalangle)>>ANGLETOFINESHIFT]; + sinv = finesine((visangle-rw_normalangle)>>ANGLETOFINESHIFT); dist = FixedDiv (rw_distance, sinv); - cosv = finecosine[(viewangle-visangle)>>ANGLETOFINESHIFT]; + cosv = finecosine((viewangle-visangle)>>ANGLETOFINESHIFT); z = abs(FixedMul (dist, cosv)); scale = FixedDiv(projection, z); return scale; @@ -480,8 +503,8 @@ fixed_t R_ScaleFromGlobalAngle (angle_t visangle) angleb = ANG90 + (visangle-rw_normalangle); // both sines are allways positive - sinea = finesine[anglea>>ANGLETOFINESHIFT]; - sineb = finesine[angleb>>ANGLETOFINESHIFT]; + sinea = finesine(anglea>>ANGLETOFINESHIFT); + sineb = finesine(angleb>>ANGLETOFINESHIFT); num = FixedMul(projection,sineb)< FRACUNIT*2) + if (finetangent(i) > FRACUNIT*2) t = -1; - else if (finetangent[i] < -FRACUNIT*2) + else if (finetangent(i) < -FRACUNIT*2) t = viewwidth+1; else { - t = FixedMul (finetangent[i], focallength); + t = FixedMul (finetangent(i), focallength); t = (centerxfrac - t+FRACUNIT-1)>>FRACBITS; if (t < -1) @@ -589,7 +612,7 @@ void R_InitTextureMapping (void) // Take out the fencepost cases from viewangletox. for (i=0 ; i>ANGLETOFINESHIFT]); + cosadj = abs(finecosine(xtoviewangle[i]>>ANGLETOFINESHIFT)); distscale[i] = FixedDiv (FRACUNIT,cosadj); } @@ -886,8 +909,8 @@ void R_SetupFrame (player_t* player) viewz = player->viewz; - viewsin = finesine[viewangle>>ANGLETOFINESHIFT]; - viewcos = finecosine[viewangle>>ANGLETOFINESHIFT]; + viewsin = finesine(viewangle>>ANGLETOFINESHIFT); + viewcos = finecosine(viewangle>>ANGLETOFINESHIFT); sscount = 0; diff --git a/src/strife/r_plane.c b/src/strife/r_plane.c index 97c0c5fe..4f6ce52c 100644 --- a/src/strife/r_plane.c +++ b/src/strife/r_plane.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -148,8 +149,8 @@ R_MapPlane length = FixedMul (distance,distscale[x1]); angle = (viewangle + xtoviewangle[x1])>>ANGLETOFINESHIFT; - ds_xfrac = viewx + FixedMul(finecosine[angle], length); - ds_yfrac = -viewy - FixedMul(finesine[angle], length); + ds_xfrac = viewx + FixedMul(finecosine(angle), length); + ds_yfrac = -viewy - FixedMul(finesine(angle), length); if (fixedcolormap) ds_colormap = fixedcolormap; @@ -198,8 +199,8 @@ void R_ClearPlanes (void) angle = (viewangle-ANG90)>>ANGLETOFINESHIFT; // scale will be unit scale at SCREENWIDTH/2 distance - basexscale = FixedDiv (finecosine[angle],centerxfrac); - baseyscale = -FixedDiv (finesine[angle],centerxfrac); + basexscale = FixedDiv (finecosine(angle),centerxfrac); + baseyscale = -FixedDiv (finesine(angle),centerxfrac); } diff --git a/src/strife/r_segs.c b/src/strife/r_segs.c index ddb0ca7e..f72c2a53 100644 --- a/src/strife/r_segs.c +++ b/src/strife/r_segs.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -265,7 +266,7 @@ void R_RenderSegLoop (void) { // calculate texture offset angle = (rw_centerangle + xtoviewangle[rw_x])>>ANGLETOFINESHIFT; - texturecolumn = rw_offset-FixedMul(finetangent[angle],rw_distance); + texturecolumn = rw_offset-FixedMul(finetangent(angle),rw_distance); texturecolumn >>= FRACBITS; // calculate lighting index = rw_scale>>LIGHTSCALESHIFT; @@ -414,7 +415,7 @@ R_StoreWallRange distangle = ANG90 - offsetangle; hyp = R_PointToDist (curline->v1->x, curline->v1->y); - sineval = finesine[distangle>>ANGLETOFINESHIFT]; + sineval = finesine(distangle>>ANGLETOFINESHIFT); rw_distance = FixedMul (hyp, sineval); @@ -634,7 +635,7 @@ R_StoreWallRange if (offsetangle > ANG90) offsetangle = ANG90; - sineval = finesine[offsetangle >>ANGLETOFINESHIFT]; + sineval = finesine(offsetangle >>ANGLETOFINESHIFT); rw_offset = FixedMul (hyp, sineval); if (rw_normalangle-rw_angle1 < ANG180) diff --git a/src/strife/s_sound.c b/src/strife/s_sound.c index 1de853fc..10e3e943 100644 --- a/src/strife/s_sound.c +++ b/src/strife/s_sound.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -383,7 +384,7 @@ static int S_AdjustSoundParams(mobj_t *listener, mobj_t *source, angle >>= ANGLETOFINESHIFT; // stereo separation - *sep = 128 - (FixedMul(S_STEREO_SWING, finesine[angle]) >> FRACBITS); + *sep = 128 - (FixedMul(S_STEREO_SWING, finesine(angle)) >> FRACBITS); // volume calculation // [STRIFE] Removed gamemap == 8 hack diff --git a/src/tables.c b/src/tables.c index 698ae98f..54241ac0 100644 --- a/src/tables.c +++ b/src/tables.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -32,36 +33,7 @@ #include "tables.h" -// to get a global angle from cartesian coordinates, the coordinates are -// flipped until they are in the first octant of the coordinate system, then -// the y (<=x) is scaled and divided by x to get a tangent (slope) value -// which is looked up in the tantoangle[] table. The +1 size is to handle -// the case when x==y without additional checking. - -int SlopeDiv(unsigned int num, unsigned int den) -{ - unsigned ans; - - if (den < 512) - { - return SLOPERANGE; - } - else - { - ans = (num << 3) / (den >> 8); - - if (ans <= SLOPERANGE) - { - return ans; - } - else - { - return SLOPERANGE; - } - } -} - -const fixed_t finetangent[4096] = +const fixed_t _finetangent[4096] = { -170910304,-56965752,-34178904,-24413316,-18988036,-15535599,-13145455,-11392683, -10052327,-8994149,-8137527,-7429880,-6835455,-6329090,-5892567,-5512368, @@ -578,7 +550,14 @@ const fixed_t finetangent[4096] = }; -const fixed_t finesine[10240] = +#if !DOOM_TINY +const fixed_t _finesine[10240] = +#else +// values are between -65535 and 65536 so we really only care about 16 bits since the sign is obvious based on the index +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Woverflow" +const uint16_t _finesine[10240] = +#endif { 25,75,125,175,226,276,326,376, 427,477,527,578,628,678,728,779, @@ -836,6 +815,7 @@ const fixed_t finesine[10240] = 65525,65526,65527,65527,65528,65529,65530,65530, 65531,65531,65532,65532,65533,65533,65534,65534, 65534,65535,65535,65535,65535,65535,65535,65535, +#if true || !DOOM_TINY 65535,65535,65535,65535,65535,65535,65535,65534, 65534,65534,65533,65533,65532,65532,65531,65531, 65530,65530,65529,65528,65527,65527,65526,65525, @@ -1860,9 +1840,15 @@ const fixed_t finesine[10240] = 65525,65526,65527,65527,65528,65529,65530,65530, 65531,65531,65532,65532,65533,65533,65534,65534, 65534,65535,65535,65535,65535,65535,65535,65535 +#endif }; +#if DOOM_TINY +#pragma GCC diagnostic pop +#endif -const fixed_t *finecosine = &finesine[FINEANGLES/4]; +#if !DOOM_TINY +const fixed_t *_finecosine = &_finesine[FINEANGLES/4]; +#endif const angle_t tantoangle[2049] = { @@ -2126,8 +2112,13 @@ const angle_t tantoangle[2049] = }; // Now where did these came from? +#if !DOOM_TINY const byte gammatable[5][256] = +#else +const byte gammatable[4][256] = +#endif { +#if !DOOM_TINY { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32, @@ -2146,7 +2137,7 @@ const byte gammatable[5][256] = 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 }, - +#endif { 2,4,5,7,8,10,11,12,14,15,16,18,19,20,21,23, 24,25,26,27,29,30,31,32,33,34,36,37,38,39,40,41, diff --git a/src/tables.h b/src/tables.h index f72406da..5d80afb3 100644 --- a/src/tables.h +++ b/src/tables.h @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -40,23 +41,46 @@ #define FINEANGLES 8192 #define FINEMASK (FINEANGLES-1) +#define FINEBITS 13 // 0x100000000 to 0x2000 #define ANGLETOFINESHIFT 19 +#if !DOOM_TINY // Effective size is 10240. -extern const fixed_t finesine[5*FINEANGLES/4]; +extern const fixed_t _finesine[5*FINEANGLES/4]; // Re-use data, is just PI/2 pahse shift. -extern const fixed_t *finecosine; +extern const fixed_t *_finecosine; +// just for checking +#define finesine(x) (_finesine[x] / (1 << (16 - FRACBITS))) +#define finecosine(x) (_finecosine[x] / (1 << (16 - FRACBITS))) +#else +#include +static_assert(FINEANGLES == 1u << FINEBITS, ""); +extern const uint16_t _finesine[5* FINEANGLES/4]; +static inline fixed_t finesine(int x) { + fixed_t rc = _finesine[x]; + // fix the top 16 bits based on the quadrant (they are either 0000 or ffff - this way without a branch) + rc -= (x & (FINEANGLES >> 1)) << (16 - (FINEBITS - 1)); + return rc; +} +#define finecosine(x) finesine((x) + FINEANGLES/4) +#endif // Effective size is 4096. -extern const fixed_t finetangent[FINEANGLES/2]; +extern const fixed_t _finetangent[FINEANGLES/2]; + +#define finetangent(x) (_finetangent[x] / (1 << (16 - FRACBITS))) // Gamma correction tables. +#if !DOOM_TINY extern const byte gammatable[5][256]; +#else +extern const byte gammatable[4][256]; +#endif // Binary Angle Measument, BAM. @@ -89,7 +113,7 @@ extern const angle_t tantoangle[SLOPERANGE+1]; // Utility function, // called by R_PointToAngle. -int SlopeDiv(unsigned int num, unsigned int den); +//int SlopeDiv(unsigned int num, unsigned int den); #endif diff --git a/src/tiny_huff.c b/src/tiny_huff.c new file mode 100644 index 00000000..ca62109f --- /dev/null +++ b/src/tiny_huff.c @@ -0,0 +1,289 @@ +#include "tiny_huff.h" +#include +#include +#if PICO_BUILD +#include "pico.h" +#else +#define __not_in_flash_func(x) x +#endif + +#ifndef NDEBUG +//#define DUMP 1 +#endif + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif +uint th_decoder_size(uint count, uint max_code_length) { + if (!count) { + return 1; + } else if (count == 1) { + return 2; + } else { + return 1 + max_code_length * 2 + (count + 1) / 2; + } +} + +uint th_decoder_size_16(uint count, uint max_code_length) { + if (!count) { + return 1; + } else if (count == 1) { + return 2; + } else { + return 1 + max_code_length * 2 + count; + } +} + + +uint16_t *th_create_decoder(uint16_t *buffer, const uint8_t *symbols_and_lengths, uint count, uint max_code_length) { + if (!count) { + *buffer++ = 0; + } else if (count == 1) { + buffer[0] = 1; + *(uint8_t*)(buffer + 1) = symbols_and_lengths[0]; + buffer += 2; + } else { + buffer[0] = 1 + max_code_length * 2; + uint8_t *symbols = (uint8_t *)(buffer + buffer[0]); + buffer++; + memset(buffer, 0, max_code_length * 2 * sizeof(uint16_t)); + // pre-use ceiling slot for num_codes + #define TH_IDX_NUM_CODES TH_IDX_CEILING + for(int i=0;i 0 && length <= max_code_length); + length--; // length 1 at index 0 + buffer[length * 2 + TH_IDX_NUM_CODES]++; + } + // init offsets + for(int length = 1; length < max_code_length; length++) { + buffer[length * 2 + TH_IDX_OFFSET] = buffer[(length-1)*2 + TH_IDX_OFFSET] + buffer[(length-1)*2 + TH_IDX_NUM_CODES]; + } + // assign symbols + for(int i=0;i=0;i--) { + putchar((code & (1u << i)) ? '1' : '0'); + } + printf("\n"); +#endif + int num_codes = buffer[length * 2 + TH_IDX_NUM_CODES]; + buffer[length * 2 + TH_IDX_OFFSET] = code - pos; + code += num_codes; +#if DUMP + printf("Last at length %d: ", length); + for(int i=length;i>=0;i--) { + putchar((code & (1u << i)) ? '1' : '0'); + } + printf("\n"); +#endif + buffer[length * 2 + TH_IDX_CEILING] = code; + pos += num_codes; + code <<= 1; + } + buffer += max_code_length * 2 + (count + 1) / 2; + } + return buffer; +} + +uint16_t *th_create_decoder_16(uint16_t *buffer, const uint8_t *symbols_and_lengths, uint count, uint max_code_length) { + if (!count) { + *buffer++ = 0; + } else if (count == 1) { + buffer[0] = 1; + buffer[1] = symbols_and_lengths[0] + (symbols_and_lengths[1] << 8); + buffer += 2; + } else { + buffer[0] = 1 + max_code_length * 2; + uint16_t *symbols = (uint16_t *)(buffer + buffer[0]); + buffer++; + memset(buffer, 0, max_code_length * 2 * sizeof(uint16_t)); + // pre-use ceiling slot for num_codes + #define TH_IDX_NUM_CODES TH_IDX_CEILING + for(int i=0;i 0 && length <= max_code_length); + length--; // length 1 at index 0 + buffer[length * 2 + TH_IDX_NUM_CODES]++; + } + // init offsets + for(int length = 1; length < max_code_length; length++) { + buffer[length * 2 + TH_IDX_OFFSET] = buffer[(length-1)*2 + TH_IDX_OFFSET] + buffer[(length-1)*2 + TH_IDX_NUM_CODES]; + } + // assign symbols + for(int i=0;i=0;i--) { + putchar((code & (1u << i)) ? '1' : '0'); + } + printf("\n"); +#endif + int num_codes = buffer[length * 2 + TH_IDX_NUM_CODES]; + buffer[length * 2 + TH_IDX_OFFSET] = code - pos; + code += num_codes; +#if DUMP + printf("Last at length %d: ", length); + for(int i=length;i>=0;i--) { + putchar((code & (1u << i)) ? '1' : '0'); + } + printf("\n"); +#endif + buffer[length * 2 + TH_IDX_CEILING] = code; + pos += num_codes; + code <<= 1; + } + buffer += max_code_length * 2 + count; + } + return buffer; +} + +uint16_t *th_read_simple_decoder(th_bit_input *bi, uint16_t *buffer, uint buffer_size, uint8_t *tmp_buf, uint tmp_buf_size) { + int non_empty = th_bit(bi); + if (!non_empty) { +#if DUMP + printf(" empty\n"); +#endif + // inlined from create_decoder + *buffer++ = 0; + return buffer; + } else { + int group8 = th_bit(bi); + uint8_t min = th_read_bits(bi, 8); + uint8_t max = th_read_bits(bi, 8); + if (min == max) { + // inlined from create_decoder + buffer[0] = 1; + *(uint8_t*)(buffer + 1) = min; + buffer += 2; + return buffer; + } +#if DUMP + printf(" Min/Max %d/%d\n", min, max); +#endif + uint min_cl = th_read_bits(bi, 4); + uint max_cl = th_read_bits(bi, 4); +#if DUMP + printf(" Code length %d->%d\n", min_cl, max_cl); +#endif + uint count = 0; + if (min_cl == max_cl) { + for(uint val=min;val<=max;val++) { + if (th_bit(bi)) { +#if DUMP + printf(" %d: %d bits\n", val, min_cl); +#endif + assert(count * 2 + 1 < tmp_buf_size); + tmp_buf[count*2] = val; + tmp_buf[count*2+1] = min_cl; + count++; + } + } + } else { + uint bit_count = 32 - __builtin_clz(max_cl - min_cl); + if (group8) { + for (int base_val = min; base_val <= max; base_val += 8) { + int all_same = th_bit(bi); + if (all_same) { + if (th_bit(bi)) { + for(uint i=0;i<=MIN(7, max - base_val); i++) { + uint code_length = min_cl + th_read_bits(bi, bit_count); + assert(count * 2 + 1 < tmp_buf_size); + tmp_buf[count*2] = base_val + i; + tmp_buf[count*2+1] = code_length; + count++; +#if DUMP + printf(" %d: %d bits\n", base_val + i, code_length); +#endif + } + } + } else { + for(int i=0;i<=MIN(7, max - base_val); i++) { + if (th_bit(bi)) { + uint code_length = min_cl + th_read_bits(bi, bit_count); + assert(count * 2 + 1 < tmp_buf_size); + tmp_buf[count*2] = base_val + i; + tmp_buf[count*2+1] = code_length; + count++; +#if DUMP + printf(" %d: %d bits\n", base_val + i, code_length); +#endif + } + } + } + } + } else { + for (int val = min; val <= max; val++) { + if (th_bit(bi)) { + uint code_length = min_cl + th_read_bits(bi, bit_count); + assert(count * 2 + 1 < tmp_buf_size); + tmp_buf[count*2] = val; + tmp_buf[count*2+1] = code_length; + count++; +#if DUMP + printf(" %d: %d bits\n", val, code_length); +#endif + } + } + } + } + assert(buffer_size >= th_decoder_size(count, max_cl)); + return th_create_decoder(buffer, tmp_buf, count, max_cl); + } +} + +const uint8_t reverse8[256] = { + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, +}; + +#pragma GCC push_options +#if PICO_ON_DEVICE +#pragma GCC optimize("O3") +#endif + +int __not_in_flash_func(th_make_prefix_length_table)(th_decoder decoder, uint8_t *prefix_lengths) { + int max_length = decoder[0] / 2; + if (max_length > 8) max_length = 8; + decoder++; + int code = 0; + memset(prefix_lengths, 0, 256); + for(int length=1; length<=max_length;length++) { + for(;code < decoder[TH_IDX_CEILING];code++) { + for(int i=0;i < (1u << (8-length)); i++) { + prefix_lengths[reverse8[i | (code << (8-length))]] = length; + } + } + code <<= 1; + decoder += 2; + } + return max_length; +} +#pragma GCC pop_options \ No newline at end of file diff --git a/src/tiny_huff.h b/src/tiny_huff.h new file mode 100644 index 00000000..e6dc19c9 --- /dev/null +++ b/src/tiny_huff.h @@ -0,0 +1,505 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +typedef unsigned int uint; + +#pragma once + +#define TH_IDX_CEILING 0 +#define TH_IDX_OFFSET 1 + +typedef const uint16_t *th_decoder; + +// tiny_huff_decoder +// 0000: symbol_offset (0 means no symbols) +// 0001: ceiling +// 0002: offset +// symbol_offset: symbol bytes + +// slightly faster it seems +#if !IS_WHD_GEN +#define TH_USE_ACCUM 1 +#endif + +typedef struct { + const uint8_t *cur; +#ifndef NDEBUG + const uint8_t *end; +#endif +#if TH_USE_ACCUM + uint32_t accum; + uint8_t bits; +#else + uint8_t bit; +#endif +} th_bit_input; + +typedef struct { + const uint8_t *cur; + uint32_t accum; + uint8_t bits; +} th_backwards_bit_input; + + +static inline void th_bit_input_init(th_bit_input *bi, const uint8_t *data) { + bi->cur = data; +#ifndef NDEBUG + bi->end = 0; +#endif +#if TH_USE_ACCUM + bi->accum = 0; + bi->bits = 0; +#else + bi->bit = 0; +#endif +} + +static inline void th_sized_bit_input_init(th_bit_input *bi, const uint8_t *data, uint size) { + bi->cur = data; +#ifndef NDEBUG + bi->end = data + size; +#endif +#if TH_USE_ACCUM + bi->accum = 0; + bi->bits = 0; +#else + bi->bit = 0; +#endif +} + +static inline void th_bit_input_init_bit_offset(th_bit_input *bi, const uint8_t *data, uint bit_offset) { + bi->cur = data + bit_offset / 8; +#ifndef NDEBUG + bi->end = 0; +#endif +#if TH_USE_ACCUM + bi->bits = 8 - (bit_offset & 7); + bi->accum = *bi->cur++ >> (bit_offset & 7); +#else + bi->bit = 0; +#endif +} + +static inline void th_sized_bit_input_init_bit_offset(th_bit_input *bi, const uint8_t *data, uint size, uint bit_offset) { + bi->cur = data + bit_offset / 8; +#ifndef NDEBUG + bi->end = data + size - bit_offset / 8; +#endif +#if TH_USE_ACCUM + bi->bits = 8 - (bit_offset & 7); + bi->accum = *bi->cur++ >> (bit_offset & 7); +#else + bi->bit = 0; +#endif +} + +static inline void th_backwards_bit_input_init(th_backwards_bit_input *bi, const uint8_t *data) { + bi->cur = data; + bi->bits = 0; + bi->accum = 0; +} + +static inline void th_backwards_bit_input_init_bit_offset(th_backwards_bit_input *bi, const uint8_t *data, uint bit_offset) { + bi->cur = data + bit_offset / 8; + bi->bits = bit_offset & 7u; + bi->accum = *bi->cur & ((1u << bi->bits)-1); // if we start on a non byte boundary, then the bits are in the wrong place +} + +void th_bit_overrun(th_bit_input *bi); // global and user provided + +static inline int th_bit(th_bit_input *bi) { +#if TH_USE_ACCUM + if (!bi->bits) { +#ifndef NDEBUG + if (bi->cur == bi->end) th_bit_overrun(bi); +#endif + bi->accum = *bi->cur++; + bi->bits = 8; + } + bi->bits--; + uint rc = bi->accum & 1u; + bi->accum >>= 1; + return rc; +#else +#ifndef NDEBUG + if (bi->cur == bi->end) th_bit_overrun(bi); +#endif + uint rc = *bi->cur & (1u << bi->bit++); + bi->cur += bi->bit >> 3u; + bi->bit &= 7u; + return rc != 0; +#endif +} +//static inline uint th_bit(th_bit_input *bi) { +//} + +#if TH_USE_ACCUM +static inline void th_fill_byte(th_bit_input *bi) { + assert(bi->bits < 24); +// if (bi->bits < 8) { +#ifndef NDEBUG + if (bi->cur == bi->end) th_bit_overrun(bi); +#endif + bi->accum |= *bi->cur++ << bi->bits; + bi->bits += 8; +// } +} +#endif + +static inline uint th_read_bits(th_bit_input *bi, int n) { +#if TH_USE_ACCUM + assert(n<=32); + while (bi->bits < n) { + th_fill_byte(bi); + } + bi->bits -= (int8_t)n; + uint tmp = bi->accum; + bi->accum >>= n; + return tmp ^ (bi->accum << n); +#else + assert(n<=32); +#ifndef NDEBUG + if (bi->cur == bi->end) th_bit_overrun(bi); +#endif + uint accum = *bi->cur >> bi->bit; + uint pos = 8 - bi->bit; + while ((int)pos < n) { + bi->cur++; +#ifndef NDEBUG + if (bi->cur == bi->end) th_bit_overrun(bi); +#endif + accum |= *bi->cur << pos; + pos += 8; + } + bi->bit = (bi->bit + n) & 7u; + if (!bi->bit) { + bi->cur++; + } + return accum & ((1u << n) - 1u); +#endif +} + +static inline uint th_read32(th_bit_input *bi) { + return th_read_bits(bi, 16) | (th_read_bits(bi, 16) << 16); +} + +static inline uint th_read_backwards_bits(th_backwards_bit_input *bi, uint n) { + assert(n<=32); + bi->bits -= n; + while (bi->bits > 32) { // really negative + bi->accum = (bi->accum << 8) | *--bi->cur; + bi->bits += 8; + } + uint tmp = bi->accum >> bi->bits; + bi->accum ^= tmp << bi->bits; + return tmp; +} + +static inline uint8_t th_decode(th_decoder decoder, th_bit_input *bi) { + assert(decoder[0]); + const uint8_t *symbols = (uint8_t *)(decoder + decoder[0]); + if (decoder[0] == 1) { + return symbols[0]; + } +// printf("DECODE AT %d %p.%d ", xarn ++, bi->cur, bi->bit); + uint code = 0; +#ifndef NDEBUG + uint max_code_length = (decoder[0] - 1)/2; + uint length = 0; +#endif + decoder++; + do { + code = (code << 1u) | th_bit(bi); + if (code < decoder[TH_IDX_CEILING]) { +// printf(" code %04x len %d symbol %02x\n", code, length + 1, symbols[code - decoder[TH_IDX_OFFSET]]); + return symbols[code - decoder[TH_IDX_OFFSET]]; + } + decoder += 2; +#ifndef NDEBUG + length++; + assert(length < max_code_length); +#endif + } while (1); +} + +extern const uint8_t reverse8[256]; +int th_make_prefix_length_table(th_decoder decoder, uint8_t *prefix_lengths); +static inline uint8_t th_decode_table_special(th_decoder decoder, const uint8_t *prefix_lengths, th_bit_input *bi) { + assert(decoder[0] > 1); // we should be called for the empty decoder case +#if TH_USE_ACCUM + if (bi->bits < 8) th_fill_byte(bi); + uint code = bi->accum; +#else + uint code = *bi->cur >> bi->bit; +// printf("DECODE AT %d %p.%d ", xarn++, bi->cur, bi->bit); + if (bi->bit) { + code |= ((bi->cur[1] << 8) >> bi->bit); + } +#endif + code &= 0xff; + uint8_t length = prefix_lengths[code]; + const uint8_t *symbols = (uint8_t *)(decoder + decoder[0]); + if (length) { + assert(length <= 8); + code = reverse8[(uint8_t)(code << (8 - length))]; + assert(code < decoder[TH_IDX_CEILING + 2*length-1]); +#if TH_USE_ACCUM + bi->bits -= length; + assert(bi->bits >= 0); + bi->accum >>= length; +#else + bi->bit += length; + bi->cur += bi->bit >> 3; + bi->bit &= 7; +#endif +// printf(" code %04x len %d symbol %02x\n", code, length, symbols[code - decoder[TH_IDX_OFFSET + 2*length-1]]); + return symbols[code - decoder[TH_IDX_OFFSET + 2*length-1]]; + } +// printf("otherwise\n"); + // we have advanced exactly one byte +#if !TH_USE_ACCUM + bi->cur++; +#else + bi->accum >>= 8; + bi->bits -= 8; + assert(bi->bits >= 0); +#endif +#ifndef NDEBUG + uint max_code_length = (decoder[0] - 1)/2; + length = 8; +#endif + code = reverse8[code]; + decoder += 17; + do { + code = (code << 1u) | th_bit(bi); + if (code < decoder[TH_IDX_CEILING]) { + return symbols[code - decoder[TH_IDX_OFFSET]]; + } + decoder += 2; +#ifndef NDEBUG + length++; + assert(length < max_code_length); +#endif + } while (1); +} + +static inline uint16_t th_decode_table_special_16(th_decoder decoder, const uint8_t *prefix_lengths, th_bit_input *bi) { + assert(decoder[0] > 1); // we should be called for the empty decoder case +#if TH_USE_ACCUM + if (bi->bits < 8) th_fill_byte(bi); + uint code = bi->accum; +#else + uint code = *bi->cur >> bi->bit; +// printf("DECODE AT %d %p.%d ", xarn++, bi->cur, bi->bit); + if (bi->bit) { + code |= ((bi->cur[1] << 8) >> bi->bit); + } +#endif + code &= 0xff; + uint8_t length = prefix_lengths[code]; + const uint16_t *symbols = (uint16_t *)(decoder + decoder[0]); + if (length) { + assert(length <= 8); + code = reverse8[(uint8_t)(code << (8 - length))]; + assert(code < decoder[TH_IDX_CEILING + 2*length-1]); +#if TH_USE_ACCUM + bi->bits -= length; + assert(bi->bits >= 0); + bi->accum >>= length; +#else + bi->bit += length; + bi->cur += bi->bit >> 3; + bi->bit &= 7; +#endif +// printf(" code %04x len %d symbol %02x\n", code, length, symbols[code - decoder[TH_IDX_OFFSET + 2*length-1]]); + return symbols[code - decoder[TH_IDX_OFFSET + 2*length-1]]; + } +// printf("otherwise\n"); + // we have advanced exactly one byte +#if !TH_USE_ACCUM + bi->cur++; +#else + bi->accum >>= 8; + bi->bits -= 8; + assert(bi->bits >= 0); +#endif +#ifndef NDEBUG + uint max_code_length = (decoder[0] - 1)/2; + length = 8; +#endif + code = reverse8[code]; + decoder += 17; + do { + code = (code << 1u) | th_bit(bi); + if (code < decoder[TH_IDX_CEILING]) { + return symbols[code - decoder[TH_IDX_OFFSET]]; + } + decoder += 2; +#ifndef NDEBUG + length++; + assert(length < max_code_length); +#endif + } while (1); +} + +#if 0 +// todo asm-ify // note we need at most 15 bits +static uint8_t th_decode_fast_special(th_decoder decoder, th_bit_input *bi) { + assert(decoder[0] > 1); // we should be called for the empty decoder case + const uint8_t *symbols = (uint8_t *)(decoder + decoder[0]); + uint code = 0; + int count; + decoder++; + do { +#if TH_USE_ACCUM + if (bi->bits < 8) th_fill_byte(bi); + uint bits = bi->accum; + count = bi->bits; +#else + assert(bi->cur < bi->end); + assert(bi->bit <= 7); + uint8_t bits = *bi->cur >> bi->bit; + count = 7 - bi->bit; // implicit count = 8 - bi->bit; if (count--) { +#endif + code = (code << 1) | (bits & 1); if (code < decoder[TH_IDX_CEILING]) break; + bits >>=1; decoder += 2; + if (count--) { + code = (code << 1) | (bits & 1); if (code < decoder[TH_IDX_CEILING]) break; + bits >>=1; decoder += 2; + if (count--) { + code = (code << 1) | (bits & 1); if (code < decoder[TH_IDX_CEILING]) break; + bits >>=1; decoder += 2; + if (count--) { + code = (code << 1) | (bits & 1); if (code < decoder[TH_IDX_CEILING]) break; + bits >>=1; decoder += 2; + if (count--) { + code = (code << 1) | (bits & 1); if (code < decoder[TH_IDX_CEILING]) break; + bits >>=1; decoder += 2; + if (count--) { + code = (code << 1) | (bits & 1); if (code < decoder[TH_IDX_CEILING]) break; + bits >>=1; decoder += 2; + if (count--) { + code = (code << 1) | (bits & 1); if (code < decoder[TH_IDX_CEILING]) break; + bits >>=1; decoder += 2; + if (count--) { + code = (code << 1) | (bits & 1); if (code < decoder[TH_IDX_CEILING]) break; + decoder += 2; + } + } + } + } + } + } + } +#if TH_USE_ACCUM + bi->bits -= 8; + assert(bi->bits >= 0); + bi->accum >>= 8; +#else + bi->cur++; + bi->bit = 0; +#endif + } while (1); +#if TH_USE_ACCUM + assert(count >= 0 && count < bi->bits); + bi->accum >>= (bi->bits - count); + bi->bits = count; +#else + bi->bit = (8 - count) & 7u; + if (!bi->bit) bi->cur++; + assert(bi->cur < bi->end || (!bi->bit && bi->cur == bi->end)); +#endif + return symbols[code - decoder[TH_IDX_OFFSET]]; +} +#endif + +static inline uint16_t th_decode_16(th_decoder decoder, th_bit_input *bi) { + assert(decoder[0]); + const uint16_t *symbols = decoder + decoder[0]; + if (decoder[0] == 1) { + return symbols[0]; + } + uint code = 0; +#ifndef NDEBUG + uint max_code_length = (decoder[0] - 1)/2; + uint length = 0; +#endif + decoder++; + do { + code = (code << 1u) | th_bit(bi); + if (code < decoder[TH_IDX_CEILING]) { + return symbols[code - decoder[TH_IDX_OFFSET]]; + } + decoder += 2; +#ifndef NDEBUG + length++; + assert(length < max_code_length); +#endif + } while (1); +} + +uint th_decoder_size(uint count, uint max_code_length); +uint16_t *th_create_decoder(uint16_t *buffer, const uint8_t *symbols_and_lengths, uint count, uint max_code_length); +uint th_decoder_size_16(uint count, uint max_code_length); +uint16_t *th_create_decoder_16(uint16_t *buffer, const uint8_t *symbols_and_lengths, uint count, uint max_code_length); +uint16_t *th_read_simple_decoder(th_bit_input *bi, uint16_t *buffer, uint buffer_size, uint8_t *tmp_buf, uint tmp_buf_size); + +static inline int to_zig(int x) { + if (x > 0) { + return x * 2 - 1; + } else { + return -x * 2; + } +} + +static inline int from_zig(int x) { + if (x & 1) { + return (x + 1) / 2; + } else { + return -x / 2; + } +} + +typedef struct { + uint8_t *cur; + uint32_t accum; + uint8_t *end; + uint8_t bits; +} th_bit_output; + +static inline void th_bit_output_init(th_bit_output *bo, uint8_t *buffer, uint size) { + bo->cur = buffer; + bo->accum = 0; + bo->bits= 0; + bo->end = buffer + size; +} + +static inline void th_flush_bytes(th_bit_output *bo) { + if (bo->cur == bo->end) return; + while (bo->bits >= 8) { + assert(bo->cur < bo->end); + *bo->cur++ = bo->accum; + bo->accum >>= 8; + bo->bits -= 8; + } +} + +static inline void th_write_bits(th_bit_output *bo, uint bits, uint n) { + assert(bo->bits + n <= 32); + assert(bits < (1u << n)); + bo->accum |= bits << bo->bits; + bo->bits += n; + th_flush_bytes(bo); +} + +static inline void th_write32(th_bit_output *bo, uint bits) { + th_write_bits(bo, bits & 0xffffu, 16); + th_write_bits(bo, bits >> 16, 16); +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/v_diskicon.c b/src/v_diskicon.c index e809a7ca..97903ebc 100644 --- a/src/v_diskicon.c +++ b/src/v_diskicon.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -16,6 +17,7 @@ // Disk load indicator. // +#if !NO_USE_LOADING_DISK #include "doomtype.h" #include "deh_str.h" #include "i_swap.h" @@ -64,7 +66,7 @@ static void CopyRegion(pixel_t *dest, int dest_pitch, static void SaveDiskData(const char *disk_lump, int xoffs, int yoffs) { pixel_t *tmpscreen; - patch_t *disk; + should_be_const patch_t *disk; // Allocate a complete temporary screen where we'll draw the patch. tmpscreen = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*tmpscreen), @@ -156,3 +158,4 @@ void V_RestoreDiskBackground(void) } } +#endif \ No newline at end of file diff --git a/src/v_diskicon.h b/src/v_diskicon.h index 0dee04d1..55ffc66a 100644 --- a/src/v_diskicon.h +++ b/src/v_diskicon.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -24,9 +25,16 @@ #define LOADING_DISK_W 16 #define LOADING_DISK_H 16 +#if !NO_USE_LOADING_DISK extern void V_EnableLoadingDisk(const char *lump_name, int xoffs, int yoffs); extern void V_BeginRead(size_t nbytes); extern void V_DrawDiskIcon(void); extern void V_RestoreDiskBackground(void); +#else +static inline void V_EnableLoadingDisk(const char *lump_name, int xoffs, int yoffs) {} +static inline void V_BeginRead(size_t nbytes) {} +static inline void V_DrawDiskIcon(void) {} +static inline void V_RestoreDiskBackground(void) {} +#endif #endif diff --git a/src/v_patch.h b/src/v_patch.h index 767f9e3a..b3cc2103 100644 --- a/src/v_patch.h +++ b/src/v_patch.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -26,19 +27,61 @@ // and we compose textures from the TEXTURE1/2 lists // of patches. +#if !USE_WHD typedef PACKED_STRUCT ( { short width; // bounding box size short height; + // todo graham these aren't useful in texture patches + // also column offs not useful when opaque (well i guess compression ruins it anyway) short leftoffset; // pixels to the left of origin short topoffset; // pixels below the origin int columnofs[8]; // only [width] used // the [0] is &columnofs[width] }) patch_t; +#define patch_topoffset(p) SHORT((p)->topoffset) +#define patch_leftoffset(p) SHORT((p)->leftoffset) +#define patch_width(p) SHORT((p)->width) +#define patch_height(p) SHORT((p)->height) + +#define vpatch_width(p) patch_width(p) +#define vpatch_height(p) patch_height(p) +#define vpatch_topoffset(p) SHORT((p)->topoffset) +#define vpatch_leftoffset(p) SHORT((p)->leftoffset) +#define patch_columnofs(p, col) LONG((p)->columnofs[col]) +#else +#include "whddata.h" +typedef const uint8_t patch_t; +#define patch_topoffset(p) 0//crud//(p)->topoffset +#define patch_leftoffset(p) 0//crud//(p)->leftoffset +#define patch_width(p) ((p)[1] | ((((p)[2]&1) << 8))) +#define patch_decoder_size_needed(p) ((((p)[2]>>1)<<2)+3) +#define patch_is_wide(p) (((p)[2])&1) +#define patch_height(p) ((p)[3]) +#define patch_columnofs(p, col) 0//crud//(p)->columnofs[col] +#define patch_byte_addressed(p) (((p)[0] & 4)!=0) +#define patch_fully_opaque(p) (((p)[0] & 2)!=0) +#define patch_has_extra(p) (((p)[0] & 1)!=0) + +// vpatch style patches are stored differently +#define vpatch_width(p) ((p)[0] | (((p)[3]&0x2)<<7u)) +#define vpatch_height(p) ((p)[1]) +#define vpatch_colorcount(p) ((p)[2]) +#define vpatch_type(p) ((p)[3]>>2) +#define vpatch_topoffset(p) ((int8_t)(p)[4]) +#define vpatch_leftoffset(p) ((int8_t)(p)[5]) +#define vpatch_palette(p) ((p)+6) +#define vpatch_has_shared_palette(p) ((p)[3]&1) +#define vpatch_shared_palette(p) ((vpatch_palette(p) + vpatch_colorcount(p))[0]) +#define vpatch_data(p) (vpatch_palette(p) + vpatch_colorcount(p) + ((p)[3]&1)) +#define NUM_SHARED_PALETTES 3 +extern const uint8_t vpatch_for_shared_palette[NUM_SHARED_PALETTES]; +#endif // posts are runs of non masked source pixels typedef PACKED_STRUCT ( { + // todo graham this is iterated in lots of places, so painful when we change byte topdelta; // -1 is the last post in a column byte length; // length data bytes follows }) post_t; @@ -46,5 +89,41 @@ typedef PACKED_STRUCT ( // column_t is a list of 0 or more post_t, (byte)-1 terminated typedef post_t column_t; +#if USE_WHD +typedef uint8_t vpatch_handle_small_t; +typedef uint16_t vpatch_handle_large_t; // well 9 bits +typedef PACKED_STRUCT ( +{ + union { + // this is index 0 in a patchlist + struct { + uint16_t size; // includes header + uint16_t max; + } header; + // this is index 1-> in a patchlist + struct { + uint32_t x:9; + uint32_t repeat:6; + uint32_t patch_handle:9; + uint32_t y:8; + } entry; + }; +}) vpatchlist_t; +static_assert(sizeof(vpatchlist_t)==4, ""); +extern vpatchlist_t *vpatchlist; +#define VPATCH_HANDLE(x) x +typedef vpatch_handle_large_t vpatch_sequence_t; // consecutive numbers starting at the value +static inline vpatch_handle_large_t vpatch_n(vpatch_sequence_t seq, int n) { + return seq + n; +} +#else +typedef should_be_const patch_t *vpatch_handle_small_t; +typedef should_be_const patch_t *vpatch_handle_large_t; +typedef vpatch_handle_large_t *vpatch_sequence_t; +#define VPATCH_HANDLE(x) W_CacheLumpName(DEH_String(x), PU_CACHE) +static inline vpatch_handle_large_t vpatch_n(vpatch_sequence_t seq, int n) { + return seq[n]; +} +#endif #endif diff --git a/src/v_video.c b/src/v_video.c index a73d3be0..56da2986 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -2,6 +2,7 @@ // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -38,39 +39,65 @@ #include "w_wad.h" #include "z_zone.h" +#if USE_WHD + +#include "doom/r_data.h" + +static_assert(VPATCH_NAME_INVALID == 0, ""); +//static_assert(NUM_VPATCHES < 256, ""); +// ^ note we have relaxed this because we are close... to allowing for sequential patches (just added to the base) to extend beyond our vpatch_small_t size which is one byte +static_assert(VPATCH_STCFN033 < 256, ""); +#endif + #include "config.h" + #ifdef HAVE_LIBPNG #include #endif +#ifndef NDEBUG // TODO: There are separate RANGECHECK defines for different games, but this // is common code. Fix this. #define RANGECHECK +#endif // Blending table used for fuzzpatch, etc. // Only used in Heretic/Hexen +#if !DOOM_ONLY +should_be_const byte *tinttable = NULL; +#endif -byte *tinttable = NULL; - +#if USE_WHD +const uint8_t vpatch_for_shared_palette[NUM_SHARED_PALETTES] = {VPATCH_STBAR, VPATCH_STCFN033, VPATCH_WIBP1}; +static const uint8_t *shared_palette8[NUM_SHARED_PALETTES]; +#endif // villsa [STRIFE] Blending table used for Strife byte *xlatab = NULL; // The screen buffer that the v_video.c code draws to. static pixel_t *dest_screen = NULL; +uint8_t vpatch_clip_top, vpatch_clip_bottom = SCREENHEIGHT; -int dirtybox[4]; +#if USE_WHD +vpatchlist_t *vpatchlist; +#else +int dirtybox[4]; +#endif +#if !DOOM_ONLY // haleyjd 08/28/10: clipping callback function for patches. // This is needed for Chocolate Strife, which clips patches to the screen. static vpatchclipfunc_t patchclip_callback = NULL; +#endif // // V_MarkRect // -void V_MarkRect(int x, int y, int width, int height) -{ - // If we are temporarily using an alternate screen, do not +#if !USE_WHD +void V_MarkRect(int x, int y, int width, int height) +{ + // If we are temporarily using an alternate screen, do not // affect the update box. if (dest_screen == I_VideoBuffer) @@ -78,46 +105,44 @@ void V_MarkRect(int x, int y, int width, int height) M_AddToBox (dirtybox, x, y); M_AddToBox (dirtybox, x + width-1, y + height-1); } -} - +} +#endif // // V_CopyRect // void V_CopyRect(int srcx, int srcy, pixel_t *source, int width, int height, - int destx, int desty) -{ + int destx, int desty) { pixel_t *src; pixel_t *dest; - -#ifdef RANGECHECK + +#ifdef RANGECHECK if (srcx < 0 - || srcx + width > SCREENWIDTH - || srcy < 0 - || srcy + height > SCREENHEIGHT - || destx < 0 - || destx + width > SCREENWIDTH - || desty < 0 - || desty + height > SCREENHEIGHT) - { - I_Error ("Bad V_CopyRect"); + || srcx + width > SCREENWIDTH + || srcy < 0 + || srcy + height > SCREENHEIGHT + || destx < 0 + || destx + width > SCREENWIDTH + || desty < 0 + || desty + height > SCREENHEIGHT) { + I_Error("Bad V_CopyRect"); } -#endif +#endif - V_MarkRect(destx, desty, width, height); - - src = source + SCREENWIDTH * srcy + srcx; - dest = dest_screen + SCREENWIDTH * desty + destx; + V_MarkRect(destx, desty, width, height); - for ( ; height>0 ; height--) - { + src = source + SCREENWIDTH * srcy + srcx; + dest = dest_screen + SCREENWIDTH * desty + destx; + + for (; height > 0; height--) { memcpy(dest, src, width * sizeof(*dest)); - src += SCREENWIDTH; - dest += SCREENWIDTH; - } -} - + src += SCREENWIDTH; + dest += SCREENWIDTH; + } +} + +#if !DOOM_ONLY // // V_SetPatchClipCallback // @@ -132,14 +157,244 @@ void V_SetPatchClipCallback(vpatchclipfunc_t func) { patchclip_callback = func; } - +#endif // // V_DrawPatch // Masks a column based masked pic to the screen. // -void V_DrawPatch(int x, int y, patch_t *patch) -{ +#if USE_WHD + +void V_BeginPatchList(vpatchlist_t *patchlist) { + vpatchlist = patchlist; + vpatchlist->header.size = 1; +} + +void V_EndPatchList(void) { + vpatchlist = 0; +} + +#pragma GCC push_options +#if PICO_ON_DEVICE +#pragma GCC optimize("O3") +#endif + +void V_DrawPatchList(const vpatchlist_t *patchlist) { + if (!shared_palette8[0]) { + for (int i = 0; i < NUM_SHARED_PALETTES; i++) { + patch_t *patch = resolve_vpatch_handle(vpatch_for_shared_palette[i]); + assert(patch); + assert(vpatch_has_shared_palette(patch)); + assert(vpatch_colorcount(patch)); + shared_palette8[i] = vpatch_palette(patch); + } + } + for (int l = 1; l < patchlist[0].header.size; l++) { + uint8_t *orig = dest_screen + (patchlist[l].entry.y) * SCREENWIDTH + patchlist[l].entry.x; + const patch_t *patch = resolve_vpatch_handle(patchlist[l].entry.patch_handle); + const uint8_t *pal; + if (!vpatch_has_shared_palette(patch)) { + pal = vpatch_palette(patch); + } else { + assert(vpatch_shared_palette(patch) < NUM_SHARED_PALETTES); + pal = shared_palette8[vpatch_shared_palette(patch)]; + } + int repeat = patchlist[l].entry.repeat; + int w = vpatch_width(patch); + int h0 = vpatch_height(patch); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + int skip_top; +#pragma GCC diagnostic pop + int type = vpatch_type(patch); + if (patchlist[l].entry.y + h0 > vpatch_clip_bottom) { + // clipping bottom which is trivial + h0 = vpatch_clip_bottom - patchlist[l].entry.y; + if (h0 <= 0) continue; + } + if (patchlist[l].entry.y < vpatch_clip_top) { + skip_top = vpatch_clip_top - patchlist[l].entry.y; + if (skip_top >= h0) continue; + h0 -= skip_top; + type += vp4_runs_clipped - vp4_runs; + } + const uint8_t *data = vpatch_data(patch); + uint8_t *desttop = orig; + int h = h0; + switch (type) { + case vp4_runs_clipped: + for(;skip_top--; desttop += SCREENWIDTH) { + uint8_t gap; + int p = 0; + while (0xff != (gap = *data++)) { + p += gap; + int len = *data++; + for (int i = 1; i < len; i += 2) { + p += 2; + data++; + } + if (len & 1) { + p++; + data++; + } + assert(p <= w); + if (p == w) break; + } + } + // fall thru + case vp4_runs: + for (; h > 0; h--, desttop += SCREENWIDTH) { + uint8_t *p = desttop; + uint8_t *pend = desttop + w; + uint8_t gap; + while (0xff != (gap = *data++)) { + p += gap; + int len = *data++; + for (int i = 1; i < len; i += 2) { + uint v = *data++; + *p++ = pal[v & 0xf]; + *p++ = pal[v >> 4]; + } + if (len & 1) { + *p++ = pal[(*data++) & 0xf]; + } + assert(p <= pend); + if (p == pend) break; + } + } + break; + case vp4_alpha_clipped: + data += ((w + 1) / 2) * skip_top; + desttop += SCREENWIDTH * skip_top; + // fallthru + case vp4_alpha: + for (; h > 0; h--, desttop += SCREENWIDTH) { + uint8_t *p = desttop; + for (int i = 0; i < w / 2; i++) { + uint v = *data++; + if (v & 0xf) p[0] = pal[v & 0xf]; + if (v >> 4) p[1] = pal[v >> 4]; + p += 2; + } + if (w & 1) { + uint v = *data++; + if (v & 0xf) p[0] = pal[v & 0xf]; + } + } + break; + case vp4_solid: + for (; h > 0; h--, desttop += SCREENWIDTH) { + uint8_t *p = desttop; + for (int i = 0; i < w / 2; i++) { + uint v = *data++; + p[0] = pal[v & 0xf]; + p[1] = pal[v >> 4]; + p += 2; + } + if (w & 1) { + uint v = *data++; + p[0] = pal[v & 0xf]; + } + } + break; + case vp6_runs_clipped: + // todo implement this (perhaps needed for multi player?) + continue; + case vp6_runs: + for (; h > 0; h--, desttop += SCREENWIDTH) { + uint8_t *p = desttop; + uint8_t *pend = desttop + w; + uint8_t gap; + while (0xff != (gap = *data++)) { + p += gap; + int len = *data++; + for (int i = 3; i < len; i += 4) { + uint v = *data++; + v |= (*data++) << 8; + v |= (*data++) << 16; + *p++ = pal[v & 0x3f]; + *p++ = pal[(v >> 6) & 0x3f]; + *p++ = pal[(v >> 12) & 0x3f]; + *p++ = pal[(v >> 18) & 0x3f]; + } + len &= 3; + if (len--) { + uint v = *data++; + *p++ = pal[v & 0x3f]; + if (len--) { + v >>= 6; + v |= (*data++) << 2; + *p++ = pal[v & 0x3f]; + if (len--) { + v >>= 6; + v |= (*data++) << 4; + *p++ = pal[v & 0x3f]; + assert(!len); + } + } + } + assert(p <= pend); + if (p == pend) break; + } + } + break; + case vp8_runs: + for (; h > 0; h--, desttop += SCREENWIDTH) { + uint8_t *p = desttop; + uint8_t *pend = desttop + w; + uint8_t gap; + while (0xff != (gap = *data++)) { + p += gap; + int len = *data++; + for (int i = 0; i < len; i++) { + *p++ = pal[*data++]; + } + assert(p <= pend); + if (p == pend) break; + } + } + break; + case vp_border_clipped: + data += 3 * skip_top; + // fall thru + case vp_border: { + for (; h > 0; h--, desttop += SCREENWIDTH) { + desttop[0] = data[0]; + for (int i = 1; i < w - 1; i++) desttop[i] = data[1]; + desttop[w - 1] = data[2]; + data += 3; + } + break; + } + default: + // these two we ignore for now + assert(type == vp8_runs_clipped || type == vp4_solid_clipped); + continue; + } + if (repeat) { + // we need them to be solid... which they are, but if not you'll just get some visual funk + //assert(vpatch_type(patch) == vp4_solid); + h = h0; + if (patchlist[l].entry.patch_handle == VPATCH_M_THERMM) w--; // hackity hack + uint8_t *desttop = orig; + for(;h>0;h--) { + for (int i = 0; i < repeat * w; i++) { + desttop[w + i] = desttop[i]; + } + desttop += SCREENWIDTH; + } + } + } +} + +#pragma GCC pop_options +#endif + +void V_DrawPatch(int x, int y, vpatch_handle_large_t patch) { + V_DrawPatchN(x, y, patch, 0); +} + +void V_DrawPatchN(int x, int y, vpatch_handle_large_t patch_handle, int repeat) { int count; int col; column_t *column; @@ -148,52 +403,68 @@ void V_DrawPatch(int x, int y, patch_t *patch) byte *source; int w; - y -= SHORT(patch->topoffset); - x -= SHORT(patch->leftoffset); +#if !USE_WHD + const patch_t *patch = patch_handle; +#else + const patch_t *patch = resolve_vpatch_handle(patch_handle); +#endif + y -= vpatch_topoffset(patch); + x -= vpatch_leftoffset(patch); +#if !DOOM_ONLY // haleyjd 08/28/10: Strife needs silent error checking here. if(patchclip_callback) { if(!patchclip_callback(patch, x, y)) return; } +#endif #ifdef RANGECHECK if (x < 0 - || x + SHORT(patch->width) > SCREENWIDTH - || y < 0 - || y + SHORT(patch->height) > SCREENHEIGHT) - { + || x + vpatch_width(patch) > SCREENWIDTH + || y < 0 + || y + vpatch_height(patch) > SCREENHEIGHT) { I_Error("Bad V_DrawPatch"); } #endif - V_MarkRect(x, y, SHORT(patch->width), SHORT(patch->height)); +#if !PICO_DOOM + V_MarkRect(x, y, vpatch_width(patch), vpatch_height(patch)); +#endif - col = 0; - desttop = dest_screen + y * SCREENWIDTH + x; +#if !USE_WHD + desttop = dest_screen + y * SCREENWIDTH + x; + w = vpatch_width(patch); + do { + col = 0; + for (; col < w; x++, col++, desttop++) { + column = (column_t *) ((byte *) patch + patch_columnofs(patch, col)); - w = SHORT(patch->width); + // step through the posts in a column + while (column->topdelta != 0xff) { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + count = column->length; - for ( ; colcolumnofs[col])); - - // step through the posts in a column - while (column->topdelta != 0xff) - { - source = (byte *)column + 3; - dest = desttop + column->topdelta*SCREENWIDTH; - count = column->length; - - while (count--) - { - *dest = *source++; - dest += SCREENWIDTH; + while (count--) { + *dest = *source++; + dest += SCREENWIDTH; + } + column = (column_t *) ((byte *) column + column->length + 4); + } } - column = (column_t *)((byte *)column + column->length + 4); - } + } while (repeat--); +#else + assert(vpatchlist); + if (vpatchlist[0].header.size <= vpatchlist[0].header.max) { + vpatchlist[vpatchlist[0].header.size].entry.patch_handle = patch_handle; + vpatchlist[vpatchlist[0].header.size].entry.x = x; + vpatchlist[vpatchlist[0].header.size].entry.y = y; + vpatchlist[vpatchlist[0].header.size].entry.repeat = repeat; + vpatchlist[0].header.size++; } +#endif } // @@ -202,60 +473,65 @@ void V_DrawPatch(int x, int y, patch_t *patch) // Flips horizontally, e.g. to mirror face. // -void V_DrawPatchFlipped(int x, int y, patch_t *patch) -{ +void V_DrawPatchFlipped(int x, int y, vpatch_handle_large_t patch_handle) { +#if !USE_WHD + const patch_t *patch = patch_handle; +#else + const patch_t *patch = resolve_vpatch_handle(patch_handle); +#endif +#if USE_WHD + panic_unsupported(); +#endif int count; - int col; - column_t *column; + int col; + column_t *column; pixel_t *desttop; pixel_t *dest; - byte *source; - int w; - - y -= SHORT(patch->topoffset); - x -= SHORT(patch->leftoffset); + byte *source; + int w; + y -= vpatch_topoffset(patch); + x -= vpatch_leftoffset(patch); + +#if !DOOM_ONLY // haleyjd 08/28/10: Strife needs silent error checking here. if(patchclip_callback) { if(!patchclip_callback(patch, x, y)) return; } +#endif -#ifdef RANGECHECK +#ifdef RANGECHECK if (x < 0 - || x + SHORT(patch->width) > SCREENWIDTH - || y < 0 - || y + SHORT(patch->height) > SCREENHEIGHT) - { + || x + vpatch_width(patch) > SCREENWIDTH + || y < 0 + || y + vpatch_height(patch) > SCREENHEIGHT) { I_Error("Bad V_DrawPatchFlipped"); } #endif - V_MarkRect (x, y, SHORT(patch->width), SHORT(patch->height)); + V_MarkRect (x, y, vpatch_width(patch), vpatch_height(patch)); col = 0; desttop = dest_screen + y * SCREENWIDTH + x; - w = SHORT(patch->width); + w = vpatch_width(patch); - for ( ; colcolumnofs[w-1-col])); + for (; col < w; x++, col++, desttop++) { + column = (column_t *) ((byte *) patch + patch_columnofs(patch, w - 1 - col)); // step through the posts in a column - while (column->topdelta != 0xff ) - { - source = (byte *)column + 3; - dest = desttop + column->topdelta*SCREENWIDTH; + while (column->topdelta != 0xff) { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; count = column->length; - while (count--) - { + while (count--) { *dest = *source++; dest += SCREENWIDTH; } - column = (column_t *)((byte *)column + column->length + 4); + column = (column_t *) ((byte *) column + column->length + 4); } } } @@ -267,17 +543,20 @@ void V_DrawPatchFlipped(int x, int y, patch_t *patch) // Draws directly to the screen on the pc. // -void V_DrawPatchDirect(int x, int y, patch_t *patch) -{ - V_DrawPatch(x, y, patch); -} +void V_DrawPatchDirect(int x, int y, vpatch_handle_large_t patch) { + V_DrawPatch(x, y, patch); +} + +void V_DrawPatchDirectN(int x, int y, vpatch_handle_large_t patch, int repeat) { + V_DrawPatchN(x, y, patch, repeat); +} // // V_DrawTLPatch // // Masks a column based translucent masked pic to the screen. // - +#if !DOOM_ONLY void V_DrawTLPatch(int x, int y, patch_t * patch) { int count, col; @@ -286,13 +565,13 @@ void V_DrawTLPatch(int x, int y, patch_t * patch) byte *source; int w; - y -= SHORT(patch->topoffset); - x -= SHORT(patch->leftoffset); + y -= patch_topoffset(patch); + x -= patch_leftoffset(patch); if (x < 0 - || x + SHORT(patch->width) > SCREENWIDTH + || x + patch_width(patch) > SCREENWIDTH || y < 0 - || y + SHORT(patch->height) > SCREENHEIGHT) + || y + patch_height(patch) > SCREENHEIGHT) { I_Error("Bad V_DrawTLPatch"); } @@ -300,7 +579,7 @@ void V_DrawTLPatch(int x, int y, patch_t * patch) col = 0; desttop = dest_screen + y * SCREENWIDTH + x; - w = SHORT(patch->width); + w = patch_width(patch); for (; col < w; x++, col++, desttop++) { column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); @@ -337,8 +616,8 @@ void V_DrawXlaPatch(int x, int y, patch_t * patch) byte *source; int w; - y -= SHORT(patch->topoffset); - x -= SHORT(patch->leftoffset); + y -= patch_topoffset(patch); + x -= patch_leftoffset(patch); if(patchclip_callback) { @@ -349,7 +628,7 @@ void V_DrawXlaPatch(int x, int y, patch_t * patch) col = 0; desttop = dest_screen + y * SCREENWIDTH + x; - w = SHORT(patch->width); + w = patch_width(patch); for(; col < w; x++, col++, desttop++) { column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); @@ -387,13 +666,13 @@ void V_DrawAltTLPatch(int x, int y, patch_t * patch) byte *source; int w; - y -= SHORT(patch->topoffset); - x -= SHORT(patch->leftoffset); + y -= patch_topoffset(patch); + x -= patch_leftoffset(patch); if (x < 0 - || x + SHORT(patch->width) > SCREENWIDTH + || x + patch_width(patch) > SCREENWIDTH || y < 0 - || y + SHORT(patch->height) > SCREENHEIGHT) + || y + patch_height(patch) > SCREENHEIGHT) { I_Error("Bad V_DrawAltTLPatch"); } @@ -401,7 +680,7 @@ void V_DrawAltTLPatch(int x, int y, patch_t * patch) col = 0; desttop = dest_screen + y * SCREENWIDTH + x; - w = SHORT(patch->width); + w = patch_width(patch); for (; col < w; x++, col++, desttop++) { column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); @@ -439,13 +718,13 @@ void V_DrawShadowedPatch(int x, int y, patch_t *patch) pixel_t *desttop2, *dest2; int w; - y -= SHORT(patch->topoffset); - x -= SHORT(patch->leftoffset); + y -= patch_topoffset(patch); + x -= patch_leftoffset(patch); if (x < 0 - || x + SHORT(patch->width) > SCREENWIDTH + || x + patch_width(patch) > SCREENWIDTH || y < 0 - || y + SHORT(patch->height) > SCREENHEIGHT) + || y + patch_height(patch) > SCREENHEIGHT) { I_Error("Bad V_DrawShadowedPatch"); } @@ -454,7 +733,7 @@ void V_DrawShadowedPatch(int x, int y, patch_t *patch) desttop = dest_screen + y * SCREENWIDTH + x; desttop2 = dest_screen + (y + 2) * SCREENWIDTH + x + 2; - w = SHORT(patch->width); + w = patch_width(patch); for (; col < w; x++, col++, desttop++, desttop2++) { column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); @@ -500,51 +779,46 @@ void V_LoadXlaTable(void) { xlatab = W_CacheLumpName("XLATAB", PU_STATIC); } +#endif // // V_DrawBlock // Draw a linear block of pixels into the view buffer. // -void V_DrawBlock(int x, int y, int width, int height, pixel_t *src) -{ +void V_DrawBlock(int x, int y, int width, int height, pixel_t *src) { pixel_t *dest; - -#ifdef RANGECHECK + +#ifdef RANGECHECK if (x < 0 - || x + width >SCREENWIDTH - || y < 0 - || y + height > SCREENHEIGHT) - { - I_Error ("Bad V_DrawBlock"); + || x + width > SCREENWIDTH + || y < 0 + || y + height > SCREENHEIGHT) { + I_Error("Bad V_DrawBlock"); } -#endif - - V_MarkRect (x, y, width, height); - - dest = dest_screen + y * SCREENWIDTH + x; +#endif - while (height--) - { - memcpy (dest, src, width * sizeof(*dest)); - src += width; - dest += SCREENWIDTH; - } -} + V_MarkRect (x, y, width, height); -void V_DrawFilledBox(int x, int y, int w, int h, int c) -{ + dest = dest_screen + y * SCREENWIDTH + x; + + while (height--) { + memcpy(dest, src, width * sizeof(*dest)); + src += width; + dest += SCREENWIDTH; + } +} + +void V_DrawFilledBox(int x, int y, int w, int h, int c) { pixel_t *buf, *buf1; int x1, y1; buf = I_VideoBuffer + SCREENWIDTH * y + x; - for (y1 = 0; y1 < h; ++y1) - { + for (y1 = 0; y1 < h; ++y1) { buf1 = buf; - for (x1 = 0; x1 < w; ++x1) - { + for (x1 = 0; x1 < w; ++x1) { *buf1++ = c; } @@ -552,56 +826,49 @@ void V_DrawFilledBox(int x, int y, int w, int h, int c) } } -void V_DrawHorizLine(int x, int y, int w, int c) -{ +void V_DrawHorizLine(int x, int y, int w, int c) { pixel_t *buf; int x1; buf = I_VideoBuffer + SCREENWIDTH * y + x; - for (x1 = 0; x1 < w; ++x1) - { + for (x1 = 0; x1 < w; ++x1) { *buf++ = c; } } -void V_DrawVertLine(int x, int y, int h, int c) -{ +void V_DrawVertLine(int x, int y, int h, int c) { pixel_t *buf; int y1; buf = I_VideoBuffer + SCREENWIDTH * y + x; - for (y1 = 0; y1 < h; ++y1) - { + for (y1 = 0; y1 < h; ++y1) { *buf = c; buf += SCREENWIDTH; } } -void V_DrawBox(int x, int y, int w, int h, int c) -{ +void V_DrawBox(int x, int y, int w, int h, int c) { V_DrawHorizLine(x, y, w, c); - V_DrawHorizLine(x, y+h-1, w, c); + V_DrawHorizLine(x, y + h - 1, w, c); V_DrawVertLine(x, y, h, c); - V_DrawVertLine(x+w-1, y, h, c); + V_DrawVertLine(x + w - 1, y, h, c); } // // Draw a "raw" screen (lump containing raw data to blit directly // to the screen) // - -void V_DrawRawScreen(pixel_t *raw) -{ + +void V_DrawRawScreen(pixel_t *raw) { memcpy(dest_screen, raw, SCREENWIDTH * SCREENHEIGHT * sizeof(*dest_screen)); } // // V_Init // -void V_Init (void) -{ +void V_Init(void) { // no-op! // There used to be separate screens that could be drawn to; these are // now handled in the upper layers. @@ -609,15 +876,13 @@ void V_Init (void) // Set the buffer that the code draws to. -void V_UseBuffer(pixel_t *buffer) -{ +void V_UseBuffer(pixel_t *buffer) { dest_screen = buffer; } // Restore screen buffer to the i_video screen buffer. -void V_RestoreBuffer(void) -{ +void V_RestoreBuffer(void) { dest_screen = I_VideoBuffer; } @@ -626,36 +891,37 @@ void V_RestoreBuffer(void) // typedef PACKED_STRUCT ( -{ - char manufacturer; - char version; - char encoding; - char bits_per_pixel; + { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; - unsigned short xmin; - unsigned short ymin; - unsigned short xmax; - unsigned short ymax; - - unsigned short hres; - unsigned short vres; + unsigned short xmin; + unsigned short ymin; + unsigned short xmax; + unsigned short ymax; - unsigned char palette[48]; - - char reserved; - char color_planes; - unsigned short bytes_per_line; - unsigned short palette_type; - - char filler[58]; - unsigned char data; // unbounded -}) pcx_t; + unsigned short hres; + unsigned short vres; + + unsigned char palette[48]; + + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + + char filler[58]; + unsigned char data; // unbounded + }) pcx_t; // // WritePCXfile // +#if !NO_FILE_ACCESS void WritePCXfile(char *filename, pixel_t *data, int width, int height, byte *palette) @@ -664,8 +930,8 @@ void WritePCXfile(char *filename, pixel_t *data, int length; pcx_t* pcx; byte* pack; - - pcx = Z_Malloc (width*height*2+1000, PU_STATIC, NULL); + + pcx = Z_Malloc (width*height*2+1000, PU_STATIC, 0); pcx->manufacturer = 0x0a; // PCX id pcx->version = 5; // 256 color @@ -686,22 +952,22 @@ void WritePCXfile(char *filename, pixel_t *data, // pack the image pack = &pcx->data; - + for (i=0 ; i + +#include "m_misc.h" +#include "w_file.h" +#include "z_zone.h" +#include "w_wad.h" +#if PICO_BUILD +#include "pico.h" +#include "pico.h" +#else +#define panic I_Error +#endif + +#if PICO_ON_DEVICE +#ifndef TINY_WAD_ADDR +#error TINY_WAD_ADDR must be specified +#endif +#define wad_map_base ((const uint8_t *)TINY_WAD_ADDR) +#endif + +#if !USE_WHD +#error no longer supported +#else +#if !PICO_ON_DEVICE +#include "tiny.whd.h" +#define wad_map_base tiny_whd +#endif +const uint8_t *whd_map_base = wad_map_base; +#endif + +extern const wad_file_class_t memory_wad_file; + +static const wad_file_t fileo = { + .file_class = &memory_wad_file, + .length = 0, // seemingly unused + .mapped = wad_map_base, + .path = "", +}; + +static wad_file_t *W_Memory_OpenFile(const char *path) +{ +#if !USE_WHD + if (fileo.mapped[0] != 'I' || fileo.mapped[1] != 'W' || fileo.mapped[2] != 'A' || fileo.mapped[3] != 'D') + panic("NO WAD"); +#else +#if WHD_SUPER_TINY + if (fileo.mapped[0] != 'I' || fileo.mapped[1] != 'W' || fileo.mapped[2] != 'H' || fileo.mapped[3] != 'X') { +#if PICO_ON_DEVICE + panic("No WXD at %p\n", TINY_WAD_ADDR); +#else + panic("Expected WXD format"); +#endif + } +#else + if (fileo.mapped[0] != 'I' || fileo.mapped[1] != 'W' || fileo.mapped[2] != 'H' || fileo.mapped[3] != 'D') { +#if PICO_ON_DEVICE + panic("No WHD at %p\n", TINY_WAD_ADDR); +#else + panic("Expected WHD format"); +#endif + } +#endif +#endif + return &fileo; +} + +static void W_Memory_CloseFile(wad_file_t *wad) +{ +} + +// Read data from the specified position in the file into the +// provided buffer. Returns the number of bytes read. + +size_t W_Memory_Read(wad_file_t *wad, unsigned int offset, + void *buffer, size_t buffer_len) +{ + memcpy(buffer, wad->mapped + offset, buffer_len); + return buffer_len; +} + + +const wad_file_class_t memory_wad_file = +{ + W_Memory_OpenFile, + W_Memory_CloseFile, + W_Memory_Read, +}; +#endif \ No newline at end of file diff --git a/src/w_file_posix.c b/src/w_file_posix.c index 9f74b5df..7aaa99ce 100644 --- a/src/w_file_posix.c +++ b/src/w_file_posix.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -18,6 +19,8 @@ #include "config.h" +// todo split this out into a separate file w_fiel type +#if !PICO_ON_DEVICE #ifdef HAVE_MMAP #include @@ -49,7 +52,11 @@ static void MapFile(posix_wad_file_t *wad, const char *filename) // change the WAD files after being read. However, there may // be code lurking in the source that does. +#if USE_READONLY_MMAP + protection = PROT_READ; +#else protection = PROT_READ|PROT_WRITE; +#endif // Writes to the mapped area result in private changes that are // *not* written to disk. @@ -64,7 +71,7 @@ static void MapFile(posix_wad_file_t *wad, const char *filename) if (result == NULL) { - fprintf(stderr, "W_POSIX_OpenFile: Unable to mmap() %s - %s\n", + stderr_print( "W_POSIX_OpenFile: Unable to mmap() %s - %s\n", filename, strerror(errno)); } } @@ -168,3 +175,46 @@ wad_file_class_t posix_wad_file = #endif /* #ifdef HAVE_MMAP */ +#else + +#include "pico.h" +#include +#include +#include +#include + +#include "m_misc.h" +#include "w_file.h" +#include "z_zone.h" + +typedef struct +{ + wad_file_t wad; + int handle; +} posix_wad_file_t; + +extern wad_file_class_t posix_wad_file; + +static wad_file_t *W_POSIX_OpenFile(const char *path) { + return NULL; + +} + +static void W_POSIX_CloseFile(wad_file_t *wad) { + +} + +size_t W_POSIX_Read(wad_file_t *wad, unsigned int offset, + void *buffer, size_t buffer_len) { + panic_unsupported(); +} + +wad_file_class_t posix_wad_file = + { + W_POSIX_OpenFile, + W_POSIX_CloseFile, + W_POSIX_Read, + }; + + +#endif \ No newline at end of file diff --git a/src/w_main.c b/src/w_main.c index ab32d9fe..e2958945 100644 --- a/src/w_main.c +++ b/src/w_main.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -35,7 +36,8 @@ boolean W_ParseCommandLine(void) boolean modifiedgame = false; int p; - // Merged PWADs are loaded first, because they are supposed to be +#if !USE_SINGLE_IWAD + // Merged PWADs are loaded first, because they are supposed to be // modified IWADs. //! @@ -67,7 +69,6 @@ boolean W_ParseCommandLine(void) // NWT-style merging: // NWT's -merge option: - //! // @arg // @category mod @@ -92,7 +93,7 @@ boolean W_ParseCommandLine(void) free(filename); } } - + // Add flats //! @@ -195,6 +196,7 @@ boolean W_ParseCommandLine(void) free(filename); } } +#endif // W_PrintDirectory(); @@ -204,6 +206,7 @@ boolean W_ParseCommandLine(void) // Load all WAD files from the given directory. void W_AutoLoadWADs(const char *path) { +#if !USE_SINGLE_IWAD glob_t *glob; const char *filename; @@ -221,6 +224,7 @@ void W_AutoLoadWADs(const char *path) } I_EndGlob(glob); +#endif } // Lump names that are unique to particular game types. This lets us check @@ -232,9 +236,11 @@ static const struct const char *lumpname; } unique_lumps[] = { { doom, "POSSA1" }, +#if !DOOM_ONLY { heretic, "IMPXA1" }, { hexen, "ETTNA1" }, { strife, "AGRDA1" }, +#endif }; void W_CheckCorrectIWAD(GameMission_t mission) diff --git a/src/w_merge.c b/src/w_merge.c index 3d71a9ae..dbd367aa 100644 --- a/src/w_merge.c +++ b/src/w_merge.c @@ -1,5 +1,6 @@ // // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -30,6 +31,8 @@ #include "w_wad.h" #include "z_zone.h" +#if !USE_SINGLE_IWAD + typedef enum { SECTION_NORMAL, @@ -719,4 +722,4 @@ void W_NWTDashMerge(const char *filename) W_CloseFile(wad_file); } - +#endif diff --git a/src/w_wad.c b/src/w_wad.c index ebf89677..1375f313 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -56,12 +57,24 @@ typedef PACKED_STRUCT ( // // Location of each lump on disk. +#if !USE_WHD lumpinfo_t **lumpinfo; +#else +const lumpinfo_t *lump_offsets; +typedef struct { + char name[10]; + uint16_t num; +} lump_name_info_t; +static lump_name_info_t *lump_names; +#endif unsigned int numlumps = 0; // Hash table for fast lookups +#if !USE_WHD static lumpindex_t *lumphash; +#endif +#if !NO_USE_RELOAD // Variables for the reload hack: filename of the PWAD to reload, and the // lumps from WADs before the reload file, so we can resent numlumps and // load the file again. @@ -69,6 +82,7 @@ static wad_file_t *reloadhandle = NULL; static lumpinfo_t *reloadlumps = NULL; static char *reloadname = NULL; static int reloadlump = -1; +#endif // Hash function used for lump names. unsigned int W_LumpNameHash(const char *s) @@ -87,6 +101,13 @@ unsigned int W_LumpNameHash(const char *s) return result; } +#if USE_WHD +#include +#include "whddata.h" +static_assert(sizeof(lumpindex_t) == 2, ""); // we rely on this in places +const whdheader_t *whdheader; +#endif + // // LUMP BASED ROUTINES. // @@ -112,6 +133,7 @@ wad_file_t *W_AddFile (const char *filename) lumpinfo_t *filelumps; int numfilelumps; +#if !NO_USE_RELOAD // If the filename begins with a ~, it indicates that we should use the // reload hack. if (filename[0] == '~') @@ -130,6 +152,7 @@ wad_file_t *W_AddFile (const char *filename) reloadlump = numlumps; ++filename; } +#endif // Open the file and add to directory wad_file = W_OpenFile(filename); @@ -140,6 +163,22 @@ wad_file_t *W_AddFile (const char *filename) return NULL; } +#if USE_MEMORY_WAD +#if !USE_WHD + extern lumpinfo_t **memory_wad_lumpinfo; + extern int memory_wad_numlumps; + extern lumpindex_t *memory_wad_lumphash; + lumpinfo = memory_wad_lumpinfo; + numlumps = memory_wad_numlumps; + lumphash = memory_wad_lumphash; +#else + numlumps = ((wadinfo_t *)whd_map_base)->numlumps; + lump_offsets = (const uint32_t *)(whd_map_base + ((wadinfo_t *)whd_map_base)->infotableofs); + whdheader = (const whdheader_t*)(whd_map_base + sizeof(wadinfo_t)); + lump_names = (lump_name_info_t *)(whd_map_base + sizeof(wadinfo_t) + sizeof(whdheader_t) + (numlumps + 1) * 4); +#endif +#else + if (strcasecmp(filename+strlen(filename)-3 , "wad" ) ) { // single lump file @@ -212,13 +251,21 @@ wad_file_t *W_AddFile (const char *filename) for (i = startlump; i < numlumps; ++i) { lumpinfo_t *lump_p = &filelumps[i - startlump]; +#if !USE_MEMMAP_ONLY lump_p->wad_file = wad_file; lump_p->position = LONG(filerover->filepos); - lump_p->size = LONG(filerover->size); lump_p->cache = NULL; strncpy(lump_p->name, filerover->name, 8); +#else + // todo having to copy name because not null terminated + char *n = calloc(1, 9); + strncpy(n, filerover->name, 8); + lump_p->name = n; + lump_p->mem = wad_file->mapped + LONG(filerover->filepos); +#endif + lump_p->size = LONG(filerover->size); lumpinfo[i] = lump_p; - +// printf("%d '%.8s'\n", i, lump_p->name); ++filerover; } @@ -230,6 +277,7 @@ wad_file_t *W_AddFile (const char *filename) lumphash = NULL; } +#if !NO_USE_RELOAD // If this is the reload file, we need to save some details about the // file so that we can close it later on when we do a reload. if (reloadname) @@ -237,7 +285,9 @@ wad_file_t *W_AddFile (const char *filename) reloadhandle = wad_file; reloadlumps = filelumps; } +#endif +#endif return wad_file; } @@ -260,10 +310,11 @@ int W_NumLumps (void) lumpindex_t W_CheckNumForName(const char *name) { - lumpindex_t i; // Do we have a hash table yet? +#if !USE_WHD + lumpindex_t i; if (lumphash != NULL) { int hash; @@ -272,9 +323,9 @@ lumpindex_t W_CheckNumForName(const char *name) hash = W_LumpNameHash(name) % numlumps; - for (i = lumphash[hash]; i != -1; i = lumpinfo[i]->next) + for (i = lumphash[hash]; i != -1; i = lump_info(i)->next) { - if (!strncasecmp(lumpinfo[i]->name, name, 8)) + if (!strncasecmp(lump_info(i)->name, name, 8)) { return i; } @@ -288,12 +339,27 @@ lumpindex_t W_CheckNumForName(const char *name) for (i = numlumps - 1; i >= 0; --i) { - if (!strncasecmp(lumpinfo[i]->name, name, 8)) + if (!strncasecmp(lump_info(i)->name, name, 8)) { return i; } } } +#else + int left = 0; + int right = whdheader->num_named_lumps; + do { + int center = (left + right) / 2; + int diff = strcasecmp(name, lump_names[center].name); + if (!diff) { + return (lumpindex_t )lump_names[center].num; + } else if (diff < 0) { + right = center; + } else { + left = center + 1; + } + } while (left != right); +#endif // TFB. Not found. @@ -321,6 +387,13 @@ lumpindex_t W_GetNumForName(const char *name) return i; } +static inline int lump_size(const lumpinfo_t *lump) { +#if !USE_WHD + return lump->size; +#else + return ((lump[1] - lump[0])&0xffffffu) - (lump[0]>>30); // just the address of the next minus this one (argh i guess we mauy have alignment issues) +#endif +} // // W_LumpLength @@ -333,7 +406,7 @@ int W_LumpLength(lumpindex_t lump) I_Error ("W_LumpLength: %i >= numlumps", lump); } - return lumpinfo[lump]->size; + return lump_size(lump_info(lump)); } @@ -345,25 +418,29 @@ int W_LumpLength(lumpindex_t lump) // void W_ReadLump(lumpindex_t lump, void *dest) { - int c; - lumpinfo_t *l; + should_be_const lumpinfo_t *l; if (lump >= numlumps) { I_Error ("W_ReadLump: %i >= numlumps", lump); } - l = lumpinfo[lump]; + l = lump_info(lump); - V_BeginRead(l->size); + V_BeginRead(lump_size(l)); - c = W_Read(l->wad_file, l->position, dest, l->size); + #if !USE_MEMMAP_ONLY + int c = W_Read(l->wad_file, l->position, dest, l->size); if (c < l->size) { I_Error("W_ReadLump: only read %i of %i on lump %i", c, l->size, lump); } + #else + printf("WARNING: W_ReadLump size %d\n", lump_size(l)); + memcpy(dest, lump_data(l), lump_size(l)); + #endif } @@ -381,28 +458,42 @@ void W_ReadLump(lumpindex_t lump, void *dest) // when no longer needed (do not use Z_ChangeTag). // -void *W_CacheLumpNum(lumpindex_t lumpnum, int tag) +#if !DOOM_TINY +should_be_const void *W_CacheLumpNum(lumpindex_t lumpnum, int tag) { - byte *result; - lumpinfo_t *lump; + should_be_const byte *result; + should_be_const lumpinfo_t *lump; if ((unsigned)lumpnum >= numlumps) { I_Error ("W_CacheLumpNum: %i >= numlumps", lumpnum); } - lump = lumpinfo[lumpnum]; + lump = lump_info(lumpnum); +#if PRINT_TOUCHED_LUMPS + if (!lump->touched) { + static int lifetime; + static FILE *f; + if (!f) { + f = fopen("lumps.txt", "w"); + } + lifetime += lump->size; + fprintf(f, "%d (+%04x = %08x %.8s)\n", lumpnum, lump->size, lifetime, lump->name); + fflush(f); + lump->touched = true; + } +#endif // Get the pointer to return. If the lump is in a memory-mapped // file, we can just return a pointer to within the memory-mapped // region. If the lump is in an ordinary file, we may already // have it cached; otherwise, load it into memory. +#if !USE_MEMMAP_ONLY if (lump->wad_file->mapped != NULL) { // Memory mapped file, return from the mmapped region. - - result = lump->wad_file->mapped + lump->position; + result = (should_be_const byte *)(lump->wad_file->mapped + lump->position); } else if (lump->cache != NULL) { @@ -419,16 +510,19 @@ void *W_CacheLumpNum(lumpindex_t lumpnum, int tag) W_ReadLump (lumpnum, lump->cache); result = lump->cache; } - +#else + result = lump_data(lump); +#endif return result; } +#endif // // W_CacheLumpName // -void *W_CacheLumpName(const char *name, int tag) +should_be_const void *W_CacheLumpName(const char *name, int tag) { return W_CacheLumpNum(W_GetNumForName(name), tag); } @@ -445,15 +539,15 @@ void *W_CacheLumpName(const char *name, int tag) void W_ReleaseLumpNum(lumpindex_t lumpnum) { - lumpinfo_t *lump; if ((unsigned)lumpnum >= numlumps) { I_Error ("W_ReleaseLumpNum: %i >= numlumps", lumpnum); } +#if !USE_MEMMAP_ONLY + lumpinfo_t *lump; lump = lumpinfo[lumpnum]; - if (lump->wad_file->mapped != NULL) { // Memory-mapped file, so nothing needs to be done here. @@ -462,6 +556,7 @@ void W_ReleaseLumpNum(lumpindex_t lumpnum) { Z_ChangeTag(lump->cache, PU_CACHE); } +#endif } void W_ReleaseLumpName(const char *name) @@ -539,6 +634,7 @@ void W_Profile (void) void W_GenerateHashTable(void) { +#if !USE_MEMORY_WAD lumpindex_t i; // Free the old hash table, if there is one: @@ -550,7 +646,7 @@ void W_GenerateHashTable(void) // Generate hash table if (numlumps > 0) { - lumphash = Z_Malloc(sizeof(lumpindex_t) * numlumps, PU_STATIC, NULL); + lumphash = Z_Malloc(sizeof(lumpindex_t) * numlumps, PU_STATIC, 0); for (i = 0; i < numlumps; ++i) { @@ -569,10 +665,11 @@ void W_GenerateHashTable(void) lumphash[hash] = i; } } - +#endif // All done! } +#if !NO_USE_RELOAD // The Doom reload hack. The idea here is that if you give a WAD file to -file // prefixed with the ~ hack, that WAD file will be reloaded each time a new // level is loaded. This lets you use a level editor in parallel and make @@ -617,13 +714,22 @@ void W_Reload(void) // fast lookup hashtable: W_GenerateHashTable(); } +#endif const char *W_WadNameForLump(const lumpinfo_t *lump) { +#if !USE_SINGLE_IWAD return M_BaseName(lump->wad_file->path); +#else + return "wadname"; +#endif } boolean W_IsIWADLump(const lumpinfo_t *lump) { +#if !USE_SINGLE_IWAD return lump->wad_file == lumpinfo[0]->wad_file; +#else + return true; +#endif } diff --git a/src/w_wad.h b/src/w_wad.h index ae5977a2..d9594c10 100644 --- a/src/w_wad.h +++ b/src/w_wad.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -34,23 +35,50 @@ // WADFILE I/O related stuff. // +#if !USE_WHD typedef struct lumpinfo_s lumpinfo_t; -typedef int lumpindex_t; +#else +typedef uint32_t lumpinfo_t; +#endif +#if !USE_WHD struct lumpinfo_s { +#if !USE_MEMMAP_ONLY char name[8]; wad_file_t *wad_file; int position; int size; void *cache; +#else + const char *name; + const void *mem; + int size; +#endif +#if PRINT_TOUCHED_LUMPS + int8_t touched; +#endif // Used for hash table lookups +#if DOOM_SMALL + short next; +#else lumpindex_t next; +#endif }; +#endif - +#if !USE_WHD extern lumpinfo_t **lumpinfo; +static inline should_be_const lumpinfo_t *lump_info(int lump) { + return lumpinfo[lump]; +} +#else +extern const lumpinfo_t *lump_offsets; +static inline const lumpinfo_t *lump_info(int lump) { + return &lump_offsets[lump]; +} +#endif extern unsigned int numlumps; wad_file_t *W_AddFile(const char *filename); @@ -62,8 +90,26 @@ lumpindex_t W_GetNumForName(const char *name); int W_LumpLength(lumpindex_t lump); void W_ReadLump(lumpindex_t lump, void *dest); -void *W_CacheLumpNum(lumpindex_t lump, int tag); -void *W_CacheLumpName(const char *name, int tag); +#if USE_MEMMAP_ONLY +extern const uint8_t *whd_map_base; +static inline should_be_const uint8_t *lump_data(const lumpinfo_t *lump) { +#if !USE_WHD + return (const uint8_t *)lump->mem; +#else + return whd_map_base + ((*lump)&0xffffffu); +#endif +} +#endif + +#if !DOOM_TINY +should_be_const void *W_CacheLumpNum(lumpindex_t lump, int tag); +#else +static inline should_be_const void *W_CacheLumpNum(lumpindex_t lumpnum, int tag) +{ + return lump_data(lump_info(lumpnum)); +} +#endif +should_be_const void *W_CacheLumpName(const char *name, int tag); void W_GenerateHashTable(void); diff --git a/src/whd_gen/CMakeLists.txt b/src/whd_gen/CMakeLists.txt new file mode 100644 index 00000000..d94781db --- /dev/null +++ b/src/whd_gen/CMakeLists.txt @@ -0,0 +1,22 @@ +if (NOT PICO_ON_DEVICE) + add_library(wad INTERFACE) + target_sources(wad INTERFACE + wad.cpp) + target_include_directories(wad INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + + add_executable(whd_gen + whd_gen.cpp + mus2seq.cpp + huff.cpp + lodepng.cpp + compress_mus.cpp + ../tiny_huff.c + ../musx_decoder.c + ../image_decoder.c + ) + + target_compile_definitions(whd_gen PRIVATE IS_WHD_GEN=1) + + target_include_directories(whd_gen PRIVATE .. ../doom) + target_link_libraries(whd_gen PRIVATE wad adpcm-lib) +endif() diff --git a/src/whd_gen/compress_mus.cpp b/src/whd_gen/compress_mus.cpp new file mode 100644 index 00000000..5ac0cece --- /dev/null +++ b/src/whd_gen/compress_mus.cpp @@ -0,0 +1,831 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#include +#include "compress_mus.h" +#include "mus2seq.h" +#include "config.h" +#include "huffman.h" +#include "tiny_huff.h" +#include +#include "musx_decoder.h" +#include "huff_sink.h" + +#if 1 +#define printf(x, ...) ((void)0) +#endif +statsomizer musx_decoder_space("MUSX Decoder Space"); + +std::vector decode_musx(std::vector &data); + +const char *seq_event_name(seq_event event) { + switch (event) { + case seq_event::change_controller: + return "change controller"; + case seq_event::delta_volume: + return "delta volume"; + case seq_event::delta_pitch: + return "delta pitch"; + case seq_event::delta_vibrato: + return "delta vibrato"; + case seq_event::press_key: + return "press key"; + case seq_event::release_key: + return "release key"; + case seq_event::system_event: + return "system event"; + case seq_event::score_end: + return "score end"; + default: + assert(false); + return "unknown"; + } +} + +std::ostream &operator<<(std::ostream &out, const seq_event &event) { + out << seq_event_name(event); + return out; +} + +template +std::ostream &operator<<(std::ostream &os, const std::pair &v) { + os << "(" << v.first << ", " << v.second << ")"; + return os; +} + +template +std::ostream &operator<<(std::ostream &os, const std::pair &v) { + os << "(" << v.first << ", " << (int) v.second << ")"; + return os; +} + +typedef std::pair channel_event; + +template> +struct mus_compressor_context { + mus_compressor_context() : + event_channel_sink("Event/Channel"), + delta_volume_sink("Delta Volume"), + delta_pitch_sink("Delta Pitch"), + delta_vibrato_sink("Delta Vibrato"), + press_note_sink("Press Note"), + press_note9_sink("Press Note 9"), + press_volume_sink("Press Volume"), + gap_sink("Gap") +#if MUS_GROUP_SIZE_CODE + , group_size_sink("Group Size") +#endif + { + for (int i = 0; i < MUSX_RELEASE_DIST_COUNT; i++) { + release_dist_sinks.emplace_back(std::string("Release Dist (") + std::to_string(i) + ")"); + } + for (int i = 0; i < MUSX_RELEASE_DIST_COUNT; i++) { + wrappers.wrappers.push_back(release_dist_sinks[i]); + } + } + + symbol_sink, BO> event_channel_sink; + symbol_sink, BO> delta_volume_sink; + symbol_sink, BO> delta_pitch_sink; // note that this seems to be nearly always multiples of 2 + symbol_sink, BO> delta_vibrato_sink; + symbol_sink, BO> press_note_sink; + symbol_sink, BO> press_note9_sink; + symbol_sink, BO> press_volume_sink; + symbol_sink, BO> gap_sink; + std::vector, BO>> release_dist_sinks; + bit_sink raw_bits; +#if MUS_GROUP_SIZE_CODE + symbol_sink, BO> group_size_sink; +#endif + sink_wrappers wrappers{event_channel_sink, delta_volume_sink, delta_pitch_sink, delta_vibrato_sink, + press_note_sink, press_note9_sink, press_volume_sink, gap_sink, raw_bits, +#if MUS_GROUP_SIZE_CODE + group_size_sink, +#endif + }; +}; + +template +void output_channel_events(BO &bit_output, huffman_encoding &huff) { + if (huff.empty()) { + assert(false); // should be handled at a higher level + return; + } + auto stats = huff.get_stats(); + + int min_cl = huff.get_min_code_length(); + int max_cl = huff.get_max_code_length(); + assert(max_cl < 16); + assert(min_cl <= max_cl); + bit_output->write(bit_sequence(min_cl, 4)); + bit_output->write(bit_sequence(max_cl, 4)); + int bit_count = 32 - __builtin_clz(max_cl - min_cl); + + for (uint ch = 0; ch < MUSX_CHANNEL_COUNT; ch++) { + uint8_t event_mask = 0; + for (const auto &e : stats.symbol_counts) { + if (e.first.first == ch) { + assert((uint) e.first.second < 8); + event_mask |= 1u << (uint) e.first.second; + } + } + bit_output->write(bit_sequence(event_mask != 0, 1)); + if (event_mask) { + for (uint bit = 0; bit < 8; bit++) { + if (event_mask & (1u << bit)) { + bit_output->write(bit_sequence(1, 1)); + auto ev = static_cast(bit); + auto length = huff.get_code_length(std::make_pair(ch, ev)); + assert(length); + if (min_cl == max_cl) { + assert(length == min_cl); + } else { + bit_output->write(bit_sequence(length - min_cl, bit_count)); + } + } else { + bit_output->write(bit_sequence(0, 1)); + } + } + } + } +} + +std::vector compress_seq(std::vector groups) { + mus_compressor_context<> ctx; + auto bitoutput = std::make_shared(); + for (int pass = 0; pass < 2; pass++) { + bool done = false; + uint pressed_note_count = 0; + for (const auto &g : groups) { +#if MUS_GROUP_SIZE_CODE + ctx.group_size_sink.output(g.items.size()); +#endif + for (size_t i = 0; i < g.items.size(); i++) { + const auto &e = g.items[i]; + printf("%d ", e.channel); + if (e.channel >= MUSX_CHANNEL_COUNT) { + fail("MUSX_NUM_CHANNELS(%d) exceeded", MUSX_CHANNEL_COUNT); + } + ctx.event_channel_sink.output(std::make_pair(e.channel, e.event)); + assert(!done); + switch (e.event) { + case seq_event::change_controller: + printf("change controller %d %d\n", e.p1, e.p2); + ctx.raw_bits.output(bit_sequence(e.p1, 4)); + ctx.raw_bits.output(bit_sequence::for_byte(e.p2)); + break; + case seq_event::delta_volume: + printf("delta volume %d\n", (int8_t) e.p1); + ctx.delta_volume_sink.output(to_zig((int8_t) e.p1)); + break; + case seq_event::delta_pitch: + printf("delta pitch %d\n", + (int8_t) e.p1); // for some songs this is always even; don't think we care that much + ctx.delta_pitch_sink.output(to_zig((int8_t) e.p1)); + break; + case seq_event::delta_vibrato: + printf("delta vibrato %d\n", (int8_t) e.p1); + ctx.delta_vibrato_sink.output(to_zig((int8_t) e.p1)); + break; + case seq_event::press_key: + printf("press key %d vol %d\n", e.p1, e.p2); + pressed_note_count++; + if (pressed_note_count > MUSX_NOTE_LIMIT) { + fail("too many simultaneous notes"); + } + if (e.channel == 9) { + ctx.press_note9_sink.output(e.p1); + } else { + ctx.press_note_sink.output(e.p1); + } + ctx.press_volume_sink.output((int8_t) e.p2); + break; + case seq_event::release_key: + pressed_note_count--; + if (pressed_note_count < 0) { + fail("too many released notes"); + } + printf("release key dist %d\n", e.p1); + if (e.p2 > 1) { + if (e.p2 >= MUSX_RELEASE_DIST_COUNT) { + fail("Release distance MUSX_RELEASE_DIST_COUNT(%d) exceeded, consider increasing", MUSX_RELEASE_DIST_COUNT); + } + ctx.release_dist_sinks[e.p2].output(e.p1); + } + break; + case seq_event::system_event: + printf("system event %d\n", e.p1); + ctx.raw_bits.output(bit_sequence(e.p1 - 10, 3)); + break; + case seq_event::score_end: + printf("score end\n"); + done = true; + break; + default: + assert(false); + } +#if MUS_PER_EVENT_GAP && !MUS_GROUP_SIZE_CODE + // will be better to have groups therefore + if (i != g.items.size()-1) { + ctx.gap_sink.output(0); + } +#endif + } +#if !MUS_PER_EVENT_GAP && !MUS_GROUP_SIZE_CODE + ctx.event_channel_sink.output(std::make_pair(1, seq_event::score_end)); // use this for gap right now +#endif + int gap = g.gap; + if (!gap) { + assert(done); + } else { + while (gap >= MUSX_GAP_MAX) { + ctx.gap_sink.output(MUSX_GAP_MAX); + gap -= MUSX_GAP_MAX; + } + // todo omit + ctx.gap_sink.output(gap); + printf("<- %d ->\n", g.gap); + } + } + if (!pass) { + ctx.wrappers.begin_output(bitoutput); + auto bitoutput2 = std::make_shared(); + printf("Bit size was %d (%d)\n", (int) bitoutput->bit_size(), (int) bitoutput->bit_size() / 8); + output_channel_events(bitoutput2, ctx.event_channel_sink.huff); + printf("Bit size now %d (%d)\n", (int) bitoutput2->bit_size(), (int) bitoutput2->bit_size() / 8); + output_min_max_best(bitoutput2, ctx.delta_volume_sink.huff); + output_min_max_best(bitoutput2, ctx.delta_pitch_sink.huff); + output_min_max_best(bitoutput2, ctx.delta_vibrato_sink.huff); + output_min_max_best(bitoutput2, ctx.press_note_sink.huff); + output_min_max_best(bitoutput2, ctx.press_note9_sink.huff); + output_min_max_best(bitoutput2, ctx.press_volume_sink.huff); +#if MUS_GROUP_SIZE_CODE + output_min_max_best(bitoutput2, ctx.group_size_sink.huff); +#endif + int last_ne = MUSX_RELEASE_DIST_COUNT - 1; + assert(ctx.release_dist_sinks[0].huff.empty()); + assert(ctx.release_dist_sinks[1].huff.empty()); + for (; last_ne >= 3 && ctx.release_dist_sinks[last_ne].huff.empty(); last_ne--); + bitoutput2->write(bit_sequence(last_ne, 4)); + for (int i = 2; i <= last_ne; i++) { + output_min_max_best(bitoutput2, ctx.release_dist_sinks[i].huff); + } + output_min_max_best(bitoutput2, ctx.gap_sink.huff); + printf("Bit size now %d (%d)\n", (int) bitoutput2->bit_size(), (int) bitoutput2->bit_size() / 8); + bitoutput2->write_to(bitoutput); + } + } + return bitoutput->get_output(); +} + +std::vector compress_mus(std::pair &e) { + std::vector seq_groups; + printf("MUS %s\n", e.second.name.c_str()); + printf("AAAAAAAA\n"); + if (mus2seq(e.second.data, seq_groups)) { + fail("Error converting MUS track %s\n", e.second.name.c_str()); + } + auto musx = compress_seq(seq_groups); + auto raw = decode_musx(musx); + std::vector mus; + mus.insert(mus.end(), e.second.data.begin(), e.second.data.begin() + 14); // copy header + // force offset of data + mus[6] = 14; + mus[7] = 0; + mus.insert(mus.end(), raw.begin(), raw.end()); + std::vector seq_groups2; + printf("BBBBBBBB\n"); + mus2seq(mus, seq_groups2); + return musx; +} + + + +#ifndef count_of +#define count_of(a) (sizeof(a)/(sizeof((a)[0]))) +#endif + +std::vector decode_musx(std::vector &data) { + std::vector mus; + typedef enum + { + mus_releasekey = 0x00, + mus_presskey = 0x10, + mus_pitchwheel = 0x20, + mus_systemevent = 0x30, + mus_changecontroller = 0x40, + mus_scoreend = 0x60 + } musevent; + + + byte_vector_bit_input vector_bi(data); + th_bit_input *bi = create_bip(vector_bi); + musx_decoder d; + uint16_t decoder_buffer[512]; + uint8_t tmp_buffer[512]; + musx_decoder_space.record(musx_decoder_init(&d, bi, decoder_buffer, count_of(decoder_buffer), tmp_buffer, count_of(tmp_buffer))); + bool done = false; + std::vector cmd; + printf("CCCCCCCC\n"); + do { + if (!d.group_remaining) { + d.group_remaining = th_decode(d.decoders + d.group_size_idx, bi); + } + uint8_t ec = th_decode(d.decoders + d.channel_event_idx, bi); + uint channel = ec >> 4; + printf("%d ", channel); + auto ev = static_cast(ec & 0xfu); + cmd.clear(); + switch (ev) { + case seq_event::change_controller: { + uint8_t p1 = th_read_bits(bi, 4); + uint8_t p2 = th_read_bits(bi, 8); + printf("change controller %d %d\n", p1, p2); + cmd.push_back(mus_changecontroller); + cmd.push_back(p1); + cmd.push_back(p2); + break; + } + case seq_event::delta_volume: { + auto delta = from_zig(th_decode(d.decoders + d.delta_volume_idx, bi)); + d.channel_last_volume[channel] += delta; + cmd.push_back(mus_changecontroller); + cmd.push_back(3); + cmd.push_back(d.channel_last_volume[channel]); + break; + } + case seq_event::delta_pitch: { + auto delta = from_zig(th_decode(d.decoders + d.delta_pitch_idx, bi)); + d.channel_last_wheel[channel] += delta; + printf("delta pitch %d, so %d\n", delta, d.channel_last_wheel[channel]); + cmd.push_back(mus_pitchwheel); + cmd.push_back(d.channel_last_wheel[channel]); + break; + } + case seq_event::delta_vibrato: { + auto delta = from_zig(th_decode(d.decoders + d.delta_vibrato_idx, bi)); + d.channel_last_vibrato[channel] += delta; + printf("delta vibrato %d, so %d\n", delta, d.channel_last_vibrato[channel]); + cmd.push_back(mus_changecontroller); + cmd.push_back(2); + cmd.push_back(d.channel_last_vibrato[channel]); + break; + } + case seq_event::press_key: { + auto note = th_decode(d.decoders + (channel == 9 ? d.press_note9_idx : d.press_note_idx), bi); + auto vol = th_decode(d.decoders + d.press_volume_idx, bi); + printf("press key %d vol %d", note, vol); + cmd.push_back(mus_presskey); + if (vol == vol_last_global) { + d.channel_last_volume[channel] = d.channel_last_press_volume[channel] = vol = d.last_volume; + cmd.push_back(note | 0x80); + cmd.push_back(vol); + } else if (vol == vol_last_channel) { + cmd.push_back(note); + vol = d.channel_last_press_volume[channel]; + } else { + d.last_volume = d.channel_last_volume[channel] = d.channel_last_press_volume[channel] = vol; + cmd.push_back(note | 0x80); + cmd.push_back(vol); + } + printf(" so %d\n", vol); + musx_record_note_on(&d, channel, note); + break; + } + case seq_event::release_key: { + assert(d.channel_note_count[channel]); + uint8_t dist = 0; + if (d.channel_note_count[channel] > 1) { + dist = th_decode(d.decoders + d.release_dist_idx[d.channel_note_count[channel]], bi); + } + uint8_t note = musx_record_note_off(&d, channel, dist); + printf("release key dist %d note %d\n", dist, note); + cmd.push_back(mus_releasekey); + cmd.push_back(note); + break; + } + case seq_event::system_event: { + auto controller = th_read_bits(bi, 3) + 10; + printf("system event %d\n", controller); + cmd.push_back(mus_systemevent); + cmd.push_back(controller); + break; + } + case seq_event::score_end: + printf("score end\n"); + cmd.push_back(mus_scoreend); + done = true; + break; + default: + assert(false); + } + int gap = 0; + if (!--d.group_remaining) { + if (!done) { + int lgap = 0; + do { + lgap = th_decode(d.decoders + d.gap_idx, bi); + gap += lgap; + } while (lgap == MUSX_GAP_MAX); + } + if (gap) { + printf("<- %d ->\n", gap); + } + } + cmd[0] |= channel == 9 ? 15 : channel; + if (gap) { + cmd[0] |= 0x80; + } + mus.insert(mus.end(), cmd.begin(), cmd.end()); + if (gap) { + uint marker = 0; + for(int bit=21; bit>=0; bit-=7) { + if (marker || gap <= (int)(0x7fu << bit)) { + marker = bit ? 0x80 : 0; + mus.push_back(marker | ((gap >> bit)&0x7fu)); + } + } + } + } while (!done); + uncreate_bip(vector_bi, bi); + return mus; +} +//struct delta_comparator { +// delta_comparator() = default; +// +// public: +// bool operator()(int8_t a, int8_t b) const { +// int aa = (a > 0) ? a * 2 - 1 : -a * 2; +// int bb = (b > 0) ? b * 2 - 1 : -b * 2; +// return aa < bb; +// } +// }; +// + +//byte_vector_bit_input bi(bitoutput->get_output()); +//std::vector channel_note_count(16); +// +//auto channel_event_decode = huffman_decoder(decode_channel_events(bi)); +//auto delta_volume_decode = huffman_decoder(decode_min_max8(bi)); +//// auto delta_pitch_decode = huffman_decoder(decode_min_max8(bi)); +//static uint8_t foo[512]; +//static uint16_t foo16[512]; +//auto bip = create_bip(bi); +//auto delta_pitch_decode = foo16; +//th_read_simple_decoder(bip, foo16, foo, sizeof(foo)); +//uncreate_bip(bi, bip); +//auto delta_vibrato_decode = huffman_decoder(decode_min_max8(bi)); +//auto press_note_decode = huffman_decoder(decode_min_max8(bi)); +//auto press_note9_decode = huffman_decoder(decode_min_max8(bi)); +//auto volume_decode = huffman_decoder(decode_min_max8(bi)); +//#if MUS_GROUP_SIZE_CODE +//auto group_size_decode = huffman_decoder(decode_min_max8(bi)); +//#endif +//int last_ne = bi.read(4); +//std::vector> release_dist_decode(last_ne + 1); +//for (int i = 2; i <= last_ne; i++) { +// release_dist_decode[i] = huffman_decoder(decode_min_max8(bi)); +//} +//auto gap_decode = huffman_decoder(decode_min_max8(bi)); +// +//bool done = false; +//do { +// int count; +//#if MUS_GROUP_SIZE_CODE +// count = group_size_decode.decode(bi); +//#else +// count = 1; +//#endif +// for (; count > 0; count--) { +// auto ec = channel_event_decode.decode(bi); +// uint channel = ec.first; +// printf("%d ", channel); +// switch (ec.second) { +// case seq_event::change_controller: { +// uint8_t p1 = bi.read(4); +// uint8_t p2 = bi.read(8); +// printf("change controller %d %d\n", p1, p2); +// break; +// } +// case seq_event::delta_volume: { +// auto delta = from_zig(delta_volume_decode.decode(bi)); +// printf("delta volume %d\n", delta); +// break; +// } +// case seq_event::delta_pitch: { +// // auto delta = from_zig(delta_pitch_decode.decode(bi)); +// auto bip = create_bip(bi); +// auto delta = from_zig(th_decode(delta_pitch_decode, bip)); +// uncreate_bip(bi, bip); +// printf("delta pitch %d\n", delta); +// break; +// } +// case seq_event::delta_vibrato: { +// auto delta = from_zig(delta_vibrato_decode.decode(bi)); +// printf("delta vibrato %d\n", delta); +// break; +// } +// case seq_event::press_key: { +// auto note = channel == 9 ? press_note9_decode.decode(bi) : press_note_decode.decode(bi); +// auto vol = ctx.press_volume_sink.decode(bi); +// channel_note_count[channel]++; +// printf("press key %d vol %d\n", note, vol); +// break; +// } +// case seq_event::release_key: { +// assert(channel_note_count[channel]); +// uint8_t dist = 0; +// if (channel_note_count[channel] > 1) { +// dist = release_dist_decode[channel_note_count[channel]].decode(bi); +// } +// channel_note_count[channel]--; +// printf("release key dist %d\n", dist); +// break; +// } +// case seq_event::system_event: { +// auto controller = bi.read(3) + 10; +// printf("system event %d\n", controller); +// break; +// } +// case seq_event::score_end: +//#if !MUS_PER_EVENT_GAP && !MUS_GROUP_SIZE_CODE +// if (channel == 1) { +// int gap = ctx.gap_sink.decode(bi); +// if (gap) { +// printf("<- %d ->\n", gap); +// } +// break; +// } +//#endif +//printf("score end\n"); +// done = true; +// break; +// default: +// assert(false); +// } +// } +//#if MUS_PER_EVENT_GAP || MUS_GROUP_SIZE_CODE +//int gap = 0; +// if (!done) { +// int lgap = 0; +// do { +// lgap = gap_decode.decode(bi); +// gap += lgap; +// } while (lgap == GAP_MAX); +// } +// if (gap) { +// printf("<- %d ->\n", gap); +// } +//#endif +//} while (!done); + +// +//template +//std::vector> decode_channel_events(BI &bi) { +// int min_cl = bi.read(4); +// int max_cl = bi.read(4); +// printf(" Code length %d->%d\n", min_cl, max_cl); +// std::vector> symbol_lengths; +// int bit_count = 32 - __builtin_clz(max_cl - min_cl); +// for (uint ch = 0; ch < NUM_CHANNELS; ch++) { +// if (bi.bit()) { +// for (uint bit = 0; bit < 8; bit++) { +// if (bi.bit()) { +// auto ev = static_cast(bit); +// int length; +// if (min_cl == max_cl) { +// length = min_cl; +// } else { +// length = min_cl + bi.read(bit_count); +// } +// symbol_lengths.template emplace_back(std::make_pair(ch, ev), length); +// } +// } +// } +// } +// return symbol_lengths; +//} + +// old stuff +//th_decoder create_decoder(const std::vector> &symbol_lengths) { +//#if 1 +// std::vector code_ceiling; +// std::vector offset; +// std::vector symbols; +// if (symbol_lengths.size() > 1) { +// // could have passed this, but fine for now +// int min_code_length = std::numeric_limits::max(); +// int max_code_length = std::numeric_limits::min(); +// for (const auto &e : symbol_lengths) { +// min_code_length = std::min(min_code_length, e.second); +// max_code_length = std::max(max_code_length, e.second); +// } +// code_ceiling.resize(max_code_length); +// offset.resize(max_code_length); +// std::vector num_codes(max_code_length); +// for (const auto &e : symbol_lengths) { +// num_codes[e.second - 1]++; +// } +// for (int i = 1; i < max_code_length; i++) { +// offset[i] = offset[i - 1] + num_codes[i - 1]; +// } +// symbols.resize(symbol_lengths.size()); +// for (const auto &e : symbol_lengths) { +// const auto length = e.second; +// symbols[offset[length - 1]++] = e.first; +// } +// int code = 0; +// int pos = 0; +// for (int length = 0; length < max_code_length; length++) { +// offset[length] = code - pos; +// code += num_codes[length]; +// code_ceiling[length] = code; +// pos += num_codes[length]; +// code <<= 1; +// } +// for (const auto &s : symbols) { +// std::cout << static_cast::ostream_type>(s) << "\n"; +// } +// } else if (symbol_lengths.size() == 1) { +// symbols.push_back(symbol_lengths[0].first); +// } +// auto decoder = new uint16_t[1 + offset.size() * 2 + (symbols.size() + 1) / 2]; +// if (symbols.empty()) { +// decoder[0] = 0; +// } else { +// decoder[0] = 1 + offset.size() * 2; +// for (uint i = 0; i < offset.size(); i++) { +// decoder[1 + i * 2] = code_ceiling[i]; +// decoder[2 + i * 2] = offset[i]; +// } +// memcpy(decoder + decoder[0], symbols.data(), symbols.size()); +// } +// +// uint16_t *buf; +// uint16_t *end; +// uint size; +// if (symbol_lengths.empty()) { +// size = th_decoder_size(symbol_lengths.size(), 0); +// buf = new uint16_t[size]; +// end = th_create_decoder(buf, nullptr, 0, 0); +// } else { +// int min_code_length = std::numeric_limits::max(); +// int max_code_length = std::numeric_limits::min(); +// for (const auto &e : symbol_lengths) { +// min_code_length = std::min(min_code_length, e.second); +// max_code_length = std::max(max_code_length, e.second); +// } +// size = th_decoder_size(symbol_lengths.size(), max_code_length); +// buf = new uint16_t[size]; +// uint8_t *sal = new uint8_t[symbol_lengths.size() * 2]; +// for (int i = 0; i < symbol_lengths.size(); i++) { +// sal[i * 2] = symbol_lengths[i].first; +// sal[i * 2 + 1] = symbol_lengths[i].second; +// } +// end = th_create_decoder(buf, sal, symbol_lengths.size(), max_code_length); +// } +// assert(end == buf + size); +// for (int i = 0; buf + i < end; i++) { +// if (decoder[i] != buf[i]) { +// printf("WAH %d %d %d\n", i, decoder[i], buf[i]); +// } +// } +// return buf; +//#endif +// +//} + +//template std::vector> decode_min_max8(BI &bi) { +// bool non_empty = bi.bit(); +// if (!non_empty) { +// printf(" No values\n"); +// return {}; +// } else { +// bool group8 = bi.bit(); +// uint8_t min = bi.read(8); +// uint8_t max = bi.read(8); +// if (min == max) { +// printf(" %d: 0 bits\n", min); +// return {std::make_pair(min, 0)}; +// } +// printf(" Min/Max %d/%d\n", min, max); +// int min_cl = bi.read(4); +// int max_cl = bi.read(4); +// printf(" Code length %d->%d\n", min_cl, max_cl); +// std::vector> symbol_lengths; +// if (min_cl == max_cl) { +// for (uint val = min; val <= max; val++) { +// if (bi.bit()) { +// printf(" %d: %d bits\n", val, min_cl); +// symbol_lengths.template emplace_back(val, min_cl); +// } +// } +// } else { +// int bit_count = 32 - __builtin_clz(max_cl - min_cl); +// if (group8) { +// for (int base_val = min; base_val <= max; base_val += 8) { +// bool all_same = bi.bit(); +// if (all_same) { +// if (bi.bit()) { +// for (uint i = 0; i <= std::min(7, max - base_val); i++) { +// int code_length = min_cl + bi.read(bit_count); +// symbol_lengths.template emplace_back(base_val + i, code_length); +// printf(" %d: %d bits\n", base_val + i, code_length); +// } +// } +// } else { +// for (int i = 0; i <= std::min(7, max - base_val); i++) { +// if (bi.bit()) { +// int code_length = min_cl + bi.read(bit_count); +// symbol_lengths.template emplace_back(base_val + i, code_length); +// printf(" %d: %d bits\n", base_val + i, code_length); +// } +// } +// } +// } +// } else { +// for (int val = min; val <= max; val++) { +// if (bi.bit()) { +// int code_length = min_cl + bi.read(bit_count); +// symbol_lengths.template emplace_back(val, code_length); +// printf(" %d: %d bits\n", val, code_length); +// } +// } +// } +// } +// return symbol_lengths; +// } +//} + + +// ctx.wrappers.dump(); +#if 0 +for(int event = 0; event< 8; event++) { + std::map chgroups; + std::map chgroups_same; + int event_count = 0; + std::vector channels; + for(const auto &g : groups) { + // look for channel groupings + for(int i=0;i(event)) continue; + channels.push_back(g.items[i].channel); + event_count++; + for(int j=i+1;j mask && g.items[i].event == g.items[j].event) { + mask |= bit; + } else { + break; + } + } + assert(mask); + uint first_bit = __builtin_ctz(mask); + if ((1u << first_bit) != mask) { + bool same = true; + const auto &e1 = g.items[first_bit]; + for(uint j = first_bit + 1; j < 16; j++) { + if (mask & (1u << j)) { + const auto &e2 = g.items[j]; + if (e1.p1 != e2.p1 || e1.p2 != e2.p2) { + same = false; + break; + } + } + } + if (same) { + chgroups_same[mask]++; + } + } + chgroups[mask]++; + } + } + printf("CHGROUPS %s\n", seq_event_name((seq_event)event)); + for(const auto& e: chgroups_same) { + assert(chgroups[e.first]!=0); + } + auto single_bit_lengths = huff(channels.data(), channels.size(), true); + for(const auto& e: chgroups) { + int separate_length = 0; + for(int i=0;i<16;i++) { + if (e.first & (1u << i)) { + separate_length += single_bit_lengths[i]; + } + } + int length = ceil(-log2( e.second / (double)event_count)); + if (length > separate_length && __builtin_popcount(e.first) > 1) continue; + for(int i=0x8000;i>0;i>>=1) { + putchar(e.first & i ? '1' : '0'); + } + putchar(__builtin_popcount(e.first) == 1 ? '*' : ' '); + printf(" % 6d % 6d %d vs %d", e.second, e.second * __builtin_popcount(e.first), length, separate_length); + if (chgroups_same[e.first]) + printf(" (%d / %d)\n", chgroups_same[e.first] * __builtin_popcount(e.first), chgroups_same[e.first]); + else + printf("\n"); + } +} +#endif diff --git a/src/whd_gen/compress_mus.h b/src/whd_gen/compress_mus.h new file mode 100644 index 00000000..13f2ed8b --- /dev/null +++ b/src/whd_gen/compress_mus.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#pragma once +#include "wad.h" +#include +#include "statsomizer.h" + +extern statsomizer musx_decoder_space; + +std::vector compress_mus(std::pair &e); + diff --git a/src/whd_gen/config.h b/src/whd_gen/config.h new file mode 100644 index 00000000..a124d710 --- /dev/null +++ b/src/whd_gen/config.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +// not sure we need anything, but doom headers expect it + +void __attribute__((noreturn)) fail(const char *msg, ...); + +//#define SAVE_PNG 1 +#define VERIFY_ENCODING 1 // extra work => warm fuzzy feeling +#define USE_MUSX 1 + +//#define MUS_PER_EVENT_GAP 1 +#define MUS_GROUP_SIZE_CODE 1 + +#define TEXTURE_PIXEL_STATS 1 +//#define PRETEND_COMPRESS 1 +// todo sounds a bit better with 5, but slower +#ifndef NDEBUG +#define LOOKAHEAD 3 +#else +#define LOOKAHEAD 5 +#endif + +#define CONSIDER_SEQUENCES 0 +#define SEQUENCE 10 + +//#define SAVE_PNG 1 + diff --git a/src/whd_gen/extra_patches.h b/src/whd_gen/extra_patches.h new file mode 100644 index 00000000..02320d30 --- /dev/null +++ b/src/whd_gen/extra_patches.h @@ -0,0 +1,436 @@ +#pragma once + +const uint8_t lump_m_dthmch[] = { + 0x87, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x02, 0x00, 0x00, 0x38, 0x02, 0x00, 0x00, 0x4c, 0x02, 0x00, 0x00, 0x60, 0x02, 0x00, 0x00, + 0x74, 0x02, 0x00, 0x00, 0x88, 0x02, 0x00, 0x00, 0x9c, 0x02, 0x00, 0x00, 0xaf, 0x02, 0x00, 0x00, 0xc2, 0x02, 0x00, 0x00, 0xd5, 0x02, 0x00, 0x00, + 0xea, 0x02, 0x00, 0x00, 0xfe, 0x02, 0x00, 0x00, 0x12, 0x03, 0x00, 0x00, 0x24, 0x03, 0x00, 0x00, 0x36, 0x03, 0x00, 0x00, 0x46, 0x03, 0x00, 0x00, + 0x52, 0x03, 0x00, 0x00, 0x5f, 0x03, 0x00, 0x00, 0x6e, 0x03, 0x00, 0x00, 0x7d, 0x03, 0x00, 0x00, 0x8e, 0x03, 0x00, 0x00, 0x9f, 0x03, 0x00, 0x00, + 0xb0, 0x03, 0x00, 0x00, 0xc1, 0x03, 0x00, 0x00, 0xd2, 0x03, 0x00, 0x00, 0xe3, 0x03, 0x00, 0x00, 0xf4, 0x03, 0x00, 0x00, 0x05, 0x04, 0x00, 0x00, + 0x16, 0x04, 0x00, 0x00, 0x27, 0x04, 0x00, 0x00, 0x31, 0x04, 0x00, 0x00, 0x3d, 0x04, 0x00, 0x00, 0x4b, 0x04, 0x00, 0x00, 0x5a, 0x04, 0x00, 0x00, + 0x68, 0x04, 0x00, 0x00, 0x74, 0x04, 0x00, 0x00, 0x81, 0x04, 0x00, 0x00, 0x8e, 0x04, 0x00, 0x00, 0x9f, 0x04, 0x00, 0x00, 0xb0, 0x04, 0x00, 0x00, + 0xc1, 0x04, 0x00, 0x00, 0xd2, 0x04, 0x00, 0x00, 0xe2, 0x04, 0x00, 0x00, 0xf3, 0x04, 0x00, 0x00, 0xfc, 0x04, 0x00, 0x00, 0x05, 0x05, 0x00, 0x00, + 0x16, 0x05, 0x00, 0x00, 0x27, 0x05, 0x00, 0x00, 0x38, 0x05, 0x00, 0x00, 0x49, 0x05, 0x00, 0x00, 0x5a, 0x05, 0x00, 0x00, 0x6b, 0x05, 0x00, 0x00, + 0x74, 0x05, 0x00, 0x00, 0x7d, 0x05, 0x00, 0x00, 0x8e, 0x05, 0x00, 0x00, 0x9f, 0x05, 0x00, 0x00, 0xb0, 0x05, 0x00, 0x00, 0xc1, 0x05, 0x00, 0x00, + 0xd2, 0x05, 0x00, 0x00, 0xe3, 0x05, 0x00, 0x00, 0xec, 0x05, 0x00, 0x00, 0xf5, 0x05, 0x00, 0x00, 0xfe, 0x05, 0x00, 0x00, 0x0f, 0x06, 0x00, 0x00, + 0x20, 0x06, 0x00, 0x00, 0x31, 0x06, 0x00, 0x00, 0x42, 0x06, 0x00, 0x00, 0x53, 0x06, 0x00, 0x00, 0x64, 0x06, 0x00, 0x00, 0x74, 0x06, 0x00, 0x00, + 0x85, 0x06, 0x00, 0x00, 0x96, 0x06, 0x00, 0x00, 0xa7, 0x06, 0x00, 0x00, 0xb7, 0x06, 0x00, 0x00, 0xc3, 0x06, 0x00, 0x00, 0xcf, 0x06, 0x00, 0x00, + 0xdb, 0x06, 0x00, 0x00, 0xe7, 0x06, 0x00, 0x00, 0xf7, 0x06, 0x00, 0x00, 0x08, 0x07, 0x00, 0x00, 0x19, 0x07, 0x00, 0x00, 0x2a, 0x07, 0x00, 0x00, + 0x3a, 0x07, 0x00, 0x00, 0x49, 0x07, 0x00, 0x00, 0x53, 0x07, 0x00, 0x00, 0x5f, 0x07, 0x00, 0x00, 0x6d, 0x07, 0x00, 0x00, 0x7c, 0x07, 0x00, 0x00, + 0x8a, 0x07, 0x00, 0x00, 0x96, 0x07, 0x00, 0x00, 0xa3, 0x07, 0x00, 0x00, 0xb0, 0x07, 0x00, 0x00, 0xc1, 0x07, 0x00, 0x00, 0xd2, 0x07, 0x00, 0x00, + 0xe3, 0x07, 0x00, 0x00, 0xf4, 0x07, 0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x15, 0x08, 0x00, 0x00, 0x1e, 0x08, 0x00, 0x00, 0x27, 0x08, 0x00, 0x00, + 0x38, 0x08, 0x00, 0x00, 0x49, 0x08, 0x00, 0x00, 0x5a, 0x08, 0x00, 0x00, 0x6b, 0x08, 0x00, 0x00, 0x7c, 0x08, 0x00, 0x00, 0x8d, 0x08, 0x00, 0x00, + 0x96, 0x08, 0x00, 0x00, 0x9f, 0x08, 0x00, 0x00, 0xad, 0x08, 0x00, 0x00, 0xba, 0x08, 0x00, 0x00, 0xc9, 0x08, 0x00, 0x00, 0xd8, 0x08, 0x00, 0x00, + 0xe9, 0x08, 0x00, 0x00, 0xfa, 0x08, 0x00, 0x00, 0x0b, 0x09, 0x00, 0x00, 0x1c, 0x09, 0x00, 0x00, 0x2d, 0x09, 0x00, 0x00, 0x3e, 0x09, 0x00, 0x00, + 0x4f, 0x09, 0x00, 0x00, 0x60, 0x09, 0x00, 0x00, 0x71, 0x09, 0x00, 0x00, 0x82, 0x09, 0x00, 0x00, 0x93, 0x09, 0x00, 0x00, 0xa4, 0x09, 0x00, 0x00, + 0xb5, 0x09, 0x00, 0x00, 0xc6, 0x09, 0x00, 0x00, 0xd7, 0x09, 0x00, 0x00, 0xe0, 0x09, 0x00, 0x00, 0xe9, 0x09, 0x00, 0x00, 0xf2, 0x09, 0x00, 0x00, + 0x03, 0x0a, 0x00, 0x00, 0x14, 0x0a, 0x00, 0x00, 0x25, 0x0a, 0x00, 0x00, 0x36, 0x0a, 0x00, 0x00, 0x47, 0x0a, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb9, 0xba, 0xbb, 0xbf, + 0xbf, 0xb8, 0xb9, 0xb9, 0xb9, 0xb8, 0xb7, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb7, 0xba, 0xbb, 0xbf, 0xbf, 0xb2, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xbb, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb5, 0xb9, 0xbb, 0xbf, 0xbf, 0xb2, 0xb5, 0xb6, 0xb5, 0xb5, 0xb5, 0xb3, + 0xbb, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb5, 0xba, 0xbb, 0xbf, 0xbf, 0xb5, 0xb8, 0xb8, 0xb8, 0xb8, 0xb5, 0xb4, 0xbb, 0xbf, 0x00, 0xff, + 0x00, 0x0f, 0x00, 0xbf, 0xb5, 0xba, 0xbb, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xb5, 0xb4, 0xbb, 0xbf, 0x00, 0xff, 0x00, 0x05, 0x00, 0xbf, + 0xb5, 0xb9, 0xbc, 0xbf, 0x00, 0x0a, 0x05, 0x00, 0xbf, 0xb2, 0xb4, 0xbb, 0xbf, 0x00, 0xff, 0x00, 0x05, 0x00, 0xbf, 0xb3, 0xb9, 0xbc, 0xbf, 0x00, + 0x0a, 0x05, 0x00, 0xbf, 0xb2, 0xb4, 0xbb, 0xbf, 0x00, 0xff, 0x00, 0x05, 0x00, 0xbf, 0xb3, 0xba, 0xbc, 0xbf, 0x00, 0x0a, 0x05, 0x00, 0xbf, 0xb2, + 0xb4, 0xbb, 0xbf, 0x00, 0xff, 0x00, 0x06, 0x00, 0xbf, 0xb5, 0xb9, 0xba, 0xbc, 0xbf, 0x00, 0x09, 0x06, 0x00, 0xbf, 0xbb, 0xb7, 0xb7, 0xbb, 0xbf, + 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb8, 0xb9, 0xb9, 0xba, 0xbc, 0xbf, 0xbf, 0xbf, 0xbb, 0xb8, 0xba, 0xba, 0xbb, 0xbf, 0x00, 0xff, 0x00, 0x0f, + 0x00, 0xbf, 0xbb, 0xb8, 0xb9, 0xb8, 0xba, 0xb9, 0xb8, 0xb8, 0xb9, 0xba, 0xbc, 0xbc, 0xbb, 0xbf, 0x00, 0xff, 0x01, 0x0d, 0x00, 0xbf, 0xba, 0xb8, + 0xb9, 0xba, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbc, 0xbf, 0x00, 0xff, 0x01, 0x0d, 0x00, 0xbf, 0xbd, 0xb8, 0xb9, 0xb9, 0xbb, 0xbb, 0xba, 0xba, + 0xba, 0xbb, 0xbd, 0xbf, 0x00, 0xff, 0x02, 0x0b, 0x00, 0xbf, 0xbf, 0xbb, 0xb8, 0xb9, 0xbc, 0xbb, 0xbb, 0xbd, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x07, + 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x08, 0x00, 0xbf, 0xbf, 0xba, 0xb9, 0xb9, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x04, + 0x0a, 0x00, 0xbf, 0xbc, 0xb9, 0xb6, 0xb6, 0xb6, 0xb5, 0xb7, 0xbc, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xb7, 0xb5, 0xb5, 0xb5, 0xb6, 0xb5, + 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb9, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, 0xb3, 0xb3, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, + 0x00, 0xbf, 0xb4, 0xb6, 0xb9, 0xb9, 0xb5, 0xb6, 0xb9, 0xb9, 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb6, 0xba, 0xbf, 0xbf, 0xb5, + 0xba, 0xbf, 0xbf, 0xb3, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xbb, 0xbf, 0xbf, 0xb4, 0xbc, 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, 0x00, + 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xba, 0xbf, 0xbf, 0xb4, 0xbb, 0xbf, 0xbf, 0xb2, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xbc, + 0xbf, 0xbf, 0xb4, 0xbc, 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xbc, 0xbf, 0xbf, 0xb2, 0xba, 0xbf, 0xbf, 0xb3, + 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb9, 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, 0xbf, 0xb4, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, + 0xbf, 0xb8, 0xbb, 0xbf, 0xbf, 0xb9, 0xbb, 0xbf, 0xbf, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x0a, 0x05, 0x00, 0xbf, 0xbf, 0xb8, 0xb8, 0xbf, 0x00, 0xff, 0x08, 0x07, 0x00, 0xbf, 0xbf, 0xba, 0xb7, + 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x06, 0x09, 0x00, 0xbf, 0xbf, 0xba, 0xb8, 0xb5, 0xb2, 0xb2, 0xb7, 0xbf, 0x00, 0xff, 0x05, 0x0a, 0x00, 0xbf, 0xba, + 0xb8, 0xb5, 0xb5, 0xb4, 0xb3, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x09, 0x00, 0xbf, 0xbb, 0xb8, 0xb6, 0xb5, 0xb6, 0xba, 0xbf, 0xbf, 0x00, 0xff, + 0x04, 0x07, 0x00, 0xbf, 0xb8, 0xb6, 0xb7, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x08, 0x00, 0xbf, 0xb7, 0xb9, 0xba, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, + 0xff, 0x03, 0x08, 0x00, 0xbf, 0xb6, 0xb8, 0xbf, 0xbf, 0xb4, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xba, 0xbf, 0xbf, 0xb4, 0xbb, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xbb, 0xb9, 0xb9, 0xb7, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xbf, 0x00, 0xff, + 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb5, 0xb6, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xba, 0xb7, 0xb7, + 0xb8, 0xb7, 0xb8, 0xb7, 0xb6, 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xbc, 0xba, 0xb9, 0xba, 0xba, 0xb9, 0xba, 0xba, 0xbb, 0xbf, + 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbe, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb8, + 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb2, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb6, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, + 0x0c, 0x00, 0xbf, 0xb1, 0xb1, 0xb2, 0xb3, 0xb1, 0xb1, 0xb2, 0xb3, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb3, 0xb3, 0xb6, + 0xb5, 0xb5, 0xb4, 0xb3, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb6, 0xb7, 0xb6, 0xb8, 0xb8, 0xb8, 0xb7, 0xb6, 0xb9, 0xbf, + 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb9, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb2, + 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb5, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb8, 0xba, 0xba, 0xba, 0xba, 0xba, 0xb9, 0xb9, 0xb9, 0xba, 0xbe, 0x00, 0xff, 0x03, + 0x0c, 0x00, 0xbf, 0xb4, 0xb9, 0xb8, 0xb8, 0xb8, 0xb7, 0xb6, 0xb5, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb7, 0xb7, 0xb6, 0xb7, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb7, 0xb8, 0xb9, 0xb9, 0xb8, 0xba, 0xba, 0xba, 0xb9, 0xbb, 0xbe, + 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xb6, 0xba, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x07, 0x04, 0x00, 0xbf, 0xb4, + 0xba, 0xbf, 0x00, 0xff, 0x07, 0x04, 0x00, 0xbf, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x07, 0x04, 0x00, 0xbf, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, + 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xb3, 0xb9, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb7, 0xb8, 0xb8, 0xb8, 0xb6, + 0xb9, 0xb9, 0xb9, 0xb8, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, 0xb4, 0xb3, 0xb8, 0xbf, 0x00, + 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb5, 0xb4, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb7, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb7, 0xba, + 0xb9, 0xb9, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xb8, 0xb7, 0xb8, 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, + 0xb5, 0xb2, 0xb3, 0xb3, 0xb3, 0xb3, 0xb4, 0xb4, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb1, 0xb3, 0xb5, 0xb5, 0xb4, 0xb3, + 0xb4, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb6, 0xb5, 0xb6, 0xb8, 0xb6, 0xb7, 0xb8, 0xb7, 0xb7, 0xb9, 0xbf, 0x00, 0xff, 0x04, + 0x0b, 0x00, 0xbf, 0xb6, 0xbb, 0xbb, 0xbc, 0xbc, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x07, 0x00, 0xbf, 0xb7, 0xbb, 0xbb, 0xbb, 0xbc, + 0xbf, 0x00, 0xff, 0x06, 0x07, 0x00, 0xbf, 0xb4, 0xba, 0xba, 0xba, 0xba, 0xbf, 0x00, 0xff, 0x06, 0x07, 0x00, 0xbf, 0xb4, 0xb9, 0xb8, 0xb6, 0xb9, + 0xbf, 0x00, 0xff, 0x05, 0x07, 0x00, 0xbf, 0xb3, 0xb5, 0xb5, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xb5, 0xb9, 0xb7, 0xb6, 0xb5, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xb8, 0xb7, 0xb5, 0xb4, 0xb3, 0xb7, 0xb8, 0xb8, 0xba, 0xbf, 0x00, 0xff, + 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb4, 0xb6, 0xb7, 0xb8, 0xba, 0xba, 0xba, 0xbb, 0xbc, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb6, 0xb8, + 0xb8, 0xba, 0xba, 0xbb, 0xba, 0xb9, 0xbb, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xb8, 0xbb, 0xbb, 0xbc, 0x2a, 0xbb, 0xbb, 0xb9, 0xba, 0xbf, + 0x00, 0xff, 0x05, 0x0a, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x0a, 0x05, 0x00, 0xbf, 0xbf, 0xb8, 0xb8, + 0xbf, 0x00, 0xff, 0x08, 0x07, 0x00, 0xbf, 0xbf, 0xba, 0xb7, 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x06, 0x09, 0x00, 0xbf, 0xbf, 0xba, 0xb8, 0xb5, 0xb2, + 0xb2, 0xb7, 0xbf, 0x00, 0xff, 0x05, 0x0a, 0x00, 0xbf, 0xba, 0xb8, 0xb5, 0xb5, 0xb4, 0xb3, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x09, 0x00, 0xbf, + 0xbb, 0xb8, 0xb6, 0xb5, 0xb6, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x07, 0x00, 0xbf, 0xb8, 0xb6, 0xb7, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x08, + 0x00, 0xbf, 0xb7, 0xb9, 0xba, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x08, 0x00, 0xbf, 0xb6, 0xb8, 0xbf, 0xbf, 0xb4, 0xb9, 0xbf, 0x00, 0xff, + 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xba, 0xbf, 0xbf, 0xb4, 0xbb, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xbb, 0xb9, + 0xb9, 0xb7, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb5, 0xb6, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xba, + 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xba, 0xb7, 0xb7, 0xb8, 0xb7, 0xb8, 0xb7, 0xb6, 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, + 0xbc, 0xba, 0xb9, 0xba, 0xba, 0xb9, 0xba, 0xba, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbe, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb8, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb2, 0xbb, 0xbf, 0x00, 0xff, 0x03, + 0x0c, 0x00, 0xbf, 0xb1, 0xb6, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb6, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb1, 0xb2, 0xb3, 0xb1, 0xb1, 0xb2, 0xb3, 0xb3, 0xb8, 0xbf, + 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb3, 0xb3, 0xb6, 0xb5, 0xb5, 0xb4, 0xb3, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, + 0xb6, 0xb7, 0xb6, 0xb8, 0xb8, 0xb8, 0xb7, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb9, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb2, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb5, 0xbb, 0xbf, 0x00, 0xff, 0x03, + 0x09, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x08, 0x00, 0xbf, 0xbf, 0xbb, 0xb9, 0xb9, 0xbb, 0xbf, 0xbf, + 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xbb, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xbc, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xb6, 0xb9, 0xb9, + 0xba, 0xba, 0xbb, 0xbb, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbb, 0xbc, 0xbc, 0xbc, 0xbc, 0xbb, 0xbc, 0xbc, 0xbc, 0x2c, 0xbf, 0x00, + 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb8, 0xbc, 0xbd, 0xbf, 0xbf, 0xbf, 0xbf, 0xb8, 0xbb, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb8, 0xbc, + 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb4, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb6, 0xbc, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb2, + 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb2, 0xb8, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb4, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, + 0xbf, 0xb2, 0xba, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb2, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb2, 0xb8, 0xbf, 0x00, 0x0b, 0x04, + 0x00, 0xbf, 0xb3, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb2, 0xb9, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb2, 0xba, 0xbf, 0x00, 0xff, + 0x03, 0x04, 0x00, 0xbf, 0xb6, 0xba, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb8, 0xba, 0xba, 0xba, 0xba, 0xba, 0xb9, 0xb9, 0xb9, 0xba, + 0xbe, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xb9, 0xb8, 0xb8, 0xb8, 0xb7, 0xb6, 0xb5, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, + 0xb4, 0xb8, 0xb8, 0xb8, 0xb8, 0xb7, 0xb7, 0xb6, 0xb7, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb7, 0xb8, 0xb9, 0xb9, 0xb8, 0xba, 0xba, + 0xba, 0xb9, 0xbb, 0xbe, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xb6, 0xba, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x07, + 0x04, 0x00, 0xbf, 0xb4, 0xba, 0xbf, 0x00, 0xff, 0x07, 0x04, 0x00, 0xbf, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x07, 0x04, 0x00, 0xbf, 0xb2, 0xb9, 0xbf, + 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xb3, 0xb9, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb7, + 0xb8, 0xb8, 0xb8, 0xb6, 0xb9, 0xb9, 0xb9, 0xb8, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, 0xb4, + 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb5, 0xb4, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb7, 0xbf, 0x00, 0xff, 0x03, 0x0c, + 0x00, 0xbf, 0xb7, 0xba, 0xb9, 0xb9, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, +}; + +const uint8_t lump_m_game[] = { + 0x3a, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, 0x1e, 0x01, 0x00, 0x00, + 0x30, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x58, 0x01, 0x00, 0x00, 0x6d, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x97, 0x01, 0x00, 0x00, + 0xae, 0x01, 0x00, 0x00, 0xc5, 0x01, 0x00, 0x00, 0xd9, 0x01, 0x00, 0x00, 0xec, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x11, 0x02, 0x00, 0x00, + 0x22, 0x02, 0x00, 0x00, 0x2c, 0x02, 0x00, 0x00, 0x38, 0x02, 0x00, 0x00, 0x46, 0x02, 0x00, 0x00, 0x55, 0x02, 0x00, 0x00, 0x63, 0x02, 0x00, 0x00, + 0x6f, 0x02, 0x00, 0x00, 0x7c, 0x02, 0x00, 0x00, 0x89, 0x02, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0xab, 0x02, 0x00, 0x00, 0xbc, 0x02, 0x00, 0x00, + 0xcd, 0x02, 0x00, 0x00, 0xdd, 0x02, 0x00, 0x00, 0xec, 0x02, 0x00, 0x00, 0xfc, 0x02, 0x00, 0x00, 0x0d, 0x03, 0x00, 0x00, 0x1e, 0x03, 0x00, 0x00, + 0x2f, 0x03, 0x00, 0x00, 0x3f, 0x03, 0x00, 0x00, 0x4b, 0x03, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, 0x63, 0x03, 0x00, 0x00, 0x6f, 0x03, 0x00, 0x00, + 0x7f, 0x03, 0x00, 0x00, 0x90, 0x03, 0x00, 0x00, 0xa1, 0x03, 0x00, 0x00, 0xb2, 0x03, 0x00, 0x00, 0xc2, 0x03, 0x00, 0x00, 0xd1, 0x03, 0x00, 0x00, + 0xde, 0x03, 0x00, 0x00, 0xed, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0d, 0x04, 0x00, 0x00, 0x1e, 0x04, 0x00, 0x00, 0x2f, 0x04, 0x00, 0x00, + 0x40, 0x04, 0x00, 0x00, 0x51, 0x04, 0x00, 0x00, 0x62, 0x04, 0x00, 0x00, 0x73, 0x04, 0x00, 0x00, 0x84, 0x04, 0x00, 0x00, 0x95, 0x04, 0x00, 0x00, + 0x04, 0x07, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x02, 0x0b, 0x00, 0xbf, 0xbf, 0xbd, 0xbb, 0xbd, 0xbd, 0xbd, 0xbb, 0xbd, + 0xbf, 0xbf, 0x00, 0xff, 0x01, 0x0d, 0x00, 0xbf, 0xbd, 0xb9, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb9, 0xbd, 0xbf, 0x00, 0xff, 0x01, 0x0d, + 0x00, 0xbf, 0xba, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xba, 0xbb, 0xbb, 0xbc, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xbd, 0xbb, 0xbc, 0xbd, + 0xbd, 0xbc, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xbb, 0xbe, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb8, 0xbc, 0xbb, 0xba, 0xbc, 0xbf, 0xbf, 0xbf, + 0xbd, 0xbc, 0xbc, 0xbc, 0xbd, 0xbf, 0x00, 0xff, 0x00, 0x06, 0x00, 0xbf, 0xb8, 0xbb, 0xba, 0xbd, 0xbf, 0x00, 0x09, 0x06, 0x00, 0xbf, 0xbc, 0xbd, + 0xbc, 0xbc, 0xbf, 0x00, 0xff, 0x00, 0x05, 0x00, 0xbf, 0xb7, 0xba, 0xbc, 0xbf, 0x00, 0x0a, 0x05, 0x00, 0xbf, 0xb7, 0xba, 0xbc, 0xbf, 0x00, 0xff, + 0x00, 0x05, 0x00, 0xbf, 0xb4, 0xba, 0xbb, 0xbf, 0x00, 0x06, 0x09, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xb4, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x00, + 0x05, 0x00, 0xbf, 0xb5, 0xba, 0xbc, 0xbf, 0x00, 0x06, 0x09, 0x00, 0xbf, 0xb7, 0xb9, 0xbf, 0xbf, 0xb5, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x00, 0x05, + 0x00, 0xbf, 0xb7, 0xba, 0xbc, 0xbf, 0x00, 0x06, 0x09, 0x00, 0xbf, 0xb5, 0xb7, 0xbf, 0xbf, 0xb5, 0xb7, 0xbc, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, + 0xbf, 0xb9, 0xb9, 0xb9, 0xb9, 0xbf, 0xbf, 0xb7, 0xb9, 0xb9, 0xb9, 0xb9, 0xb8, 0xbb, 0xbf, 0x00, 0xff, 0x01, 0x0e, 0x00, 0xbf, 0xbd, 0xbc, 0xbb, + 0xbf, 0xbf, 0xb7, 0xb8, 0xb8, 0xb8, 0xb7, 0xb7, 0xbc, 0xbf, 0x00, 0xff, 0x01, 0x0e, 0x00, 0xbf, 0xbb, 0xbc, 0xbc, 0xbf, 0xbf, 0xb8, 0xba, 0xba, + 0xb9, 0xba, 0xba, 0xbc, 0xbf, 0x00, 0xff, 0x02, 0x0d, 0x00, 0xbf, 0xbb, 0xbb, 0xbf, 0xbf, 0xb8, 0xbc, 0xbb, 0xbb, 0xbc, 0xbc, 0xbc, 0xbf, 0x00, + 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x0a, 0x05, 0x00, 0xbf, 0xbf, 0xb8, + 0xb8, 0xbf, 0x00, 0xff, 0x08, 0x07, 0x00, 0xbf, 0xbf, 0xba, 0xb7, 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x06, 0x09, 0x00, 0xbf, 0xbf, 0xba, 0xb8, 0xb5, + 0xb2, 0xb2, 0xb7, 0xbf, 0x00, 0xff, 0x05, 0x0a, 0x00, 0xbf, 0xba, 0xb8, 0xb5, 0xb5, 0xb4, 0xb3, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x09, 0x00, + 0xbf, 0xbb, 0xb8, 0xb6, 0xb5, 0xb6, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x07, 0x00, 0xbf, 0xb8, 0xb6, 0xb7, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x03, + 0x08, 0x00, 0xbf, 0xb7, 0xb9, 0xba, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x08, 0x00, 0xbf, 0xb6, 0xb8, 0xbf, 0xbf, 0xb4, 0xb9, 0xbf, 0x00, + 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xba, 0xbf, 0xbf, 0xb4, 0xbb, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xbb, + 0xb9, 0xb9, 0xb7, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb5, 0xb6, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, + 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xba, 0xb7, 0xb7, 0xb8, 0xb7, 0xb8, 0xb7, 0xb6, 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, + 0xbf, 0xbc, 0xba, 0xb9, 0xba, 0xba, 0xb9, 0xba, 0xba, 0xbb, 0xbf, 0x00, 0xff, 0x05, 0x0a, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xb8, 0xb7, 0xb8, 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, + 0xb5, 0xb2, 0xb3, 0xb3, 0xb3, 0xb3, 0xb4, 0xb4, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb1, 0xb3, 0xb5, 0xb5, 0xb4, 0xb3, + 0xb4, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb6, 0xb5, 0xb6, 0xb8, 0xb6, 0xb7, 0xb8, 0xb7, 0xb7, 0xb9, 0xbf, 0x00, 0xff, 0x04, + 0x0b, 0x00, 0xbf, 0xb6, 0xbb, 0xbb, 0xbc, 0xbc, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x07, 0x00, 0xbf, 0xb7, 0xbb, 0xbb, 0xbb, 0xbc, + 0xbf, 0x00, 0xff, 0x06, 0x07, 0x00, 0xbf, 0xb4, 0xba, 0xba, 0xba, 0xba, 0xbf, 0x00, 0xff, 0x06, 0x07, 0x00, 0xbf, 0xb4, 0xb9, 0xb8, 0xb6, 0xb9, + 0xbf, 0x00, 0xff, 0x05, 0x07, 0x00, 0xbf, 0xb3, 0xb5, 0xb5, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xb5, 0xb9, 0xb7, 0xb6, 0xb5, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xb8, 0xb7, 0xb5, 0xb4, 0xb3, 0xb7, 0xb8, 0xb8, 0xba, 0xbf, 0x00, 0xff, + 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb4, 0xb6, 0xb7, 0xb8, 0xba, 0xba, 0xba, 0xbb, 0xbc, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb6, 0xb8, + 0xb8, 0xba, 0xba, 0xbb, 0xba, 0xb9, 0xbb, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xb8, 0xbb, 0xbb, 0xbc, 0x2a, 0xbb, 0xbb, 0xb9, 0xba, 0xbf, + 0x00, 0xff, 0x05, 0x0a, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x08, 0x00, 0xbf, 0xbf, 0xba, 0xb9, + 0xb9, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xbc, 0xb9, 0xb6, 0xb6, 0xb6, 0xb5, 0xb7, 0xbc, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, + 0xbf, 0xb7, 0xb5, 0xb5, 0xb5, 0xb6, 0xb5, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb9, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, 0xb3, + 0xb3, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xb6, 0xb9, 0xb9, 0xb5, 0xb6, 0xb9, 0xb9, 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, + 0x00, 0xbf, 0xb6, 0xba, 0xbf, 0xbf, 0xb5, 0xba, 0xbf, 0xbf, 0xb3, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xbb, 0xbf, 0xbf, 0xb4, + 0xbc, 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xba, 0xbf, 0xbf, 0xb4, 0xbb, 0xbf, 0xbf, 0xb2, 0xba, 0xbf, 0x00, + 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xbc, 0xbf, 0xbf, 0xb4, 0xbc, 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xbc, + 0xbf, 0xbf, 0xb2, 0xba, 0xbf, 0xbf, 0xb3, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb9, 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, 0xbf, 0xb4, + 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb8, 0xbb, 0xbf, 0xbf, 0xb9, 0xbb, 0xbf, 0xbf, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, +}; + +const uint8_t lump_m_host[] = { + 0x39, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0x28, 0x01, 0x00, 0x00, + 0x3c, 0x01, 0x00, 0x00, 0x50, 0x01, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x6e, 0x01, 0x00, 0x00, 0x78, 0x01, 0x00, 0x00, 0x82, 0x01, 0x00, 0x00, + 0x8c, 0x01, 0x00, 0x00, 0xa0, 0x01, 0x00, 0x00, 0xb4, 0x01, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00, 0xdc, 0x01, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, + 0x04, 0x02, 0x00, 0x00, 0x11, 0x02, 0x00, 0x00, 0x20, 0x02, 0x00, 0x00, 0x2f, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x51, 0x02, 0x00, 0x00, + 0x62, 0x02, 0x00, 0x00, 0x73, 0x02, 0x00, 0x00, 0x84, 0x02, 0x00, 0x00, 0x95, 0x02, 0x00, 0x00, 0xa6, 0x02, 0x00, 0x00, 0xb7, 0x02, 0x00, 0x00, + 0xc8, 0x02, 0x00, 0x00, 0xd7, 0x02, 0x00, 0x00, 0xe6, 0x02, 0x00, 0x00, 0xf3, 0x02, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x15, 0x03, 0x00, 0x00, + 0x26, 0x03, 0x00, 0x00, 0x37, 0x03, 0x00, 0x00, 0x48, 0x03, 0x00, 0x00, 0x59, 0x03, 0x00, 0x00, 0x6a, 0x03, 0x00, 0x00, 0x7b, 0x03, 0x00, 0x00, + 0x8c, 0x03, 0x00, 0x00, 0x9d, 0x03, 0x00, 0x00, 0xae, 0x03, 0x00, 0x00, 0xbf, 0x03, 0x00, 0x00, 0xd0, 0x03, 0x00, 0x00, 0xe3, 0x03, 0x00, 0x00, + 0xf4, 0x03, 0x00, 0x00, 0xfd, 0x03, 0x00, 0x00, 0x06, 0x04, 0x00, 0x00, 0x17, 0x04, 0x00, 0x00, 0x28, 0x04, 0x00, 0x00, 0x39, 0x04, 0x00, 0x00, + 0x4a, 0x04, 0x00, 0x00, 0x5b, 0x04, 0x00, 0x00, 0x6c, 0x04, 0x00, 0x00, 0x75, 0x04, 0x00, 0x00, 0x7e, 0x04, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb9, 0xba, 0xba, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb8, 0xb7, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb2, 0xb6, 0xb7, 0xb7, 0xb8, 0xb6, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb1, 0xb6, 0xb7, 0xb7, 0xb8, 0xb6, 0xb5, 0xb6, 0xb5, 0xb5, 0xb5, 0xb3, + 0xb8, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb6, 0xb9, 0xba, 0xba, 0xba, 0xb9, 0xb7, 0xb7, 0xb9, 0xb9, 0xb8, 0xb7, 0xba, 0xbf, 0x00, 0xff, + 0x00, 0x0f, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xb6, 0xb8, 0xba, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x05, 0x00, 0xbf, + 0xb4, 0xb8, 0xba, 0xbf, 0x00, 0xff, 0x05, 0x05, 0x00, 0xbf, 0xb2, 0xb8, 0xba, 0xbf, 0x00, 0xff, 0x05, 0x05, 0x00, 0xbf, 0xb2, 0xb9, 0xbb, 0xbf, + 0x00, 0xff, 0x05, 0x05, 0x00, 0xbf, 0xb7, 0xb9, 0xbc, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xb7, 0xba, 0xbc, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb7, 0xba, 0xba, 0xba, 0xbb, 0xbb, 0xba, 0xbb, 0xbc, 0xbc, 0xbc, 0xbc, + 0x2c, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb3, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbd, 0xbf, 0x00, 0xff, + 0x00, 0x0f, 0x00, 0xbf, 0xb3, 0xb8, 0xb8, 0xb9, 0xb9, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba, 0xb9, 0xbb, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, + 0xb8, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xbc, 0xbc, 0xbc, 0xbb, 0xbc, 0xbb, 0xbc, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x08, 0x00, 0xbf, 0xbf, 0xba, 0xb8, 0xb8, 0xb8, 0xbf, 0xbf, 0x00, + 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xbb, 0xb2, 0xb4, 0xb4, 0xb4, 0xb4, 0xb2, 0xbb, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xb1, 0xb3, 0xb5, 0xb5, + 0xb4, 0xb3, 0xb2, 0xb7, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb9, 0xb5, 0xb5, 0xb8, 0xb8, 0xb8, 0xb6, 0xb3, 0xb2, 0xb9, 0xbf, 0x00, 0xff, + 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb5, 0xbb, 0xbf, 0xbf, 0xbf, 0xbf, 0xb5, 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xb8, 0xbf, + 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb1, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xb8, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb1, 0xb7, + 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xbb, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb1, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, + 0xb1, 0xbb, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb3, 0xbc, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xba, 0xbf, 0x00, 0x0b, 0x04, 0x00, + 0xbf, 0xb4, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xb6, 0xbc, 0xbf, 0xbf, 0xbf, 0xbf, 0xb6, 0xb8, 0xbb, 0xbf, 0x00, 0xff, 0x03, + 0x0c, 0x00, 0xbf, 0xb9, 0xb7, 0xb7, 0xb9, 0xb9, 0xb9, 0xba, 0xb9, 0xba, 0xbb, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xb8, 0xb8, 0xb8, 0xba, + 0xbb, 0xbb, 0xba, 0xb8, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xbb, 0xb5, 0xb6, 0xb9, 0xbb, 0xbb, 0xba, 0xbc, 0xbf, 0x00, 0xff, 0x05, 0x08, + 0x00, 0xbf, 0xbf, 0xba, 0xba, 0xba, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x0a, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0x00, 0xff, 0x04, 0x06, 0x00, 0xbf, 0xbb, 0xb9, 0xb9, 0xbb, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb8, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, + 0xbf, 0xb6, 0xb4, 0xb4, 0xb4, 0xb4, 0xba, 0xbf, 0xbf, 0xb2, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb4, 0xb4, 0xb3, 0xb4, 0xbc, + 0xbf, 0xbf, 0xb1, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb8, 0xb8, 0xb8, 0xb7, 0xba, 0xbf, 0xbf, 0xb1, 0xba, 0xbf, 0x00, 0xff, + 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xbb, 0xbf, 0xbf, 0xb3, 0xbc, 0xbf, 0xbf, 0xb3, 0xbc, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xbc, 0xbf, + 0xbf, 0xb2, 0xba, 0xbf, 0xbf, 0xb2, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xbb, 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, 0xbf, 0xb3, 0xbb, + 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xba, 0xbf, 0xbf, 0xb2, 0xba, 0xbf, 0xbf, 0xb6, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, + 0xb6, 0xba, 0xbf, 0xbf, 0xb4, 0xbc, 0xbf, 0xbf, 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xba, 0xbf, 0xbf, 0xb4, 0xbc, 0xbc, + 0xbb, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb7, 0xbd, 0xbf, 0xbf, 0xb6, 0xb8, 0xb8, 0xb8, 0xb8, 0xba, 0xbf, 0x00, 0xff, 0x03, + 0x0c, 0x00, 0xbf, 0xb9, 0xbe, 0xbf, 0xbf, 0xba, 0xb8, 0xb9, 0xb9, 0xb8, 0xbc, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xba, 0xbc, 0xbf, 0x00, + 0x08, 0x06, 0x00, 0xbf, 0xbb, 0xba, 0xba, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xbe, 0xbf, 0xbf, 0x00, 0x09, 0x04, 0x00, 0xbf, 0xbf, + 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb8, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb2, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0c, + 0x00, 0xbf, 0xb1, 0xb6, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb6, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb1, 0xb2, 0xb3, 0xb1, 0xb1, 0xb2, 0xb3, 0xb3, 0xb8, 0xbf, 0x00, + 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb3, 0xb3, 0xb6, 0xb5, 0xb5, 0xb4, 0xb3, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb6, + 0xb7, 0xb6, 0xb8, 0xb8, 0xb8, 0xb7, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb9, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb2, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb5, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x04, + 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, +}; + +const uint8_t lump_m_join[] = { + 0x31, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xd6, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, + 0xf4, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x12, 0x01, 0x00, 0x00, 0x26, 0x01, 0x00, 0x00, 0x3a, 0x01, 0x00, 0x00, + 0x4d, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x72, 0x01, 0x00, 0x00, 0x84, 0x01, 0x00, 0x00, 0x91, 0x01, 0x00, 0x00, 0xa0, 0x01, 0x00, 0x00, + 0xaf, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xd1, 0x01, 0x00, 0x00, 0xe2, 0x01, 0x00, 0x00, 0xf3, 0x01, 0x00, 0x00, 0x04, 0x02, 0x00, 0x00, + 0x15, 0x02, 0x00, 0x00, 0x26, 0x02, 0x00, 0x00, 0x37, 0x02, 0x00, 0x00, 0x48, 0x02, 0x00, 0x00, 0x57, 0x02, 0x00, 0x00, 0x66, 0x02, 0x00, 0x00, + 0x73, 0x02, 0x00, 0x00, 0x84, 0x02, 0x00, 0x00, 0x95, 0x02, 0x00, 0x00, 0xa6, 0x02, 0x00, 0x00, 0xb7, 0x02, 0x00, 0x00, 0xc8, 0x02, 0x00, 0x00, + 0xd9, 0x02, 0x00, 0x00, 0xe9, 0x02, 0x00, 0x00, 0xfa, 0x02, 0x00, 0x00, 0x0b, 0x03, 0x00, 0x00, 0x1c, 0x03, 0x00, 0x00, 0x2c, 0x03, 0x00, 0x00, + 0x38, 0x03, 0x00, 0x00, 0x44, 0x03, 0x00, 0x00, 0x50, 0x03, 0x00, 0x00, 0x5b, 0x03, 0x00, 0x00, 0x6c, 0x03, 0x00, 0x00, 0x7d, 0x03, 0x00, 0x00, + 0x8e, 0x03, 0x00, 0x00, 0x9f, 0x03, 0x00, 0x00, 0xaf, 0x03, 0x00, 0x00, 0x0a, 0x05, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x0a, 0x05, + 0x00, 0xbf, 0xba, 0xba, 0xbc, 0xbf, 0x00, 0xff, 0x0a, 0x05, 0x00, 0xbf, 0xb9, 0xb7, 0xbb, 0xbf, 0x00, 0xff, 0x0a, 0x05, 0x00, 0xbf, 0xba, 0xb4, + 0xbd, 0xbf, 0x00, 0xff, 0x0a, 0x05, 0x00, 0xbf, 0xba, 0xb4, 0x2c, 0xbf, 0x00, 0xff, 0x0a, 0x05, 0x00, 0xbf, 0xb9, 0xb4, 0xbc, 0xbf, 0x00, 0xff, + 0x0a, 0x05, 0x00, 0xbf, 0xb9, 0xb4, 0xbc, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xb8, 0xb4, 0xba, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb6, 0xb9, 0xba, 0xba, 0xba, 0xb9, 0xb9, 0xb9, 0xb9, 0xb6, 0xb5, 0xb4, 0xb9, 0xbf, + 0x00, 0xff, 0x00, 0x0e, 0x00, 0xbf, 0xb2, 0xb6, 0xb7, 0xb7, 0xb8, 0xb6, 0xb5, 0xb6, 0xb5, 0xb5, 0xb5, 0xb3, 0xbf, 0x00, 0xff, 0x00, 0x0e, 0x00, + 0xbf, 0xb3, 0xb6, 0xb7, 0xb7, 0xb8, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x00, 0x0d, 0x00, 0xbf, 0xb9, 0xba, 0xba, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x00, 0x0d, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x08, 0x00, 0xbf, 0xbf, 0xba, 0xb8, 0xb8, 0xb8, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xbb, 0xb2, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb2, 0xbb, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xb1, 0xb3, 0xb5, 0xb5, 0xb4, 0xb3, 0xb2, 0xb7, 0xbf, 0x00, 0xff, 0x03, + 0x0c, 0x00, 0xbf, 0xb9, 0xb5, 0xb5, 0xb8, 0xb8, 0xb8, 0xb6, 0xb3, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb5, 0xbb, 0xbf, + 0xbf, 0xbf, 0xbf, 0xb5, 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xb8, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb1, 0xb8, 0xbf, + 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xb8, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb1, 0xb7, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, + 0xbb, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb1, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xbb, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, + 0xb3, 0xbc, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xba, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb4, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, + 0x00, 0xbf, 0xb4, 0xb6, 0xbc, 0xbf, 0xbf, 0xbf, 0xbf, 0xb6, 0xb8, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb9, 0xb7, 0xb7, 0xb9, 0xb9, + 0xb9, 0xba, 0xb9, 0xba, 0xbb, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xb8, 0xb8, 0xb8, 0xba, 0xbb, 0xbb, 0xba, 0xb8, 0xbf, 0x00, 0xff, 0x04, + 0x0a, 0x00, 0xbf, 0xbb, 0xb5, 0xb6, 0xb9, 0xbb, 0xbb, 0xba, 0xbc, 0xbf, 0x00, 0xff, 0x05, 0x08, 0x00, 0xbf, 0xbf, 0xba, 0xba, 0xba, 0xba, 0xbf, + 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, + 0xb7, 0xb9, 0xb8, 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb9, 0xba, 0xba, 0xba, 0xbb, 0xb9, + 0xb7, 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb8, 0xb8, 0xba, 0xb9, 0xb9, 0xbb, 0xb9, 0xb8, 0xba, 0xbf, 0x00, 0xff, 0x03, + 0x0c, 0x00, 0xbf, 0xb8, 0xbb, 0xba, 0xba, 0xba, 0xbb, 0xbb, 0xba, 0xb9, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xb8, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xba, 0xbb, 0xbb, 0xbf, 0x00, + 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb4, 0xb6, 0xb5, 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb5, + 0xb5, 0xb5, 0xb6, 0xb5, 0xb4, 0xb3, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xba, 0xb4, 0xb5, 0xb4, 0xb6, 0xb4, 0xb7, 0xb8, 0xb8, + 0xba, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xba, 0xb6, 0xb6, 0xb6, 0xb6, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x07, 0x00, 0xbf, + 0xba, 0xb6, 0xb6, 0xb6, 0xba, 0xbf, 0x00, 0xff, 0x06, 0x07, 0x00, 0xbf, 0xba, 0xb6, 0xb4, 0xb2, 0xba, 0xbf, 0x00, 0xff, 0x07, 0x07, 0x00, 0xbf, + 0xba, 0xb2, 0xb2, 0xb2, 0xba, 0xbf, 0x00, 0xff, 0x08, 0x06, 0x00, 0xbf, 0xba, 0xb4, 0xb5, 0xb3, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xb4, 0xb4, 0xb2, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb8, 0xb6, 0xb6, 0xb7, 0xb7, 0xb6, 0xb2, 0xb2, + 0xb2, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb6, 0xbf, 0x00, 0xff, 0x03, 0x0c, + 0x00, 0xbf, 0xb1, 0xb4, 0xb5, 0xb4, 0xb3, 0xb3, 0xb3, 0xb3, 0xb2, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0b, 0x00, 0xbf, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0a, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, +}; + +const uint8_t lump_m_name[] = { + 0x3f, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x16, 0x01, 0x00, 0x00, 0x29, 0x01, 0x00, 0x00, 0x3d, 0x01, 0x00, 0x00, + 0x51, 0x01, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x76, 0x01, 0x00, 0x00, 0x82, 0x01, 0x00, 0x00, 0x8e, 0x01, 0x00, 0x00, 0x9a, 0x01, 0x00, 0x00, + 0xa6, 0x01, 0x00, 0x00, 0xb9, 0x01, 0x00, 0x00, 0xcd, 0x01, 0x00, 0x00, 0xe1, 0x01, 0x00, 0x00, 0xf5, 0x01, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, + 0x1c, 0x02, 0x00, 0x00, 0x26, 0x02, 0x00, 0x00, 0x32, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x4f, 0x02, 0x00, 0x00, 0x5d, 0x02, 0x00, 0x00, + 0x69, 0x02, 0x00, 0x00, 0x76, 0x02, 0x00, 0x00, 0x83, 0x02, 0x00, 0x00, 0x94, 0x02, 0x00, 0x00, 0xa5, 0x02, 0x00, 0x00, 0xb6, 0x02, 0x00, 0x00, + 0xc7, 0x02, 0x00, 0x00, 0xd7, 0x02, 0x00, 0x00, 0xe6, 0x02, 0x00, 0x00, 0xf6, 0x02, 0x00, 0x00, 0x07, 0x03, 0x00, 0x00, 0x18, 0x03, 0x00, 0x00, + 0x29, 0x03, 0x00, 0x00, 0x39, 0x03, 0x00, 0x00, 0x45, 0x03, 0x00, 0x00, 0x51, 0x03, 0x00, 0x00, 0x5d, 0x03, 0x00, 0x00, 0x69, 0x03, 0x00, 0x00, + 0x79, 0x03, 0x00, 0x00, 0x8a, 0x03, 0x00, 0x00, 0x9b, 0x03, 0x00, 0x00, 0xac, 0x03, 0x00, 0x00, 0xbc, 0x03, 0x00, 0x00, 0xcb, 0x03, 0x00, 0x00, + 0xd8, 0x03, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00, 0xf6, 0x03, 0x00, 0x00, 0x07, 0x04, 0x00, 0x00, 0x18, 0x04, 0x00, 0x00, 0x29, 0x04, 0x00, 0x00, + 0x3a, 0x04, 0x00, 0x00, 0x4b, 0x04, 0x00, 0x00, 0x5c, 0x04, 0x00, 0x00, 0x6d, 0x04, 0x00, 0x00, 0x7e, 0x04, 0x00, 0x00, 0x8f, 0x04, 0x00, 0x00, + 0xa0, 0x04, 0x00, 0x00, 0xa1, 0x04, 0x00, 0x00, 0xb2, 0x04, 0x00, 0x00, 0xc3, 0x04, 0x00, 0x00, 0xd4, 0x04, 0x00, 0x00, 0x02, 0x0d, 0x00, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x01, 0x0e, 0x00, 0xbf, 0xba, 0xba, 0xba, 0xb9, 0xba, 0xba, + 0xb9, 0xba, 0xba, 0xbb, 0xbb, 0xbc, 0xbe, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb5, 0xb5, 0xb5, 0xb5, 0xb7, 0xb9, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0xb9, 0xbc, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb7, 0xb6, 0xb6, 0xb5, 0xb6, 0xb8, 0xb6, 0xb7, 0xb9, 0xba, 0xba, 0xbb, 0xbc, 0xbf, 0x00, + 0xff, 0x01, 0x0e, 0x00, 0xbf, 0xb4, 0xb6, 0xb8, 0xb8, 0xb8, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, 0xbc, 0xbd, 0xbf, 0x00, 0xff, 0x02, 0x0d, 0x00, 0xbf, + 0xb4, 0xb7, 0xb8, 0xb9, 0xb8, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x07, 0x00, 0xbf, 0xb4, 0xb8, 0xb7, 0xb7, 0xba, 0xbf, + 0x00, 0xff, 0x04, 0x07, 0x00, 0xbf, 0xb4, 0xb7, 0xb8, 0xb8, 0xbb, 0xbf, 0x00, 0xff, 0x05, 0x07, 0x00, 0xbf, 0xb4, 0xb7, 0xb9, 0xb9, 0xbc, 0xbf, + 0x00, 0xff, 0x06, 0x07, 0x00, 0xbf, 0xb4, 0xb7, 0xb6, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x00, 0x0e, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xb8, 0xb6, 0xb6, 0xb8, 0xba, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb8, 0xba, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb7, 0xb8, 0xb8, 0xb6, + 0xb8, 0xbc, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb4, 0xb8, 0xb7, 0xb3, 0xb3, 0xb4, 0xb5, 0xb5, 0xb7, 0xb7, 0xb5, 0xb6, 0xb9, 0xbf, 0x00, + 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb4, 0xb8, 0xb7, 0xb7, 0xb6, 0xb6, 0xb8, 0xb7, 0xb7, 0xb7, 0xb7, 0xb4, 0xb9, 0xbf, 0x00, 0xff, 0x00, 0x0e, 0x00, + 0xbf, 0xb9, 0xbb, 0xbb, 0xbb, 0xba, 0xb9, 0xba, 0xba, 0xb9, 0xb9, 0xb9, 0xbb, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x0a, 0x05, 0x00, 0xbf, 0xbf, 0xb8, 0xb8, 0xbf, 0x00, 0xff, 0x08, 0x07, + 0x00, 0xbf, 0xbf, 0xba, 0xb7, 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x06, 0x09, 0x00, 0xbf, 0xbf, 0xba, 0xb8, 0xb5, 0xb2, 0xb2, 0xb7, 0xbf, 0x00, 0xff, + 0x05, 0x0a, 0x00, 0xbf, 0xba, 0xb8, 0xb5, 0xb5, 0xb4, 0xb3, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x09, 0x00, 0xbf, 0xbb, 0xb8, 0xb6, 0xb5, 0xb6, + 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x07, 0x00, 0xbf, 0xb8, 0xb6, 0xb7, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x08, 0x00, 0xbf, 0xb7, 0xb9, 0xba, + 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x08, 0x00, 0xbf, 0xb6, 0xb8, 0xbf, 0xbf, 0xb4, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, + 0xba, 0xbf, 0xbf, 0xb4, 0xbb, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xbb, 0xb9, 0xb9, 0xb7, 0xb9, 0xb9, 0xba, + 0xba, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb5, 0xb6, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, + 0x00, 0xbf, 0xba, 0xb7, 0xb7, 0xb8, 0xb7, 0xb8, 0xb7, 0xb6, 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xbc, 0xba, 0xb9, 0xba, 0xba, + 0xb9, 0xba, 0xba, 0xbb, 0xbf, 0x00, 0xff, 0x05, 0x0a, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x0b, + 0x00, 0xbf, 0xb8, 0xb7, 0xb8, 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb5, 0xb2, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb4, 0xb4, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb1, 0xb3, 0xb5, 0xb5, 0xb4, 0xb3, 0xb4, 0xb3, 0xb8, 0xbf, 0x00, 0xff, + 0x03, 0x0c, 0x00, 0xbf, 0xb6, 0xb5, 0xb6, 0xb8, 0xb6, 0xb7, 0xb8, 0xb7, 0xb7, 0xb9, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xb6, 0xbb, 0xbb, + 0xbc, 0xbc, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x07, 0x00, 0xbf, 0xb7, 0xbb, 0xbb, 0xbb, 0xbc, 0xbf, 0x00, 0xff, 0x06, 0x07, 0x00, + 0xbf, 0xb4, 0xba, 0xba, 0xba, 0xba, 0xbf, 0x00, 0xff, 0x06, 0x07, 0x00, 0xbf, 0xb4, 0xb9, 0xb8, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x05, 0x07, 0x00, + 0xbf, 0xb3, 0xb5, 0xb5, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xb5, 0xb9, 0xb7, 0xb6, 0xb5, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, + 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xb8, 0xb7, 0xb5, 0xb4, 0xb3, 0xb7, 0xb8, 0xb8, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb4, + 0xb6, 0xb7, 0xb8, 0xba, 0xba, 0xba, 0xbb, 0xbc, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb6, 0xb8, 0xb8, 0xba, 0xba, 0xbb, 0xba, 0xb9, + 0xbb, 0xbf, 0x00, 0xff, 0x04, 0x0b, 0x00, 0xbf, 0xb8, 0xbb, 0xbb, 0xbc, 0x2a, 0xbb, 0xbb, 0xb9, 0xba, 0xbf, 0x00, 0xff, 0x05, 0x0a, 0x00, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x08, 0x00, 0xbf, 0xbf, 0xba, 0xb9, 0xb9, 0xba, 0xbf, 0xbf, 0x00, 0xff, + 0x04, 0x0a, 0x00, 0xbf, 0xbc, 0xb9, 0xb6, 0xb6, 0xb6, 0xb5, 0xb7, 0xbc, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xb7, 0xb5, 0xb5, 0xb5, 0xb6, + 0xb5, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb9, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, 0xb3, 0xb3, 0xba, 0xbf, 0x00, 0xff, 0x03, + 0x0c, 0x00, 0xbf, 0xb4, 0xb6, 0xb9, 0xb9, 0xb5, 0xb6, 0xb9, 0xb9, 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb6, 0xba, 0xbf, 0xbf, + 0xb5, 0xba, 0xbf, 0xbf, 0xb3, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xbb, 0xbf, 0xbf, 0xb4, 0xbc, 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, + 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xba, 0xbf, 0xbf, 0xb4, 0xbb, 0xbf, 0xbf, 0xb2, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, + 0xbc, 0xbf, 0xbf, 0xb4, 0xbc, 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xbc, 0xbf, 0xbf, 0xb2, 0xba, 0xbf, 0xbf, + 0xb3, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb9, 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, 0xbf, 0xb4, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, + 0x00, 0xbf, 0xb8, 0xbb, 0xbf, 0xbf, 0xb9, 0xbb, 0xbf, 0xbf, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0xff, 0x04, 0x04, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0x0a, 0x04, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, + 0x00, 0xff, 0x04, 0x04, 0x00, 0xbf, 0xb8, 0xb6, 0xbf, 0x00, 0x0a, 0x04, 0x00, 0xbf, 0xb8, 0xb6, 0xbf, 0x00, 0xff, 0x04, 0x04, 0x00, 0xbf, 0xb6, + 0xb5, 0xbf, 0x00, 0x0a, 0x04, 0x00, 0xbf, 0xb6, 0xb5, 0xbf, 0x00, 0xff, 0x04, 0x04, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0x0a, 0x04, 0x00, 0xbf, + 0xbf, 0xbf, 0xbf, 0x00, 0xff, +}; + +const uint8_t lump_m_netwk[] = { + 0x62, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00, 0x00, 0xa2, 0x01, 0x00, 0x00, 0xb5, 0x01, 0x00, 0x00, 0xc9, 0x01, 0x00, 0x00, + 0xdd, 0x01, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x0e, 0x02, 0x00, 0x00, 0x1a, 0x02, 0x00, 0x00, 0x26, 0x02, 0x00, 0x00, + 0x32, 0x02, 0x00, 0x00, 0x45, 0x02, 0x00, 0x00, 0x59, 0x02, 0x00, 0x00, 0x6d, 0x02, 0x00, 0x00, 0x81, 0x02, 0x00, 0x00, 0x94, 0x02, 0x00, 0x00, + 0xa6, 0x02, 0x00, 0x00, 0xb3, 0x02, 0x00, 0x00, 0xc2, 0x02, 0x00, 0x00, 0xd1, 0x02, 0x00, 0x00, 0xe2, 0x02, 0x00, 0x00, 0xf3, 0x02, 0x00, 0x00, + 0x04, 0x03, 0x00, 0x00, 0x15, 0x03, 0x00, 0x00, 0x26, 0x03, 0x00, 0x00, 0x37, 0x03, 0x00, 0x00, 0x48, 0x03, 0x00, 0x00, 0x59, 0x03, 0x00, 0x00, + 0x6a, 0x03, 0x00, 0x00, 0x7b, 0x03, 0x00, 0x00, 0x84, 0x03, 0x00, 0x00, 0x8d, 0x03, 0x00, 0x00, 0x9e, 0x03, 0x00, 0x00, 0xaf, 0x03, 0x00, 0x00, + 0xc0, 0x03, 0x00, 0x00, 0xd1, 0x03, 0x00, 0x00, 0xe2, 0x03, 0x00, 0x00, 0xf3, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0b, 0x04, 0x00, 0x00, + 0x1b, 0x04, 0x00, 0x00, 0x2c, 0x04, 0x00, 0x00, 0x3d, 0x04, 0x00, 0x00, 0x4e, 0x04, 0x00, 0x00, 0x5a, 0x04, 0x00, 0x00, 0x66, 0x04, 0x00, 0x00, + 0x72, 0x04, 0x00, 0x00, 0x7e, 0x04, 0x00, 0x00, 0x8a, 0x04, 0x00, 0x00, 0x96, 0x04, 0x00, 0x00, 0xa7, 0x04, 0x00, 0x00, 0xb8, 0x04, 0x00, 0x00, + 0xc9, 0x04, 0x00, 0x00, 0xd9, 0x04, 0x00, 0x00, 0xe8, 0x04, 0x00, 0x00, 0xf5, 0x04, 0x00, 0x00, 0x04, 0x05, 0x00, 0x00, 0x13, 0x05, 0x00, 0x00, + 0x24, 0x05, 0x00, 0x00, 0x35, 0x05, 0x00, 0x00, 0x46, 0x05, 0x00, 0x00, 0x57, 0x05, 0x00, 0x00, 0x68, 0x05, 0x00, 0x00, 0x79, 0x05, 0x00, 0x00, + 0x8a, 0x05, 0x00, 0x00, 0x9b, 0x05, 0x00, 0x00, 0xac, 0x05, 0x00, 0x00, 0xbb, 0x05, 0x00, 0x00, 0xca, 0x05, 0x00, 0x00, 0xd7, 0x05, 0x00, 0x00, + 0xe8, 0x05, 0x00, 0x00, 0xf9, 0x05, 0x00, 0x00, 0x0a, 0x06, 0x00, 0x00, 0x1b, 0x06, 0x00, 0x00, 0x2c, 0x06, 0x00, 0x00, 0x3d, 0x06, 0x00, 0x00, + 0x4a, 0x06, 0x00, 0x00, 0x57, 0x06, 0x00, 0x00, 0x64, 0x06, 0x00, 0x00, 0x75, 0x06, 0x00, 0x00, 0x86, 0x06, 0x00, 0x00, 0x97, 0x06, 0x00, 0x00, + 0xa8, 0x06, 0x00, 0x00, 0xb8, 0x06, 0x00, 0x00, 0xc9, 0x06, 0x00, 0x00, 0xda, 0x06, 0x00, 0x00, 0xeb, 0x06, 0x00, 0x00, 0xfc, 0x06, 0x00, 0x00, + 0x0d, 0x07, 0x00, 0x00, 0x1e, 0x07, 0x00, 0x00, 0x28, 0x07, 0x00, 0x00, 0x34, 0x07, 0x00, 0x00, 0x42, 0x07, 0x00, 0x00, 0x52, 0x07, 0x00, 0x00, + 0x63, 0x07, 0x00, 0x00, 0x77, 0x07, 0x00, 0x00, 0x89, 0x07, 0x00, 0x00, 0x99, 0x07, 0x00, 0x00, 0x02, 0x0d, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x01, 0x0e, 0x00, 0xbf, 0xba, 0xba, 0xba, 0xb9, 0xba, 0xba, 0xb9, 0xba, 0xba, 0xbb, + 0xbb, 0xbc, 0xbe, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb5, 0xb5, 0xb5, 0xb5, 0xb7, 0xb9, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xb9, 0xbc, 0xbf, 0x00, + 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb7, 0xb6, 0xb6, 0xb5, 0xb6, 0xb8, 0xb6, 0xb7, 0xb9, 0xba, 0xba, 0xbb, 0xbc, 0xbf, 0x00, 0xff, 0x01, 0x0e, 0x00, + 0xbf, 0xb4, 0xb6, 0xb8, 0xb8, 0xb8, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, 0xbc, 0xbd, 0xbf, 0x00, 0xff, 0x02, 0x0d, 0x00, 0xbf, 0xb4, 0xb7, 0xb8, 0xb9, + 0xb8, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x07, 0x00, 0xbf, 0xb4, 0xb8, 0xb7, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x04, 0x07, + 0x00, 0xbf, 0xb4, 0xb7, 0xb8, 0xb8, 0xbb, 0xbf, 0x00, 0xff, 0x05, 0x07, 0x00, 0xbf, 0xb4, 0xb7, 0xb9, 0xb9, 0xbc, 0xbf, 0x00, 0xff, 0x06, 0x07, + 0x00, 0xbf, 0xb4, 0xb7, 0xb6, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x00, 0x0e, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xb8, 0xb6, 0xb6, + 0xb8, 0xba, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb8, 0xba, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb7, 0xb8, 0xb8, 0xb6, 0xb8, 0xbc, 0xbf, 0x00, + 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb4, 0xb8, 0xb7, 0xb3, 0xb3, 0xb4, 0xb5, 0xb5, 0xb7, 0xb7, 0xb5, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, + 0xbf, 0xb4, 0xb8, 0xb7, 0xb7, 0xb6, 0xb6, 0xb8, 0xb7, 0xb7, 0xb7, 0xb7, 0xb4, 0xb9, 0xbf, 0x00, 0xff, 0x00, 0x0e, 0x00, 0xbf, 0xb9, 0xbb, 0xbb, + 0xbb, 0xba, 0xb9, 0xba, 0xba, 0xb9, 0xb9, 0xb9, 0xbb, 0xbf, 0x00, 0xff, 0x00, 0x0d, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x08, 0x00, 0xbf, 0xbf, 0xba, 0xb9, 0xb9, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xbc, + 0xb9, 0xb6, 0xb6, 0xb6, 0xb5, 0xb7, 0xbc, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xb7, 0xb5, 0xb5, 0xb5, 0xb6, 0xb5, 0xb2, 0xb9, 0xbf, 0x00, + 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb9, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, 0xb3, 0xb3, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xb6, + 0xb9, 0xb9, 0xb5, 0xb6, 0xb9, 0xb9, 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb6, 0xba, 0xbf, 0xbf, 0xb5, 0xba, 0xbf, 0xbf, 0xb3, + 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xbb, 0xbf, 0xbf, 0xb4, 0xbc, 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, + 0xbf, 0xb3, 0xba, 0xbf, 0xbf, 0xb4, 0xbb, 0xbf, 0xbf, 0xb2, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xbc, 0xbf, 0xbf, 0xb4, 0xbc, + 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xbc, 0xbf, 0xbf, 0xb2, 0xba, 0xbf, 0xbf, 0xb3, 0xba, 0xbf, 0x00, 0xff, + 0x03, 0x0c, 0x00, 0xbf, 0xb3, 0xb9, 0xbf, 0xbf, 0xb2, 0xb9, 0xbf, 0xbf, 0xb4, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb8, 0xbb, 0xbf, + 0xbf, 0xb9, 0xbb, 0xbf, 0xbf, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb8, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb2, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, + 0xbf, 0xb1, 0xb6, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb1, 0xb2, 0xb3, 0xb1, 0xb1, 0xb2, 0xb3, 0xb3, 0xb8, 0xbf, 0x00, 0xff, + 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb3, 0xb3, 0xb6, 0xb5, 0xb5, 0xb4, 0xb3, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb6, 0xb7, + 0xb6, 0xb8, 0xb8, 0xb8, 0xb7, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb9, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb2, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0a, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0x00, 0xff, 0x03, 0x0b, 0x00, 0xbf, 0xb6, 0xb7, 0xb7, 0xb7, 0xb8, 0xb7, 0xb6, 0xb6, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, + 0xb5, 0xb5, 0xb5, 0xb7, 0xb4, 0xb3, 0xb5, 0xb5, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb5, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb3, 0xb4, + 0xb3, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xb3, 0xb2, 0xb2, 0xb2, 0xbc, 0xbf, 0x00, 0xff, 0x07, 0x07, + 0x00, 0xbf, 0xb2, 0xb4, 0xb3, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x06, 0x07, 0x00, 0xbf, 0xb2, 0xb8, 0xb7, 0xb8, 0xbc, 0xbf, 0x00, 0xff, 0x05, 0x07, + 0x00, 0xbf, 0xb2, 0xb7, 0xb7, 0xb7, 0xbc, 0xbf, 0x00, 0xff, 0x05, 0x07, 0x00, 0xbf, 0xb2, 0xb8, 0xb7, 0xb6, 0xbb, 0xbf, 0x00, 0xff, 0x06, 0x07, + 0x00, 0xbf, 0xb2, 0xb7, 0xb8, 0xb9, 0xbb, 0xbf, 0x00, 0xff, 0x07, 0x07, 0x00, 0xbf, 0xb4, 0xbb, 0xbb, 0xba, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, + 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbb, 0xbb, 0xba, 0xb6, 0xbd, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb9, 0xbc, 0xbb, 0xba, 0xba, + 0xb9, 0xb8, 0xb8, 0xb8, 0xbc, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb6, 0xbd, 0xbc, 0xbb, 0xb9, 0xb8, 0xb9, 0xb9, 0xb8, 0xbd, 0xbf, 0x00, + 0xff, 0x03, 0x0b, 0x00, 0xbf, 0xb8, 0xba, 0xb9, 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0a, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x05, 0x08, 0x00, 0xbf, 0xbf, 0xba, 0xb8, 0xb8, 0xb8, 0xbf, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, + 0xbf, 0xbb, 0xb2, 0xb4, 0xb4, 0xb4, 0xb4, 0xb2, 0xbb, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xb1, 0xb3, 0xb5, 0xb5, 0xb4, 0xb3, 0xb2, 0xb7, + 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb9, 0xb5, 0xb5, 0xb8, 0xb8, 0xb8, 0xb6, 0xb3, 0xb2, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, + 0xb3, 0xb5, 0xbb, 0xbf, 0xbf, 0xbf, 0xbf, 0xb5, 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xb8, 0xbf, 0x00, 0x0b, 0x04, 0x00, + 0xbf, 0xb1, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xb8, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb1, 0xb7, 0xbf, 0x00, 0xff, 0x03, + 0x04, 0x00, 0xbf, 0xb1, 0xbb, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb1, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xbb, 0xbf, 0x00, + 0x0b, 0x04, 0x00, 0xbf, 0xb3, 0xbc, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xba, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb4, 0xba, 0xbf, + 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb4, 0xb6, 0xbc, 0xbf, 0xbf, 0xbf, 0xbf, 0xb6, 0xb8, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb9, + 0xb7, 0xb7, 0xb9, 0xb9, 0xb9, 0xba, 0xb9, 0xba, 0xbb, 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xb8, 0xb8, 0xb8, 0xba, 0xbb, 0xbb, 0xba, 0xb8, + 0xbf, 0x00, 0xff, 0x04, 0x0a, 0x00, 0xbf, 0xbb, 0xb5, 0xb6, 0xb9, 0xbb, 0xbb, 0xba, 0xbc, 0xbf, 0x00, 0xff, 0x05, 0x08, 0x00, 0xbf, 0xbf, 0xba, + 0xba, 0xba, 0xba, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, + 0x03, 0x0c, 0x00, 0xbf, 0xb8, 0xba, 0xbf, 0xbf, 0xb7, 0xb8, 0xb7, 0xb6, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb8, 0xbf, + 0xbf, 0xb2, 0xb4, 0xb4, 0xb5, 0xb5, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb9, 0xbf, 0xbf, 0xb1, 0xb6, 0xb5, 0xb4, 0xb4, 0xba, + 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb8, 0xbf, 0xbf, 0xb1, 0xb6, 0xb5, 0xb6, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, + 0xb1, 0xb9, 0xbf, 0xbf, 0xb1, 0xb8, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x08, 0x00, 0xbf, 0xb1, 0xb9, 0xbf, 0xbf, 0xb1, 0xba, 0xbf, + 0x00, 0xff, 0x03, 0x08, 0x00, 0xbf, 0xb1, 0xb9, 0xbf, 0xbf, 0xb1, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x08, 0x00, 0xbf, 0xb1, 0xb8, 0xbf, 0xbf, 0xb3, + 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb7, 0xbf, 0xbf, 0xb2, 0xb7, 0xba, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, + 0xbf, 0xb2, 0xb7, 0xb8, 0xb8, 0xb3, 0xb3, 0xb2, 0xb2, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb4, 0xb5, 0xb4, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb2, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xba, 0xb2, 0xb3, 0xb5, 0xb5, 0xb4, 0xb4, 0xb3, 0xb2, 0xb9, 0xbf, 0x00, 0xff, + 0x04, 0x0b, 0x00, 0xbf, 0xba, 0xb6, 0xb8, 0xbf, 0xbf, 0xb7, 0xb8, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb6, 0xb7, 0xb6, 0xb7, 0xb9, 0xb8, 0xb7, 0xb8, 0xb6, 0xb9, 0xbf, + 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb2, 0xb6, 0xb3, 0xb2, 0xb3, 0xb3, 0xb4, 0xb5, 0xb5, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, + 0xb3, 0xb4, 0xb1, 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb5, 0xb4, 0xb6, 0xb7, 0xb4, 0xb7, 0xb7, 0xb6, + 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xb5, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x06, 0x05, + 0x00, 0xbf, 0xb2, 0xb6, 0xb9, 0xbf, 0x00, 0xff, 0x05, 0x07, 0x00, 0xbf, 0xb2, 0xb5, 0xb7, 0xb4, 0xb8, 0xbf, 0x00, 0xff, 0x04, 0x09, 0x00, 0xbf, + 0xb1, 0xb4, 0xb4, 0xb4, 0xb3, 0xb3, 0xb8, 0xbf, 0x00, 0xff, 0x03, 0x0b, 0x00, 0xbf, 0xb1, 0xb4, 0xb4, 0xb3, 0xb4, 0xb3, 0xb2, 0xb2, 0xb7, 0xbf, + 0x00, 0xff, 0x03, 0x0c, 0x00, 0xbf, 0xb1, 0xb6, 0xb4, 0xb9, 0xbf, 0xb2, 0xb3, 0xb3, 0xb4, 0xbb, 0xbf, 0x00, 0xff, 0x03, 0x05, 0x00, 0xbf, 0xb1, + 0xb5, 0xb8, 0xbf, 0x00, 0x09, 0x06, 0x00, 0xbf, 0xb5, 0xb8, 0xb8, 0xbc, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xb1, 0xba, 0xbf, 0x00, 0x0a, + 0x05, 0x00, 0xbf, 0xb4, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x03, 0x03, 0x00, 0xbf, 0xb8, 0xbf, 0x00, 0x0b, 0x04, 0x00, 0xbf, 0xb5, 0xbb, 0xbf, 0x00, + 0xff, 0x03, 0x02, 0x00, 0xbf, 0xbf, 0x00, 0x0c, 0x03, 0x00, 0xbf, 0xb6, 0xbf, 0x00, 0xff, +}; + +const uint8_t lump_m_two[] = { + 0x0d, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, + 0x89, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0xb1, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0xd9, 0x00, 0x00, 0x00, 0xed, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x16, 0x01, 0x00, 0x00, 0x2a, 0x01, 0x00, 0x00, 0x08, 0x07, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, + 0x00, 0x05, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0x07, 0x08, 0x00, 0xbf, 0xba, 0xb6, 0xb6, 0xb7, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x00, 0x05, + 0x00, 0xbf, 0xb9, 0xba, 0xbb, 0xbf, 0x00, 0x06, 0x09, 0x00, 0xbf, 0xba, 0xb8, 0xb8, 0xb7, 0xb7, 0xb7, 0xba, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, + 0xbf, 0xb7, 0xb9, 0xb9, 0xbf, 0xbf, 0xba, 0xb8, 0xb7, 0xb8, 0xb7, 0xb6, 0xb5, 0xba, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb3, 0xb7, 0xbb, + 0xbf, 0xbf, 0xb4, 0xb6, 0xb6, 0xba, 0xba, 0xb7, 0xb7, 0xbb, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb4, 0xb6, 0xba, 0xbf, 0xbf, 0xb2, 0xba, + 0xbc, 0xbf, 0xbf, 0xb1, 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb4, 0xb8, 0xba, 0xbf, 0xbf, 0xb3, 0xb7, 0xba, 0xbf, 0xbf, 0xb1, + 0xb5, 0xb9, 0xbf, 0x00, 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb6, 0xb8, 0xbb, 0xbf, 0xbf, 0xb4, 0xb6, 0xb9, 0xbf, 0xbf, 0xb1, 0xb5, 0xb8, 0xbf, 0x00, + 0xff, 0x00, 0x0f, 0x00, 0xbf, 0xb7, 0xba, 0xb8, 0xba, 0xba, 0xb5, 0xb5, 0xb9, 0xbf, 0xbf, 0xb1, 0xb3, 0xb7, 0xbf, 0x00, 0xff, 0x01, 0x0e, 0x00, + 0xbc, 0xb9, 0xb8, 0xb7, 0xb6, 0xb5, 0xb5, 0xbb, 0xbf, 0xbf, 0xb1, 0xb2, 0xb7, 0xbf, 0x00, 0xff, 0x01, 0x08, 0x00, 0xbf, 0xba, 0xb5, 0xb5, 0xb4, + 0xb6, 0xba, 0xbf, 0x00, 0x0a, 0x05, 0x00, 0xbf, 0xb6, 0xb8, 0xb9, 0xbf, 0x00, 0xff, 0x02, 0x06, 0x00, 0xbf, 0xba, 0xb8, 0xb8, 0xba, 0xbf, 0x00, + 0x0a, 0x05, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, 0x03, 0x04, 0x00, 0xbf, 0xbf, 0xbf, 0xbf, 0x00, 0xff, +}; + +typedef struct { + const char *name; + const uint8_t *data; + int len; +} extra_patch_t; + +const extra_patch_t extra_patches[] = { + { "M_DTHMCH", lump_m_dthmch, count_of(lump_m_dthmch) }, + { "M_GAME", lump_m_game, count_of(lump_m_game) }, + { "M_HOST", lump_m_host, count_of(lump_m_host) }, + { "M_JOIN", lump_m_join, count_of(lump_m_join) }, + { "M_NAME", lump_m_name, count_of(lump_m_name) }, + { "M_NETWK", lump_m_netwk, count_of(lump_m_netwk) }, + { "M_TWO", lump_m_two, count_of(lump_m_two) }, +}; \ No newline at end of file diff --git a/src/whd_gen/huff.cpp b/src/whd_gen/huff.cpp new file mode 100644 index 00000000..c0510006 --- /dev/null +++ b/src/whd_gen/huff.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +// C++ program for Huffman Coding with STL +#include +#include +#include +#include +#include "huff.h" + +using namespace std; + +// A Huffman tree node +struct MinHeapNode { + + // One of the input characters + uint8_t data; + + // Frequency of the character + unsigned freq; + + // Left and right child + MinHeapNode *left, *right; + + MinHeapNode(uint8_t data, unsigned freq) { + + left = right = NULL; + this->data = data; + this->freq = freq; + } +}; + +// For comparison of +// two heap nodes (needed in min heap) +struct compare { + + bool operator()(MinHeapNode *l, MinHeapNode *r) { + return (l->freq > r->freq); + } +}; + +// Prints huffman codes from +// the root of Huffman Tree. +void printCodes(struct MinHeapNode *root, string str) { + + if (!root) + return; + + if (!root->left && !root->right) { + cout << std::hex << (int)root->data << ": " << std::dec; + cout << "(" << root->freq << ")"; + cout << " " << str << "\n"; + } + + printCodes(root->left, str + "0"); + printCodes(root->right, str + "1"); +} + +template std::vector huff<256>(uint8_t *data, int size, bool dump); +template std::vector huff(uint8_t *data, int size, bool dump) { + if (!size) return std::vector(N); + struct MinHeapNode *left, *right, *top; + + // Create a min heap & inserts all characters of data[] + priority_queue, compare> minHeap; + + vector counts(N); + for(int i=0;ifreq + right->freq); + + top->left = left; + top->right = right; + + minHeap.push(top); + } + + // Print Huffman codes using + // the Huffman tree built above + if (dump) { + printCodes(minHeap.top(), ""); + } + std::vector lengths(N); + std::functionassignCodes = [&](MinHeapNode *n, int length) { + if (!n) return; + if (!n->left && !n->right) { + lengths[n->data] = length; + } + if (n->left) { + assignCodes(n->left, length + 1); + } + if (n->right) { + assignCodes(n->right, length + 1); + } + }; + assignCodes(minHeap.top(), 0); +// int length = 0; +// for(int i=0;i +#include + +template std::vector huff(uint8_t *data, int size, bool dump = false); \ No newline at end of file diff --git a/src/whd_gen/huff_sink.h b/src/whd_gen/huff_sink.h new file mode 100644 index 00000000..b3642c07 --- /dev/null +++ b/src/whd_gen/huff_sink.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#pragma once + +#include "huffman.h" + +template, bool DEBUG=false> + struct symbol_sink { + using S = typename H::symbol_type; + + symbol_sink() : name("") {} + + explicit symbol_sink(std::string name) : name(std::move(name)) {} + + void output(S symbol) { + if (bit_output) { + bit_output->write(huff.encode(symbol)); + } else { + stats.add(symbol); + } + } + + void begin_output(BO &bo) { + + if (!huff.initialized()) huff = stats.template create_huffman_encoding(); + bit_output = bo; + } + + template + S decode(BI &bi) { + if (!huff.initialized()) { + huff = stats.template create_huffman_encoding(); + } + return huff.template decode(bi); + } + + void dump(bool force_debug = false) { + if (DEBUG || force_debug) { + if (!huff.initialized()) { + huff = stats.template create_huffman_encoding(); + } + huff.dump(); + auto level1_stats = huff.get_length_stats(); + auto level1_huff = level1_stats.template create_huffman_encoding>(); + // level1_huff.dump(); +// printf("SIZE %d %d\n", stats.get_total(), (int) huff.template size() / 8); +// printf("CODE SIZE %d %d\n", level1_stats.get_total(), (int) level1_huff.template size() / 8); + printf("\n"); + } + } + + std::string get_name() const { return name; } + + symbol_stats stats; + huffman_encoding huff; + BO bit_output; + std::string name; +}; + +template> + struct bit_sink { + void output(bit_sequence bs) { + if (bit_output) { + bit_output->write(bs); + } + } + + void begin_output(BO bo) { + bit_output = bo; + } + + void dump() { + } + + std::string get_name() const { return "raw bits"; } + + private: + BO bit_output; + }; + +template> + struct sink_wrapper { + template + sink_wrapper(T &t) { + auto ptr = &t; + dump = [ptr]() { return ptr->dump(); }; + get_name = [ptr]() { return ptr->get_name(); }; + begin_output = [ptr](BO bo) { + ptr->begin_output(bo); +// std::cout << ptr->get_name() << "\n"; +// ptr->dump(); + }; + } + + std::function dump; + std::function get_name; + std::function begin_output; + }; + +template> + struct sink_wrappers { + sink_wrappers(std::initializer_list> &&init) : wrappers( + std::forward>>(init)) {} + + void dump() { + std::for_each(wrappers.begin(), wrappers.end(), [](auto e) { + std::cout << e.get_name() << std::endl; + e.dump(); + }); + } + + void begin_output(BO bo) { + std::for_each(wrappers.begin(), wrappers.end(), [bo](auto &e) { + e.begin_output(bo); + }); + } + + std::vector> wrappers; + }; + +static inline th_bit_input *create_bip(byte_vector_bit_input &bi) { + auto bip = new th_bit_input(); + bip->cur = bi.data.data() + (bi.pos >> 3); +#ifndef NDEBUG + bip->end = bi.data.data() + bi.data.size(); +#endif + bip->bit = bi.pos & 7u; + return bip; +} + +static inline void uncreate_bip(byte_vector_bit_input &bi, th_bit_input *bip) { + bi.pos = (bip->cur - bi.data.data()) * 8 + bip->bit; + delete bip; +} + diff --git a/src/whd_gen/huffman.h b/src/whd_gen/huffman.h new file mode 100644 index 00000000..aacde942 --- /dev/null +++ b/src/whd_gen/huffman.h @@ -0,0 +1,676 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#pragma once + +#include +#include +#include +#include +#include + +template, int N = 15> struct huffman_params { + using symbol_type = S; + using comparator_type = C; + static const int max_code_size = N; +}; + +template> struct huffman_encoding; + +template struct symbol_stats { + void add(S symbol, uint32_t count = 1) { + symbol_counts[symbol] += count; + total += count; + } + uint32_t get_total() { return total; } + template huffman_encoding create_huffman_encoding() { + return create_huffman_encoding(H()); + } + template huffman_encoding create_huffman_encoding(const H& params); + std::map symbol_counts; + uint32_t total = 0; +}; + +// bit sequences are little endian +struct bit_sequence : public std::pair { + bit_sequence() : pair(0,0) {} + bit_sequence(uint32_t bits, int length) : pair(bits, length) { + assert(length <= 32); + assert(length == 32 || bits < (1u << length)); + } + static bit_sequence for_byte(uint8_t byte) { + return bit_sequence(byte, 8); + } + + bit_sequence append(bool bit) const { + assert(second < 32); + return bit_sequence(first | (bit * (1u << second)), second + 1); + } + + uint length() const { return second; } + uint32_t bits() const { return first; } + bool bit(int pos) const { return first & (1u << pos); } + friend std::ostream& operator <<(std::ostream& out, const bit_sequence &bs) { + if (bs.length()) { + for(uint bit = 0; bit < bs.length(); bit++) { + out << ((bs.first & (1u << bit)) ? '1' : '0'); + } + } + return out; + } +}; + +template struct symbol_adapter { + using ostream_type = S; +}; + +template<> struct symbol_adapter { + using ostream_type = int; +}; + +template<> struct symbol_adapter { + using ostream_type = int; +}; + +static inline uint32_t __rev(uint32_t v) { + v = ((v & 0x55555555u) << 1u) | ((v >> 1u) & 0x55555555u); + v = ((v & 0x33333333u) << 2u) | ((v >> 2u) & 0x33333333u); + v = ((v & 0x0f0f0f0fu) << 4u) | ((v >> 4u) & 0x0f0f0f0fu); + return (v << 24u) | ((v & 0xff00u) << 8u) | ((v >> 8u) & 0xff00u) | (v >> 24u); +} + +template struct huffman_encoding { + struct node; + using node_ptr = std::shared_ptr; + static const int CODE_MAX = 32; // longest we expect to see + + huffman_encoding() = default; + + struct node { + node() :symbol_index(-1), count(0) {} + node(int symbol_index, uint32_t count) : symbol_index(symbol_index), count(count) {} + node(const node_ptr& n1, const node_ptr& n2) : symbol_index(-1), count(n1->count + n2->count), zero(n1), one(n2) {} + int symbol_index; + uint32_t count; + node_ptr zero, one; + }; + + struct node_comparator { + bool operator()(const node_ptr& a, const node_ptr &b) { + return a->count > b->count; + } + }; + + static huffman_encoding get(const symbol_stats &stats) { + return huffman_encoding(stats); + } + + explicit huffman_encoding(const symbol_stats &stats, const H& params) : symbol_encodings() , stats(stats) { + initted = true; + std::priority_queue,node_comparator> node_queue; + for(const auto &e : stats.symbol_counts) { + if (e.second) { + node_queue.push(std::make_shared((int)symbols.size(), e.second)); + symbols.push_back(e.first); + } + } + while (node_queue.size() > 1) { + auto n1 = node_queue.top(); + node_queue.pop(); + auto n2 = node_queue.top(); + node_queue.pop(); + node_queue.push(std::make_shared(n1,n2)); + } + if (node_queue.empty()) { + root = nullptr; + } else { + // we need to assigned in symbol comparator order rather than tree traversal order, so we assign the codes + // according to length in comparator order, then rebuild the tree to match (to maintain the ability to + // decode) + std::vector> symbol_indexes_and_lengths; + std::vector length_counts; + std::function classify_by_length = [&](const node_ptr& node, uint length) { + if (node->symbol_index < 0) { + assert(node->one && node->zero); + classify_by_length(node->zero, length + 1); + classify_by_length(node->one, length + 1); + } else { + symbol_indexes_and_lengths.template emplace_back(node->symbol_index, length); + if (length >= length_counts.size()) length_counts.resize(length+1); + length_counts[length]++; + } + }; + classify_by_length(node_queue.top(), 0); + auto comparator = typename H::comparator_type(); + auto sorter = [&](const auto &a, const auto &b) { + if (a.second < b.second) return true; + if (a.second > b.second) return false; + return comparator(symbols[a.first], symbols[b.first]); + }; + std::sort(symbol_indexes_and_lengths.begin(), symbol_indexes_and_lengths.end(), sorter); + + assert (symbol_indexes_and_lengths.size() < (1u << H::max_code_size)); + if (limit_code_size(length_counts, H::max_code_size)) { + // regenerate the lengths + uint pos = 0; + for(uint l=0;l(); + std::function insert_node = [&](const node_ptr& n, const bit_sequence& code, uint length, uint symbol_index) { + if (length < code.length()) { + if (code.bit(length)) { + if (!n->one) n->one = std::make_shared(); + n->one->symbol_index = -1; + insert_node(n->one, code, length+1, symbol_index); + } else { + if (!n->zero) n->zero = std::make_shared(); + insert_node(n->zero, code, length+1, symbol_index); + } + } else { + assert(!n->one); + assert(!n->zero); + assert(!n->count); + n->symbol_index = symbol_index; + n->count = 1; + } + }; + uint32_t code_bits = 0; + uint last_length = 0; + for(auto &e : symbol_indexes_and_lengths) { + const auto &s = e.first; + const auto length = e.second; + assert(length >= last_length); + assert(length < 16); // todo fix this + if (length > last_length) { + code_bits <<= (length - last_length); + last_length = length; + } + uint32_t reversed = __rev(code_bits) >> (32 - length); + auto code = bit_sequence(reversed, length); + symbol_encodings[symbols[s]] = code; +// std::cout << ">>: " << code << " " << static_cast::ostream_type>(symbols[s]) << "\n"; + insert_node(root, code, 0, s); + code_bits++; + } + min_code_length = H::max_code_size; + max_code_length = 0; + for(uint l=0;lfirst; + } + + S get_last_symbol() const { + return symbol_encodings.rbegin()->first; + } + + uint8_t get_min_code_length() { + return min_code_length; + } + uint8_t get_max_code_length() { + return max_code_length; + } + void dump() { + std::function dump_node = [&](const node_ptr& node) { + if (node->symbol_index < 0) { + assert(node->one && node->zero); + dump_node(node->zero); + dump_node(node->one); + } else { + std::cout << symbol_encodings[symbols[node->symbol_index]] << ": " + << static_cast::ostream_type>(symbols[node->symbol_index]) << " (" + << stats.symbol_counts[symbols[node->symbol_index]] << ")" << std::endl; + } + }; + std::cout << "A\n"; + if (root) dump_node(root); + std::cout << "B\n"; + for(const auto &e : symbol_encodings) { + std::cout << static_cast::ostream_type>(e.first) << " " << e.second << " (" << stats.symbol_counts[e.first] << ")\n"; + } + } + + bit_sequence encode(const S& symbol) { + auto it = symbol_encodings.find(symbol); + assert(it != symbol_encodings.end()); + return it->second; + } + + int get_code_length(const S& symbol) const { + auto it = symbol_encodings.find(symbol); + return it == symbol_encodings.end() ? 0 : it->second.length(); + } + + int get_stats_length() const { + int length = 0; + for(const auto &e : stats.symbol_counts) { + length += e.second * get_code_length(e.first); + } + return length; + } + + template S decode(BI& bi) { + auto n = root; + while (n->symbol_index < 0) { + if (bi.bit()) n = n->one; + else n = n->zero; + } + return symbols[n->symbol_index]; + } + + template size_t size(Iterator it, const Iterator end) { + size_t size = 0; + while (it != end) { + size += encode(*it++).length(); + } + return size; + } + + size_t size() { + size_t size = 0; + for(const auto& e : stats.symbol_counts) { + if (e.second) + size += e.second * encode(e.first).length(); + } + return size; + } + + + template void decode(const S& symbol, I& input) { + input.bit(); + } + + symbol_stats get_stats() const { return stats; } + symbol_stats get_length_stats() const { return length_stats; } + bool initialized() const { return initted; } + std::vector get_symbols() const { return symbols; } + +private: + static bool limit_code_size(std::vector& length_counts, uint max_code_size) { + if (max_code_size >= length_counts.size()) return false; + for (uint i = max_code_size + 1; i < length_counts.size(); i++) { + length_counts[max_code_size] += length_counts[i]; + } + uint32_t total = 0; + for (int i = (int)max_code_size; i > 0; i--) { + total += (((uint32_t) length_counts[i]) << (max_code_size - i)); + } + bool rc = false; + while (total > (1u << max_code_size)) { + length_counts[max_code_size]--; + for (int i = (int)max_code_size - 1; i > 0; i--) { + if (length_counts[i]) { + length_counts[i]--; + length_counts[i + 1] += 2; + break; + } + } + total--; + rc = true; + } + length_counts.resize(max_code_size+1); + return rc; + } + + node_ptr root; + std::map symbol_encodings; + symbol_stats stats; + symbol_stats length_stats; + std::vector symbols; + uint min_code_length, max_code_length; + bool initted = false; +}; + +template template huffman_encoding symbol_stats::create_huffman_encoding(const H& params) { + return huffman_encoding(*this, params); +} + +struct byte_vector_bit_input { + explicit byte_vector_bit_input(const std::vector& data) : data(data), pos(0) {} + bool bit() { + assert(pos < data.size() * 8); + bool rc = data[pos>>3]&(1u<<(pos&7u)); + pos++; + return rc; + } + uint32_t read(int n) { + uint32_t accum = 0; + for(int i=0;i data; + uint32_t pos; +}; + + +struct byte_vector_bit_output { + byte_vector_bit_output() : accumulator(0), bits(0) {} + void write(const bit_sequence& seq) { + accumulator |= seq.bits() << bits; + bits += seq.length(); + while (bits >= 8) { + output.push_back(accumulator); + accumulator >>= 8; + bits -= 8; + } + } + std::vector get_output() { + pad_to_byte(); + return output; + } + + uint bit_size() const { + return output.size() * 8 + bits; + } + + uint bit_index() const { + return bits; + } + + void truncate(uint size) { + assert(size <= bit_size()); + output.resize(size/8); + bits = size & 7u; + } + + template void write_to(std::shared_ptr bo) { + for(const auto& b : output) { + bo->write(bit_sequence(b, 8)); + } + if (bits) { + bo->write(bit_sequence(accumulator, bits)); + } + } + + template void write_to(BO &bo) { + for(const auto& b : output) { + bo.write(bit_sequence(b, 8)); + } + if (bits) { + bo.write(bit_sequence(accumulator, bits)); + } + } + + void pad_to_byte() { + if (bits) { + assert(bits < 8); + write(bit_sequence(0, 8u-bits)); + } + } +private: + uint32_t accumulator; + uint bits; + std::vector output; +}; + +template struct huffman_decoder { + huffman_decoder() = default; + explicit huffman_decoder(const std::vector>& symbol_lengths) { + if (symbol_lengths.size() > 1) { + // could have passed this, but fine for now + int min_code_length = std::numeric_limits::max(); + int max_code_length = std::numeric_limits::min(); + for(const auto & e : symbol_lengths) { + min_code_length = std::min(min_code_length, e.second); + max_code_length = std::max(max_code_length, e.second); + } + code_ceiling.resize(max_code_length); + offset.resize(max_code_length); + std::vector num_codes(max_code_length); + for(const auto & e : symbol_lengths) { + num_codes[e.second-1]++; + } + for(int i = 1; i < max_code_length; i++) { + offset[i] = offset[i-1] + num_codes[i-1]; + } + symbols.resize(symbol_lengths.size()); + for(const auto &e : symbol_lengths) { + const auto length = e.second; + symbols[offset[length-1]++] = e.first; + } + int code = 0; + int pos = 0; + for(int length = 0; length < max_code_length; length++) { + if (DEBUG) { + std::cout << "Length " << length << "\n"; + for(int i=0; i< num_codes[length]; i++) { + uint32_t reversed = __rev(code + i) >> (31 - length); + auto code = bit_sequence(reversed, length+1); + std::cout << (pos + i) << " " << code << " " << static_cast::ostream_type>(symbols[pos + i]) << "\n"; + } + } + offset[length] = code - pos; + code += num_codes[length]; + code_ceiling[length] = code; + pos += num_codes[length]; + code <<= 1; + } + } else if (symbol_lengths.size() == 1) { + symbols.push_back(symbol_lengths[0].first); + } + } + + template S decode(BI& bi) { + assert(!symbols.empty()); + if (symbols.size() == 1) { + return symbols[0]; + } + uint code = 0; + uint length = 0; + do { + code = (code << 1u) | bi.bit(); + if (code < code_ceiling[length]) { + return symbols[code - offset[length]]; + } + length++; + } while (true); + } + + std::vector code_ceiling; + std::vector offset; + std::vector symbols; +}; + +template +void output_min_max_best(BO &bit_output, huffman_encoding &huff) { + bit_output->write(bit_sequence(!huff.empty(), 1)); + if (huff.empty()) return; + auto bo1 = std::make_shared(); + output_min_max(bo1, huff, true, true); + auto bo2 = std::make_shared(); + output_min_max(bo2, huff, true, false); + auto bo3 = std::make_shared(); + output_min_max(bo3, huff, false, false); +// printf("BO1/2/3 %d %d %d\n", (int) bo1->bit_size(), (int) bo2->bit_size(), (int) bo3->bit_size()); + size_t min = std::min(bo1->bit_size(), std::min(bo2->bit_size(), bo3->bit_size())); + if (bo1->bit_size() == min) { + bit_output->write(bit_sequence(1, 1)); + bo1->write_to(bit_output); + } else if (bo2->bit_size() == min) { + bit_output->write(bit_sequence(0, 1)); + bo2->write_to(bit_output); + } else { +// printf("WA\n"); // now seems to happen, but rare so not sure we care enough to add complexity + if (bo1->bit_size() < bo2->bit_size()) { + bit_output->write(bit_sequence(1, 1)); + bo1->write_to(bit_output); + } else { + bit_output->write(bit_sequence(0, 1)); + bo2->write_to(bit_output); + } + //assert(false); // doesn't seem to be ever, plan to remove + // bit_output->write(bit_sequence(0b11, 2)); + // bo3->write_to(bit_output); + } +} + +template +void output_min_max(BO &bit_output, huffman_encoding &huff, bool zero_flag, bool group8) { + if (huff.empty()) { + assert(false); // should be handled at a higher level + return; + } + uint8_t min = huff.get_first_symbol(); + uint8_t max = huff.get_last_symbol(); + bit_output->write(bit_sequence(min, 8)); + bit_output->write(bit_sequence(max, 8)); + int min_cl = huff.get_min_code_length(); + int max_cl = huff.get_max_code_length(); + if (min == max) { + assert(min_cl == max_cl); + assert(!min_cl); + return; + } + assert(max_cl < 16); + assert(min_cl <= max_cl); + bit_output->write(bit_sequence(min_cl, 4)); + bit_output->write(bit_sequence(max_cl, 4)); + if (min_cl == max_cl && zero_flag) { + for (int val = min; val <= max; val++) { + int length = huff.get_code_length(val); + assert(!length || length == min_cl); + bit_output->write(bit_sequence(length != 0, 1)); + } + } else { + uint bit_count = 32 - __builtin_clz(max_cl - min_cl + (zero_flag ? 0 : 1)); + auto stats = huff.get_stats(); + if (zero_flag) { + if (group8) { + for (uint base_val = min; base_val <= max; base_val += 8) { + uint8_t mask = 0; + uint8_t bits = 0; + for (uint i = 0; i <= std::min(7u, max - base_val); i++) { + if (huff.get_code_length(base_val + i)) mask |= 1u << i; + bits |= 1u << i; + + } + if (mask == 0) { + bit_output->write(bit_sequence(0b01, 2)); + } else if (mask == bits) { + bit_output->write(bit_sequence(0b11, 2)); + for (uint i = 0; i <= std::min(7u, max - base_val); i++) { + int length = huff.get_code_length(base_val + i); + assert(length); + length -= min_cl; + bit_output->write(bit_sequence(length, bit_count)); + } + } else { + bit_output->write(bit_sequence(0b0, 1)); + for (uint i = 0; i <= std::min(7u, max - base_val); i++) { + int length = huff.get_code_length(base_val + i); + bit_output->write(bit_sequence(length != 0, 1)); + if (length) { + length -= min_cl; + bit_output->write(bit_sequence(length, bit_count)); + } + } + } + } + } else { + for (int val = min; val <= max; val++) { + int length = huff.get_code_length(val); + bit_output->write(bit_sequence(length != 0, 1)); + if (length) { + length -= min_cl; + bit_output->write(bit_sequence(length, bit_count)); + } + } + } + } else { + for (int val = min; val <= max; val++) { + int length = huff.get_code_length(val); + if (length) { + length -= min_cl; + } else { + length = (1u << bit_count) - 1; + } + bit_output->write(bit_sequence(length, bit_count)); + } + } + } +} + +template std::vector> decode_min_max8(BI &bi) { + bool non_empty = bi.bit(); + if (!non_empty) { + printf(" No values\n"); + return {}; + } else { + bool group8 = bi.bit(); + uint8_t min = bi.read(8); + uint8_t max = bi.read(8); + if (min == max) { + printf(" %d: 0 bits\n", min); + return {std::make_pair(min, 0)}; + } + printf(" Min/Max %d/%d\n", min, max); + int min_cl = bi.read(4); + int max_cl = bi.read(4); + printf(" Code length %d->%d\n", min_cl, max_cl); + std::vector> symbol_lengths; + if (min_cl == max_cl) { + for (uint val = min; val <= max; val++) { + if (bi.bit()) { + printf(" %d: %d bits\n", val, min_cl); + symbol_lengths.template emplace_back(val, min_cl); + } + } + } else { + int bit_count = 32 - __builtin_clz(max_cl - min_cl); + if (group8) { + for (int base_val = min; base_val <= max; base_val += 8) { + bool all_same = bi.bit(); + if (all_same) { + if (bi.bit()) { + for (uint i = 0; i <= std::min(7, max - base_val); i++) { + int code_length = min_cl + bi.read(bit_count); + symbol_lengths.template emplace_back(base_val + i, code_length); + printf(" %d: %d bits\n", base_val + i, code_length); + } + } + } else { + for (int i = 0; i <= std::min(7, max - base_val); i++) { + if (bi.bit()) { + int code_length = min_cl + bi.read(bit_count); + symbol_lengths.template emplace_back(base_val + i, code_length); + printf(" %d: %d bits\n", base_val + i, code_length); + } + } + } + } + } else { + for (int val = min; val <= max; val++) { + if (bi.bit()) { + int code_length = min_cl + bi.read(bit_count); + symbol_lengths.template emplace_back(val, code_length); + printf(" %d: %d bits\n", val, code_length); + } + } + } + } + return symbol_lengths; + } +} diff --git a/src/whd_gen/lodepng.cpp b/src/whd_gen/lodepng.cpp new file mode 100644 index 00000000..c0f7b9b9 --- /dev/null +++ b/src/whd_gen/lodepng.cpp @@ -0,0 +1,6255 @@ +/* +LodePNG version 20160418 + +Copyright (c) 2005-2016 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "lodepng.h" + +#include +#include +#include + +// graham ... this is slow but who cares... it does what we want - i.e. allows us to specify a destination palette not containing all the source colors +#define NEAREST_NEIGHBOR + +#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ +#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ +#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ +#endif /*_MSC_VER */ + +const char* LODEPNG_VERSION_STRING = "20160418"; + +/* +This source file is built up in the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/*The malloc, realloc and free functions defined here with "lodepng_" in front +of the name, so that you can easily change them to others related to your +platform if needed. Everything else in the code calls these. Pass +-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out +#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and +define them in your own project's source files without needing to change +lodepng source code. Don't forget to remove "static" if you copypaste them +from here.*/ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +static void* lodepng_malloc(size_t size) +{ + return malloc(size); +} + +static void* lodepng_realloc(void* ptr, size_t new_size) +{ + return realloc(ptr, new_size); +} + +static void lodepng_free(void* ptr) +{ + free(ptr); +} +#else /*LODEPNG_COMPILE_ALLOCATORS*/ +void* lodepng_malloc(size_t size); +void* lodepng_realloc(void* ptr, size_t new_size); +void lodepng_free(void* ptr); +#endif /*LODEPNG_COMPILE_ALLOCATORS*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code)\ +{\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/*Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code)\ +{\ + errorvar = code;\ + return code;\ +} + +/*Try the code, if it returns error, also return the error.*/ +#define CERROR_TRY_RETURN(call)\ +{\ + unsigned error = call;\ + if(error) return error;\ +} + +/*Set error var to the error code, and return from the void function.*/ +#define CERROR_RETURN(errorvar, code)\ +{\ + errorvar = code;\ + return;\ +} + +/* +About uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +/*dynamic vector of unsigned ints*/ +typedef struct uivector +{ + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) +{ + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + lodepng_free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_reserve(uivector* p, size_t allocsize) +{ + if(allocsize > p->allocsize) + { + size_t newsize = (allocsize > p->allocsize * 2) ? allocsize : (allocsize * 3 / 2); + void* data = lodepng_realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = (unsigned*)data; + } + else return 0; /*error: not enough memory*/ + } + return 1; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) +{ + if(!uivector_reserve(p, size * sizeof(unsigned))) return 0; + p->size = size; + return 1; /*success*/ +} + +/*resize and give all new elements the value*/ +static unsigned uivector_resizev(uivector* p, size_t size, unsigned value) +{ + size_t oldsize = p->size, i; + if(!uivector_resize(p, size)) return 0; + for(i = oldsize; i < size; ++i) p->data[i] = value; + return 1; +} + +static void uivector_init(uivector* p) +{ + p->data = NULL; + p->size = p->allocsize = 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) +{ + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector +{ + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_reserve(ucvector* p, size_t allocsize) +{ + if(allocsize > p->allocsize) + { + size_t newsize = (allocsize > p->allocsize * 2) ? allocsize : (allocsize * 3 / 2); + void* data = lodepng_realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = (unsigned char*)data; + } + else return 0; /*error: not enough memory*/ + } + return 1; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) +{ + if(!ucvector_reserve(p, size * sizeof(unsigned char))) return 0; + p->size = size; + return 1; /*success*/ +} + +#ifdef LODEPNG_COMPILE_PNG + +static void ucvector_cleanup(void* p) +{ + ((ucvector*)p)->size = ((ucvector*)p)->allocsize = 0; + lodepng_free(((ucvector*)p)->data); + ((ucvector*)p)->data = NULL; +} + +static void ucvector_init(ucvector* p) +{ + p->data = NULL; + p->size = p->allocsize = 0; +} +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ZLIB +/*you can both convert from vector to buffer&size and vica versa. If you use +init_buffer to take over a buffer and size, it is not needed to use cleanup*/ +static void ucvector_init_buffer(ucvector* p, unsigned char* buffer, size_t size) +{ + p->data = buffer; + p->allocsize = p->size = size; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#if (defined(LODEPNG_COMPILE_PNG) && defined(LODEPNG_COMPILE_ANCILLARY_CHUNKS)) || defined(LODEPNG_COMPILE_ENCODER) +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_push_back(ucvector* p, unsigned char c) +{ + if(!ucvector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned string_resize(char** out, size_t size) +{ + char* data = (char*)lodepng_realloc(*out, size + 1); + if(data) + { + data[size] = 0; /*null termination char*/ + *out = data; + } + return data != 0; +} + +/*init a {char*, size_t} pair for use as string*/ +static void string_init(char** out) +{ + *out = NULL; + string_resize(out, 0); +} + +/*free the above pair again*/ +static void string_cleanup(char** out) +{ + lodepng_free(*out); + *out = NULL; +} + +static void string_set(char** out, const char* in) +{ + size_t insize = strlen(in), i; + if(string_resize(out, insize)) + { + for(i = 0; i != insize; ++i) + { + (*out)[i] = in[i]; + } + } +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_read32bitInt(const unsigned char* buffer) +{ + return (unsigned)((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]); +} + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) +/*buffer must have at least 4 allocated bytes available*/ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) +{ + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + +#ifdef LODEPNG_COMPILE_ENCODER +static void lodepng_add32bitInt(ucvector* buffer, unsigned value) +{ + ucvector_resize(buffer, buffer->size + 4); /*todo: give error if resize failed*/ + lodepng_set32bitInt(&buffer->data[buffer->size - 4], value); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +/* returns negative value on error. This should be pure C compatible, so no fstat. */ +static long lodepng_filesize(const char* filename) +{ + FILE* file; + long size; + file = fopen(filename, "rb"); + if(!file) return -1; + + if(fseek(file, 0, SEEK_END) != 0) + { + fclose(file); + return -1; + } + + size = ftell(file); + /* It may give LONG_MAX as directory size, this is invalid for us. */ + if(size == LONG_MAX) size = -1; + + fclose(file); + return size; +} + +/* load file into buffer that already has the correct allocated size. Returns error code.*/ +static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) +{ + FILE* file; + size_t readsize; + file = fopen(filename, "rb"); + if(!file) return 78; + + readsize = fread(out, 1, size, file); + fclose(file); + + if (readsize != size) return 78; + return 0; +} + +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) +{ + long size = lodepng_filesize(filename); + if (size < 0) return 78; + *outsize = (size_t)size; + + *out = (unsigned char*)lodepng_malloc((size_t)size); + if(!(*out) && size > 0) return 83; /*the above malloc failed*/ + + return lodepng_buffer_file(*out, (size_t)size, filename); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) +{ + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + fwrite((char*)buffer , 1 , buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER +/*TODO: this ignores potential out of memory errors*/ +#define addBitToStream(/*size_t**/ bitpointer, /*ucvector**/ bitstream, /*unsigned char*/ bit)\ +{\ + /*add a new byte at the end*/\ + if(((*bitpointer) & 7) == 0) ucvector_push_back(bitstream, (unsigned char)0);\ + /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/\ + (bitstream->data[bitstream->size - 1]) |= (bit << ((*bitpointer) & 0x7));\ + ++(*bitpointer);\ +} + +static void addBitsToStream(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) +{ + size_t i; + for(i = 0; i != nbits; ++i) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> i) & 1)); +} + +static void addBitsToStreamReversed(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) +{ + size_t i; + for(i = 0; i != nbits; ++i) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> (nbits - 1 - i)) & 1)); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +#define READBIT(bitpointer, bitstream) ((bitstream[bitpointer >> 3] >> (bitpointer & 0x7)) & (unsigned char)1) + +static unsigned char readBitFromStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)(READBIT(*bitpointer, bitstream)); + ++(*bitpointer); + return result; +} + +static unsigned readBitsFromStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0, i; + for(i = 0; i != nbits; ++i) + { + result += ((unsigned)READBIT(*bitpointer, bitstream)) << i; + ++(*bitpointer); + } + return result; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + +/*the order in which "code length alphabet code lengths" are stored, out of this +the huffman tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree +{ + unsigned* tree2d; + unsigned* tree1d; + unsigned* lengths; /*the lengths of the codes of the 1d-tree*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ +} HuffmanTree; + +/*function used for debug purposes to draw the tree in ascii art with C++*/ +/* +static void HuffmanTree_draw(HuffmanTree* tree) +{ + std::cout << "tree. length: " << tree->numcodes << " maxbitlen: " << tree->maxbitlen << std::endl; + for(size_t i = 0; i != tree->tree1d.size; ++i) + { + if(tree->lengths.data[i]) + std::cout << i << " " << tree->tree1d.data[i] << " " << tree->lengths.data[i] << std::endl; + } + std::cout << std::endl; +}*/ + +static void HuffmanTree_init(HuffmanTree* tree) +{ + tree->tree2d = 0; + tree->tree1d = 0; + tree->lengths = 0; +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) +{ + lodepng_free(tree->tree2d); + lodepng_free(tree->tree1d); + lodepng_free(tree->lengths); +} + +/*the tree representation used by the decoder. return value is error*/ +static unsigned HuffmanTree_make2DTree(HuffmanTree* tree) +{ + unsigned nodefilled = 0; /*up to which node it is filled*/ + unsigned treepos = 0; /*position in the tree (1 of the numcodes columns)*/ + unsigned n, i; + + tree->tree2d = (unsigned*)lodepng_malloc(tree->numcodes * 2 * sizeof(unsigned)); + if(!tree->tree2d) return 83; /*alloc fail*/ + + /* + convert tree1d[] to tree2d[][]. In the 2D array, a value of 32767 means + uninited, a value >= numcodes is an address to another bit, a value < numcodes + is a code. The 2 rows are the 2 possible bit values (0 or 1), there are as + many columns as codes - 1. + A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + Here, the internal nodes are stored (what their 0 and 1 option point to). + There is only memory for such good tree currently, if there are more nodes + (due to too long length codes), error 55 will happen + */ + for(n = 0; n < tree->numcodes * 2; ++n) + { + tree->tree2d[n] = 32767; /*32767 here means the tree2d isn't filled there yet*/ + } + + for(n = 0; n < tree->numcodes; ++n) /*the codes*/ + { + for(i = 0; i != tree->lengths[n]; ++i) /*the bits for this code*/ + { + unsigned char bit = (unsigned char)((tree->tree1d[n] >> (tree->lengths[n] - i - 1)) & 1); + /*oversubscribed, see comment in lodepng_error_text*/ + if(treepos > 2147483647 || treepos + 2 > tree->numcodes) return 55; + if(tree->tree2d[2 * treepos + bit] == 32767) /*not yet filled in*/ + { + if(i + 1 == tree->lengths[n]) /*last bit*/ + { + tree->tree2d[2 * treepos + bit] = n; /*put the current code in it*/ + treepos = 0; + } + else + { + /*put address of the next step in here, first that address has to be found of course + (it's just nodefilled + 1)...*/ + ++nodefilled; + /*addresses encoded with numcodes added to it*/ + tree->tree2d[2 * treepos + bit] = nodefilled + tree->numcodes; + treepos = nodefilled; + } + } + else treepos = tree->tree2d[2 * treepos + bit] - tree->numcodes; + } + } + + for(n = 0; n < tree->numcodes * 2; ++n) + { + if(tree->tree2d[n] == 32767) tree->tree2d[n] = 0; /*remove possible remaining 32767's*/ + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) +{ + uivector blcount; + uivector nextcode; + unsigned error = 0; + unsigned bits, n; + + uivector_init(&blcount); + uivector_init(&nextcode); + + tree->tree1d = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); + if(!tree->tree1d) error = 83; /*alloc fail*/ + + if(!uivector_resizev(&blcount, tree->maxbitlen + 1, 0) + || !uivector_resizev(&nextcode, tree->maxbitlen + 1, 0)) + error = 83; /*alloc fail*/ + + if(!error) + { + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits != tree->numcodes; ++bits) ++blcount.data[tree->lengths[bits]]; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; ++bits) + { + nextcode.data[bits] = (nextcode.data[bits - 1] + blcount.data[bits - 1]) << 1; + } + /*step 3: generate all the codes*/ + for(n = 0; n != tree->numcodes; ++n) + { + if(tree->lengths[n] != 0) tree->tree1d[n] = nextcode.data[tree->lengths[n]]++; + } + } + + uivector_cleanup(&blcount); + uivector_cleanup(&nextcode); + + if(!error) return HuffmanTree_make2DTree(tree); + else return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) +{ + unsigned i; + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", +Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ + +/*chain node for boundary package merge*/ +typedef struct BPMNode +{ + int weight; /*the sum of all weights in this chain*/ + unsigned index; /*index of this leaf node (called "count" in the paper)*/ + struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ + int in_use; +} BPMNode; + +/*lists of chains*/ +typedef struct BPMLists +{ + /*memory pool*/ + unsigned memsize; + BPMNode* memory; + unsigned numfree; + unsigned nextfree; + BPMNode** freelist; + /*two heads of lookahead chains per list*/ + unsigned listsize; + BPMNode** chains0; + BPMNode** chains1; +} BPMLists; + +/*creates a new chain node with the given parameters, from the memory in the lists */ +static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) +{ + unsigned i; + BPMNode* result; + + /*memory full, so garbage collect*/ + if(lists->nextfree >= lists->numfree) + { + /*mark only those that are in use*/ + for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; + for(i = 0; i != lists->listsize; ++i) + { + BPMNode* node; + for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; + for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; + } + /*collect those that are free*/ + lists->numfree = 0; + for(i = 0; i != lists->memsize; ++i) + { + if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; + } + lists->nextfree = 0; + } + + result = lists->freelist[lists->nextfree++]; + result->weight = weight; + result->index = index; + result->tail = tail; + return result; +} + +/*sort the leaves with stable mergesort*/ +static void bpmnode_sort(BPMNode* leaves, size_t num) +{ + BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); + size_t width, counter = 0; + for(width = 1; width < num; width *= 2) + { + BPMNode* a = (counter & 1) ? mem : leaves; + BPMNode* b = (counter & 1) ? leaves : mem; + size_t p; + for(p = 0; p < num; p += 2 * width) + { + size_t q = (p + width > num) ? num : (p + width); + size_t r = (p + 2 * width > num) ? num : (p + 2 * width); + size_t i = p, j = q, k; + for(k = p; k < r; k++) + { + if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; + else b[k] = a[j++]; + } + } + counter++; + } + if(counter & 1) memcpy(leaves, mem, sizeof(*leaves) * num); + lodepng_free(mem); +} + +/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ +static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) +{ + unsigned lastindex = lists->chains1[c]->index; + + if(c == 0) + { + if(lastindex >= numpresent) return; + lists->chains0[c] = lists->chains1[c]; + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); + } + else + { + /*sum of the weights of the head nodes of the previous lookahead chains.*/ + int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; + lists->chains0[c] = lists->chains1[c]; + if(lastindex < numpresent && sum > leaves[lastindex].weight) + { + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); + return; + } + lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); + /*in the end we are only interested in the chain of the last list, so no + need to recurse if we're at the last one (this gives measurable speedup)*/ + if(num + 1 < (int)(2 * numpresent - 2)) + { + boundaryPM(lists, leaves, numpresent, c - 1, num); + boundaryPM(lists, leaves, numpresent, c - 1, num); + } + } +} + +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) +{ + unsigned error = 0; + unsigned i; + size_t numpresent = 0; /*number of symbols with non-zero frequency*/ + BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + if((1u << maxbitlen) < numcodes) return 80; /*error: represent all symbols*/ + + leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); + if(!leaves) return 83; /*alloc fail*/ + + for(i = 0; i != numcodes; ++i) + { + if(frequencies[i] > 0) + { + leaves[numpresent].weight = (int)frequencies[i]; + leaves[numpresent].index = i; + ++numpresent; + } + } + + for(i = 0; i != numcodes; ++i) lengths[i] = 0; + + /*ensure at least two present symbols. There should be at least one symbol + according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To + make these work as well ensure there are at least two symbols. The + Package-Merge code below also doesn't work correctly if there's only one + symbol, it'd give it the theoritical 0 bits but in practice zlib wants 1 bit*/ + if(numpresent == 0) + { + lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ + } + else if(numpresent == 1) + { + lengths[leaves[0].index] = 1; + lengths[leaves[0].index == 0 ? 1 : 0] = 1; + } + else + { + BPMLists lists; + BPMNode* node; + + bpmnode_sort(leaves, numpresent); + + lists.listsize = maxbitlen; + lists.memsize = 2 * maxbitlen * (maxbitlen + 1); + lists.nextfree = 0; + lists.numfree = lists.memsize; + lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); + lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); + lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ + + if(!error) + { + for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; + + bpmnode_create(&lists, leaves[0].weight, 1, 0); + bpmnode_create(&lists, leaves[1].weight, 2, 0); + + for(i = 0; i != lists.listsize; ++i) + { + lists.chains0[i] = &lists.memory[0]; + lists.chains1[i] = &lists.memory[1]; + } + + /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ + for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); + + for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) + { + for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; + } + } + + lodepng_free(lists.memory); + lodepng_free(lists.freelist); + lodepng_free(lists.chains0); + lodepng_free(lists.chains1); + } + + lodepng_free(leaves); + return error; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t mincodes, size_t numcodes, unsigned maxbitlen) +{ + unsigned error = 0; + while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ + tree->maxbitlen = maxbitlen; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->lengths = (unsigned*)lodepng_realloc(tree->lengths, numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + /*initialize all lengths to 0*/ + memset(tree->lengths, 0, numcodes * sizeof(unsigned)); + + error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); + if(!error) error = HuffmanTree_makeFromLengths2(tree); + return error; +} + +static unsigned HuffmanTree_getCode(const HuffmanTree* tree, unsigned index) +{ + return tree->tree1d[index]; +} + +static unsigned HuffmanTree_getLength(const HuffmanTree* tree, unsigned index) +{ + return tree->lengths[index]; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; ++i) bitlen[i] = 8; + for(i = 144; i <= 255; ++i) bitlen[i] = 9; + for(i = 256; i <= 279; ++i) bitlen[i] = 7; + for(i = 280; i <= 287; ++i) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code, or (unsigned)(-1) if error happened +inbitlength is the length of the complete buffer, in bits (so its byte length times 8) +*/ +static unsigned huffmanDecodeSymbol(const unsigned char* in, size_t* bp, + const HuffmanTree* codetree, size_t inbitlength) +{ + unsigned treepos = 0, ct; + for(;;) + { + if(*bp >= inbitlength) return (unsigned)(-1); /*error: end of input memory reached without endcode*/ + /* + decode the symbol from the tree. The "readBitFromStream" code is inlined in + the expression below because this is the biggest bottleneck while decoding + */ + ct = codetree->tree2d[(treepos << 1) + READBIT(*bp, in)]; + ++(*bp); + if(ct < codetree->numcodes) return ct; /*the symbol is decoded, return it*/ + else treepos = ct - codetree->numcodes; /*symbol not yet decoded, instead move tree position*/ + + if(treepos >= codetree->numcodes) return (unsigned)(-1); /*error: it appeared outside the codetree*/ + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static void getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) +{ + /*TODO: check for out of memory errors*/ + generateFixedLitLenTree(tree_ll); + generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + const unsigned char* in, size_t* bp, size_t inlength) +{ + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + size_t inbitlength = inlength * 8; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if((*bp) + 14 > (inlength << 3)) return 49; /*error: the bit pointer is or will go past the memory*/ + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBitsFromStream(bp, in, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBitsFromStream(bp, in, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBitsFromStream(bp, in, 4) + 4; + + if((*bp) + HCLEN * 3 > (inlength << 3)) return 50; /*error: the bit pointer is or will go past the memory*/ + + HuffmanTree_init(&tree_cl); + + while(!error) + { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + + bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) ERROR_BREAK(83 /*alloc fail*/); + + for(i = 0; i != NUM_CODE_LENGTH_CODES; ++i) + { + if(i < HCLEN) bitlen_cl[CLCL_ORDER[i]] = readBitsFromStream(bp, in, 3); + else bitlen_cl[CLCL_ORDER[i]] = 0; /*if not, it must stay 0*/ + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + for(i = 0; i != NUM_DEFLATE_CODE_SYMBOLS; ++i) bitlen_ll[i] = 0; + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen_d[i] = 0; + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while(i < HLIT + HDIST) + { + unsigned code = huffmanDecodeSymbol(in, bp, &tree_cl, inbitlength); + if(code <= 15) /*a length code*/ + { + if(i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + ++i; + } + else if(code == 16) /*repeat previous*/ + { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + if((*bp + 2) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + replength += readBitsFromStream(bp, in, 2); + + if(i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) + { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + ++i; + } + } + else if(code == 17) /*repeat "0" 3-10 times*/ + { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + if((*bp + 3) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + replength += readBitsFromStream(bp, in, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) + { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } + else if(code == 18) /*repeat "0" 11-138 times*/ + { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + if((*bp + 7) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + replength += readBitsFromStream(bp, in, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) + { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } + else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + if(code == (unsigned)(-1)) + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = (*bp) > inbitlength ? 10 : 11; + } + else error = 16; /*unexisting code, this can never happen*/ + break; + } + } + if(error) break; + + if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + lodepng_free(bitlen_cl); + lodepng_free(bitlen_ll); + lodepng_free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree*/ +static unsigned inflateHuffmanBlock(ucvector* out, const unsigned char* in, size_t* bp, + size_t* pos, size_t inlength, unsigned btype) +{ + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + size_t inbitlength = inlength * 8; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) getTreeInflateFixed(&tree_ll, &tree_d); + else if(btype == 2) error = getTreeInflateDynamic(&tree_ll, &tree_d, in, bp, inlength); + + while(!error) /*decode all symbols until end reached, breaks at end code*/ + { + /*code_ll is literal, length or end code*/ + unsigned code_ll = huffmanDecodeSymbol(in, bp, &tree_ll, inbitlength); + if(code_ll <= 255) /*literal symbol*/ + { + /*ucvector_push_back would do the same, but for some reason the two lines below run 10% faster*/ + if(!ucvector_resize(out, (*pos) + 1)) ERROR_BREAK(83 /*alloc fail*/); + out->data[*pos] = (unsigned char)code_ll; + ++(*pos); + } + else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ + { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, forward, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if((*bp + numextrabits_l) > inbitlength) ERROR_BREAK(51); /*error, bit pointer will jump past memory*/ + length += readBitsFromStream(bp, in, numextrabits_l); + + /*part 3: get distance code*/ + code_d = huffmanDecodeSymbol(in, bp, &tree_d, inbitlength); + if(code_d > 29) + { + if(code_ll == (unsigned)(-1)) /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = (*bp) > inlength * 8 ? 10 : 11; + } + else error = 18; /*error: invalid distance code (30-31 are never used)*/ + break; + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if((*bp + numextrabits_d) > inbitlength) ERROR_BREAK(51); /*error, bit pointer will jump past memory*/ + distance += readBitsFromStream(bp, in, numextrabits_d); + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = (*pos); + if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + + if(!ucvector_resize(out, (*pos) + length)) ERROR_BREAK(83 /*alloc fail*/); + if (distance < length) { + for(forward = 0; forward < length; ++forward) + { + out->data[(*pos)++] = out->data[backward++]; + } + } else { + memcpy(out->data + *pos, out->data + backward, length); + *pos += length; + } + } + else if(code_ll == 256) + { + break; /*end code, break the loop*/ + } + else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = ((*bp) > inlength * 8) ? 10 : 11; + break; + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, const unsigned char* in, size_t* bp, size_t* pos, size_t inlength) +{ + size_t p; + unsigned LEN, NLEN, n, error = 0; + + /*go to first boundary of byte*/ + while(((*bp) & 0x7) != 0) ++(*bp); + p = (*bp) / 8; /*byte position*/ + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(p + 4 >= inlength) return 52; /*error, bit pointer will jump past memory*/ + LEN = in[p] + 256u * in[p + 1]; p += 2; + NLEN = in[p] + 256u * in[p + 1]; p += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(LEN + NLEN != 65535) return 21; /*error: NLEN is not one's complement of LEN*/ + + if(!ucvector_resize(out, (*pos) + LEN)) return 83; /*alloc fail*/ + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(p + LEN > inlength) return 23; /*error: reading outside of in buffer*/ + for(n = 0; n < LEN; ++n) out->data[(*pos)++] = in[p++]; + + (*bp) = p * 8; + + return error; +} + +static unsigned lodepng_inflatev(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) +{ + /*bit pointer in the "in" data, current byte is bp >> 3, current bit is bp & 0x7 (from lsb to msb of the byte)*/ + size_t bp = 0; + unsigned BFINAL = 0; + size_t pos = 0; /*byte position in the out buffer*/ + unsigned error = 0; + + (void)settings; + + while(!BFINAL) + { + unsigned BTYPE; + if(bp + 2 >= insize * 8) return 52; /*error, bit pointer will jump past memory*/ + BFINAL = readBitFromStream(&bp, in); + BTYPE = 1u * readBitFromStream(&bp, in); + BTYPE += 2u * readBitFromStream(&bp, in); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, in, &bp, &pos, insize); /*no compression*/ + else error = inflateHuffmanBlock(out, in, &bp, &pos, insize, BTYPE); /*compression, BTYPE 01 or 10*/ + + if(error) return error; + } + + return error; +} + +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) +{ + unsigned error; + ucvector v; + ucvector_init_buffer(&v, *out, *outsize); + error = lodepng_inflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) +{ + if(settings->custom_inflate) + { + return settings->custom_inflate(out, outsize, in, insize, settings); + } + else + { + return lodepng_inflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*bitlen is the size in bits of the code*/ +static void addHuffmanSymbol(size_t* bp, ucvector* compressed, unsigned code, unsigned bitlen) +{ + addBitsToStreamReversed(bp, compressed, code, bitlen); +} + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) +{ + /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ + size_t left = 1; + size_t right = array_size - 1; + + while(left <= right) { + size_t mid = (left + right) >> 1; + if (array[mid] >= value) right = mid - 1; + else left = mid + 1; + } + if(left >= array_size || array[left] > value) left--; + return left; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) +{ + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + uivector_push_back(values, length_code + FIRST_LENGTH_CODE_INDEX); + uivector_push_back(values, extra_length); + uivector_push_back(values, dist_code); + uivector_push_back(values, extra_distance); +} + +/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 +bytes as input because 3 is the minimum match length for deflate*/ +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ + +typedef struct Hash +{ + int* head; /*hash value to head circular pos - can be outdated if went around window*/ + /*circular pos to prev circular pos*/ + unsigned short* chain; + int* val; /*circular pos to hash value*/ + + /*TODO: do this not only for zeros but for any repeated byte. However for PNG + it's always going to be the zeros that dominate, so not important for PNG*/ + int* headz; /*similar to head, but for chainz*/ + unsigned short* chainz; /*those with same amount of zeros*/ + unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ +} Hash; + +static unsigned hash_init(Hash* hash, unsigned windowsize) +{ + unsigned i; + hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); + hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); + hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); + hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) + { + return 83; /*alloc fail*/ + } + + /*initialize hash table*/ + for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; + for(i = 0; i != windowsize; ++i) hash->val[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ + + for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ + + return 0; +} + +static void hash_cleanup(Hash* hash) +{ + lodepng_free(hash->head); + lodepng_free(hash->val); + lodepng_free(hash->chain); + + lodepng_free(hash->zeros); + lodepng_free(hash->headz); + lodepng_free(hash->chainz); +} + + + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) +{ + unsigned result = 0; + if(pos + 2 < size) + { + /*A simple shift and xor hash is used. Since the data of PNGs is dominated + by zeroes due to the filters, a better hash does not have a significant + effect on speed in traversing the chain, and causes more time spend on + calculating the hash.*/ + result ^= (unsigned)(data[pos + 0] << 0u); + result ^= (unsigned)(data[pos + 1] << 4u); + result ^= (unsigned)(data[pos + 2] << 8u); + } else { + size_t amount, i; + if(pos >= size) return 0; + amount = size - pos; + for(i = 0; i != amount; ++i) result ^= (unsigned)(data[pos + i] << (i * 8u)); + } + return result & HASH_BIT_MASK; +} + +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) +{ + const unsigned char* start = data + pos; + const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; + if(end > data + size) end = data + size; + data = start; + while(data != end && *data == 0) ++data; + /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ + return (unsigned)(data - start); +} + +/*wpos = pos & (windowsize - 1)*/ +static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) +{ + hash->val[wpos] = (int)hashval; + if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; + hash->head[hashval] = wpos; + + hash->zeros[wpos] = numzeros; + if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; + hash->headz[numzeros] = wpos; +} + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowsize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, Hash* hash, + const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, + unsigned minmatch, unsigned nicematch, unsigned lazymatching) +{ + size_t pos; + unsigned i, error = 0; + /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ + unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8; + unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; + + unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ + unsigned numzeros = 0; + + unsigned offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length; + unsigned lazy = 0; + unsigned lazylength = 0, lazyoffset = 0; + unsigned hashval; + unsigned current_offset, current_length; + unsigned prev_offset; + const unsigned char *lastptr, *foreptr, *backptr; + unsigned hashpos; + + if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ + if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ + + if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; + + for(pos = inpos; pos < insize; ++pos) + { + size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ + unsigned chainlength = 0; + + hashval = getHash(in, insize, pos); + + if(usezeros && hashval == 0) + { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } + else + { + numzeros = 0; + } + + updateHashChain(hash, wpos, hashval, numzeros); + + /*the length and offset found for the current position*/ + length = 0; + offset = 0; + + hashpos = hash->chain[wpos]; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + /*search for the longest string*/ + prev_offset = 0; + for(;;) + { + if(chainlength++ >= maxchainlength) break; + current_offset = hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize; + + if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ + prev_offset = current_offset; + if(current_offset > 0) + { + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[pos - current_offset]; + + /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ + if(numzeros >= 3) + { + unsigned skip = hash->zeros[hashpos]; + if(skip > numzeros) skip = numzeros; + backptr += skip; + foreptr += skip; + } + + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ + { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + + if(current_length > length) + { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*jump out once a length of max length is found (speed gain). This also jumps + out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ + if(current_length >= nicematch) break; + } + } + + if(hashpos == hash->chain[hashpos]) break; + + if(numzeros >= 3 && length > numzeros) + { + hashpos = hash->chainz[hashpos]; + if(hash->zeros[hashpos] != numzeros) break; + } + else + { + hashpos = hash->chain[hashpos]; + /*outdated hash value, happens if particular value was not encountered in whole last window*/ + if(hash->val[hashpos] != (int)hashval) break; + } + } + + if(lazymatching) + { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) + { + lazy = 1; + lazylength = length; + lazyoffset = offset; + continue; /*try the next byte*/ + } + if(lazy) + { + lazy = 0; + if(pos == 0) ERROR_BREAK(81); + if(length > lazylength + 1) + { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); + } + else + { + length = lazylength; + offset = lazyoffset; + hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ + hash->headz[numzeros] = -1; /*idem*/ + --pos; + } + } + } + if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); + + /*encode it as length/distance pair or literal value*/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ + { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } + else if(length < minmatch || (length == 3 && offset > 4096)) + { + /*compensate for the fact that longer offsets have more extra bits, a + length of only 3 may be not worth it then*/ + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } + else + { + addLengthDistance(out, length, offset); + for(i = 1; i < length; ++i) + { + ++pos; + wpos = pos & (windowsize - 1); + hashval = getHash(in, insize, pos); + if(usezeros && hashval == 0) + { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } + else + { + numzeros = 0; + } + updateHashChain(hash, wpos, hashval, numzeros); + } + } + } /*end of the loop through each character of input*/ + + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) +{ + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, j, numdeflateblocks = (datasize + 65534) / 65535; + unsigned datapos = 0; + for(i = 0; i != numdeflateblocks; ++i) + { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1) << 1) + ((BTYPE & 2) << 1)); + ucvector_push_back(out, firstbyte); + + LEN = 65535; + if(datasize - datapos < 65535) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + ucvector_push_back(out, (unsigned char)(LEN & 255)); + ucvector_push_back(out, (unsigned char)(LEN >> 8)); + ucvector_push_back(out, (unsigned char)(NLEN & 255)); + ucvector_push_back(out, (unsigned char)(NLEN >> 8)); + + /*Decompressed data*/ + for(j = 0; j < 65535 && datapos < datasize; ++j) + { + ucvector_push_back(out, data[datapos++]); + } + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(size_t* bp, ucvector* out, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) +{ + size_t i = 0; + for(i = 0; i != lz77_encoded->size; ++i) + { + unsigned val = lz77_encoded->data[i]; + addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_ll, val), HuffmanTree_getLength(tree_ll, val)); + if(val > 256) /*for a length code, 3 more things have to be added*/ + { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + addBitsToStream(bp, out, length_extra_bits, n_length_extra_bits); + addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_d, distance_code), + HuffmanTree_getLength(tree_d, distance_code)); + addBitsToStream(bp, out, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash, + const unsigned char* data, size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) +{ + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lenghts used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + uivector frequencies_ll; /*frequency of lit,len codes*/ + uivector frequencies_d; /*frequency of dist codes*/ + uivector frequencies_cl; /*frequency of code length codes*/ + uivector bitlen_lld; /*lit,len,dist code lenghts (int bits), literally (without repeat codes).*/ + uivector bitlen_lld_e; /*bitlen_lld encoded with repeat codes (this is a rudemtary run length compression)*/ + /*bitlen_cl is the code length code lengths ("clcl"). The bit lengths of codes to represent tree_cl + (these are written as is in the file, it would be crazy to compress these using yet another huffman + tree that needs to be represented by yet another set of code lengths)*/ + uivector bitlen_cl; + size_t datasize = dataend - datapos; + + /* + Due to the huffman compression of huffman tree representations ("two levels"), there are some anologies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = final; + size_t numcodes_ll, numcodes_d, i; + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + uivector_init(&frequencies_ll); + uivector_init(&frequencies_d); + uivector_init(&frequencies_cl); + uivector_init(&bitlen_lld); + uivector_init(&bitlen_lld_e); + uivector_init(&bitlen_cl); + + /*This while loop never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) + { + if(settings->use_lz77) + { + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(error) break; + } + else + { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); + for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(83 /*alloc fail*/); + if(!uivector_resizev(&frequencies_d, 30, 0)) ERROR_BREAK(83 /*alloc fail*/); + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i != lz77_encoded.size; ++i) + { + unsigned symbol = lz77_encoded.data[i]; + ++frequencies_ll.data[symbol]; + if(symbol > 256) + { + unsigned dist = lz77_encoded.data[i + 2]; + ++frequencies_d.data[dist]; + i += 3; + } + } + frequencies_ll.data[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll.data, 257, frequencies_ll.size, 15); + if(error) break; + /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d.data, 2, frequencies_d.size, 15); + if(error) break; + + numcodes_ll = tree_ll.numcodes; if(numcodes_ll > 286) numcodes_ll = 286; + numcodes_d = tree_d.numcodes; if(numcodes_d > 30) numcodes_d = 30; + /*store the code lengths of both generated trees in bitlen_lld*/ + for(i = 0; i != numcodes_ll; ++i) uivector_push_back(&bitlen_lld, HuffmanTree_getLength(&tree_ll, (unsigned)i)); + for(i = 0; i != numcodes_d; ++i) uivector_push_back(&bitlen_lld, HuffmanTree_getLength(&tree_d, (unsigned)i)); + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i != (unsigned)bitlen_lld.size; ++i) + { + unsigned j = 0; /*amount of repititions*/ + while(i + j + 1 < (unsigned)bitlen_lld.size && bitlen_lld.data[i + j + 1] == bitlen_lld.data[i]) ++j; + + if(bitlen_lld.data[i] == 0 && j >= 2) /*repeat code for zeroes*/ + { + ++j; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ + { + uivector_push_back(&bitlen_lld_e, 17); + uivector_push_back(&bitlen_lld_e, j - 3); + } + else /*repeat code 18 supports max 138 zeroes*/ + { + if(j > 138) j = 138; + uivector_push_back(&bitlen_lld_e, 18); + uivector_push_back(&bitlen_lld_e, j - 11); + } + i += (j - 1); + } + else if(j >= 3) /*repeat code for value other than zero*/ + { + size_t k; + unsigned num = j / 6, rest = j % 6; + uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]); + for(k = 0; k < num; ++k) + { + uivector_push_back(&bitlen_lld_e, 16); + uivector_push_back(&bitlen_lld_e, 6 - 3); + } + if(rest >= 3) + { + uivector_push_back(&bitlen_lld_e, 16); + uivector_push_back(&bitlen_lld_e, rest - 3); + } + else j -= rest; + i += j; + } + else /*too short to benefit from repeat code*/ + { + uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]); + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + + if(!uivector_resizev(&frequencies_cl, NUM_CODE_LENGTH_CODES, 0)) ERROR_BREAK(83 /*alloc fail*/); + for(i = 0; i != bitlen_lld_e.size; ++i) + { + ++frequencies_cl.data[bitlen_lld_e.data[i]]; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e.data[i] >= 16) ++i; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl.data, + frequencies_cl.size, frequencies_cl.size, 7); + if(error) break; + + if(!uivector_resize(&bitlen_cl, tree_cl.numcodes)) ERROR_BREAK(83 /*alloc fail*/); + for(i = 0; i != tree_cl.numcodes; ++i) + { + /*lenghts of code length tree is in the order as specified by deflate*/ + bitlen_cl.data[i] = HuffmanTree_getLength(&tree_cl, CLCL_ORDER[i]); + } + while(bitlen_cl.data[bitlen_cl.size - 1] == 0 && bitlen_cl.size > 4) + { + /*remove zeros at the end, but minimum size must be 4*/ + if(!uivector_resize(&bitlen_cl, bitlen_cl.size - 1)) ERROR_BREAK(83 /*alloc fail*/); + } + if(error) break; + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lenghts of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + addBitToStream(bp, out, BFINAL); + addBitToStream(bp, out, 0); /*first bit of BTYPE "dynamic"*/ + addBitToStream(bp, out, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)bitlen_cl.size - 4; + /*trim zeroes for HCLEN. HLIT and HDIST were already trimmed at tree creation*/ + while(!bitlen_cl.data[HCLEN + 4 - 1] && HCLEN > 0) --HCLEN; + addBitsToStream(bp, out, HLIT, 5); + addBitsToStream(bp, out, HDIST, 5); + addBitsToStream(bp, out, HCLEN, 4); + + /*write the code lenghts of the code length alphabet*/ + for(i = 0; i != HCLEN + 4; ++i) addBitsToStream(bp, out, bitlen_cl.data[i], 3); + + /*write the lenghts of the lit/len AND the dist alphabet*/ + for(i = 0; i != bitlen_lld_e.size; ++i) + { + addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_cl, bitlen_lld_e.data[i]), + HuffmanTree_getLength(&tree_cl, bitlen_lld_e.data[i])); + /*extra bits of repeat codes*/ + if(bitlen_lld_e.data[i] == 16) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 2); + else if(bitlen_lld_e.data[i] == 17) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 3); + else if(bitlen_lld_e.data[i] == 18) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(bp, out, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(HuffmanTree_getLength(&tree_ll, 256) == 0) ERROR_BREAK(64); + + /*write the end code*/ + addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, 256), HuffmanTree_getLength(&tree_ll, 256)); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + uivector_cleanup(&frequencies_ll); + uivector_cleanup(&frequencies_d); + uivector_cleanup(&frequencies_cl); + uivector_cleanup(&bitlen_lld_e); + uivector_cleanup(&bitlen_lld); + uivector_cleanup(&bitlen_cl); + + return error; +} + +static unsigned deflateFixed(ucvector* out, size_t* bp, Hash* hash, + const unsigned char* data, + size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) +{ + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = final; + unsigned error = 0; + size_t i; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + generateFixedLitLenTree(&tree_ll); + generateFixedDistanceTree(&tree_d); + + addBitToStream(bp, out, BFINAL); + addBitToStream(bp, out, 1); /*first bit of BTYPE*/ + addBitToStream(bp, out, 0); /*second bit of BTYPE*/ + + if(settings->use_lz77) /*LZ77 encoded*/ + { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(!error) writeLZ77data(bp, out, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } + else /*no LZ77, but still will be Huffman compressed*/ + { + for(i = datapos; i < dataend; ++i) + { + addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, data[i]), HuffmanTree_getLength(&tree_ll, data[i])); + } + } + /*add END code*/ + if(!error) addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, 256), HuffmanTree_getLength(&tree_ll, 256)); + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) +{ + unsigned error = 0; + size_t i, blocksize, numdeflateblocks; + size_t bp = 0; /*the bit pointer*/ + Hash hash; + + if(settings->btype > 2) return 61; + else if(settings->btype == 0) return deflateNoCompression(out, in, insize); + else if(settings->btype == 1) blocksize = insize; + else /*if(settings->btype == 2)*/ + { + /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ + blocksize = insize / 8 + 8; + if(blocksize < 65536) blocksize = 65536; + if(blocksize > 262144) blocksize = 262144; + } + + numdeflateblocks = (insize + blocksize - 1) / blocksize; + if(numdeflateblocks == 0) numdeflateblocks = 1; + + error = hash_init(&hash, settings->windowsize); + if(error) return error; + + for(i = 0; i != numdeflateblocks && !error; ++i) + { + unsigned final = (i == numdeflateblocks - 1); + size_t start = i * blocksize; + size_t end = start + blocksize; + if(end > insize) end = insize; + + if(settings->btype == 1) error = deflateFixed(out, &bp, &hash, in, start, end, settings, final); + else if(settings->btype == 2) error = deflateDynamic(out, &bp, &hash, in, start, end, settings, final); + } + + hash_cleanup(&hash); + + return error; +} + +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) +{ + unsigned error; + ucvector v; + ucvector_init_buffer(&v, *out, *outsize); + error = lodepng_deflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) +{ + if(settings->custom_deflate) + { + return settings->custom_deflate(out, outsize, in, insize, settings); + } + else + { + return lodepng_deflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) +{ + unsigned s1 = adler & 0xffff; + unsigned s2 = (adler >> 16) & 0xffff; + + while(len > 0) + { + /*at least 5550 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5550 ? 5550 : len; + len -= amount; + while(amount > 0) + { + s1 += (*data++); + s2 += s1; + --amount; + } + s1 %= 65521; + s2 %= 65521; + } + + return (s2 << 16) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) +{ + return update_adler32(1L, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) +{ + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) + { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) + { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) + { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflate(out, outsize, in + 2, insize - 2, settings); + if(error) return error; + + if(!settings->ignore_adler32) + { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(*out, (unsigned)(*outsize)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) +{ + if(settings->custom_zlib) + { + return settings->custom_zlib(out, outsize, in, insize, settings); + } + else + { + return lodepng_zlib_decompress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) +{ + /*initially, *out must be NULL and outsize 0, if you just give some random *out + that's pointing to a non allocated buffer, this'll crash*/ + ucvector outv; + size_t i; + unsigned error; + unsigned char* deflatedata = 0; + size_t deflatesize = 0; + + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + /*ucvector-controlled version of the output buffer, for dynamic array*/ + ucvector_init_buffer(&outv, *out, *outsize); + + ucvector_push_back(&outv, (unsigned char)(CMFFLG >> 8)); + ucvector_push_back(&outv, (unsigned char)(CMFFLG & 255)); + + error = deflate(&deflatedata, &deflatesize, in, insize, settings); + + if(!error) + { + unsigned ADLER32 = adler32(in, (unsigned)insize); + for(i = 0; i != deflatesize; ++i) ucvector_push_back(&outv, deflatedata[i]); + lodepng_free(deflatedata); + lodepng_add32bitInt(&outv, ADLER32); + } + + *out = outv.data; + *outsize = outv.size; + + return error; +} + +/* compress using the default or custom zlib function */ +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) +{ + if(settings->custom_zlib) + { + return settings->custom_zlib(out, outsize, in, insize, settings); + } + else + { + return lodepng_zlib_compress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) +{ + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) +{ + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) +{ + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->use_lz77 = 1; + settings->windowsize = DEFAULT_WINDOWSIZE; + settings->minmatch = 3; + settings->nicematch = 128; + settings->lazymatching = 1; + + settings->custom_zlib = 0; + settings->custom_deflate = 0; + settings->custom_context = 0; +} + +const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; + + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) +{ + settings->ignore_adler32 = 0; + + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + +const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +#ifndef LODEPNG_NO_COMPILE_CRC +/* CRC polynomial: 0xedb88320 */ +static unsigned lodepng_crc32_table[256] = { + 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, + 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, + 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, + 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, + 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, + 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, + 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, + 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, + 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, + 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, + 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, + 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, + 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, + 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, + 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, + 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, + 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, + 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, + 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, + 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, + 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, + 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, + 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, + 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, + 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, + 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, + 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, + 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, + 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, + 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, + 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, + 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u +}; + +/*Return the CRC of the bytes buf[0..len-1].*/ +unsigned lodepng_crc32(const unsigned char* data, size_t length) +{ + unsigned r = 0xffffffffu; + size_t i; + for(i = 0; i < length; ++i) + { + r = lodepng_crc32_table[(r ^ data[i]) & 0xff] ^ (r >> 8); + } + return r ^ 0xffffffffu; +} +#else /* !LODEPNG_NO_COMPILE_CRC */ +unsigned lodepng_crc32(const unsigned char* data, size_t length); +#endif /* !LODEPNG_NO_COMPILE_CRC */ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing single bits and bytes from/to stream for LodePNG / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + ++(*bitpointer); + return result; +} + +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0; + size_t i; + for(i = nbits - 1; i < nbits; --i) + { + result += (unsigned)readBitFromReversedStream(bitpointer, bitstream) << i; + } + return result; +} + +#ifdef LODEPNG_COMPILE_DECODER +static void setBitOfReversedStream0(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream must be 0 for this to work*/ + if(bit) + { + /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/ + bitstream[(*bitpointer) >> 3] |= (bit << (7 - ((*bitpointer) & 0x7))); + } + ++(*bitpointer); +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3] &= (unsigned char)(~(1 << (7 - ((*bitpointer) & 0x7)))); + else bitstream[(*bitpointer) >> 3] |= (1 << (7 - ((*bitpointer) & 0x7))); + ++(*bitpointer); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_chunk_length(const unsigned char* chunk) +{ + return lodepng_read32bitInt(&chunk[0]); +} + +void lodepng_chunk_type(char type[5], const unsigned char* chunk) +{ + unsigned i; + for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) +{ + if(strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) +{ + return((chunk[4] & 32) != 0); +} + +unsigned char lodepng_chunk_private(const unsigned char* chunk) +{ + return((chunk[6] & 32) != 0); +} + +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) +{ + return((chunk[7] & 32) != 0); +} + +unsigned char* lodepng_chunk_data(unsigned char* chunk) +{ + return &chunk[8]; +} + +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) +{ + return &chunk[8]; +} + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) +{ + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else return 0; +} + +void lodepng_chunk_generate_crc(unsigned char* chunk) +{ + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_crc32(&chunk[4], length + 4); + lodepng_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* lodepng_chunk_next(unsigned char* chunk) +{ + unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk) +{ + unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk) +{ + unsigned i; + unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + unsigned char *chunk_start, *new_buffer; + size_t new_length = (*outlength) + total_chunk_length; + if(new_length < total_chunk_length || new_length < (*outlength)) return 77; /*integer overflow happened*/ + + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outlength) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; + + return 0; +} + +unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data) +{ + unsigned i; + unsigned char *chunk, *new_buffer; + size_t new_length = (*outlength) + length + 12; + if(new_length < length + 12 || new_length < (*outlength)) return 77; /*integer overflow happened*/ + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outlength) = new_length; + chunk = &(*out)[(*outlength) - length - 12]; + + /*1: length*/ + lodepng_set32bitInt(chunk, (unsigned)length); + + /*2: chunk name (4 letters)*/ + chunk[4] = (unsigned char)type[0]; + chunk[5] = (unsigned char)type[1]; + chunk[6] = (unsigned char)type[2]; + chunk[7] = (unsigned char)type[3]; + + /*3: the data*/ + for(i = 0; i != length; ++i) chunk[8 + i] = data[i]; + + /*4: CRC (of the chunkname characters and the data)*/ + lodepng_chunk_generate_crc(chunk); + + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types and such / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*return type is a LodePNG error code*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) /*bd = bitdepth*/ +{ + switch(colortype) + { + case 0: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; /*grey*/ + case 2: if(!( bd == 8 || bd == 16)) return 37; break; /*RGB*/ + case 3: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; /*palette*/ + case 4: if(!( bd == 8 || bd == 16)) return 37; break; /*grey + alpha*/ + case 6: if(!( bd == 8 || bd == 16)) return 37; break; /*RGBA*/ + default: return 31; + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(LodePNGColorType colortype) +{ + switch(colortype) + { + case 0: return 1; /*grey*/ + case 2: return 3; /*RGB*/ + case 3: return 1; /*palette*/ + case 4: return 2; /*grey + alpha*/ + case 6: return 4; /*RGBA*/ + } + return 0; /*unexisting color type*/ +} + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) +{ + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void lodepng_color_mode_init(LodePNGColorMode* info) +{ + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +void lodepng_color_mode_cleanup(LodePNGColorMode* info) +{ + lodepng_palette_clear(info); +} + +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) +{ + size_t i; + lodepng_color_mode_cleanup(dest); + *dest = *source; + if(source->palette) + { + dest->palette = (unsigned char*)lodepng_malloc(1024); + if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ + for(i = 0; i != source->palettesize * 4; ++i) dest->palette[i] = source->palette[i]; + } + return 0; +} + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) +{ + size_t i; + if(a->colortype != b->colortype) return 0; + if(a->bitdepth != b->bitdepth) return 0; + if(a->key_defined != b->key_defined) return 0; + if(a->key_defined) + { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + /*if one of the palette sizes is 0, then we consider it to be the same as the + other: it means that e.g. the palette was not given by the user and should be + considered the same as the palette inside the PNG.*/ + if(1/*a->palettesize != 0 && b->palettesize != 0*/) { + if(a->palettesize != b->palettesize) return 0; + for(i = 0; i != a->palettesize * 4; ++i) + { + if(a->palette[i] != b->palette[i]) return 0; + } + } + return 1; +} + +void lodepng_palette_clear(LodePNGColorMode* info) +{ + if(info->palette) lodepng_free(info->palette); + info->palette = 0; + info->palettesize = 0; +} + +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + unsigned char* data; + /*the same resize technique as C++ std::vectors is used, and here it's made so that for a palette with + the max of 256 colors, it'll have the exact alloc size*/ + if(!info->palette) /*allocate palette if empty*/ + { + /*room for 256 colors with 4 bytes each*/ + data = (unsigned char*)lodepng_realloc(info->palette, 1024); + if(!data) return 83; /*alloc fail*/ + else info->palette = data; + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + ++info->palettesize; + return 0; +} + +unsigned lodepng_get_bpp(const LodePNGColorMode* info) +{ + /*calculate bits per pixel out of colortype and bitdepth*/ + return lodepng_get_bpp_lct(info->colortype, info->bitdepth); +} + +unsigned lodepng_get_channels(const LodePNGColorMode* info) +{ + return getNumColorChannels(info->colortype); +} + +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) +{ + return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; +} + +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) +{ + return (info->colortype & 4) != 0; /*4 or 6*/ +} + +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) +{ + return info->colortype == LCT_PALETTE; +} + +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) +{ + size_t i; + for(i = 0; i != info->palettesize; ++i) + { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) +{ + return info->key_defined + || lodepng_is_alpha_type(info) + || lodepng_has_palette_alpha(info); +} + +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) +{ + /*will not overflow for any color type if roughly w * h < 268435455*/ + size_t bpp = lodepng_get_bpp(color); + size_t n = w * h; + return ((n / 8) * bpp) + ((n & 7) * bpp + 7) / 8; +} + +size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) +{ + /*will not overflow for any color type if roughly w * h < 268435455*/ + size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); + size_t n = w * h; + return ((n / 8) * bpp) + ((n & 7) * bpp + 7) / 8; +} + + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_DECODER +/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer*/ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, const LodePNGColorMode* color) +{ + /*will not overflow for any color type if roughly w * h < 268435455*/ + size_t bpp = lodepng_get_bpp(color); + size_t line = ((w / 8) * bpp) + ((w & 7) * bpp + 7) / 8; + return h * line; +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static void LodePNGUnknownChunks_init(LodePNGInfo* info) +{ + unsigned i; + for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; + for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; +} + +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) +{ + unsigned i; + for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); +} + +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) +{ + unsigned i; + + LodePNGUnknownChunks_cleanup(dest); + + for(i = 0; i != 3; ++i) + { + size_t j; + dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; + dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); + if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ + for(j = 0; j < src->unknown_chunks_size[i]; ++j) + { + dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; + } + } + + return 0; +} + +/******************************************************************************/ + +static void LodePNGText_init(LodePNGInfo* info) +{ + info->text_num = 0; + info->text_keys = NULL; + info->text_strings = NULL; +} + +static void LodePNGText_cleanup(LodePNGInfo* info) +{ + size_t i; + for(i = 0; i != info->text_num; ++i) + { + string_cleanup(&info->text_keys[i]); + string_cleanup(&info->text_strings[i]); + } + lodepng_free(info->text_keys); + lodepng_free(info->text_strings); +} + +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) +{ + size_t i = 0; + dest->text_keys = 0; + dest->text_strings = 0; + dest->text_num = 0; + for(i = 0; i != source->text_num; ++i) + { + CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); + } + return 0; +} + +void lodepng_clear_text(LodePNGInfo* info) +{ + LodePNGText_cleanup(info); +} + +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) +{ + char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); + if(!new_keys || !new_strings) + { + lodepng_free(new_keys); + lodepng_free(new_strings); + return 83; /*alloc fail*/ + } + + ++info->text_num; + info->text_keys = new_keys; + info->text_strings = new_strings; + + string_init(&info->text_keys[info->text_num - 1]); + string_set(&info->text_keys[info->text_num - 1], key); + + string_init(&info->text_strings[info->text_num - 1]); + string_set(&info->text_strings[info->text_num - 1], str); + + return 0; +} + +/******************************************************************************/ + +static void LodePNGIText_init(LodePNGInfo* info) +{ + info->itext_num = 0; + info->itext_keys = NULL; + info->itext_langtags = NULL; + info->itext_transkeys = NULL; + info->itext_strings = NULL; +} + +static void LodePNGIText_cleanup(LodePNGInfo* info) +{ + size_t i; + for(i = 0; i != info->itext_num; ++i) + { + string_cleanup(&info->itext_keys[i]); + string_cleanup(&info->itext_langtags[i]); + string_cleanup(&info->itext_transkeys[i]); + string_cleanup(&info->itext_strings[i]); + } + lodepng_free(info->itext_keys); + lodepng_free(info->itext_langtags); + lodepng_free(info->itext_transkeys); + lodepng_free(info->itext_strings); +} + +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) +{ + size_t i = 0; + dest->itext_keys = 0; + dest->itext_langtags = 0; + dest->itext_transkeys = 0; + dest->itext_strings = 0; + dest->itext_num = 0; + for(i = 0; i != source->itext_num; ++i) + { + CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], + source->itext_transkeys[i], source->itext_strings[i])); + } + return 0; +} + +void lodepng_clear_itext(LodePNGInfo* info) +{ + LodePNGIText_cleanup(info); +} + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str) +{ + char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); + char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); + char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) + { + lodepng_free(new_keys); + lodepng_free(new_langtags); + lodepng_free(new_transkeys); + lodepng_free(new_strings); + return 83; /*alloc fail*/ + } + + ++info->itext_num; + info->itext_keys = new_keys; + info->itext_langtags = new_langtags; + info->itext_transkeys = new_transkeys; + info->itext_strings = new_strings; + + string_init(&info->itext_keys[info->itext_num - 1]); + string_set(&info->itext_keys[info->itext_num - 1], key); + + string_init(&info->itext_langtags[info->itext_num - 1]); + string_set(&info->itext_langtags[info->itext_num - 1], langtag); + + string_init(&info->itext_transkeys[info->itext_num - 1]); + string_set(&info->itext_transkeys[info->itext_num - 1], transkey); + + string_init(&info->itext_strings[info->itext_num - 1]); + string_set(&info->itext_strings[info->itext_num - 1], str); + + return 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void lodepng_info_init(LodePNGInfo* info) +{ + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNGText_init(info); + LodePNGIText_init(info); + + info->time_defined = 0; + info->phys_defined = 0; + + LodePNGUnknownChunks_init(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void lodepng_info_cleanup(LodePNGInfo* info) +{ + lodepng_color_mode_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNGText_cleanup(info); + LodePNGIText_cleanup(info); + + LodePNGUnknownChunks_cleanup(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) +{ + lodepng_info_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->color); + CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); + CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); + + LodePNGUnknownChunks_init(dest); + CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + return 0; +} + +void lodepng_info_swap(LodePNGInfo* a, LodePNGInfo* b) +{ + LodePNGInfo temp = *a; + *a = *b; + *b = temp; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) +{ + unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index & m; + in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ + in = in << (bits * (m - p)); + if(p == 0) out[index * bits / 8] = in; + else out[index * bits / 8] |= in; +} + +typedef struct ColorTree ColorTree; + +/* +One node of a color tree +This is the data structure used to count the number of unique colors and to get a palette +index for a color. It's like an octree, but because the alpha channel is used too, each +node has 16 instead of 8 children. +*/ +struct ColorTree +{ + ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ + int index; /*the payload. Only has a meaningful value if this is in the last level*/ +}; + +static void color_tree_init(ColorTree* tree) +{ + int i; + for(i = 0; i != 16; ++i) tree->children[i] = 0; + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) +{ + int i; + for(i = 0; i != 16; ++i) + { + if(tree->children[i]) + { + color_tree_cleanup(tree->children[i]); + lodepng_free(tree->children[i]); + } + } +} + +/*returns -1 if color not present, its index otherwise*/ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + int bit = 0; + for(bit = 0; bit < 8; ++bit) + { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return color_tree_get(tree, r, g, b, a) >= 0; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*color is not allowed to already exist. +Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist")*/ +static void color_tree_add(ColorTree* tree, + unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) +{ + int bit; + for(bit = 0; bit < 8; ++bit) + { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) + { + tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = (int)index; +} + +int get_closest_color(const unsigned char *palette, int palette_size, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + int best_index = -1; + if (palette) { + int32_t best_score = INT32_MAX; + // todo should we hike alpha cost? + // todo this is super inefficient but we don't care + for(int index = 0; index < palette_size; index++) { + const unsigned char *rgba = &palette[index*4]; + int32_t r_diff = rgba[0]-(int32_t)r; + int32_t g_diff = rgba[1]-(int32_t)g; + int32_t b_diff = rgba[2]-(int32_t)b; + int32_t a_diff = rgba[3]-(int32_t)a; + int32_t score = r_diff*r_diff + g_diff*g_diff + b_diff*b_diff + a_diff*a_diff; + if (score < best_score) { + best_score = score; + best_index = index; + } + } + } + return best_index; +} + +/*put a pixel, given its RGBA color, into image of any color type*/ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, + const unsigned char *palette, int palette_size, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + if(mode->colortype == LCT_GREY) + { + unsigned char grey = r; /*((unsigned short)r + g + b) / 3*/; + if(mode->bitdepth == 8) out[i] = grey; + else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = grey; + else + { + /*take the most significant bits of grey*/ + grey = (grey >> (8 - mode->bitdepth)) & ((1 << mode->bitdepth) - 1); + addColorBits(out, i, mode->bitdepth, grey); + } + } + else if(mode->colortype == LCT_RGB) + { + if(mode->bitdepth == 8) + { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } + else + { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } + else if(mode->colortype == LCT_PALETTE) + { +#ifdef NEAREST_NEIGHBOR + int index = get_closest_color(palette, palette_size, r, g, b, a); +#else + int index = color_tree_get(tree, r, g, b, a); +#endif + if(index < 0) return 82; /*color not in palette*/ + if(mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, (unsigned)index); + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + unsigned char grey = r; /*((unsigned short)r + g + b) / 3*/; + if(mode->bitdepth == 8) + { + out[i * 2 + 0] = grey; + out[i * 2 + 1] = a; + } + else if(mode->bitdepth == 16) + { + out[i * 4 + 0] = out[i * 4 + 1] = grey; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } + else if(mode->colortype == LCT_RGBA) + { + if(mode->bitdepth == 8) + { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } + else + { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + + return 0; /*no error*/ +} + +/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ +static void rgba16ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, + unsigned short r, unsigned short g, unsigned short b, unsigned short a) +{ + if(mode->colortype == LCT_GREY) + { + unsigned short grey = r; /*((unsigned)r + g + b) / 3*/; + out[i * 2 + 0] = (grey >> 8) & 255; + out[i * 2 + 1] = grey & 255; + } + else if(mode->colortype == LCT_RGB) + { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + unsigned short grey = r; /*((unsigned)r + g + b) / 3*/; + out[i * 4 + 0] = (grey >> 8) & 255; + out[i * 4 + 1] = grey & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } + else if(mode->colortype == LCT_RGBA) + { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } +} + +/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ +static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, + unsigned char* b, unsigned char* a, + const unsigned char* in, size_t i, + const LodePNGColorMode* mode) +{ + if(mode->colortype == LCT_GREY) + { + if(mode->bitdepth == 8) + { + *r = *g = *b = in[i]; + if(mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } + else if(mode->bitdepth == 16) + { + *r = *g = *b = in[i * 2 + 0]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } + else + { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if(mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } + else if(mode->colortype == LCT_RGB) + { + if(mode->bitdepth == 8) + { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } + else + { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } + else if(mode->colortype == LCT_PALETTE) + { + unsigned index; + if(mode->bitdepth == 8) index = in[i]; + else + { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + + if(index >= mode->palettesize) + { + /*This is an error according to the PNG spec, but common PNG decoders make it black instead. + Done here too, slightly faster due to no error handling needed.*/ + *r = *g = *b = 0; + *a = 255; + } + else + { + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + if(mode->bitdepth == 8) + { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } + else + { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } + else if(mode->colortype == LCT_RGBA) + { + if(mode->bitdepth == 8) + { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } + else + { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } +} + +/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color +mode test cases, optimized to convert the colors much faster, when converting +to RGBA or RGB with 8 bit per cannel. buffer must be RGBA or RGB output with +enough memory, if has_alpha is true the output is RGBA. mode has the color mode +of the input buffer.*/ +static void getPixelColorsRGBA8(unsigned char* buffer, size_t numpixels, + unsigned has_alpha, const unsigned char* in, + const LodePNGColorMode* mode) +{ + unsigned num_channels = has_alpha ? 4 : 3; + size_t i; + if(mode->colortype == LCT_GREY) + { + if(mode->bitdepth == 8) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i]; + if(has_alpha) buffer[3] = mode->key_defined && in[i] == mode->key_r ? 0 : 255; + } + } + else if(mode->bitdepth == 16) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + if(has_alpha) buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } + else + { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + if(has_alpha) buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } + else if(mode->colortype == LCT_RGB) + { + if(mode->bitdepth == 8) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = in[i * 3 + 0]; + buffer[1] = in[i * 3 + 1]; + buffer[2] = in[i * 3 + 2]; + if(has_alpha) buffer[3] = mode->key_defined && buffer[0] == mode->key_r + && buffer[1]== mode->key_g && buffer[2] == mode->key_b ? 0 : 255; + } + } + else + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + if(has_alpha) buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } + else if(mode->colortype == LCT_PALETTE) + { + unsigned index; + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + if(mode->bitdepth == 8) index = in[i]; + else index = readBitsFromReversedStream(&j, in, mode->bitdepth); + + if(index >= mode->palettesize) + { + /*This is an error according to the PNG spec, but most PNG decoders make it black instead. + Done here too, slightly faster due to no error handling needed.*/ + buffer[0] = buffer[1] = buffer[2] = 0; + if(has_alpha) buffer[3] = 255; + } + else + { + buffer[0] = mode->palette[index * 4 + 0]; + buffer[1] = mode->palette[index * 4 + 1]; + buffer[2] = mode->palette[index * 4 + 2]; + if(has_alpha) buffer[3] = mode->palette[index * 4 + 3]; + } + } + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + if(mode->bitdepth == 8) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + if(has_alpha) buffer[3] = in[i * 2 + 1]; + } + } + else + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + if(has_alpha) buffer[3] = in[i * 4 + 2]; + } + } + } + else if(mode->colortype == LCT_RGBA) + { + if(mode->bitdepth == 8) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = in[i * 4 + 0]; + buffer[1] = in[i * 4 + 1]; + buffer[2] = in[i * 4 + 2]; + if(has_alpha) buffer[3] = in[i * 4 + 3]; + } + } + else + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + if(has_alpha) buffer[3] = in[i * 8 + 6]; + } + } + } +} + +/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with +given color type, but the given color type must be 16-bit itself.*/ +static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, + const unsigned char* in, size_t i, const LodePNGColorMode* mode) +{ + if(mode->colortype == LCT_GREY) + { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } + else if(mode->colortype == LCT_RGB) + { + *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; + if(mode->key_defined + && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; + } + else if(mode->colortype == LCT_RGBA) + { + *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; + } +} + +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h) +{ + size_t i; + ColorTree tree; + size_t numpixels = w * h; + + if(lodepng_color_mode_equal(mode_out, mode_in)) + { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + for(i = 0; i != numbytes; ++i) out[i] = in[i]; + return 0; + } + + const unsigned char* palette = 0; + size_t palette_size = 0; + if(mode_out->colortype == LCT_PALETTE) + { + palette_size = mode_out->palettesize; + palette = mode_out->palette; + size_t palsize = 1u << mode_out->bitdepth; + /*if the user specified output palette but did not give the values, assume + they want the values of the input color type (assuming that one is palette). + Note that we never create a new palette ourselves.*/ + if(palette_size == 0) + { + palette_size = mode_in->palettesize; + palette = mode_in->palette; + } + if(palette_size < palsize) palsize = palette_size; + color_tree_init(&tree); + for(i = 0; i != palsize; ++i) + { + const unsigned char* p = &palette[i * 4]; + color_tree_add(&tree, p[0], p[1], p[2], p[3], i); + } + } + + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) + { + for(i = 0; i != numpixels; ++i) + { + unsigned short r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + rgba16ToPixel(out, i, mode_out, r, g, b, a); + } + } + else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) + { + getPixelColorsRGBA8(out, numpixels, 1, in, mode_in); + } + else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) + { + getPixelColorsRGBA8(out, numpixels, 0, in, mode_in); + } + else + { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + CERROR_TRY_RETURN(rgba8ToPixel(out, i, mode_out, &tree, palette, palette_size, r, g, b, a)); + } + } + + if(mode_out->colortype == LCT_PALETTE) + { + color_tree_cleanup(&tree); + } + + return 0; /*no error*/ +} + +#ifdef LODEPNG_COMPILE_ENCODER + +void lodepng_color_profile_init(LodePNGColorProfile* profile) +{ + profile->colored = 0; + profile->key = 0; + profile->alpha = 0; + profile->key_r = profile->key_g = profile->key_b = 0; + profile->numcolors = 0; + profile->bits = 1; +} + +/*function used for debug purposes with C++*/ +/*void printColorProfile(LodePNGColorProfile* p) +{ + std::cout << "colored: " << (int)p->colored << ", "; + std::cout << "key: " << (int)p->key << ", "; + std::cout << "key_r: " << (int)p->key_r << ", "; + std::cout << "key_g: " << (int)p->key_g << ", "; + std::cout << "key_b: " << (int)p->key_b << ", "; + std::cout << "alpha: " << (int)p->alpha << ", "; + std::cout << "numcolors: " << (int)p->numcolors << ", "; + std::cout << "bits: " << (int)p->bits << std::endl; +}*/ + +/*Returns how many bits needed to represent given value (max 8 bit)*/ +static unsigned getValueRequiredBits(unsigned char value) +{ + if(value == 0 || value == 255) return 1; + /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ + if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; + return 8; +} + +/*profile must already have been inited with mode. +It's ok to set some parameters of profile to done already.*/ +unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, + const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* mode) +{ + unsigned error = 0; + size_t i; + ColorTree tree; + size_t numpixels = w * h; + + unsigned colored_done = lodepng_is_greyscale_type(mode) ? 1 : 0; + unsigned alpha_done = lodepng_can_have_alpha(mode) ? 0 : 1; + unsigned numcolors_done = 0; + unsigned bpp = lodepng_get_bpp(mode); + unsigned bits_done = bpp == 1 ? 1 : 0; + unsigned maxnumcolors = 257; + unsigned sixteen = 0; + if(bpp <= 8) maxnumcolors = bpp == 1 ? 2 : (bpp == 2 ? 4 : (bpp == 4 ? 16 : 256)); + + color_tree_init(&tree); + + /*Check if the 16-bit input is truly 16-bit*/ + if(mode->bitdepth == 16) + { + unsigned short r, g, b, a; + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode); + if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || + (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ + { + sixteen = 1; + break; + } + } + } + + if(sixteen) + { + unsigned short r = 0, g = 0, b = 0, a = 0; + profile->bits = 16; + bits_done = numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ + + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode); + + if(!colored_done && (r != g || r != b)) + { + profile->colored = 1; + colored_done = 1; + } + + if(!alpha_done) + { + unsigned matchkey = (r == profile->key_r && g == profile->key_g && b == profile->key_b); + if(a != 65535 && (a != 0 || (profile->key && !matchkey))) + { + profile->alpha = 1; + alpha_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + else if(a == 0 && !profile->alpha && !profile->key) + { + profile->key = 1; + profile->key_r = r; + profile->key_g = g; + profile->key_b = b; + } + else if(a == 65535 && profile->key && matchkey) + { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + profile->alpha = 1; + alpha_done = 1; + } + } + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(profile->key && !profile->alpha) + { + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode); + if(a != 0 && r == profile->key_r && g == profile->key_g && b == profile->key_b) + { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + profile->alpha = 1; + alpha_done = 1; + } + } + } + } + else /* < 16-bit */ + { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode); + + if(!bits_done && profile->bits < 8) + { + /*only r is checked, < 8 bits is only relevant for greyscale*/ + unsigned bits = getValueRequiredBits(r); + if(bits > profile->bits) profile->bits = bits; + } + bits_done = (profile->bits >= bpp); + + if(!colored_done && (r != g || r != b)) + { + profile->colored = 1; + colored_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ + } + + if(!alpha_done) + { + unsigned matchkey = (r == profile->key_r && g == profile->key_g && b == profile->key_b); + if(a != 255 && (a != 0 || (profile->key && !matchkey))) + { + profile->alpha = 1; + alpha_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + else if(a == 0 && !profile->alpha && !profile->key) + { + profile->key = 1; + profile->key_r = r; + profile->key_g = g; + profile->key_b = b; + } + else if(a == 255 && profile->key && matchkey) + { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + profile->alpha = 1; + alpha_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + + if(!numcolors_done) + { + if(!color_tree_has(&tree, r, g, b, a)) + { + color_tree_add(&tree, r, g, b, a, profile->numcolors); + if(profile->numcolors < 256) + { + unsigned char* p = profile->palette; + unsigned n = profile->numcolors; + p[n * 4 + 0] = r; + p[n * 4 + 1] = g; + p[n * 4 + 2] = b; + p[n * 4 + 3] = a; + } + ++profile->numcolors; + numcolors_done = profile->numcolors >= maxnumcolors; + } + } + + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(profile->key && !profile->alpha) + { + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode); + if(a != 0 && r == profile->key_r && g == profile->key_g && b == profile->key_b) + { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + profile->alpha = 1; + alpha_done = 1; + } + } + } + + /*make the profile's key always 16-bit for consistency - repeat each byte twice*/ + profile->key_r += (profile->key_r << 8); + profile->key_g += (profile->key_g << 8); + profile->key_b += (profile->key_b << 8); + } + + color_tree_cleanup(&tree); + return error; +} + +/*Automatically chooses color type that gives smallest amount of bits in the +output image, e.g. grey if there are only greyscale pixels, palette if there +are less than 256 colors, ... +Updates values of mode with a potentially smaller color model. mode_out should +contain the user chosen color model, but will be overwritten with the new chosen one.*/ +unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in) +{ + LodePNGColorProfile prof; + unsigned error = 0; + unsigned i, n, palettebits, grey_ok, palette_ok; + + lodepng_color_profile_init(&prof); + error = lodepng_get_color_profile(&prof, image, w, h, mode_in); + if(error) return error; + mode_out->key_defined = 0; + + if(prof.key && w * h <= 16) + { + prof.alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ + if(prof.bits < 8) prof.bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + grey_ok = !prof.colored && !prof.alpha; /*grey without alpha, with potentially low bits*/ + n = prof.numcolors; + palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); + palette_ok = n <= 256 && (n * 2 < w * h) && prof.bits <= 8; + if(w * h < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ + if(grey_ok && prof.bits <= palettebits) palette_ok = 0; /*grey is less overhead*/ + + if(palette_ok) + { + unsigned char* p = prof.palette; + lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ + for(i = 0; i != prof.numcolors; ++i) + { + error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); + if(error) break; + } + + mode_out->colortype = LCT_PALETTE; + mode_out->bitdepth = palettebits; + + if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize + && mode_in->bitdepth == mode_out->bitdepth) + { + /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ + lodepng_color_mode_cleanup(mode_out); + lodepng_color_mode_copy(mode_out, mode_in); + } + } + else /*8-bit or 16-bit per channel*/ + { + mode_out->bitdepth = prof.bits; + mode_out->colortype = prof.alpha ? (prof.colored ? LCT_RGBA : LCT_GREY_ALPHA) + : (prof.colored ? LCT_RGB : LCT_GREY); + + if(prof.key && !prof.alpha) + { + unsigned mask = (1u << mode_out->bitdepth) - 1u; /*profile always uses 16-bit, mask converts it*/ + mode_out->key_r = prof.key_r & mask; + mode_out->key_g = prof.key_g & mask; + mode_out->key_b = prof.key_b & mask; + mode_out->key_defined = 1; + } + } + + return error; +} + +#endif /* #ifdef LODEPNG_COMPILE_ENCODER */ + +/* +Paeth predicter, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) +{ + short pa = abs(b - c); + short pb = abs(a - c); + short pc = abs(a + b - c - c); + + if(pc < pa && pc < pb) return (unsigned char)c; + else if(pb < pa) return (unsigned char)b; + else return (unsigned char)a; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* +Outputs various dimensions and positions in the image related to the Adam7 reduced images. +passw: output containing the width of the 7 passes +passh: output containing the height of the 7 passes +filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes +padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines +passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images +w, h: width and height of non-interlaced image +bpp: bits per pixel +"padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte +*/ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) +{ + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i != 7; ++i) + { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i != 7; ++i) + { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1 + (passw[i] * bpp + 7) / 8) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7) / 8); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7) / 8; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, + const unsigned char* in, size_t insize) +{ + LodePNGInfo* info = &state->info_png; + if(insize == 0 || in == 0) + { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if(insize < 33) + { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) + { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if(lodepng_chunk_length(in + 8) != 13) + { + CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ + } + if(!lodepng_chunk_type_equals(in + 8, "IHDR")) + { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + *w = lodepng_read32bitInt(&in[16]); + *h = lodepng_read32bitInt(&in[20]); + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + if(*w == 0 || *h == 0) + { + CERROR_RETURN_ERROR(state->error, 93); + } + + if(!state->decoder.ignore_crc) + { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if(CRC != checksum) + { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } + + /*error: only compression method 0 is allowed in the specification*/ + if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + return state->error; +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) +{ + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) + { + case 0: + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + break; + case 1: + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if(precon) + { + for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; + } + else + { + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) + { + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1); + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) >> 1); + } + else + { + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + (recon[i - bytewidth] >> 1); + } + break; + case 4: + if(precon) + { + for(i = 0; i != bytewidth; ++i) + { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + for(i = bytewidth; i < length; ++i) + { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); + } + } + else + { + for(i = 0; i != bytewidth; ++i) + { + recon[i] = scanline[i]; + } + for(i = bytewidth; i < length; ++i) + { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[i - bytewidth]); + } + } + break; + default: return 36; /*error: unexisting filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7) / 8; + size_t linebytes = (w * bpp + 7) / 8; + + for(y = 0; y < h; ++y) + { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + + prevline = &out[outindex]; + } + + return 0; +} + +/* +in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. +out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +bpp: bits per pixel +out has the following size in bits: w * h * bpp. +in is possibly bigger due to padding bits between reduced images. +out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation +(because that's likely a little bit faster) +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) + { + for(i = 0; i != 7; ++i) + { + unsigned x, y, b; + size_t bytewidth = bpp / 8; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) + { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; ++b) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for(i = 0; i != 7; ++i) + { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) + { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; ++b) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + /*note that this function assumes the out buffer is completely 0, use setBitOfReversedStream otherwise*/ + setBitOfReversedStream0(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) +{ + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; ++y) + { + size_t x; + for(x = 0; x < olinebits; ++x) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, + unsigned w, unsigned h, const LodePNGInfo* info_png) +{ + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= posible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(info_png->interlace_method == 0) + { + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) + { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7) / 8) * 8, h); + } + /*we can immediately filter into the out buffer, no other steps needed*/ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } + else /*interlace_method is 1 (Adam7)*/ + { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i != 7; ++i) + { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) + { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7) / 8) * 8, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return 0; +} + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) +{ + unsigned pos = 0, i; + if(color->palette) lodepng_free(color->palette); + color->palettesize = chunkLength / 3; + color->palette = (unsigned char*)lodepng_malloc(4 * color->palettesize); + if(!color->palette && color->palettesize) + { + color->palettesize = 0; + return 83; /*alloc fail*/ + } + if(color->palettesize > 256) return 38; /*error: palette too big*/ + + for(i = 0; i != color->palettesize; ++i) + { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) +{ + unsigned i; + if(color->colortype == LCT_PALETTE) + { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > color->palettesize) return 38; + + for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; + } + else if(color->colortype == LCT_GREY) + { + /*error: this chunk must be 2 bytes for greyscale image*/ + if(chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; + } + else if(color->colortype == LCT_RGB) + { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256u * data[0] + data[1]; + color->key_g = 256u * data[2] + data[3]; + color->key_b = 256u * data[4] + data[5]; + } + else return 42; /*error: tRNS chunk not allowed for other color models*/ + + return 0; /* OK */ +} + + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*background color chunk (bKGD)*/ +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + if(info->color.colortype == LCT_PALETTE) + { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) return 43; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = data[0]; + } + else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) + { + /*error: this chunk must be 2 bytes for greyscale image*/ + if(chunkLength != 2) return 44; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; + } + else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) + { + /*error: this chunk must be 6 bytes for greyscale image*/ + if(chunkLength != 6) return 45; + + info->background_defined = 1; + info->background_r = 256u * data[0] + data[1]; + info->background_g = 256u * data[2] + data[3]; + info->background_b = 256u * data[4] + data[5]; + } + + return 0; /* OK */ +} + +/*text chunk (tEXt)*/ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + unsigned error = 0; + char *key = 0, *str = 0; + unsigned i; + + while(!error) /*not really a while loop, only used to break on error*/ + { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) ++length; + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i != length; ++i) key[i] = (char)data[i]; + + string2_begin = length + 1; /*skip keyword null terminator*/ + + length = chunkLength < string2_begin ? 0 : chunkLength - string2_begin; + str = (char*)lodepng_malloc(length + 1); + if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ + + str[length] = 0; + for(i = 0; i != length; ++i) str[i] = (char)data[string2_begin + i]; + + error = lodepng_add_text(info, key, str); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*compressed text chunk (zTXt)*/ +static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) +{ + unsigned error = 0; + unsigned i; + + unsigned length, string2_begin; + char *key = 0; + ucvector decoded; + + ucvector_init(&decoded); + + while(!error) /*not really a while loop, only used to break on error*/ + { + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i != length; ++i) key[i] = (char)data[i]; + + if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + + length = chunkLength - string2_begin; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&decoded.data, &decoded.size, + (unsigned char*)(&data[string2_begin]), + length, zlibsettings); + if(error) break; + ucvector_push_back(&decoded, 0); + + error = lodepng_add_text(info, key, (char*)decoded.data); + + break; + } + + lodepng_free(key); + ucvector_cleanup(&decoded); + + return error; +} + +/*international text chunk (iTXt)*/ +static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) +{ + unsigned error = 0; + unsigned i; + + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + ucvector decoded; + ucvector_init(&decoded); + + while(!error) /*not really a while loop, only used to break on error*/ + { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i != length; ++i) key[i] = (char)data[i]; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty for the next 3 texts*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + langtag = (char*)lodepng_malloc(length + 1); + if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ + + langtag[length] = 0; + for(i = 0; i != length; ++i) langtag[i] = (char)data[begin + i]; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + transkey = (char*)lodepng_malloc(length + 1); + if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ + + transkey[length] = 0; + for(i = 0; i != length; ++i) transkey[i] = (char)data[begin + i]; + + /*read the actual text*/ + begin += length + 1; + + length = chunkLength < begin ? 0 : chunkLength - begin; + + if(compressed) + { + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&decoded.data, &decoded.size, + (unsigned char*)(&data[begin]), + length, zlibsettings); + if(error) break; + if(decoded.allocsize < decoded.size) decoded.allocsize = decoded.size; + ucvector_push_back(&decoded, 0); + } + else + { + if(!ucvector_resize(&decoded, length + 1)) CERROR_BREAK(error, 83 /*alloc fail*/); + + decoded.data[length] = 0; + for(i = 0; i != length; ++i) decoded.data[i] = data[begin + i]; + } + + error = lodepng_add_itext(info, key, langtag, transkey, (char*)decoded.data); + + break; + } + + lodepng_free(key); + lodepng_free(langtag); + lodepng_free(transkey); + ucvector_cleanup(&decoded); + + return error; +} + +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ + + info->time_defined = 1; + info->time.year = 256u * data[0] + data[1]; + info->time.month = data[2]; + info->time.day = data[3]; + info->time.hour = data[4]; + info->time.minute = data[5]; + info->time.second = data[6]; + + return 0; /* OK */ +} + +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ + + info->phys_defined = 1; + info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; + info->phys_unit = data[8]; + + return 0; /* OK */ +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) +{ + unsigned char IEND = 0; + const unsigned char* chunk; + size_t i; + ucvector idat; /*the data from idat chunks*/ + ucvector scanlines; + size_t predict; + size_t numpixels; + size_t outsize = 0; + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + /*provide some proper output values if error will happen*/ + *out = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if(state->error) return; + + numpixels = *w * *h; + + /*multiplication overflow*/ + if(*h != 0 && numpixels / *h != *w) CERROR_RETURN(state->error, 92); + /*multiplication overflow possible further below. Allows up to 2^31-1 pixel + bytes with 16-bit RGBA, the rest is room for filter bytes.*/ + if(numpixels > 268435455) CERROR_RETURN(state->error, 92); + + ucvector_init(&idat); + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND && !state->error) + { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + /*error: size of the in buffer too small to contain next chunk*/ + if((size_t)((chunk - in) + 12) > insize || chunk < in) CERROR_BREAK(state->error, 30); + + /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) CERROR_BREAK(state->error, 63); + + if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) + { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ + } + + data = lodepng_chunk_data_const(chunk); + + /*IDAT chunk, containing compressed image data*/ + if(lodepng_chunk_type_equals(chunk, "IDAT")) + { + size_t oldsize = idat.size; + if(!ucvector_resize(&idat, oldsize + chunkLength)) CERROR_BREAK(state->error, 83 /*alloc fail*/); + for(i = 0; i != chunkLength; ++i) idat.data[oldsize + i] = data[i]; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + /*IEND chunk*/ + else if(lodepng_chunk_type_equals(chunk, "IEND")) + { + IEND = 1; + } + /*palette chunk (PLTE)*/ + else if(lodepng_chunk_type_equals(chunk, "PLTE")) + { + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + /*palette transparency chunk (tRNS)*/ + else if(lodepng_chunk_type_equals(chunk, "tRNS")) + { + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if(state->error) break; + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + else if(lodepng_chunk_type_equals(chunk, "bKGD")) + { + state->error = readChunk_bKGD(&state->info_png, data, chunkLength); + if(state->error) break; + } + /*text chunk (tEXt)*/ + else if(lodepng_chunk_type_equals(chunk, "tEXt")) + { + if(state->decoder.read_text_chunks) + { + state->error = readChunk_tEXt(&state->info_png, data, chunkLength); + if(state->error) break; + } + } + /*compressed text chunk (zTXt)*/ + else if(lodepng_chunk_type_equals(chunk, "zTXt")) + { + if(state->decoder.read_text_chunks) + { + state->error = readChunk_zTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; + } + } + /*international text chunk (iTXt)*/ + else if(lodepng_chunk_type_equals(chunk, "iTXt")) + { + if(state->decoder.read_text_chunks) + { + state->error = readChunk_iTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; + } + } + else if(lodepng_chunk_type_equals(chunk, "tIME")) + { + state->error = readChunk_tIME(&state->info_png, data, chunkLength); + if(state->error) break; + } + else if(lodepng_chunk_type_equals(chunk, "pHYs")) + { + state->error = readChunk_pHYs(&state->info_png, data, chunkLength); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + else /*it's not an implemented chunk type, so ignore it: skip over the data*/ + { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(!lodepng_chunk_ancillary(chunk)) CERROR_BREAK(state->error, 69); + + unknown = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(state->decoder.remember_unknown_chunks) + { + state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], + &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ + { + if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ + } + + if(!IEND) chunk = lodepng_chunk_next_const(chunk); + } + + ucvector_init(&scanlines); + /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. + If the decompressed size does not match the prediction, the image must be corrupt.*/ + if(state->info_png.interlace_method == 0) + { + /*The extra *h is added because this are the filter bytes every scanline starts with*/ + predict = lodepng_get_raw_size_idat(*w, *h, &state->info_png.color) + *h; + } + else + { + /*Adam-7 interlaced: predicted size is the sum of the 7 sub-images sizes*/ + const LodePNGColorMode* color = &state->info_png.color; + predict = 0; + predict += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, color) + ((*h + 7) >> 3); + if(*w > 4) predict += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, color) + ((*h + 7) >> 3); + predict += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, color) + ((*h + 3) >> 3); + if(*w > 2) predict += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, color) + ((*h + 3) >> 2); + predict += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, color) + ((*h + 1) >> 2); + if(*w > 1) predict += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, color) + ((*h + 1) >> 1); + predict += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, color) + ((*h + 0) >> 1); + } + if(!state->error && !ucvector_reserve(&scanlines, predict)) state->error = 83; /*alloc fail*/ + if(!state->error) + { + state->error = zlib_decompress(&scanlines.data, &scanlines.size, idat.data, + idat.size, &state->decoder.zlibsettings); + if(!state->error && scanlines.size != predict) state->error = 91; /*decompressed size doesn't match prediction*/ + } + ucvector_cleanup(&idat); + + if(!state->error) + { + outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!*out) state->error = 83; /*alloc fail*/ + } + if(!state->error) + { + for(i = 0; i < outsize; i++) (*out)[i] = 0; + state->error = postProcessScanlines(*out, scanlines.data, *w, *h, &state->info_png); + } + ucvector_cleanup(&scanlines); +} + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) +{ + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if(state->error) return state->error; + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) + { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if(!state->decoder.color_convert) + { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if(state->error) return state->error; + } + } + else + { + /*color conversion needed; sort of copy of the data*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from greyscale input color type, to 8-bit greyscale or greyscale with alpha"*/ + if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) + && !(state->info_raw.bitdepth == 8)) + { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!(*out)) + { + state->error = 83; /*alloc fail*/ + } + else state->error = lodepng_convert(*out, data, &state->info_raw, + &state->info_png.color, *w, *h); + lodepng_free(data); + } + return state->error; +} + +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + error = lodepng_decode(out, w, h, &state, in, insize); + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) +{ + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); +} + +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) +{ + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer = 0; + size_t buffersize; + unsigned error; + error = lodepng_load_file(&buffer, &buffersize, filename); + if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) +{ + return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); +} + +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) +{ + return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) +{ + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->read_text_chunks = 1; + settings->remember_unknown_chunks = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignore_crc = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) + +void lodepng_state_init(LodePNGState* state) +{ +#ifdef LODEPNG_COMPILE_DECODER + lodepng_decoder_settings_init(&state->decoder); +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + lodepng_encoder_settings_init(&state->encoder); +#endif /*LODEPNG_COMPILE_ENCODER*/ + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; +} + +void lodepng_state_cleanup(LodePNGState* state) +{ + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} + +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) +{ + lodepng_state_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->info_raw); + lodepng_info_init(&dest->info_png); + dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; + dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; +} + +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*chunkName must be string of 4 characters*/ +static unsigned addChunk(ucvector* out, const char* chunkName, const unsigned char* data, size_t length) +{ + CERROR_TRY_RETURN(lodepng_chunk_create(&out->data, &out->size, (unsigned)length, chunkName, data)); + out->allocsize = out->size; /*fix the allocsize again*/ + return 0; +} + +static void writeSignature(ucvector* out) +{ + /*8 bytes PNG signature, aka the magic bytes*/ + ucvector_push_back(out, 137); + ucvector_push_back(out, 80); + ucvector_push_back(out, 78); + ucvector_push_back(out, 71); + ucvector_push_back(out, 13); + ucvector_push_back(out, 10); + ucvector_push_back(out, 26); + ucvector_push_back(out, 10); +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) +{ + unsigned error = 0; + ucvector header; + ucvector_init(&header); + + lodepng_add32bitInt(&header, w); /*width*/ + lodepng_add32bitInt(&header, h); /*height*/ + ucvector_push_back(&header, (unsigned char)bitdepth); /*bit depth*/ + ucvector_push_back(&header, (unsigned char)colortype); /*color type*/ + ucvector_push_back(&header, 0); /*compression method*/ + ucvector_push_back(&header, 0); /*filter method*/ + ucvector_push_back(&header, interlace_method); /*interlace method*/ + + error = addChunk(out, "IHDR", header.data, header.size); + ucvector_cleanup(&header); + + return error; +} + +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) +{ + unsigned error = 0; + size_t i; + ucvector PLTE; + ucvector_init(&PLTE); + for(i = 0; i != info->palettesize * 4; ++i) + { + /*add all channels except alpha channel*/ + if(i % 4 != 3) ucvector_push_back(&PLTE, info->palette[i]); + } + error = addChunk(out, "PLTE", PLTE.data, PLTE.size); + ucvector_cleanup(&PLTE); + + return error; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) +{ + unsigned error = 0; + size_t i; + ucvector tRNS; + ucvector_init(&tRNS); + if(info->colortype == LCT_PALETTE) + { + size_t amount = info->palettesize; + /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ + for(i = info->palettesize; i != 0; --i) + { + if(info->palette[4 * (i - 1) + 3] == 255) --amount; + else break; + } + /*add only alpha channel*/ + for(i = 0; i != amount; ++i) ucvector_push_back(&tRNS, info->palette[4 * i + 3]); + } + else if(info->colortype == LCT_GREY) + { + if(info->key_defined) + { + ucvector_push_back(&tRNS, (unsigned char)(info->key_r >> 8)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_r & 255)); + } + } + else if(info->colortype == LCT_RGB) + { + if(info->key_defined) + { + ucvector_push_back(&tRNS, (unsigned char)(info->key_r >> 8)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_r & 255)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_g >> 8)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_g & 255)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_b >> 8)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_b & 255)); + } + } + + error = addChunk(out, "tRNS", tRNS.data, tRNS.size); + ucvector_cleanup(&tRNS); + + return error; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNGCompressSettings* zlibsettings) +{ + ucvector zlibdata; + unsigned error = 0; + + /*compress with the Zlib compressor*/ + ucvector_init(&zlibdata); + error = zlib_compress(&zlibdata.data, &zlibdata.size, data, datasize, zlibsettings); + if(!error) error = addChunk(out, "IDAT", zlibdata.data, zlibdata.size); + ucvector_cleanup(&zlibdata); + + return error; +} + +static unsigned addChunk_IEND(ucvector* out) +{ + unsigned error = 0; + error = addChunk(out, "IEND", 0, 0); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) +{ + unsigned error = 0; + size_t i; + ucvector text; + ucvector_init(&text); + for(i = 0; keyword[i] != 0; ++i) ucvector_push_back(&text, (unsigned char)keyword[i]); + if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/ + ucvector_push_back(&text, 0); /*0 termination char*/ + for(i = 0; textstring[i] != 0; ++i) ucvector_push_back(&text, (unsigned char)textstring[i]); + error = addChunk(out, "tEXt", text.data, text.size); + ucvector_cleanup(&text); + + return error; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNGCompressSettings* zlibsettings) +{ + unsigned error = 0; + ucvector data, compressed; + size_t i, textsize = strlen(textstring); + + ucvector_init(&data); + ucvector_init(&compressed); + for(i = 0; keyword[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)keyword[i]); + if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/ + ucvector_push_back(&data, 0); /*0 termination char*/ + ucvector_push_back(&data, 0); /*compression method: 0*/ + + error = zlib_compress(&compressed.data, &compressed.size, + (unsigned char*)textstring, textsize, zlibsettings); + if(!error) + { + for(i = 0; i != compressed.size; ++i) ucvector_push_back(&data, compressed.data[i]); + error = addChunk(out, "zTXt", data.data, data.size); + } + + ucvector_cleanup(&compressed); + ucvector_cleanup(&data); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compressed, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) +{ + unsigned error = 0; + ucvector data; + size_t i, textsize = strlen(textstring); + + ucvector_init(&data); + + for(i = 0; keyword[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)keyword[i]); + if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/ + ucvector_push_back(&data, 0); /*null termination char*/ + ucvector_push_back(&data, compressed ? 1 : 0); /*compression flag*/ + ucvector_push_back(&data, 0); /*compression method*/ + for(i = 0; langtag[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)langtag[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + for(i = 0; transkey[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)transkey[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + + if(compressed) + { + ucvector compressed_data; + ucvector_init(&compressed_data); + error = zlib_compress(&compressed_data.data, &compressed_data.size, + (unsigned char*)textstring, textsize, zlibsettings); + if(!error) + { + for(i = 0; i != compressed_data.size; ++i) ucvector_push_back(&data, compressed_data.data[i]); + } + ucvector_cleanup(&compressed_data); + } + else /*not compressed*/ + { + for(i = 0; textstring[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)textstring[i]); + } + + if(!error) error = addChunk(out, "iTXt", data.data, data.size); + ucvector_cleanup(&data); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) +{ + unsigned error = 0; + ucvector bKGD; + ucvector_init(&bKGD); + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r >> 8)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_r & 255)); + } + else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r >> 8)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_r & 255)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_g >> 8)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_g & 255)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_b >> 8)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_b & 255)); + } + else if(info->color.colortype == LCT_PALETTE) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r & 255)); /*palette index*/ + } + + error = addChunk(out, "bKGD", bKGD.data, bKGD.size); + ucvector_cleanup(&bKGD); + + return error; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) +{ + unsigned error = 0; + unsigned char* data = (unsigned char*)lodepng_malloc(7); + if(!data) return 83; /*alloc fail*/ + data[0] = (unsigned char)(time->year >> 8); + data[1] = (unsigned char)(time->year & 255); + data[2] = (unsigned char)time->month; + data[3] = (unsigned char)time->day; + data[4] = (unsigned char)time->hour; + data[5] = (unsigned char)time->minute; + data[6] = (unsigned char)time->second; + error = addChunk(out, "tIME", data, 7); + lodepng_free(data); + return error; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) +{ + unsigned error = 0; + ucvector data; + ucvector_init(&data); + + lodepng_add32bitInt(&data, info->phys_x); + lodepng_add32bitInt(&data, info->phys_y); + ucvector_push_back(&data, info->phys_unit); + + error = addChunk(out, "pHYs", data.data, data.size); + ucvector_cleanup(&data); + + return error; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) +{ + size_t i; + switch(filterType) + { + case 0: /*None*/ + for(i = 0; i != length; ++i) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; + break; + case 2: /*Up*/ + if(prevline) + { + for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; + } + else + { + for(i = 0; i != length; ++i) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) + { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); + } + else + { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); + } + break; + case 4: /*Paeth*/ + if(prevline) + { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; ++i) + { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } + else + { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*unexisting filter type given*/ + } +} + +/* log2 approximation. A slight bit faster than std::log. */ +static float flog2(float f) +{ + float result = 0; + while(f > 32) { result += 4; f /= 16; } + while(f > 2) { ++result; f /= 2; } + return result + 1.442695f * (f * f * f / 3 - 3 * f * f / 2 + 3 * f - 1.83333f); +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* info, const LodePNGEncoderSettings* settings) +{ + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7) / 8, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = lodepng_get_bpp(info); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = (w * bpp + 7) / 8; + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7) / 8; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + LodePNGFilterStrategy strategy = settings->filter_strategy; + + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + + If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + heuristic is used. + */ + if(settings->filter_palette_zero && + (info->colortype == LCT_PALETTE || info->bitdepth < 8)) strategy = LFS_ZERO; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(strategy == LFS_ZERO) + { + for(y = 0; y != h; ++y) + { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + out[outindex] = 0; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, 0); + prevline = &in[inindex]; + } + } + else if(strategy == LFS_MINSUM) + { + /*adaptive filtering*/ + size_t sum[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned char type, bestType = 0; + + for(type = 0; type != 5; ++type) + { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) return 83; /*alloc fail*/ + } + + if(!error) + { + for(y = 0; y != h; ++y) + { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) + { + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + sum[type] = 0; + if(type == 0) + { + for(x = 0; x != linebytes; ++x) sum[type] += (unsigned char)(attempt[type][x]); + } + else + { + for(x = 0; x != linebytes; ++x) + { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + unsigned char s = attempt[type][x]; + sum[type] += s < 128 ? s : (255U - s); + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum[type] < smallest) + { + bestType = type; + smallest = sum[type]; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else if(strategy == LFS_ENTROPY) + { + float sum[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + float smallest = 0; + unsigned type, bestType = 0; + unsigned count[256]; + + for(type = 0; type != 5; ++type) + { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) return 83; /*alloc fail*/ + } + + for(y = 0; y != h; ++y) + { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) + { + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + for(x = 0; x != 256; ++x) count[x] = 0; + for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; + ++count[type]; /*the filter type itself is part of the scanline*/ + sum[type] = 0; + for(x = 0; x != 256; ++x) + { + float p = count[x] / (float)(linebytes + 1); + sum[type] += count[x] == 0 ? 0 : flog2(1 / p) * p; + } + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum[type] < smallest) + { + bestType = type; + smallest = sum[type]; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else if(strategy == LFS_PREDEFINED) + { + for(y = 0; y != h; ++y) + { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + unsigned char type = settings->predefined_filters[y]; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } + else if(strategy == LFS_BRUTE_FORCE) + { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNGCompressSettings zlibsettings = settings->zlibsettings; + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG + images only, so disable it*/ + zlibsettings.custom_zlib = 0; + zlibsettings.custom_deflate = 0; + for(type = 0; type != 5; ++type) + { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) return 83; /*alloc fail*/ + } + for(y = 0; y != h; ++y) /*try the 5 filter types*/ + { + for(type = 0; type != 5; ++type) + { + unsigned testsize = linebytes; + /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ + + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); + lodepng_free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) + { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else return 88; /* unknown filter strategy */ + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) +{ + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y != h; ++y) + { + size_t x; + for(x = 0; x < ilinebits; ++x) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); + } +} + +/* +in: non-interlaced image with size w*h +out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with + no padding bits between scanlines, but between reduced images so that each + reduced image starts at a byte. +bpp: bits per pixel +there are no padding bits, not between scanlines, not between reduced images +in has the following size in bits: w * h * bpp. +out is possibly bigger due to padding bits between reduced images +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) + { + for(i = 0; i != 7; ++i) + { + unsigned x, y, b; + size_t bytewidth = bpp / 8; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) + { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; ++b) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for(i = 0; i != 7; ++i) + { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) + { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; ++b) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + unsigned w, unsigned h, + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) +{ + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= posible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + unsigned error = 0; + + if(info_png->interlace_method == 0) + { + *outsize = h + (h * ((w * bpp + 7) / 8)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ + + if(!error) + { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) + { + unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7) / 8)); + if(!padded) error = 83; /*alloc fail*/ + if(!error) + { + addPaddingBits(padded, in, ((w * bpp + 7) / 8) * 8, w * bpp, h); + error = filter(*out, padded, w, h, &info_png->color, settings); + } + lodepng_free(padded); + } + else + { + /*we can immediately filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &info_png->color, settings); + } + } + } + else /*interlace_method is 1 (Adam7)*/ + { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned char* adam7; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out)) error = 83; /*alloc fail*/ + + adam7 = (unsigned char*)lodepng_malloc(passstart[7]); + if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ + + if(!error) + { + unsigned i; + + Adam7_interlace(adam7, in, w, h, bpp); + for(i = 0; i != 7; ++i) + { + if(bpp < 8) + { + unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); + if(!padded) ERROR_BREAK(83); /*alloc fail*/ + addPaddingBits(padded, &adam7[passstart[i]], + ((passw[i] * bpp + 7) / 8) * 8, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], padded, + passw[i], passh[i], &info_png->color, settings); + lodepng_free(padded); + } + else + { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &info_png->color, settings); + } + + if(error) break; + } + } + + lodepng_free(adam7); + } + + return error; +} + +/* +palette must have 4 * palettesize bytes allocated, and given in format RGBARGBARGBARGBA... +returns 0 if the palette is opaque, +returns 1 if the palette has a single color with alpha 0 ==> color key +returns 2 if the palette is semi-translucent. +*/ +static unsigned getPaletteTranslucency(const unsigned char* palette, size_t palettesize) +{ + size_t i; + unsigned key = 0; + unsigned r = 0, g = 0, b = 0; /*the value of the color with alpha 0, so long as color keying is possible*/ + for(i = 0; i != palettesize; ++i) + { + if(!key && palette[4 * i + 3] == 0) + { + r = palette[4 * i + 0]; g = palette[4 * i + 1]; b = palette[4 * i + 2]; + key = 1; + i = (size_t)(-1); /*restart from beginning, to detect earlier opaque colors with key's value*/ + } + else if(palette[4 * i + 3] != 255) return 2; + /*when key, no opaque RGB may have key's RGB*/ + else if(key && r == palette[i * 4 + 0] && g == palette[i * 4 + 1] && b == palette[i * 4 + 2]) return 2; + } + return key; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) +{ + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) + { + CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = lodepng_chunk_next(inchunk); + } + return 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state) +{ + LodePNGInfo info; + ucvector outv; + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + state->error = 0; + + lodepng_info_init(&info); + lodepng_info_copy(&info, &state->info_png); + + if((info.color.colortype == LCT_PALETTE || state->encoder.force_palette) + && (info.color.palettesize == 0 || info.color.palettesize > 256)) + { + state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ + return state->error; + } + + if(state->encoder.auto_convert) + { + state->error = lodepng_auto_choose_color(&info.color, image, w, h, &state->info_raw); + } + if(state->error) return state->error; + + if(state->encoder.zlibsettings.btype > 2) + { + CERROR_RETURN_ERROR(state->error, 61); /*error: unexisting btype*/ + } + if(state->info_png.interlace_method > 1) + { + CERROR_RETURN_ERROR(state->error, 71); /*error: unexisting interlace mode*/ + } + + state->error = checkColorValidity(info.color.colortype, info.color.bitdepth); + if(state->error) return state->error; /*error: unexisting color type given*/ + state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); + if(state->error) return state->error; /*error: unexisting color type given*/ + + if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) + { + unsigned char* converted; + size_t size = (w * h * lodepng_get_bpp(&info.color) + 7) / 8; + + converted = (unsigned char*)lodepng_malloc(size); + if(!converted && size) state->error = 83; /*alloc fail*/ + if(!state->error) + { + state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } + if(!state->error) preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); + lodepng_free(converted); + } + else preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); + + ucvector_init(&outv); + while(!state->error) /*while only executed once, to break on error*/ + { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + writeSignature(&outv); + /*IHDR*/ + addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks_data[0]) + { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*PLTE*/ + if(info.color.colortype == LCT_PALETTE) + { + addChunk_PLTE(&outv, &info.color); + } + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) + { + addChunk_PLTE(&outv, &info.color); + } + /*tRNS*/ + if(info.color.colortype == LCT_PALETTE && getPaletteTranslucency(info.color.palette, info.color.palettesize) != 0) + { + addChunk_tRNS(&outv, &info.color); + } + if((info.color.colortype == LCT_GREY || info.color.colortype == LCT_RGB) && info.color.key_defined) + { + addChunk_tRNS(&outv, &info.color); + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) addChunk_bKGD(&outv, &info); + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) addChunk_pHYs(&outv, &info); + + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks_data[1]) + { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) addChunk_tIME(&outv, &info.time); + /*tEXt and/or zTXt*/ + for(i = 0; i != info.text_num; ++i) + { + if(strlen(info.text_keys[i]) > 79) + { + state->error = 66; /*text chunk too large*/ + break; + } + if(strlen(info.text_keys[i]) < 1) + { + state->error = 67; /*text chunk too small*/ + break; + } + if(state->encoder.text_compression) + { + addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); + } + else + { + addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); + } + } + /*LodePNG version id in text chunk*/ + if(state->encoder.add_id) + { + unsigned alread_added_id_text = 0; + for(i = 0; i != info.text_num; ++i) + { + if(!strcmp(info.text_keys[i], "LodePNG")) + { + alread_added_id_text = 1; + break; + } + } + if(alread_added_id_text == 0) + { + addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + } + } + /*iTXt*/ + for(i = 0; i != info.itext_num; ++i) + { + if(strlen(info.itext_keys[i]) > 79) + { + state->error = 66; /*text chunk too large*/ + break; + } + if(strlen(info.itext_keys[i]) < 1) + { + state->error = 67; /*text chunk too small*/ + break; + } + addChunk_iTXt(&outv, state->encoder.text_compression, + info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], + &state->encoder.zlibsettings); + } + + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks_data[2]) + { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + addChunk_IEND(&outv); + + break; /*this isn't really a while loop; no error happened so break out now!*/ + } + + lodepng_info_cleanup(&info); + lodepng_free(data); + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; + + return state->error; +} + +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + state.info_png.color.colortype = colortype; + state.info_png.color.bitdepth = bitdepth; + lodepng_encode(out, outsize, image, w, h, &state); + error = state.error; + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_file(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) +{ + lodepng_compress_settings_init(&settings->zlibsettings); + settings->filter_palette_zero = 1; + settings->filter_strategy = LFS_MINSUM; + settings->auto_convert = 1; + settings->force_palette = 0; + settings->predefined_filters = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 0; + settings->text_compression = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* lodepng_error_text(unsigned code) +{ + switch(code) + { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + case 16: return "unexisting code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too big"; /*more than 256 colors*/ + case 39: return "more palette alpha values given in tRNS chunk than there are colors in the palette"; + case 40: return "tRNS chunk has wrong size for greyscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for greyscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + case 54: return "repeat symbol in tree while there was no value symbol yet"; + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lenghts. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + case 56: return "given output image colortype or bitdepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "requested color conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to greyscale conversion formula to the user.*/ + case 62: return "conversion from color to greyscale not supported"; + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; /*(2^31-1)*/ + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "unexisting interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, unexisting compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + case 82: return "color conversion to palette requested while a color isn't in palette"; + case 83: return "memory allocation failed"; + case 84: return "given image too small to contain all pixels to be encoded"; + case 86: return "impossible offset in lz77 encoding (internal bug)"; + case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; + case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; + case 89: return "text chunk keyword too short or long: must have size 1-79"; + /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ + case 90: return "windowsize must be a power of two"; + case 91: return "invalid decompressed idat size"; + case 92: return "too many pixels, not supported"; + case 93: return "zero width or height is invalid"; + case 94: return "header chunk must have a size of 13 bytes"; + } + return "unknown error code"; +} +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // C++ Wrapper // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng +{ + +#ifdef LODEPNG_COMPILE_DISK +unsigned load_file(std::vector& buffer, const std::string& filename) +{ + long size = lodepng_filesize(filename.c_str()); + if(size < 0) return 78; + buffer.resize((size_t)size); + return size == 0 ? 0 : lodepng_buffer_file(&buffer[0], (size_t)size, filename.c_str()); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned save_file(const std::vector& buffer, const std::string& filename) +{ + return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); +} +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings) +{ + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_decompress(&buffer, &buffersize, in, insize, &settings); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings) +{ + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings) +{ + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings) +{ + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ + + +#ifdef LODEPNG_COMPILE_PNG + +State::State() +{ + lodepng_state_init(this); +} + +State::State(const State& other) +{ + lodepng_state_init(this); + lodepng_state_copy(this, &other); +} + +State::~State() +{ + lodepng_state_cleanup(this); +} + +State& State::operator=(const State& other) +{ + lodepng_state_copy(this, &other); + return *this; +} + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); + if(buffer && !error) + { + State state; + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) +{ + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize) +{ + unsigned char* buffer = NULL; + unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); + if(buffer && !error) + { + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in) +{ + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) +{ + std::vector buffer; + unsigned error = load_file(buffer, filename); + if(error) return error; + return decode(out, w, h, buffer, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DECODER */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} + +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state) +{ + if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, state); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + std::vector buffer; + unsigned error = encode(buffer, in, w, h, colortype, bitdepth); + if(!error) error = save_file(buffer, filename); + return error; +} + +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_PNG */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ diff --git a/src/whd_gen/lodepng.h b/src/whd_gen/lodepng.h new file mode 100644 index 00000000..ee08cea8 --- /dev/null +++ b/src/whd_gen/lodepng.h @@ -0,0 +1,1759 @@ +/* +LodePNG version 20160418 + +Copyright (c) 2005-2016 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include /*for size_t*/ + +extern const char* LODEPNG_VERSION_STRING; + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +The "NO_COMPILE" defines are designed to be used to pass as defines to the +compiler command to disable them without modifying this header, e.g. +-DLODEPNG_NO_COMPILE_ZLIB for gcc. +In addition to those below, you can also define LODEPNG_NO_COMPILE_CRC to +allow implementing a custom lodepng_crc32. +*/ +/*deflate & zlib. If disabled, you must specify alternative zlib functions in +the custom_zlib field of the compress and decompress settings*/ +#ifndef LODEPNG_NO_COMPILE_ZLIB +#define LODEPNG_COMPILE_ZLIB +#endif +/*png encoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_PNG +#define LODEPNG_COMPILE_PNG +#endif +/*deflate&zlib decoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_DECODER +#define LODEPNG_COMPILE_DECODER +#endif +/*deflate&zlib encoder and png encoder*/ +#ifndef LODEPNG_NO_COMPILE_ENCODER +#define LODEPNG_COMPILE_ENCODER +#endif +/*the optional built in harddisk file loading and saving functions*/ +#ifndef LODEPNG_NO_COMPILE_DISK +#define LODEPNG_COMPILE_DISK +#endif +/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ +#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +#endif +/*ability to convert error numerical codes to English text string*/ +#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT +#define LODEPNG_COMPILE_ERROR_TEXT +#endif +/*Compile the default allocators (C's free, malloc and realloc). If you disable this, +you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your +source files with custom allocators.*/ +#ifndef LODEPNG_NO_COMPILE_ALLOCATORS +#define LODEPNG_COMPILE_ALLOCATORS +#endif +/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ +#ifdef __cplusplus +#ifndef LODEPNG_NO_COMPILE_CPP +#define LODEPNG_COMPILE_CPP +#endif +#endif + +#ifdef LODEPNG_COMPILE_CPP +#include +#include +#endif /*LODEPNG_COMPILE_CPP*/ + +#ifdef LODEPNG_COMPILE_PNG +/*The PNG color types (also used for raw).*/ +typedef enum LodePNGColorType +{ + LCT_GREY = 0, /*greyscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*greyscale with alpha: 8,16 bit*/ + LCT_RGBA = 6 /*RGB with alpha: 8,16 bit*/ +} LodePNGColorType; + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + After decoding, its size is w * h * (bytes per pixel) bytes larger than + initially. Bytes per pixel depends on colortype and bitdepth. + Must be freed after usage with free(*out). + Note: for 16-bit per channel colors, uses big endian format like PNG does. +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colortype: the desired color type for the raw output image. See explanation on PNG color types. +bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image.*/ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); + +/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image.*/ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colortype and bitdepth + of the output PNG image cannot be chosen, they are automatically determined + by the colortype, bitdepth and content of the input pixel data. + Note: for 16-bit per channel colors, needs big endian format like PNG does. +out: Output parameter. Pointer to buffer that will contain the PNG image data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colortype: the color type of the raw input image. See explanation on PNG color types. +bitdepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned lodepng_encode_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng +{ +#ifdef LODEPNG_COMPILE_DECODER +/*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype +is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts PNG file from disk to raw pixel data in memory. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::string& filename, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*Same as lodepng_encode_memory, but encodes to an std::vector. colortype +is that of the raw input data. The output PNG color type will be auto chosen.*/ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts 32-bit RGBA raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/*Returns an English description of the numerical error code.*/ +const char* lodepng_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Settings for zlib decompression*/ +typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; +struct LodePNGDecompressSettings +{ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + + /*use custom zlib decoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGDecompressSettings lodepng_default_decompress_settings; +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Settings for zlib compression. Tweaking these settings tweaks the balance +between speed and compression ratio. +*/ +typedef struct LodePNGCompressSettings LodePNGCompressSettings; +struct LodePNGCompressSettings /*deflate = compress*/ +{ + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ + unsigned minmatch; /*mininum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ + unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ + unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ + + /*use custom zlib encoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + /*use custom deflate encoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_deflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGCompressSettings lodepng_default_compress_settings; +void lodepng_compress_settings_init(LodePNGCompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* +Color mode of an image. Contains all information required to decode the pixel +bits to RGBA colors. This information is the same as used in the PNG file +format, and is used both for PNG and raw image data in LodePNG. +*/ +typedef struct LodePNGColorMode +{ + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + When encoding a PNG, to store your colors in the palette of the LodePNGColorMode, first use + lodepng_palette_clear, then for each color use lodepng_palette_add. + If you encode an image without alpha with palette, don't forget to put value 255 in each A byte of the palette. + + When decoding, by default you can ignore this palette, since LodePNG already + fills the palette colors in the pixels of the raw RGBA output. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order. When allocated, must be either 0, or have size 1024*/ + size_t palettesize; /*palette size in number of colors (amount of bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For greyscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/greyscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNGColorMode; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_color_mode_init(LodePNGColorMode* info); +void lodepng_color_mode_cleanup(LodePNGColorMode* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); + +void lodepng_palette_clear(LodePNGColorMode* info); +/*add 1 color to the palette*/ +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info); +/*get the amount of color channels used, based on colortype in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned lodepng_get_channels(const LodePNGColorMode* info); +/*is it a greyscale type? (only colortype 0 or 4)*/ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); +/*has it got an alpha channel? (only colortype 2 or 6)*/ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); +/*has it got a palette? (only colortype 3)*/ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); +/*Returns the byte size of a raw image buffer with given width, height and color mode*/ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*The information of a Time chunk in PNG.*/ +typedef struct LodePNGTime +{ + unsigned year; /*2 bytes used (0-65535)*/ + unsigned month; /*1-12*/ + unsigned day; /*1-31*/ + unsigned hour; /*0-23*/ + unsigned minute; /*0-59*/ + unsigned second; /*0-60 (to allow for leap seconds)*/ +} LodePNGTime; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Information about the PNG image, except pixels, width and height.*/ +typedef struct LodePNGInfo +{ + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + suggested background color chunk (bKGD) + This color uses the same color mode as the PNG (except alpha channel), which can be 1-bit to 16-bit. + + For greyscale PNGs, r, g and b will all 3 be set to the same. When encoding + the encoder writes the red one. For palette PNGs: When decoding, the RGB value + will be stored, not a palette index. But when encoding, specify the index of + the palette in background_r, the other two are then ignored. + + The decoder does not use this background color to edit the color of pixels. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /* + non-international text chunks (tEXt and zTXt) + + The char** arrays each contain num strings. The actual messages are in + text_strings, while text_keys are keywords that give a short description what + the actual text represents, e.g. Title, Author, Description, or anything else. + + A keyword is minimum 1 character and maximum 79 characters long. It's + discouraged to use a single line length longer than 79 characters for texts. + + Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use lodepng_add_text and lodepng_clear_text. + */ + size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** text_strings; /*the actual text*/ + + /* + international text chunks (iTXt) + Similar to the non-international text chunks, but with additional strings + "langtags" and "transkeys". + */ + size_t itext_num; /*the amount of international texts in this PNG*/ + char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** itext_strings; /*the actual international text - UTF-8 string*/ + + /*time chunk (tIME)*/ + unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ + LodePNGTime time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + + /* + unknown chunks + There are 3 buffers, one for each position in the PNG where unknown chunks can appear + each buffer contains all unknown chunks for that position consecutively + The 3 buffers are the unknown chunks between certain critical chunks: + 0: IHDR-PLTE, 1: PLTE-IDAT, 2: IDAT-IEND + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. + */ + unsigned char* unknown_chunks_data[3]; + size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGInfo; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_info_init(LodePNGInfo* info); +void lodepng_info_cleanup(LodePNGInfo* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ + +void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/* +Converts raw buffer from one color type to another color type, based on +LodePNGColorMode structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (lodepng_get_bpp). +For < 8 bpp images, there should not be padding bits at the end of scanlines. +For 16-bit per channel colors, uses big endian format like PNG does. +Return value is LodePNG error code +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DECODER +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNGDecoderSettings +{ + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + unsigned ignore_crc; /*ignore CRC checksums*/ + + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ + /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ + unsigned remember_unknown_chunks; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGDecoderSettings; + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ +typedef enum LodePNGFilterStrategy +{ + /*every filter at zero*/ + LFS_ZERO, + /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/ + LFS_MINSUM, + /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending + on the image, this is better or worse than minsum.*/ + LFS_ENTROPY, + /* + Brute-force-search PNG filters by compressing each filter for each scanline. + Experimental, very slow, and only rarely gives better compression than MINSUM. + */ + LFS_BRUTE_FORCE, + /*use predefined_filters buffer: you specify the filter type for each scanline*/ + LFS_PREDEFINED +} LodePNGFilterStrategy; + +/*Gives characteristics about the colors of the image, which helps decide which color model to use for encoding. +Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ +typedef struct LodePNGColorProfile +{ + unsigned colored; /*not greyscale*/ + unsigned key; /*if true, image is not opaque. Only if true and alpha is false, color key is possible.*/ + unsigned short key_r; /*these values are always in 16-bit bitdepth in the profile*/ + unsigned short key_g; + unsigned short key_b; + unsigned alpha; /*alpha channel or alpha palette required*/ + unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16.*/ + unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order*/ + unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for greyscale only. 16 if 16-bit per channel required.*/ +} LodePNGColorProfile; + +void lodepng_color_profile_init(LodePNGColorProfile* profile); + +/*Get a LodePNGColorProfile of the image.*/ +unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); +/*The function LodePNG uses internally to decide the PNG color with auto_convert. +Chooses an optimal color model, e.g. grey if only grey pixels, palette if < 256 colors, ...*/ +unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); + +/*Settings for the encoder.*/ +typedef struct LodePNGEncoderSettings +{ + LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ + + /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than + 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to + completely follow the official PNG heuristic, filter_palette_zero must be true and + filter_strategy must be LFS_MINSUM*/ + unsigned filter_palette_zero; + /*Which filter strategy to use when not using zeroes due to filter_palette_zero. + Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ + LodePNGFilterStrategy filter_strategy; + /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with + the same length as the amount of scanlines in the image, and each value must <= 5. You + have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero + must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ + const unsigned char* predefined_filters; + + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is _always_ created.*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG identifier and version as a text chunk, for debugging*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGEncoderSettings; + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) +/*The settings, state and information for extended encoding and decoding.*/ +typedef struct LodePNGState +{ +#ifdef LODEPNG_COMPILE_DECODER + LodePNGDecoderSettings decoder; /*the decoding settings*/ +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + LodePNGEncoderSettings encoder; /*the encoding settings*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; +#ifdef LODEPNG_COMPILE_CPP + /* For the lodepng::State subclass. */ + virtual ~LodePNGState(){} +#endif +} LodePNGState; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and +getting much more information about the PNG image and color mode. +*/ +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the header chunk of the PNG, such as width, height and color type. The +information is placed in the info_png field of the LodePNGState. +*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* +The lodepng_chunk functions are normally not needed, except to traverse the +unknown chunks stored in the LodePNGInfo struct, or add new ones to it. +It also allows traversing the chunks of an encoded PNG file yourself. + +PNG standard chunk naming conventions: +First byte: uppercase = critical, lowercase = ancillary +Second byte: uppercase = public, lowercase = private +Third byte: must be uppercase +Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + +/* +Gets the length of the data of the chunk. Total chunk length has 12 bytes more. +There must be at least 4 bytes to read from. If the result value is too large, +it may be corrupt data. +*/ +unsigned lodepng_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void lodepng_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); + +/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); + +/*0: public, 1: private (see PNG standard)*/ +unsigned char lodepng_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk, where the input points to the header of the chunk*/ +unsigned char* lodepng_chunk_data(unsigned char* chunk); +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); + +/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk); + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void lodepng_chunk_generate_crc(unsigned char* chunk); + +/*iterate to next chunks. don't use on IEND chunk, as there is no next chunk then*/ +unsigned char* lodepng_chunk_next(unsigned char* chunk); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outlength are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outlength are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data); + + +/*Calculate CRC32 of buffer*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len); +#endif /*LODEPNG_COMPILE_PNG*/ + + +#ifdef LODEPNG_COMPILE_ZLIB +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); + +/* +Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compresses data with Zlib. Reallocates the out buffer and appends the data. +Zlib adds a small header and trailer around the deflate data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +/* +Find length-limited Huffman code for given frequencies. This function is in the +public interface only for tests, it's used internally by lodepng_deflate. +*/ +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen); + +/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you should free it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) +*/ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) +*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef LODEPNG_COMPILE_CPP +/* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ +namespace lodepng +{ +#ifdef LODEPNG_COMPILE_PNG +class State : public LodePNGState +{ + public: + State(); + State(const State& other); + virtual ~State(); + State& operator=(const State& other); +}; + +#ifdef LODEPNG_COMPILE_DECODER +/* Same as other lodepng::decode, but using a State for more settings and information. */ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Same as other lodepng::encode, but using a State for more settings and information. */ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into an std::vector. +return value: error code (0 means ok) +*/ +unsigned load_file(std::vector& buffer, const std::string& filename); + +/* +Save the binary data in an std::vector to a file on disk. The file is overwritten +without warning. +*/ +unsigned save_file(const std::vector& buffer, const std::string& filename); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_PNG */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +/* Zlib-decompress an unsigned char buffer */ +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +/* Zlib-decompress an std::vector */ +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Zlib-compress an unsigned char buffer */ +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +/* Zlib-compress an std::vector */ +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with various compilers - done but needs to be redone for every newer version +[X] converting color to 16-bit per channel types +[ ] read all public PNG chunk types (but never let the color profile and gamma ones touch RGB values) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[X] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] don't stop decoding on errors like 69, 57, 58 (make warnings) +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +[ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... +[ ] allow user to give data (void*) to custom allocator +*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. color conversions + 6.3. padding bits + 6.4. A note about 16-bits per channel and endianness + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. decoder C example + 11. state settings reference + 12. changes + 13. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types and alpha channel. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://lodev.org/lodepng/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +examples from the LodePNG website to see how to use it in code, or check the +smaller examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. There are functions to decode and encode a PNG with +a single function call, and extended versions of these functions taking a +LodePNGState struct allowing to specify or get more information. By default +the colors of the raw image are always RGB or RGBA, no matter what color type +the PNG file uses. To read and write files, there are simple functions to +convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demos and small +programs, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported (generated/interpreted) by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + +1.2. features not supported +--------------------------- + +The following features are _not_ supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The following public chunks are not supported but treated as unknown chunks by LodePNG + cHRM, gAMA, iCCP, sRGB, sBIT, hIST, sPLT + Some of these are not supported on purpose: LodePNG wants to provide the RGB values + stored in the pixels, not values modified by system dependent gamma or color models. + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. You need to use init and cleanup functions for each struct whenever +using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has extra functions with std::vectors in the interface and the +lodepng::State class which is a LodePNGState with constructor and destructor. + +These files work without modification for both C and C++ compilers because all +the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers +ignore it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp +(instead of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +Even if carefully designed, it's always possible that LodePNG contains possible +exploits. If you discover one, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well +as the C-style structs when working with C++. The following conventions are used +for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already. + + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as +lodepng_decode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_decode. For C++, all decoding can be done with the +various lodepng::decode functions, and lodepng::State can be used for advanced +features. + +When using the LodePNGState, it uses the following fields for decoding: +*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here +*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get +*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use + +LodePNGInfo info_png +-------------------- + +After decoding, this contains extra information of the PNG image, except the actual +pixels, width and height because these are already gotten directly from the decoder +functions. + +It contains for example the original color type of the PNG image, text comments, +suggested background color, etc... More details about the LodePNGInfo struct are +at its declaration documentation. + +LodePNGColorMode info_raw +------------------------- + +When decoding, here you can specify which color type you want +the resulting raw image to be. If this is different from the colortype of the +PNG, then the decoder will automatically convert the result. This conversion +always works, except if you want it to convert a color PNG to greyscale or to +a palette with missing colors. + +By default, 32-bit color is used for the result. + +LodePNGDecoderSettings decoder +------------------------------ + +The settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNGInfo. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as +lodepng_encode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_encode. For C++, all encoding can be done with the +various lodepng::encode functions, and lodepng::State can be used for advanced +features. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +When using the LodePNGState, it uses the following fields for encoding: +*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. +*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has +*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use + +LodePNGInfo info_png +-------------------- + +When encoding, you use this the opposite way as when decoding: for encoding, +you fill in the values you want the PNG to have before encoding. By default it's +not needed to specify a color type for the PNG since it's automatically chosen, +but it's possible to choose it yourself given the right settings. + +The encoder will not always exactly match the LodePNGInfo struct you give, +it tries as close as possible. Some things are ignored by the encoder. The +encoder uses, for example, the following settings from it when applicable: +colortype and bitdepth, text chunks, time chunk, the color key, the palette, the +background color, the interlace method, unknown chunks, ... + +When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +LodePNGColorMode info_raw +------------------------- + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +LodePNGEncoderSettings encoder +------------------------------ + +The following settings are supported (some are in sub-structs): +*) auto_convert: when this option is enabled, the encoder will +automatically choose the smallest possible color mode (including color key) that +can encode the colors of all pixels without information loss. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, + 2 = dynamic huffman tree (best compression). Should be 2 for proper + compression. +*) use_lz77: whether or not to use LZ77 for compressed block types. Should be + true for proper compression. +*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value + 2048 by default, but can be set to 32768 for better, but slow, compression. +*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG " to the image. +*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +An important thing to note about LodePNG, is that the color type of the PNG, and +the color type of the raw image, are completely independent. By default, when +you decode a PNG, you get the result as a raw image in the color type you want, +no matter whether the PNG was encoded with a palette, greyscale or RGBA color. +And if you encode an image, by default LodePNG will automatically choose the PNG +color type that gives good compression based on the values of colors and amount +of colors in the image. It can be configured to let you control it instead as +well, though. + +To be able to do this, LodePNG does conversions from one color mode to another. +It can convert from almost any color type to any other color type, except the +following conversions: RGB to greyscale is not supported, and converting to a +palette when the palette doesn't have a required color is not supported. This is +not supported on purpose: this is information loss which requires a color +reduction algorithm that is beyong the scope of a PNG encoder (yes, RGB to grey +is easy, but there are multiple ways if you want to give some channels more +weight). + +By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB +color, no matter what color type the PNG has. And by default when encoding, +LodePNG automatically picks the best color model for the output PNG, and expects +the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control +the color format of the images yourself, you can skip this chapter. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification gives the following color types: + +0: greyscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: greyscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel is: amount of channels * bitdepth. + +6.2. color conversions +---------------------- + +As explained in the sections about the encoder and decoder, you can specify +color types and bit depths in info_png and info_raw to change the default +behaviour. + +If, when decoding, you want the raw image to be something else than the default, +you need to set the color type and bit depth you want in the LodePNGColorMode, +or the parameters colortype and bitdepth of the simple decoding function. + +If, when encoding, you use another color type than the default in the raw input +image, you need to specify its color type and bit depth in the LodePNGColorMode +of the raw image, or use the parameters colortype and bitdepth of the simple +encoding function. + +If, when encoding, you don't want LodePNG to choose the output PNG color type +but control it yourself, you need to set auto_convert in the encoder settings +to false, and specify the color type you want in the LodePNGInfo of the +encoder (including palette: it can generate a palette if auto_convert is true, +otherwise not). + +If the input and output color type differ (whether user chosen or auto chosen), +LodePNG will do a color conversion, which follows the rules below, and may +sometimes result in an error. + +To avoid some confusion: +-the decoder converts from PNG to raw image +-the encoder converts from raw image to PNG +-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image +-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG +-when encoding, the color type in LodePNGInfo is ignored if auto_convert + is enabled, it is automatically generated instead +-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original + PNG image, but it can be ignored since the raw image has the color type you requested instead +-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion + between the color types is done if the color types are supported. If it is not + supported, an error is returned. If the types are the same, no conversion is done. +-even though some conversions aren't supported, LodePNG supports loading PNGs from any + colortype and saving PNGs to any colortype, sometimes it just requires preparing + the raw image correctly before encoding. +-both encoder and decoder use the same color converter. + +Non supported color conversions: +-color to greyscale: no error is thrown, but the result will look ugly because +only the red channel is taken +-anything to palette when that palette does not have that color in it: in this +case an error is thrown + +Supported color conversions: +-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA +-any grey or grey+alpha, to grey or grey+alpha +-anything to a palette, as long as the palette has the requested colors in it +-removing alpha channel +-higher to smaller bitdepth, and vice versa + +If you want no color conversion to be done (e.g. for speed or control): +-In the encoder, you can make it save a PNG with any color type by giving the +raw color mode and LodePNGInfo the same color mode, and setting auto_convert to +false. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +info_raw are then ignored. + +The function lodepng_convert does the color conversion. It is available in the +interface but normally isn't needed since the encoder and decoder already call +it. + +6.3. padding bits +----------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the the 8th bit of the first byte, +not the first bit of a new byte. + +6.4. A note about 16-bits per channel and endianness +---------------------------------------------------- + +LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like +for any other color format. The 16-bit values are stored in big endian (most +significant byte first) in these arrays. This is the opposite order of the +little endian used by x86 CPU's. + +LodePNG always uses big endian because the PNG file format does so internally. +Conversions to other formats than PNG uses internally are not supported by +LodePNG on purpose, there are myriads of formats, including endianness of 16-bit +colors, the order in which you store R, G, B and A, and so on. Supporting and +converting to/from all that is outside the scope of LodePNG. + +This may mean that, depending on your use case, you may want to convert the big +endian output of LodePNG to little endian with a for loop. This is certainly not +always needed, many applications and libraries support big endian 16-bit colors +anyway, but it means you cannot simply cast the unsigned char* buffer to an +unsigned short* buffer on x86 CPUs. + + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or a non-zero code if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +lodepng_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of lodepng_error_text to see the meaning of each code. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if your +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using lodepng_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned lodepng_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void lodepng_chunk_type(char type[5], const unsigned char* chunk): +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char lodepng_chunk_critical(const unsigned char* chunk): +unsigned char lodepng_chunk_private(const unsigned char* chunk): +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* lodepng_chunk_data(unsigned char* chunk): +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk): +void lodepng_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* lodepng_chunk_next(unsigned char* chunk): +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk): +unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outlength. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + +8.2. chunks in info_png +----------------------- + +The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 +buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distionction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +info_png.unknown_chunks_data[0] is the chunks before PLTE +info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT +info_png.unknown_chunks_data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.remember_unknown_chunks to 1. By default, this +option is off (0). + +The encoder will always encode unknown chunks that are stored in the info_png. +If you need it to add a particular chunk that isn't known by LodePNG, you can +use lodepng_chunk_append or lodepng_chunk_create to the chunk data in +info_png.unknown_chunks_data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there instead. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +It is compatible with C90 and up, and C++03 and up. + +If performance is important, use optimization when compiling! For both the +encoder and decoder, this makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.7.1 on Linux, 32-bit and 64-bit. + +*) Clang + +Fully supported and warning-free. + +*) Mingw + +The Mingw compiler (a port of gcc for Windows) should be fully supported by +LodePNG. + +*) Visual Studio and Visual C++ Express Edition + +LodePNG should be warning-free with warning level W4. Two warnings were disabled +with pragmas though: warning 4244 about implicit conversions, and warning 4996 +where it wants to use a non-standard function fopen_s instead of the standard C +fopen. + +Visual Studio may want "stdafx.h" files to be included in each source file and +give an error "unexpected end of file while looking for precompiled header". +This is not standard C++ and will not be added to the stock LodePNG. You can +disable it for lodepng.cpp only by right clicking it, Properties, C/C++, +Precompiled Headers, and set it to Not Using Precompiled Headers there. + +NOTE: Modern versions of VS should be fully supported, but old versions, e.g. +VS6, are not guaranteed to work. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for +C and C++. + +*) Other Compilers + +If you encounter problems on any compilers, feel free to let me know and I may +try to fix it if the compiler is modern and standards complient. + + +10. examples +------------ + +This decoder example shows the most basic usage of LodePNG. More complex +examples can be found on the LodePNG website. + +10.1. decoder C++ example +------------------------- + +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) +{ + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector image; + unsigned width, height; + unsigned error = lodepng::decode(image, width, height, filename); + + //if there's an error, display it + if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +10.2. decoder C example +----------------------- + +#include "lodepng.h" + +int main(int argc, char *argv[]) +{ + unsigned error; + unsigned char* image; + size_t width, height; + const char* filename = argc > 1 ? argv[1] : "test.png"; + + error = lodepng_decode32_file(&image, &width, &height, filename); + + if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); + + / * use image here * / + + free(image); + return 0; +} + +11. state settings reference +---------------------------- + +A quick reference of some settings to set on the LodePNGState + +For decoding: + +state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums +state.decoder.zlibsettings.custom_...: use custom inflate function +state.decoder.ignore_crc: ignore CRC checksums +state.decoder.color_convert: convert internal PNG color to chosen one +state.decoder.read_text_chunks: whether to read in text metadata chunks +state.decoder.remember_unknown_chunks: whether to read in unknown chunks +state.info_raw.colortype: desired color type for decoded image +state.info_raw.bitdepth: desired bit depth for decoded image +state.info_raw....: more color settings, see struct LodePNGColorMode +state.info_png....: no settings for decoder but ouput, see struct LodePNGInfo + +For encoding: + +state.encoder.zlibsettings.btype: disable compression by setting it to 0 +state.encoder.zlibsettings.use_lz77: use LZ77 in compression +state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize +state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match +state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching +state.encoder.zlibsettings.lazymatching: try one more LZ77 matching +state.encoder.zlibsettings.custom_...: use custom deflate function +state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png +state.encoder.filter_palette_zero: PNG filter strategy for palette +state.encoder.filter_strategy: PNG filter strategy to encode with +state.encoder.force_palette: add palette even if not encoding to one +state.encoder.add_id: add LodePNG identifier and version as a text chunk +state.encoder.text_compression: use compressed text chunks for metadata +state.info_raw.colortype: color type of raw input image you provide +state.info_raw.bitdepth: bit depth of raw input image you provide +state.info_raw: more color settings, see struct LodePNGColorMode +state.info_png.color.colortype: desired color type if auto_convert is false +state.info_png.color.bitdepth: desired bit depth if auto_convert is false +state.info_png.color....: more color settings, see struct LodePNGColorMode +state.info_png....: more PNG related settings, see struct LodePNGInfo + + +12. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +*) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). +*) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within + the limits of pure C90). +*) 08 dec 2015: Made load_file function return error if file can't be opened. +*) 24 okt 2015: Bugfix with decoding to palette output. +*) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. +*) 23 aug 2014: Reduced needless memory usage of decoder. +*) 28 jun 2014: Removed fix_png setting, always support palette OOB for + simplicity. Made ColorProfile public. +*) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. +*) 22 dec 2013: Power of two windowsize required for optimization. +*) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. +*) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). +*) 11 mar 2013 (!): Bugfix with custom free. Changed from "my" to "lodepng_" + prefix for the custom allocators and made it possible with a new #define to + use custom ones in your project without needing to change lodepng's code. +*) 28 jan 2013: Bugfix with color key. +*) 27 okt 2012: Tweaks in text chunk keyword length error handling. +*) 8 okt 2012 (!): Added new filter strategy (entropy) and new auto color mode. + (no palette). Better deflate tree encoding. New compression tweak settings. + Faster color conversions while decoding. Some internal cleanups. +*) 23 sep 2012: Reduced warnings in Visual Studio a little bit. +*) 1 sep 2012 (!): Removed #define's for giving custom (de)compression functions + and made it work with function pointers instead. +*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc + and free functions and toggle #defines from compiler flags. Small fixes. +*) 6 may 2012 (!): Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012 (!): Made interface more consistent, renaming a lot. Removed + redundant C++ codec classes. Reduced amount of structs. Everything changed, + but it is cleaner now imho and functionality remains the same. Also fixed + several bugs and shrunk the implementation code. Made new samples. +*) 6 nov 2011 (!): By default, the encoder now automatically chooses the best + PNG color model and bit depth, based on the amount and type of colors of the + raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. +*) 9 okt 2011: simpler hash chain implementation for the encoder. +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011 (!): changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also various fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +13. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2016 Lode Vandevenne +*/ diff --git a/src/whd_gen/mus2seq.cpp b/src/whd_gen/mus2seq.cpp new file mode 100644 index 00000000..0a131920 --- /dev/null +++ b/src/whd_gen/mus2seq.cpp @@ -0,0 +1,343 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2006 Ben Ryves 2006 +// Copyright(C) 2021-2022 Graham Sanderson +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// mus2mid.c - Ben Ryves 2006 - http://benryves.com - benryves@benryves.com +// Use to convert a MUS file into a single track, type 0 MIDI file. + +#include "mus2seq.h" +#include +#include +#include +#include + +#if 1 +#define printf(x, ...) ((void)0) +#endif + +typedef uint8_t byte; + +#define MIDI_PERCUSSION_CHAN 9 +#define MUS_PERCUSSION_CHAN 15 + +//#define DUMP 1 + +// MUS event codes +typedef enum +{ + mus_releasekey = 0x00, + mus_presskey = 0x10, + mus_pitchwheel = 0x20, + mus_systemevent = 0x30, + mus_changecontroller = 0x40, + mus_scoreend = 0x60 +} musevent; + +struct input { + explicit input(const std::vector& in) : in(in), pos(0) {} + const std::vector ∈ + size_t pos; + + template bool read(T *out) { + if (pos + sizeof(T) > in.size()) { + return false; + } + memcpy((void *)out, (const void *)(in.data() + pos), sizeof(T)); + pos += sizeof(T); + return true; + } +}; + +// Structure to hold MUS file header +typedef struct + { + uint8_t id[4]; + unsigned short scorelength; + unsigned short scorestart; + unsigned short primarychannels; + unsigned short secondarychannels; + unsigned short instrumentcount; + } musheader; + +static_assert(sizeof(musheader)==14,""); +static int channel_map[SEQ_MAX_CHANNEL_COUNT]; + +// Allocate a free MIDI channel. + +static int AllocateMIDIChannel(void) { + int result; + int max; + int i; + + // Find the current highest-allocated channel. + + max = -1; + + for (i = 0; i < SEQ_MAX_CHANNEL_COUNT; ++i) { + if (channel_map[i] > max) { + max = channel_map[i]; + } + } + + // max is now equal to the highest-allocated MIDI channel. We can + // now allocate the next available channel. This also works if + // no channels are currently allocated (max=-1) + + result = max + 1; + + // Don't allocate the MIDI percussion channel! + + if (result == MIDI_PERCUSSION_CHAN) { + ++result; + } + + return result; +} + +// Given a MUS channel number, get the MIDI channel number to use +// in the outputted file. + +static int GetMIDIChannel(int mus_channel, std::vector& items) { + // Find the MIDI channel to use for this MUS channel. + // MUS channel 15 is the percusssion channel. + + if (mus_channel == MUS_PERCUSSION_CHAN) { + return MIDI_PERCUSSION_CHAN; + } else { + // If a MIDI channel hasn't been allocated for this MUS channel + // yet, allocate the next free MIDI channel. + + if (channel_map[mus_channel] == -1) { + channel_map[mus_channel] = AllocateMIDIChannel(); + + // First time using the channel, send an "all notes off" + // event. This fixes "The D_DDTBLU disease" described here: + // https://www.doomworld.com/vb/source-ports/66802-the +#if DUMP + printf("%d SYSTEM %d\n", mus_channel, 0xb); +#endif + + items.push_back(seq_item::all_notes_off(channel_map[mus_channel])); + } + return channel_map[mus_channel]; + } +} + +bool mus2seq(const std::vector &musinput, std::vector &seq_groups) { + input in(musinput); + // Header for the MUS file + musheader musfileheader; + + // Descriptor for the current MUS event + byte eventdescriptor; + int channel; // Channel number + + // Flag for when the score end marker is hit. + bool hitscoreend = false; + + // Initialise channel map to mark all channels as unused. + + for (channel = 0; channel < SEQ_MAX_CHANNEL_COUNT; ++channel) { + channel_map[channel] = -1; + } + + // Grab the header + + if (!in.read(&musfileheader)) { + return true; + } + + // Seek to where the data is held + in.pos = musfileheader.scorestart; + + // So, we can assume the MUS file is faintly legit. Let's start + // writing MIDI data... + + auto seq_items = std::vector(); + std::vector> channel_notes(SEQ_MAX_CHANNEL_COUNT); + uint8_t last_volume = 0; + std::vector last_volumes(SEQ_MAX_CHANNEL_COUNT); + std::vector last_press_volumes(SEQ_MAX_CHANNEL_COUNT); + std::vector last_vibratos(SEQ_MAX_CHANNEL_COUNT); + std::vector last_wheel(SEQ_MAX_CHANNEL_COUNT); + // vibratos initialized to 0 + for(auto &e : last_volumes) e = MUSX_INITIAL_CHANNEL_VOLUME; + for(auto &e : last_press_volumes) e = MUSX_INITIAL_CHANNEL_VOLUME; + for(auto &e : last_wheel) e = MUSX_INITIAL_CHANNEL_WHEEL; + +#if DUMP + int ecount = 0; +#endif + // Now, process the MUS file: + while (!hitscoreend) { + // Handle a block of events: + + while (!hitscoreend) { + // Fetch channel number and event code: + + if (!in.read(&eventdescriptor)) { + return true; + } + +#if DUMP + printf("%d: ", ecount++); +#endif + channel = GetMIDIChannel(eventdescriptor & 0x0F, seq_items); + switch ((musevent) (eventdescriptor & 0x70)) { + case mus_releasekey: { + uint8_t key; + if (!in.read(&key)) { + return true; + } +#if DUMP + printf("%d RELEASEKEY %d\n", channel, key); +#endif + auto it = std::find(channel_notes[channel].begin(), channel_notes[channel].end(), key); + if (it == channel_notes[channel].end()) { + printf("RELEASE OF NOTE NOT ON\n"); + return true; + } + uint dist = it - channel_notes[channel].begin(); + uint max_dist = channel_notes[channel].size(); + channel_notes[channel].erase(it); + seq_items.push_back(seq_item::release_key(channel, dist, max_dist)); + break; + } + case mus_presskey: { + uint8_t key; + if (!in.read(&key)) { + return true; + } + uint8_t vol = vol_last_channel; + // the net here is somewhat arbitrary; i.e. we only update the global last volume + // if there was a volume change in the input MUS... todo compare this with updating every time + if (key & 0x80) { + if (!in.read(&vol)) { + return true; + } +#if DUMP + printf("%d PRESSKEYVOL %d %d\n", channel, key&0x7f, vol); +#endif + last_volumes[channel] = vol; + last_press_volumes[channel] = vol; + if (vol == last_volume) { + vol = vol_last_global; + } else { + last_volume = vol; + } + key &= 0x7fu; + } else { +#if DUMP + printf("%d PRESSKEY %d %d\n", channel, key&0x7f, last_press_volumes[channel]); +#endif + } + seq_items.push_back(seq_item::press_key(channel, key, vol)); + channel_notes[channel].push_back(key); + break; + } + case mus_pitchwheel: { + uint8_t wheel; + if (!in.read(&wheel)) { + return true; + } +#if DUMP + printf("%d PITCHWHEEL %d\n", channel, wheel); +#endif + seq_items.push_back(seq_item::delta_pitch(channel, wheel - last_wheel[channel])); + last_wheel[channel] = wheel; + break; + } + case mus_systemevent: + { + uint8_t controllernumber; + if (!in.read(&controllernumber)) { + return true; + } + if (controllernumber < 10 || controllernumber > 14) { + return true; + } +#if DUMP + printf("%d SYSTEM %d\n", channel, controllernumber); +#endif + seq_items.push_back(seq_item::system_event(channel, controllernumber)); + break; + } + case mus_changecontroller: + { + uint8_t controllernumber; + uint8_t controllervalue; + if (!in.read(&controllernumber)) { + return true; + } + if (!in.read(&controllervalue)) { + return true; + + } + if (controllernumber > 9) { + return true; + } + if (controllernumber == 2) { + seq_items.push_back(seq_item::delta_vibrato(channel, controllervalue - last_vibratos[channel])); + last_vibratos[channel] = controllervalue; + } else if (controllernumber == 3) { + seq_items.push_back(seq_item::delta_volume(channel, controllervalue - last_volumes[channel])); + last_volumes[channel] = controllervalue; + } else { + seq_items.push_back(seq_item::change_controller(channel, controllernumber, controllervalue)); + } +#if DUMP + printf("%d CONTROLLER %d %d\n", channel, controllernumber, controllervalue); +#endif + break; + } + case mus_scoreend: + seq_items.push_back(seq_item::score_end()); +#if DUMP + printf("%d SCOREEND\n", channel); +#endif + hitscoreend = 1; + break; + + default: + return true; + } + + if (eventdescriptor & 0x80) { + break; + } + } + // Now we need to read the time code: + if (!hitscoreend) { + uint32_t gap = 0; + uint8_t b; + do { + if (!in.read(&b)) { + return true; + } + gap = (gap<<7u) + (b & 0x7fu); + } while (b & 0x80u); +#if DUMP + if (gap) { + printf("<- %d ->\n", gap); + } +#endif + seq_groups.push_back({seq_items, gap}); + seq_items.clear(); + } else { + seq_groups.push_back({seq_items, 0}); + } + } + return false; +} diff --git a/src/whd_gen/mus2seq.h b/src/whd_gen/mus2seq.h new file mode 100644 index 00000000..c28f485c --- /dev/null +++ b/src/whd_gen/mus2seq.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#pragma once + +#include +#include +#include +#include +#include "musx_decoder.h" + +#define SEQ_MAX_CHANNEL_COUNT 16 + +struct seq_item { + seq_event event; + uint8_t channel; + uint8_t p1; + uint8_t p2; + + static seq_item system_event(uint8_t channel, uint8_t event) { + return { + .event = seq_event::system_event, + .channel = channel, + .p1 = event, + .p2 = 0, + }; + } + + static seq_item change_controller(uint8_t channel, uint8_t controller, uint8_t value) { + return { + .event= seq_event::change_controller, + .channel = channel, + .p1 = controller, + .p2 = value, + }; + } + + static seq_item all_notes_off(uint8_t channel) { + return system_event(channel, 0xb); + } + + static seq_item release_key(uint8_t channel, uint8_t dist, uint8_t max_dist) { + return { + .event= seq_event::release_key, + .channel = channel, + .p1 = dist, + .p2 = max_dist, + }; + } + + static seq_item press_key(uint8_t channel, uint8_t note, uint8_t vol) { + return { + .event= seq_event::press_key, + .channel = channel, + .p1 = note, + .p2 = vol, + }; + } + + static seq_item delta_pitch(uint8_t channel, uint8_t delta) { + return { + .event= seq_event::delta_pitch, + .channel = channel, + .p1 = delta, + .p2 = 0, + }; + } + + static seq_item delta_volume(uint8_t channel, uint8_t delta) { + return { + .event= seq_event::delta_volume, + .channel = channel, + .p1 = delta, + .p2 = 0, + }; + } + + static seq_item delta_vibrato(uint8_t channel, uint8_t delta) { + return { + .event= seq_event::delta_vibrato, + .channel = channel, + .p1 = delta, + .p2 = 0, + }; + } + + static seq_item score_end() { + return { + .event= seq_event::score_end, + .channel = 0, // note this is assumed to be the case when huffman encoding later + .p1 = 0, + .p2 = 0, + }; + } +}; + +struct seq_group { + std::vector items; + uint32_t gap; +}; + +// return true on error +bool mus2seq(const std::vector &musinput, std::vector &musoutput); diff --git a/src/whd_gen/statsomizer.h b/src/whd_gen/statsomizer.h new file mode 100644 index 00000000..2696bc9a --- /dev/null +++ b/src/whd_gen/statsomizer.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#pragma once +#include +#include + +struct statsomizer { + const std::string name; + + explicit statsomizer(std::string name) : name(std::move(name)) { reset(); } + + void record(int value) { + total += value; + min = std::min(min, value); + max = std::max(max, value); + count++; + } + + void record_print(int value) { + record(value); + printf("%20s: value=%d min=%d max=%d avg=%d\n", name.c_str(), value, min, max, + count ? ((int) (total / count)) : 0); + } + + void print_summary() const { + printf("%20s: min=%d max=%d avg=%d count=%d total=%ld\n", name.c_str(), min, max, + count ? ((int) (total / count)) : 0, count, total); + } + + void reset() { + total = 0; + count = 0; + min = std::numeric_limits::max(); + max = std::numeric_limits::min(); + } + + long total; + int count, min, max; +}; diff --git a/src/whd_gen/wad.cpp b/src/whd_gen/wad.cpp new file mode 100644 index 00000000..eed7ec04 --- /dev/null +++ b/src/whd_gen/wad.cpp @@ -0,0 +1,286 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "wad.h" +#include +#include +#include +#include +#include "../whddata.h" + +typedef struct __attribute__((packed)) { + // Should be "IWAD" or "PWAD". + char identification[4]; + int32_t numlumps; + int32_t infotableofs; +} wadinfo_t; + +// todo we may be able to limit all lumps to 64K? +typedef struct __attribute__((packed)) { + uint32_t offset; + int size; + char name[9]; + char _pad; + short next; +} whd_lump_t; + +typedef struct __attribute__((packed)){ +int32_t filepos; +int32_t size; +char name[8]; +} filelump_t; + +template struct typed_element_ptr { + std::vector storage; + int count; + typed_element_ptr(std::vector v, int count) : storage(std::move(v)), count(count) {} + + int size() { + return count; + } + T* get(int index = 0) { + assert(index < count); + return ((T*)storage.data()) + index; + } +}; + +template typed_element_ptr read_raw(FILE *in, int count = 1) { + size_t size = count * sizeof(T); + std::vector v(size); + if (1 != fread(v.data(), size, 1, in)) { + throw std::runtime_error(std::string("Failed to read ") + std::to_string(size) + " bytes from file"); + } + return typed_element_ptr(v, count); +} + +template void write_raw(FILE *out, T* data, int count = 1) { + size_t size = count * sizeof(T); + if (1 != fwrite(data, size, 1, out)) { + throw std::runtime_error(std::string("Failed to write ") + std::to_string(size) + " bytes to file"); + } +} + +void write_raw(FILE *out, const std::vector& data) { + size_t size = data.size(); + if (1 != fwrite(data.data(), size, 1, out)) { + throw std::runtime_error(std::string("Failed to write ") + std::to_string(size) + " bytes to file"); + } +} + +wad wad::read(const std::string &filename) { + wad rc; + FILE *in = fopen(filename.c_str(), "rb"); + if (!in) throw std::invalid_argument(filename + " not found"); + + auto header_raw = read_raw(in); + auto header = header_raw.get(); + if (strncmp(header->identification, "IWAD", 4)) { + throw std::runtime_error("file is not an IWAD"); + } + fseek(in, header->infotableofs, SEEK_SET); + auto lumps_raw = read_raw(in, header->numlumps); + for(int i=0;i<(int)lumps_raw.size();i++) { + auto lraw = lumps_raw.get(i); + std::string name = wad::wad_string(lraw->name); + if (!lraw->size) { + rc.lumps[i] = lump(name, std::vector(), i); + } else { + fseek(in, lraw->filepos, SEEK_SET); + rc.lumps[i] = lump(name, read_raw(in, lraw->size).storage, i); + } + rc.lump_names[to_lower(name)] = i; + } + printf("LUMP METADATA %d (%dK)\n", (int)(lumps_raw.size() * sizeof(filelump_t)), ((int)(lumps_raw.size() * sizeof(filelump_t))+512)/1024); + fclose(in); + return rc; +} + +// Hash function used for lump names. +unsigned int W_LumpNameHash(const char *s) +{ + // This is the djb2 string hash function, modded to work on strings + // that have a maximum length of 8. + + unsigned int result = 5381; + unsigned int i; + + for (i=0; i < 8 && s[i] != '\0'; ++i) + { + result = ((result << 5) ^ result ) ^ toupper(s[i]); + } + + return result; +} + +void wad::write_whd(const std::string &filename, std::set name_required, uint32_t hash, bool super_tiny) { + FILE *out = fopen(filename.c_str(), "wb"); + if (!out) throw std::invalid_argument(filename + " can't be opened for write"); + + std::set name_required_lower; // use lower case to match c stricmp sorting + for (const auto&s : name_required) { + if (get_lump_index(s) >= 0) { + name_required_lower.insert(to_lower(s)); + } + } + int num_lumps = 1 + lumps.rbegin()->first; + wadinfo_t header { + .identification = { 'I', 'W', 'H', super_tiny ? 'X' : 'D' }, + .numlumps = num_lumps, + .infotableofs = sizeof(wadinfo_t) + sizeof(whdheader_t), + }; + write_raw(out, &header); + uint32_t name_count = name_required_lower.size(); + whdheader_t whdheader = { + .hash = hash, + .num_named_lumps = (uint16_t)name_count, + }; + strcpy(whdheader.name, name.c_str()); + write_raw(out, &whdheader); // we will write it again later with size + assert(ftell(out) == header.infotableofs); +#if 0 + int num = 0; + whd_lump_t empty_lump = { + .offset = 0, + .size = 0, + .name = { '?', 0, 0, 0, 0, 0, 0, 0, 0}, + ._pad = 0, + .next = -1 + }; + int num_hash_entries = num_lumps; // no reason for this + int base_data_offset = header.infotableofs + num_lumps * sizeof(whd_lump_t) + num_hash_entries * 2; + int data_offset = base_data_offset; + std::vector next(num_lumps, -1); + + for(const auto &e : lumps) { + while (num < e.first) { + write_raw(out, &empty_lump); + num++; + } + const auto& l = e.second; + int size = l.data.size(); + whd_lump_t fl; + fl.offset = data_offset; + fl.size = size; + memset(&fl.name, 0, 9); + fl._pad = 0; + int hash = W_LumpNameHash(l.name.c_str()) % num_lumps; + fl.next = next[hash]; + next[hash] = num; + if (l.name.size() < 8) + strcpy(fl.name, l.name.c_str()); + else + memcpy(fl.name, l.name.c_str(), 8); + write_raw(out, &fl); +// printf("%d %08x %08x %s\n", num, data_offset, size, l.name.c_str()); + data_offset += size; + data_offset = (data_offset + 3) &~3; + num++; + } + for(int n : next) { + short s = (short)n; + write_raw(out, &s); + } +#else + int base_data_offset = header.infotableofs + (num_lumps + 1) * sizeof(uint32_t) + name_count * 12; + int data_offset = base_data_offset; + int num = 0; + for(const auto &e : lumps) { + while (num < e.first) { + write_raw(out, &data_offset); + num++; + } + const auto& l = e.second; + int size = l.data.size(); + uint32_t combined = data_offset | ((4 -size) << 30); // store amount to substract off word aligned size to get real size in two high bits + write_raw(out, &combined); + data_offset += size; + data_offset = (data_offset + 3) &~3; + num++; + } +#endif + write_raw(out, &data_offset); + for(const auto &s : name_required_lower) { + std::vector n(10); + strncpy((char *)n.data(), s.c_str(), 8); + write_raw(out, n); + int16_t lnum = get_lump_index(s); +// printf("%s %d\n", s.c_str(), lnum); + assert(num >= 0); + write_raw(out, &lnum); + } + printf("WHD LUMP METADATA %d (%dK)\n", (int)ftell(out), (((int)ftell(out))+512)/1024); + + assert(ftell(out) == base_data_offset); + for(const auto &e : lumps) { + if (e.second.data.size()) { + write_raw(out, e.second.data); + for(int i=e.second.data.size() & 3; i && i < 4; i++) { + fputc(0, out); + } + } + } + whdheader.size = ftell(out); + fseek(out, sizeof(wadinfo_t), SEEK_SET); + write_raw(out, &whdheader); + fclose(out); +} + +void wad::write(const std::string &filename) { + FILE *out = fopen(filename.c_str(), "wb"); + if (!out) throw std::invalid_argument(filename + " can't be opened for write"); + + int num_lumps = 1 + lumps.rbegin()->first; + wadinfo_t header { + .identification = { 'I', 'W', 'A', 'D' }, + .numlumps = num_lumps, + .infotableofs = sizeof(wadinfo_t), + }; + write_raw(out, &header); + assert(ftell(out) == header.infotableofs); + int num = 0; + filelump_t empty_lump = { + .filepos = 0, + .size = 0, + .name = { '?' }, + }; + int base_data_offset = header.infotableofs + num_lumps * sizeof(filelump_t); + int data_offset = base_data_offset; + std::vector next(num_lumps, -1); + + for(const auto &e : lumps) { + while (num < e.first) { + write_raw(out, &empty_lump); + num++; + } + const auto& l = e.second; + int size = l.data.size(); + filelump_t fl; + fl.filepos = data_offset; + fl.size = size; + memset(&fl.name, 0, 8); + int hash = W_LumpNameHash(l.name.c_str()) % num_lumps; + next[hash] = num; + + if (l.name.size() < 8) + strcpy(fl.name, l.name.c_str()); + else + memcpy(fl.name, l.name.c_str(), 8); + write_raw(out, &fl); + data_offset += size; + data_offset = (data_offset + 3) &~3; + num++; + } + + assert(ftell(out) == base_data_offset); + for(const auto &e : lumps) { + if (e.second.data.size()) { + write_raw(out, e.second.data); + for(int i=e.second.data.size() & 3; i && i < 4; i++) { + fputc(0, out); + } + } + } + fclose(out); +} \ No newline at end of file diff --git a/src/whd_gen/wad.h b/src/whd_gen/wad.h new file mode 100644 index 00000000..0ed92dfd --- /dev/null +++ b/src/whd_gen/wad.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +inline std::string to_lower(std::string x) { + std::for_each(x.begin(), x.end(), [](char & c){ + c = ::tolower(c); + }); + return x; +} + +inline std::string to_upper(std::string x) { + std::for_each(x.begin(), x.end(), [](char & c){ + c = ::toupper(c); + }); + return x; +} + +struct lump { + lump() = default; + explicit lump(std::string name, std::vector v, int num) : name(std::move(name)), data(std::move(v)), num(num) {} + std::string name; + std::vector data; + int num = -1; +}; + +struct wad { + wad() { + set_name(""); + } + static wad read(const std::string& filename); + void write(const std::string& filename); + void write_whd(const std::string& filename, std::set name_required, uint32_t hash, bool super_tiny); + + std::map& get_lumps() { + return lumps; + } + + void keep_only(const std::set& nums) { + for(auto it = lumps.begin(); it != lumps.end();) { + if (it->second.data.empty() || nums.find(it->first) != nums.end()) { +// printf("Keeping %s\n", it->second.name.c_str()); + it++; + } else { + it = lumps.erase(it); + lump_names.erase(it->second.name); + } + } + } + + static std::string wad_string(const char data[8]) { + std::string rc = data[7] ? std::string(data, 8) : std::string(data); + rc = rc.substr(0, strlen(rc.c_str())); + return rc; + } + + int get_lump_index(const std::string &name) { + auto it = lump_names.find(to_lower(name)); + if (it != lump_names.end()) { + return it->second; + } + return -1; + } + + int get_lump(const std::string &name, lump& lump_out) { + auto it = lump_names.find(to_lower(name)); + if (it != lump_names.end()) { + lump_out = lumps[it->second]; + return it->second + 1; + } + return 0; + } + + bool get_lump(int num, lump& lump_out) { + if (lumps[num].data.size()) { + lump_out = lumps[num]; + return true; + } + return false; + } + + void update_lump(const lump& lump) { + assert(lump.num>=0); + if (to_lower(lumps[lump.num].name) != to_lower(lump.name)) { + auto it = lump_names.find(to_lower(name)); + if (it != lump_names.end()) { + lump_names.erase(it); + } + lump_names[to_lower(lump.name)] = lump.num; + lumps[lump.num].name = lump.name; + } + lumps[lump.num].num = lump.num; // incase this is a new lump + lumps[lump.num].data = lump.data; + } + + void remove_lump(const std::string &name) { + auto it = lump_names.find(to_lower(name)); + if (it != lump_names.end()) { + lumps.erase(lumps.find(it->second)); + lump_names.erase(it); + } + } + + void set_name(std::string name) { + if (!name.empty() && name.length() <= 13) + this->name = to_upper(name); + else + this->name = "DOOM.WAD"; + } +protected: + std::string name; + std::map lumps; // we support spare wads for now + std::map lump_names; +}; \ No newline at end of file diff --git a/src/whd_gen/whd_gen.cpp b/src/whd_gen/whd_gen.cpp new file mode 100644 index 00000000..7963840e --- /dev/null +++ b/src/whd_gen/whd_gen.cpp @@ -0,0 +1,5331 @@ +/* + * Copyright (c) 20222 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "wad.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "doomdata.h" +#include "whddata.h" +#include "compress_mus.h" +#include "lodepng.h" +#include "statsomizer.h" +#include "extra_patches.h" +#include +#include +#include "musx_decoder.h" +#include "image_decoder.h" + +//#define USE_PIXELS_ONLY_PATCH 1 // dont use c3 on patches +#define USE_PIXELS_ONLY_FLAT 1 // dont use c3 on flats +//#define DEBUG_TEXTURE_OPTIMIZATION 1 +//#define DEBUG_SAVE_PNG 1 + +typedef struct __packed { + const char * const name; + const uint8_t * const data; + const uint8_t w; + const uint8_t h; +} txt_font_t; +#include "../../textscreen/fonts/normal.h" + + +bool super_tiny = true; + +using std::vector; + +template std::ostream &operator<<(std::ostream &os, const std::pair &v); +#include "huffman.h" +#include "huff.h" +#include "huff_sink.h" + +int dumped_patch_count, converted_patch_count, converted_patch_size; +int bit_addressable_patch; +std::vector winners(16); +std::vector fwinners(4); +std::set all_linedef_flags; + +statsomizer flat_have_same_savings("Flat same savings"); +statsomizer side_meta("Side meta"); +statsomizer side_metaz("Side meta z"); +statsomizer line_meta("Line meta"); +statsomizer line_metaz("Line meta z"); +statsomizer line_scale("Line scale"); +statsomizer vertex_x("VX"); +statsomizer vertex_y("VY"); +statsomizer ss_delta("subsector delta"); +statsomizer demo_size_orig("demo size orig"); +statsomizer demo_size("demo size"); + +statsomizer single_patch_metadata_size("single patch metadata"); + +static std::map touched; +static std::vector cleared_lumps; +static std::set compressed; +static std::set name_required; + +// map from thing in the lump to some stat buckets +#if 0 +#define TOUCHED_PATCH "Graphics" +#define TOUCHED_VPATCH "Graphics" +#define TOUCHED_FLAT "Graphics" +#define TOUCHED_SPRITE_METADATA "Graphics" +#define TOUCHED_PATCH_METADATA "Graphics" +#define TOUCHED_PNAMES "Graphics" +#define TOUCHED_TEX_METADATA "Graphics" +#define TOUCHED_DMX "Unused" +#define TOUCHED_UNUSED_GRAPHIC "Unused" +#define TOUCHED_UNUSED "Unused" +#define TOUCHED_PALETTE "Misc" +#define TOUCHED_COLORMAP "Misc" +#define TOUCHED_ENDOOM "Misc" +#define TOUCHED_SFX "SFX" +#define TOUCHED_MUSIC "Music" +#define TOUCHED_GENMIDI "Misc" +#define TOUCHED_LEVEL "Level" +#define TOUCHED_LEVEL_THINGS "Level" +#define TOUCHED_LEVEL_SIDEDEFS "Level" +#define TOUCHED_LEVEL_NODES "Level" +#define TOUCHED_LEVEL_VERTEXES "Level" +#define TOUCHED_LEVEL_LINEDEFS "Level" +#define TOUCHED_LEVEL_SECTORS "Level" +#define TOUCHED_LEVEL_SEGS "Level" +#define TOUCHED_LEVEL_REJECT "Level" +#define TOUCHED_LEVEL_SSECTORS "Level" +#define TOUCHED_LEVEL_BLOCKMAP "Level" +#else +#define TOUCHED_PATCH "Graphics Patch" +#define TOUCHED_VPATCH "Graphics V-Patch" +#define TOUCHED_FLAT "Graphics Flat" +#define TOUCHED_SPRITE_METADATA "Graphics Patch" +#define TOUCHED_PATCH_METADATA "Graphics Patch" +#define TOUCHED_PNAMES "Graphics Meta" +#define TOUCHED_TEX_METADATA "Graphics Textures" +#define TOUCHED_DMX "Unused" +#define TOUCHED_UNUSED_GRAPHIC "Unused" +#define TOUCHED_UNUSED "Unused" +#define TOUCHED_PALETTE "Misc" +#define TOUCHED_COLORMAP "Misc" +#define TOUCHED_ENDOOM "Misc" +#define TOUCHED_SFX "SFX" +#define TOUCHED_MUSIC "Music" +#define TOUCHED_GENMIDI "Misc" +#define TOUCHED_LEVEL "Level" +#define TOUCHED_LEVEL_THINGS "Level Things" +#define TOUCHED_LEVEL_SIDEDEFS "Level Sidedefs" +#define TOUCHED_LEVEL_NODES "Level Nodes" +#define TOUCHED_LEVEL_VERTEXES "Level Vertexes" +#define TOUCHED_LEVEL_LINEDEFS "Level Linedefs" +#define TOUCHED_LEVEL_SECTORS "Level Sectors" +#define TOUCHED_LEVEL_SEGS "Level Segs" +#define TOUCHED_LEVEL_REJECT "Level Reject" +#define TOUCHED_LEVEL_SSECTORS "Level Subsectors" +#define TOUCHED_LEVEL_BLOCKMAP "Level Blockmap" + +#endif +// all the lump names that are referenced by doom source code... we must keep the names +static std::vector named_lumps = { + "PLAYPAL", + "ENDOOM", + "P_START", + "P_END", + "GENMIDI", + "COLORMAP", + "TEXTURE1", + "TEXTURE2", + "F_START", + "F_SKY1", + "F_END", + "S_START", + "S_END", + "CREDIT", + "HELP", + "HELP1", + "HELP2", + "PFUB1", + "PFUB2", + "ENDPIC", + "TITLEPIC", + "INTERPIC", + "VICTORY2", + "BOSSBACK", + "FLOOR4_8", + "SFLR6_1", + "MFLR8_4", + "MFLR8_3", + "SLIME16", + "RROCK14", + "RROCK07", + "RROCK17", + "RROCK13", + "RROCK19", + "SLIME16", + "RROCK14", + "RROCK07", + "RROCK17", + "RROCK13", + "RROCK19", + "SLIME16", + "RROCK14", + "RROCK07", + "RROCK17", + "RROCK13", + "RROCK19", + "E4M1", + "E3M1", +}; + +// large menu graphics with lots of transparency, ecnodedd as runs +static std::vector run16_menu_vpatches = { + "M_RDTHIS", + "M_OPTION", + "M_QUITG", + "M_NGAME", + "M_THERMR", + "M_THERMM", + "M_THERML", + "M_ENDGAM", + "M_PAUSE", + "M_MESSG", + "M_MSGON", + "M_MSGOFF", + "M_EPISOD", + "M_EPI1", + "M_EPI2", + "M_EPI3", + "M_EPI4", + "M_HURT", + "M_JKILL", + "M_ROUGH", + "M_SKILL", + "M_NEWG", + "M_ULTRA", + "M_NMARE", + "M_SVOL", + "M_OPTTTL", + "M_SAVEG", + "M_LOADG", + "M_DISP", + "M_MSENS", + "M_GDHIGH", + "M_GDLOW", + "M_DETAIL", + "M_DISOPT", + "M_SCRNSZ", + "M_SGTTL", + "M_LGTTL", + "M_SFXVOL", + "M_MUSVOL", + "M_LSLEFT", + "M_LSCNTR", + "M_LSRGHT", + // these are our new network menu items + "M_DTHMCH", + "M_GAME", + "M_HOST", + "M_JOIN", + "M_NAME", + "M_NETWK", + "M_TWO", + // these seem red but have other colors + "WISPLAT", + "WIURH0", + "WIURH1", +}; + +// these (all) red graphics share a single 16 color palette... use alpha as the runs are very short +static std::vector alpha16_shpal_red_vpatches = { + "STCFN033", + "STCFN034", + "STCFN035", + "STCFN036", + "STCFN037", + "STCFN038", + "STCFN039", + "STCFN040", + "STCFN041", + "STCFN042", + "STCFN043", + "STCFN044", + "STCFN045", + "STCFN046", + "STCFN047", + "STCFN048", + "STCFN049", + "STCFN050", + "STCFN051", + "STCFN052", + "STCFN053", + "STCFN054", + "STCFN055", + "STCFN056", + "STCFN057", + "STCFN058", + "STCFN059", + "STCFN060", + "STCFN061", + "STCFN062", + "STCFN063", + "STCFN064", + "STCFN065", + "STCFN066", + "STCFN067", + "STCFN068", + "STCFN069", + "STCFN070", + "STCFN071", + "STCFN072", + "STCFN073", + "STCFN074", + "STCFN075", + "STCFN076", + "STCFN077", + "STCFN078", + "STCFN079", + "STCFN080", + "STCFN081", + "STCFN082", + "STCFN083", + "STCFN084", + "STCFN085", + "STCFN086", + "STCFN087", + "STCFN088", + "STCFN089", + "STCFN090", + "STCFN091", + "STCFN092", + "STCFN093", + "STCFN094", + "STCFN095", + "STCFN121", + "STTMINUS", + "STTNUM0", + "STTNUM1", + "STTNUM2", + "STTNUM3", + "STTNUM4", + "STTNUM5", + "STTNUM6", + "STTNUM7", + "STTNUM8", + "STTNUM9", + "STTPRCNT", + "WICOLON", + "WIENTER", + "WIF", + "WIFRGS", + "WIKILRS", + "WIMINUS", + "WIMSTAR", + "WIMSTT", + "WINUM0", + "WINUM1", + "WINUM2", + "WINUM3", + "WINUM4", + "WINUM5", + "WINUM6", + "WINUM7", + "WINUM8", + "WINUM9", + "WIOSTF", + "WIOSTI", + "WIOSTK", + "WIOSTS", + "WIP1", + "WIP2", + "WIP3", + "WIP4", + "WIPAR", + "WIPCNT", + "WISCRT2", + "WISUCKS", + "WITIME", + "WIVCTMS", +}; + +// these level name graphics share a single 16 color palette +static std::vector alpha16_shpal_white_vpatches = { + "WIBP1", + "WIBP2", + "WIBP3", + "WIBP4", + "WILV00", + "WILV01", + "WILV02", + "WILV03", + "WILV04", + "WILV05", + "WILV06", + "WILV07", + "WILV08", + "WILV10", + "WILV11", + "WILV12", + "WILV13", + "WILV14", + "WILV15", + "WILV16", + "WILV17", + "WILV18", + "WILV20", + "WILV21", + "WILV22", + "WILV23", + "WILV24", + "WILV25", + "WILV26", + "WILV27", + "WILV28", + "WILV30", + "WILV31", + "WILV32", + "WILV33", + "WILV34", + "WILV35", + "WILV36", + "WILV37", + "WILV38", + "CWILV00", + "CWILV01", + "CWILV02", + "CWILV03", + "CWILV04", + "CWILV05", + "CWILV06", + "CWILV07", + "CWILV08", + "CWILV09", + "CWILV10", + "CWILV11", + "CWILV12", + "CWILV13", + "CWILV14", + "CWILV15", + "CWILV16", + "CWILV17", + "CWILV18", + "CWILV19", + "CWILV20", + "CWILV21", + "CWILV22", + "CWILV23", + "CWILV24", + "CWILV25", + "CWILV26", + "CWILV27", + "CWILV28", + "CWILV29", + "CWILV30", + "CWILV31", + "CWILV32", + "CWILV33", + "CWILV34", + "CWILV35", + "CWILV36", + "CWILV37", + "CWILV38", + "CWILV39", +}; + +// these grayscale graphics share a 16 color palette (note STBAR must use a shared palette as rendering from a shared +// palette which is cached in screen pixel format in RAM is faster). STBAR is "alpha" however it is rendered opaquely +// always for speed +static std::vector alpha_shpal_grey_graphics = { + "STBAR", + "STARMS", +}; + +// 16 color status bar graphics, alpha for rendering simplicity/space +static std::vector alpha16_status_vpatches = { + "STYSNUM0", + "STYSNUM1", + "STYSNUM2", + "STYSNUM3", + "STYSNUM4", + "STYSNUM5", + "STYSNUM6", + "STYSNUM7", + "STYSNUM8", + "STYSNUM9", + "STKEYS0", + "STKEYS1", + "STKEYS2", + "STKEYS3", + "STKEYS4", + "STKEYS5", + "STGNUM0", // unused todo remove + "STGNUM1", // unused todo remove + "STGNUM2", + "STGNUM3", + "STGNUM4", + "STGNUM5", + "STGNUM6", + "STGNUM7", + "STGNUM8", // unused todo remove + "STGNUM9", // unused todo remove +}; + +// these player background graphics are checked specially to see if they are a single color center with a border (they are in regular doom). +// if so they are encodded in a special vpatch type which is quicker to redner (and more compact)... the rendering speed is important +// otherwise we run out of stbar scanline time in network games +static std::vector special_player_background_vpatches = { + "STFB0", + "STFB1", + "STFB2", + "STFB3", + "STPB0", + "STPB1", + "STPB2", + "STPB3", +}; + +// status bar face graphics use 64 color palettes +static std::vector run64_face_vpatches = { + "STFST00", + "STFST01", + "STFST02", + "STFTR00", + "STFTL00", + "STFOUCH0", + "STFEVL0", + "STFKILL0", + "STFST10", + "STFST11", + "STFST12", + "STFTR10", + "STFTL10", + "STFOUCH1", + "STFEVL1", + "STFKILL1", + "STFST20", + "STFST21", + "STFST22", + "STFTR20", + "STFTL20", + "STFOUCH2", + "STFEVL2", + "STFKILL2", + "STFST30", + "STFST31", + "STFST32", + "STFTR30", + "STFTL30", + "STFOUCH3", + "STFEVL3", + "STFKILL3", + "STFST40", + "STFST41", + "STFST42", + "STFTR40", + "STFTL40", + "STFOUCH4", + "STFEVL4", + "STFKILL4", + "STFGOD0", + "STFDEAD0", +}; + +// remaining <16 color graphics ... actually less - we don't use another shared palette as the palettes are +// small and we're short of scratch_x space. these are encoded as "pixel runs" for rendering speed +static std::vector run16_misc_vpatches = { + "AMMNUM0", + "AMMNUM1", + "AMMNUM2", + "AMMNUM3", + "AMMNUM4", + "AMMNUM5", + "AMMNUM6", + "AMMNUM7", + "AMMNUM8", + "AMMNUM9", + "END0", + "END1", + "END2", + "END3", + "END4", + "END5", + "END6", +}; + +// remaining <64 color graphics ... these are encoded as "pixel runs" for rendering speed +static std::vector run64_misc_vpatches = { + "M_SKULL1", + "M_SKULL2", + "M_THERMO", + "WIA00000", + "WIA00001", + "WIA00002", + "WIA00100", + "WIA00101", + "WIA00102", + "WIA00200", + "WIA00201", + "WIA00202", + "WIA00300", + "WIA00301", + "WIA00302", + "WIA00400", + "WIA00401", + "WIA00402", + "WIA00500", + "WIA00501", + "WIA00502", + "WIA00600", + "WIA00601", + "WIA00602", + "WIA00700", + "WIA00701", + "WIA00702", + "WIA00800", + "WIA00801", + "WIA00802", + "WIA00900", + "WIA00901", + "WIA00902", + "WIA10000", + "WIA10100", + "WIA10200", + "WIA10300", + "WIA10400", + "WIA10500", + "WIA10600", + "WIA10700", + "WIA10701", + "WIA10702", + "WIA20000", + "WIA20001", + "WIA20002", + "WIA20100", + "WIA20101", + "WIA20102", + "WIA20200", + "WIA20201", + "WIA20202", + "WIA20300", + "WIA20301", + "WIA20302", + "WIA20400", + "WIA20401", + "WIA20402", + "WIA20500", + "WIA20501", + "WIA20502", +}; + +// 256 color graphics ... note "raw" refers to the fact that we encode them with alpha rather than pixel runs +static std::vector run256_misc_vpatches = { + "M_DOOM", +}; + +// splash screen patches (which need to be encoded as real patches) +static std::vector splash_graphics = { + "HELP", + "HELP1", + "HELP2", + "CREDIT", + "ENDPIC", + "TITLEPIC", + "INTERPIC", + "VICTORY2", + "BOSSBACK", + "PFUB1", + "PFUB2", +}; +#if __BIG_ENDIAN__ +#error didn not bother +#endif + +const std::vector sprite_names = { + "TROO", "SHTG", "PUNG", "PISG", "PISF", "SHTF", "SHT2", "CHGG", "CHGF", "MISG", + "MISF", "SAWG", "PLSG", "PLSF", "BFGG", "BFGF", "BLUD", "PUFF", "BAL1", "BAL2", + "PLSS", "PLSE", "MISL", "BFS1", "BFE1", "BFE2", "TFOG", "IFOG", "PLAY", "POSS", + "SPOS", "VILE", "FIRE", "FATB", "FBXP", "SKEL", "MANF", "FATT", "CPOS", "SARG", + "HEAD", "BAL7", "BOSS", "BOS2", "SKUL", "SPID", "BSPI", "APLS", "APBX", "CYBR", + "PAIN", "SSWV", "KEEN", "BBRN", "BOSF", "ARM1", "ARM2", "BAR1", "BEXP", "FCAN", + "BON1", "BON2", "BKEY", "RKEY", "YKEY", "BSKU", "RSKU", "YSKU", "STIM", "MEDI", + "SOUL", "PINV", "PSTR", "PINS", "MEGAFGA", "SUIT", "PMAP", "PVIS", "CLIP", "AMMO", + "ROCK", "BROK", "CELL", "CELP", "SHEL", "SBOX", "BPAK", "BFUG", "MGUN", "CSAW", + "LAUN", "PLAS", "SHOT", "SGN2", "COLU", "SMT2", "GOR1", "POL2", "POL5", "POL4", + "POL3", "POL1", "POL6", "GOR2", "GOR3", "GOR4", "GOR5", "SMIT", "COL1", "COL2", + "COL3", "COL4", "CAND", "CBRA", "COL6", "TRE1", "TRE2", "ELEC", "CEYE", "FSKU", + "COL5", "TBLU", "TGRN", "TRED", "SMBT", "SMGT", "SMRT", "HDB1", "HDB2", "HDB3", + "HDB4", "HDB5", "HDB6", "POB1", "POB2", "BRS1", "TLMP", "TLP2" +}; + +// these special textures will be placed first, as they are handled specially in the code, and must be looked up by index/enum value +// note they also delibrately have small indexes +std::vector special_textures = { + NAMED_TEXTURE_LIST +}; + +// these special textures will be placed first, as they are handled specially in the code, and must be looked up by index/enum value +// note they also delibrately have small indexes +std::vector special_flats = { + NAMED_FLAT_LIST +}; + +std::vector vpatch_names = { + VPATCH_LIST +}; + +typedef struct { + const char *first; + const char *last; +} anim_range_t; + +const anim_range_t flat_animdefs[] = +{ + {FLAT_NAME(NUKAGE1), FLAT_NAME(NUKAGE3)}, + {FLAT_NAME(FWATER1), FLAT_NAME(FWATER4)}, + {FLAT_NAME(SWATER1), FLAT_NAME(SWATER4)}, + {FLAT_NAME(LAVA1), FLAT_NAME(LAVA4)}, + {FLAT_NAME(BLOOD1), FLAT_NAME(BLOOD3)}, + + // DOOM II flat animations. + { FLAT_NAME(RROCK05), FLAT_NAME(RROCK08) }, + { FLAT_NAME(SLIME01), FLAT_NAME(SLIME04) }, + { FLAT_NAME(SLIME05), FLAT_NAME(SLIME08) }, + { FLAT_NAME(SLIME09), FLAT_NAME(SLIME12) }, +}; + +const anim_range_t tex_animdefs[] = { + { TEXTURE_NAME(SLADRIP1), TEXTURE_NAME(SLADRIP3) }, + + { TEXTURE_NAME(BLODGR1), TEXTURE_NAME(BLODGR4) }, + { TEXTURE_NAME(BLODRIP1), TEXTURE_NAME(BLODRIP4) }, + { TEXTURE_NAME(FIREWALA), TEXTURE_NAME(FIREWALL) }, + { TEXTURE_NAME(GSTFONT1), TEXTURE_NAME(GSTFONT3) }, + { TEXTURE_NAME(FIRELAV3), TEXTURE_NAME(FIRELAVA) }, + { TEXTURE_NAME(FIREMAG1), TEXTURE_NAME(FIREMAG3) }, + { TEXTURE_NAME(FIREBLU1), TEXTURE_NAME(FIREBLU2) }, + { TEXTURE_NAME(ROCKRED1), TEXTURE_NAME(ROCKRED3) }, + + { TEXTURE_NAME(BFALL1), TEXTURE_NAME(BFALL4) }, + { TEXTURE_NAME(SFALL1), TEXTURE_NAME(SFALL4) }, + { TEXTURE_NAME(WFALL1), TEXTURE_NAME(WFALL4) }, + { TEXTURE_NAME(DBRAIN1), TEXTURE_NAME(DBRAIN4) }, +}; + +void __attribute__((noreturn)) fail(const char *msg, ...) { + va_list va; + va_start(va, msg); + vprintf(msg, va); + va_end(va); + printf("\n"); + exit(1); +} + +struct texture { + whdtexture_t whd{0}; +}; + +struct texture_index { + std::map lookup; + std::vector textures; + + int find(const std::string &name) const { + if (name == "-") return 0; + auto it = lookup.find(to_lower(name)); + if (it == lookup.end()) { + fail("Unable to locate texture %s\n", name.c_str()); + } + return it->second; + } +}; + +extern "C" { +#include "adpcm-lib.h" +} + +template +T get_field(const std::vector &data, int offset) { + assert(data.begin() + offset <= data.end()); + return *(const T *) (data.data() + offset); +} + +template +void append_field(std::vector &data, const T &field) { + const uint8_t *p = (uint8_t *) &field; + data.template insert(data.end(), p, p + sizeof(T)); +} + +template +T get_field_inc(const std::vector &data, int &offset) { + assert(data.begin() + offset <= data.end()); + T rc = *(const T *) (data.data() + offset); + offset += sizeof(rc); + return rc; +} + +static void usage() { + throw std::invalid_argument("usage: whd_gen "); +} + +std::set music_lumpnames = { + "d_e1m1", + "d_e1m2", + "d_e1m3", + "d_e1m4", + "d_e1m5", + "d_e1m6", + "d_e1m7", + "d_e1m8", + "d_e1m9", + "d_e2m1", + "d_e2m2", + "d_e2m3", + "d_e2m4", + "d_e2m5", + "d_e2m6", + "d_e2m7", + "d_e2m8", + "d_e2m9", + "d_e3m1", + "d_e3m2", + "d_e3m3", + "d_e3m4", + "d_e3m5", + "d_e3m6", + "d_e3m7", + "d_e3m8", + "d_e3m9", + "d_inter", + "d_intro", + "d_bunny", + "d_victor", + "d_introa", + "d_runnin", + "d_stalks", + "d_countd", + "d_betwee", + "d_doom", + "d_the_da", + "d_shawn", + "d_ddtblu", + "d_in_cit", + "d_dead", + "d_stlks2", + "d_theda2", + "d_doom2", + "d_ddtbl2", + "d_runni2", + "d_dead2", + "d_stlks3", + "d_romero", + "d_shawn2", + "d_messag", + "d_count2", + "d_ddtbl3", + "d_ampie", + "d_theda3", + "d_adrian", + "d_messg2", + "d_romer2", + "d_tense", + "d_shawn3", + "d_openin", + "d_evil", + "d_ultima", + "d_read_m", + "d_dm2ttl", + "d_dm2int", +}; + +std::set sfx_lumpnames = { + "dspistol", + "dsshotgn", + "dssgcock", + "dsdshtgn", + "dsdbopn", + "dsdbcls", + "dsdbload", + "dsplasma", + "dsbfg", + "dssawup", + "dssawidl", + "dssawful", + "dssawhit", + "dsrlaunc", + "dsrxplod", + "dsfirsht", + "dsfirxpl", + "dspstart", + "dspstop", + "dsdoropn", + "dsdorcls", + "dsstnmov", + "dsswtchn", + "dsswtchx", + "dsplpain", + "dsdmpain", + "dspopain", + "dsvipain", + "dsmnpain", + "dspepain", + "dsslop", + "dsitemup", + "dswpnup", + "dsoof", + "dstelept", + "dsposit1", + "dsposit2", + "dsposit3", + "dsbgsit1", + "dsbgsit2", + "dssgtsit", + "dscacsit", + "dsbrssit", + "dscybsit", + "dsspisit", + "dsbspsit", + "dskntsit", + "dsvilsit", + "dsmansit", + "dspesit", + "dssklatk", + "dssgtatk", + "dsskepch", + "dsvilatk", + "dsclaw", + "dsskeswg", + "dspldeth", + "dspdiehi", + "dspodth1", + "dspodth2", + "dspodth3", + "dsbgdth1", + "dsbgdth2", + "dssgtdth", + "dscacdth", + "dsskldth", + "dsbrsdth", + "dscybdth", + "dsspidth", + "dsbspdth", + "dsvildth", + "dskntdth", + "dspedth", + "dsskedth", + "dsposact", + "dsbgact", + "dsdmact", + "dsbspact", + "dsbspwlk", + "dsvilact", + "dsnoway", + "dsbarexp", + "dspunch", + "dshoof", + "dsmetal", + "dschgun", // this is a link, does it actually exist? + "dstink", + "dsbdopn", + "dsbdcls", + "dsitmbk", + "dsflame", + "dsflamst", + "dsgetpow", + "dsbospit", + "dsboscub", + "dsbossit", + "dsbospn", + "dsbosdth", + "dsmanatk", + "dsmandth", + "dssssit", + "dsssdth", + "dskeenpn", + "dskeendt", + "dsskeact", + "dsskesit", + "dsskeatk", + "dsradio", +}; + +bool convert_sound(std::pair &e); + +void dump_patch(const char *name, int num, lump &patch); + +struct patch_header { + short width; // bounding box size + short height; + short leftoffset; // pixels to the left of origin + short topoffset; // pixels below the origin +}; + +statsomizer patch_meta_size("Patch meta size"); +statsomizer patch_decoder_size("Patch decoder size"); +statsomizer patch_orig_meta_size("Patch orig meta size"); +statsomizer patch_widths("Patch width"); +statsomizer patch_heights("Patch height"); +statsomizer patch_left_offsets("Patch left offset"); +statsomizer patch_top_offsets("Patch top offset"); +statsomizer vpatch_left_offsets("VPatch left offset"); +statsomizer vpatch_top_offsets("VPatch top offset"); +statsomizer vpatch_left_0offsets("VPatch left zero offset"); +statsomizer vpatch_top_0offsets("VPatch top zero offset"); +statsomizer patch_sizes("Patch size"); +statsomizer patch_colors("Patch colors"); +statsomizer patch_column_colors("Patch column colors"); +statsomizer patch_columns_under_colors[] = { + statsomizer("2 color post"), + statsomizer("4 color post"), + statsomizer("8 color post"), + statsomizer("16 color post"), + statsomizer("32 color post"), + statsomizer("64 color post"), + statsomizer("128 color post"), + statsomizer("256 color post"), +}; +statsomizer patch_post_counts("Patch column posts"); +statsomizer patch_one("Patch one post columns"); +statsomizer patch_all_post_counts(" Patcl total posts"); +statsomizer patch_pixels("Patch total pixels"); +statsomizer patch_col_hack_huff_pixels("Patch Col Hack huff size"); +statsomizer patch_hack_huff_pixels("Patch Hack huff size"); + +statsomizer subsector_length("Subsector length"); +//#define ZLIB_HEADER 1 + +statsomizer texture_col_metadata("Texture column metadata size"); +statsomizer texture_transparent("Textures with transparency"); +statsomizer texture_transparent_patch_count("Transparent tex patch count"); +statsomizer texture_column_patches("Texture column patches"); +statsomizer texture_column_patches1("Texture column 1 patch"); +statsomizer texture_single_patch("Texture single patch"); +statsomizer texture_single_patch00("Texture single patch 00"); + +statsomizer sector_lightlevel("Sector Lightlevel"); +statsomizer sector_floorheight("Sector Floor Height"); +statsomizer sector_ceilingheight("Sector Ceiling Height"); +statsomizer sector_special("Sector Special num"); +statsomizer color_runs("Color runs"); +statsomizer same_columns("Same columns"); + +statsomizer sfx_orig_size("SFX original size"); +statsomizer sfx_new_size("SFX compressed size"); +statsomizer patch_orig_size("Patch original size"); +statsomizer patch_new_size("Patch compressed size"); +statsomizer vpatch_orig_size("VPatch original size"); +statsomizer vpatch_new_size("VPatch compressed size"); +statsomizer tex_orig_size("Tex original size"); +statsomizer tex_new_size("Tex compressed size"); + +uint32_t hash; + +void save_png(wad &wad, std::string prefix, std::string name, uint w, uint h, std::vector data, const std::vector palette_colors) { + lodepng::State state; + lodepng_state_init(&state); + state.info_raw.colortype = LCT_RGBA; + state.info_raw.bitdepth = 8; // one byte per pixel still + state.encoder.auto_convert = 0; + // hack converted global + std::vector pixels(data.size()); + for (size_t i = 0; i < data.size(); i++) { + if (data[i] < 0) pixels[i] = 0; + else pixels[i] = palette_colors[data[i]]; + } + std::vector png_data; + auto error = lodepng::encode(png_data, (byte *) pixels.data(), w, h, state); + if (!error) { + error = lodepng::save_file(png_data, std::string("output/").append(prefix).append("/").append(name).append( + ".png").c_str()); + } +} + +void save_png(wad &wad, std::string prefix, std::string name, uint w, uint h, std::vector data) { + lump palette; + wad.get_lump("playpal", palette); + std::vector palette_colors(256); + for (int i = 0; i < 256; i++) { + palette_colors[i] = + 0xff000000 | (palette.data[i * 3 + 2] << 16) | (palette.data[i * 3 + 1] << 8) | (palette.data[i * 3]); + } + save_png(wad, prefix, name, w, h, data, palette_colors); +} + +std::vector unpack_patch(lump &patch) { + const patch_header *ph = (const patch_header *) patch.data.data(); + auto pixels = std::vector(ph->width * ph->height, -1); + for (int col = 0; col < ph->width; col++) { + uint32_t col_offset = *(uint32_t *) (patch.data.data() + 8 + col * 4); + const uint8_t *post = patch.data.data() + col_offset; + while (post[0] != 0xff) { + for (int y = 3; y < 3 + post[1]; y++) { + size_t index = col + (post[0] + y - 3) * ph->width; + assert(index < pixels.size()); + pixels[index] = post[y]; + } + post += 4 + post[1]; + } + } + return pixels; +} + +int opaque_pixels; +int transparent_pixels; +std::vector> to_merged_posts(const std::vector& pix, uint width, uint height, std::vector& same, bool& have_same) { + std::vector> merged_posts(width); + same.clear(); + same.resize(width); + have_same = false; +#if 1 + for(int i=1;i<(int)width;i++) { + int match = 0; + for(int j=0;j= 0; + // pix[i + y * ph.width] = 0xfc; + } + same_columns.record(non_transparent); + } + } +#endif + for(int x=0;x<(int)width;x++) { + if (!same[x]) { + for(int y = 0; y < (int)(width * height); y += width) { + if (pix[x+y]>=0) { + merged_posts[x].push_back(pix[x+y]); + opaque_pixels++; + } else { + transparent_pixels++; + } + } + } + } + return merged_posts; +} + +template void output_min_max_c3(BO &bit_output, huffman_encoding, H> &huff, bool zero_flag, bool group8) { + if (huff.empty()) { + assert(false); // should be handled at a higher level + return; + } + uint8_t min = huff.get_first_symbol().second; + uint8_t max = min; + for(const auto &s : huff.get_symbols()) { + if (!s.first) { + max = std::max(max, s.second); + } + }; + bit_output->write(bit_sequence(min, 8)); + bit_output->write(bit_sequence(max, 8)); + int min_cl = huff.get_min_code_length(); + int max_cl = huff.get_max_code_length(); + assert(max_cl < 16); + assert(min_cl <= max_cl); + bit_output->write(bit_sequence(min_cl, 4)); + bit_output->write(bit_sequence(max_cl, 4)); + if (min_cl == max_cl && zero_flag) { + for (int val = min; val <= max; val++) { + int length = huff.get_code_length(std::make_pair(0,val)); + assert(!length || length == min_cl); + bit_output->write(bit_sequence(length != 0, 1)); + } + } else { + uint bit_count = 32 - __builtin_clz(max_cl - min_cl + (zero_flag ? 0 : 1)); + auto stats = huff.get_stats(); + if (zero_flag) { + if (group8) { + for (uint base_val = min; base_val <= max; base_val += 8) { + uint8_t mask = 0; + uint8_t bits = 0; + for (uint i = 0; i <= std::min(7u, max - base_val); i++) { + if (huff.get_code_length(std::make_pair(0,base_val + i))) mask |= 1u << i; + bits |= 1u << i; + + } + if (mask == 0) { + bit_output->write(bit_sequence(0b01, 2)); + } else if (mask == bits) { + bit_output->write(bit_sequence(0b11, 2)); + for (uint i = 0; i <= std::min(7u, max - base_val); i++) { + int length = huff.get_code_length(std::make_pair(0,base_val + i)); + assert(length); + length -= min_cl; + bit_output->write(bit_sequence(length, bit_count)); + } + } else { + bit_output->write(bit_sequence(0b0, 1)); + for (uint i = 0; i <= std::min(7u, max - base_val); i++) { + int length = huff.get_code_length(std::make_pair(0,base_val + i)); + bit_output->write(bit_sequence(length != 0, 1)); + if (length) { + length -= min_cl; + bit_output->write(bit_sequence(length, bit_count)); + } + } + } + } + } else { + for (int val = min; val <= max; val++) { + int length = huff.get_code_length(std::make_pair(0,val)); + bit_output->write(bit_sequence(length != 0, 1)); + if (length) { + length -= min_cl; + bit_output->write(bit_sequence(length, bit_count)); + } + } + } + } else { + for (int val = min; val <= max; val++) { + int length = huff.get_code_length(std::make_pair(0,val)); + if (length) { + length -= min_cl; + } else { + length = (1u << bit_count) - 1; + } + bit_output->write(bit_sequence(length, bit_count)); + } + } + } +} + +template +void output_min_max_best_c3(BO &bit_output, huffman_encoding, H> &huff) { + bit_output->write(bit_sequence(!huff.empty(), 1)); + if (huff.empty()) return; + auto bo1 = std::make_shared(); + output_min_max_c3(bo1, huff, true, true); + auto bo2 = std::make_shared(); + output_min_max_c3(bo2, huff, true, false); + auto bo3 = std::make_shared(); + output_min_max_c3(bo3, huff, false, false); + // printf("BO1/2/3 %d %d %d\n", (int) bo1->bit_size(), (int) bo2->bit_size(), (int) bo3->bit_size()); + size_t min = std::min(bo1->bit_size(), std::min(bo2->bit_size(), bo3->bit_size())); + if (bo1->bit_size() == min) { + bit_output->write(bit_sequence(1, 1)); + bo1->write_to(bit_output); + } else if (bo2->bit_size() == min) { + bit_output->write(bit_sequence(0, 1)); + bo2->write_to(bit_output); + } else { + // printf("WA\n"); // now seems to happen, but rare so not sure we care enough to add complexity + if (bo1->bit_size() < bo2->bit_size()) { + bit_output->write(bit_sequence(1, 1)); + bo1->write_to(bit_output); + } else { + bit_output->write(bit_sequence(0, 1)); + bo2->write_to(bit_output); + } + //assert(false); // doesn't seem to be ever, plan to remove + // bit_output->write(bit_sequence(0b11, 2)); + // bo3->write_to(bit_output); + } +} + +template +uint decoder_size(huffman_encoding &huff) { + return th_decoder_size(huff.get_symbols().size(), huff.get_max_code_length()); +} + +template +void output_raw_pixels(BO &bit_output, huffman_encoding &huff) { + if (huff.empty()) { + assert(false); // should be handled at a higher level + return; + } + auto stats = huff.get_stats(); + int min_cl = huff.get_min_code_length(); + int max_cl = huff.get_max_code_length(); + assert(max_cl < 16); + assert(min_cl <= max_cl); + bit_output->write(bit_sequence(min_cl, 4)); + bit_output->write(bit_sequence(max_cl, 4)); + int bit_count = 32 - __builtin_clz(max_cl - min_cl); + std::vector set(32); + for(const auto &e : huff.get_symbols()) { + set[e>>3] |= 1u << (e&7u); + } + + for (uint p = 0; p < 32; p++) { + bit_output->write(bit_sequence(set[p]!=0, 1)); + if (set[p]!=0) { + for (uint bit = 0; bit < 8; bit++) { + if (set[p] & (1u << bit)) { + bit_output->write(bit_sequence(1, 1)); + auto length = huff.get_code_length(p*8 + bit); + assert(!max_cl || length); + if (min_cl == max_cl) { + assert(length == min_cl); + } else { + bit_output->write(bit_sequence(length - min_cl, bit_count)); + } + } else { + bit_output->write(bit_sequence(0, 1)); + } + } + } + } +} + +template +void output_raw_pixels_c3(BO &bit_output, huffman_encoding, H> &huff) { + if (huff.empty()) { + assert(false); // should be handled at a higher level + return; + } + auto stats = huff.get_stats(); + int min_cl = huff.get_min_code_length(); + int max_cl = huff.get_max_code_length(); + assert(max_cl < 16); + assert(min_cl <= max_cl); + bit_output->write(bit_sequence(min_cl, 4)); + bit_output->write(bit_sequence(max_cl, 4)); + int bit_count = 32 - __builtin_clz(max_cl - min_cl); + std::vector set(32); + for(const auto &e : huff.get_symbols()) { + if (!e.first) set[e.second>>3] |= 1u << (e.second&7u); + } + + for (uint p = 0; p < 32; p++) { + bit_output->write(bit_sequence(set[p]!=0, 1)); + if (set[p]!=0) { + for (uint bit = 0; bit < 8; bit++) { + if (set[p] & (1u << bit)) { + bit_output->write(bit_sequence(1, 1)); + auto length = huff.get_code_length(std::make_pair(0,p*8 + bit)); + assert(!max_cl || length); + if (min_cl == max_cl) { + assert(length == min_cl); + } else { + bit_output->write(bit_sequence(length - min_cl, bit_count)); + } + } else { + bit_output->write(bit_sequence(0, 1)); + } + } + } + } +} + + +statsomizer cp1_pixels("CP1 post pixels"); +statsomizer cp1_run("CP1 run"); +statsomizer cp1_raw_run("CP1 raw run"); +statsomizer cp1_size("CP1 size"); +statsomizer cp2_size("CP2 size"); +statsomizer cp_wtf_size("CP WTF size"); +statsomizer cp_po_size("CP POP size"); +statsomizer cp_size("CP size"); +template +std::ostream &operator<<(std::ostream &os, const std::pair &v) { + os << "(" << static_cast::ostream_type>(v.first) << ", " << static_cast::ostream_type>(v.second) << ")"; + return os; +} + +uint consider_compress3(const std::string& name, const std::vector>& posts, + std::shared_ptr& decoder_output, std::vector>& zposts, + uint width, uint height, uint& decoder_size) { + // enum { + // cp1_raw = 0, + // cp1_delta3 = 1, + // }; + symbol_sink>> sink("pixel/delta"); + + zposts.clear(); + zposts.resize(width); + decoder_output = std::make_shared(); + int last_color = -1; + int color_change_count = 0; + for(int pass=0;pass<2;pass++) { +// printf("BEGIN %s %d\n", name.c_str(), pass); + for(int x=0;x<(int)width;x++) { + const auto& post = posts[x]; + if (pass) { + zposts[x] = std::make_shared(); + sink.begin_output(zposts[x]); + } + if (post.empty()) continue; + sink.output(std::make_pair(false, post[0])); + for(int y=1;y<(int)post.size();) { + int d1 = post[y]-post[y-1]; + int code = -1; + if (abs(d1) < 4) { + code = d1 + 3; + } + if (code == -1) { + if (last_color != post[y]) { + last_color = post[y]; + color_change_count++; + } + sink.output(std::make_pair(false, post[y])); y++; + } else if (code < 7) { + sink.output(std::make_pair(true, code)); y++; + } else { + assert(false); + } + } + } + if (!pass) { + if (color_change_count == 0) { // very pointless but CYAN in doom2 is this + // this is simpler than adding special case in the decode + printf("warning: zero colors for compression, adding some dummies\n"); + sink.output(std::make_pair(false, 0)); + sink.output(std::make_pair(false, 1)); + } else if (color_change_count == 1) { + // this is simpler than adding special case in the decode + printf("warning: only one color for compression, adding a dummy\n"); + sink.output(std::make_pair(false, (uint8_t)(last_color + 1))); + } + + sink.begin_output(decoder_output); + output_raw_pixels_c3(decoder_output, sink.huff); + uint min_cl = sink.huff.get_min_code_length(); + uint bit_count = 32 - __builtin_clz(1 + sink.huff.get_max_code_length() - sink.huff.get_min_code_length()); + for(uint8_t i=0;i<7;i++) { + uint len = sink.huff.get_code_length(std::make_pair(1, i)); + if (len) { + decoder_output->write(bit_sequence(len-min_cl, bit_count)); + } else { + decoder_output->write(bit_sequence((1u << bit_count) -1, bit_count)); + } + } + } + } + + auto check = std::make_shared(); + decoder_output->write_to(check); + for(auto &col_bo : zposts) { + col_bo->write_to(check); + } + auto result = check->get_output(); +#if 1 // must be 1 now as we need to set decoder_size + byte_vector_bit_input biv(result); + auto bi = create_bip(biv); + + uint16_t buf[512]; + uint8_t tmp[1024]; + uint16_t *pos = buf; + uint pos_size = count_of(buf); + uint16_t *decoder = pos; + pos = read_raw_pixels_decoder_c3(bi, pos, pos_size, tmp, count_of(tmp)); + decoder_size = pos - buf; + pos_size -= (pos - buf); + assert(pos < buf + count_of(buf)); + std::vector> decoded(width); + for(int x=0;x<(int)width;x++) { + auto& post = decoded[x]; + while (post.size() < posts[x].size()) { + uint16_t p = th_decode_16(decoder, bi); + if (p < 256) { + post.push_back(p); + } else { + int prev = post.size() - 1; + assert(prev>=0); + assert(1 == p >> 8); + p &= 0xff; + assert(p<7); + post.push_back(post[prev] + p - 3); + } + } + if (!std::equal(post.begin(), post.end(), posts[x].begin(), posts[x].end())) { + if (post.size() == posts[x].size()) { + for(int i=0;i<(int)post.size();i++) { + printf("%d %02x %02x %c\n", i, posts[x][i], post[i], posts[x][i] != post[i] ? '*' : ' '); + } + } + fail("Post mismatcher %d %d vs %d\n", x, (int)post.size(), (int)posts[x].size()); + } + } + uncreate_bip(biv, bi); +#endif + + cp1_size.record(result.size()); + return result.size(); +} + +uint consider_compress_pixels_only(const std::string& name, const std::vector>& posts, + std::shared_ptr& decoder_output, std::vector>& zposts, uint width, uint height, uint& decoder_size_out) { + symbol_sink> raw_pixel_sink("Raw Pixel"); + sink_wrappers> wrappers{raw_pixel_sink}; + + zposts.clear(); + zposts.resize(width); + decoder_output = std::make_shared(); + int last_color = -1; + int color_change_count = 0; + for(int pass=0;pass<2;pass++) { + for(int x=0;x<(int)width;x++) { + const auto& post = posts[x]; + if (pass) { + zposts[x] = std::make_shared(); + wrappers.begin_output(zposts[x]); + } else { + cp1_pixels.record(post.size()); + } + for(uint y=0;y(); + output_raw_pixels(bo2, raw_pixel_sink.huff); + auto bo3 = std::make_shared(); + output_min_max_best(bo3, raw_pixel_sink.huff); + //printf( " bo2 %u bo3 %u\n", bo2->bit_size(), bo3->bit_size()); + if (bo2->bit_size() < bo3->bit_size()) { + decoder_output->write(bit_sequence(0, 1)); + bo2->write_to(decoder_output); + } else { + decoder_output->write(bit_sequence(1, 1)); + bo3->write_to(decoder_output); + } + } + } + + auto check = std::make_shared(); + decoder_output->write_to(check); + for(auto &col_bo : zposts) { + col_bo->write_to(check); + } + auto result = check->get_output(); + cp_po_size.record(result.size()); +#if 1 // must be 1 now as we set have to set decoder_size + byte_vector_bit_input biv(result); + auto bi = create_bip(biv); + + uint16_t buf[512]; + uint8_t tmp[512]; + uint16_t *pos = buf; + uint pos_size = count_of(buf); + uint16_t *rp_decoder = buf; + if (th_bit(bi)) { + pos = th_read_simple_decoder(bi, pos, pos_size, tmp, count_of(tmp)); + } else { + pos = read_raw_pixels_decoder(bi, pos, pos_size, tmp, count_of(tmp)); + } + decoder_size_out = pos - buf; + th_make_prefix_length_table(rp_decoder, tmp); + assert(pos < buf + count_of(buf)); + std::vector> decoded(width); + for(int x=0;x<(int)width;x++) { + auto& post = decoded[x]; + for(int i=0;i<(int)posts[x].size();i++) { +// uint8_t pix = th_decode(rp_decoder, bi); + uint8_t pix = th_decode_table_special(rp_decoder, tmp, bi); + post.push_back(pix); + } + if (!std::equal(post.begin(), post.end(), posts[x].begin(), posts[x].end())) { + printf("Post mismatcher %d %d vs %d\n", x, (int)post.size(), (int)posts[x].size()); + if (post.size() == posts[x].size()) { + for(int i=0;i<(int)post.size();i++) { + printf("%d %02x %02x %c\n", i, posts[x][i], post[i], posts[x][i] != post[i] ? '*' : ' '); + } + } + } + } + uncreate_bip(biv, bi); +#endif + + return result.size(); +} + +uint consider_compress_data(const std::string& name, std::vector& input, + std::shared_ptr& output) { + symbol_sink> byte_sink("Raw Data"); + sink_wrappers> wrappers{byte_sink}; + + output = std::make_shared(); + for(int pass=0;pass<2;pass++) { + for(const auto & b : input) { + byte_sink.output(b); + } + if (!pass) { + wrappers.begin_output(output); + auto bo2 = std::make_shared(); + output_raw_pixels(bo2, byte_sink.huff); + auto bo3 = std::make_shared(); + output_min_max_best(bo3, byte_sink.huff); + if (bo2->bit_size() < bo3->bit_size()) { + output->write(bit_sequence(0, 1)); + bo2->write_to(output); + } else { + output->write(bit_sequence(1, 1)); + bo3->write_to(output); + } + } + } + + auto check = std::make_shared(); + output->write_to(check); + auto result = check->get_output(); + cp_po_size.record(result.size()); +#if 1 + byte_vector_bit_input biv(result); + auto bi = create_bip(biv); + + uint16_t buf[512]; + uint8_t tmp[512]; + uint16_t *pos = buf; + uint pos_size = count_of(buf); + uint16_t *rp_decoder = buf; + if (th_bit(bi)) { + pos = th_read_simple_decoder(bi, pos, pos_size, tmp, count_of(tmp)); + } else { + pos = read_raw_pixels_decoder(bi, pos, pos_size, tmp, count_of(tmp)); + } + th_make_prefix_length_table(rp_decoder, tmp); + assert(pos < buf + count_of(buf)); + std::vector decoded; + for(int i=0;i<(int)input.size();i++) { + uint8_t pix = th_decode_table_special(rp_decoder, tmp, bi); + decoded.push_back(pix); + } + if (!std::equal(decoded.begin(), decoded.end(), input.begin(), input.end())) { + printf("Post mismatcher %d vs %d\n", (int)decoded.size(), (int)input.size()); + if (decoded.size() == input.size()) { + for(int i=0; i < (int)decoded.size(); i++) { + printf("%d %02x %02x %c\n", i, input[i], decoded[i], input[i] != decoded[i] ? '*' : ' '); + } + } + fail("post mismatched"); + } + uncreate_bip(biv, bi); +#endif + + return result.size(); +} + +void patch_for_each_pixel(lump &patch, std::function callback) { + const patch_header *ph = (const patch_header *) patch.data.data(); + for (int col = 0; col < ph->width; col++) { + uint32_t col_offset = *(uint32_t *) (patch.data.data() + 8 + col * 4); + const uint8_t *post = patch.data.data() + col_offset; + while (post[0] != 0xff) { + for (int y = 3; y < 3 + post[1]; y++) { + callback(post[y]); + } + post += 4 + post[1]; + } + } +} + +symbol_stats patch_run_stats; +symbol_stats patch_width_stats; +symbol_stats patch_height_stats; + +const uint8_t bitcount8_table[256] = { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + }; + +void convert_patch(wad &wad, int num, lump &patch) { + dump_patch(patch.name.c_str(), num, patch); + auto ph = get_field(patch.data, 0); + auto pix = unpack_patch(patch); + converted_patch_count++; + assert(ph.height < 256); +#if DEBUG_SAVE_PNG + save_png(wad, "raw", patch.name, ph.width, ph.height, pix); +#endif +#if 0 + const int bigoff = 6; + for(auto& p : pix) { + if (p >=0) { + switch (p >> 4) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + p = (p + 0x10) & 0xe0; + break; + case 7: + case 8: + case 11: + case 12: + case 13: + p = (p & 0xf0) + bigoff; + break; + case 9: + case 10: + p = (p&0xf8) + 3; + break; + case 15: + if (p < 0xf8) p = 0xc0 + bigoff; + else p = 0xfb; + break; + default: + p = 0xfb; + } + } + } +#endif +#if 0 + auto pix_at = [&](int x, int y) { return pix[x + y * ph.width]; }; +#define MIN_RUN 3 + for(int i=0;i lengths(ph.height); + std::vector deltas(ph.height); +//#define COLORING 1 // constant rate +//#define COLORING 2 // flat +//#define COLORING 3 // small delta +//#define COLORING 4 // larger delta +#define COLORING 5 // largest delta +#if COLORING == 1 + for(int y=0;y y1 && lengths[y1]) { + if (lengths[y] < lengths[y1]) { + lengths[y] = y1 - y; + } else { + lengths[y1] = 0; + } + } + } + } + for(int y=0;y= MIN_RUN) { + int length = lengths[y]; + for(int y1 = y; y1 < y + lengths[y]; y1++) { + if (pix[i + y1 * ph.width] < 0) { + length--; + } + } + if (length >= MIN_RUN) { + int col; +#if COLORING == 1 + if (deltas[y] == 0) col = 0xfb; + else if (deltas[y] < 0) col = 0xf9; + else col = 0xc3; +#else + col = 0xfb; +#endif + for(int y1 = y; y1 < y + lengths[y]; y1++) { + if (pix[i + y1 * ph.width] >= 0) { + pix[i + y1 * ph.width] = col; + same_columns++; + } + } + same_columns--; + pix[i + y * ph.width] = 0xf9; + color_runs.record(length); + } + } + } + } +#endif +#if DEBUG_SAVE_PNG + save_png(wad, "flat", patch.name, ph.width, ph.height, pix); +#endif + int choice = 0; + uint best = std::numeric_limits::max(); + std::vector> zposts; + std::shared_ptr decoder_output; + std::vector> best_zposts; + std::shared_ptr best_decoder_output; + uint decoder_size; + uint best_decoder_size; + auto choose = [&](int c, uint size) { + if (size < best) { + choice = c; + best = size; + best_zposts = zposts; + best_decoder_output = std::make_shared(*decoder_output); + best_decoder_size = decoder_size; + } + }; + std::vector same; + bool have_same; + auto posts = to_merged_posts(pix, ph.width, ph.height, same, have_same); + // i think 0 and 3 may be fine on their own + choose(0, consider_compress_pixels_only(patch.name, posts, decoder_output, zposts, ph.width, ph.height, decoder_size)); +#if !USE_PIXELS_ONLY_PATCH + choose(1, consider_compress3(patch.name, posts, decoder_output, zposts, ph.width, ph.height, decoder_size)); + //choose(2, consider_compress_wtf(patch.name, posts, decoder_output, zposts, ph.width, ph.height)); + // todo put this back if we need 5K +// choose(3, consider_compress1(patch.name, posts, ph.width, ph.height, 2, 8)); +#endif + winners[choice]++; + cp_size.record(best); + converted_patch_size += ph.width * ph.height; + + // ------------------ + // start with metadata without post pixels + int full_or_same_column_count = 0; + uint orig_meta_size = 0; + for (int col = 0; col < ph.width; col++) { + uint32_t col_offset = *(uint32_t *) (patch.data.data() + 8 + col * 4); + const uint8_t *post = patch.data.data() + col_offset; + int post_count = 0; + int last = 0; + orig_meta_size++; + while (post[0] != 0xff) { + patch_run_stats.add(ph.height - last); + patch_run_stats.add(ph.height - post[0]); + last = post[0] + post[1]; + orig_meta_size+=2; + + post += 4 + post[1]; + post_count++; + } + if (last != ph.height) { + assert( last < ph.height); + patch_run_stats.add(ph.height - last); + } + if ((int)posts[col].size() == ph.height || same[col]) { + full_or_same_column_count++; + } + } + + uint16_t w = ph.width; + uint16_t h = ph.height; + uint16_t lo = ph.leftoffset; + uint16_t to = ph.topoffset; + bool extra = (lo>>8) || (to >> 8); + bool fully_opaque = full_or_same_column_count == ph.width; + uint8_t flags = extra; + flags |= fully_opaque << 1; + + std::vector col_offsets; + auto bitwidth = [](int v) { + assert(v>=0 && v<256); + return bitcount8_table[v]; + }; + + auto write_meta = [&](bool bit_aligned) { + auto bo = byte_vector_bit_output(); + col_offsets.clear(); + col_offsets.resize(ph.width+1); + for(int x=0;xwrite_to(bo); + + // column post metadata is afterwards and will be reversed + if (!fully_opaque) { + std::vector col_metadata; + uint32_t col_offset = *(uint32_t *) (patch.data.data() + 8 + x * 4); + const uint8_t *post = patch.data.data() + col_offset; + int last = 0; + while (last < ph.height) { + int run = post[0] - last; + if (post[0] == 0xff) run = ph.height - last; + if (run == 0 && last != 0) { + // see #if/todo below ... +// assert(post[0] == 0xff); + if (post[0]==0xff) break; + } + patch_run_stats.add(run); + col_metadata.emplace_back(run, bitwidth(ph.height-last)); // todo could shrink range by 1 after the first - doubt it makes much difference + last += run; + if (last >= ph.height) break; + run = post[1]; + // todo disabled for now as code might care +#if 0 + // seems like there is a post limit of 128... we can remove that + if (post[4 + post[1]] != 0xff && post[4 + post[1]] == last + run) { + run += post[5 + post[1]]; + assert(run < 256); + post += 4 + post[1]; + } +#endif + assert(run>0); + col_metadata.emplace_back(run, bitwidth(ph.height-last)); + last += run; + post += 4 + post[1]; + } + assert(last == ph.height); + int metadata_bits = 0; + for(const auto &bs : col_metadata) metadata_bits += bs.length(); + if (!bit_aligned) { + // we need the reverse the bo_col_meta to end on an 8 bit boundary, so we may need to pad in the middle + bo.write(bit_sequence(0, (8 - bo.bit_size() - metadata_bits) & 7)); + } + // write the metadata backwards + for(int i=col_metadata.size()-1;i>=0;i--) { + bo.write(col_metadata[i]); + } + } else { + if (!bit_aligned) bo.pad_to_byte(); + } + } + if (!bit_aligned) { + assert(!bo.bit_index()); // should already be aligned + } + col_offsets[ph.width] = bo.bit_size() / (bit_aligned ? 1 : 8); + return bo; + }; + auto metadata = write_meta(true); + if (metadata.bit_size() >= 0xff00) { + // have to do it byte aligned + metadata = write_meta(false); + flags |= 4; + } else { + bit_addressable_patch++; + } +// patch_meta_size.record(6 + 2 * ph.width + (extra?2:0) + metadata.bit_size()/8); + patch_meta_size.record(6 + 2 * ph.width + (extra?2:0) + metadata.bit_size()/8); + patch_decoder_size.record(best_decoder_output->bit_size()); + patch_orig_meta_size.record(8 + 2 * ph.width + orig_meta_size); + + std::vector p2; + // we want multiples of 2, so we do 2 byte width (actually use upper 7 bits for decoder size) and assume height is always <256 + p2.push_back(flags); + p2.push_back(w & 0xff); + if ((best_decoder_size>>2) > 127) { + fail("decoder size too big"); + } + p2.push_back((w >> 8) | ((best_decoder_size>>2)<<1)); + p2.push_back(h & 0xff); + p2.push_back(lo & 0xff); + p2.push_back(to & 0xff); + if (extra) { + p2.push_back(lo >> 8); + p2.push_back(to >> 8); + } + byte_vector_bit_output prefixed_decoder; + assert(choice < 2); + prefixed_decoder.write(bit_sequence(choice, 1)); + best_decoder_output->write_to(prefixed_decoder); + auto decoder = prefixed_decoder.get_output(); + assert(decoder.size() < 512); + p2.push_back(decoder.size()/2+1); + p2.insert(p2.end(), decoder.begin(), decoder.end()); + if (!(decoder.size() & 1)) p2.push_back(0); + assert(!(p2.size() & 1)); + + assert((int)col_offsets.size() == ph.width+1); + for(const auto &co : col_offsets) { + assert(co < 0xff00); + if (co < 0) { + p2.push_back((-co - 1) & 0xff); + p2.push_back(0xff); + } else { + p2.push_back(co & 0xff); + p2.push_back(co >> 8); + } + } + auto meta = metadata.get_output(); + p2.insert(p2.end(), meta.begin(), meta.end()); +#if 0 + symbol_stats pixel_stats; + patch_for_each_pixel(patch, [&](uint8_t p) { + pixel_stats.add(p); + }); + auto pixel_huffman = pixel_stats.create_huffman_encoding(); + byte_vector_bit_output output; + patch_for_each_pixel(patch, [&](uint8_t p) { + output.write(pixel_huffman.encode(p)); + }); + auto c2 = output.get_output(); + p2.insert(p2.end(), c2.begin(), c2.end()); +#endif + printf(" encoding %d %d->%d ds %d\n", choice, (int)patch.data.size(), (int)p2.size(), best_decoder_size); + patch_orig_size.record(patch.data.size()); + patch.data = p2; + patch_new_size.record(patch.data.size()); + wad.update_lump(patch); + compressed.insert(num); + touched[num] = TOUCHED_PATCH; +} + +// use_runs = true to do runs of pixels, false to use 0 as transparent color +void convert_vpatch(wad &wad, lump &patch, int max_colors, bool use_runs, std::set colors, int shared_palette_handle, bool first) { + touched[patch.num] = TOUCHED_VPATCH; + compressed.insert(patch.num); + dump_patch(patch.name.c_str(), patch.num, patch); + auto ph = get_field(patch.data, 0); + auto pix = unpack_patch(patch); + if (colors.empty()) { + for (const auto &p: pix) { + colors.insert(p); + } + } + lump palette; + wad.get_lump("playpal", palette); + + // note in the case of shared_palettes we are doing the same color reduction work for every patch, but it is idempotent, and it was easier + // than extracting/refactoring the code!!! + + if (use_runs && colors.find(-1) != colors.end()) max_colors++; + // this only saves 500 bytes (trying 4 color textures) +// if (max_colors == 16 || max_colors == 17) { +// if (colors.size() <= max_colors - 12) { +// max_colors -= 12; +// } +// } + while ((int)colors.size() > max_colors) { +// printf("Colors now:\n"); +// for(const auto &c : colors) { +// if (c == -1) printf(" %d transparent\n", c); +// else printf(" %d %02x,%02x,%02x\n", c, +// palette.data[c*3], +// palette.data[c*3+1], +// palette.data[c*3+2]); +// } + float lowest_score = INFINITY; + int lowest_c0 = 0; + int lowest_c1 = 0; + for (int c0: colors) { + if (c0 == -1) continue; + for (int c1: colors) { + if (c1 <= c0) continue; + float dr = ((float) palette.data[c0 * 3]) - ((float) palette.data[c1 * 3]); + float dg = ((float) palette.data[c0 * 3 + 1]) - ((float) palette.data[c1 * 3 + 1]); + float db = ((float) palette.data[c0 * 3 + 2]) - ((float) palette.data[c1 * 3 + 2]); + // todo we can improve heuristic + float score = dr * dr + dg * dg + db * db; + if (score < lowest_score) { + lowest_score = score; + lowest_c0 = c0; + lowest_c1 = c1; +// printf("potential replacement %d %02x,%02x,%02x with %d %02x,%02x,%02x %f\n", +// c0, +// palette.data[c0*3], +// palette.data[c0*3+1], +// palette.data[c0*3+2], +// c1, +// palette.data[c1*3], +// palette.data[c1*3+1], +// palette.data[c1*3+2], +// score); + } + } + } + // todo we should pick best of c0, c1, or another color from the palette. + // ACTUALLY - tried this, but it produces undesirable results... breaks stylized looks of icons, and turns status bar non grey + int replacement = lowest_c1; +// printf("replacing %d %02x,%02x,%02x with %d %02x,%02x,%02x\n", +// lowest_c0, +// palette.data[lowest_c0*3], +// palette.data[lowest_c0*3+1], +// palette.data[lowest_c0*3+2], +// replacement, +// palette.data[replacement*3], +// palette.data[replacement*3+1], +// palette.data[replacement*3+2]); + for (auto &p: pix) { + if (p == lowest_c0) p = replacement; + } + colors.erase(lowest_c0); + } + std::map color_mapping; + for (const auto &c: colors) { + color_mapping[c] = color_mapping.size(); + } + for (auto &p: pix) { + p = color_mapping[p]; + } +#if DEBUG_SAVE_PNG + std::vector pal; + for (const auto &c: colors) { + uint32_t argb; + if (c == -1) argb = 0; + else + argb = 0xff000000 | (palette.data[c * 3 + 2] << 16) | (palette.data[c * 3 + 1] << 8) | + (palette.data[c * 3]); + pal.push_back(argb); + } + std::string prefix = "misc/"; + if (shared_palette_handle == -1) { + prefix += "unshared"; + } else { + prefix += std::to_string(shared_palette_handle); + } + save_png(wad, prefix, patch.name.c_str(), ph.width, ph.height, pix, pal); +#endif + int orig_size = patch.data.size(); + vpatch_orig_size.record(orig_size); + + patch.data.clear(); + bool has_transparent = colors.find(-1) != colors.end(); + if (use_runs && has_transparent) { + for (auto &p: pix) { + p--; + } + colors.erase(-1); + } + patch.data.push_back(ph.width); + patch.data.push_back(ph.height); + if (shared_palette_handle==-1 || first) { + patch.data.push_back(colors.size()); + } else { + patch.data.push_back(0); // not super efficient, but makes decode easier, and we are saving a palette altogether + } + uint bpp = 31 - __builtin_clz(max_colors); + int vpt = -1; + if (bpp == 4) { + if (use_runs) vpt = vp4_runs; + else if (has_transparent) vpt = vp4_alpha; + else vpt = vp4_solid; + } else if (bpp == 6) { + if (use_runs) vpt = vp6_runs; + } else if (bpp == 8) { + if (use_runs) vpt = vp8_runs; + } + if (vpt == -1) fail("Unknown vpt type"); + patch.data.push_back((vpt<<2) | ((ph.width &0x100)>>7) | (shared_palette_handle>=0 ? 1:0)); + // todo shrink this some; it is often zero + patch.data.push_back(ph.topoffset); + patch.data.push_back(ph.leftoffset); + if (shared_palette_handle == -1 || first) { + for (const auto &c: colors) { + if (use_runs && c == -1) continue; + patch.data.push_back(c); + } + } + if (shared_palette_handle >= 0) { + patch.data.push_back(shared_palette_handle); + } + if (pix.size() & 1) pix.push_back(0); + for(int i=0, y=0;y= 255) fail("gap too big"); + patch.data.push_back(xend - x); + x = xend; + while (xend < ph.width && pix[i + xend] != -1) xend++; + if (xend - x > 255) fail("run too big"); + patch.data.push_back(xend - x); + } + } else { + xend = ph.width; + } + uint accum = 0; + uint bits = 0; + for (;x < xend; x++) { + accum |= pix[i+x] << bits; + bits += bpp; + if (bits >= 8) { + patch.data.push_back(accum); + accum >>= 8; + bits -= 8; + } + } + if (bits) { + patch.data.push_back(accum); + } + } + i += ph.width; + } + vpatch_top_offsets.record(ph.topoffset); + vpatch_left_offsets.record(ph.leftoffset); + vpatch_top_0offsets.record(ph.topoffset == 0); + vpatch_left_0offsets.record(ph.leftoffset == 0); + printf("VPATCH %s %d->%d\n", patch.name.c_str(), orig_size, (int)patch.data.size()); + vpatch_new_size.record(patch.data.size()); + wad.update_lump(patch); +} + +void add_patch_colors(lump& patch, std::set& colors) { + auto pix = unpack_patch(patch); + for (const auto &p: pix) { + colors.insert(p); + } +} + +void convert_vpatches(wad &wad, const std::vector& names, int max_colors, bool use_runs, int shared_palette_handle = -1) { + std::set colors; + if (shared_palette_handle >= 0) { + for (const auto &cg: names) { + lump l; + int indexp1 = wad.get_lump(cg, l); + if (indexp1) { + add_patch_colors(l, colors); + } + } + } + bool first = true; + for (const auto &cg: names) { + lump l; + int indexp1 = wad.get_lump(cg, l); + if (indexp1) { + convert_vpatch(wad, l, max_colors, use_runs, colors, shared_palette_handle, first); + first = false; + } else { + printf("MISSING named patch %s\n", cg.c_str()); + } + } +} + +void convert_patches(wad &wad, std::string from_name, std::string to_name) { + lump pnames; + int start = wad.get_lump_index(from_name); + int end = wad.get_lump_index(to_name); + std::set pname_patches; + + std::set temp_hack; + for (int num = start + 1; num < end; num++) { + lump patch; + if (wad.get_lump(num, patch)) { + if (wad.get_lump_index(patch.name) != num) { + // seems to be a duplicate patch, and not the right one + patch.data.clear(); + wad.update_lump(patch); + continue; + } + + // for now remove the data + //wad.remove_lump(name); + + convert_patch(wad, num, patch); + } else { + printf(" %d - not found\n", num); + } + } +} + +void dump_patch(const char *name, int num, lump &patch) { + static std::set dumped; + if (!dumped.insert(num).second) return; + dumped_patch_count++; +#if 1 + const patch_header *ph = (const patch_header *)patch.data.data(); + patch_widths.record(ph->width); + patch_heights.record(ph->height); + patch_width_stats.add(ph->width); + patch_height_stats.add(ph->height); + patch_left_offsets.record(ph->leftoffset); + patch_top_offsets.record(ph->topoffset); + patch_sizes.record(ph->width * ph->height); + std::set patch_color_set; + std::vector patch_raw_pixels; + std::vector column_raw_pixels; + int all_posts_count=0; + for(int col=0;colwidth;col++) { + column_raw_pixels.clear(); + uint32_t col_offset = *(uint32_t *)(patch.data.data() + 8 + col * 4); + const uint8_t *post = patch.data.data() + col_offset; + int post_count = 0; + int pixel_count = 0; + while (post[0] != 0xff) { + std::set post_color_set; + post_count++; + pixel_count += post[1]; + for(int y = 3; y < 3 + post[1]; y++) { + post_color_set.insert(post[y]); + patch_raw_pixels.push_back(post[y]); + column_raw_pixels.push_back(post[y]); + } + post += 4 + post[1]; + patch_column_colors.record(post_color_set.size()); + patch_color_set.insert(post_color_set.begin(), post_color_set.end()); + uint x = post_color_set.size(); + if (x) x--; + if (x) x = 31 - __builtin_clz(x); + assert(x < 8); + patch_columns_under_colors[x].record(post_color_set.size()); + } + auto lengths = huff(column_raw_pixels.data(), column_raw_pixels.size()); + int huffl = 0; + for(const auto &p : column_raw_pixels) huffl += lengths[p]; + patch_col_hack_huff_pixels.record((huffl+7)/8); + if (post_count > 1 || pixel_count != ph->height) { + post = patch.data.data() + col_offset; + while (post[0] != 0xff) { + post += 4 + post[1]; + } + } + if (post_count == 1) { + patch_one.record(1); + } + all_posts_count += post_count; + patch_post_counts.record(post_count); + } + patch_colors.record(patch_color_set.size()); + patch_all_post_counts.record(all_posts_count); + auto lengths = huff(patch_raw_pixels.data(), patch_raw_pixels.size()); + int huffl = 0; + for(const auto &p : patch_raw_pixels) huffl += lengths[p]; + patch_hack_huff_pixels.record((huffl+7)/8); + patch_pixels.record(patch_raw_pixels.size()); + + symbol_stats pixel_stats; + patch_for_each_pixel(patch, [&](uint8_t p) { + pixel_stats.add(p); + }); + auto pixel_huffman = pixel_stats.create_huffman_encoding>(); + byte_vector_bit_output output; + patch_for_each_pixel(patch, [&](uint8_t p) { + output.write(pixel_huffman.encode(p)); + }); + auto c2 = output.get_output(); +#endif + printf(" %d %s - %dx%d +%d,%d colors %d\n", num, name, ph->width, ph->height, ph->leftoffset, ph->topoffset, (int)patch_color_set.size()); + if (ph->width >= 256) { + printf(" wide\n"); + } +} + +std::vector convert_sidedefs(wad &wad, const texture_index &tex_index, lump &lump) { + assert(lump.data.size() % sizeof(mapsidedef_t) == 0); + int count = lump.data.size() / sizeof(mapsidedef_t); + printf("Converting %d sidedefs in lump %s\n", count, lump.name.c_str()); + std::vector sidedef_mapping; + int offset = 0; + hash = hash * 31 + count; + if (super_tiny) { + std::vector sides_z; + for (int i = 0; i < count; i++) { + uint s0 = sides_z.size(); + auto msd = get_field_inc(lump.data, offset); + assert(sides_z.size() < 65536); + sidedef_mapping.push_back(sides_z.size()); + + short toptexture = tex_index.find(wad::wad_string(msd.toptexture)); + short midtexture = tex_index.find(wad::wad_string(msd.midtexture)); + short bottomtexture = tex_index.find(wad::wad_string(msd.bottomtexture)); + if (toptexture < 0 || midtexture < 0 || bottomtexture < 0) { + fail("Missing sidedef texture"); + } + + // some textures may be 0, we consider the following combinations of none or the same textures... + // we want to encode as few of A, B, C as possible. + // + // ENC : T M O + // ----:------ + // 0 | - - - + // 1 | - - C + // 2 | - B - + // 3 | - B B + // 4 | - B C + // 5 | A - - + // 6 | A - A + // 7 | A - C + // 8 | A A - + // 9 | A A A + // 10 | A A B + // 11 | A A C + // 12 | A B - + // 13 | A B A + // 14 | A B B + // 15 | A B C + static uint8_t hasA[16] = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + static uint8_t hasB[16] = { 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1}; + static uint8_t hasC[16] = { 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1}; + static uint8_t posM[26] = { 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 3, 3, 3, 3}; + static uint8_t posO[26] = { 0, 2, 0, 2, 3, 0, 2, 3, 0, 2, 3, 4, 0, 2, 3, 4}; + static uint8_t offset_offset[26] = {2, 3, 3, 3, 4, 3, 3, 4, 3, 3, 4, 4, 4, 4, 4, 5}; + + bool onebyte_texture = (!toptexture || toptexture < 256) && + (!midtexture || midtexture < 256) && + (!bottomtexture || bottomtexture < 256); + + uint enc; + if (toptexture == 0) { + if (midtexture == 0) { + // 0 | - - - + // 1 | - - C + enc = bottomtexture == 0 ? 0 : 1; + } else { + // 2 | - B - + // 3 | - B B + // 4 | - B C + enc = bottomtexture == 0 ? 2 : ((bottomtexture == toptexture) ? 3 : 4); + } + } else { + if (!midtexture) { + // 5 | A - - + // 6 | A - A + // 7 | A - C + enc = bottomtexture == 0 ? 5 : ((bottomtexture == toptexture) ? 6 : 7); + } else if (midtexture == toptexture) { + // 8 | A A - + // 9 | A A A + // 10 | A A B + // 11 | A A C + if (!bottomtexture) enc = 8; + else if (bottomtexture == toptexture) enc = 9; + else if (bottomtexture == midtexture) enc = 10; + else enc = 11; + } else { + // 12 | A B - + // 13 | A B A + // 14 | A B B + // 15 | A B C + if (!bottomtexture) enc = 12; + else if (bottomtexture == toptexture) enc = 13; + else if (bottomtexture == midtexture) enc = 14; + else enc = 15; + } + } + bool have_rowoff = msd.rowoffset != 0; + uint toff_size; + if (msd.textureoffset == 0) { + toff_size = 0; + } else if (msd.textureoffset >= 0 && !(msd.textureoffset & 1) && (msd.textureoffset / 2 < 256)) { + toff_size = 1; + } else { + toff_size = 2; + } + bool onebyte_sector = msd.sector < 256; + bool onebyte = onebyte_sector | onebyte_texture; + sides_z.push_back(enc | (!(onebyte) << 7) | (have_rowoff << 6) | (toff_size << 4)); + sides_z.push_back(msd.sector); + assert(onebyte); + if (hasA[enc]) { + sides_z.push_back(toptexture); + } + if (hasB[enc]) { + sides_z.push_back(midtexture); + } + if (hasC[enc]) { + sides_z.push_back(bottomtexture); + } + if (toff_size == 1) { + sides_z.push_back(msd.textureoffset / 2); + } else if (toff_size == 2) { + sides_z.push_back(msd.textureoffset >> 8); + sides_z.push_back(msd.textureoffset & 0xff); + } + if (have_rowoff) { + if (msd.rowoffset >= 0 && !(msd.rowoffset & 1) && (msd.rowoffset / 2 < 128)) { + sides_z.push_back(msd.rowoffset / 2); + } else { + assert(msd.rowoffset >= -16384 && msd.rowoffset < 13684); + sides_z.push_back(0x80 | (msd.rowoffset >> 8)); + sides_z.push_back(msd.rowoffset & 0xff); + } + } + assert(msd.sector < 512); + { + uint enc2 = sides_z[s0] & 0xf; + int tex_t = enc2 < 5 ? 0 : sides_z[s0+2]; + int tex_m = posM[enc2] ? sides_z[s0+posM[enc2]] : 0; + int tex_b = posO[enc2] ? sides_z[s0+posO[enc2]] : 0; + if (tex_t != toptexture || tex_m != midtexture || tex_b != bottomtexture) { + fail("sidedefs are not compatible with super tiny\n"); + } + int texoff = 0; + if (sides_z[s0] & 48) { + if (sides_z[s0] & 32) { + texoff = (sides_z[s0 + offset_offset[enc2]] << 8) + sides_z[s0 + 1 + offset_offset[enc2]]; + } else { + texoff = sides_z[s0 + offset_offset[enc2]] << 1; + } + } + assert(msd.textureoffset == texoff); + int rowoff = 0; + if (sides_z[s0] & 64) { + uint pos = offset_offset[enc2] + ((sides_z[s0] >> 4)&3); + rowoff = sides_z[s0 + pos]; + if (rowoff & 128) { + rowoff = (int16_t)(((rowoff << 8) | sides_z[s0 + pos + 1])<<1); + rowoff /= 2; + } else { + rowoff <<= 1; + } + } + assert(msd.rowoffset == rowoff); + } + side_meta.record(sizeof(whdsidedef_t)); + side_metaz.record(sides_z.size() - s0); + } + lump.data = sides_z; + } else { + std::vector whd_sides; + for (int i = 0; i < count; i++) { + sidedef_mapping.push_back(i); + auto msd = get_field_inc(lump.data, offset); + + short toptexture = tex_index.find(wad::wad_string(msd.toptexture)); + short midtexture = tex_index.find(wad::wad_string(msd.midtexture)); + short bottomtexture = tex_index.find(wad::wad_string(msd.bottomtexture)); + + if (toptexture < 0 || midtexture < 0 || bottomtexture < 0) { + fail("Missing sidedef texture"); + } + whdsidedef_t sd = { + .textureoffset = msd.textureoffset, + .rowoffset = msd.rowoffset, + .toptexture = toptexture, + .bottomtexture = bottomtexture, + .midtexture = midtexture, + .sector = msd.sector, + }; + side_meta.record(sizeof(sd)); + append_field(whd_sides, sd); + } + lump.data = whd_sides; + } + wad.update_lump(lump); + return sidedef_mapping; +} + +// order here is important +#define ML_NO_PREDICT_SIDE 256 +#define ML_NO_PREDICT_V1 512 // can share with ML_MAPPED +#define ML_NO_PREDICT_V2 1024 +#define ML_HAS_SPECIAL 2048 +#define ML_HAS_TAG 4096 +#define ML_SIDE_MASK 0xe000u + +#define line_onesided(l) ((l[1] & (ML_SIDE_MASK >> 8)) == 0) +#define line_predict_side(l) ((l[1] & (ML_NO_PREDICT_SIDE >> 8)) == 0) +#define line_predict_v1(l) ((l[1] & (ML_NO_PREDICT_V1 >> 8)) == 0) +#define line_predict_v2(l) ((l[1] & (ML_NO_PREDICT_V2 >> 8)) == 0) + +static inline int line_sidenum(const uint8_t *lines, uint16_t whd_sidemul, const uint8_t *l, int side) { + if (side && line_onesided(l)) return -1; + int s; + if (line_predict_side(l)) { + s = ((l - lines) * whd_sidemul) >> 16; + s += (int8_t)l[2]; + } else { + s = l[2] + (l[3] << 8); + } + if (side) { + s += l[1] >> 5; + } + return s; +} + +static inline int line_v1(const uint8_t *lines, uint16_t whd_vmul, const uint8_t *l) { + const static uint8_t v1pos[2] = { 3, 4 }; + int v; + uint pos = v1pos[l[1]&1]; + if (line_predict_v1(l)) { + v = ((l - lines) * whd_vmul) >> 16; + v += (int8_t)l[pos]; + } else { + v = l[pos] + (l[pos+1] << 8); + } + return v; +} + +static inline int line_v2(const uint8_t *lines, uint16_t whd_vmul, const uint8_t *l) { + const static uint8_t v2pos[4] = { 4, 5, 5, 6 }; + uint pos = v2pos[l[1]&3]; + int v; + if (line_predict_v2(l)) { + v = ((l - lines) * whd_vmul) >> 16; + v += (int8_t)l[pos]; + } else { + v = l[pos] + (l[pos+1] << 8); + } + return v; +} + +static inline int line_special(const uint8_t *lines, const uint8_t *l) { + int special = 0; + if ((l[1] & (ML_HAS_SPECIAL >> 8)) != 0) { + const static uint8_t special_pos[8] = { 5, 6, 6, 7, 6, 7, 7, 8 }; + uint pos = special_pos[l[1]&7]; + special = l[pos]; + } + return special; +} + +static inline int line_tag(const uint8_t *lines, const uint8_t *l) { + int tag = 0; + if ((l[1] & (ML_HAS_TAG >> 8)) != 0) { + const static uint8_t special_pos[8] = { 5, 6, 6, 7, 6, 7, 7, 8 }; + uint pos = special_pos[l[1]&7] + ((l[1] & (ML_HAS_SPECIAL >> 8)) != 0); + tag = l[pos]; + } + return tag; +} + +std::vector convert_linedefs(wad &wad, lump &lump, const std::vector& sidedef_mapping, const std::vector>& vertexes) { + assert(lump.data.size() % sizeof(maplinedef_t) == 0); + int count = lump.data.size() / sizeof(maplinedef_t); + hash = hash * 31 + count; + if (super_tiny) { + compressed.insert(lump.num); + printf("Converting %d linedefs in lump %s\n", count, lump.name.c_str()); + int offset = 0; + int max_side_num = 0; + int max_v = 0; + for (int i = 0; i < count; i++) { + auto msd = get_field_inc(lump.data, offset); + all_linedef_flags.insert(msd.flags); + assert(msd.sidenum[0] >= 0); + assert(msd.sidenum[1] == -1 || msd.sidenum[1] == msd.sidenum[0]+1); + if (msd.sidenum[0] >= 0) { + msd.sidenum[0] = sidedef_mapping[msd.sidenum[0]]; + max_side_num = std::max(max_side_num, (int)(uint16_t)msd.sidenum[0]); + } + if (msd.sidenum[1] >= 0) { + msd.sidenum[1] = sidedef_mapping[msd.sidenum[1]]; + max_side_num = std::max(max_side_num, (int)(uint16_t)msd.sidenum[1]); + } + max_v = std::max((int)msd.v1, max_v); + } + auto try_encode = [&](uint size_guess) { + printf("Guess %d\n", size_guess); + uint vmul = 65536 * max_v / size_guess; + assert(vmul < 65536); + uint smul = 65536 * max_side_num / size_guess; + assert(smul < 65536); + offset = 0; + std::vector result; + + statsomizer side_num_range("Side num range"); + statsomizer side_num_range_v("Side num range"); + statsomizer v1_range("V1 range"); + statsomizer v1_range_v("V1 range"); + statsomizer v2_range("V2 range"); + statsomizer v2_range_v("V2 range"); + + std::vector offsets; + for (int i = 0; i < count; i++) { + offsets.push_back(result.size()); + auto msd = get_field_inc(lump.data, offset); + // short v1; + // short v2; + // short flags; + // short special; + // short tag; + // // sidenum[1] will be -1 if one sided + // short sidenum[2]; + + uint base = result.size(); + uint flags = msd.flags; + assert(flags < 256); + result.push_back(flags); + result.push_back(0); // to fill in later + + int sidenum = sidedef_mapping[msd.sidenum[0]]; + if (msd.sidenum[1] != -1) { + uint delta = sidedef_mapping[msd.sidenum[1]] - sidenum; + assert(delta > 0 && delta <= 7); + flags |= delta << 13; + } + + int s_est = (int)((base * smul) >> 16u); + bool s_in_range = abs(sidenum - s_est) <= 127; + side_num_range.record(s_in_range); + side_num_range_v.record(sidenum - s_est); + if (s_in_range) { + result.push_back(sidenum - s_est); + } else { + flags |= ML_NO_PREDICT_SIDE; + assert(sidenum < 65536); + result.push_back(sidenum & 0xff); + result.push_back(sidenum >> 8); + } + + int v_est = (int)((base * vmul) >> 16u); + bool v1_in_range = abs(msd.v1 - v_est) <= 127; + v1_range.record(v1_in_range); + v1_range_v.record(msd.v1 - v_est); + if (v1_in_range) { + result.push_back(msd.v1 - v_est); + } else { + flags |= ML_NO_PREDICT_V1; + result.push_back(msd.v1 & 0xff); + result.push_back(msd.v1 >> 8); + } + + bool v2_in_range = abs(msd.v2 - v_est) <= 127; + v2_range.record(v2_in_range); + v2_range_v.record(msd.v2 - v_est); + if (v2_in_range) { + result.push_back(msd.v2 - v_est); + } else { + flags |= ML_NO_PREDICT_V2; + result.push_back(msd.v2 & 0xff); + result.push_back(msd.v2 >> 8); + } + if (msd.special) { + assert(msd.special < 256); + flags |= ML_HAS_SPECIAL; + result.push_back(msd.special); + } + if (msd.tag) { + assert(msd.tag < 256); + flags |= ML_HAS_TAG; + result.push_back(msd.tag); + } + + result[base+1] = flags >> 8; + } + side_num_range.print_summary(); + side_num_range_v.print_summary(); + v1_range.print_summary(); + v1_range_v.print_summary(); + v2_range.print_summary(); + v2_range_v.print_summary(); + printf("%d -> %d\n", (int)lump.data.size(), (int)result.size()); + return std::make_pair(result, offsets); + }; + uint size_guess = 5 * count; + auto last_encoding = try_encode(size_guess); + while (true) { + auto encoding = try_encode(last_encoding.first.size()); + if (encoding.first.size() >= last_encoding.first.size()) break; + size_guess = last_encoding.first.size(); + last_encoding = encoding; + } + line_metaz.record(last_encoding.first.size()); + line_meta.record(lump.data.size()); + +#if 1 + // temp until we fix that above + offset = 0; + const uint8_t *lines = last_encoding.first.data(); + const auto &offsets = last_encoding.second; + uint smul = 65536 * max_side_num / size_guess; + uint vmul = 65536 * max_v / size_guess; + for (int i = 0; i < count; i++) { + auto msd = get_field_inc(lump.data, offset); + all_linedef_flags.insert(msd.flags); + if (msd.sidenum[0] >= 0) { + msd.sidenum[0] = sidedef_mapping[msd.sidenum[0]]; + max_side_num = std::max(max_side_num, (int)(uint16_t)msd.sidenum[0]); + } + if (msd.sidenum[1] >= 0) { + msd.sidenum[1] = sidedef_mapping[msd.sidenum[1]]; + max_side_num = std::max(max_side_num, (int)(uint16_t)msd.sidenum[1]); + } + max_v = std::max((int)msd.v1, std::max((int)msd.v2, max_v)); + int s0 = line_sidenum(lines, smul, lines + offsets[i], 0); + int s1 = line_sidenum(lines, smul, lines + offsets[i], 1); + assert(msd.sidenum[0] == s0); + assert(msd.sidenum[1] == s1); + int v1 = line_v1(lines, vmul, lines + offsets[i]); + int v2 = line_v2(lines, vmul, lines + offsets[i]); + assert(msd.v1 == v1); + assert(msd.v2 == v2); + assert(msd.tag == line_tag(lines, lines + offsets[i])); + assert(msd.special == line_special(lines, lines + offsets[i])); + } +#endif + lump.data.clear(); + uint16_t tmp = count; append_field(lump.data, tmp); + tmp = smul; append_field(lump.data, tmp); + tmp = vmul; append_field(lump.data, tmp); + assert(last_encoding.first.size() < 65536); + lump.data.insert(lump.data.end(), last_encoding.first.begin(), last_encoding.first.end()); + wad.update_lump(lump); + line_scale.record(100 * lump.data.size() / count); + return last_encoding.second; + } else { + // unchanged, so return an identity mapping + std::vector rc; + for (int i = 0; i < count; i++) { + rc.push_back(i); + } + return rc; + } +} + +std::vector convert_segs(wad &wad, lump &lump, const std::vector& linedef_mapping) { + assert(lump.data.size() % sizeof(mapseg_t) == 0); + int count = lump.data.size() / sizeof(mapseg_t); + hash = hash * 31 + count; + std::vector newdata; + printf("Converting %d segs in lump %s\n", count, lump.name.c_str()); + std::vector rc; + if (!super_tiny) { + int offset = 0; + for (int i = 0; i < count; i++) { + rc.push_back(i); + auto ms = get_field_inc(lump.data, offset); + ms.linedef = linedef_mapping[ms.linedef]; + append_field(newdata, ms); + } + rc.push_back(count); // we need an end marker too + } else { + compressed.insert(lump.num); + int offset = 0; + for (int i = 0; i < count; i++) { + rc.push_back(newdata.size()); + auto ms = get_field_inc(lump.data, offset); + uint16_t l = linedef_mapping[ms.linedef]; + assert(ms.v1 < 2048); + assert(ms.v2 < 2048); + assert(ms.side == 0 || ms.side == 1); + newdata.push_back(ms.side << 7 | ((ms.offset != 0) << 6) | ((ms.v2 >> 5)&0x38) | ((ms.v1 >> 8)&0x7)); + newdata.push_back(ms.v1 & 0xff); + newdata.push_back(ms.v2 & 0xff); + newdata.push_back(l & 0xff); + newdata.push_back(l >> 8); + if (ms.offset) { + if (!(ms.offset & 3) && ms.offset >= 0 && ms.offset < 512) { + newdata.push_back((ms.offset>>2)&0x7f); + } else { + assert(abs(ms.offset)<16384); + newdata.push_back(128 | (ms.offset>>8)); + newdata.push_back(ms.offset&0xff); + } + } + } + offset = 0; + int pos = 0; + for (int i = 0; i < count; i++) { + auto ms = get_field_inc(lump.data, offset); + uint16_t l = newdata[pos+3] + (newdata[pos+4] << 8); + assert(l == linedef_mapping[ms.linedef]); + assert((newdata[pos] >> 7) == ms.side); + uint16_t v1 = newdata[pos+1] | ((newdata[pos]&7)<<8); + uint16_t v2 = newdata[pos+2] | ((newdata[pos]&0x38)<<5); + assert(v1 == ms.v1); + assert(v2 == ms.v2); + short soffset = 0; + if (newdata[pos] & 64) { + if (newdata[pos+5] < 128) { + soffset = newdata[pos+5] * 4; + pos++; + } else { + soffset = (newdata[pos+5] << 9) | (newdata[pos+6] << 1); + soffset /= 2; + pos+=2; + } + } + pos += 5; + assert(soffset == ms.offset); + } + rc.push_back(newdata.size()); // we need an end marker too + } + lump.data = newdata; + wad.update_lump(lump); + return rc; +} + +std::vector> convert_vertexes(wad &wad, lump &lump) { + // todo 4->3 + assert(lump.data.size() % sizeof(mapvertex_t) == 0); + int count = lump.data.size() / sizeof(mapvertex_t); + hash = hash * 31 + count; + printf("Converting %d vertexes in lump %s\n", count, lump.name.c_str()); + int offset = 0; + std::vector> vertexes; + for (int i = 0; i < count; i++) { + auto mv = get_field_inc(lump.data, offset); + int x = mv.x; + int y = mv.y; + vertex_x.record(x); + vertex_y.record(y); + vertexes.emplace_back(x, y); + } + return vertexes; +} + +static inline bool is_leaf(const mapnode_t &node, int which) { + return node.children[which] >= 32768; +} + +enum { + BOXTOP, + BOXBOTTOM, + BOXLEFT, + BOXRIGHT +}; // bbox coordinates + +void convert_nodes(wad &wad, lump &lump) { + assert(lump.data.size() % sizeof(mapnode_t) == 0); + int count = lump.data.size() / sizeof(mapnode_t); + hash = hash * 31 + count; + if (super_tiny) { + std::vector newdata; + printf("Converting %d nodes in lump %s\n", count, lump.name.c_str()); + int offset = 0; + // TODO we could do without encoding children, it only saves 2 bytes per node, but still it works + // in all the WADs we care about for now + std::vector mapnodes(count); + std::vector whdnodes(count); + + for (int i = 0; i < count; i++) { + mapnodes[i] = get_field_inc(lump.data, offset); + } + std::function push_bbox = [&](int i, int16_t *bbox) { + const auto &mnode = mapnodes[i]; + whdnodes[i].x = mnode.x; + whdnodes[i].y = mnode.y; + whdnodes[i].dx = mnode.dx; + whdnodes[i].dy = mnode.dy; + if (is_leaf(mnode, 1)) { + // L is leaf + if (is_leaf(mnode, 0)) { + // L is leaf, R is leaf + int deltaL = i - (mnode.children[1] & 0x7fffu); + int deltaR = i - (mnode.children[0] & 0x7fffu); + if (deltaL < -64 || deltaL > 63 || deltaR < -64 || deltaR > 63) { + fail("Out of range/non standard node tree, node %d has L leaf delta %d, R leaf delta %d (require -64 <= x <= 63)\n", + i, deltaL, deltaR); + } + whdnodes[i].coded_children = 0xc000 | ((deltaL & 0x7fu) << 7u) | (deltaR & 0x7fu); + } else { + // L is leaf, R is node + if (mnode.children[0] != i - 1) { + fail("Non standard tree, node %d, expected R node child to be N-1 i.e. %d but was %d\n", i, + i - 1, + mnode.children[0]); + } + int deltaL = i - (mnode.children[1] & 0x7fffu); + if (deltaL < -64 || deltaL > 63) { + fail("Out of range/non standard node tree, node %d has L leaf delta %d (require -64 <= x <= 63)\n", + deltaL); + } + whdnodes[i].coded_children = 0x8000 | ((deltaL & 0x7fu) << 7u); + } + } else { + // L is node + if (mnode.children[1] != i - 1) { + fail("Non standard tree, node %d, expected L node child to be N-1 i.e. %d but was %d\n", i, i - 1, + mnode.children[1]); + } + if (is_leaf(mnode, 0)) { + // L is node, R is leaf + int deltaR = i - (mnode.children[0] & 0x7fffu); + if (deltaR < -64 || deltaR > 63) { + fail("Out of range/non standard node tree, node %d has R leaf delta %d (require -64 <= x <= 63)\n", + deltaR); + } + whdnodes[i].coded_children = 0x4000 | (deltaR & 0x7fu); + } else { + // L is node, R is node + int deltaR = i - (mnode.children[0] & 0x7fffu); + if (deltaR < 2 || deltaR > 16383) { + fail("Out of range/non standard node tree, node %d has R node delta %d (require 2 <= x <= 16383) \n", + i, deltaR); + } + whdnodes[i].coded_children = (deltaR & 0x3fffu); + } + } +#if VERIFY_ENCODING + // code is 11ll llll lrrr rrrr : l/r are 7 bit signed values; left leaf = 0x8000 + n - l; right leaf = 0x8000 + n - r + // 10ll llll l--- ---- : right node is n- 1, left leaf via l as above + // 01-- ---- -rrr rrrr : left node is n - 1, right leaf via r is as above, + // 00rr rrrr rrrr rrrr : left node is n - 1, right node is n - r + int v[2]; + if (whdnodes[i].coded_children & 0x8000) { + v[1] = 0x8000 + i - (((int32_t) (whdnodes[i].coded_children << 18)) >> 25); + if (whdnodes[i].coded_children & 0x4000) { + v[0] = 0x8000 + i - (((int32_t) (whdnodes[i].coded_children << 25)) >> 25); + } else { + v[0] = i - 1; + } + } else { + v[1] = i - 1; + if (whdnodes[i].coded_children & 0x4000) { + v[0] = 0x8000 + i - (((int32_t) (whdnodes[i].coded_children << 25)) >> 25); + } else { + v[0] = i - (whdnodes[i].coded_children & 0x3fffu); + } + } + if (v[0] != mapnodes[i].children[0] || v[1] != mapnodes[i].children[1]) { + fail("Encoding error"); + } +#endif + for (int s = 0; s < 2; s++) { + assert(bbox[BOXRIGHT] != bbox[BOXLEFT]); // saw this in E4M7... keeping as a reminder + int l = 16 * (mnode.bbox[s][BOXLEFT] - bbox[BOXLEFT]) / (bbox[BOXRIGHT] - bbox[BOXLEFT]); + if (l == 16) l = 15; + int ldash = bbox[BOXLEFT] + (l * (bbox[BOXRIGHT] - bbox[BOXLEFT])) / 16; + // printf(" L %d->%d->%d = %d (%d/16))\n", bbox[BOXLEFT], mnode.bbox[s][BOXLEFT], bbox[BOXRIGHT], ldash, l); + int w; + if (bbox[BOXRIGHT] == ldash) { + w = 0; + } else { + w = (16 * (mnode.bbox[s][BOXRIGHT] - ldash) - 1) / (bbox[BOXRIGHT] - ldash); + } + int rdash = ldash + ((bbox[BOXRIGHT] - ldash) * (1 + w)) / 16; + // printf(" R %d->%d->%d = %d (%d/16))\n", ldash, mnode.bbox[s][BOXRIGHT], bbox[BOXRIGHT], rdash, w); + assert(bbox[BOXBOTTOM] != bbox[BOXTOP]); // saw this in E4M7... keeping as a reminder + int b = 16 * (mnode.bbox[s][BOXBOTTOM] - bbox[BOXBOTTOM]) / (bbox[BOXTOP] - bbox[BOXBOTTOM]); + if (b == 16) b = 15; + int bdash = bbox[BOXBOTTOM] + (b * (bbox[BOXTOP] - bbox[BOXBOTTOM])) / 16; + int h; + if (bbox[BOXTOP] == bdash) { + h = 0; + } else { + h = (16 * (mnode.bbox[s][BOXTOP] - bdash) - 1) / (bbox[BOXTOP] - bdash); + } + int tdash = bdash + ((bbox[BOXTOP] - bdash) * (1 + h)) / 16; + if (l < 0 || l > 15 || b < 0 || b > 15 || w < 0 || w > 15 || h < 0 || h >> 15) { + fail("Bad bounding box conversion %d.%d %d->%d,%d->%d becomes %d->%d,%d->%d, but lwbh is %d,%d %d,%d\n", + i, s, + mnode.bbox[s][BOXLEFT], mnode.bbox[s][BOXRIGHT], mnode.bbox[s][BOXBOTTOM], + mnode.bbox[s][BOXTOP], + ldash, rdash, bdash, tdash, l, w, b, h); + } + if (ldash > mnode.bbox[s][BOXLEFT] || + rdash < mnode.bbox[s][BOXRIGHT] || + bdash > mnode.bbox[s][BOXBOTTOM] || + tdash < mnode.bbox[s][BOXTOP]) { + fail("Bad bounding box conversion %d.%d %d->%d,%d->%d becomes %d->%d,%d->%d\n", i, s, + mnode.bbox[s][BOXLEFT], mnode.bbox[s][BOXRIGHT], mnode.bbox[s][BOXBOTTOM], + mnode.bbox[s][BOXTOP], + ldash, rdash, bdash, tdash); + } + // printf("%d.%d x,y %d->%d,%d->%d becomes %d->%d,%d->%d\n", i, s, + // mnode.bbox[s][BOXLEFT], mnode.bbox[s][BOXRIGHT], mnode.bbox[s][BOXBOTTOM], mnode.bbox[s][BOXTOP], + // ldash, rdash, bdash, tdash); + whdnodes[i].bbox_lw[s] = (l << 4u) | w; + whdnodes[i].bbox_th[s] = (b << 4u) | h; + // whdnodes[i].children[s] = mnode.children[s]; + if (mnode.children[s] < 32768) { + int16_t childbbox[4]; + childbbox[BOXLEFT] = ldash; + childbbox[BOXRIGHT] = rdash; + childbbox[BOXTOP] = tdash; + childbbox[BOXBOTTOM] = bdash; + push_bbox(mnode.children[s], childbbox); + } + } + }; + int16_t initial_bbox[4] = {32767, -32768, -32768, 32767}; + push_bbox(count - 1, initial_bbox); + lump.data.clear(); + for (int i = 0; i < count; i++) { + append_field(lump.data, whdnodes[i]); + } + wad.update_lump(lump); + } +} + +void convert_sectors(wad &wad, lump &lump) { + assert(lump.data.size() % sizeof(mapsector_t) == 0); + int count = lump.data.size() / sizeof(mapsector_t); + hash = hash * 31 + count; + std::vector newdata; + printf("Converting %d sidedefs in lump %s\n", count, lump.name.c_str()); + int offset = 0; + int fstart = wad.get_lump_index("f_start")+1; + + for (int i = 0; i < count; i++) { + auto ms = get_field_inc(lump.data, offset); + int floorpic = wad.get_lump_index(wad::wad_string(ms.floorpic)); + int ceilingpic = wad.get_lump_index(wad::wad_string(ms.ceilingpic)); + if (super_tiny) { + if (!(ms.tag == 999 || ms.tag == 666 || ms.tag == 667 || ms.tag < 254)) { + // note we will fail for existing tags of 254, 255 which we could fix by renumbering + fail("Tag out of range %d\n", ms.tag); + } + } + + if (floorpic < 0 || ceilingpic < 0) { + fail("Missing sector flat"); + } + floorpic -= fstart; + ceilingpic -= fstart; + if (floorpic < 0 || floorpic > 255 || ceilingpic < 0 || ceilingpic > 255) { + fail("Expect <256 flats"); + } + sector_lightlevel.record(ms.lightlevel); + sector_floorheight.record(ms.floorheight); + sector_ceilingheight.record(ms.ceilingheight); + sector_special.record(ms.special); + short tag = ms.tag; + if (super_tiny) { + if (tag == 666) tag = 254; + if (tag == 667) tag = 255; + if (tag == 999) tag = 0; + } + + whdsector_t se = { + .floorheight = ms.floorheight, + .ceilingheight = ms.ceilingheight, + .floorpic = (uint8_t) floorpic, + .ceilingpic = (uint8_t) ceilingpic, + .lightlevel = ms.lightlevel, + .special = ms.special, + .tag = tag + }; + append_field(newdata, se); + } + lump.data = newdata; + wad.update_lump(lump); +} + +void convert_subsectors(wad &wad, lump &lump, const std::vector& seg_mapping) { + assert(lump.data.size() % sizeof(mapsubsector_t) == 0); + int count = lump.data.size() / sizeof(mapsubsector_t); + hash = hash * 31 + count; + std::vector newdata; + printf("Converting %d subsectors in lump %s\n", count, lump.name.c_str()); + int offset = 0; + int expected = 0; + auto appender= [&](int val) { + if (val > 65535) fail("segment no %d is too big", val); + uint16_t v = val; + append_field(newdata, v); + }; + for (int i = 0; i < count; i++) { + auto mss = get_field_inc(lump.data, offset); + assert(mss.firstseg == expected); + expected = mss.firstseg + mss.numsegs; + subsector_length.record(mss.numsegs); + appender(seg_mapping[mss.firstseg]); + } + assert((int) seg_mapping.size() == expected + 1); + appender(seg_mapping[expected]); + lump.data = newdata; + wad.update_lump(lump); +} + +statsomizer blockmap_empty("blockmap empty"); +statsomizer blockmap_one("blockmap one"); +statsomizer blockmap_length("blockmap length"); +statsomizer blockmap_row_size("blockmap row size"); +statsomizer block_map_deltas("blockmap deltas"); +statsomizer block_map_deltas_1byte("blockmap delta 1byte"); +statsomizer blockmap_sizes("blockmap sizes"); +statsomizer blockmap_blocks("blockmap blocks"); + +void write_hword(std::vector &bytes, size_t pos, size_t word) { + if (pos + 2 > bytes.size()) { + bytes.resize(pos + 2); + } + bytes[pos] = word & 0xff; + bytes[pos + 1] = (word >> 8) & 0xff; +} + +void write_word(std::vector &bytes, size_t pos, size_t word) { + if (pos + 4 > bytes.size()) { + bytes.resize(pos + 4); + } + bytes[pos] = word & 0xff; + bytes[pos + 1] = (word >> 8) & 0xff; + bytes[pos + 2] = (word >> 16) & 0xff; + bytes[pos + 3] = (word >> 24) & 0xff; +} + +void convert_blockmap(wad &wad, lump &lump, const std::vector& linedef_mapping) { + const short *bm = (const short *) lump.data.data(); + int w = bm[2]; + int h = bm[3]; +// printf("blockmap %dx%d\n", w, h); + size_t header_span_size = 2 + (w + 7) / 8; + std::vector new_bm(header_span_size * h); + // note this isn't super efficient storage-wise, but done this way for the sake of sanity checking + std::vector>> blockmap; + blockmap.resize(h); + blockmap_blocks.record(w * h); + for (int y = 0; y < h; y++) { + blockmap[y].resize(w); + int per_row_non_empty_count = 0; + for (int x = 0; x < w; x++) { + int i = bm[y * w + x + 4]; + // note this check assumes sorted, which we currently, do ... we could sort obviously + if (bm[i] != 0) { + fail("block map cell without zero"); + } + i++; // skip the zero. + int per_cell_non_empty_count = 0; + auto &cell = blockmap[y][x]; + if (bm[i] == -1) { + blockmap_empty.record(1); + } else { + if (bm[i + 1] == -1) { + blockmap_one.record(1); + cell.push_back(linedef_mapping[bm[i]]); + } else { + for (int last = 0; bm[i] != -1; i++) { + int delta = bm[i] - last; + if (delta == 0) { + printf(" duplicate %d\n", last); + continue; + } + cell.push_back(linedef_mapping[bm[i]]); + if (delta < 0) fail("out of order delta"); + delta--; +// printf("%d ", bm[i]); + block_map_deltas.record(delta); + block_map_deltas_1byte.record(delta >= 0 && delta < 128); + } + } + } + if (!cell.empty()) per_row_non_empty_count++; + blockmap_length.record(per_cell_non_empty_count); +// printf("\n"); + } + blockmap_row_size.record(per_row_non_empty_count); + + // main header points to sparse per row stuff + size_t row_start_pos = new_bm.size(); + write_hword(new_bm, y * header_span_size, row_start_pos); + // leave space for 2 bytes per non-empty cell in the row + new_bm.resize(row_start_pos + per_row_non_empty_count * 2); + int non_empty_cell_index = 0; + for (int x = 0; x < w; x++) { + const auto &cell = blockmap[y][x]; + if (!cell.empty()) { + // set the bitmap bit + new_bm[y * header_span_size + 2 + (x / 8)] |= 1u << (x & 7u); + size_t cell_metadata_offset = row_start_pos + non_empty_cell_index * 2; + bool use_list = true; + if (cell.size() == 1) { + // single occupancy cell, so don't bother redirecting unless it won't fit here + if (cell[0] <= 0xfff) { + write_hword(new_bm, cell_metadata_offset, 0xf000 | cell[0]); + use_list = false; + } + } + if (use_list) { + // multi occupancy cell, so redirect to variable length list (note the length + // will be filledin later, below... 6:12 length:offset) + size_t values_list_offset = new_bm.size() - row_start_pos; + if (values_list_offset > 0x3ff) { + fail("blockmap sparse data too big"); + } + // write the link to the values list + write_hword(new_bm, cell_metadata_offset, values_list_offset); + int last = 0; + for (const auto &v : cell) { + int delta = v - last; + if (delta == 0) { + continue; + } + delta--; + assert(v); + per_row_non_empty_count++; + if (delta < 127) { + new_bm.push_back(delta); + } else { + if (delta > 37267) fail("duh"); + new_bm.push_back(0x80 | (delta >> 8)); + new_bm.push_back(delta & 0xff); + } + last = v; + } + // we currently use 4 top bits set to mean single cell + if (cell.size() >= 0b111100 + 1) { + fail("block has too many lines"); + } + new_bm[cell_metadata_offset + 1] |= ((cell.size() - 1) << 2); + } + non_empty_cell_index++; + } + } + } + blockmap_sizes.record(new_bm.size()); +#if VERIFY_ENCODING + auto popcount8 = [](uint n) { + assert(n < 256); + return __builtin_popcount(n); + }; + auto popcount8_2 = [](uint8_t v) { + static const uint8_t table[128] = { + 0x10, 0x21, 0x21, 0x32, 0x21, 0x32, 0x32, 0x43, 0x21, 0x32, 0x32, 0x43, 0x32, 0x43, 0x43, 0x54, + 0x21, 0x32, 0x32, 0x43, 0x32, 0x43, 0x43, 0x54, 0x32, 0x43, 0x43, 0x54, 0x43, 0x54, 0x54, 0x65, + 0x21, 0x32, 0x32, 0x43, 0x32, 0x43, 0x43, 0x54, 0x32, 0x43, 0x43, 0x54, 0x43, 0x54, 0x54, 0x65, + 0x32, 0x43, 0x43, 0x54, 0x43, 0x54, 0x54, 0x65, 0x43, 0x54, 0x54, 0x65, 0x54, 0x65, 0x65, 0x76, + 0x21, 0x32, 0x32, 0x43, 0x32, 0x43, 0x43, 0x54, 0x32, 0x43, 0x43, 0x54, 0x43, 0x54, 0x54, 0x65, + 0x32, 0x43, 0x43, 0x54, 0x43, 0x54, 0x54, 0x65, 0x43, 0x54, 0x54, 0x65, 0x54, 0x65, 0x65, 0x76, + 0x32, 0x43, 0x43, 0x54, 0x43, 0x54, 0x54, 0x65, 0x43, 0x54, 0x54, 0x65, 0x54, 0x65, 0x65, 0x76, + 0x43, 0x54, 0x54, 0x65, 0x54, 0x65, 0x65, 0x76, 0x54, 0x65, 0x65, 0x76, 0x65, 0x76, 0x76, 0x87, + }; + return (v & 1) ? (table[v / 2] >> 4) : (table[v / 2] & 0xf); + }; + for (int i = 0; i < 256; i++) { + assert(popcount8(i) == popcount8_2(i)); + } + + std::vector check; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + uint row_header_offset = y * header_span_size; + uint data_offset = new_bm[row_header_offset] | (new_bm[row_header_offset + 1] << 8u); + check.clear(); + int bmx = x / 8; + if (new_bm[row_header_offset + 2 + bmx] & (1u << (x & 7))) { + int cell_metadata_index = popcount8(new_bm[row_header_offset + 2 + bmx] & ((1u << (x & 7)) - 1)); + for (int xx = 0; xx < bmx; xx++) { + cell_metadata_index += popcount8(new_bm[row_header_offset + 2 + xx]); + } + uint cell_metadata = new_bm[data_offset + cell_metadata_index * 2] | + (new_bm[data_offset + cell_metadata_index * 2 + 1] << 8u); + if ((cell_metadata & 0xf000) == 0xf000) { + check.push_back(cell_metadata & 0xfffu); + } else { + int count = (cell_metadata >> 10u) + 1; + uint element_offset = data_offset + (cell_metadata & 0x3ff); + int last = 0; + while (count--) { + uint b = new_bm[element_offset++]; + if (b & 0x80) { + b = ((b & 0x7f) << 8) + new_bm[element_offset++]; + } + last += b + 1; + check.push_back(last); + } + } + } + const auto &cell = blockmap[y][x]; + if (!std::equal(check.begin(), check.end(), cell.begin(), cell.end())) { + printf("Expected: "); + for (auto &e : cell) printf("%d ", e); + printf("\nGot: "); + for (auto &e : check) printf("%d ", e); + printf("\n"); + fail("(Mismatched blockmap decoding at cell %d,%d)\n", x, y); + } + } + } +#endif + lump.data.resize(8); // keep the x,y,w,h + lump.data.insert(lump.data.end(), new_bm.begin(), new_bm.end()); + wad.update_lump(lump); +} + + +// +// Texture definition. +// Each texture is composed of one or more patches, +// with patches being lumps stored in the WAD. +// The lumps are referenced by number, and patched +// into the rectangular texture space using origin +// and possibly other attributes. +// +typedef PACKED_STRUCT ( + { + short originx; + short originy; + short patch; + short stepdir; + short colormap; + }) mappatch_t; + + +// +// Texture definition. +// A DOOM wall texture is a list of patches +// which are to be combined in a predefined order. +// +typedef PACKED_STRUCT ( + { + char name[8]; + int masked; + short width; + short height; + int obsolete; + short patchcount; + }) maptexture_t; + +struct sprite_frame { + std::array rotations = {-1, -1, -1, -1, -1, -1, -1, -1, -1}; +}; + +void convert_sprites(wad &wad) { + int s_start = wad.get_lump_index("s_start"); + int s_end = wad.get_lump_index("s_end"); + + int sprite_frames = 0; + std::vector> sprite_info(sprite_names.size()); + const int FLAG = (1u << 15); + for (int l = s_start + 1; l < s_end; l++) { + lump sprite; + if (wad.get_lump(l, sprite)) { + auto it = std::find(sprite_names.begin(), sprite_names.end(), to_upper(sprite.name.substr(0, 4))); + if (it == sprite_names.end() || (sprite.name.length() != 6 && sprite.name.length() != 8)) { + printf("Unexpected sprite name %s\n", sprite.name.c_str()); + } else { + int num = (int) (it - sprite_names.begin()); +// printf("Found sprite frame %d %s\n", num, sprite.name.c_str()); + sprite_frames++; + uint frame = toupper(sprite.name[4]) - 'A'; + uint rotation = sprite.name[5] - '0'; + if (sprite_info[num].size() <= frame) { + sprite_info[num].resize(frame + 1); + } + sprite_info[num][frame].rotations[rotation] = l - (s_start + 1); + if (sprite.name.length() == 8) { + frame = toupper(sprite.name[6]) - 'A'; + rotation = sprite.name[7] - '0'; + if (!rotation) + fail("didn't expect a flipped sprite for non rotated version"); // currently disabled in the WHD code though we could use a different bit for flipped vs rotatable + if (sprite_info[num].size() <= frame) { + sprite_info[num].resize(frame + 1); + } + sprite_info[num][frame].rotations[rotation] = FLAG | (l - (s_start + 1)); + } + } + } + } + std::vector sprite_offset; + std::vector frame0_or_rotation_offset; + std::vector rotation_frames; + for (uint i = 0; i < sprite_names.size(); i++) { + sprite_offset.push_back(frame0_or_rotation_offset.size()); + for (uint j = 0; j < sprite_info[i].size(); j++) { + const auto &rotations = sprite_info[i][j].rotations; + int c = 0; + for (int k = 1; k < 9; k++) { + if (rotations[k] != -1) { + c++; + } + } + if (rotations[0] != -1) { + if (c != 0) { + fail("%d %d EXTRA FRAMES FOR SINGLE FRAME\n", i, j); + } else { +// printf("%d %d SINGLE FRAME OK\n", i, j); + frame0_or_rotation_offset.push_back(rotations[0]); + } + } else { + switch (c) { + case 0: + fail("%d %d MISSING\n", i, j); + break; + case 8: + frame0_or_rotation_offset.push_back(FLAG | rotation_frames.size()); + for (int k = 1; k < 9; k++) { + rotation_frames.push_back(rotations[k]); + } + break; + default: + fail("%d %d INVALID WITH %d ANGLES\n", i, j, c); + break; + } + } + } + } + printf("Sprite frames %d\n", sprite_frames); + sprite_offset.push_back(frame0_or_rotation_offset.size()); + std::vector data; + uint16_t off = sprite_offset.size(); + for (uint16_t e : sprite_offset) { + e += off; + append_field(data, e); + } + off += frame0_or_rotation_offset.size(); + for (uint16_t e : frame0_or_rotation_offset) { + if (e & FLAG) e += off; + append_field(data, e); + } + for (uint16_t e : rotation_frames) { + append_field(data, e); + } + printf("Sprite table count = %d\n", (int) sprite_offset.size()); + printf("Frame info count = %d\n", (int) frame0_or_rotation_offset.size()); + printf("Rotation frame count = %d\n", (int) rotation_frames.size()); + printf("Total sprite frame metadata size = %d\n", (int) data.size()); + lump s_end_lump; + wad.get_lump("S_END", s_end_lump); + touched[s_end_lump.num] = TOUCHED_SPRITE_METADATA; + + // put metadata in s_end as we're using s_start for per sprite patch info + s_end_lump.data = data; + wad.update_lump(s_end_lump); +} + +void clear_lump(wad& wad, std::string name, std::string touched_name) { + lump l; + if (wad.get_lump(name, l)) { + l.data.clear(); + cleared_lumps.push_back(l.num); + wad.update_lump(l); + touched[l.num] = touched_name; + } +} + +lump get_free_lump(wad& wad) { + lump l; + if (!cleared_lumps.empty()) { + l.num = cleared_lumps[cleared_lumps.size() - 1]; + printf("get_free_lump resuing %d\n", l.num); + cleared_lumps.pop_back(); + } else { + auto it = wad.get_lumps().end(); + it--; + l.num = it->first + 1; + printf("get_free_lump allocating new %d\n", l.num); + } + return l; +} + +std::vector optimize_column(std::string name, int col, std::vector cmds, const std::vector local_patches, int height, int &seg_count) { + using seg = std::pair>; + + std::vector original_segs; + assert(!(cmds.size()&3)); // everything starts as a regular 4 byte command + int y = 0; + for(int i=0;i<(int)cmds.size();i+=4) { + std::vector cmd; + cmd.insert(cmd.end(), cmds.begin() + i, cmds.begin() + i + 4); + cmd[1] = (cmd[1] & 0x7f) + 1; + original_segs.emplace_back(y, cmd); + y += cmd[1]; + } + assert(seg_count == (int)original_segs.size()); + + // helper method to re-render a column for comparison + auto draw_column = [&](const std::vector &segs, bool leniant = false) { + std::vector> patch_and_y(height); + std::fill(patch_and_y.begin(), patch_and_y.end(), std::make_pair(-1,-1)); + for(const auto &e : segs) { + const auto& cmds = e.second; + int y = e.first; + // 0 is local patch + flags + // 1 is length - 1 + end flag + int i = 0; + int m0 = cmds[i]; + int length = cmds[i + 1]; + i += 2; + if (m0 & WHD_COL_SEG_EXPLICIT_Y) { + y = cmds[i++]; + } + if (y + length > height) { + if (leniant) return std::vector>(); // given garbage return something that wont match + assert(false); + } + if (m0 & WHD_COL_SEG_MEMCPY) { + // memcpy + int from = cmds[i++]; + if (!(m0 & WHD_COL_SEG_MEMCPY_IS_BACKWARDS)) { + for (int j = 0; j < length; j++) { + patch_and_y[y + j] = patch_and_y[from + j]; + } + } else { + for (int j = length-1; j >= 0; j--) { + patch_and_y[y + j] = patch_and_y[from + j]; + } + } + } else { + int lp = m0 & 0xf; + for (int j = 0; j < length; j++) { + patch_and_y[y + j] = std::make_pair(lp, j + cmds[i + 1]); + } + } + } + return patch_and_y; + }; + +#if DEBUG_TEXTURE_OPTIMIZATION + bool had_dump = false; + std::function dump_original; + auto dump_segs = [&](std::vector& segs_to_dump, const char *msg, ...) { + if (!had_dump) { + had_dump = true; + dump_original(); + } + printf("Tex %s col %d: ", name.c_str(), col); + va_list va; + va_start(va, msg); + vprintf(msg, va); + va_end(va); + printf("\n"); + int y= 0; + for(int i=0;i<(int)segs_to_dump.size(); i++) { + const auto &ey = segs_to_dump[i].first; + const auto &ecmds = segs_to_dump[i].second; + int length = ecmds[1]; + printf("%d%s ", ey, y == ey ? " ":"*"); + if (ecmds[0] & 0x80) { + printf("copy %s for %d from %d", ecmds[0]& WHD_COL_SEG_MEMCPY_IS_BACKWARDS ? "backwards" : "forwards", length, ecmds[2]); + } else { + printf("%d for %d +/- %d,%d", ecmds[0]&0xf, length, ecmds[2], ecmds[3]); + } + if (ecmds[0] & WHD_COL_SEG_MEMCPY_SOURCE) printf(" (copyable)"); + printf("\n"); + y = ey + length; + } + }; + dump_original = [&]() { + dump_segs(original_segs, "original"); + }; +#endif + // first try and find patches that have been split by one or more patches in front... these fragments can be drawn + // as a single run, then the one or more patches drawn over the front. (funnily enough this is how things are + // specified in the first place!!!) + auto segs = original_segs; + auto original_result = draw_column(original_segs); + for(int candidate=1; candidate < (int)segs.size(); ) { + bool candidate_removed = false; + for (int match=0; match < candidate && !candidate_removed; match++) { + if (segs[candidate].second[0] == segs[match].second[0] && + segs[candidate].second[2] == segs[match].second[2]) { + // ^ we are the same column and xoffset + if (segs[candidate].second[3] == segs[match].second[3] + segs[candidate].first - segs[match].first) { + // ^ we have what seems like an obscured piece.... can we just draw the top one bigger underneath? + auto new_segs = std::vector(); + new_segs.insert(new_segs.end(), segs.begin(), segs.begin() + candidate); + new_segs.insert(new_segs.end(), segs.begin() + candidate + 1, segs.end()); + new_segs[match].second[1] = segs[candidate].first + segs[candidate].second[1] - segs[match].first; + if (original_result == draw_column(new_segs)) { + segs = new_segs; +#if DEBUG_TEXTURE_OPTIMIZATION + dump_segs(segs, "coalesce same part of same obscured %d and %d", match, candidate); +#endif + candidate_removed = true; + } + } + } + } + if (!candidate_removed) candidate++; + } + + // we often repeat patches vertically, so look to replace segments with memcpy from another one + for(int candidate=1; candidate < (int)segs.size(); candidate++) { + for (int match=0; match < candidate; match++) { + if (!(segs[candidate].second[0] & 0x80) && !(segs[match].second[0] & 0x80) && + (segs[candidate].second[0] & 0xf) == (segs[match].second[0] & 0xf) && + segs[candidate].second[2] == segs[match].second[2]) { + // ^ we are the same column and xoffset and neither is a memcpy + int to_data_top = segs[candidate].second[3]; + int to_data_bottom = to_data_top + segs[candidate].second[1]; + int from_data_top = segs[match].second[3]; + int from_data_bottom = from_data_top + segs[match].second[1]; + if (to_data_top >= from_data_top) { + auto new_segs = std::vector(); + new_segs.insert(new_segs.end(), segs.begin(), segs.begin() + candidate); + if (to_data_bottom <= from_data_bottom) { +// printf("EASY COPY POSSIBILITY\n"); + } else { +// printf("HARD COPY POSSIBILITY\n"); + new_segs[match].second[1] = to_data_bottom - from_data_top; + } + new_segs[match].second[0] |= WHD_COL_SEG_MEMCPY_SOURCE; + new_segs.emplace_back(segs[candidate].first, std::vector{ + 0x80, segs[candidate].second[1], (uint8_t)(segs[match].first + to_data_top - from_data_top) + }); + new_segs.insert(new_segs.end(), segs.begin() + candidate + 1, segs.end()); + if (original_result == draw_column(new_segs)) { +#if DEBUG_TEXTURE_OPTIMIZATION + dump_segs(segs, "memcpy from %d to %d", match, candidate); +#endif + segs = new_segs; + } else { + new_segs[candidate].second[0] |= WHD_COL_SEG_MEMCPY_IS_BACKWARDS; + if (original_result == draw_column(new_segs)) { +#if DEBUG_TEXTURE_OPTIMIZATION + dump_segs(segs, "backwards memcpy from %d to %d", match, candidate); +#endif + segs = new_segs; + } else if (candidate > match + 1){ + // ok lets try doing the copy straight after + new_segs.clear(); + new_segs.insert(new_segs.end(), segs.begin(), segs.begin() + match + 1); + if (to_data_bottom <= from_data_bottom) { +// printf("EASY COPY POSSIBILITY\n"); + } else { +// printf("HARD COPY POSSIBILITY\n"); + new_segs[match].second[1] = segs[candidate].first + segs[candidate].second[1] - segs[match].first; + } + new_segs[match].second[0] |= WHD_COL_SEG_MEMCPY_SOURCE; + new_segs.emplace_back(segs[candidate].first, std::vector{ + 0x80, segs[candidate].second[1], (uint8_t)(segs[match].first + to_data_top - from_data_top) + }); + new_segs.insert(new_segs.end(), segs.begin() + match + 1 , segs.begin() + candidate); + new_segs.insert(new_segs.end(), segs.begin() + candidate + 1, segs.end()); + if (original_result == draw_column(new_segs)) { +#if DEBUG_TEXTURE_OPTIMIZATION + dump_segs(segs, "memcpy from %d to %d but with the latter moved next to the former", match, candidate); +#endif + segs = new_segs; + } else { + new_segs[candidate].second[0] |= WHD_COL_SEG_MEMCPY_IS_BACKWARDS; + if (original_result == draw_column(new_segs)) { +#if DEBUG_TEXTURE_OPTIMIZATION + dump_segs(segs, "memcpy backwards from %d to %d but with the latter moved next to the former", match, candidate); +#endif + segs = new_segs; + } + } + } + } + } + } + } + } + // todo use flags here + // we often have things like 16 of the same patch tiled vertically. this will now be 1 regular segment and 15 memcpy segments. + // we can often do better by collapsing this into fewer memcpys (in this case 1) + for(int candidate=2; candidate < (int)segs.size(); ) { + bool candidate_removed = false; + if (segs[candidate].second[0] & 128) { + for(int earlier=1; earlier segs[earlier].first)) { + // don't think too much just try it + auto new_segs = std::vector(); + new_segs.insert(new_segs.end(), segs.begin(), segs.begin() + candidate); + new_segs.insert(new_segs.end(), segs.begin() + candidate + 1, segs.end()); + new_segs[earlier].second[1] += segs[candidate].second[1]; + // call with second arg = true (lenient) as we may produce an invalid memcpy here, but it is hard to check without doing the same work that draw_column does + if (original_result == draw_column(new_segs, true)) { + // hail mary success! + segs = new_segs; +#if DEBUG_TEXTURE_OPTIMIZATION + dump_segs(segs, "memcpy coalesce!"); +#endif + candidate_removed = true; + break; + } + } + } + } + } + if (!candidate_removed) candidate++; + } + if (segs != original_segs) { +#if DEBUG_TEXTURE_OPTIMIZATION + dump_segs(segs, "optimized"); +#endif + assert(original_result == draw_column(segs)); + cmds.clear(); + y = 0; + for(int i=0;i<(int)segs.size();i++) { + const auto &ey = segs[i].first; + const auto &ecmds = segs[i].second; + int length = ecmds[1]; + cmds.push_back(ecmds[0] | (y != ey ? 0x40 : 0)); + cmds.push_back((length-1) | (i == (int)segs.size() - 1 ? 0x80 : 0)); + if (y != ey) { + cmds.push_back(ey); + } + cmds.insert(cmds.end(), ecmds.begin() + 2, ecmds.end()); + y = ey + length; + } + seg_count = segs.size(); + } + return cmds; +} + +std::vector image_to_patch(std::vector image, int w, int h) { + std::vector patch_data; + std::vector offset_data; + patch_header header = { + .width = (short)w, + .height = (short)h, + .leftoffset = 0, + .topoffset = 0, + }; + append_field(patch_data, header); + std::vector post_data; + for (int col = 0; col < w; col++) { + uint32_t off = 8 + 4 * w + post_data.size(); + append_field(offset_data, off); + int y=0; + do { + while (y < h && image[y*w+col] == -1) y++; + if (y == h) { + post_data.push_back(0xff); + break; + } + int base = y; + while (y < h && image[y*w+col] != -1) y++; + post_data.push_back(base); + assert(y > base); + post_data.push_back(y - base); + post_data.push_back(0); + for(int yy=base; yy pname_lookup; + if (wad.get_lump("pnames", pnames)) { + int count = get_field(pnames.data, 0); + char *data = (char *) pnames.data.data(); + printf("Patch count %d\n", count); + pname_lookup.resize(count); + for (int i = 0; i < count; i++) { + auto name = wad::wad_string(data + 4 + i * 8); + pname_lookup[i] = wad.get_lump_index(name); +// printf("patch %d %s\n", i, name.c_str()); + } + touched[pnames.num]=TOUCHED_PNAMES; + } else { + fail("No PNAMES lump"); + } + + statsomizer widths("texture width"); + statsomizer heights("texture height"); + texture_index tex_index; + lump tex1_lump, tex2_lump; + if (!wad.get_lump("texture1", tex1_lump)) { + fail("no texture1 lump"); + } + touched[tex1_lump.num]=TOUCHED_TEX_METADATA; + auto numtextures1 = get_field(tex1_lump.data, 0); + int numtextures = numtextures1; + if (wad.get_lump("texture2", tex2_lump)) { + numtextures += get_field(tex2_lump.data, 0); + clear_lump(wad, "texture2", TOUCHED_TEX_METADATA); + } + + int directory_offset = 4; + lump *tex_lump = &tex1_lump; + printf("Num textures: %d\n", numtextures); + int new_texture_count = special_textures.size(); + std::vector all_metadata; + std::map orig_tex_numbers; + for (int i = 0; i < numtextures; i++, directory_offset += 4) { + if (i == numtextures1) { + tex_lump = &tex2_lump; + directory_offset = 4; + } + + int offset = get_field(tex_lump->data, directory_offset); + if (offset > (int) tex_lump->data.size()) + fail("bad texture directory"); + + auto mtexture = get_field_inc(tex_lump->data, offset); + std::string name = to_lower(wad::wad_string(mtexture.name)); + orig_tex_numbers[name] = i; + auto it = tex_index.lookup.find(name); + if (it != tex_index.lookup.end()) { + fail("Duplicate texture %s\n", name.c_str()); + } + + // we want to reassign the texture indexes; + auto it2 = std::find(special_textures.begin(), special_textures.end(), to_upper(name)); + int index; + if (it2 != special_textures.end()) { + index = it2 - special_textures.begin(); + } else { + index = new_texture_count++; + } + tex_index.lookup[name] = index; + tex_index.textures.resize(new_texture_count); + auto &tex_whd = tex_index.textures[index].whd; + tex_whd.width = mtexture.width; + int pow2 = 1u << (31 - __builtin_clz(tex_whd.width)); + // engine actually rounds down texture sizes to power of 2 (notably all textures are power of 2 except AASTINKY + if (mtexture.width != pow2) { +// printf("WARNING non pow2 tex width truncated %s\n", name.c_str()); + tex_whd.width = pow2; + } + if (mtexture.height >= 256) { + fail("Texture too tall"); + } + tex_whd.height = mtexture.height; + tex_whd.patch0 = 0; + tex_whd.patch_count = mtexture.patchcount; + printf("Texture %d %s %dx%d, %d patches\n", index, name.c_str(), mtexture.width, mtexture.height, mtexture.patchcount); + widths.record(mtexture.width); + heights.record(mtexture.height); + lump patch; + std::array colorCounts{}; + std::vector mpatches; + +#if TEXTURE_PIXEL_STATS + static std::vector pixel_usage; + static std::vector column_usage; + pixel_usage.clear(); + column_usage.clear(); + pixel_usage.resize(tex_whd.width * tex_whd.height); + column_usage.resize(tex_whd.width); +#endif + int solid_patches = 0; + std::vector> column_pixels(mtexture.width); + std::vector pixel_patch(mtexture.width * mtexture.height); + std::fill(pixel_patch.begin(), pixel_patch.end(), -1); + + for (int j = 0; j < mtexture.patchcount; j++) { + auto mpatch = get_field_inc(tex_lump->data, offset); + mpatches.push_back(mpatch); + if (pname_lookup[mpatch.patch] == -1) { + fail("Missing pname mapping for patch %d in texture %d\n", pname_lookup[j], j); + } + if (wad.get_lump(pname_lookup[mpatch.patch], patch)) { + patch_for_each_pixel(patch, [&](uint8_t pixel) { + colorCounts[pixel]++; + }); + bool patch_solid = true; +#if TEXTURE_PIXEL_STATS + // render every pixel + const patch_header *ph = (const patch_header *) patch.data.data(); + for (int col = 0; col < ph->width; col++) { + int xp = col + mpatch.originx; + if (xp < 0) continue; + if (xp >= tex_whd.width) break; + if (column_pixels[xp].empty()) { + column_pixels[xp].resize(tex_whd.height); + std::fill(column_pixels[xp].begin(), column_pixels[xp].end(), -1); + } + uint32_t col_offset = *(uint32_t *) (patch.data.data() + 8 + col * 4); + const uint8_t *post = patch.data.data() + col_offset; + bool hit_column = false; + int touched_pixels = 0; + int y0 = mpatch.originy; + if (y0 < -128 || y0 > 127) { + fail("originY is %d\n", y0); + } + if (y0 < 0) y0 = 0; + if (y0 > tex_whd.height) y0 = tex_whd.height; + int y1 = mpatch.originy + ph->height; + if (y1 < 0) y1 = 0; + if (y1 > tex_whd.height) y1 = tex_whd.height; + while (post[0] != 0xff) { + int yp = post[0] + mpatch.originy; + for (int k = 3; k < 3 + post[1]; k++, yp++) { + if (yp >= tex_whd.height) break; + if (yp < 0) continue; + pixel_usage[yp * tex_whd.width + xp]++; + pixel_patch[yp * tex_whd.width + xp] = j; + column_pixels[xp][yp] = post[k]; + + touched_pixels++; + if (!hit_column) { + column_usage[xp]++; + hit_column = true; + } + } + post += 4 + post[1]; + } + if (touched_pixels != y1 - y0) { + patch_solid = false; + } + } + if (patch_solid) solid_patches++; +#endif + printf(" Patch %d (%d): %d at %d,%d %dx%d solid = %d\n", j, pname_lookup[mpatch.patch], mpatch.patch, + mpatch.originx, mpatch.originy, ph->width, ph->height, patch_solid); + } else { + // seems unlike!y! + fail("Patch %d missing\n", pname_lookup[j]); + } + } +#if TEXTURE_PIXEL_STATS + for (int x = 0; x < tex_whd.width; x++) { + texture_column_patches.record(column_usage[x]); + texture_column_patches1.record(column_usage[x] == 1); + } + bool had_transparent = false; + for (auto &xx : pixel_usage) { + if (!xx) { + had_transparent = true; + break; + } + } + texture_transparent.record(had_transparent); + if (had_transparent) { + texture_transparent_patch_count.record(mpatches.size()); + } +#endif + + if (mpatches.size() == 1) { + texture_single_patch.record(1); + if (mpatches[0].originx == 0 && mpatches[0].originy == 0) { + texture_single_patch00.record(1); + tex_whd.patch_count = 0; // special marker for unmoved patch + tex_whd.patch0 = pname_lookup[mpatches[0].patch]; + } else { + texture_single_patch00.record(0); + if (had_transparent) { + printf("WARNING: Found a transparent not at offset 0,0 %s\n", name.c_str()); + } + } + } else { + texture_single_patch.record(0); + if (had_transparent) { + printf("Found a transparent with > 1 patch\n"); + } + } + //tex_whd.approx_color = std::max_element(colorCounts.begin(), colorCounts.end()) - colorCounts.begin(); + int max_unique_col_patches = 0; + int max_seg_count = 0; + std::vector metadata; + if (tex_whd.patch_count) { + std::vector local_patches; + std::vector> patch_runs(tex_whd.width); + for (int x = 0; x < tex_whd.width; x++) { + auto &col_patch_runs = patch_runs[x]; + std::set unique_col_patches; + int last = -1; + int run = 0; + int localp; + bool had_col_transparent = false; + for (int y = 0; y < tex_whd.height; y++) { + int p = pixel_patch[y * tex_whd.width + x]; + if (p != last) { + if (run) { + if (localp == 0xff) { + had_col_transparent = true; + } else { + col_patch_runs.push_back(localp); // which patch + if (run > 128) fail("run is too long %d\n", run); + col_patch_runs.push_back(run - 1); + col_patch_runs.push_back((uint8_t) mpatches[last].originx); + col_patch_runs.push_back((int8_t) (y-run)-mpatches[last].originy); + } + } + last = p; + if (p != -1) { + auto f = std::find(local_patches.begin(), local_patches.end(), pname_lookup[mpatches[p].patch]); + localp = f - local_patches.begin(); + if (f == local_patches.end()) { + local_patches.push_back( pname_lookup[mpatches[p].patch]); + } + unique_col_patches.insert(localp); + } else { + localp = 0xff; + } + run = 1; + } else { + run++; + } + } + if (run) { + // hack for sky - we allow transparency at the bottom of a texture + if (localp == 0xff) { +// had_col_transparent = true; + if (!col_patch_runs.empty()) { + col_patch_runs[col_patch_runs.size() - 3] |= 0x80; + } + } else { + col_patch_runs.push_back(localp); // which patch + if (run > 128) fail("run is too long %d\n", run); + col_patch_runs.push_back(128 | (run - 1)); + col_patch_runs.push_back((uint8_t) mpatches[last].originx); + col_patch_runs.push_back((int8_t) (tex_whd.height-run)-mpatches[last].originy); + } + } + if (had_col_transparent) { + if (col_patch_runs.size() != 4 && !col_patch_runs.empty()) { + printf("WARNING: Can't mix transparency with anything but single patch column, will be undefined. tex=%s col %d\n", name.c_str(), x); + } + } + if (col_patch_runs.size() == 4) { + col_patch_runs[3] = 0; // force originy to 0 for single patch column to match DOOM R_GenerateLookup behavior + } else if (!had_transparent) { + // only interested in duplicate columns if they aren't already single patch (which are de-duplicated nyway) + for (int x2 = 0; x2 < x; x2++) { + if (column_pixels[x] == column_pixels[x2]) { + // todo encode "same" columns? ... + //printf("OOH %d == %d\n", x, x2); + break; + } + } + } + max_unique_col_patches = std::max(max_unique_col_patches, (int)unique_col_patches.size()); + } + assert(local_patches.size() && local_patches.size() <= tex_whd.patch_count); + printf(" local patch size %d\n", (int)local_patches.size()); + tex_whd.patch_count = local_patches.size(); + if (local_patches.size() > 16) { + fail("too many local patches %d\n", (int) local_patches.size()); + } + for(int n=0;n>8); + } + std::vector single_patch_metadata; + int single_patch_last = -1; + int single_patch_start = 0; + int single_patch_xoffset_last = 0; + int single_patch_xoffset = 0; + int single_patch_lpn = 0; + int single_patch_lpn_last = 0; + for(int x = 0; x (int) patch_runs.size()) break; + for (; x2 < (int) patch_runs.size(); x2++) { + if (patch_runs[x] != patch_runs[x2]) break; + } + if (x2 - x > 256) x2 = x + 256; + for (int e = 0; e < (int) patch_runs[x].size(); e += 4) { + int xoffset = (x - patch_runs[x][e + 2]) & 0xff; + patch_runs[x][e + 2] = xoffset; + } + bool have_repeat = false; // same source column from same patch multiple times in the same texture colum + // lets not go crazy with re-ordering or anything + for (int e = 0; e < (int) patch_runs[x].size(); e += 4) { + for (int e2 = 0; e2 < e; e2 += 4) { + if (patch_runs[x][e2] == patch_runs[x][e] && patch_runs[x][e2 + 2] == patch_runs[x][e + 2]) { + have_repeat = true; + break; + } + } + } + int seg_count = patch_runs[x].size() / 4; + if (have_repeat) { + patch_runs[x] = optimize_column(name, x, patch_runs[x], local_patches, tex_whd.height, seg_count); + } + max_seg_count = std::max(seg_count, max_seg_count); + metadata.push_back(x2 - x - 1); + metadata.insert(metadata.end(), patch_runs[x].begin(), patch_runs[x].end()); + x = x2; + } + } + printf(" Solids %d/%d max segs %d max unique col patches %d\n", solid_patches, mtexture.patchcount, max_seg_count, max_unique_col_patches); + if (tex_whd.patch_count && solid_patches != mtexture.patchcount) { + printf("warning: multi patch transparent texture tex=%s\n", name.c_str()); // todo is this actually allowed on a per column basis (i.e. mix compostie cols with transparent non composite cols?) + } + if (max_seg_count > WHD_MAX_COL_SEGS || max_unique_col_patches > WHD_MAX_COL_UNIQUE_PATCHES) { + auto new_patch = get_free_lump(wad); + char lname[32]; + sprintf(lname, "_SYN%d", new_patch.num); + new_patch.name = lname; + printf("warning: overly complex texture %s rendering as new patch %d\n", name.c_str(), new_patch.num); + tex_whd.patch_count = 0; + tex_whd.patch0 = new_patch.num; + metadata.clear(); + std::vector pixels(tex_whd.width * tex_whd.height); + for(uint x=0;x whd_textures;//( tex_index.textures.size() * sizeof(whdtexture_t)); + whd_textures.push_back(tex_index.textures.size() & 0xff); + whd_textures.push_back(tex_index.textures.size() >> 8); + uint metadata_start = tex_index.textures.size() * sizeof(whdtexture_t); // relative to the texture array not the beginning of whd_textures + for (auto &t : tex_index.textures) { + if (t.whd.patch_count) { + int new_offset = metadata_start + t.whd.metdata_offset; + assert(new_offset < 65536); + t.whd.metdata_offset = new_offset; + } + append_field(whd_textures, t); + } + assert(whd_textures.size() == metadata_start + 2); + whd_textures.insert(whd_textures.end(), all_metadata.begin(), all_metadata.end()); + tex_orig_size.record(tex1_lump.data.size()); + tex1_lump.data = whd_textures; + tex_new_size.record(tex1_lump.data.size()); + printf("WHD TEXTURE DATA SIZE %d\n", (int) tex1_lump.data.size()); + wad.update_lump(tex1_lump); + widths.print_summary(); + heights.print_summary(); + for(const auto & e: tex_index.lookup) { + printf("%s : %d\n", e.first.c_str(), e.second); + } + for(auto &e : tex_animdefs) { + auto it1 = tex_index.lookup.find(to_lower(e.first)); + auto it2 = tex_index.lookup.find(to_lower(e.last)); + if (it1 != tex_index.lookup.end() && it2 != tex_index.lookup.end()) { + int remapped_range = it2->second - it1->second; + int orig_range = orig_tex_numbers[to_lower(e.last)] - orig_tex_numbers[to_lower(e.first)]; + printf("HAVE TEX ANIM %s -> %s range %d\n", e.first, e.last, remapped_range); + if (remapped_range != orig_range) { + fail("Mismatch in anim textures original range %d not equal to remapped range %d", orig_range, remapped_range); + } + } else { + printf("DONT HAVE TEX ANIM %s -> %s\n", e.first, e.last); + } + } + return tex_index; +} + +statsomizer flat_rawsize("Flat raw size"); +statsomizer flat_c2size("Flat c2 size"); +statsomizer flat_colors("Flat colors"); +statsomizer flat_under_colors[] = { + statsomizer("2 color flat"), + statsomizer("4 color flat"), + statsomizer("8 color flat"), + statsomizer("16 color flat"), + statsomizer("32 color flat"), + statsomizer("64 color flat"), + statsomizer("128 color flat"), + statsomizer("256 color flat"), + }; + +void convert_flats(wad &wad) { + int fstart = wad.get_lump_index("f_start"); + int fend = wad.get_lump_index("f_end"); + if (fstart == -1 || fend == -1) { + fail("missing f_start/f_end marker"); + } + // it is too painful to reorder flats, so we keep a mapping from the special_flat indexes to the real indexes + std::vector special_to_flat(special_flats.size()); + std::fill(special_to_flat.begin(), special_to_flat.end(), 0xff); + std::vector flat_to_special; + for (int f = fstart+1; f < fend; f++) { + // todo we only need flats mentioned in sectors (or well known) + lump lump; + if (wad.get_lump(f, lump)) { + auto ff = std::find(special_flats.begin(), special_flats.end(), to_upper(lump.name)); + if (ff != special_flats.end()) { +// printf("FOUND SPECIAL %d %s at %d\n", (int)(ff - special_flats.begin()), lump.name.c_str(), f-fstart-1); + special_to_flat[ff - special_flats.begin()] = f - fstart - 1; + flat_to_special.push_back(ff - special_flats.begin()); + } else { + flat_to_special.push_back(0xff); + } + std::set colors; + for (const auto &p: lump.data) colors.insert(p); + assert(lump.data.size() == 64 * 64); + std::vector pix; + for (int y = 0; y < 64; y++) { + for (int x = 0; x < 64; x++) { + pix.push_back(lump.data[y * 64 + x]); + } + } + uint best = std::numeric_limits::max(); + std::vector> zposts; + std::vector> best_zposts; + std::shared_ptr decoder_output; + std::shared_ptr best_decoder_output; + uint decoder_size; + uint best_decoder_size; + int choice = 0; + auto choose = [&](int c, uint size) { + if (size < best) { + choice = c; + best = size; + best_zposts = zposts; + best_decoder_output = std::make_shared(*decoder_output); + best_decoder_size = decoder_size; + } + return size; + }; + std::vector same; + bool have_same; + auto posts = to_merged_posts(pix, 64, 64, same, have_same); + uint s1 = choose(0, consider_compress_pixels_only(lump.name, posts, decoder_output, zposts, 64, 64, decoder_size)); ((void)s1); +#if !USE_PIXELS_ONLY_FLAT + uint s2 = choose(1, consider_compress3(lump.name, posts, decoder_output, zposts, 64, 64)); +#endif + if (best_decoder_size > WHD_FLAT_DECODER_MAX_SIZE) { + fail("flat decoder is too big %s %d", lump.name.c_str(), best_decoder_size); + } + fwinners[choice]++; + flat_rawsize.record(4096); + if (have_same) { + int savings = 0; + for (int i = 0; i < 64; i++) { + if (same[i]) { + savings += zposts[same[i] - 1]->bit_size(); + savings -= 1 + bitcount8_table[i]; + } + } + if (savings < 0) have_same = false; + flat_have_same_savings.record(have_same); + } + byte_vector_bit_output final_bo; +#if !USE_PIXELS_ONLY_FLAT + assert(choice < 2); + final_bo.write(bit_sequence(choice, 1)); +#endif + decoder_output->write_to(final_bo); + final_bo.write(bit_sequence(have_same, 1)); + for (int x = 0; x < 64; x++) { + if (have_same) { + final_bo.write(bit_sequence(same[x] != 0, 1)); + if (same[x]) { + assert(!zposts[x]->bit_size()); + assert(same[x] - 1 < x); + assert(!same[same[x] - 1]); + // todo down one + final_bo.write(bit_sequence(same[x] - 1, bitcount8_table[x])); + } else { + zposts[x]->write_to(final_bo); + } + } else { + zposts[x]->write_to(final_bo); + } + } + compressed.insert(f); + touched[f] = TOUCHED_FLAT; + lump.data = final_bo.get_output(); + flat_c2size.record(lump.data.size()); + wad.update_lump(lump); + flat_colors.record(colors.size()); + uint x = colors.size(); + if (x) x--; + x = 31 - __builtin_clz(x); + assert(x < 8); + flat_under_colors[x].record(colors.size()); + } else { + flat_to_special.push_back(0xff); + } + } + lump fstart_lump; + wad.get_lump("f_start", fstart_lump); + fstart_lump.data = special_to_flat; + fstart_lump.data.insert(fstart_lump.data.end(), flat_to_special.begin(), flat_to_special.end()); + wad.update_lump(fstart_lump); +} + +void convert_demo(wad & wad, lump& l) { + touched[l.num] = "Demo"; + compressed.insert(l.num); + name_required.insert(l.name); + using wap = symbol_sink, std::shared_ptr,true>; + wap fb_delta("FwdBack Delta"); + wap strafes("Strafe"); + wap turn_delta("Turn Delta"); + wap buttonss("Buttons"); + wap changes("Changes"); + sink_wrappers> wrappers{ + fb_delta, + strafes, + turn_delta, + buttonss, + changes}; + + auto bo = std::make_shared(); + statsomizer all_same("All same"); + statsomizer all_same_1("All sameNB"); + for(int pass=0;pass<2;pass++) { + uint pos = 13; + const auto &data = l.data; + int last_fb = 0; + int last_turn = 0; + while (pos < data.size()) { + int8_t fb = data[pos++]; + if (fb == -128) { + changes.output(16); break; + break; + } + int8_t strafe = data[pos++]; + int8_t turn = data[pos++]; + uint8_t buttons = data[pos++]; + changes.output((last_fb != fb) | ((strafe!=0)<<1) | ((last_turn!=turn)<<2) | ((buttons!=0)<<3)); + if (last_fb != fb) { + fb_delta.output(to_zig(fb - last_fb)); + } + if (strafe != 0) { + strafes.output(to_zig(strafe)); + } + if (last_turn != turn) { + turn_delta.output(to_zig(turn - last_turn)); + } + if (buttons != 0) { + buttonss.output(buttons); + } + last_fb = fb; + last_turn = turn; + } + assert(pos == data.size()); + if (!pass) { + wrappers.begin_output(bo); + output_min_max_best(bo, changes.huff); + output_min_max_best(bo, fb_delta.huff); + output_min_max_best(bo, strafes.huff); + output_min_max_best(bo, turn_delta.huff); + output_min_max_best(bo, buttonss.huff); + } + } + all_same_1.print_summary(); + all_same.print_summary(); + auto result = bo->get_output(); + printf("%d -> %d\n", (int)l.data.size(), (int)result.size()); + demo_size_orig.record(l.data.size()); + demo_size.record(result.size()); + uint dsize = decoder_size(changes.huff) + decoder_size(fb_delta.huff) + decoder_size(strafes.huff) + decoder_size(turn_delta.huff) + decoder_size(buttonss.huff); + assert(dsize < 256); // could make this bigger (oh no an extra 3 bytes), or divide by 2 but seems fine for now + l.data.resize(13); // original header + l.data.push_back(dsize); + l.data.insert(l.data.end(), result.begin(), result.end()); + wad.update_lump(l); +} + +int mus_total1, mus_total2; + +// Structure to hold MUS file header +typedef struct { + byte id[4]; + unsigned short scorelength; + unsigned short scorestart; +} mus_header; + +void convert_music(std::pair &e) { + touched[e.first] = TOUCHED_MUSIC; + name_required.insert(e.second.name); +#if USE_MUSX + auto &h = e.second.data; + if (h[0] == 'M' && h[1] == 'U' && h[2] == 'S' && h[3] == 26) { + auto new_mus = compress_mus(e); + int original_size = e.second.data.size(); + h.clear(); + h.push_back('M'); + h.push_back('U'); + h.push_back('S'); + h.push_back('X'); + printf("Compress %s MUS %d -> %d\n", e.second.name.c_str(), original_size, (int) new_mus.size()); + mus_total1 += original_size; + mus_total2 += new_mus.size(); + write_word(h, 4, new_mus.size()); + h.insert(h.end(), new_mus.begin(), new_mus.end()); + compressed.insert(e.first); + } else { + fail("Expected MUS track %s\n", e.second.name.c_str()); + } +#else +#error no longer supported +#endif +} + + +static int +adpcm_encode_data(const std::vector &in, std::vector &out, int num_channels, int samples_per_block, + int lookahead, int noise_shaping) { + size_t block_size = (samples_per_block - 1) / (num_channels ^ 3) + (num_channels * 4); + int16_t *pcm_block = (int16_t *) malloc(samples_per_block * num_channels * 2); + uint8_t *adpcm_block = (uint8_t *) malloc(block_size); + void *adpcm_cnxt = NULL; + + if (!pcm_block || !adpcm_block) { + fprintf(stderr, "could not allocate memory for buffers!\n"); + return -1; + } + + int num_samples = in.size(); + int off = 0; + while (num_samples) { + int this_block_adpcm_samples = samples_per_block; + int this_block_pcm_samples = samples_per_block; + size_t num_bytes; + + if (this_block_pcm_samples > num_samples) { + this_block_adpcm_samples = ((num_samples + 6) & ~7) + 1; + block_size = (this_block_adpcm_samples - 1) / (num_channels ^ 3) + (num_channels * 4); + this_block_pcm_samples = num_samples; + } + + // if this is the last block and it's not full, duplicate the last sample(s) so we don't + // create problems for the lookahead + + memcpy(pcm_block, in.data() + off * num_channels, this_block_pcm_samples * num_channels * 2); + if (this_block_adpcm_samples > this_block_pcm_samples) { + int16_t *dst = pcm_block + this_block_pcm_samples * num_channels, *src = dst - num_channels; + int dups = (this_block_adpcm_samples - this_block_pcm_samples) * num_channels; + while (dups--) + *dst++ = *src++; + } + + // if this is the first block, compute a decaying average (in reverse) so that we can let the + // encoder know what kind of initial deltas to expect (helps initializing index) + if (!adpcm_cnxt) { + int32_t average_deltas[2]; + int i; + + average_deltas[0] = average_deltas[1] = 0; + + for (i = this_block_adpcm_samples * num_channels; i -= num_channels;) { + average_deltas[0] -= average_deltas[0] >> 3; + average_deltas[0] += abs((int32_t) pcm_block[i] - pcm_block[i - num_channels]); + + if (num_channels == 2) { + average_deltas[1] -= average_deltas[1] >> 3; + average_deltas[1] += abs((int32_t) pcm_block[i - 1] - pcm_block[i + 1]); + } + } + + average_deltas[0] >>= 3; + average_deltas[1] >>= 3; + + adpcm_cnxt = adpcm_create_context(num_channels, lookahead, noise_shaping, average_deltas); + } + + adpcm_encode_block(adpcm_cnxt, adpcm_block, &num_bytes, pcm_block, this_block_adpcm_samples); + + if (num_bytes != block_size) { + fprintf(stderr, "\radpcm_encode_block() did not return expected value (expected %d, got %d)!\n", + (int) block_size, (int) num_bytes); + return -1; + } + out.insert(out.end(), adpcm_block, adpcm_block + num_bytes); + + num_samples -= this_block_pcm_samples; + off += this_block_pcm_samples; + } + + if (adpcm_cnxt) + adpcm_free_context(adpcm_cnxt); + + free(adpcm_block); + free(pcm_block); + return 0; +} + +bool convert_sound(std::pair &e) { + auto &lump = e.second; + const int lumplen = lump.data.size(); + const uint8_t *data = lump.data.data(); + // Check the header, and ensure this is a valid sound + + if (lumplen < 8 + || data[0] != 0x03 || data[1] != 0x00) { + // Invalid sound + return false; + } + + name_required.insert(lump.name); + // 16 bit sample rate field, 32 bit length field + + int samplerate = (data[3] << 8) | data[2]; + if (samplerate != 11025) { + printf("oou %s %d\n", lump.name.c_str(), samplerate); + } + int length = (data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4]; + + // If the header specifies that the length of the sound is greater than + // the length of the lump itself, this is an invalid sound lump + + // We also discard sound lumps that are less than 49 samples long, + // as this is how DMX behaves - although the actual cut-off length + // seems to vary slightly depending on the sample rate. This needs + // further investigation to better understand the correct + // behavior. + + if (length > lumplen - 8 || length <= 48) { + return false; + } + + // The DMX sound library seems to skip the first 16 and last 16 + // bytes of the lump - reason unknown. + + std::vector out; + out.insert(out.end(), data, data + 8); // copy the original header + out[1] = 0x80; // something difference + + data += 16; + length -= 32; + std::vector in(length); + for (int i = 0; i < length; i++) { + in[i] = (data[i] ^ 0x80) << 8; + } + + int block_size = 128; + int num_channels = 1; + int lookahead = LOOKAHEAD; + int samples_per_block = (block_size - num_channels * 4) * (num_channels ^ 3) + 1; + if (adpcm_encode_data(in, out, num_channels, samples_per_block, lookahead, + //NOISE_SHAPING_DYNAMIC + NOISE_SHAPING_OFF // noise shaping sounds worse at this low frequency (and we low pass + // after upscalinga at runtime later anyway) + ) < 0) { + return false; + } + compressed.insert(e.first); + sfx_orig_size.record(e.second.data.size()); + e.second.data = out; + sfx_new_size.record(e.second.data.size()); + return true; +} + +void ColorShiftPalette (byte *inpal, byte *outpal + , int r, int g, int b, int shift, int steps) +{ + int i; + int dr, dg, db; + byte *in_p, *out_p; + + in_p = inpal; + out_p = outpal; + + for (i=0 ; i<256 ; i++) + { + dr = r - in_p[0]; + dg = g - in_p[1]; + db = b - in_p[2]; + + out_p[0] = in_p[0] + dr*shift/steps; + out_p[1] = in_p[1] + dg*shift/steps; + out_p[2] = in_p[2] + db*shift/steps; + + in_p += 3; + out_p += 3; + } +} + +#if 0 +std::vector png_to_patch(wad& wad, const char *prefix, const char *name) { + std::vector buffer; + std::vector image32; + unsigned w, h; + + std::string filename = prefix; + filename += name; + filename += ".png"; + lodepng::load_file(buffer, filename); //load the image file with given filename + lodepng::State state; + unsigned error = lodepng::decode(image32, w, h, state, buffer); + lump palette; + std::map palette_lookup; + wad.get_lump("playpal", palette); + for(int i=0;i<256;i++) { + uint32_t pixel = palette.data[i*3] + (palette.data[i*3+1] << 8) + (palette.data[i*3+2] << 16) + 0xff000000u; + palette_lookup[pixel]=i; + } + auto image = std::vector(w*h); + for(uint i = 0; i < w*h; i++) { + if (image[i*4+3] == 0xff) { + uint32_t col = image32[i*4] + (image32[i*4+1]<<8) + (image32[i*4+2]<<16) + (image32[i*4+3]<<24); + image[i] = palette_lookup[col]; + } else { + image[i] = -1; + } + } + auto patch_data = image_to_patch(image, w, h); + printf("const uint8_t lump_%s[] = {\n", name); + for(int i=0;i<(int)patch_data.size();i+=24) { + printf(" "); + for(int j=i;j= argc) { + if (required) usage(); + return (const char *)NULL; + } + if (!strcmp(argv[argn], "-no-super-tiny")) { + super_tiny = false; + } + return argv[argn++]; + }; + try { + const char *wad_name = next_arg(); + auto wad = wad::read(wad_name); + int size = 0; + for(const auto &e : wad.get_lumps()) { + size += e.second.data.size(); + } + printf("LUMPS ORIG SIZE %d\n", size); + auto output_filename = next_arg(); + next_arg(false); // check for more options + const char *pos = std::max(strrchr(wad_name, '\\'), strrchr(wad_name, '/')); + if (pos) pos++; + else pos = wad_name; + wad.set_name(pos); + for (auto &e : wad.get_lumps()) { + std::string dpsound = to_lower(e.second.name); + if (dpsound[1] == 'p') { + dpsound[1] = 's'; + if (sfx_lumpnames.find(dpsound) != sfx_lumpnames.end()) { + e.second.data.clear(); + cleared_lumps.push_back(e.first); + touched[e.first] = TOUCHED_SFX; + } + } + } + clear_lump(wad, "dmxgus", TOUCHED_DMX); + clear_lump(wad, "dmxgusc", TOUCHED_DMX); + clear_lump(wad, "stdisk", TOUCHED_UNUSED_GRAPHIC); + clear_lump(wad, "stcdrom", TOUCHED_UNUSED_GRAPHIC); + + lump palette; + wad.get_lump("playpal", palette); + static uint8_t outpal[768]; + bool mismatch = false; + for(int i=1;i<14;i++) { + if (i < 9) ColorShiftPalette(palette.data.data(), outpal, 255, 0, 0, i, 9); + else if (i < 13) ColorShiftPalette(palette.data.data(), outpal, 215, 186, 69, i-8, 8); + else ColorShiftPalette(palette.data.data(), outpal, 0, 256, 0, 1, 8); + if (!memcmp(palette.data.data()+i*768, outpal, 768)) { +// printf("Palette %d matches\n", i); + } else { +// printf("Palette %d mismatch\n", i); + mismatch = true; + } + } + touched[palette.num] = TOUCHED_PALETTE; + lump lmisc; + wad.get_lump("colormap", lmisc); + touched[lmisc.num] = TOUCHED_COLORMAP; + wad.get_lump("genmidi", lmisc); + touched[lmisc.num] = TOUCHED_GENMIDI; + + + if (!mismatch) { + // truncate the palette to a single copy + palette.data.resize(768); + wad.update_lump(palette); + compressed.insert(palette.num); + } else { + printf("warning: palettes are not standard\n"); + } + +#if 0 + std::vector font; + font.insert(font.begin(), normal_font_data, normal_font_data + sizeof(normal_font_data)); + auto fontz = std::make_shared(); + printf("FONTO %d %d\n", (int)font.size(), consider_compress_data("font", font, fontz)); + auto fontzd = fontz->get_output(); + printf("static const uint8_t normal_font_data_z[%d] = {\n", (int)fontzd.size()); + for(int i=0;i(); + printf("ENDOOMALL %d %d\n", (int)endoom.data.size(), consider_compress_data("endoom", endoom.data, endoomz)); + std::vector attr; + std::vector text; + for(int i=0;i<(int)endoom.data.size();i+=2) { + text.push_back(endoom.data[i]); + attr.push_back(endoom.data[i+1]); + } + auto textz = std::make_shared(); + auto attrz = std::make_shared(); + printf("ENDOOM TEXT %d %d\n", (int)text.size(), consider_compress_data("text", text, textz)); + printf("ENDOOM ATTR %d %d\n", (int)attr.size(), consider_compress_data("attr", attr, attrz)); + byte_vector_bit_output combined; + textz->write_to(combined); + attrz->write_to(combined); + endoom.data = combined.get_output(); + wad.update_lump(endoom); + } + + // need to get these in before vpatch renumbering + // insert our network menu items, reusing some of the freeed beep sound ids + for(int i=0;i<(int)count_of(extra_patches);i++) { + lump l = get_free_lump(wad); + l.name = extra_patches[i].name; + l.data.insert(l.data.end(), extra_patches[i].data, extra_patches[i].data + extra_patches[i].len); + wad.update_lump(l); + } + + // do this before patches for now + auto tex_index = convert_textures(wad); + + + for(auto &s : named_lumps) name_required.insert(s); + convert_patches(wad, "p_start", "p_end"); + std::vector vpatch_lookup; + int vp_num=0; + for(const auto &n : vpatch_names) { + int index = wad.get_lump_index(n); + if (index < 0) { + if (!n.empty()) printf("Missing vpatch %s\n", n.c_str()); + index = 0; + } + printf("VPATCH %d %s lump=%d\n", vp_num++, n.c_str(), index); + vpatch_lookup.push_back(index & 0xff); + vpatch_lookup.push_back(index >> 8); + } + lump pstart; + wad.get_lump("p_start", pstart); + touched[pstart.num] = TOUCHED_PATCH_METADATA; + assert(pstart.data.empty()); + pstart.data = vpatch_lookup; + wad.update_lump(pstart); + + convert_sprites(wad); + int s_start = wad.get_lump_index("s_start"); + int s_end = wad.get_lump_index("s_end"); + if (s_start < 0 || s_end < 0) fail("missing s_start/s_end"); + std::vector sprite_metadata; + for (int s = s_start + 1; s < s_end; s++) { + uint32_t metadata = 0; + lump patch; + if (wad.get_lump(s, patch)) { + auto ph = get_field(patch.data, 0); + if (ph.width > 0x3ff) fail("patch width %d too big", ph.width); + if (ph.leftoffset < -0x400 || ph.leftoffset > 0x3ff) + fail("patch left offset %d out of range", ph.leftoffset); + if (ph.topoffset < -0x400 || ph.topoffset > 0x3ff) + fail("patch top offset %d out of range", ph.topoffset); + metadata = ph.width | (ph.leftoffset << 21u) | ((0x7ffu & ph.topoffset) << 10u); + } + append_field(sprite_metadata, metadata); + } + lump s_start_lump; + // hack to insert for now + wad.get_lump("S_START", s_start_lump); + touched[s_start_lump.num] = TOUCHED_SPRITE_METADATA; + s_start_lump.data = sprite_metadata; + wad.update_lump(s_start_lump); + convert_patches(wad, "s_start", "s_end"); + for (const auto &cg : splash_graphics) { + lump l; + int indexp1 = wad.get_lump(cg, l); + if (indexp1) { + convert_patch(wad, indexp1 - 1, l); + } + } + convert_vpatches(wad, run16_misc_vpatches, 16, true); + convert_vpatches(wad, run64_misc_vpatches, 64, true); + convert_vpatches(wad, run256_misc_vpatches, 256, true); + convert_vpatches(wad, alpha_shpal_grey_graphics, 16, false, 0); // share palettes + convert_vpatches(wad, alpha16_shpal_red_vpatches, 16, false, 1); // share palettes + convert_vpatches(wad, alpha16_shpal_white_vpatches, 16, false, 2); // share palettes + // special case player background to see if they are rectangle with broder which we'll ecncode specially, + // not so much as to save space (it does) but we don't quite have enough time to draw the status bar background + // always, and rendering a flat color is quIcker + for(const auto& s : special_player_background_vpatches) { + lump patch; + int indexp1 = wad.get_lump(s, patch); + if (indexp1) { + auto ph = get_field(patch.data, 0); + auto pix = unpack_patch(patch); + std::set colors; + bool match = true; + // each line is encoded as 3 palette indexes; one for first pixel, one for middle pixels and one for last pixels + // this is obviously not crazy efficient, but is easy for clipping etc when rendering, and is more compact than + // the raw data + std::vector lines; + for(int y=0;y>7)); + // todo shrink this some; it is often zero + patch.data.push_back(ph.topoffset); + patch.data.push_back(ph.leftoffset); + patch.data.insert(patch.data.end(), lines.begin(), lines.end()); + wad.update_lump(patch); + } else { + convert_vpatch(wad, patch, 16, false, colors, -1, false); + } + } + } + convert_vpatches(wad, run64_face_vpatches, 64, true); + convert_vpatches(wad, alpha16_status_vpatches, 16, false); + convert_vpatches(wad, run16_menu_vpatches, 16, true); + + convert_flats(wad); + // filter again + for (auto &e : wad.get_lumps()) { + if (sfx_lumpnames.find(to_lower(e.second.name)) != sfx_lumpnames.end()) { + if (!convert_sound(e)) { + printf("Failed to convert sound %s\n", e.second.name.c_str()); + // todo remove? + } + touched[e.first] = TOUCHED_SFX; + } + } + for (auto &e : wad.get_lumps()) { + if (e.second.name.substr(0, 5) == "WIMAP") { + //dump_patch(e.second.name.c_str(), e.first, e.second); + touched[e.first] = TOUCHED_PATCH; + convert_patch(wad, e.first, e.second); + name_required.insert(e.second.name); + } + } + for (auto &e : wad.get_lumps()) { + if (e.second.name.length() > 4 && e.second.name.substr(0, 4) == "BRDR") { + touched[e.first] = TOUCHED_UNUSED_GRAPHIC; + e.second.data.clear(); + } + } + int total_size = 0; + std::vector level_data = { + "THINGS", "LINEDEFS", "SIDEDEFS", "VERTEXES", "SEGS", "SSECTORS", "NODES", "SECTORS", "REJECT", + "BLOCKMAP" + }; + std::vector level_data_struct_size = { + sizeof(mapthing_t), + sizeof(maplinedef_t), + sizeof(mapsidedef_t), + sizeof(mapvertex_t), + sizeof(mapseg_t), + sizeof(mapsubsector_t), + sizeof(mapnode_t), + sizeof(mapsector_t), + 1, + 2 + }; + int level_data_size = 0; + std::vector level_data_orig_sizes; + std::vector level_data_sizes; + std::transform(level_data.begin(), level_data.end(), std::back_inserter(level_data_orig_sizes), + [](auto &name) { return statsomizer(name + " orig size"); }); + std::transform(level_data.begin(), level_data.end(), std::back_inserter(level_data_sizes), + [](auto &name) { return statsomizer(name + " size"); }); + + // little old, but keep for now + for (auto &e : wad.get_lumps()) { + auto it = std::find(level_data.begin(), level_data.end(), e.second.name); + if (it != level_data.end()) { + int which = it - level_data.begin(); + level_data_orig_sizes[which].record(e.second.data.size()); + } + } + + auto convert_level = [&](::wad& wad, std::string name) { + int index = wad.get_lump_index(name); + if (index >= 0) { + printf("Converting level %s\n", name.c_str()); + } else { + return; + } + name_required.insert(name); + touched[index] = TOUCHED_LEVEL; + + lump l; + if (!wad.get_lump(index+ML_THINGS, l) || l.name != "THINGS") { + fail("missing THINGS for %s", name.c_str()); + } + printf("Convert THINGS in lump %d\n", index+ML_THINGS); + touched[index+ML_THINGS] = TOUCHED_LEVEL_THINGS; + // todo + + if (!wad.get_lump(index+ML_SIDEDEFS, l) || l.name != "SIDEDEFS") { + fail("missing SIDEDEFS for %s", name.c_str()); + } + printf("Convert SIDEDEF in lump %d\n", index+ML_SIDEDEFS); + compressed.insert(index+ML_SIDEDEFS); + touched[index+ML_SIDEDEFS] = TOUCHED_LEVEL_SIDEDEFS; + auto sidedef_mapping = convert_sidedefs(wad, tex_index, l); + + if (!wad.get_lump(index+ML_VERTEXES, l) || l.name != "VERTEXES") { + fail("missing VERTEXES for %s", name.c_str()); + } + printf("Convert VERTEXES in lump %d\n", index+ML_VERTEXES); + touched[index+ML_VERTEXES] = TOUCHED_LEVEL_VERTEXES; + auto vertexes = convert_vertexes(wad, l); + + if (!wad.get_lump(index+ML_LINEDEFS, l) || l.name != "LINEDEFS") { + fail("missing LINEDEFS for %s", name.c_str()); + } + printf("Convert LINEDEFS in lump %d\n", index+ML_LINEDEFS); + touched[index+ML_LINEDEFS] = TOUCHED_LEVEL_LINEDEFS; + auto linedef_mapping = convert_linedefs(wad, l, sidedef_mapping, vertexes); + + if (!wad.get_lump(index+ML_SEGS, l) || l.name != "SEGS") { + fail("missing SEGS for %s", name.c_str()); + } + printf("Convert SEGS in lump %d\n", index+ML_SEGS); + touched[index+ML_SEGS] = TOUCHED_LEVEL_SEGS; + auto seg_mapping = convert_segs(wad, l, linedef_mapping); + + if (!wad.get_lump(index+ML_SSECTORS, l) || l.name != "SSECTORS") { + fail("missing SSECTORS for %s", name.c_str()); + } + printf("Convert SSECTORS in lump %d\n", index+ML_SSECTORS); + touched[index+ML_SSECTORS] = TOUCHED_LEVEL_SSECTORS; + convert_subsectors(wad, l, seg_mapping); + + if (!wad.get_lump(index+ML_NODES, l) || l.name != "NODES") { + fail("missing NODES for %s", name.c_str()); + } + printf("Convert NODES in lump %d\n", index+ML_NODES); + compressed.insert(index+ML_NODES); + touched[index+ML_NODES] = TOUCHED_LEVEL_NODES; + convert_nodes(wad, l); + + if (!wad.get_lump(index+ML_SECTORS, l) || l.name != "SECTORS") { + fail("missing SECTORS for %s", name.c_str()); + } + printf("Convert SECTORS in lump %d\n", index+ML_SECTORS); + touched[index+ML_SECTORS] = TOUCHED_LEVEL_SECTORS; + convert_sectors(wad, l); + + if (!wad.get_lump(index+ML_REJECT, l) || l.name != "REJECT") { + fail("missing REJECT for %s", name.c_str()); + } + printf("Convert REJECT in lump %d\n", index+ML_REJECT); + // todo convert_reject(wad, l); + touched[index+ML_REJECT] = TOUCHED_LEVEL_REJECT; + + if (!wad.get_lump(index+ML_BLOCKMAP, l) || l.name != "BLOCKMAP") { + fail("missing BLOCKMAP for %s", name.c_str()); + } + printf("Convert BLOCKMAP in lump %d\n", index+ML_BLOCKMAP); + touched[index+ML_BLOCKMAP] = TOUCHED_LEVEL_BLOCKMAP; + compressed.insert(index+ML_BLOCKMAP); + convert_blockmap(wad, l, linedef_mapping); + }; + for(int e=1; e<=4; e++) { + for(int m=1; m<=9; m++) { + convert_level(wad, "E"+std::to_string(e)+"M"+std::to_string(m)); + } + } + for(int m=1; m<=32; m++) { + char name[10]; + sprintf(name, "MAP%02d", m); + convert_level(wad, name); + } + for (auto &e : wad.get_lumps()) { + auto it = std::find(level_data.begin(), level_data.end(), e.second.name); + if (it != level_data.end()) { + int which = it - level_data.begin(); + level_data_sizes[which].record(e.second.data.size()); + // this doesn't much make sense now +// level_data_counts[which].record(e.second.data.size() / level_data_struct_size[which]); + level_data_size += e.second.data.size(); + } + } + + for (auto &e : wad.get_lumps()) { + if (music_lumpnames.find(to_lower(e.second.name)) != music_lumpnames.end()) { + convert_music(e); + } + total_size += e.second.data.size(); + //printf("%s %08x\n", e.second.name.c_str(), (int)e.second.data.size()); + } + + lump tmp; + if (wad.get_lump("f_sky1", tmp)) { + touched[tmp.num] = TOUCHED_UNUSED_GRAPHIC; + tmp.data.clear(); // we don't need data in here + wad.update_lump(tmp); + } + lump demo; + if (wad.get_lump("demo1", demo)) convert_demo(wad, demo); + if (wad.get_lump("demo2", demo)) convert_demo(wad, demo); + if (wad.get_lump("demo3", demo)) convert_demo(wad, demo); + if (wad.get_lump("demo4", demo)) convert_demo(wad, demo); + int total = 0; + for (const auto &e : wad.get_lumps()) { + if (touched.find(e.first) == touched.end()) { + printf("UNTOUCHED %d %s (%d)\n", e.first, e.second.name.c_str(), (int) e.second.data.size()); + total += e.second.data.size(); + touched[e.first] = TOUCHED_UNUSED; + } + } + printf("UNTOUCHED TOTAL %d\n", total); + total = 0; +// for (const auto &e : wad.get_lumps()) { +// if (compressed.find(e.first) == compressed.end() && e.second.data.size()) { +// printf("UNCOMPRESSED %d %s (%d)\n", e.first, e.second.name.c_str(), (int) e.second.data.size()); +// total += e.second.data.size(); +// } +// } +// printf("UNCOMPRESSED TOTAL %d\n", total); + + patch_widths.print_summary(); + patch_heights.print_summary(); + patch_left_offsets.print_summary(); + patch_top_offsets.print_summary(); + vpatch_left_offsets.print_summary(); + vpatch_top_offsets.print_summary(); + vpatch_left_0offsets.print_summary(); + vpatch_top_0offsets.print_summary(); + patch_sizes.print_summary(); + patch_colors.print_summary(); + patch_column_colors.print_summary(); + for (const auto &s : patch_columns_under_colors) { + s.print_summary(); + } + patch_post_counts.print_summary(); + patch_one.print_summary(); + patch_all_post_counts.print_summary(); + patch_col_hack_huff_pixels.print_summary(); + patch_hack_huff_pixels.print_summary(); + patch_meta_size.print_summary(); + patch_orig_meta_size.print_summary(); + patch_decoder_size.print_summary(); + + texture_column_patches.print_summary(); + texture_column_patches1.print_summary(); + texture_single_patch.print_summary(); + texture_single_patch00.print_summary(); + texture_transparent.print_summary(); + texture_transparent_patch_count.print_summary(); + texture_col_metadata.print_summary(); + printf("MUS %d\n", mus_total1); + printf("MUSX %d\n", mus_total2); + musx_decoder_space.print_summary(); + // todo this should be dynamic and stored in WAD + if (musx_decoder_space.max > MUSX_MAX_DECODER_SPACE) { + fail("MUSX decoder space exceeded (max %d)\n", MUSX_MAX_DECODER_SPACE); + } + int i = 0; + int t=0; + for (const auto &s : level_data_orig_sizes) { + printf("%2d ", (int) level_data_struct_size[i++]); + s.print_summary(); + t += s.total; + } + printf("TOTAL %d %08x\n", t, t); + printf("\n"); + t = 0; + for (const auto &s : level_data_sizes) { + s.print_summary(); + t += s.total; + } + printf("TOTAL %d %08x\n", t, t); + printf("\n"); + subsector_length.print_summary(); + blockmap_empty.print_summary(); + blockmap_one.print_summary(); + blockmap_length.print_summary(); + blockmap_row_size.print_summary(); + block_map_deltas.print_summary(); + block_map_deltas_1byte.print_summary(); + blockmap_sizes.print_summary(); + blockmap_blocks.print_summary(); + + sector_lightlevel.print_summary(); + sector_floorheight.print_summary(); + sector_ceilingheight.print_summary(); + sector_special.print_summary(); + + printf("level data size %08x\n", level_data_size); + printf("total size %08x\n", total_size); + sfx_orig_size.print_summary(); + sfx_new_size.print_summary(); + patch_orig_size.print_summary(); + patch_new_size.print_summary(); + vpatch_orig_size.print_summary(); + vpatch_new_size.print_summary(); + tex_orig_size.print_summary(); + tex_new_size.print_summary(); + + same_columns.print_summary(); + + for(i=0;i<(int)winners.size();i++) { + printf("WIN %d %d\n", i, winners[i]); + } + patch_pixels.print_summary(); + cp1_pixels.print_summary(); + cp1_size.print_summary(); + cp2_size.print_summary(); + cp_wtf_size.print_summary(); + cp_po_size.print_summary(); + cp1_run.print_summary(); + cp1_raw_run.print_summary(); + cp_size.print_summary(); + printf("Bit addressable %d\n", bit_addressable_patch); + printf("Dumped patches %d Converted patches %d Size %d\n", dumped_patch_count, converted_patch_count, converted_patch_size); + printf("Opaque %d Transparent %d total %d\n", opaque_pixels, transparent_pixels, opaque_pixels + transparent_pixels); + flat_rawsize.print_summary(); + flat_c2size.print_summary(); + flat_have_same_savings.print_summary(); + flat_colors.print_summary(); + for (const auto &s : flat_under_colors) { + s.print_summary(); + } + for(i=0;i<(int)fwinners.size();i++) { + printf("FWIN %d %d\n", i, fwinners[i]); + } + + color_runs.print_summary(); + side_meta.print_summary(); + side_metaz.print_summary(); + line_meta.print_summary(); + line_metaz.print_summary(); + line_scale.print_summary(); + ss_delta.print_summary(); + demo_size_orig.print_summary(); + demo_size.print_summary(); + single_patch_metadata_size.print_summary(); + wad.write_whd(output_filename, name_required, hash, super_tiny); + size = 0; + for(const auto &e : wad.get_lumps()) { + size += e.second.data.size(); + } + printf("LUMPS NEW SIZE %d\n", size); + + // just dump some extra stats +#if 1 + printf("WHD -------------\n"); + std::map ltype_size; + std::map lname_to_ltype; + for(const auto &e : touched) { + lump l; + wad.get_lump(e.first, l); + lname_to_ltype[l.name] = e.second; + ltype_size[e.second] += l.data.size(); + } + total=0; + for(const auto &e : ltype_size) { + printf("%s: %d (%dK)\n", e.first.c_str(), e.second, (e.second + 512) / 1024); + total += e.second; + } + printf("TOTAL %d (%dK)\n", total, (total+512)/1024); + + printf("WAD -------------\n"); + ltype_size.clear(); + auto wad2 = wad::read(wad_name); + for(const auto &e : wad2.get_lumps()) { + std::string ltype = lname_to_ltype[e.second.name]; + if (ltype.empty()) ltype = TOUCHED_UNUSED; + ltype_size[ltype] += e.second.data.size(); + } + total=0; + for(const auto &e : ltype_size) { + printf("%s: %d (%dK)\n", e.first.c_str(), e.second, (e.second + 512) / 1024); + total += e.second; + } + printf("TOTAL %d (%dK)\n", total, (total+512)/1024); +#endif + } catch (std::exception &e) { + std::cerr << e.what(); + return -1; + } +} + +int count_codes(vector &items, vector &sizes) { + int s = 0; + for (const auto &e : items) { + s += sizes[e]; + } + return s; +} + +void th_bit_overrun(th_bit_input *bi) { + fail("Bit overrun in decoding"); +} diff --git a/src/whddata.h b/src/whddata.h new file mode 100644 index 00000000..358b220b --- /dev/null +++ b/src/whddata.h @@ -0,0 +1,783 @@ +#ifndef __WHDDATA__ +#define __WHDDATA__ + +#include "doomtype.h" + +#if !USE_WHD +typedef char textureorflatname_def_t[9]; +typedef const char* textureorflatname_t; +#define TEXTURE_NAME(x) __STRING(x) +#define TEXTURE_NAME_NONE "" +#define FLAT_NAME(x) __STRING(x) +#define VPATCH_NAME(x) __STRING(x) +#define VPATCH_NAME_INVALID "" +#define DEH_VPATCH_NAME(x) DEH_String(VPATCH_NAME(x)) +typedef const char *vpatchname_t; +#define DEH_TextureName(n) DEH_String(n) +typedef const char *textureorflatname_t; +#else +typedef int8_t textureorflatname_def_t; +typedef textureorflatname_def_t textureorflatname_t; +#define TEXTURE_NAME(x) __CONCAT(NTEX_, x) +#define TEXTURE_NAME_NONE (-1) +#define FLAT_NAME(x) __CONCAT(NFLAT_, x) +#define VPATCH_NAME(x) __CONCAT(VPATCH_, x) +#define DEH_VPATCH_NAME(x) VPATCH_NAME(x) +typedef uint8_t vpatchname_t; +#define DEH_TextureName(n) (n) +typedef int8_t textureorflatname_t; +#endif +typedef textureorflatname_def_t texturename_def_t; +typedef textureorflatname_t texturename_t; +typedef textureorflatname_t flatname_t; + +typedef struct { + short floorheight; + short ceilingheight; + short floorpic; + short ceilingpic; + short lightlevel; + short special; + short tag; + +// 1.5 int16_t Xrawfloorheight; +// 1.5 int16_t Xrawceilingheight; +// 1 uint8_t Xfloorpic; +// 1 uint8_t Xlightlevel; +// 1 uint8_t Xspecial +// 2 short tag; // immutable; how big? (CAN BE CONST) +// 2 cardinal_t line_index; // within linebuffer of first line (CAN BE CONST) +// 1 cardinal_t linecount; // seems unlikely to be more than 8 bit really (CAN BE CONST) +// 1 uint8_t ceilingpic; // (CAN BE CONST) +// 4 uint8_t blockbox[4]; // (CAN BE CONST) +// 4 xy_positioned_t soundorg; // middle of bbox (CAN BE CONST) + +} whdsector_t; + +//typedef struct { +// int16_t floor_height:12; +// int16_t ceiling_height:12; +// uint8_t floor_pic; +// uint8_t ceiling_pic; +// uint8_t light_level; +// uint8_t special; +// uint8_t line_count; +// uint16_t line_index; +// int16_t tag; +// uint8_t blockbox[4]; +// //xy_positioned_t soundorg; +//} foo; + +typedef struct { + short textureoffset; + short rowoffset; + short toptexture; + short bottomtexture; + short midtexture; + // Front sector, towards viewer. + short sector; +} whdsidedef_t; + +// not sure where this comes in yet +//typedef struct { +// int16_t originx; +// int16_t originy; +// lumpindex_t patch; +//} whdpatch_t; + +typedef struct { + uint8_t width_m1; + uint8_t height; + int8_t leftoffset; + int8_t topoffset; + uint16_t columnofs[1]; +} whdpatch_t; + +typedef struct { + uint16_t width; + uint8_t height; + uint8_t patch_count; +// uint8_t approx_color; // seems like it might be handy + union { + uint16_t patch0; + uint16_t metdata_offset; + }; + // todo do we need flags +} whdtexture_t; + +/* + // DOOM II flat animations. + FLATANIM_DEF(SLIME04, SLIME01, 8), + FLATANIM_DEF(SLIME08, SLIME05, 8), + FLATANIM_DEF(SLIME12, SLIME09, 8), + */ + +enum vpatch_type { + vp4_solid, + vp4_runs, + vp4_alpha, + vp6_runs, + vp8_runs, + vp_border, + vp4_solid_clipped, // note these are in same order as above + vp4_runs_clipped, + vp4_alpha_clipped, + vp6_runs_clipped, + vp8_runs_clipped, + vp_border_clipped, +}; + +#define VPATCH_LIST \ + VPATCH_NAME_INVALID, \ + VPATCH_NAME(AMMNUM0), \ + VPATCH_NAME(AMMNUM1), \ + VPATCH_NAME(AMMNUM2), \ + VPATCH_NAME(AMMNUM3), \ + VPATCH_NAME(AMMNUM4), \ + VPATCH_NAME(AMMNUM5), \ + VPATCH_NAME(AMMNUM6), \ + VPATCH_NAME(AMMNUM7), \ + VPATCH_NAME(AMMNUM8), \ + VPATCH_NAME(AMMNUM9), \ + VPATCH_NAME(M_LOADG), \ + VPATCH_NAME(M_LSLEFT), \ + VPATCH_NAME(M_LSCNTR), \ + VPATCH_NAME(M_LSRGHT), \ + VPATCH_NAME(M_SVOL), \ + VPATCH_NAME(M_SKILL), \ + VPATCH_NAME(M_DOOM), \ + VPATCH_NAME(M_NEWG), \ + VPATCH_NAME(M_THERML), \ + VPATCH_NAME(M_THERMM), \ + VPATCH_NAME(M_THERMO), \ + VPATCH_NAME(M_THERMR), \ + VPATCH_NAME(M_EPISOD),\ + VPATCH_NAME(M_OPTTTL),\ + VPATCH_NAME(M_MSGON), \ + VPATCH_NAME(M_MSGOFF), \ + VPATCH_NAME(M_NGAME), \ + VPATCH_NAME(M_OPTION), \ + VPATCH_NAME(M_SAVEG), \ + VPATCH_NAME(M_RDTHIS), \ + VPATCH_NAME(M_QUITG), \ + VPATCH_NAME(M_EPI1), \ + VPATCH_NAME(M_EPI2), \ + VPATCH_NAME(M_EPI3), \ + VPATCH_NAME(M_EPI4), \ + VPATCH_NAME(M_JKILL), \ + VPATCH_NAME(M_ROUGH), \ + VPATCH_NAME(M_HURT), \ + VPATCH_NAME(M_ULTRA), \ + VPATCH_NAME(M_NMARE), \ + VPATCH_NAME(M_ENDGAM), \ + VPATCH_NAME(M_MESSG), \ + VPATCH_NAME(M_DETAIL), \ + VPATCH_NAME(M_SCRNSZ), \ + VPATCH_NAME(M_MSENS), \ + VPATCH_NAME(M_SFXVOL), \ + VPATCH_NAME(M_MUSVOL), \ + VPATCH_NAME(M_SKULL1), \ + VPATCH_NAME(M_SKULL2), \ + VPATCH_NAME(M_PAUSE), \ + VPATCH_NAME(STTMINUS), \ + VPATCH_NAME(STFST00), \ + VPATCH_NAME(STFST01), \ + VPATCH_NAME(STFST02), \ + VPATCH_NAME(STFTR00), \ + VPATCH_NAME(STFTL00), \ + VPATCH_NAME(STFOUCH0), \ + VPATCH_NAME(STFEVL0), \ + VPATCH_NAME(STFKILL0), \ + VPATCH_NAME(STFST10), \ + VPATCH_NAME(STFST11), \ + VPATCH_NAME(STFST12), \ + VPATCH_NAME(STFTR10), \ + VPATCH_NAME(STFTL10), \ + VPATCH_NAME(STFOUCH1), \ + VPATCH_NAME(STFEVL1), \ + VPATCH_NAME(STFKILL1), \ + VPATCH_NAME(STFST20), \ + VPATCH_NAME(STFST21), \ + VPATCH_NAME(STFST22), \ + VPATCH_NAME(STFTR20), \ + VPATCH_NAME(STFTL20), \ + VPATCH_NAME(STFOUCH2), \ + VPATCH_NAME(STFEVL2), \ + VPATCH_NAME(STFKILL2), \ + VPATCH_NAME(STFST30), \ + VPATCH_NAME(STFST31), \ + VPATCH_NAME(STFST32), \ + VPATCH_NAME(STFTR30), \ + VPATCH_NAME(STFTL30), \ + VPATCH_NAME(STFOUCH3), \ + VPATCH_NAME(STFEVL3), \ + VPATCH_NAME(STFKILL3), \ + VPATCH_NAME(STFST40), \ + VPATCH_NAME(STFST41), \ + VPATCH_NAME(STFST42), \ + VPATCH_NAME(STFTR40), \ + VPATCH_NAME(STFTL40), \ + VPATCH_NAME(STFOUCH4), \ + VPATCH_NAME(STFEVL4), \ + VPATCH_NAME(STFKILL4),\ + VPATCH_NAME(STFGOD0),\ + VPATCH_NAME(STFDEAD0),\ + VPATCH_NAME(STFB0), \ + VPATCH_NAME(STFB1), \ + VPATCH_NAME(STFB2), \ + VPATCH_NAME(STFB3), \ + VPATCH_NAME(STTNUM0), \ + VPATCH_NAME(STTNUM1), \ + VPATCH_NAME(STTNUM2), \ + VPATCH_NAME(STTNUM3), \ + VPATCH_NAME(STTNUM4), \ + VPATCH_NAME(STTNUM5), \ + VPATCH_NAME(STTNUM6), \ + VPATCH_NAME(STTNUM7), \ + VPATCH_NAME(STTNUM8), \ + VPATCH_NAME(STTNUM9), \ + VPATCH_NAME(STTPRCNT), \ + VPATCH_NAME(STYSNUM0), \ + VPATCH_NAME(STYSNUM1), \ + VPATCH_NAME(STYSNUM2), \ + VPATCH_NAME(STYSNUM3), \ + VPATCH_NAME(STYSNUM4), \ + VPATCH_NAME(STYSNUM5), \ + VPATCH_NAME(STYSNUM6), \ + VPATCH_NAME(STYSNUM7), \ + VPATCH_NAME(STYSNUM8), \ + VPATCH_NAME(STYSNUM9), \ + VPATCH_NAME(STGNUM0), \ + VPATCH_NAME(STGNUM1), \ + VPATCH_NAME(STGNUM2), \ + VPATCH_NAME(STGNUM3), \ + VPATCH_NAME(STGNUM4), \ + VPATCH_NAME(STGNUM5), \ + VPATCH_NAME(STGNUM6), \ + VPATCH_NAME(STGNUM7), \ + VPATCH_NAME(STGNUM8), \ + VPATCH_NAME(STGNUM9), \ + VPATCH_NAME(STKEYS0), \ + VPATCH_NAME(STKEYS1), \ + VPATCH_NAME(STKEYS2), \ + VPATCH_NAME(STKEYS3), \ + VPATCH_NAME(STKEYS4), \ + VPATCH_NAME(STKEYS5), \ + VPATCH_NAME(STARMS), \ + VPATCH_NAME(STBAR), \ + VPATCH_NAME(WINUM0), \ + VPATCH_NAME(WINUM1), \ + VPATCH_NAME(WINUM2), \ + VPATCH_NAME(WINUM3), \ + VPATCH_NAME(WINUM4), \ + VPATCH_NAME(WINUM5), \ + VPATCH_NAME(WINUM6), \ + VPATCH_NAME(WINUM7), \ + VPATCH_NAME(WINUM8), \ + VPATCH_NAME(WINUM9), \ + VPATCH_NAME(WIMINUS), \ + VPATCH_NAME(WIPCNT), \ + VPATCH_NAME(WIF), \ + VPATCH_NAME(WIENTER), \ + VPATCH_NAME(WIOSTK), \ + VPATCH_NAME(WIOSTS), \ + VPATCH_NAME(WISCRT2), \ + VPATCH_NAME(WIOSTI), \ + VPATCH_NAME(WIFRGS), \ + VPATCH_NAME(WICOLON), \ + VPATCH_NAME(WITIME), \ + VPATCH_NAME(WISUCKS), \ + VPATCH_NAME(WIPAR), \ + VPATCH_NAME(WIKILRS), \ + VPATCH_NAME(WIVCTMS), \ + VPATCH_NAME(WIMSTT), \ + VPATCH_NAME(WIURH0), \ + VPATCH_NAME(WIURH1), \ + VPATCH_NAME(WISPLAT), \ + VPATCH_NAME(WIBP0), \ + VPATCH_NAME(WIBP1), \ + VPATCH_NAME(WIBP2), \ + VPATCH_NAME(WIBP3), \ + VPATCH_NAME(STPB0), \ + VPATCH_NAME(STPB1), \ + VPATCH_NAME(STPB2), \ + VPATCH_NAME(STPB3), \ + VPATCH_NAME(M_GAME), \ + VPATCH_NAME(M_HOST), \ + VPATCH_NAME(M_JOIN), \ + VPATCH_NAME(M_NAME), \ + VPATCH_NAME(M_NETWK), \ + VPATCH_NAME(M_DTHMCH), \ + VPATCH_NAME(M_TWO), \ + VPATCH_NAME(STCFN033), \ + VPATCH_NAME(STCFN034), \ + VPATCH_NAME(STCFN035), \ + VPATCH_NAME(STCFN036), \ + VPATCH_NAME(STCFN037), \ + VPATCH_NAME(STCFN038), \ + VPATCH_NAME(STCFN039), \ + VPATCH_NAME(STCFN040), \ + VPATCH_NAME(STCFN041), \ + VPATCH_NAME(STCFN042), \ + VPATCH_NAME(STCFN043), \ + VPATCH_NAME(STCFN044), \ + VPATCH_NAME(STCFN045), \ + VPATCH_NAME(STCFN046), \ + VPATCH_NAME(STCFN047), \ + VPATCH_NAME(STCFN048), \ + VPATCH_NAME(STCFN049), \ + VPATCH_NAME(STCFN050), \ + VPATCH_NAME(STCFN051), \ + VPATCH_NAME(STCFN052), \ + VPATCH_NAME(STCFN053), \ + VPATCH_NAME(STCFN054), \ + VPATCH_NAME(STCFN055), \ + VPATCH_NAME(STCFN056), \ + VPATCH_NAME(STCFN057), \ + VPATCH_NAME(STCFN058), \ + VPATCH_NAME(STCFN059), \ + VPATCH_NAME(STCFN060), \ + VPATCH_NAME(STCFN061), \ + VPATCH_NAME(STCFN062), \ + VPATCH_NAME(STCFN063), \ + VPATCH_NAME(STCFN064), \ + VPATCH_NAME(STCFN065), \ + VPATCH_NAME(STCFN066), \ + VPATCH_NAME(STCFN067), \ + VPATCH_NAME(STCFN068), \ + VPATCH_NAME(STCFN069), \ + VPATCH_NAME(STCFN070), \ + VPATCH_NAME(STCFN071), \ + VPATCH_NAME(STCFN072), \ + VPATCH_NAME(STCFN073), \ + VPATCH_NAME(STCFN074), \ + VPATCH_NAME(STCFN075), \ + VPATCH_NAME(STCFN076), \ + VPATCH_NAME(STCFN077), \ + VPATCH_NAME(STCFN078), \ + VPATCH_NAME(STCFN079), \ + VPATCH_NAME(STCFN080), \ + VPATCH_NAME(STCFN081), \ + VPATCH_NAME(STCFN082), \ + VPATCH_NAME(STCFN083), \ + VPATCH_NAME(STCFN084), \ + VPATCH_NAME(STCFN085), \ + VPATCH_NAME(STCFN086), \ + VPATCH_NAME(STCFN087), \ + VPATCH_NAME(STCFN088), \ + VPATCH_NAME(STCFN089), \ + VPATCH_NAME(STCFN090), \ + VPATCH_NAME(STCFN091), \ + VPATCH_NAME(STCFN092), \ + VPATCH_NAME(STCFN093), \ + VPATCH_NAME(STCFN094), \ + VPATCH_NAME(STCFN095), \ + /* WILV, CWILV, WIA are all vpatch_sequence_t which is a 16 bit handle, so these can go at index > 256 */ \ + VPATCH_NAME(WILV00), \ + VPATCH_NAME(WILV01), \ + VPATCH_NAME(WILV02), \ + VPATCH_NAME(WILV03), \ + VPATCH_NAME(WILV04), \ + VPATCH_NAME(WILV05), \ + VPATCH_NAME(WILV06), \ + VPATCH_NAME(WILV07), \ + VPATCH_NAME(WILV08), \ + VPATCH_NAME(WILV10), \ + VPATCH_NAME(WILV11), \ + VPATCH_NAME(WILV12), \ + VPATCH_NAME(WILV13), \ + VPATCH_NAME(WILV14), \ + VPATCH_NAME(WILV15), \ + VPATCH_NAME(WILV16), \ + VPATCH_NAME(WILV17), \ + VPATCH_NAME(WILV18), \ + VPATCH_NAME(WILV20), \ + VPATCH_NAME(WILV21), \ + VPATCH_NAME(WILV22), \ + VPATCH_NAME(WILV23), \ + VPATCH_NAME(WILV24), \ + VPATCH_NAME(WILV25), \ + VPATCH_NAME(WILV26), \ + VPATCH_NAME(WILV27), \ + VPATCH_NAME(WILV28), \ + VPATCH_NAME(WILV30), \ + VPATCH_NAME(WILV31), \ + VPATCH_NAME(WILV32), \ + VPATCH_NAME(WILV33), \ + VPATCH_NAME(WILV34), \ + VPATCH_NAME(WILV35), \ + VPATCH_NAME(WILV36), \ + VPATCH_NAME(WILV37), \ + VPATCH_NAME(WILV38), \ + VPATCH_NAME(CWILV00), \ + VPATCH_NAME(CWILV01), \ + VPATCH_NAME(CWILV02), \ + VPATCH_NAME(CWILV03), \ + VPATCH_NAME(CWILV04), \ + VPATCH_NAME(CWILV05), \ + VPATCH_NAME(CWILV06), \ + VPATCH_NAME(CWILV07), \ + VPATCH_NAME(CWILV08), \ + VPATCH_NAME(CWILV09), \ + VPATCH_NAME(CWILV10), \ + VPATCH_NAME(CWILV11), \ + VPATCH_NAME(CWILV12), \ + VPATCH_NAME(CWILV13), \ + VPATCH_NAME(CWILV14), \ + VPATCH_NAME(CWILV15), \ + VPATCH_NAME(CWILV16), \ + VPATCH_NAME(CWILV17), \ + VPATCH_NAME(CWILV18), \ + VPATCH_NAME(CWILV19), \ + VPATCH_NAME(CWILV20), \ + VPATCH_NAME(CWILV21), \ + VPATCH_NAME(CWILV22), \ + VPATCH_NAME(CWILV23), \ + VPATCH_NAME(CWILV24), \ + VPATCH_NAME(CWILV25), \ + VPATCH_NAME(CWILV26), \ + VPATCH_NAME(CWILV27), \ + VPATCH_NAME(CWILV28), \ + VPATCH_NAME(CWILV29), \ + VPATCH_NAME(CWILV30), \ + VPATCH_NAME(CWILV31), \ + VPATCH_NAME(CWILV32), \ + VPATCH_NAME(CWILV33), \ + VPATCH_NAME(CWILV34), \ + VPATCH_NAME(CWILV35), \ + VPATCH_NAME(CWILV36), \ + VPATCH_NAME(CWILV37), \ + VPATCH_NAME(CWILV38), \ + VPATCH_NAME(WIA00000), \ + VPATCH_NAME(WIA00001), \ + VPATCH_NAME(WIA00002), \ + VPATCH_NAME(WIA00100), \ + VPATCH_NAME(WIA00101), \ + VPATCH_NAME(WIA00102), \ + VPATCH_NAME(WIA00200), \ + VPATCH_NAME(WIA00201), \ + VPATCH_NAME(WIA00202), \ + VPATCH_NAME(WIA00300), \ + VPATCH_NAME(WIA00301), \ + VPATCH_NAME(WIA00302), \ + VPATCH_NAME(WIA00400), \ + VPATCH_NAME(WIA00401), \ + VPATCH_NAME(WIA00402), \ + VPATCH_NAME(WIA00500), \ + VPATCH_NAME(WIA00501), \ + VPATCH_NAME(WIA00502), \ + VPATCH_NAME(WIA00600), \ + VPATCH_NAME(WIA00601), \ + VPATCH_NAME(WIA00602), \ + VPATCH_NAME(WIA00700), \ + VPATCH_NAME(WIA00701), \ + VPATCH_NAME(WIA00702), \ + VPATCH_NAME(WIA00800), \ + VPATCH_NAME(WIA00801), \ + VPATCH_NAME(WIA00802), \ + VPATCH_NAME(WIA00900), \ + VPATCH_NAME(WIA00901), \ + VPATCH_NAME(WIA00902), \ + VPATCH_NAME(WIA10000), \ + VPATCH_NAME(WIA10100), \ + VPATCH_NAME(WIA10200), \ + VPATCH_NAME(WIA10300), \ + VPATCH_NAME(WIA10400), \ + VPATCH_NAME(WIA10500), \ + VPATCH_NAME(WIA10600), \ + VPATCH_NAME(WIA10700), \ + VPATCH_NAME(WIA10701), \ + VPATCH_NAME(WIA10702), \ + VPATCH_NAME(WIA20000), \ + VPATCH_NAME(WIA20001), \ + VPATCH_NAME(WIA20002), \ + VPATCH_NAME(WIA20100), \ + VPATCH_NAME(WIA20101), \ + VPATCH_NAME(WIA20102), \ + VPATCH_NAME(WIA20200), \ + VPATCH_NAME(WIA20201), \ + VPATCH_NAME(WIA20202), \ + VPATCH_NAME(WIA20300), \ + VPATCH_NAME(WIA20301), \ + VPATCH_NAME(WIA20302), \ + VPATCH_NAME(WIA20400), \ + VPATCH_NAME(WIA20401), \ + VPATCH_NAME(WIA20402), \ + VPATCH_NAME(WIA20500), \ + VPATCH_NAME(WIA20501), \ + VPATCH_NAME(WIA20502),\ + VPATCH_NAME(END0), \ + VPATCH_NAME(END1), \ + VPATCH_NAME(END2), \ + VPATCH_NAME(END3), \ + VPATCH_NAME(END4), \ + VPATCH_NAME(END5), \ + VPATCH_NAME(END6) + +// note we must include intermediate flats even if not directly references (since we deal with consecutive ranges) +#define NAMED_FLAT_LIST \ + FLAT_NAME(NUKAGE1), \ + FLAT_NAME(NUKAGE2), \ + FLAT_NAME(NUKAGE3), \ + FLAT_NAME(FWATER1), \ + FLAT_NAME(FWATER2), \ + FLAT_NAME(FWATER3), \ + FLAT_NAME(FWATER4), \ + FLAT_NAME(SWATER1), \ + FLAT_NAME(SWATER4), \ + FLAT_NAME(LAVA1), \ + FLAT_NAME(LAVA2), \ + FLAT_NAME(LAVA3), \ + FLAT_NAME(LAVA4), \ + FLAT_NAME(BLOOD1), \ + FLAT_NAME(BLOOD2), \ + FLAT_NAME(BLOOD3), \ + FLAT_NAME(RROCK05), \ + FLAT_NAME(RROCK06), \ + FLAT_NAME(RROCK07), \ + FLAT_NAME(RROCK08), \ + FLAT_NAME(SLIME01), \ + FLAT_NAME(SLIME02), \ + FLAT_NAME(SLIME03), \ + FLAT_NAME(SLIME04), \ + FLAT_NAME(SLIME05), \ + FLAT_NAME(SLIME06), \ + FLAT_NAME(SLIME07), \ + FLAT_NAME(SLIME08), \ + FLAT_NAME(SLIME09), \ + FLAT_NAME(SLIME10), \ + FLAT_NAME(SLIME11), \ + FLAT_NAME(SLIME12), \ + FLAT_NAME(F_SKY1) + +// These are all the textures referenced by the game code (it won't compile otherwise because of TEXTURE_NAME) +// +// these are also therefore all the texture names that are used in animations, so we have the WHD use these offsets directly for texture numbers, so +// we can keep some shorter arrays for dealing with mutable info for changed textures +#define NAMED_TEXTURE_LIST \ + TEXTURE_NAME(_), /* 0 is not a valid texture number */ \ + TEXTURE_NAME(SKY4), /* this is not a switch texture, but oh well, here it keeps the evenness ok */ \ + /* switches start on an even number */ \ + TEXTURE_NAME(SW1BRCOM), \ + TEXTURE_NAME(SW2BRCOM),\ + \ + TEXTURE_NAME(SW1BRN1), \ + TEXTURE_NAME(SW2BRN1), \ + \ + TEXTURE_NAME(SW1BRN2), \ + TEXTURE_NAME(SW2BRN2), \ + \ + TEXTURE_NAME(SW1BRNGN), \ + TEXTURE_NAME(SW2BRNGN), \ + \ + TEXTURE_NAME(SW1BROWN), \ + TEXTURE_NAME(SW2BROWN), \ + \ + TEXTURE_NAME(SW1COMM), \ + TEXTURE_NAME(SW2COMM), \ + \ + TEXTURE_NAME(SW1COMP), \ + TEXTURE_NAME(SW2COMP), \ + \ + TEXTURE_NAME(SW1DIRT), \ + TEXTURE_NAME(SW2DIRT), \ + \ + TEXTURE_NAME(SW1EXIT), \ + TEXTURE_NAME(SW2EXIT), \ + \ + TEXTURE_NAME(SW1GRAY), \ + TEXTURE_NAME(SW2GRAY), \ + \ + TEXTURE_NAME(SW1GRAY1), \ + TEXTURE_NAME(SW2GRAY1), \ + \ + TEXTURE_NAME(SW1METAL), \ + TEXTURE_NAME(SW2METAL), \ + \ + TEXTURE_NAME(SW1PIPE), \ + TEXTURE_NAME(SW2PIPE), \ + \ + TEXTURE_NAME(SW1SLAD), \ + TEXTURE_NAME(SW2SLAD), \ + \ + TEXTURE_NAME(SW1STON2), \ + TEXTURE_NAME(SW2STON2), \ + \ + TEXTURE_NAME(SW1STARG), \ + TEXTURE_NAME(SW2STARG), \ + \ + TEXTURE_NAME(SW1STON1), \ + TEXTURE_NAME(SW2STON1), \ + \ + TEXTURE_NAME(SW1GARG), \ + TEXTURE_NAME(SW2GARG), \ + \ + TEXTURE_NAME(SW1HOT), \ + TEXTURE_NAME(SW2HOT), \ + \ + TEXTURE_NAME(SW1BLUE), \ + TEXTURE_NAME(SW2BLUE), \ + \ + TEXTURE_NAME(SW1SATYR), \ + TEXTURE_NAME(SW2SATYR), \ + \ + TEXTURE_NAME(SW1SKIN), \ + TEXTURE_NAME(SW2SKIN), \ + \ + TEXTURE_NAME(SW1VINE), \ + TEXTURE_NAME(SW2VINE), \ + \ + TEXTURE_NAME(SW1WOOD), \ + TEXTURE_NAME(SW2WOOD), \ + \ + TEXTURE_NAME(SW1PANEL), \ + TEXTURE_NAME(SW2PANEL), \ + \ + TEXTURE_NAME(SW1ROCK), \ + TEXTURE_NAME(SW2ROCK), \ + \ + TEXTURE_NAME(SW1MET2), \ + TEXTURE_NAME(SW2MET2), \ + \ + TEXTURE_NAME(SW1WDMET), \ + TEXTURE_NAME(SW2WDMET), \ + \ + TEXTURE_NAME(SW1BRIK), \ + TEXTURE_NAME(SW2BRIK), \ + \ + TEXTURE_NAME(SW1MOD1), \ + TEXTURE_NAME(SW2MOD1), \ + \ + TEXTURE_NAME(SW1ZIM), \ + TEXTURE_NAME(SW2ZIM), \ + \ + TEXTURE_NAME(SW1STON6), \ + TEXTURE_NAME(SW2STON6), \ + \ + TEXTURE_NAME(SW1TEK), \ + TEXTURE_NAME(SW2TEK), \ + \ + TEXTURE_NAME(SW1MARB), \ + TEXTURE_NAME(SW2MARB), \ + \ + TEXTURE_NAME(SW1SKULL), \ + TEXTURE_NAME(SW2SKULL), \ + \ + TEXTURE_NAME(SW1STONE), \ + TEXTURE_NAME(SW2STONE), \ + \ + TEXTURE_NAME(SW1STRTN), \ + TEXTURE_NAME(SW2STRTN), \ + \ + TEXTURE_NAME(SW1CMT), \ + TEXTURE_NAME(SW2CMT), \ + \ + TEXTURE_NAME(SW1GSTON), \ + TEXTURE_NAME(SW2GSTON), \ + \ + TEXTURE_NAME(SW1LION), \ + TEXTURE_NAME(SW2LION), \ + /* end of switch textures - note SW2LION is used as a constant */ \ + TEXTURE_NAME(BLODGR1), \ + TEXTURE_NAME(BLODGR2), \ + TEXTURE_NAME(BLODGR3), \ + TEXTURE_NAME(BLODGR4), \ + TEXTURE_NAME(SLADRIP1), \ + TEXTURE_NAME(SLADRIP2), \ + TEXTURE_NAME(SLADRIP3), \ + TEXTURE_NAME(BLODRIP1), \ + TEXTURE_NAME(BLODRIP2), \ + TEXTURE_NAME(BLODRIP3), \ + TEXTURE_NAME(BLODRIP4), \ + TEXTURE_NAME(FIREWALA), \ + TEXTURE_NAME(FIREWALB), \ + TEXTURE_NAME(FIREWALL), \ + TEXTURE_NAME(GSTFONT1), \ + TEXTURE_NAME(GSTFONT2), \ + TEXTURE_NAME(GSTFONT3), \ + TEXTURE_NAME(FIRELAV3), \ + /* TEXTURE_NAME(FIRELAV2), */ \ + TEXTURE_NAME(FIRELAVA), \ + TEXTURE_NAME(FIREMAG1), \ + TEXTURE_NAME(FIREMAG2), \ + TEXTURE_NAME(FIREMAG3), \ + TEXTURE_NAME(FIREBLU1), \ + TEXTURE_NAME(FIREBLU2), \ + TEXTURE_NAME(ROCKRED1), \ + TEXTURE_NAME(ROCKRED2), \ + TEXTURE_NAME(ROCKRED3), \ + TEXTURE_NAME(BFALL1), \ + TEXTURE_NAME(BFALL2), \ + TEXTURE_NAME(BFALL3), \ + TEXTURE_NAME(BFALL4), \ + TEXTURE_NAME(SFALL1), \ + TEXTURE_NAME(SFALL2), \ + TEXTURE_NAME(SFALL3), \ + TEXTURE_NAME(SFALL4), \ + TEXTURE_NAME(WFALL1), \ + TEXTURE_NAME(WFALL2), \ + TEXTURE_NAME(WFALL3), \ + TEXTURE_NAME(WFALL4), \ + TEXTURE_NAME(DBRAIN1), \ + TEXTURE_NAME(DBRAIN2), \ + TEXTURE_NAME(DBRAIN3), \ + TEXTURE_NAME(DBRAIN4), \ + TEXTURE_NAME(SKY1), \ + TEXTURE_NAME(SKY2), \ + TEXTURE_NAME(SKY3) + +#if USE_WHD +#define LAST_SWITCH_TEXTURE NTEX_SW2LION +enum { + NAMED_TEXTURE_LIST, + NUM_SPECIAL_TEXTURES +}; +enum { + NAMED_FLAT_LIST, + NUM_SPECIAL_FLATS +}; +enum { + VPATCH_LIST, + NUM_VPATCHES, +}; +#include +static_assert(NUM_VPATCHES < 512, ""); +#endif + +typedef PACKED_STRUCT({ + // Partition line from (x,y) to x+dx,y+dy) + short x; + short y; + short dx; + short dy; + //uint16_t children[2]; + uint16_t coded_children; + + // Bounding box for each child, + // clip against view frustum. + uint8_t bbox_lw[2]; + uint8_t bbox_th[2]; +}) whdnode_t; +#include +static_assert(sizeof(whdnode_t)==14, ""); + +typedef PACKED_STRUCT({ + uint32_t size; + uint32_t hash; + char name[14]; + uint16_t num_named_lumps; +}) whdheader_t; +static_assert(sizeof(whdheader_t)==24, ""); +extern const whdheader_t *whdheader; + +#define WHD_MAX_COL_SEGS 8 // todo may be smaller +#define WHD_MAX_COL_UNIQUE_PATCHES 4 // 4 * 128 = 512 which is how big we like to keep the decoder_tmp in pd_render_nh (when used for decoding) + +#define WHD_COL_SEG_MEMCPY 0x80 // segment is a memcpy +#define WHD_COL_SEG_EXPLICIT_Y 0x40 // segment has an explcit start y rather than starting from the bottom of the previous seg +#define WHD_COL_SEG_MEMCPY_SOURCE 0x20 // segment is targeted by a memcpy (which means at runtime we cannot skip rendering it just because it is out side the visible y range) +#define WHD_COL_SEG_MEMCPY_IS_BACKWARDS 0x10 // direction for memcpy is explicit to get the right effect for overlapping (sometimes we want to interfere, sometimes not) + +#define WHD_PATCH_MAX_WIDTH 257 +#define WHD_FLAT_DECODER_MAX_SIZE 512 +#endif \ No newline at end of file diff --git a/src/z_zone.c b/src/z_zone.c index c5c2dbf7..9765c0c4 100644 --- a/src/z_zone.c +++ b/src/z_zone.c @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -23,7 +24,17 @@ #include "m_argv.h" #include "z_zone.h" +#include "deh_str.h" +#include +// todo graham rework to use shortptr during alloc/free etc. + +// todo we have perfectly good dumping +//#define USE_MEM_USE_TRACKING + +#ifdef USE_MEM_USE_TRACKING +int32_t mem_used; +#endif // // ZONE MEMORY ALLOCATION @@ -41,14 +52,50 @@ typedef struct memblock_s { + shortptr_t /*struct memblock_s*/ sp_next; + shortptr_t /*struct memblock_s**/ sp_prev; +#if !DOOM_TINY int size; // including the header and possibly tiny fragments +#else + uint16_t size4; +#endif +#if !NO_Z_MALLOC_USER_PTR void** user; - int tag; // PU_FREE if this is free +#endif + uint8_t tag; // PU_FREE if this is free +#if !NO_Z_ZONE_ID int id; // should be ZONEID - struct memblock_s* next; - struct memblock_s* prev; +#endif +#if Z_MALOOC_EXTRA_DATA + uint8_t extra; +#endif } memblock_t; +#if Z_MALOOC_EXTRA_DATA +uint8_t *Z_ObjectExtra(void *ptr) { + memblock_t *block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); + return &block->extra; +} +#endif + +#define memblock_next(mb) ((memblock_t *)shortptr_to_ptr((mb)->sp_next)) +#define memblock_prev(mb) ((memblock_t *)shortptr_to_ptr((mb)->sp_prev)) +#if !DOOM_TINY +#define memblock_size(mb) ((const int)(mb)->size) +#define set_memblock_size(mb, sz) (mb)->size=sz; +#else +#define memblock_size(mb) (((mb)->size4)<<2) +static inline void set_memblock_size(memblock_t *mb, int size) { + assert(size < 65536*4); + mb->size4 = (size + 3) / 4; +} +#if PICO_ON_DEVICE +static_assert(sizeof(memblock_t) == 8, ""); +#endif +#endif +static inline shortptr_t memblock_to_shortptr(memblock_t *mb) { + return ptr_to_shortptr(mb); +} typedef struct { @@ -65,34 +112,13 @@ typedef struct static memzone_t *mainzone; +#if !NO_ZONE_DEBUG static boolean zero_on_free; static boolean scan_on_free; - - -// -// Z_ClearZone -// -void Z_ClearZone (memzone_t* zone) -{ - memblock_t* block; - - // set the entire zone to one free block - zone->blocklist.next = - zone->blocklist.prev = - block = (memblock_t *)( (byte *)zone + sizeof(memzone_t) ); - - zone->blocklist.user = (void *)zone; - zone->blocklist.tag = PU_STATIC; - zone->rover = block; - - block->prev = block->next = &zone->blocklist; - - // a free block. - block->tag = PU_FREE; - - block->size = zone->size - sizeof(memzone_t); -} - +#else +#define zero_on_free false +#define scan_on_free false +#endif // @@ -106,22 +132,26 @@ void Z_Init (void) mainzone = (memzone_t *)I_ZoneBase (&size); mainzone->size = size; + block = (memblock_t *)( (byte *)mainzone + sizeof(memzone_t) ); // set the entire zone to one free block - mainzone->blocklist.next = - mainzone->blocklist.prev = - block = (memblock_t *)( (byte *)mainzone + sizeof(memzone_t) ); + mainzone->blocklist.sp_next = + mainzone->blocklist.sp_prev = memblock_to_shortptr(block); + +#if !NO_Z_MALLOC_USER_PTR mainzone->blocklist.user = (void *)mainzone; +#endif mainzone->blocklist.tag = PU_STATIC; mainzone->rover = block; - block->prev = block->next = &mainzone->blocklist; + block->sp_prev = block->sp_next = memblock_to_shortptr(&mainzone->blocklist); // free block block->tag = PU_FREE; - block->size = mainzone->size - sizeof(memzone_t); + set_memblock_size(block, mainzone->size - sizeof(memzone_t)); +#if !NO_ZONE_DEBUG // [Deliberately undocumented] // Zone memory debugging flag. If set, memory is zeroed after it is freed // to deliberately break any code that attempts to use it after free. @@ -133,6 +163,7 @@ void Z_Init (void) // heap is scanned to look for remaining pointers to the freed block. // scan_on_free = M_ParmExists("-zonescan"); +#endif } // Scan the zone heap for pointers within the specified range, and warn about @@ -143,9 +174,9 @@ static void ScanForBlock(void *start, void *end) void **mem; int i, len, tag; - block = mainzone->blocklist.next; + block = memblock_next(&mainzone->blocklist); - while (block->next != &mainzone->blocklist) + while (memblock_next(block) != &mainzone->blocklist) { tag = block->tag; @@ -154,13 +185,13 @@ static void ScanForBlock(void *start, void *end) // Scan for pointers on the assumption that pointers are aligned // on word boundaries (word size depending on pointer size): mem = (void **) ((byte *) block + sizeof(memblock_t)); - len = (block->size - sizeof(memblock_t)) / sizeof(void *); + len = (memblock_size(block) - sizeof(memblock_t)) / sizeof(void *); for (i = 0; i < len; ++i) { if (start <= mem[i] && mem[i] <= end) { - fprintf(stderr, + stderr_print( "%p has dangling pointer into freed block " "%p (%p -> %p)\n", mem, start, &mem[i], mem[i]); @@ -168,7 +199,7 @@ static void ScanForBlock(void *start, void *end) } } - block = block->next; + block = memblock_next(block); } } @@ -182,40 +213,52 @@ void Z_Free (void* ptr) block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); +#if !NO_Z_ZONE_ID if (block->id != ZONEID) I_Error ("Z_Free: freed a pointer without ZONEID"); +#endif +#if !NO_Z_MALLOC_USER_PTR if (block->tag != PU_FREE && block->user != NULL) { // clear the user's mark *block->user = 0; } +#endif + +#ifdef USE_MEM_USE_TRACKING + mem_used -= memblock_size(block) + sizeof(memblock_t); +#endif // mark as free block->tag = PU_FREE; +#if !NO_Z_MALLOC_USER_PTR block->user = NULL; +#endif +#if !NO_Z_ZONE_ID block->id = 0; +#endif // If the -zonezero flag is provided, we zero out the block on free // to break code that depends on reading freed memory. if (zero_on_free) { - memset(ptr, 0, block->size - sizeof(memblock_t)); + memset(ptr, 0, memblock_size(block) - sizeof(memblock_t)); } if (scan_on_free) { ScanForBlock(ptr, - (byte *) ptr + block->size - sizeof(memblock_t)); + (byte *) ptr + memblock_size(block) - sizeof(memblock_t)); } - other = block->prev; + other = memblock_prev(block); if (other->tag == PU_FREE) { // merge with previous free block - other->size += block->size; - other->next = block->next; - other->next->prev = other; + set_memblock_size(other, memblock_size(other) + memblock_size(block)); + other->sp_next = block->sp_next; + memblock_next(other)->sp_prev = memblock_to_shortptr(other); if (block == mainzone->rover) mainzone->rover = other; @@ -223,13 +266,13 @@ void Z_Free (void* ptr) block = other; } - other = block->next; + other = memblock_next(block); if (other->tag == PU_FREE) { // merge the next free block onto the end - block->size += other->size; - block->next = other->next; - block->next->prev = block; + set_memblock_size(block, memblock_size(other) + memblock_size(block)); + block->sp_next = other->sp_next; + memblock_next(block)->sp_prev = memblock_to_shortptr(block); if (other == mainzone->rover) mainzone->rover = block; @@ -244,12 +287,18 @@ void Z_Free (void* ptr) // #define MINFRAGMENT 64 - +#if !NO_Z_MALLOC_USER_PTR void* Z_Malloc ( int size, int tag, void* user ) +#else +void* +Z_MallocNoUser +( int size, + int tag ) +#endif { int extra; memblock_t* start; @@ -272,18 +321,22 @@ Z_Malloc // back up over them base = mainzone->rover; - if (base->prev->tag == PU_FREE) - base = base->prev; + if (memblock_prev(base)->tag == PU_FREE) + base = memblock_prev(base); rover = base; - start = base->prev; + start = memblock_prev(base); do { if (rover == start) { // scanned all the way around the list +#if DOOM_TINY + panic("out of memory"); +#else I_Error ("Z_Malloc: failed on allocation of %i bytes", size); +#endif } if (rover->tag != PU_FREE) @@ -292,69 +345,93 @@ Z_Malloc { // hit a block that can't be purged, // so move base past it - base = rover = rover->next; + base = rover = memblock_next(rover); } else { // free the rover block (adding the size to base) // the rover can be the base block - base = base->prev; + base = memblock_prev(base); Z_Free ((byte *)rover+sizeof(memblock_t)); - base = base->next; - rover = base->next; + base = memblock_next(base); + rover = memblock_next(base); } } else { - rover = rover->next; + rover = memblock_next(rover); } - } while (base->tag != PU_FREE || base->size < size); + } while (base->tag != PU_FREE || memblock_size(base) < size); // found a block big enough - extra = base->size - size; + extra = memblock_size(base) - size; if (extra > MINFRAGMENT) { // there will be a free fragment after the allocated block newblock = (memblock_t *) ((byte *)base + size ); - newblock->size = extra; + set_memblock_size(newblock, extra); newblock->tag = PU_FREE; - newblock->user = NULL; - newblock->prev = base; - newblock->next = base->next; - newblock->next->prev = newblock; +#if !NO_Z_MALLOC_USER_PTR + newblock->user = NULL; +#endif + newblock->sp_prev = memblock_to_shortptr(base); + newblock->sp_next = base->sp_next; + memblock_next(newblock)->sp_prev = memblock_to_shortptr(newblock); - base->next = newblock; - base->size = size; + base->sp_next = memblock_to_shortptr(newblock); + set_memblock_size(base, size); } - + +#if !NO_Z_MALLOC_USER_PTR if (user == NULL && tag >= PU_PURGELEVEL) I_Error ("Z_Malloc: an owner is required for purgable blocks"); base->user = user; +#else + assert( tag < PU_PURGELEVEL); +#endif base->tag = tag; result = (void *) ((byte *)base + sizeof(memblock_t)); +#if !NO_Z_MALLOC_USER_PTR if (base->user) { *base->user = result; } +#endif // next allocation will start looking here - mainzone->rover = base->next; - + mainzone->rover = memblock_next(base); + +#if !NO_Z_ZONE_ID base->id = ZONEID; - +#endif + +#ifdef USE_MEM_USE_TRACKING + mem_used += memblock_size(base) + sizeof(memblock_t); + static int8_t pants; + if (0 == (0xf & pants++)) + printf("Mem used %d\n", (int)mem_used); +#endif +#if DOOM_SMALL + assert(size - sizeof(memblock_t) >= 4); + *(uint32_t *)result = 0; // if this is a thinker_t we have zeroed out the +#if !PICO_ON_DEVICE + if (size - sizeof(memblock_t) >= 12) { + ((uint32_t *)result)[1] = 0; // if this is a thinker_t we have zeroed out the pool_info field + ((uint32_t *)result)[2] = 0; // if this is a thinker_t we have zeroed out the pool_info field + } +#endif +#endif return result; } - - // // Z_FreeTags // @@ -366,12 +443,12 @@ Z_FreeTags memblock_t* block; memblock_t* next; - for (block = mainzone->blocklist.next ; + for (block = memblock_next(&mainzone->blocklist) ; block != &mainzone->blocklist ; block = next) { // get link before freeing - next = block->next; + next = memblock_next(block); // free block? if (block->tag == PU_FREE) @@ -401,30 +478,35 @@ Z_DumpHeap printf ("tag range: %i to %i\n", lowtag, hightag); - for (block = mainzone->blocklist.next ; ; block = block->next) + for (block = memblock_next(&mainzone->blocklist) ; ; block = memblock_next(block)) { if (block->tag >= lowtag && block->tag <= hightag) - printf ("block:%p size:%7i user:%p tag:%3i\n", - block, block->size, block->user, block->tag); +#if !NO_Z_MALLOC_USER_PTR + printf ("block:%p size:%7i user:%p tag:%3i\n", + block, memblock_size(block), block->user, block->tag); +#else + printf ("block:%p size:%7i tag:%3i\n", + block, memblock_size(block), block->tag); +#endif - if (block->next == &mainzone->blocklist) + if (memblock_next(block) == &mainzone->blocklist) { // all blocks have been hit break; } - if ( (byte *)block + block->size != (byte *)block->next) + if ( (byte *)block + memblock_size(block) != (byte *)memblock_next(block)) printf ("ERROR: block size does not touch the next block\n"); - if ( block->next->prev != block) + if ( memblock_prev(memblock_next(block))!= block) printf ("ERROR: next block doesn't have proper back link\n"); - if (block->tag == PU_FREE && block->next->tag == PU_FREE) + if (block->tag == PU_FREE && memblock_next(block)->tag == PU_FREE) printf ("ERROR: two consecutive free blocks\n"); } } - +#if !NO_FILE_ACCESS // // Z_FileDumpHeap // @@ -434,28 +516,33 @@ void Z_FileDumpHeap (FILE* f) fprintf (f,"zone size: %i location: %p\n",mainzone->size,mainzone); - for (block = mainzone->blocklist.next ; ; block = block->next) + for (block = memblock_next(&mainzone->blocklist) ; ; block = memblock_next(block)) { +#if !NO_Z_MALLOC_USER_PTR fprintf (f,"block:%p size:%7i user:%p tag:%3i\n", - block, block->size, block->user, block->tag); + block, memblock_size(block), block->user, block->tag); +#else + fprintf (f,"block:%p size:%7i tag:%3i\n", + block, memblock_size(block), block->tag); +#endif - if (block->next == &mainzone->blocklist) + if (memblock_next(block) == &mainzone->blocklist) { // all blocks have been hit break; } - if ( (byte *)block + block->size != (byte *)block->next) + if ( (byte *)block + memblock_size(block) != (byte *)memblock_next(block)) fprintf (f,"ERROR: block size does not touch the next block\n"); - if ( block->next->prev != block) + if ( memblock_prev(memblock_next(block)) != block) fprintf (f,"ERROR: next block doesn't have proper back link\n"); - if (block->tag == PU_FREE && block->next->tag == PU_FREE) + if (block->tag == PU_FREE && memblock_next(block)->tag == PU_FREE) fprintf (f,"ERROR: two consecutive free blocks\n"); } } - +#endif // @@ -463,25 +550,27 @@ void Z_FileDumpHeap (FILE* f) // void Z_CheckHeap (void) { +#if !NO_ZONE_DEBUG memblock_t* block; - for (block = mainzone->blocklist.next ; ; block = block->next) + for (block = memblock_next(&mainzone->blocklist) ; ; block = memblock_next(block)) { - if (block->next == &mainzone->blocklist) + if (memblock_next(block) == &mainzone->blocklist) { // all blocks have been hit break; } - if ( (byte *)block + block->size != (byte *)block->next) + if ( (byte *)block + memblock_size(block) != (byte *)memblock_next(block)) I_Error ("Z_CheckHeap: block size does not touch the next block\n"); - if ( block->next->prev != block) + if ( memblock_prev(memblock_next(block)) != block) I_Error ("Z_CheckHeap: next block doesn't have proper back link\n"); - if (block->tag == PU_FREE && block->next->tag == PU_FREE) + if (block->tag == PU_FREE && memblock_next(block)->tag == PU_FREE) I_Error ("Z_CheckHeap: two consecutive free blocks\n"); } +#endif } @@ -496,32 +585,39 @@ void Z_ChangeTag2(void *ptr, int tag, const char *file, int line) block = (memblock_t *) ((byte *)ptr - sizeof(memblock_t)); +#if !NO_Z_ZONE_ID if (block->id != ZONEID) I_Error("%s:%i: Z_ChangeTag: block without a ZONEID!", file, line); +#endif +#if !NO_Z_MALLOC_USER_PTR if (tag >= PU_PURGELEVEL && block->user == NULL) I_Error("%s:%i: Z_ChangeTag: an owner is required " "for purgable blocks", file, line); +#endif block->tag = tag; } +#if !NO_Z_MALLOC_USER_PTR void Z_ChangeUser(void *ptr, void **user) { memblock_t* block; block = (memblock_t *) ((byte *)ptr - sizeof(memblock_t)); +#if !NO_Z_ZONE_ID if (block->id != ZONEID) { I_Error("Z_ChangeUser: Tried to change user for invalid block!"); } +#endif block->user = user; *user = ptr; } - +#endif // @@ -533,13 +629,13 @@ int Z_FreeMemory (void) int free; free = 0; - - for (block = mainzone->blocklist.next ; + + for (block = memblock_next(&mainzone->blocklist) ; block != &mainzone->blocklist; - block = block->next) + block = memblock_next(block)) { if (block->tag == PU_FREE || block->tag >= PU_PURGELEVEL) - free += block->size; + free += memblock_size(block); } return free; @@ -549,4 +645,3 @@ unsigned int Z_ZoneSize(void) { return mainzone->size; } - diff --git a/src/z_zone.h b/src/z_zone.h index 22bd6264..175d7f04 100644 --- a/src/z_zone.h +++ b/src/z_zone.h @@ -1,6 +1,7 @@ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2021-2022 Graham Sanderson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -51,7 +52,13 @@ enum void Z_Init (void); +#if !NO_Z_MALLOC_USER_PTR void* Z_Malloc (int size, int tag, void *ptr); +#else +#include +void* Z_MallocNoUser (int size, int tag); +#define Z_Malloc(s, t, p) ({ static_assert(!(p), ""); Z_MallocNoUser(s,t); }) +#endif void Z_Free (void *ptr); void Z_FreeTags (int lowtag, int hightag); void Z_DumpHeap (int lowtag, int hightag); @@ -62,6 +69,9 @@ void Z_ChangeUser(void *ptr, void **user); int Z_FreeMemory (void); unsigned int Z_ZoneSize(void); +#if Z_MALOOC_EXTRA_DATA +unsigned char *Z_ObjectExtra(void *ptr); +#endif // // This is used to get the local FILE:LINE info from CPP // prior to really call the function in question. diff --git a/textscreen/CMakeLists.txt b/textscreen/CMakeLists.txt index 2a1802a6..113244d9 100644 --- a/textscreen/CMakeLists.txt +++ b/textscreen/CMakeLists.txt @@ -1,28 +1,32 @@ -add_library(textscreen - textscreen.h - txt_conditional.c txt_conditional.h - txt_checkbox.c txt_checkbox.h - txt_desktop.c txt_desktop.h - txt_dropdown.c txt_dropdown.h - txt_fileselect.c txt_fileselect.h - txt_gui.c txt_gui.h - txt_inputbox.c txt_inputbox.h - txt_io.c txt_io.h - txt_main.h - txt_button.c txt_button.h - txt_label.c txt_label.h - txt_radiobutton.c txt_radiobutton.h - txt_scrollpane.c txt_scrollpane.h - txt_separator.c txt_separator.h - txt_spinctrl.c txt_spinctrl.h - txt_sdl.c txt_sdl.h - txt_strut.c txt_strut.h - txt_table.c txt_table.h - txt_utf8.c txt_utf8.h - txt_widget.c txt_widget.h - txt_window.c txt_window.h - txt_window_action.c txt_window_action.h) -target_include_directories(textscreen - INTERFACE "." - PRIVATE "../src/") -target_link_libraries(textscreen m SDL2::SDL2) +if (PICO_SDK) + add_library(textscreen INTERFACE) +else() + add_library(textscreen + textscreen.h + txt_conditional.c txt_conditional.h + txt_checkbox.c txt_checkbox.h + txt_desktop.c txt_desktop.h + txt_dropdown.c txt_dropdown.h + txt_fileselect.c txt_fileselect.h + txt_gui.c txt_gui.h + txt_inputbox.c txt_inputbox.h + txt_io.c txt_io.h + txt_main.h + txt_button.c txt_button.h + txt_label.c txt_label.h + txt_radiobutton.c txt_radiobutton.h + txt_scrollpane.c txt_scrollpane.h + txt_separator.c txt_separator.h + txt_spinctrl.c txt_spinctrl.h + txt_sdl.c txt_sdl.h + txt_strut.c txt_strut.h + txt_table.c txt_table.h + txt_utf8.c txt_utf8.h + txt_widget.c txt_widget.h + txt_window.c txt_window.h + txt_window_action.c txt_window_action.h) + target_include_directories(textscreen + INTERFACE "." + PRIVATE "../src/") + target_link_libraries(textscreen m SDL2::SDL2) +endif() diff --git a/textscreen/fonts/normal.h b/textscreen/fonts/normal.h index a98584fb..e15c0495 100644 --- a/textscreen/fonts/normal.h +++ b/textscreen/fonts/normal.h @@ -2,6 +2,7 @@ Please see textscreen/fonts/README for copyright information. */ +#if !DOOM_TINY static const uint8_t normal_font_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -517,8 +518,96 @@ static const uint8_t normal_font_data[] = 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; - static const txt_font_t normal_font = { "normal", normal_font_data, 8, 16, }; + +#else + +static const uint8_t normal_font_data_z[1983] = { + 0x82, 0x87, 0x69, 0xb1, 0xe2, 0x88, 0x25, 0xb6, 0xb8, 0x4c, 0x0b, 0xaf, 0x79, 0x2f, 0x51, 0x55, 0xb9, 0x58, 0xac, 0x3b, 0xf2, 0x96, 0x15, 0x4f, + 0x35, 0x32, 0x85, 0x8b, 0x20, 0xf2, 0xe2, 0x62, 0xab, 0x12, 0x29, 0x76, 0xdd, 0xe4, 0xc5, 0x91, 0xc9, 0xbd, 0x23, 0x71, 0x6a, 0xab, 0xe9, 0xc2, + 0x08, 0xd0, 0x04, 0x82, 0x4b, 0x22, 0x10, 0x51, 0xe5, 0xa5, 0x31, 0x85, 0x4b, 0x35, 0x5e, 0xef, 0xba, 0x02, 0x37, 0x86, 0x29, 0xce, 0x0f, 0x5c, + 0x23, 0xa6, 0xba, 0x00, 0x00, 0x38, 0xff, 0xf4, 0x1f, 0x3f, 0xfd, 0xf4, 0x97, 0x9f, 0x7f, 0xfa, 0xe9, 0x0c, 0x9c, 0xf7, 0xfb, 0x7d, 0xbf, 0x7c, + 0xde, 0xf7, 0x33, 0xc0, 0xbc, 0x6d, 0xdb, 0xb6, 0x1e, 0x1f, 0x03, 0x3c, 0x3e, 0xae, 0xdb, 0x7a, 0x7c, 0x0c, 0x90, 0x65, 0xf9, 0xfc, 0xf9, 0x73, + 0xb2, 0x00, 0xb2, 0x9c, 0xf7, 0xfd, 0x9c, 0x2c, 0x00, 0x64, 0x59, 0x02, 0xec, 0xfb, 0xbe, 0xef, 0xfb, 0xe7, 0xcb, 0xe5, 0xf3, 0xbe, 0xef, 0xfb, + 0xbe, 0x83, 0xa5, 0xfe, 0xe9, 0x4f, 0x75, 0x01, 0xfb, 0xbe, 0xef, 0xfb, 0xe5, 0xe7, 0xbf, 0xfc, 0xe5, 0xe7, 0xcb, 0xbe, 0xef, 0xfb, 0xce, 0xdb, + 0x37, 0xff, 0xf8, 0x0f, 0xd7, 0xd3, 0x34, 0x4d, 0xd7, 0xc0, 0x52, 0x6b, 0xad, 0x4b, 0xce, 0x09, 0xf0, 0xfb, 0x7f, 0xfd, 0x3e, 0x0c, 0xc3, 0xf0, + 0xe4, 0xf0, 0x08, 0xf8, 0xdb, 0x2f, 0x7f, 0xfb, 0xe5, 0x97, 0x5f, 0x7e, 0xf9, 0xbf, 0xcf, 0xaf, 0x3a, 0x20, 0xb9, 0x5f, 0x3e, 0x2f, 0xf7, 0x09, + 0xf8, 0xa6, 0x7b, 0x74, 0xb8, 0xd9, 0x6e, 0x0e, 0x8f, 0xba, 0x6f, 0xc0, 0x77, 0xa7, 0x37, 0x6f, 0x7f, 0xdc, 0x7e, 0x7c, 0xfb, 0xe6, 0xf4, 0x1d, + 0x90, 0xe5, 0x9c, 0xe4, 0xbc, 0x04, 0x50, 0x6b, 0xad, 0xb5, 0x56, 0xb5, 0x02, 0x7f, 0xbb, 0xbf, 0xbf, 0xff, 0xdf, 0x4f, 0x9f, 0x3e, 0x7d, 0xfa, + 0x04, 0xd6, 0xd2, 0x1f, 0xe7, 0x52, 0xe6, 0xe3, 0x58, 0x56, 0x00, 0x6c, 0xdb, 0xb6, 0x01, 0x59, 0xce, 0x49, 0xce, 0x4b, 0xce, 0x40, 0x96, 0x73, + 0x92, 0x24, 0x01, 0x92, 0x24, 0xc9, 0x79, 0x09, 0x80, 0x8c, 0xdb, 0x18, 0x00, 0x0c, 0xfd, 0xd6, 0x0f, 0x00, 0xd0, 0x75, 0xdd, 0x06, 0x80, 0x3f, + 0xd4, 0xbd, 0xfe, 0x01, 0x80, 0xc7, 0xc7, 0xe3, 0xba, 0x6e, 0x1b, 0x80, 0x6d, 0x5b, 0xd7, 0xe3, 0xf1, 0x31, 0x00, 0x00, 0x20, 0xcb, 0xb2, 0x24, + 0x91, 0x80, 0x5a, 0xeb, 0x1f, 0x00, 0x80, 0x79, 0xde, 0xe6, 0x79, 0xde, 0xe6, 0x19, 0x92, 0xb5, 0xbc, 0xe8, 0xd6, 0xd3, 0xe9, 0xcf, 0x65, 0x4d, + 0x80, 0x17, 0x65, 0xcc, 0xd0, 0x97, 0x3f, 0x03, 0xc7, 0x79, 0x3e, 0xde, 0xdd, 0x4e, 0xd3, 0x74, 0x07, 0x86, 0x61, 0xe8, 0x01, 0xc0, 0x98, 0x61, + 0x18, 0x86, 0x61, 0xc8, 0x08, 0x0c, 0x19, 0xc7, 0x71, 0x1c, 0xc7, 0x0c, 0x00, 0xea, 0xb2, 0x2f, 0x15, 0x00, 0xc9, 0x39, 0x01, 0x00, 0x24, 0x19, + 0x00, 0xd8, 0x00, 0x00, 0x48, 0x00, 0xbe, 0x3b, 0x8d, 0x19, 0xfa, 0xee, 0x1b, 0x60, 0xa9, 0x97, 0xcb, 0xfd, 0xfd, 0xe5, 0x52, 0x17, 0x20, 0xc7, + 0xeb, 0x24, 0x49, 0xce, 0xc0, 0x5a, 0x4e, 0x63, 0x86, 0xbe, 0x2b, 0x1b, 0xb0, 0x96, 0xd3, 0x69, 0x39, 0x9d, 0x4e, 0x65, 0x05, 0xc6, 0x67, 0xcb, + 0x3c, 0x6d, 0xe3, 0x38, 0xbe, 0x05, 0xb6, 0xae, 0xeb, 0x9e, 0x9f, 0x4e, 0xa7, 0xb2, 0x02, 0xc7, 0xbe, 0xeb, 0x9e, 0x97, 0x52, 0xca, 0x0a, 0x6c, + 0xe5, 0x74, 0x1a, 0x33, 0x0c, 0xc3, 0x00, 0xac, 0xa5, 0x94, 0xb5, 0x94, 0x52, 0x56, 0x60, 0x2d, 0xa5, 0x9c, 0x4f, 0xa7, 0xd3, 0x78, 0x0d, 0x90, + 0x20, 0x01, 0x90, 0x20, 0x19, 0x00, 0xa7, 0x31, 0x43, 0x3f, 0x64, 0x3c, 0x01, 0x38, 0x73, 0x06, 0xa0, 0x1f, 0x32, 0x9e, 0xc6, 0x0c, 0x3d, 0xb0, + 0x96, 0x32, 0x26, 0x91, 0x00, 0xd6, 0x52, 0xde, 0xbd, 0x7b, 0x77, 0xdb, 0xad, 0xc0, 0xe3, 0xe3, 0x5c, 0xca, 0x56, 0x4a, 0x29, 0xc0, 0xf3, 0x5a, + 0xeb, 0x5a, 0x6b, 0xad, 0xcf, 0x81, 0xa5, 0xbe, 0xe8, 0xba, 0xae, 0x7b, 0x51, 0x17, 0xe0, 0x66, 0xae, 0xb5, 0xd6, 0x5a, 0xe7, 0x1b, 0x60, 0xab, + 0xdf, 0x7f, 0x7b, 0xfd, 0x6d, 0xff, 0x7d, 0xdd, 0x80, 0xad, 0x7e, 0xff, 0xed, 0xf5, 0xb7, 0x7d, 0xdf, 0x1f, 0x80, 0xa5, 0xbe, 0xe8, 0xba, 0x77, + 0xa5, 0xd4, 0x7f, 0x02, 0x4a, 0x29, 0x65, 0x2b, 0xa5, 0x94, 0x02, 0x2c, 0x49, 0x92, 0x24, 0x0b, 0xf0, 0x76, 0x1c, 0xc7, 0x71, 0x9c, 0xa6, 0xe9, + 0x1a, 0x78, 0x55, 0xeb, 0x7c, 0x7d, 0x3d, 0xd7, 0xfa, 0x0a, 0x38, 0xf4, 0x7d, 0xdf, 0xf7, 0xfd, 0xf7, 0x75, 0x03, 0x2e, 0x9f, 0xf7, 0xfd, 0xfe, + 0x72, 0xb9, 0x5c, 0x2e, 0x40, 0x79, 0xf5, 0x7a, 0x7b, 0xf7, 0x43, 0x29, 0xa5, 0x00, 0x6b, 0x29, 0xa5, 0x94, 0x52, 0xca, 0x0a, 0x3c, 0xaf, 0xb5, + 0xae, 0x7d, 0xdf, 0xf7, 0x07, 0x60, 0x2d, 0xa5, 0x94, 0x52, 0xfe, 0xe5, 0xdd, 0x3a, 0xbe, 0x81, 0xe7, 0xb5, 0xd6, 0x75, 0xae, 0xb5, 0xbe, 0x02, + 0xd6, 0x52, 0xfa, 0xe3, 0x78, 0x2a, 0x65, 0x05, 0xf6, 0xfb, 0x9f, 0x93, 0x24, 0x59, 0x80, 0x52, 0x4a, 0x29, 0xa5, 0x94, 0xb2, 0x02, 0x97, 0xcb, + 0xe5, 0x72, 0xb9, 0x5c, 0xea, 0x12, 0xe0, 0x72, 0xb9, 0x5c, 0x2e, 0xf7, 0xf7, 0x7b, 0xad, 0xc0, 0xe5, 0x52, 0x97, 0x64, 0xa9, 0x97, 0x0b, 0x70, + 0xb9, 0x5c, 0xea, 0x92, 0x24, 0x0b, 0xb0, 0x5f, 0xfe, 0x3c, 0x66, 0xe8, 0xff, 0xfd, 0xb2, 0x03, 0xcb, 0x30, 0x0c, 0xc3, 0x30, 0x0c, 0xc3, 0x02, + 0xf8, 0xa6, 0x7b, 0xf4, 0xe4, 0xf8, 0xec, 0xcd, 0xe9, 0x3b, 0x60, 0x19, 0xc7, 0x71, 0x1c, 0xc7, 0x71, 0x5c, 0xe0, 0xf1, 0x71, 0x2e, 0x00, 0x00, + 0x00, 0x76, 0x86, 0x21, 0x00, 0x00, 0x5c, 0x8f, 0xeb, 0x34, 0x4d, 0x77, 0xc0, 0xa3, 0xbe, 0xbf, 0x9e, 0x6b, 0xad, 0x75, 0x05, 0xb0, 0x96, 0xae, + 0xeb, 0xca, 0x0a, 0x3c, 0x1b, 0xc7, 0x65, 0x9e, 0xa6, 0x69, 0xba, 0x03, 0xb0, 0x96, 0xad, 0xeb, 0xca, 0x0a, 0x1c, 0xe7, 0x3f, 0xf6, 0x87, 0xbe, + 0xef, 0xfb, 0x03, 0x80, 0xbb, 0x69, 0x9a, 0xa6, 0x69, 0x1d, 0xa7, 0x6b, 0x3c, 0xea, 0xfb, 0xf9, 0xae, 0xd6, 0x5a, 0x5f, 0x01, 0x89, 0x63, 0x92, + 0x64, 0x01, 0x4e, 0x27, 0x6f, 0x4e, 0xa7, 0xd3, 0xe9, 0x74, 0xaa, 0x75, 0xc1, 0xa3, 0xbe, 0xaf, 0xf3, 0xf5, 0xf5, 0x5c, 0x5f, 0x01, 0xc7, 0x24, + 0x49, 0x92, 0x05, 0xc0, 0xab, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0x1e, 0xc0, 0x6d, 0xad, 0xb5, 0xd6, 0x0a, 0x60, 0x2d, 0xa5, 0x94, 0xb2, 0x02, 0xb8, + 0xad, 0xb5, 0xd6, 0xba, 0xf6, 0xfd, 0x01, 0xb8, 0x9b, 0xa6, 0x69, 0x9a, 0xd6, 0x71, 0x7c, 0x0b, 0xdc, 0xde, 0xd5, 0xbe, 0xef, 0x0f, 0x00, 0xd6, + 0xd2, 0x1f, 0xc7, 0xb2, 0x02, 0x8f, 0x87, 0xe1, 0xf9, 0x30, 0x0c, 0x43, 0x7b, 0x06, 0x60, 0x9a, 0xa6, 0x69, 0x9a, 0xee, 0x00, 0x5c, 0x2e, 0x97, + 0x4b, 0x5d, 0x02, 0xe0, 0x72, 0xb9, 0xdc, 0xdf, 0xef, 0x15, 0xc0, 0xa5, 0x2e, 0x59, 0xea, 0x05, 0x40, 0x29, 0xa5, 0x94, 0x72, 0x3e, 0x8d, 0x37, + 0xc0, 0x36, 0x65, 0xe8, 0xcb, 0x06, 0xbc, 0x49, 0xf2, 0x24, 0x49, 0xde, 0x00, 0x49, 0x22, 0x49, 0x02, 0x3c, 0x49, 0xf2, 0x26, 0x49, 0x9e, 0x00, + 0x77, 0xb7, 0x00, 0x00, 0x8f, 0x8f, 0x73, 0x29, 0x65, 0x03, 0x2c, 0xf5, 0x45, 0xd7, 0x75, 0x2f, 0xea, 0x32, 0x9e, 0x56, 0x98, 0x98, 0xa6, 0x69, + 0x9a, 0xa6, 0x3b, 0x30, 0x66, 0xb0, 0x96, 0xad, 0xeb, 0xca, 0x0a, 0x1e, 0x1f, 0x67, 0xd7, 0xe3, 0x3a, 0x4d, 0xd3, 0x1d, 0x30, 0x71, 0x3d, 0xae, + 0xd3, 0x34, 0xdd, 0x81, 0x7e, 0x88, 0xeb, 0x71, 0x9d, 0xa6, 0xe9, 0x0e, 0x1c, 0xe7, 0xa3, 0xeb, 0x71, 0x9d, 0xa6, 0xe9, 0x0e, 0x60, 0xa9, 0x7d, + 0x5f, 0x97, 0xf1, 0xb4, 0xc0, 0xe3, 0xe3, 0x6c, 0x2d, 0x5b, 0xd7, 0x95, 0x15, 0x28, 0xac, 0x65, 0xeb, 0xba, 0xb2, 0x82, 0x7e, 0x88, 0xb5, 0x6c, + 0x5d, 0x57, 0x56, 0xa0, 0x72, 0x4c, 0x92, 0x2c, 0x20, 0x4b, 0x75, 0x4c, 0x92, 0x2c, 0xa0, 0x1f, 0xe2, 0x98, 0x24, 0x59, 0x40, 0xf1, 0xf8, 0x38, + 0x97, 0xb2, 0x95, 0x52, 0xe0, 0x38, 0x1f, 0x1d, 0xe7, 0x52, 0xb6, 0x52, 0x0a, 0x64, 0xe8, 0x6d, 0xb5, 0x5f, 0xfb, 0xbe, 0x6e, 0x00, 0xfe, 0xf5, + 0x7f, 0x3e, 0x9d, 0xaf, 0x6e, 0xbf, 0x00, 0x3f, 0xce, 0xd3, 0xb4, 0x4d, 0xd3, 0x34, 0xfd, 0x00, 0x1e, 0x1f, 0x67, 0x6b, 0x29, 0xa5, 0x94, 0x15, + 0x28, 0xac, 0xa5, 0x94, 0x52, 0x56, 0xd0, 0x0f, 0xb1, 0x96, 0x52, 0x4a, 0x59, 0xc1, 0x70, 0x3d, 0x99, 0xa6, 0x69, 0x9a, 0xa6, 0x3b, 0xd0, 0x0f, + 0x31, 0x4d, 0xd3, 0x34, 0x4d, 0x77, 0x40, 0xa1, 0x94, 0x52, 0x4a, 0x39, 0x9f, 0xc6, 0x6b, 0x8a, 0xb5, 0x94, 0x52, 0x4a, 0x29, 0x2b, 0x28, 0x4a, + 0x29, 0xa5, 0x94, 0x52, 0x56, 0x90, 0x9c, 0x2f, 0x5d, 0xd7, 0x5d, 0xce, 0x09, 0x38, 0xce, 0x7f, 0xec, 0x0f, 0x7d, 0xdf, 0xf7, 0xaf, 0x9e, 0x03, + 0x97, 0xba, 0x64, 0xcf, 0x9e, 0x04, 0x3c, 0xaf, 0x75, 0xfd, 0xbe, 0xfe, 0x7f, 0xad, 0xf5, 0xaf, 0xe0, 0xcd, 0xa7, 0x24, 0xe7, 0x24, 0xc9, 0xd5, + 0x13, 0x64, 0xe8, 0x5d, 0x8f, 0xeb, 0x34, 0x4d, 0x77, 0x60, 0xcc, 0xe0, 0x98, 0x24, 0x59, 0x40, 0x86, 0xde, 0x5a, 0x4a, 0x29, 0x65, 0x05, 0x19, + 0x7a, 0xd3, 0x34, 0x4d, 0xd3, 0x74, 0x07, 0xdc, 0xdd, 0xba, 0xad, 0xb5, 0xd6, 0x5a, 0xe1, 0xee, 0x56, 0x79, 0xf5, 0x7a, 0x7b, 0xf7, 0x43, 0x29, + 0x05, 0x2c, 0xf3, 0xfc, 0xa3, 0x33, 0x00, 0xc7, 0x79, 0x3e, 0x5a, 0x01, 0x30, 0x0c, 0x86, 0xa1, 0xef, 0x4a, 0x59, 0x01, 0xd8, 0xba, 0xae, 0xeb, + 0x00, 0xb0, 0x9d, 0x4e, 0xa7, 0x13, 0xd0, 0x75, 0x2f, 0xca, 0x94, 0xa1, 0xff, 0xe1, 0xbf, 0x4f, 0xe3, 0x57, 0x74, 0xdd, 0x8b, 0x32, 0x65, 0xa8, + 0x3f, 0xfc, 0xf3, 0x8f, 0xa7, 0x13, 0x24, 0x92, 0x2c, 0xcb, 0x12, 0x00, 0x6d, 0xbe, 0x9a, 0x1b, 0x00, 0xae, 0xe6, 0x36, 0x5f, 0x01, 0xef, 0x9f, + 0xbe, 0x7f, 0xfa, 0xfe, 0xe9, 0xfb, 0xa7, 0xef, 0x9f, 0xbe, 0x7f, 0xfa, 0xfe, 0xe9, 0xfb, 0xa7, 0x1f, 0x5e, 0x7e, 0x78, 0xf9, 0xe1, 0xe5, 0x87, + 0x97, 0x1f, 0x5e, 0x7e, 0x78, 0xf9, 0xe1, 0xe5, 0x87, 0x97, 0x1f, 0xbf, 0x7c, 0xfc, 0xf2, 0xf1, 0xcb, 0xc7, 0x2f, 0x1f, 0xbf, 0x7c, 0xfc, 0xf2, + 0xf1, 0xcb, 0xc7, 0x2f, 0x49, 0x92, 0x24, 0x49, 0x92, 0x24, 0x49, 0x92, 0xe4, 0x26, 0x49, 0x92, 0x24, 0x49, 0x72, 0x93, 0x9b, 0x24, 0x49, 0x92, + 0xd6, 0x5a, 0x6b, 0xad, 0xbd, 0x6e, 0xad, 0xb5, 0xd6, 0x5a, 0x03, 0x6c, 0xad, 0xb5, 0xd6, 0x5a, 0x6b, 0xe0, 0x26, 0x37, 0x49, 0x92, 0x24, 0xad, + 0xb5, 0xd6, 0x5e, 0x9f, 0x5e, 0xb7, 0xd6, 0x5a, 0x6b, 0xad, 0xb5, 0xd6, 0x5a, 0x6b, 0xad, 0xb5, 0xd6, 0x5a, 0x6b, 0xad, 0x81, 0xed, 0xf4, 0xba, + 0xb5, 0xd6, 0x5a, 0x6b, 0xad, 0xb5, 0xd6, 0xda, 0xeb, 0xd3, 0x06, 0xd0, 0x5a, 0x6b, 0xad, 0xb5, 0x0d, 0x20, 0x49, 0x72, 0x93, 0x1b, 0x00, 0xc0, + 0x4d, 0x92, 0x24, 0x49, 0x92, 0x24, 0xf9, 0x0a, 0x90, 0x24, 0x49, 0x76, 0x00, 0xc0, 0x9e, 0x24, 0x49, 0x92, 0x24, 0x49, 0xbe, 0x26, 0x49, 0x92, + 0x00, 0x76, 0x80, 0x24, 0x49, 0xb2, 0x27, 0x49, 0x92, 0x24, 0x49, 0xbe, 0xe6, 0x6b, 0x92, 0x24, 0x49, 0x6b, 0xad, 0xb5, 0xd6, 0x7e, 0x6d, 0xad, + 0xb5, 0xd6, 0x5a, 0x6b, 0xad, 0xb5, 0xf6, 0xeb, 0xf0, 0x3b, 0x00, 0xf8, 0x7d, 0xf8, 0xb5, 0xb5, 0xd6, 0x5a, 0x6b, 0xad, 0xb5, 0xd6, 0xda, 0x6f, + 0x76, 0x00, 0xb0, 0xfb, 0xad, 0xb5, 0xd6, 0x5a, 0x6b, 0xad, 0xb5, 0xd6, 0xda, 0xaf, 0xc3, 0xaf, 0xad, 0xb5, 0xd6, 0x5a, 0x6b, 0x60, 0xb7, 0x03, + 0xb4, 0xd6, 0x5a, 0xfb, 0xcd, 0x6f, 0xad, 0xb5, 0xd6, 0x5a, 0x6b, 0x49, 0x92, 0xdd, 0x0e, 0xd0, 0x5a, 0x6b, 0xad, 0xb5, 0x1d, 0x00, 0xec, 0xf6, + 0x24, 0x49, 0x12, 0xc0, 0xde, 0x5a, 0x6b, 0xad, 0xb5, 0xd6, 0x5a, 0x6b, 0xad, 0xb5, 0xdf, 0x01, 0x92, 0x24, 0x5f, 0xf3, 0x15, 0x00, 0x7c, 0xcd, + 0xd7, 0x24, 0x49, 0x12, 0xc0, 0xef, 0xad, 0xb5, 0xd6, 0x5a, 0x6b, 0xad, 0xb5, 0xd6, 0x5a, 0xdb, 0x5b, 0x6b, 0xad, 0xb5, 0xd6, 0x92, 0x24, 0x7b, + 0xf6, 0x24, 0x49, 0x92, 0x24, 0x49, 0x72, 0x03, 0x00, 0xf8, 0x9a, 0x24, 0x49, 0xb2, 0xef, 0xfb, 0xbe, 0xef, 0xfb, 0xbe, 0xef, 0xfb, 0xbe, 0xef, + 0xfb, 0x0e, 0xd8, 0xf7, 0x7d, 0xdf, 0xf7, 0x7d, 0xdf, 0x0f, 0x87, 0xc3, 0xe1, 0x70, 0x38, 0x1c, 0x0e, 0x87, 0xc3, 0xe1, 0x70, 0x38, 0x1c, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0xf6, 0x7d, 0xdf, 0xf7, 0x7d, 0x07, 0x00, 0xee, 0x6e, + 0xaf, 0xae, 0xae, 0x6e, 0xef, 0x80, 0xeb, 0x69, 0x9a, 0xae, 0xa6, 0x52, 0xca, 0x04, 0x6c, 0xa5, 0x74, 0x5d, 0xd7, 0x75, 0x5d, 0x07, 0xb0, 0xcd, + 0xf3, 0x3c, 0xcf, 0xf3, 0x0c, 0xd8, 0x4a, 0x3f, 0x64, 0xe8, 0xcb, 0x06, 0xe0, 0x7c, 0x75, 0x75, 0x75, 0x75, 0xf5, 0x04, 0xa0, 0xd6, 0x5a, 0xeb, + 0xda, 0xf7, 0x1d, 0xe0, 0xee, 0x36, 0x49, 0x12, 0xc0, 0x39, 0x4b, 0xad, 0x75, 0xc9, 0x19, 0x70, 0x9c, 0x4b, 0xd9, 0x4a, 0x99, 0x8f, 0xc0, 0x71, + 0x2e, 0xa5, 0xcc, 0xf3, 0x3c, 0xff, 0x1b, 0xf0, 0x76, 0xc8, 0xf8, 0x63, 0xad, 0xb5, 0x2e, 0x00, 0xce, 0xf7, 0xf7, 0xf7, 0x67, 0x00, 0xff, 0x79, + 0x3a, 0xdf, 0xdf, 0xff, 0xf5, 0xdc, 0x77, 0xc0, 0xb3, 0xa1, 0xef, 0xd7, 0xbe, 0xef, 0x87, 0x67, 0x80, 0xb5, 0x94, 0x52, 0x4a, 0x29, 0x05, 0x60, + 0x63, 0x63, 0x03, 0x90, 0x9c, 0x13, 0x76, 0xc0, 0x90, 0xf1, 0x34, 0x66, 0x70, 0x06, 0x8c, 0x19, 0xfa, 0x21, 0xa3, 0x33, 0xf0, 0xe6, 0xd3, 0xa7, + 0x24, 0x49, 0x92, 0x24, 0x49, 0x92, 0x24, 0x57, 0x57, 0x57, 0x4f, 0x00, 0x12, 0x67, 0x09, 0x00, 0x77, 0xb7, 0xee, 0x6e, 0x01, 0xc7, 0x79, 0x3e, + 0x02, 0x00, 0x90, 0x00, 0x00, 0x02, 0xf0, 0x30, 0x8e, 0xe3, 0x38, 0xfe, 0xfd, 0x3c, 0x2f, 0xcf, 0xc0, 0xd5, 0x3c, 0xcf, 0xf3, 0x0c, 0xc0, 0x93, + 0xab, 0xa1, 0xff, 0xbb, 0x1b, 0x00, 0xb0, 0xae, 0xeb, 0xba, 0xae, 0x2b, 0x00, 0x00, 0x00, +}; +#endif diff --git a/up.sh b/up.sh new file mode 100755 index 00000000..709d309d --- /dev/null +++ b/up.sh @@ -0,0 +1,5 @@ +#xxd -i tiny.wad | sed "s/unsigned/const unsigned/g" >src/tiny.wad.h +xxd -i tiny.whd | sed "s/unsigned/const unsigned/g" >src/tiny.whd.h +#cp tiny.wad.meta.h src/ +echo dont forget +echo picotool load -v -n -t bin tiny.whd -o 0x10040000