diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index ee251e0..8cd2e55 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -29,4 +29,4 @@ jobs: esp_idf_version: release/v5.4 # (Optional) cppcheck args - cppcheck_args: -i$GITHUB_WORKSPACE/components/gbc/gnuboy -i$GITHUB_WORKSPACE/components/nes/nofrendo -i$GITHUB_WORKSPACE/components/msx/fmsx -i$GITHUB_WORKSPACE/components/sms/smsplus -i$GITHUB_WORKSPACE/components/genesis/gwenesis -i$GITHUB_WORKSPACE/components/gui/generated -i$GITHUB_WORKSPACE/components/menu/generated -i$GITHUB_WORKSPACE/components/jpegdec --check-level=exhaustive --force --enable=all --inline-suppr --inconclusive --platform=mips32 --std=c++17 --suppressions-list=$GITHUB_WORKSPACE/suppressions.txt + cppcheck_args: -i$GITHUB_WORKSPACE/components/gbc/gnuboy -i$GITHUB_WORKSPACE/components/nes/nofrendo -i$GITHUB_WORKSPACE/components/msx/fmsx -i$GITHUB_WORKSPACE/components/doom/prboom -i$GITHUB_WORKSPACE/components/sms/smsplus -i$GITHUB_WORKSPACE/components/genesis/gwenesis -i$GITHUB_WORKSPACE/components/gui/generated -i$GITHUB_WORKSPACE/components/menu/generated -i$GITHUB_WORKSPACE/components/jpegdec --check-level=exhaustive --force --enable=all --inline-suppr --inconclusive --platform=mips32 --std=c++17 --suppressions-list=$GITHUB_WORKSPACE/suppressions.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index ccbcfbc..68d0c55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,9 +21,6 @@ set(GBC_COMPONENTS "gbc") ### SMS ### set(SMS_COMPONENTS "sms") -### SNES ### -# set(SNES_COMPONENTS "snes") - ### MSX ### set(MSX_COMPONENTS "msx") @@ -31,7 +28,10 @@ set(MSX_COMPONENTS "msx") set(GENESIS_COMPONENTS "genesis") ### DOOM ### -# set(DOOM_COMPONENTS "doom") +set(DOOM_COMPONENTS "doom") + +### SNES ### +# set(SNES_COMPONENTS "snes") add_compile_definitions(BOARD_HAS_PSRAM) diff --git a/README.md b/README.md index d4de786..21067ef 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,32 @@ +![image](https://github.com/user-attachments/assets/209ed9aa-22f0-4ee0-9868-65abb1b64bbc) + +https://github.com/user-attachments/assets/2d3da6ea-2e80-42c3-bbd6-5a2c59601201 + + +**Table of Contents** + +- [esp-box-emu](#esp-box-emu) + - [Overview](#overview) + - [Images and Videos](#images-and-videos) + - [Parts](#parts) + - [Features](#features) + - [Cloning](#cloning) + - [Build and Flash](#build-and-flash) + - [Rom Setup and Configuration (uSD Card)](#rom-setup-and-configuration-usd-card) + - [ROM Images](#rom-images) + - [Metadata.csv format](#metadatacsv-format) + - [References and Inspiration:](#references-and-inspiration) + - [Other NES Emulators](#other-nes-emulators) + - [Other Genesis Emulators](#other-genesis-emulators) + - [Useful Background / Information](#useful-background--information) + + + +## Overview + The ESP-BOX-EMU is a gameboy-inspired add-on for the ESP32-S3-BOX and ESP32-S3-BOX-3 which provides: - Game Controller (gamepad input with a/b/x/y, start/select, d-pad) @@ -30,16 +56,27 @@ ESP32-S3-BOX-3 which provides: - Unlocked mode (fastest execution), toggled with the X button - Genesis emulator (gwenesis) - full speed / buttery smooth when muted; unmuted it runs a little slower but has nice sound - Regular Controls (D-Pad/A/B/C/Start/Select) (note: A is mapped to B, B is mapped to A, and C is mapped to Y) + - Doom engine (prboom) - full speed with audio and control inputs. A is fire/enter, B is strafe/backspace, X is use, Y is weapontoggle, START is escape, and SELECT is map. - LVGL main menu with rom select (including boxart display) and settings page (all generated from Squareline Studio) - LVGL emulation paused menu with save slot select, save slot image display, and configuration (sound, brightness, display, etc.). (all generated from Squareline Studio) +## Images and Videos + +![image](https://github.com/user-attachments/assets/d867d5fa-4c22-42e3-b04f-1edd5288c5d2) +![image](https://github.com/user-attachments/assets/ad892905-37d4-4e16-8d5f-a7ff8d2a2c52) +![image](https://github.com/user-attachments/assets/b141daab-2cda-481c-98c2-980b012f177e) + +![image](https://github.com/user-attachments/assets/d23659b6-10d4-4375-8017-675e156a1a4b) + https://github.com/esp-cpp/esp-box-emu/assets/213467/3b77f6bd-4c42-417a-9eb7-a648f31b4008 https://github.com/esp-cpp/esp-box-emu/assets/213467/a3d18d03-c6a1-4911-89d1-e18119e8cc03 +## Parts + This project is designed to be in the same form factor as the Gameboy Color and to reuse the same button plastics and membranes for a good play feel. @@ -84,8 +121,9 @@ This project has the following features (still WIP): - [x] Sega Master System (SMS) / GameGear (GG) emulator - [x] MSX emulator - [x] Sega Mega Drive / Genesis emulator + - [x] Doom + - [ ] Dark Forces (WIP) - [ ] SNES emulator (WIP) - - [ ] Doom (WIP) - [x] uSD card (FAT) filesystem over SPI - [x] TinyUSB MSC device for optionally exposing the uSD to the attached USB host - [x] Memory mapping of selected rom data from storage into raw data partition diff --git a/boxart/doom_2_boxart.jpg b/boxart/doom_2_boxart.jpg new file mode 100644 index 0000000..064c419 Binary files /dev/null and b/boxart/doom_2_boxart.jpg differ diff --git a/boxart/doom_boxart.jpg b/boxart/doom_boxart.jpg new file mode 100644 index 0000000..693e143 Binary files /dev/null and b/boxart/doom_boxart.jpg differ diff --git a/boxart/doom_ultimate_boxart.jpg b/boxart/doom_ultimate_boxart.jpg new file mode 100644 index 0000000..e80f476 Binary files /dev/null and b/boxart/doom_ultimate_boxart.jpg differ diff --git a/boxart/source/doom_2_boxart.jpg b/boxart/source/doom_2_boxart.jpg new file mode 100644 index 0000000..b911d6a Binary files /dev/null and b/boxart/source/doom_2_boxart.jpg differ diff --git a/boxart/source/doom_boxart.jpg b/boxart/source/doom_boxart.jpg new file mode 100644 index 0000000..4224ee7 Binary files /dev/null and b/boxart/source/doom_boxart.jpg differ diff --git a/boxart/source/doom_ultimate_boxart.jpg b/boxart/source/doom_ultimate_boxart.jpg new file mode 100644 index 0000000..afefd69 Binary files /dev/null and b/boxart/source/doom_ultimate_boxart.jpg differ diff --git a/boxart/source/resize.bash b/boxart/source/resize.bash index 092d7a9..efa2b8f 100755 --- a/boxart/source/resize.bash +++ b/boxart/source/resize.bash @@ -1,2 +1,2 @@ #!/bin/bash -find . -maxdepth 1 -iname "*.jpg" | xargs -L1 -I{} convert -resize 100x "{}" ../"{}" +find . -maxdepth 1 -iname "*.jpg" | xargs -L1 -I{} magick -quiet "{}" -resize 100x ../"{}" diff --git a/components/box-emu/CMakeLists.txt b/components/box-emu/CMakeLists.txt index c9017d7..9bece60 100644 --- a/components/box-emu/CMakeLists.txt +++ b/components/box-emu/CMakeLists.txt @@ -30,4 +30,5 @@ idf_component_register( "statistics" "max1704x" "esp-box" + "pool_allocator" ) diff --git a/components/box-emu/include/box-emu.hpp b/components/box-emu/include/box-emu.hpp index d0ccc66..0bc48db 100644 --- a/components/box-emu/include/box-emu.hpp +++ b/components/box-emu/include/box-emu.hpp @@ -36,6 +36,7 @@ #include "video_setting.hpp" #include "make_color.h" +#include "pool_allocator.h" class BoxEmu : public espp::BaseComponent { public: @@ -126,6 +127,7 @@ class BoxEmu : public espp::BaseComponent { ///////////////////////////////////////////////////////////////////////////// bool initialize_memory(); + void deinitialize_memory(); size_t copy_file_to_romdata(const std::string& filename); uint8_t *romdata() const; @@ -154,6 +156,7 @@ class BoxEmu : public espp::BaseComponent { void clear_screen(); void display_size(size_t width, size_t height); void native_size(size_t width, size_t height, int pitch = -1); + const uint16_t *palette() const; void palette(const uint16_t *palette, size_t size = 256); void push_frame(const void* frame); VideoSetting video_setting() const; @@ -185,7 +188,6 @@ class BoxEmu : public espp::BaseComponent { bool is_native() const; int x_offset() const; int y_offset() const; - const uint16_t *palette() const; bool video_task_callback(std::mutex &m, std::condition_variable &cv, bool &task_notified); class InputBase { diff --git a/components/box-emu/src/box-emu.cpp b/components/box-emu/src/box-emu.cpp index b91e487..1fadefe 100644 --- a/components/box-emu/src/box-emu.cpp +++ b/components/box-emu/src/box-emu.cpp @@ -114,7 +114,7 @@ bool BoxEmu::initialize_sdcard() { memset(&mount_config, 0, sizeof(mount_config)); mount_config.format_if_mount_failed = false; mount_config.max_files = 5; - mount_config.allocation_unit_size = 16 * 1024; + mount_config.allocation_unit_size = 2 * 1024; // Use settings defined above to initialize SD card and mount FAT filesystem. // Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions. @@ -136,7 +136,7 @@ bool BoxEmu::initialize_sdcard() { bus_cfg.sclk_io_num = sdcard_sclk; bus_cfg.quadwp_io_num = -1; bus_cfg.quadhd_io_num = -1; - bus_cfg.max_transfer_sz = 8192; + bus_cfg.max_transfer_sz = 4096; spi_host_device_t host_id = (spi_host_device_t)host.slot; ret = spi_bus_initialize(host_id, &bus_cfg, SDSPI_DEFAULT_DMA); if (ret != ESP_OK) { @@ -182,6 +182,8 @@ sdmmc_card_t *BoxEmu::sdcard() const { // Memory ///////////////////////////////////////////////////////////////////////////// +static constexpr size_t memory_size = 4*1024*1024; + extern "C" uint8_t *osd_getromdata() { auto &emu = BoxEmu::get(); return emu.romdata(); @@ -195,7 +197,7 @@ bool BoxEmu::initialize_memory() { logger_.info("Initializing memory (romdata)"); // allocate memory for the ROM and make sure it's on the SPIRAM - romdata_ = (uint8_t*)heap_caps_malloc(4*1024*1024, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + romdata_ = (uint8_t*)heap_caps_malloc(memory_size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); if (romdata_ == nullptr) { logger_.error("Couldn't allocate memory for ROM!"); return false; @@ -204,6 +206,14 @@ bool BoxEmu::initialize_memory() { return true; } +void BoxEmu::deinitialize_memory() { + if (romdata_) { + logger_.info("Deinitializing memory (romdata)"); + free(romdata_); + romdata_ = nullptr; + } +} + size_t BoxEmu::copy_file_to_romdata(const std::string& filename) { // load the file data and iteratively copy it over std::ifstream romfile(filename, std::ios::binary | std::ios::ate); //open file at end @@ -648,7 +658,7 @@ bool BoxEmu::initialize_usb() { .mount_config = { .format_if_mount_failed = false, .max_files = 5, - .allocation_unit_size = 16 * 1024, // sector size is 512 bytes, this should be between sector size and (128 * sector size). Larger means higher read/write performance and higher overhead for small files. + .allocation_unit_size = 2 * 1024, // sector size is 512 bytes, this should be between sector size and (128 * sector size). Larger means higher read/write performance and higher overhead for small files. .disk_status_check_enable = false, // true if you see issues or are unmounted properly; slows down I/O }, }; diff --git a/components/doom/CMakeLists.txt b/components/doom/CMakeLists.txt new file mode 100644 index 0000000..30a83ca --- /dev/null +++ b/components/doom/CMakeLists.txt @@ -0,0 +1,15 @@ +idf_component_register( + INCLUDE_DIRS "include" + SRC_DIRS "src" "prboom" + PRIV_INCLUDE_DIRS "." "prboom" + REQUIRES box-emu shared_memory + ) +target_compile_options(${COMPONENT_LIB} PRIVATE + -Wno-error=address + -Wno-misleading-indentation + -Wno-format-overflow + -Wno-char-subscripts + -Wno-missing-field-initializers + -DHAVE_CONFIG_H + -O2 +) diff --git a/components/doom/data/doom1.wad b/components/doom/data/doom1.wad new file mode 100644 index 0000000..1a58f66 Binary files /dev/null and b/components/doom/data/doom1.wad differ diff --git a/components/doom/data/prboom.wad b/components/doom/data/prboom.wad new file mode 100644 index 0000000..be73e43 Binary files /dev/null and b/components/doom/data/prboom.wad differ diff --git a/components/doom/include/doom.hpp b/components/doom/include/doom.hpp new file mode 100644 index 0000000..f5c835d --- /dev/null +++ b/components/doom/include/doom.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include +#include + +void reset_doom(); +void init_doom(const std::string& rom_filename, uint8_t *romdata, size_t rom_data_size); +void doom_init_shared_memory(); +void pause_doom_tasks(); +void resume_doom_tasks(); +void load_doom(std::string_view save_path, int save_slot); +void save_doom(std::string_view save_path, int save_slot); +void run_doom_rom(); +void deinit_doom(); +std::span get_doom_video_buffer(); diff --git a/components/doom/linker.lf b/components/doom/linker.lf new file mode 100644 index 0000000..26fefb3 --- /dev/null +++ b/components/doom/linker.lf @@ -0,0 +1,8 @@ +# Move gbc code into IRAM for perforamnce +[mapping:doom] +archive: libdoom.a +entries: + d_main (noflash_text) + r_draw (noflash_text) + g_game (noflash_text) + m_misc (noflash_text) diff --git a/components/doom/prboom/AUTHORS b/components/doom/prboom/AUTHORS new file mode 100644 index 0000000..7839f8e --- /dev/null +++ b/components/doom/prboom/AUTHORS @@ -0,0 +1,163 @@ +This file is now the amalgamated list of authors, contributors and credits +for PrBoom. Hopefully by keeping these all in one place, they will remain +more accurate. + +Doom was originally written by id software; when playing with any id main +wad file, you can see their list of credits, which includes the list of +programmers. After some years, they released the source code, to allow +others to work on improving the game. + +One of the first projects was DosDoom, by Chi Hoang. This was a quick port +of the released source code, which was for Linux, to DOS. This was then +picked up by TeamTNT (http://www.teamtnt.com/), who produced Boom, a greatly +debugged and extended version of Doom. The Boom programmers were Lee +Killough, Jim Flynn, Rand Phares, Ty Halderman. + +Several projects started working from the Boom source code. One was PrBoom, +made by Florian Schulze, that ported the code to Windows, added suport for +higher resolutions and later OpenGL. Another was Marine's Best Friend +(known as MBF) by Lee Killough, which fixed a lot of Boom bugs and added +many new game features. Finally, there was LxDoom, a port of Boom to Linux +by Colin Phipps. + +In October 1999, id Software re-released the Doom source code under the +GNU General Public License. TeamTNT have also changed to the new license, +and the other sources mentioned above have all allowed their code to be +GPLed. So PrBoom is covered by the GPL. + +In May 2000, LxDoom, PrBoom, and a derived port called LSDLDoom, merged into +one. The current authors of PrBoom are: + +Florian Schulze +Colin Phipps +Neil Stevens - Mac OS X porting +Andrey Budko +Rob Young (RjY) + +Our thanks go to all the authors of the ports mentioned above, and also the +following people who contributed code to LxDoom or PrBoom: + +Jess Haas +Of LSDLdoom, who merged his project into PrBoom, contributing his SDL code. + +Nicolas Kalkhof +Much work on the OpenGL renderer. + +James "Quasar" Haley +Ever willing to talk about source ideas, and has pointed me in the direction of +a lot of Boom and MBF bugs; also various bits code from his port Eternity have +been used, such as the BEX enhancements. + +Bob Aman (sporkmonger.com) +Created the RMUDAnsiTextView class used in the Mac launcher. + +Gady Kozma gady@math.tau.ac.il +Added hires to the SVGALib version of LxDoom, and other useful patches. + +Dick Leban +Lots of feedback about portability issues and helping get the network code +working properly back at v1.3.6. + +Eduardo Casino Almao +Lots of helpful feedback and suggestions, but more importantly actually getting +to grips with the code and giving very precise bug reports and patches. + +Joey Hess +For numerous patches, like the glibc fixes and window manager updates, and +help with the music. + +Ben Winslow +Various useful patches, like the colour ENDOOM code. + +Josh Parsons josh@schlick.anu.edu.au +Sent me the patches to use autoconf for configuring LxDoom. + +Steve Van Devender +Found the bug causing slight noise at the start of sounds playing, and other +patches. + +Barry Mead +Improvements to the mouse code and other odd patches. + +Mattias Kunkel +Made the lxdoom.spec file for creating LxDoom RPMs. + +Vicente Aguilar vicente@hal.dhis.org +Handy patch for the file handling + +Benjamin L McGee +Patch fixing the joystick code. + +Chris Young +Patch improving the ENDOOM printing + +Peter Jay Salzman +Cleanup patches + +Oliver Kraus +Send bug reports and patches for Solaris/Sparc. + +Paul S Jenner +Nice patch to make RPM building easier + +Julian +Fixed inline asm for gcc-2.95 (from Eternity) + +Lionel Ulmer +Patch to fix alignment problems on ARM processors. + +Ville Vuorinen +Spotted and helped patch the player spawn bug, as well as helping with some +Win32 issues. + +Steven Elliot +Misc patches. + +Andreas Dehmel +Spotted & patched a savegame bug. + +Jon Dowland +Bug reports & fixes, documentation improvements. + +If you have sent in patches and I forgot to list you, I apologise. Please email +me and I will add you. + +Also, thanks to the following people who have helped in various ways: + +Simon "fraggle" Howard +More MBF bugs. + +Robert Phipps +Network game testing, feature suggestions etc. + +Udo Monk +His port xdoom is very portable, and I referred to his code sometimes for help +with the X stuff; also his collection of Doom tools (XWadTools) is the +definitive tools collection for Linux. + +Andre Majorel +For Yadex, so I can debug those problematic levels more easily. + +Michael Heasley +Author of musserver, which helped me first add music support. + +Rafael Reilova +Helped with the music server program for LxDoom + +Frederic Oghdayan +For useful feedback on LxDoom v1.0.1, and repeating his bug reports until I +believed them :-). + +Adam Hegyi +Prompted me to hunt down those last few demo sync bugs, and provided some useful +insights and example demos to help. + +Adam Williamson +Pointing me toward yet another compatibility bug. + +Ingo van Lil +Another bug spotter. + +Everyone who contributed indirectly to MBF and Boom and Doom; see the +respective documentation files. + diff --git a/components/doom/prboom/CMakeLists.txt b/components/doom/prboom/CMakeLists.txt new file mode 100644 index 0000000..3f4b6f8 --- /dev/null +++ b/components/doom/prboom/CMakeLists.txt @@ -0,0 +1,19 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") +set(COMPONENT_REQUIRES "retro-go") +register_component() + +# The PSRAM cache bug seems responsible for very odd bugs that I spent way too much time +# trying to debug... In retro-go the fix is disabled because of the huge performance overhead +# but I guess we'll have to live with it in at least the prboom-go module... + +rg_setup_compile_options( + -Wno-error=address + -Wno-misleading-indentation + -Wno-format-overflow + -Wno-char-subscripts + -Wno-missing-field-initializers + -mfix-esp32-psram-cache-issue + -DHAVE_CONFIG_H + -O2 +) diff --git a/components/doom/prboom/COPYING b/components/doom/prboom/COPYING new file mode 100644 index 0000000..f698bce --- /dev/null +++ b/components/doom/prboom/COPYING @@ -0,0 +1,367 @@ +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +Preamble +The licenses for most software are designed to take away your freedom +to share and change it. By contrast, the GNU General Public License +is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit +to using it. (Some other Free Software Foundation software is covered +by the GNU Library General Public License instead.) You can apply it +to your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge +for this service if you wish), that you receive source code or can +get it if you want it, that you can change the software or use pieces +of it in new free programs; and that you know you can do these +things. + +To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + +We protect your rights with two steps: (1) copyright the software, +and (2) offer you this license which gives you legal permission to +copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, +we want its recipients to know that what they have is not the +original, so that any problems introduced by others will not reflect +on the original authors' reputations. + +Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making +the program proprietary. To prevent this, we have made it clear that +any patent must be licensed for everyone's free use or not licensed +at all. + +The precise terms and conditions for copying, distribution and +modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +0. This License applies to any program or other work which contains a +notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the +Program is covered only if its contents constitute a work based on +the Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any +warranty; and give any other recipients of the Program a copy of this +License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a +fee. + +2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + +a) You must cause the modified files to carry prominent notices +stating that you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in +whole or in part contains or is derived from the Program or any part +thereof, to be licensed as a whole at no charge to all third parties +under the terms of this License. + +c) If the modified program normally reads commands interactively when +run, you must cause it, when started running for such interactive use +in the most ordinary way, to print or display an announcement +including an appropriate copyright notice and a notice that there is +no warranty (or else, saying that you provide a warranty) and that +users may redistribute the program under these conditions, and +telling the user how to view a copy of this License. (Exception: if +the Program itself is interactive but does not normally print such an +announcement, your work based on the Program is not required to print +an announcement.) +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the +Program with the Program (or with a work based on the Program) on a +volume of a storage or distribution medium does not bring the other +work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the +following: + +a) Accompany it with the complete corresponding machine-readable +source code, which must be distributed under the terms of Sections 1 +and 2 above on a medium customarily used for software interchange; +or, + +b) Accompany it with a written offer, valid for at least three years, +to give any third party, for a charge no more than your cost of +physically performing source distribution, a complete +machine-readable copy of the corresponding source code, to be +distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to +distribute corresponding source code. (This alternative is allowed +only for noncommercial distribution and only if you received the +program in object code or executable form with such an offer, in +accord with Subsection b above.) +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this +License. However, parties who have received copies, or rights, from +you under this License will not have their licenses terminated so +long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject +to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted +herein. You are not responsible for enforcing compliance by third +parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do +not excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not distribute the Program at all. For example, +if a patent license would not permit royalty-free redistribution of +the Program by all those who receive copies directly or indirectly +through you, then the only way you could satisfy both it and this +License would be to refrain entirely from distribution of the +Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended +to apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is +willing to distribute software through any other system and a +licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new +versions of the General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published +by the Free Software Foundation. If the Program does not specify a +version number of this License, you may choose any version ever +published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the +author to ask for permission. For software which is copyrighted by +the Free Software Foundation, write to the Free Software Foundation; +we sometimes make exceptions for this. Our decision will be guided by +the two goals of preserving the free status of all derivatives of our +free software and of promoting the sharing and reuse of software +generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + +END OF TERMS AND CONDITIONS +How to Apply These Terms to Your New Programs +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make +it free software which everyone can redistribute and change under +these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + +one line to give the program's name and an idea of what it does. +Copyright (C) yyyy name of author + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +02111-1307, USA. + +Also add information on how to contact you by electronic and paper +mail. + +If the program is interactive, make it output a short notice like +this when it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) yyyy name of author +Gnomovision comes with ABSOLUTELY NO WARRANTY; for details +type `show w'. This is free software, and you are welcome +to redistribute it under certain conditions; type `show c' +for details. + +The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than `show w' and +`show c'; they could even be mouse-clicks or menu items--whatever +suits your program. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the +program, if necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright +interest in the program `Gnomovision' +(which makes passes at compilers) written +by James Hacker. + +signature of Ty Coon, 1 April 1989 +Ty Coon, President of Vice + +This General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking +proprietary applications with the library. If this is what you want +to do, use the GNU Library General Public License instead of this +License. + + +---------------------------------------------------------------------- +---------- +Return to GNU's home page. +FSF & GNU inquiries & questions to gnu@gnu.org. Other ways to contact +the FSF. + +Comments on these web pages to webmasters@www.gnu.org, send other +questions to gnu@gnu.org. + +Copyright notice above. +Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, +MA 02111, USA + +Updated: 16 Feb 1998 tower + diff --git a/components/doom/prboom/am_map.c b/components/doom/prboom/am_map.c new file mode 100644 index 0000000..3ab48c3 --- /dev/null +++ b/components/doom/prboom/am_map.c @@ -0,0 +1,1584 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * the automap code + * + *----------------------------------------------------------------------------- + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "doomstat.h" +#include "st_stuff.h" +#include "r_main.h" +#include "p_setup.h" +#include "p_maputl.h" +#include "w_wad.h" +#include "v_video.h" +#include "p_spec.h" +#include "am_map.h" +#include "dstrings.h" +#include "d_deh.h" // Ty 03/27/98 - externalizations +#include "lprintf.h" // jff 08/03/98 - declaration of lprintf +#include "g_game.h" + +//jff 1/7/98 default automap colors added +int mapcolor_back; // map background +int mapcolor_grid; // grid lines color +int mapcolor_wall; // normal 1s wall color +int mapcolor_fchg; // line at floor height change color +int mapcolor_cchg; // line at ceiling height change color +int mapcolor_clsd; // line at sector with floor=ceiling color +int mapcolor_rkey; // red key color +int mapcolor_bkey; // blue key color +int mapcolor_ykey; // yellow key color +int mapcolor_rdor; // red door color (diff from keys to allow option) +int mapcolor_bdor; // blue door color (of enabling one but not other ) +int mapcolor_ydor; // yellow door color +int mapcolor_tele; // teleporter line color +int mapcolor_secr; // secret sector boundary color +int mapcolor_exit; // jff 4/23/98 add exit line color +int mapcolor_unsn; // computer map unseen line color +int mapcolor_flat; // line with no floor/ceiling changes +int mapcolor_sprt; // general sprite color +int mapcolor_item; // item sprite color +int mapcolor_frnd; // friendly sprite color +int mapcolor_enemy; // enemy sprite color +int mapcolor_hair; // crosshair color +int mapcolor_sngl; // single player arrow color +int mapcolor_me; // consoleplayer's chosen colour +int mapcolor_plyr[4] = { 112, 88, 64, 32 }; // colors for player arrows in multiplayer + +//jff 3/9/98 add option to not show secret sectors until entered +int map_secret_after; +//jff 4/3/98 add symbols for "no-color" for disable and "black color" for black +#define NC 0 +#define BC 247 + +// drawing stuff +#define FB 0 + +// scale on entry +#define INITSCALEMTOF (.2*FRACUNIT) +// how much the automap moves window per tic in frame-buffer coordinates +// moves 140 pixels in 1 second +#define F_PANINC 4 +// how much zoom-in per tic +// goes to 2x in 1 second +#define M_ZOOMIN ((int) (1.02*FRACUNIT)) +// how much zoom-out per tic +// pulls out to 0.5x in 1 second +#define M_ZOOMOUT ((int) (FRACUNIT/1.02)) + +#define PLAYERRADIUS (16*(1<>16) +// translates between frame-buffer and map coordinates +#define CXMTOF(x) (f_x + MTOF((x)-m_x)) +#define CYMTOF(y) (f_y + (f_h - MTOF((y)-m_y))) + +typedef struct +{ + mpoint_t a, b; +} mline_t; + +// +// The vector graphics for the automap. +// A line drawing of the player pointing right, +// starting from the middle. +// +#define R ((8*PLAYERRADIUS)/7) +mline_t player_arrow[] = +{ + { { -R+R/8, 0 }, { R, 0 } }, // ----- + { { R, 0 }, { R-R/2, R/4 } }, // -----> + { { R, 0 }, { R-R/2, -R/4 } }, + { { -R+R/8, 0 }, { -R-R/8, R/4 } }, // >----> + { { -R+R/8, 0 }, { -R-R/8, -R/4 } }, + { { -R+3*R/8, 0 }, { -R+R/8, R/4 } }, // >>---> + { { -R+3*R/8, 0 }, { -R+R/8, -R/4 } } +}; +#undef R +#define NUMPLYRLINES (sizeof(player_arrow)/sizeof(mline_t)) + +#define R ((8*PLAYERRADIUS)/7) +mline_t cheat_player_arrow[] = +{ // killough 3/22/98: He's alive, Jim :) + { { -R+R/8, 0 }, { R, 0 } }, // ----- + { { R, 0 }, { R-R/2, R/4 } }, // -----> + { { R, 0 }, { R-R/2, -R/4 } }, + { { -R+R/8, 0 }, { -R-R/8, R/4 } }, // >----> + { { -R+R/8, 0 }, { -R-R/8, -R/4 } }, + { { -R+3*R/8, 0 }, { -R+R/8, R/4 } }, // >>---> + { { -R+3*R/8, 0 }, { -R+R/8, -R/4 } }, + { { -R/10-R/6, R/4}, {-R/10-R/6, -R/4} }, // J + { { -R/10-R/6, -R/4}, {-R/10-R/6-R/8, -R/4} }, + { { -R/10-R/6-R/8, -R/4}, {-R/10-R/6-R/8, -R/8} }, + { { -R/10, R/4}, {-R/10, -R/4}}, // F + { { -R/10, R/4}, {-R/10+R/8, R/4}}, + { { -R/10+R/4, R/4}, {-R/10+R/4, -R/4}}, // F + { { -R/10+R/4, R/4}, {-R/10+R/4+R/8, R/4}}, +}; +#undef R +#define NUMCHEATPLYRLINES (sizeof(cheat_player_arrow)/sizeof(mline_t)) + +#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 NUMTRIANGLEGUYLINES (sizeof(triangle_guy)/sizeof(mline_t)) + +//jff 1/5/98 new symbol for keys on automap +#define R (FRACUNIT) +mline_t cross_mark[] = +{ + { { -R, 0 }, { R, 0} }, + { { 0, -R }, { 0, R } }, +}; +#undef R +#define NUMCROSSMARKLINES (sizeof(cross_mark)/sizeof(mline_t)) +//jff 1/5/98 end of new symbol + +#define R (FRACUNIT) +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) } } +}; +#undef R +#define NUMTHINTRIANGLEGUYLINES (sizeof(thintriangle_guy)/sizeof(mline_t)) + +int ddt_cheating = 0; // killough 2/7/98: make global, rename to ddt_* + +static int leveljuststarted = 1; // kluge until AM_LevelInit() is called + +enum automapmode_e automapmode; // Mode that the automap is in + +// location of window on screen +static int f_x; +static int f_y; + +// size of window on screen +static int f_w; +static int f_h; + +static mpoint_t m_paninc; // how far the window pans each tic (map coords) +static fixed_t mtof_zoommul; // how far the window zooms each tic (map coords) +static fixed_t ftom_zoommul; // how far the window zooms each tic (fb coords) + +static fixed_t m_x, m_y; // LL x,y window location on the map (map coords) +static fixed_t m_x2, m_y2; // UR x,y window location on the map (map coords) + +// +// width/height of window on map (map coords) +// +static fixed_t m_w; +static fixed_t m_h; + +// based on level size +static fixed_t min_x; +static fixed_t min_y; +static fixed_t max_x; +static fixed_t max_y; + +static fixed_t max_w; // max_x-min_x, +static fixed_t max_h; // max_y-min_y + +// based on player size +static fixed_t min_w; +static fixed_t min_h; + + +static fixed_t min_scale_mtof; // used to tell when to stop zooming out +static fixed_t max_scale_mtof; // used to tell when to stop zooming in + +// old stuff for recovery later +static fixed_t old_m_w, old_m_h; +static fixed_t old_m_x, old_m_y; + +// old location used by the Follower routine +static mpoint_t f_oldloc; + +// used by MTOF to scale from map-to-frame-buffer coords +static fixed_t scale_mtof = (fixed_t)INITSCALEMTOF; +// used by FTOM to scale from frame-buffer-to-map coords (=1/scale_mtof) +static fixed_t scale_ftom; + +static player_t *plr; // the player represented by an arrow + +// killough 2/22/98: Remove limit on automap marks, +// and make variables external for use in savegames. + +mpoint_t *markpoints = NULL; // where the points are +int markpointnum = 0; // next point to be assigned (also number of points now) +int markpointnum_max = 0; // killough 2/22/98 + +static boolean stopped = true; + +// +// AM_activateNewScale() +// +// Changes the map scale after zooming or translating +// +// Passed nothing, returns nothing +// +static void AM_activateNewScale(void) +{ + m_x += m_w/2; + m_y += m_h/2; + m_w = FTOM(f_w); + m_h = FTOM(f_h); + m_x -= m_w/2; + m_y -= m_h/2; + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; +} + +// +// AM_saveScaleAndLoc() +// +// Saves the current center and zoom +// Affects the variables that remember old scale and loc +// +// Passed nothing, returns nothing +// +static void AM_saveScaleAndLoc(void) +{ + old_m_x = m_x; + old_m_y = m_y; + old_m_w = m_w; + old_m_h = m_h; +} + +// +// AM_restoreScaleAndLoc() +// +// restores the center and zoom from locally saved values +// Affects global variables for location and scale +// +// Passed nothing, returns nothing +// +static void AM_restoreScaleAndLoc(void) +{ + m_w = old_m_w; + m_h = old_m_h; + if (!(automapmode & am_follow)) + { + m_x = old_m_x; + m_y = old_m_y; + } + else + { + m_x = (plr->mo->x >> FRACTOMAPBITS) - m_w/2;//e6y + m_y = (plr->mo->y >> FRACTOMAPBITS) - m_h/2;//e6y + } + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; + + // Change the scaling multipliers + scale_mtof = FixedDiv(f_w<= markpointnum_max) + markpoints = realloc(markpoints, + (markpointnum_max = markpointnum_max ? + markpointnum_max*2 : 16) * sizeof(*markpoints)); + + markpoints[markpointnum].x = m_x + m_w/2; + markpoints[markpointnum].y = m_y + m_h/2; + markpointnum++; +} + +// +// AM_findMinMaxBoundaries() +// +// Determines bounding box of all vertices, +// sets global variables controlling zoom range. +// +// Passed nothing, returns nothing +// +static void AM_findMinMaxBoundaries(void) +{ + int i; + fixed_t a; + fixed_t b; + + min_x = min_y = INT_MAX; + max_x = max_y = -INT_MAX; + + for (i=0;i max_x) + max_x = vertexes[i].x; + + if (vertexes[i].y < min_y) + min_y = vertexes[i].y; + else if (vertexes[i].y > max_y) + max_y = vertexes[i].y; + } + + max_w = (max_x >>= FRACTOMAPBITS) - (min_x >>= FRACTOMAPBITS);//e6y + max_h = (max_y >>= FRACTOMAPBITS) - (min_y >>= FRACTOMAPBITS);//e6y + + min_w = 2*PLAYERRADIUS; // const? never changed? + min_h = 2*PLAYERRADIUS; + + a = FixedDiv(f_w< max_x) + m_x = max_x - m_w/2; + else if (m_x + m_w/2 < min_x) + m_x = min_x - m_w/2; + + if (m_y + m_h/2 > max_y) + m_y = max_y - m_h/2; + else if (m_y + m_h/2 < min_y) + m_y = min_y - m_h/2; + + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; +} + + +// +// AM_initVariables() +// +// Initialize the variables for the automap +// +// Affects the automap global variables +// Status bar is notified that the automap has been entered +// Passed nothing, returns nothing +// +static void AM_initVariables(void) +{ + int pnum; + static event_t st_notify = { ev_keyup, AM_MSGENTERED, 0, 0 }; + + automapmode |= am_active; + + f_oldloc.x = INT_MAX; + + m_paninc.x = m_paninc.y = 0; + ftom_zoommul = FRACUNIT; + mtof_zoommul = FRACUNIT; + + m_w = FTOM(f_w); + m_h = FTOM(f_h); + + // find player to center on initially + if (!playeringame[pnum = consoleplayer]) + for (pnum=0;pnummo->x >> FRACTOMAPBITS) - m_w/2;//e6y + m_y = (plr->mo->y >> FRACTOMAPBITS) - m_h/2;//e6y + AM_changeWindowLoc(); + + // for saving & restoring + old_m_x = m_x; + old_m_y = m_y; + old_m_w = m_w; + old_m_h = m_h; + + // inform the status bar of the change + ST_Responder(&st_notify); +} + +// +// AM_loadPics() +// +static void AM_loadPics(void) +{ + // cph - mark numbers no longer needed cached +} + +// +// AM_unloadPics() +// +static void AM_unloadPics(void) +{ + // cph - mark numbers no longer needed cached +} + +// +// AM_clearMarks() +// +// Sets the number of marks to 0, thereby clearing them from the display +// +// Affects the global variable markpointnum +// Passed nothing, returns nothing +// +void AM_clearMarks(void) +{ + markpointnum = 0; +} + +// +// AM_LevelInit() +// +// Initialize the automap at the start of a new level +// should be called at the start of every level +// +// Passed nothing, returns nothing +// Affects automap's global variables +// +// CPhipps - get status bar height from status bar code +static void AM_LevelInit(void) +{ + leveljuststarted = 0; + + f_x = f_y = 0; + f_w = SCREENWIDTH; // killough 2/7/98: get rid of finit_ vars + f_h = SCREENHEIGHT-ST_SCALED_HEIGHT;// to allow runtime setting of width/height + + AM_findMinMaxBoundaries(); + scale_mtof = FixedDiv(min_scale_mtof, (int) (0.7*FRACUNIT)); + if (scale_mtof > max_scale_mtof) + scale_mtof = min_scale_mtof; + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); +} + +// +// AM_Stop() +// +// Cease automap operations, unload patches, notify status bar +// +// Passed nothing, returns nothing +// +void AM_Stop (void) +{ + static event_t st_notify = { 0, ev_keyup, AM_MSGEXITED, 0 }; + + AM_unloadPics(); + automapmode &= ~am_active; + ST_Responder(&st_notify); + stopped = true; +} + +// +// AM_Start() +// +// Start up automap operations, +// if a new level, or game start, (re)initialize level variables +// init map variables +// load mark patches +// +// Passed nothing, returns nothing +// +void AM_Start(void) +{ + static int lastlevel = -1, lastepisode = -1; + + if (!stopped) + AM_Stop(); + stopped = false; + if (lastlevel != gamemap || lastepisode != gameepisode) + { + AM_LevelInit(); + lastlevel = gamemap; + lastepisode = gameepisode; + } + AM_initVariables(); + AM_loadPics(); +} + +// +// AM_minOutWindowScale() +// +// Set the window scale to the maximum size +// +// Passed nothing, returns nothing +// +static void AM_minOutWindowScale(void) +{ + scale_mtof = min_scale_mtof; + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + AM_activateNewScale(); +} + +// +// AM_maxOutWindowScale(void) +// +// Set the window scale to the minimum size +// +// Passed nothing, returns nothing +// +static void AM_maxOutWindowScale(void) +{ + scale_mtof = max_scale_mtof; + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + AM_activateNewScale(); +} + +// +// AM_Responder() +// +// Handle events (user inputs) in automap mode +// +// Passed an input event, returns true if its handled +// +boolean AM_Responder +( event_t* ev ) +{ + int rc; + static int bigstate=0; + int ch; // phares + + rc = false; + + if (!(automapmode & am_active)) + { + if (ev->type == ev_keydown && ev->data1 == key_map) // phares + { + AM_Start (); + rc = true; + } + } + else if (ev->type == ev_keydown) + { + rc = true; + ch = ev->data1; // phares + if (ch == key_map_right) // | + if (!(automapmode & am_follow)) // V + m_paninc.x = FTOM(F_PANINC); + else + rc = false; + else if (ch == key_map_left) + if (!(automapmode & am_follow)) + m_paninc.x = -FTOM(F_PANINC); + else + rc = false; + else if (ch == key_map_up) + if (!(automapmode & am_follow)) + m_paninc.y = FTOM(F_PANINC); + else + rc = false; + else if (ch == key_map_down) + if (!(automapmode & am_follow)) + m_paninc.y = -FTOM(F_PANINC); + else + rc = false; + else if (ch == key_map_zoomout) + { + mtof_zoommul = M_ZOOMOUT; + ftom_zoommul = M_ZOOMIN; + } + else if (ch == key_map_zoomin) + { + mtof_zoommul = M_ZOOMIN; + ftom_zoommul = M_ZOOMOUT; + } + else if (ch == key_map) + { + bigstate = 0; + AM_Stop (); + } + else if (ch == key_map_gobig) + { + bigstate = !bigstate; + if (bigstate) + { + AM_saveScaleAndLoc(); + AM_minOutWindowScale(); + } + else + AM_restoreScaleAndLoc(); + } + else if (ch == key_map_follow) + { + automapmode ^= am_follow; // CPhipps - put all automap mode stuff into one enum + f_oldloc.x = INT_MAX; + // Ty 03/27/98 - externalized + plr->message = (automapmode & am_follow) ? s_AMSTR_FOLLOWON : s_AMSTR_FOLLOWOFF; + } + else if (ch == key_map_grid) + { + automapmode ^= am_grid; // CPhipps + // Ty 03/27/98 - *not* externalized + plr->message = (automapmode & am_grid) ? s_AMSTR_GRIDON : s_AMSTR_GRIDOFF; + } + else if (ch == key_map_mark) + { + /* Ty 03/27/98 - *not* externalized + * cph 2001/11/20 - use doom_printf so we don't have our own buffer */ + doom_printf("%s %d", s_AMSTR_MARKEDSPOT, markpointnum); + AM_addMark(); + } + else if (ch == key_map_clear) + { + AM_clearMarks(); // Ty 03/27/98 - *not* externalized + plr->message = s_AMSTR_MARKSCLEARED; // ^ + } // | + else if (ch == key_map_rotate) { + automapmode ^= am_rotate; + plr->message = (automapmode & am_rotate) ? s_AMSTR_ROTATEON : s_AMSTR_ROTATEOFF; + } + else if (ch == key_map_overlay) { + automapmode ^= am_overlay; + plr->message = (automapmode & am_overlay) ? s_AMSTR_OVERLAYON : s_AMSTR_OVERLAYOFF; + } + else // phares + { + rc = false; + } + } + else if (ev->type == ev_keyup) + { + rc = false; + ch = ev->data1; + if (ch == key_map_right) + { + if (!(automapmode & am_follow)) + m_paninc.x = 0; + } + else if (ch == key_map_left) + { + if (!(automapmode & am_follow)) + m_paninc.x = 0; + } + else if (ch == key_map_up) + { + if (!(automapmode & am_follow)) + m_paninc.y = 0; + } + else if (ch == key_map_down) + { + if (!(automapmode & am_follow)) + m_paninc.y = 0; + } + else if ((ch == key_map_zoomout) || (ch == key_map_zoomin)) + { + mtof_zoommul = FRACUNIT; + ftom_zoommul = FRACUNIT; + } + } + return rc; +} + +// +// AM_rotate() +// +// Rotation in 2D. +// Used to rotate player arrow line character. +// +// Passed the coordinates of a point, and an angle +// Returns the coordinates rotated by the angle +// +// CPhipps - made static & enhanced for automap rotation + +static void AM_rotate(fixed_t* x, fixed_t* y, angle_t a, fixed_t xorig, fixed_t yorig) +{ + fixed_t tmpx; + + //e6y + xorig>>=FRACTOMAPBITS; + yorig>>=FRACTOMAPBITS; + + tmpx = + FixedMul(*x - xorig,finecosine[a>>ANGLETOFINESHIFT]) + - FixedMul(*y - yorig,finesine[a>>ANGLETOFINESHIFT]); + + *y = yorig + + FixedMul(*x - xorig,finesine[a>>ANGLETOFINESHIFT]) + + FixedMul(*y - yorig,finecosine[a>>ANGLETOFINESHIFT]); + + *x = tmpx + xorig; +} + +// +// AM_changeWindowScale() +// +// Automap zooming +// +// Passed nothing, returns nothing +// +static void AM_changeWindowScale(void) +{ + // Change the scaling multipliers + scale_mtof = FixedMul(scale_mtof, mtof_zoommul); + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + + if (scale_mtof < min_scale_mtof) + AM_minOutWindowScale(); + else if (scale_mtof > max_scale_mtof) + AM_maxOutWindowScale(); + else + AM_activateNewScale(); +} + +// +// AM_doFollowPlayer() +// +// Turn on follow mode - the map scrolls opposite to player motion +// +// Passed nothing, returns nothing +// +static void AM_doFollowPlayer(void) +{ + if (f_oldloc.x != plr->mo->x || f_oldloc.y != plr->mo->y) + { + m_x = FTOM(MTOF(plr->mo->x >> FRACTOMAPBITS)) - m_w/2;//e6y + m_y = FTOM(MTOF(plr->mo->y >> FRACTOMAPBITS)) - m_h/2;//e6y + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; + f_oldloc.x = plr->mo->x; + f_oldloc.y = plr->mo->y; + } +} + +// +// AM_Ticker() +// +// Updates on gametic - enter follow mode, zoom, or change map location +// +// Passed nothing, returns nothing +// +void AM_Ticker (void) +{ + if (!(automapmode & am_active)) + return; + + if (automapmode & am_follow) + AM_doFollowPlayer(); + + // Change the zoom if necessary + if (ftom_zoommul != FRACUNIT) + AM_changeWindowScale(); + + // Change x,y location + if (m_paninc.x || m_paninc.y) + AM_changeWindowLoc(); +} + +// +// AM_clipMline() +// +// Automap clipping of lines. +// +// Based on Cohen-Sutherland clipping algorithm but with a slightly +// faster reject and precalculated slopes. If the speed is needed, +// use a hash algorithm to handle the common cases. +// +// Passed the line's coordinates on map and in the frame buffer performs +// clipping on them in the lines frame coordinates. +// Returns true if any part of line was not clipped +// +static boolean AM_clipMline +( mline_t* ml, + fline_t* fl ) +{ + enum + { + LEFT =1, + RIGHT =2, + BOTTOM =4, + TOP =8 + }; + + register int outcode1 = 0; + register int outcode2 = 0; + register int outside; + + fpoint_t tmp; + int dx; + int dy; + + +#define DOOUTCODE(oc, mx, my) \ + (oc) = 0; \ + if ((my) < 0) (oc) |= TOP; \ + else if ((my) >= f_h) (oc) |= BOTTOM; \ + if ((mx) < 0) (oc) |= LEFT; \ + else if ((mx) >= f_w) (oc) |= RIGHT; + + + // do trivial rejects and outcodes + if (ml->a.y > m_y2) + outcode1 = TOP; + else if (ml->a.y < m_y) + outcode1 = BOTTOM; + + if (ml->b.y > m_y2) + outcode2 = TOP; + else if (ml->b.y < m_y) + outcode2 = BOTTOM; + + if (outcode1 & outcode2) + return false; // trivially outside + + if (ml->a.x < m_x) + outcode1 |= LEFT; + else if (ml->a.x > m_x2) + outcode1 |= RIGHT; + + if (ml->b.x < m_x) + outcode2 |= LEFT; + else if (ml->b.x > m_x2) + outcode2 |= RIGHT; + + if (outcode1 & outcode2) + return false; // trivially outside + + // transform to frame-buffer coordinates. + fl->a.x = CXMTOF(ml->a.x); + fl->a.y = CYMTOF(ml->a.y); + fl->b.x = CXMTOF(ml->b.x); + fl->b.y = CYMTOF(ml->b.y); + + DOOUTCODE(outcode1, fl->a.x, fl->a.y); + DOOUTCODE(outcode2, fl->b.x, fl->b.y); + + if (outcode1 & outcode2) + return false; + + while (outcode1 | outcode2) + { + // may be partially inside box + // find an outside point + if (outcode1) + outside = outcode1; + else + outside = outcode2; + + // clip to each side + if (outside & TOP) + { + dy = fl->a.y - fl->b.y; + dx = fl->b.x - fl->a.x; + tmp.x = fl->a.x + (dx*(fl->a.y))/dy; + tmp.y = 0; + } + else if (outside & BOTTOM) + { + dy = fl->a.y - fl->b.y; + dx = fl->b.x - fl->a.x; + tmp.x = fl->a.x + (dx*(fl->a.y-f_h))/dy; + tmp.y = f_h-1; + } + else if (outside & RIGHT) + { + dy = fl->b.y - fl->a.y; + dx = fl->b.x - fl->a.x; + tmp.y = fl->a.y + (dy*(f_w-1 - fl->a.x))/dx; + tmp.x = f_w-1; + } + else if (outside & LEFT) + { + dy = fl->b.y - fl->a.y; + dx = fl->b.x - fl->a.x; + tmp.y = fl->a.y + (dy*(-fl->a.x))/dx; + tmp.x = 0; + } + + if (outside == outcode1) + { + fl->a = tmp; + DOOUTCODE(outcode1, fl->a.x, fl->a.y); + } + else + { + fl->b = tmp; + DOOUTCODE(outcode2, fl->b.x, fl->b.y); + } + + if (outcode1 & outcode2) + return false; // trivially outside + } + + return true; +} +#undef DOOUTCODE + +// +// AM_drawMline() +// +// Clip lines, draw visible parts of lines. +// +// Passed the map coordinates of the line, and the color to draw it +// Color -1 is special and prevents drawing. Color 247 is special and +// is translated to black, allowing Color 0 to represent feature disable +// in the defaults file. +// Returns nothing. +// +static void AM_drawMline +( mline_t* ml, + int color ) +{ + static fline_t fl; + + if (color==-1) // jff 4/3/98 allow not drawing any sort of line + return; // by setting its color to -1 + if (color==247) // jff 4/3/98 if color is 247 (xparent), use black + color=0; + + if (AM_clipMline(ml, &fl)) + V_DrawLine(&fl, color); // draws it on frame buffer using fb coords +} + +// +// AM_drawGrid() +// +// Draws blockmap aligned grid lines. +// +// Passed the color to draw the grid lines +// Returns nothing +// +static void AM_drawGrid(int color) +{ + fixed_t x, y; + fixed_t start, end; + mline_t ml; + + // Figure out start of vertical gridlines + start = m_x; + if ((start-bmaporgx)%(MAPBLOCKUNITS<> LockedKeyShift; + if (!type || type==7) + return 3; //any or all keys + else return (type-1)%3; + } + switch (type) // closed keyed door + { + case 26: case 32: case 99: case 133: + /*bluekey*/ + return 1; + case 27: case 34: case 136: case 137: + /*yellowkey*/ + return 2; + case 28: case 33: case 134: case 135: + /*redkey*/ + return 0; + default: + return -1; //not a keyed door + } +} + +// +// Determines visible lines, draws them. +// This is LineDef based, not LineSeg based. +// +// jff 1/5/98 many changes in this routine +// backward compatibility not needed, so just changes, no ifs +// addition of clauses for: +// doors opening, keyed door id, secret sectors, +// teleports, exit lines, key things +// ability to suppress any of added features or lines with no height changes +// +// support for gamma correction in automap abandoned +// +// jff 4/3/98 changed mapcolor_xxxx=0 as control to disable feature +// jff 4/3/98 changed mapcolor_xxxx=-1 to disable drawing line completely +// +static void AM_drawWalls(void) +{ + int i; + static mline_t l; + + // draw the unclipped visible portions of all lines + for (i=0;ix >> FRACTOMAPBITS;//e6y + l.a.y = lines[i].v1->y >> FRACTOMAPBITS;//e6y + l.b.x = lines[i].v2->x >> FRACTOMAPBITS;//e6y + l.b.y = lines[i].v2->y >> FRACTOMAPBITS;//e6y + + if (automapmode & am_rotate) { + AM_rotate(&l.a.x, &l.a.y, ANG90-plr->mo->angle, plr->mo->x, plr->mo->y); + AM_rotate(&l.b.x, &l.b.y, ANG90-plr->mo->angle, plr->mo->x, plr->mo->y); + } + + // if line has been seen or IDDT has been used + if (ddt_cheating || (lines[i].flags & ML_MAPPED)) + { + if ((lines[i].flags & ML_DONTDRAW) && !ddt_cheating) + continue; + { + /* cph - show keyed doors and lines */ + int amd; + if ((mapcolor_bdor || mapcolor_ydor || mapcolor_rdor) && + !(lines[i].flags & ML_SECRET) && /* non-secret */ + (amd = AM_DoorColor(lines[i].special)) != -1 + ) + { + { + switch (amd) /* closed keyed door */ + { + case 1: + /*bluekey*/ + AM_drawMline(&l, + mapcolor_bdor? mapcolor_bdor : mapcolor_cchg); + continue; + case 2: + /*yellowkey*/ + AM_drawMline(&l, + mapcolor_ydor? mapcolor_ydor : mapcolor_cchg); + continue; + case 0: + /*redkey*/ + AM_drawMline(&l, + mapcolor_rdor? mapcolor_rdor : mapcolor_cchg); + continue; + case 3: + /*any or all*/ + AM_drawMline(&l, + mapcolor_clsd? mapcolor_clsd : mapcolor_cchg); + continue; + } + } + } + } + if /* jff 4/23/98 add exit lines to automap */ + ( + mapcolor_exit && + ( + lines[i].special==11 || + lines[i].special==52 || + lines[i].special==197 || + lines[i].special==51 || + lines[i].special==124 || + lines[i].special==198 + ) + ) { + AM_drawMline(&l, mapcolor_exit); /* exit line */ + continue; + } + + if (!lines[i].backsector) + { + // jff 1/10/98 add new color for 1S secret sector boundary + if (mapcolor_secr && //jff 4/3/98 0 is disable + ( + ( + map_secret_after && + P_WasSecret(lines[i].frontsector) && + !P_IsSecret(lines[i].frontsector) + ) + || + ( + !map_secret_after && + P_WasSecret(lines[i].frontsector) + ) + ) + ) + AM_drawMline(&l, mapcolor_secr); // line bounding secret sector + else //jff 2/16/98 fixed bug + AM_drawMline(&l, mapcolor_wall); // special was cleared + } + else /* now for 2S lines */ + { + // jff 1/10/98 add color change for all teleporter types + if + ( + mapcolor_tele && !(lines[i].flags & ML_SECRET) && + (lines[i].special == 39 || lines[i].special == 97 || + lines[i].special == 125 || lines[i].special == 126) + ) + { // teleporters + AM_drawMline(&l, mapcolor_tele); + } + else if (lines[i].flags & ML_SECRET) // secret door + { + AM_drawMline(&l, mapcolor_wall); // wall color + } + else if + ( + mapcolor_clsd && + !(lines[i].flags & ML_SECRET) && // non-secret closed door + ((lines[i].backsector->floorheight==lines[i].backsector->ceilingheight) || + (lines[i].frontsector->floorheight==lines[i].frontsector->ceilingheight)) + ) + { + AM_drawMline(&l, mapcolor_clsd); // non-secret closed door + } //jff 1/6/98 show secret sector 2S lines + else if + ( + mapcolor_secr && //jff 2/16/98 fixed bug + ( // special was cleared after getting it + (map_secret_after && + ( + (P_WasSecret(lines[i].frontsector) + && !P_IsSecret(lines[i].frontsector)) || + (P_WasSecret(lines[i].backsector) + && !P_IsSecret(lines[i].backsector)) + ) + ) + || //jff 3/9/98 add logic to not show secret til after entered + ( // if map_secret_after is true + !map_secret_after && + (P_WasSecret(lines[i].frontsector) || + P_WasSecret(lines[i].backsector)) + ) + ) + ) + { + AM_drawMline(&l, mapcolor_secr); // line bounding secret sector + } //jff 1/6/98 end secret sector line change + else if (lines[i].backsector->floorheight != + lines[i].frontsector->floorheight) + { + AM_drawMline(&l, mapcolor_fchg); // floor level change + } + else if (lines[i].backsector->ceilingheight != + lines[i].frontsector->ceilingheight) + { + AM_drawMline(&l, mapcolor_cchg); // ceiling level change + } + else if (mapcolor_flat && ddt_cheating) + { + AM_drawMline(&l, mapcolor_flat); //2S lines that appear only in IDDT + } + } + } // now draw the lines only visible because the player has computermap + else if (plr->powers[pw_allmap]) // computermap visible lines + { + if (!(lines[i].flags & ML_DONTDRAW)) // invisible flag lines do not show + { + if + ( + mapcolor_flat + || + !lines[i].backsector + || + lines[i].backsector->floorheight + != lines[i].frontsector->floorheight + || + lines[i].backsector->ceilingheight + != lines[i].frontsector->ceilingheight + ) + AM_drawMline(&l, mapcolor_unsn); + } + } + } +} + +// +// AM_drawLineCharacter() +// +// Draws a vector graphic according to numerous parameters +// +// Passed the structure defining the vector graphic shape, the number +// of vectors in it, the scale to draw it at, the angle to draw it at, +// the color to draw it with, and the map coordinates to draw it at. +// Returns nothing +// +static void AM_drawLineCharacter +( mline_t* lineguy, + int lineguylines, + fixed_t scale, + angle_t angle, + int color, + fixed_t x, + fixed_t y ) +{ + int i; + mline_t l; + + if (automapmode & am_rotate) angle -= plr->mo->angle - ANG90; // cph + + for (i=0;imo->angle, + mapcolor_sngl, //jff color + plr->mo->x >> FRACTOMAPBITS,//e6y + plr->mo->y >> FRACTOMAPBITS//e6y + ); + else + AM_drawLineCharacter + ( + player_arrow, + NUMPLYRLINES, + 0, + plr->mo->angle, + mapcolor_sngl, //jff color + plr->mo->x >> FRACTOMAPBITS,//e6y + plr->mo->y >> FRACTOMAPBITS);//e6y + return; + } + + for (i=0;imo->x >> FRACTOMAPBITS, y = p->mo->y >> FRACTOMAPBITS;//e6y + if (automapmode & am_rotate) + AM_rotate(&x, &y, ANG90-plr->mo->angle, plr->mo->x, plr->mo->y); + + AM_drawLineCharacter (player_arrow, NUMPLYRLINES, 0, p->mo->angle, + p->powers[pw_invisibility] ? 246 /* *close* to black */ + : mapcolor_plyr[i], //jff 1/6/98 use default color + x, y); + } + } +} + +// +// AM_drawThings() +// +// Draws the things on the automap in double IDDT cheat mode +// +// Passed colors and colorrange, no longer used +// Returns nothing +// +static void AM_drawThings(void) +{ + int i; + mobj_t* t; + + // for all sectors + for (i=0;ix >> FRACTOMAPBITS, y = t->y >> FRACTOMAPBITS;//e6y + + if (automapmode & am_rotate) + AM_rotate(&x, &y, ANG90-plr->mo->angle, plr->mo->x, plr->mo->y); + + //jff 1/5/98 case over doomednum of thing being drawn + if (mapcolor_rkey || mapcolor_ykey || mapcolor_bkey) + { + switch(t->info->doomednum) + { + //jff 1/5/98 treat keys special + case 38: case 13: //jff red key + AM_drawLineCharacter + ( + cross_mark, + NUMCROSSMARKLINES, + 16<angle, + mapcolor_rkey!=-1? mapcolor_rkey : mapcolor_sprt, + x, y + ); + t = t->snext; + continue; + case 39: case 6: //jff yellow key + AM_drawLineCharacter + ( + cross_mark, + NUMCROSSMARKLINES, + 16<angle, + mapcolor_ykey!=-1? mapcolor_ykey : mapcolor_sprt, + x, y + ); + t = t->snext; + continue; + case 40: case 5: //jff blue key + AM_drawLineCharacter + ( + cross_mark, + NUMCROSSMARKLINES, + 16<angle, + mapcolor_bkey!=-1? mapcolor_bkey : mapcolor_sprt, + x, y + ); + t = t->snext; + continue; + default: + break; + } + } + //jff 1/5/98 end added code for keys + //jff previously entire code + AM_drawLineCharacter + ( + thintriangle_guy, + NUMTHINTRIANGLEGUYLINES, + 16<angle, + t->flags & MF_FRIEND && !t->player ? mapcolor_frnd : + /* cph 2006/07/30 - Show count-as-kills in red. */ + ((t->flags & (MF_COUNTKILL | MF_CORPSE)) == MF_COUNTKILL) ? mapcolor_enemy : + /* bbm 2/28/03 Show countable items in yellow. */ + t->flags & MF_COUNTITEM ? mapcolor_item : mapcolor_sprt, + x, y + ); + t = t->snext; + } + } +} + +// +// AM_drawMarks() +// +// Draw the marked locations on the automap +// +// Passed nothing, returns nothing +// +// killough 2/22/98: +// Rewrote AM_drawMarks(). Removed limit on marks. +// +static void AM_drawMarks(void) +{ + int i; + for (i=0;imo->angle, plr->mo->x, plr->mo->y); + + fx = CXMTOF(fx); fy = CYMTOF(fy); + + do + { + int d = j % 10; + if (d==1) // killough 2/22/98: less spacing for '1' + fx++; + + if (fx >= f_x && fx < f_w - w && fy >= f_y && fy < f_h - h) { + // cph - construct patch name and draw marker + char namebuf[] = { 'A', 'M', 'M', 'N', 'U', 'M', '0'+d, 0 }; + + V_DrawNamePatch(fx, fy, FB, namebuf, CR_DEFAULT, VPT_NONE); + } + fx -= w-1; // killough 2/22/98: 1 space backwards + j /= 10; + } + while (j>0); + } +} + +// +// AM_drawCrosshair() +// +// Draw the single point crosshair representing map center +// +// Passed the color to draw the pixel with +// Returns nothing +// +// CPhipps - made static inline, and use the general pixel plotter function + +inline static void AM_drawCrosshair(int color) +{ + fline_t line; + + line.a.x = (f_w/2)-1; + line.a.y = (f_h/2); + line.b.x = (f_w/2)+1; + line.b.y = (f_h/2); + V_DrawLine(&line, color); + + line.a.x = (f_w/2); + line.a.y = (f_h/2)-1; + line.b.x = (f_w/2); + line.b.y = (f_h/2)+1; + V_DrawLine(&line, color); +} + +// +// AM_Drawer() +// +// Draws the entire automap +// +// Passed nothing, returns nothing +// +void AM_Drawer (void) +{ + // CPhipps - all automap modes put into one enum + if (!(automapmode & am_active)) return; + + if (!(automapmode & am_overlay)) // cph - If not overlay mode, clear background for the automap + V_FillRect(FB, f_x, f_y, f_w, f_h, (byte)mapcolor_back); //jff 1/5/98 background default color + if (automapmode & am_grid) + AM_drawGrid(mapcolor_grid); //jff 1/7/98 grid default color + AM_drawWalls(); + AM_drawPlayers(); + if (ddt_cheating==2) + AM_drawThings(); //jff 1/5/98 default double IDDT sprite + AM_drawCrosshair(mapcolor_hair); //jff 1/7/98 default crosshair color + + AM_drawMarks(); +} diff --git a/components/doom/prboom/am_map.h b/components/doom/prboom/am_map.h new file mode 100644 index 0000000..850c9ce --- /dev/null +++ b/components/doom/prboom/am_map.h @@ -0,0 +1,111 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * AutoMap module. + * + *-----------------------------------------------------------------------------*/ + +#ifndef __AMMAP_H__ +#define __AMMAP_H__ + +#include "d_event.h" + +#define MAPBITS 12 +#define FRACTOMAPBITS (FRACBITS-MAPBITS) + +// Used by ST StatusBar stuff. +#define AM_MSGHEADER (('a'<<24)+('m'<<16)) +#define AM_MSGENTERED (AM_MSGHEADER | ('e'<<8)) +#define AM_MSGEXITED (AM_MSGHEADER | ('x'<<8)) + +// Called by main loop. +boolean AM_Responder (event_t* ev); + +// Called by main loop. +void AM_Ticker (void); + +// Called by main loop, +// called instead of view drawer if automap active. +void AM_Drawer (void); + +// Called to force the automap to quit +// if the level is completed while it is up. +void AM_Stop (void); + +// killough 2/22/98: for saving automap information in savegame: + +extern void AM_Start(void); + +//jff 4/16/98 make externally available + +extern void AM_clearMarks(void); + +typedef struct +{ + fixed_t x,y; +} mpoint_t; + +extern mpoint_t *markpoints; +extern int markpointnum, markpointnum_max; + +// end changes -- killough 2/22/98 + +// killough 5/2/98: moved from m_misc.c + +//jff 1/7/98 automap colors added +extern int mapcolor_back; // map background +extern int mapcolor_grid; // grid lines color +extern int mapcolor_wall; // normal 1s wall color +extern int mapcolor_fchg; // line at floor height change color +extern int mapcolor_cchg; // line at ceiling height change color +extern int mapcolor_clsd; // line at sector with floor=ceiling color +extern int mapcolor_rkey; // red key color +extern int mapcolor_bkey; // blue key color +extern int mapcolor_ykey; // yellow key color +extern int mapcolor_rdor; // red door color (diff from keys to allow option) +extern int mapcolor_bdor; // blue door color (of enabling one not other) +extern int mapcolor_ydor; // yellow door color +extern int mapcolor_tele; // teleporter line color +extern int mapcolor_secr; // secret sector boundary color +//jff 4/23/98 +extern int mapcolor_exit; // exit line +extern int mapcolor_unsn; // computer map unseen line color +extern int mapcolor_flat; // line with no floor/ceiling changes +extern int mapcolor_sprt; // general sprite color +extern int mapcolor_item; // item sprite color +extern int mapcolor_enemy; // enemy sprite color +extern int mapcolor_frnd; // friendly sprite color +extern int mapcolor_hair; // crosshair color +extern int mapcolor_sngl; // single player arrow color +extern int mapcolor_plyr[4]; // colors for players in multiplayer +extern int mapcolor_me; // consoleplayer's chosen colour +//jff 3/9/98 +extern int map_secret_after; // secrets do not appear til after bagged + +#endif diff --git a/components/doom/prboom/config.h b/components/doom/prboom/config.h new file mode 100644 index 0000000..5b039af --- /dev/null +++ b/components/doom/prboom/config.h @@ -0,0 +1,52 @@ +/* Uncomment this to exhaustively run memory checks while the game is running + (this is EXTREMELY slow). */ +/* #undef CHECKHEAP */ + +/* Define for support for MBF helper dogs */ +//#define DOGS 0 + +/* Uncomment this to cause heap dumps to be generated. Only useful if + INSTRUMENTED is also defined. */ +/* #undef HEAPDUMP */ + +/* Define on targets supporting 386 assembly */ +/* #undef I386_ASM */ + +/* Define this to see real-time memory allocation statistics, and enable extra + debugging features */ +/* #undef INSTRUMENTED */ + +/* If your platform has a fast version of max, define MAX to it */ +/* #undef MAX */ + +/* If your platform has a fast version of min, define MIN to it */ +/* #undef MIN */ + +/* Name of package */ +#define PACKAGE "prboom-go" + +/* Set to the attribute to apply to struct definitions to make them packed */ +#define PACKEDATTR __attribute__((packed)) + +/* Define to enable internal range checking */ +/* #undef RANGECHECK */ + +/* When defined this causes quick checks which only impose significant + overhead if a posible error is detected. */ +#define SIMPLECHECKS + +/* Defining this causes time stamps to be created each time a lump is locked, + and lumps locked for long periods of time are reported */ +/* #undef TIMEDIAG */ + +/* Version number of package */ +#define VERSION "2.5.0" + +/* Define to remove DEH support */ +#define NODEHSUPPORT + +/* Define to remove true color support (keep 8bit palette only) */ +#define NOTRUECOLOR + +/* Define to bundle prboom.wad (minus the trig tables, which we always include) */ +#define PRBOOMWAD diff --git a/components/doom/prboom/d_client.c b/components/doom/prboom/d_client.c new file mode 100644 index 0000000..9887755 --- /dev/null +++ b/components/doom/prboom/d_client.c @@ -0,0 +1,529 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * Network client. Passes information to/from server, staying + * synchronised. + * Contains the main wait loop, waiting for network input or + * time before doing the next tic. + * Rewritten for LxDoom, but based around bits of the old code. + * + *----------------------------------------------------------------------------- + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#ifdef USE_SDL_NET + #include "SDL.h" +#endif + +#include "doomtype.h" +#include "doomstat.h" +#include "d_net.h" +#include "z_zone.h" + +#include "d_main.h" +#include "g_game.h" +#include "m_menu.h" + +#include "protocol.h" +#include "i_network.h" +#include "i_system.h" +#include "i_main.h" +#include "i_video.h" +#include "m_argv.h" +#include "r_fps.h" +#include "lprintf.h" + +ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS]; +static ticcmd_t* localcmds; +int maketic; +int ticdup = 1; + +static boolean isExtraDDisplay = false; + +#ifdef HAVE_NET +static boolean server; +static int remotetic; // Tic expected from the remote +static int remotesend; // Tic expected by the remote +static unsigned numqueuedpackets; +static packet_header_t** queuedpacket; +static int xtratics = 0; +static int wanted_player_number; + +static void D_QuitNetGame (void); + +void D_InitNetGame (void) +{ + int i; + int numplayers = 1; + + i = M_CheckParm("-net"); + if (i && i < myargc-1) i++; + + if (!(netgame = server = !!i)) { + playeringame[consoleplayer = 0] = true; + // e6y + // for play, recording or playback using "single-player coop" mode. + // Equivalent to using prboom_server with -N 1 + netgame = M_CheckParm("-solo-net"); + } else { + // Get game info from server + packet_header_t *packet = Z_Malloc(1000, PU_STATIC, NULL); + struct setup_packet_s *sinfo = (void*)(packet+1); + struct { packet_header_t head; short pn; } PACKEDATTR initpacket; + + I_InitNetwork(); + udp_socket = I_Socket(0); + I_ConnectToServer(myargv[i]); + + do + { + do { + // Send init packet + initpacket.pn = doom_htons(wanted_player_number); + packet_set(&initpacket.head, PKT_INIT, 0); + I_SendPacket(&initpacket.head, sizeof(initpacket)); + I_WaitForPacket(5000); + } while (!I_GetPacket(packet, 1000)); + if (packet->type == PKT_DOWN) I_Error("Server aborted the game"); + } while (packet->type != PKT_SETUP); + + // Once we have been accepted by the server, we should tell it when we leave + atexit(D_QuitNetGame); + + // Get info from the setup packet + consoleplayer = sinfo->yourplayer; + compatibility_level = sinfo->complevel; + G_Compatibility(); + startskill = sinfo->skill; + deathmatch = sinfo->deathmatch; + startmap = sinfo->level; + startepisode = sinfo->episode; + ticdup = sinfo->ticdup; + xtratics = sinfo->extratic; + G_ReadOptions(sinfo->game_options); + + lprintf(LO_INFO, "\tjoined game as player %d/%d; %d WADs specified\n", + consoleplayer+1, numplayers = sinfo->players, sinfo->numwads); + { + char *p = sinfo->wadnames; + int i = sinfo->numwads; + + while (i--) { + D_AddFile(p, source_net); + p += strlen(p) + 1; + } + } + Z_Free(packet); + } + localcmds = netcmds[displayplayer = consoleplayer]; + for (i=0; itype != PKT_GO); + } + Z_Free(packet); +} + +boolean D_NetGetWad(const char* name) +{ +#if defined(HAVE_WAIT_H) + size_t psize = sizeof(packet_header_t) + strlen(name) + 500; + packet_header_t *packet; + boolean done = false; + + if (!server || strchr(name, '/')) return false; // If it contains path info, reject + + do { + // Send WAD request to remote + packet = Z_Malloc(psize, PU_STATIC, NULL); + packet_set(packet, PKT_WAD, 0); + *(byte*)(packet+1) = consoleplayer; + strcpy(1+(byte*)(packet+1), name); + I_SendPacket(packet, sizeof(packet_header_t) + strlen(name) + 2); + + I_uSleep(10000); + } while (!I_GetPacket(packet, psize) || (packet->type != PKT_WAD)); + Z_Free(packet); + + if (!strcasecmp((void*)(packet+1), name)) { + pid_t pid; + int rv; + byte *p = (byte*)(packet+1) + strlen(name) + 1; + + /* Automatic wad file retrieval using wget (supports http and ftp, using URLs) + * Unix systems have all these commands handy, this kind of thing is easy + * Any windo$e port will have some awkward work replacing these. + */ + /* cph - caution here. This is data from an untrusted source. + * Don't pass it via a shell. */ + if ((pid = fork()) == -1) + perror("fork"); + else if (!pid) { + /* Child chains to wget, does the download */ + execlp("wget", "wget", p, NULL); + } + /* This is the parent, i.e. main LxDoom process */ + wait(&rv); + if (!(done = !access(name, R_OK))) { + if (!strcmp(p+strlen(p)-4, ".zip")) { + p = strrchr(p, '/')+1; + if ((pid = fork()) == -1) + perror("fork"); + else if (!pid) { + /* Child executes decompressor */ + execlp("unzip", "unzip", p, name, NULL); + } + /* Parent waits for the file */ + wait(&rv); + done = !!access(name, R_OK); + } + /* Add more decompression protocols here as desired */ + } + Z_Free(buffer); + } + return done; +#else /* HAVE_WAIT_H */ + return false; +#endif +} + +void NetUpdate(void) +{ + static int lastmadetic; + if (isExtraDDisplay) + return; + if (server) { // Receive network packets + size_t recvlen; + packet_header_t *packet = Z_Malloc(10000, PU_STATIC, NULL); + while ((recvlen = I_GetPacket(packet, 10000))) { + switch(packet->type) { + case PKT_TICS: + { + byte *p = (void*)(packet+1); + int tics = *p++; + unsigned long ptic = doom_ntohl(packet->tic); + if (ptic > (unsigned)remotetic) { // Missed some + packet_set(packet, PKT_RETRANS, remotetic); + *(byte*)(packet+1) = consoleplayer; + I_SendPacket(packet, sizeof(*packet)+1); + } else { + if (ptic + tics <= (unsigned)remotetic) break; // Will not improve things + remotetic = ptic; + while (tics--) { + int players = *p++; + while (players--) { + int n = *p++; + RawToTic(&netcmds[n][remotetic%BACKUPTICS], p); + p += sizeof(ticcmd_t); + } + remotetic++; + } + } + } + break; + case PKT_RETRANS: // Resend request + remotesend = doom_ntohl(packet->tic); + break; + case PKT_DOWN: // Server downed + { + int j; + for (j=0; j 0 ? newtics : 0); + lastmadetic += newtics; + if (ffmap) newtics++; + while (newtics--) { + I_StartTic(); + if (maketic - gametic > BACKUPTICS/2) break; + G_BuildTiccmd(&localcmds[maketic%BACKUPTICS]); + maketic++; + } + if (server && maketic > remotesend) { // Send the tics to the server + int sendtics; + remotesend -= xtratics; + if (remotesend < 0) remotesend = 0; + sendtics = maketic - remotesend; + { + size_t pkt_size = sizeof(packet_header_t) + 2 + sendtics * sizeof(ticcmd_t); + packet_header_t *packet = Z_Malloc(pkt_size, PU_STATIC, NULL); + + packet_set(packet, PKT_TICC, maketic - sendtics); + *(byte*)(packet+1) = sendtics; + *(((byte*)(packet+1))+1) = consoleplayer; + { + void *tic = ((byte*)(packet+1)) +2; + while (sendtics--) { + TicToRaw(tic, &localcmds[remotesend++%BACKUPTICS]); + tic = (byte *)tic + sizeof(ticcmd_t); + } + } + I_SendPacket(packet, pkt_size); + Z_Free(packet); + } + } + } +} +#else + +void D_BuildNewTiccmds(void) +{ + static int lastmadetic; + int newtics = I_GetTime() - lastmadetic; + lastmadetic += newtics; + while (newtics--) + { + I_StartTic(); + if (maketic - gametic > BACKUPTICS/2) break; + G_BuildTiccmd(&localcmds[maketic%BACKUPTICS]); + maketic++; + } +} +#endif + +#ifdef HAVE_NET +/* cph - data passed to this must be in the Doom (little-) endian */ +void D_NetSendMisc(netmisctype_t type, size_t len, void* data) +{ + if (server) { + size_t size = sizeof(packet_header_t) + 3*sizeof(int) + len; + packet_header_t *packet = Z_Malloc(size, PU_STATIC, NULL); + int *p = (void*)(packet+1); + + packet_set(packet, PKT_EXTRA, gametic); + *p++ = LONG(type); *p++ = LONG(consoleplayer); *p++ = LONG(len); + memcpy(p, data, len); + I_SendPacket(packet, size); + + Z_Free(packet); + } +} + +static void CheckQueuedPackets(void) +{ + int i; + for (i=0; (unsigned)itic) <= gametic) + switch (queuedpacket[i]->type) { + case PKT_QUIT: // Player quit the game + { + int pn = *(byte*)(queuedpacket[i]+1); + playeringame[pn] = false; + doom_printf("Player %d left the game\n", pn); + } + break; + case PKT_EXTRA: + { + int *p = (int*)(queuedpacket[i]+1); + size_t len = LONG(*(p+2)); + switch (LONG(*p)) { + case nm_plcolour: + G_ChangedPlayerColour(LONG(*(p+1)), LONG(*(p+3))); + break; + case nm_savegamename: + if (len < SAVEDESCLEN) { + memcpy(savedescription, p+3, len); + // Force terminating 0 in case + savedescription[len] = 0; + } + break; + } + } + break; + default: // Should not be queued + break; + } + + { // Requeue remaining packets + int newnum = 0; + packet_header_t **newqueue = NULL; + + for (i=0; (unsigned)itic) > gametic) { + newqueue = Z_Realloc(newqueue, ++newnum * sizeof *newqueue, + PU_STATIC, NULL); + newqueue[newnum-1] = queuedpacket[i]; + } else Z_Free(queuedpacket[i]); + + Z_Free(queuedpacket); + numqueuedpackets = newnum; queuedpacket = newqueue; + } +} +#endif // HAVE_NET + +void TryRunTics (void) +{ + int runtics; + int entertime = I_GetTime(); + + // Wait for tics to run + while (1) { +#ifdef HAVE_NET + NetUpdate(); + runtics = (server ? remotetic : maketic) - gametic; +#else + D_BuildNewTiccmds(); + runtics = maketic - gametic; +#endif + if (!runtics) { + if (!movement_smooth) { +#ifdef HAVE_NET + if (server) + I_WaitForPacket(ms_to_next_tick); + else +#endif + // I_uSleep(ms_to_next_tick*1000); + } + if (I_GetTime() - entertime > 10) { +#ifdef HAVE_NET + if (server) { + char buf[sizeof(packet_header_t)+1]; + remotesend--; + packet_set((packet_header_t *)buf, PKT_RETRANS, remotetic); + buf[sizeof(buf)-1] = consoleplayer; + I_SendPacket((packet_header_t *)buf, sizeof buf); + } +#endif + M_Ticker(); return; + } + //if ((displaytime) < (tic_vars.next-SDL_GetTicks())) + { + WasRenderedInTryRunTics = true; + if (movement_smooth && gamestate==wipegamestate) + { + isExtraDDisplay = true; + D_Display(); + isExtraDDisplay = false; + } + } + } else break; + } + + while (runtics--) { +#ifdef HAVE_NET + if (server) CheckQueuedPackets(); +#endif + if (advancedemo) + D_DoAdvanceDemo (); + M_Ticker (); + if (movement_smooth) { + tic_vars.start = I_GetTimeMS(); + tic_vars.next = (unsigned int)((tic_vars.start * tic_vars.msec + 1.0f) / tic_vars.msec); + tic_vars.step = tic_vars.next - tic_vars.start; + } + G_Ticker (); + gametic++; +#ifdef HAVE_NET + NetUpdate(); // Keep sending our tics to avoid stalling remote nodes +#endif + } +} + +#ifdef HAVE_NET +static void D_QuitNetGame (void) +{ + byte buf[1 + sizeof(packet_header_t)]; + packet_header_t *packet = (void*)buf; + int i; + + if (!server) return; + buf[sizeof(packet_header_t)] = consoleplayer; + packet_set(packet, PKT_QUIT, gametic); + + for (i=0; i<4; i++) { + I_SendPacket(packet, 1 + sizeof(packet_header_t)); + I_uSleep(10000); + } +} +#endif diff --git a/components/doom/prboom/d_deh.c b/components/doom/prboom/d_deh.c new file mode 100644 index 0000000..3653541 --- /dev/null +++ b/components/doom/prboom/d_deh.c @@ -0,0 +1,3083 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2004 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * Dehacked file support + * New for the TeamTNT "Boom" engine + * + * Author: Ty Halderman, TeamTNT + * + *--------------------------------------------------------------------*/ + +// killough 5/2/98: fixed headers, removed rendunant external declarations: +#include "doomdef.h" +#include "doomtype.h" +#include "doomstat.h" +#include "d_deh.h" +#include "sounds.h" +#include "info.h" +#include "m_cheat.h" +#include "p_inter.h" +#include "p_enemy.h" +#include "g_game.h" +#include "d_think.h" +#include "w_wad.h" + +// CPhipps - modify to use logical output routine +#include "lprintf.h" + +// haleyjd 9/22/99 +int HelperThing = -1; // in P_SpawnMapThing to substitute helper thing + +// variables used in other routines +boolean deh_pars = false; // in wi_stuff to allow pars in modified games + +// #include "d_deh.h" -- we don't do that here but we declare the +// variables. This externalizes everything that there is a string +// set for in the language files. See d_deh.h for detailed comments, +// original English values etc. These are set to the macro values, +// which are set by D_ENGLSH.H or D_FRENCH.H(etc). BEX files are a +// better way of changing these strings globally by language. + +// ==================================================================== +// Any of these can be changed using the bex extensions +#include "dstrings.h" // to get the initial values +/* cph - const's + * - removed redundant "can't XXX in a netgame" strings. + */ +const char* const s_D_DEVSTR = D_DEVSTR; +const char* const s_D_CDROM = D_CDROM; +const char* const s_PRESSKEY = PRESSKEY; +const char* const s_PRESSYN = PRESSYN; +const char* const s_QUITMSG = QUITMSG; +const char* const s_QSAVESPOT = QSAVESPOT; // PRESSKEY; +const char* const s_SAVEDEAD = SAVEDEAD; // PRESSKEY; // remove duplicate y/n +const char* const s_QSPROMPT = QSPROMPT; // PRESSYN; +const char* const s_QLPROMPT = QLPROMPT; // PRESSYN; +const char* const s_NEWGAME = NEWGAME; // PRESSKEY; +const char* const s_RESTARTLEVEL= RESTARTLEVEL; // PRESSYN; +const char* const s_NIGHTMARE = NIGHTMARE; // PRESSYN; +const char* const s_SWSTRING = SWSTRING; // PRESSKEY; +const char* const s_MSGOFF = MSGOFF; +const char* const s_MSGON = MSGON; +const char* const s_NETEND = NETEND; // PRESSKEY; +const char* const s_ENDGAME = ENDGAME; // PRESSYN; // killough 4/4/98: end +const char* const s_DOSY = DOSY; +const char* const s_DETAILHI = DETAILHI; +const char* const s_DETAILLO = DETAILLO; +const char* const s_GAMMALVL0 = GAMMALVL0; +const char* const s_GAMMALVL1 = GAMMALVL1; +const char* const s_GAMMALVL2 = GAMMALVL2; +const char* const s_GAMMALVL3 = GAMMALVL3; +const char* const s_GAMMALVL4 = GAMMALVL4; +const char* const s_EMPTYSTRING = EMPTYSTRING; +const char* const s_GOTARMOR = GOTARMOR; +const char* const s_GOTMEGA = GOTMEGA; +const char* const s_GOTHTHBONUS = GOTHTHBONUS; +const char* const s_GOTARMBONUS = GOTARMBONUS; +const char* const s_GOTSTIM = GOTSTIM; +const char* const s_GOTMEDINEED = GOTMEDINEED; +const char* const s_GOTMEDIKIT = GOTMEDIKIT; +const char* const s_GOTSUPER = GOTSUPER; +const char* const s_GOTBLUECARD = GOTBLUECARD; +const char* const s_GOTYELWCARD = GOTYELWCARD; +const char* const s_GOTREDCARD = GOTREDCARD; +const char* const s_GOTBLUESKUL = GOTBLUESKUL; +const char* const s_GOTYELWSKUL = GOTYELWSKUL; +const char* const s_GOTREDSKULL = GOTREDSKULL; +const char* const s_GOTINVUL = GOTINVUL; +const char* const s_GOTBERSERK = GOTBERSERK; +const char* const s_GOTINVIS = GOTINVIS; +const char* const s_GOTSUIT = GOTSUIT; +const char* const s_GOTMAP = GOTMAP; +const char* const s_GOTVISOR = GOTVISOR; +const char* const s_GOTMSPHERE = GOTMSPHERE; +const char* const s_GOTCLIP = GOTCLIP; +const char* const s_GOTCLIPBOX = GOTCLIPBOX; +const char* const s_GOTROCKET = GOTROCKET; +const char* const s_GOTROCKBOX = GOTROCKBOX; +const char* const s_GOTCELL = GOTCELL; +const char* const s_GOTCELLBOX = GOTCELLBOX; +const char* const s_GOTSHELLS = GOTSHELLS; +const char* const s_GOTSHELLBOX = GOTSHELLBOX; +const char* const s_GOTBACKPACK = GOTBACKPACK; +const char* const s_GOTBFG9000 = GOTBFG9000; +const char* const s_GOTCHAINGUN = GOTCHAINGUN; +const char* const s_GOTCHAINSAW = GOTCHAINSAW; +const char* const s_GOTLAUNCHER = GOTLAUNCHER; +const char* const s_GOTPLASMA = GOTPLASMA; +const char* const s_GOTSHOTGUN = GOTSHOTGUN; +const char* const s_GOTSHOTGUN2 = GOTSHOTGUN2; +const char* const s_PD_BLUEO = PD_BLUEO; +const char* const s_PD_REDO = PD_REDO; +const char* const s_PD_YELLOWO = PD_YELLOWO; +const char* const s_PD_BLUEK = PD_BLUEK; +const char* const s_PD_REDK = PD_REDK; +const char* const s_PD_YELLOWK = PD_YELLOWK; +const char* const s_PD_BLUEC = PD_BLUEC; +const char* const s_PD_REDC = PD_REDC; +const char* const s_PD_YELLOWC = PD_YELLOWC; +const char* const s_PD_BLUES = PD_BLUES; +const char* const s_PD_REDS = PD_REDS; +const char* const s_PD_YELLOWS = PD_YELLOWS; +const char* const s_PD_ANY = PD_ANY; +const char* const s_PD_ALL3 = PD_ALL3; +const char* const s_PD_ALL6 = PD_ALL6; +const char* const s_GGSAVED = GGSAVED; +const char* const s_HUSTR_MSGU = HUSTR_MSGU; +const char* const s_HUSTR_E1M1 = HUSTR_E1M1; +const char* const s_HUSTR_E1M2 = HUSTR_E1M2; +const char* const s_HUSTR_E1M3 = HUSTR_E1M3; +const char* const s_HUSTR_E1M4 = HUSTR_E1M4; +const char* const s_HUSTR_E1M5 = HUSTR_E1M5; +const char* const s_HUSTR_E1M6 = HUSTR_E1M6; +const char* const s_HUSTR_E1M7 = HUSTR_E1M7; +const char* const s_HUSTR_E1M8 = HUSTR_E1M8; +const char* const s_HUSTR_E1M9 = HUSTR_E1M9; +const char* const s_HUSTR_E2M1 = HUSTR_E2M1; +const char* const s_HUSTR_E2M2 = HUSTR_E2M2; +const char* const s_HUSTR_E2M3 = HUSTR_E2M3; +const char* const s_HUSTR_E2M4 = HUSTR_E2M4; +const char* const s_HUSTR_E2M5 = HUSTR_E2M5; +const char* const s_HUSTR_E2M6 = HUSTR_E2M6; +const char* const s_HUSTR_E2M7 = HUSTR_E2M7; +const char* const s_HUSTR_E2M8 = HUSTR_E2M8; +const char* const s_HUSTR_E2M9 = HUSTR_E2M9; +const char* const s_HUSTR_E3M1 = HUSTR_E3M1; +const char* const s_HUSTR_E3M2 = HUSTR_E3M2; +const char* const s_HUSTR_E3M3 = HUSTR_E3M3; +const char* const s_HUSTR_E3M4 = HUSTR_E3M4; +const char* const s_HUSTR_E3M5 = HUSTR_E3M5; +const char* const s_HUSTR_E3M6 = HUSTR_E3M6; +const char* const s_HUSTR_E3M7 = HUSTR_E3M7; +const char* const s_HUSTR_E3M8 = HUSTR_E3M8; +const char* const s_HUSTR_E3M9 = HUSTR_E3M9; +const char* const s_HUSTR_E4M1 = HUSTR_E4M1; +const char* const s_HUSTR_E4M2 = HUSTR_E4M2; +const char* const s_HUSTR_E4M3 = HUSTR_E4M3; +const char* const s_HUSTR_E4M4 = HUSTR_E4M4; +const char* const s_HUSTR_E4M5 = HUSTR_E4M5; +const char* const s_HUSTR_E4M6 = HUSTR_E4M6; +const char* const s_HUSTR_E4M7 = HUSTR_E4M7; +const char* const s_HUSTR_E4M8 = HUSTR_E4M8; +const char* const s_HUSTR_E4M9 = HUSTR_E4M9; +const char* const s_HUSTR_1 = HUSTR_1; +const char* const s_HUSTR_2 = HUSTR_2; +const char* const s_HUSTR_3 = HUSTR_3; +const char* const s_HUSTR_4 = HUSTR_4; +const char* const s_HUSTR_5 = HUSTR_5; +const char* const s_HUSTR_6 = HUSTR_6; +const char* const s_HUSTR_7 = HUSTR_7; +const char* const s_HUSTR_8 = HUSTR_8; +const char* const s_HUSTR_9 = HUSTR_9; +const char* const s_HUSTR_10 = HUSTR_10; +const char* const s_HUSTR_11 = HUSTR_11; +const char* const s_HUSTR_12 = HUSTR_12; +const char* const s_HUSTR_13 = HUSTR_13; +const char* const s_HUSTR_14 = HUSTR_14; +const char* const s_HUSTR_15 = HUSTR_15; +const char* const s_HUSTR_16 = HUSTR_16; +const char* const s_HUSTR_17 = HUSTR_17; +const char* const s_HUSTR_18 = HUSTR_18; +const char* const s_HUSTR_19 = HUSTR_19; +const char* const s_HUSTR_20 = HUSTR_20; +const char* const s_HUSTR_21 = HUSTR_21; +const char* const s_HUSTR_22 = HUSTR_22; +const char* const s_HUSTR_23 = HUSTR_23; +const char* const s_HUSTR_24 = HUSTR_24; +const char* const s_HUSTR_25 = HUSTR_25; +const char* const s_HUSTR_26 = HUSTR_26; +const char* const s_HUSTR_27 = HUSTR_27; +const char* const s_HUSTR_28 = HUSTR_28; +const char* const s_HUSTR_29 = HUSTR_29; +const char* const s_HUSTR_30 = HUSTR_30; +const char* const s_HUSTR_31 = HUSTR_31; +const char* const s_HUSTR_32 = HUSTR_32; +const char* const s_PHUSTR_1 = PHUSTR_1; +const char* const s_PHUSTR_2 = PHUSTR_2; +const char* const s_PHUSTR_3 = PHUSTR_3; +const char* const s_PHUSTR_4 = PHUSTR_4; +const char* const s_PHUSTR_5 = PHUSTR_5; +const char* const s_PHUSTR_6 = PHUSTR_6; +const char* const s_PHUSTR_7 = PHUSTR_7; +const char* const s_PHUSTR_8 = PHUSTR_8; +const char* const s_PHUSTR_9 = PHUSTR_9; +const char* const s_PHUSTR_10 = PHUSTR_10; +const char* const s_PHUSTR_11 = PHUSTR_11; +const char* const s_PHUSTR_12 = PHUSTR_12; +const char* const s_PHUSTR_13 = PHUSTR_13; +const char* const s_PHUSTR_14 = PHUSTR_14; +const char* const s_PHUSTR_15 = PHUSTR_15; +const char* const s_PHUSTR_16 = PHUSTR_16; +const char* const s_PHUSTR_17 = PHUSTR_17; +const char* const s_PHUSTR_18 = PHUSTR_18; +const char* const s_PHUSTR_19 = PHUSTR_19; +const char* const s_PHUSTR_20 = PHUSTR_20; +const char* const s_PHUSTR_21 = PHUSTR_21; +const char* const s_PHUSTR_22 = PHUSTR_22; +const char* const s_PHUSTR_23 = PHUSTR_23; +const char* const s_PHUSTR_24 = PHUSTR_24; +const char* const s_PHUSTR_25 = PHUSTR_25; +const char* const s_PHUSTR_26 = PHUSTR_26; +const char* const s_PHUSTR_27 = PHUSTR_27; +const char* const s_PHUSTR_28 = PHUSTR_28; +const char* const s_PHUSTR_29 = PHUSTR_29; +const char* const s_PHUSTR_30 = PHUSTR_30; +const char* const s_PHUSTR_31 = PHUSTR_31; +const char* const s_PHUSTR_32 = PHUSTR_32; +const char* const s_THUSTR_1 = THUSTR_1; +const char* const s_THUSTR_2 = THUSTR_2; +const char* const s_THUSTR_3 = THUSTR_3; +const char* const s_THUSTR_4 = THUSTR_4; +const char* const s_THUSTR_5 = THUSTR_5; +const char* const s_THUSTR_6 = THUSTR_6; +const char* const s_THUSTR_7 = THUSTR_7; +const char* const s_THUSTR_8 = THUSTR_8; +const char* const s_THUSTR_9 = THUSTR_9; +const char* const s_THUSTR_10 = THUSTR_10; +const char* const s_THUSTR_11 = THUSTR_11; +const char* const s_THUSTR_12 = THUSTR_12; +const char* const s_THUSTR_13 = THUSTR_13; +const char* const s_THUSTR_14 = THUSTR_14; +const char* const s_THUSTR_15 = THUSTR_15; +const char* const s_THUSTR_16 = THUSTR_16; +const char* const s_THUSTR_17 = THUSTR_17; +const char* const s_THUSTR_18 = THUSTR_18; +const char* const s_THUSTR_19 = THUSTR_19; +const char* const s_THUSTR_20 = THUSTR_20; +const char* const s_THUSTR_21 = THUSTR_21; +const char* const s_THUSTR_22 = THUSTR_22; +const char* const s_THUSTR_23 = THUSTR_23; +const char* const s_THUSTR_24 = THUSTR_24; +const char* const s_THUSTR_25 = THUSTR_25; +const char* const s_THUSTR_26 = THUSTR_26; +const char* const s_THUSTR_27 = THUSTR_27; +const char* const s_THUSTR_28 = THUSTR_28; +const char* const s_THUSTR_29 = THUSTR_29; +const char* const s_THUSTR_30 = THUSTR_30; +const char* const s_THUSTR_31 = THUSTR_31; +const char* const s_THUSTR_32 = THUSTR_32; +const char* const s_HUSTR_CHATMACRO1 = HUSTR_CHATMACRO1; +const char* const s_HUSTR_CHATMACRO2 = HUSTR_CHATMACRO2; +const char* const s_HUSTR_CHATMACRO3 = HUSTR_CHATMACRO3; +const char* const s_HUSTR_CHATMACRO4 = HUSTR_CHATMACRO4; +const char* const s_HUSTR_CHATMACRO5 = HUSTR_CHATMACRO5; +const char* const s_HUSTR_CHATMACRO6 = HUSTR_CHATMACRO6; +const char* const s_HUSTR_CHATMACRO7 = HUSTR_CHATMACRO7; +const char* const s_HUSTR_CHATMACRO8 = HUSTR_CHATMACRO8; +const char* const s_HUSTR_CHATMACRO9 = HUSTR_CHATMACRO9; +const char* const s_HUSTR_CHATMACRO0 = HUSTR_CHATMACRO0; +const char* const s_HUSTR_TALKTOSELF1 = HUSTR_TALKTOSELF1; +const char* const s_HUSTR_TALKTOSELF2 = HUSTR_TALKTOSELF2; +const char* const s_HUSTR_TALKTOSELF3 = HUSTR_TALKTOSELF3; +const char* const s_HUSTR_TALKTOSELF4 = HUSTR_TALKTOSELF4; +const char* const s_HUSTR_TALKTOSELF5 = HUSTR_TALKTOSELF5; +const char* const s_HUSTR_MESSAGESENT = HUSTR_MESSAGESENT; +const char* const s_HUSTR_PLRGREEN = HUSTR_PLRGREEN; +const char* const s_HUSTR_PLRINDIGO = HUSTR_PLRINDIGO; +const char* const s_HUSTR_PLRBROWN = HUSTR_PLRBROWN; +const char* const s_HUSTR_PLRRED = HUSTR_PLRRED; +const char* const s_AMSTR_FOLLOWON = AMSTR_FOLLOWON; +const char* const s_AMSTR_FOLLOWOFF = AMSTR_FOLLOWOFF; +const char* const s_AMSTR_GRIDON = AMSTR_GRIDON; +const char* const s_AMSTR_GRIDOFF = AMSTR_GRIDOFF; +const char* const s_AMSTR_MARKEDSPOT = AMSTR_MARKEDSPOT; +const char* const s_AMSTR_MARKSCLEARED = AMSTR_MARKSCLEARED; +// CPhipps - automap rotate & overlay +const char* const s_AMSTR_ROTATEON = AMSTR_ROTATEON; +const char* const s_AMSTR_ROTATEOFF = AMSTR_ROTATEOFF; +const char* const s_AMSTR_OVERLAYON = AMSTR_OVERLAYON; +const char* const s_AMSTR_OVERLAYOFF = AMSTR_OVERLAYOFF; +const char* const s_STSTR_MUS = STSTR_MUS; +const char* const s_STSTR_NOMUS = STSTR_NOMUS; +const char* const s_STSTR_DQDON = STSTR_DQDON; +const char* const s_STSTR_DQDOFF = STSTR_DQDOFF; +const char* const s_STSTR_KFAADDED = STSTR_KFAADDED; +const char* const s_STSTR_FAADDED = STSTR_FAADDED; +const char* const s_STSTR_NCON = STSTR_NCON; +const char* const s_STSTR_NCOFF = STSTR_NCOFF; +const char* const s_STSTR_BEHOLD = STSTR_BEHOLD; +const char* const s_STSTR_BEHOLDX = STSTR_BEHOLDX; +const char* const s_STSTR_CHOPPERS = STSTR_CHOPPERS; +const char* const s_STSTR_CLEV = STSTR_CLEV; +const char* const s_STSTR_COMPON = STSTR_COMPON; +const char* const s_STSTR_COMPOFF = STSTR_COMPOFF; +const char* const s_E1TEXT = E1TEXT; +const char* const s_E2TEXT = E2TEXT; +const char* const s_E3TEXT = E3TEXT; +const char* const s_E4TEXT = E4TEXT; +const char* const s_C1TEXT = C1TEXT; +const char* const s_C2TEXT = C2TEXT; +const char* const s_C3TEXT = C3TEXT; +const char* const s_C4TEXT = C4TEXT; +const char* const s_C5TEXT = C5TEXT; +const char* const s_C6TEXT = C6TEXT; +const char* const s_P1TEXT = P1TEXT; +const char* const s_P2TEXT = P2TEXT; +const char* const s_P3TEXT = P3TEXT; +const char* const s_P4TEXT = P4TEXT; +const char* const s_P5TEXT = P5TEXT; +const char* const s_P6TEXT = P6TEXT; +const char* const s_T1TEXT = T1TEXT; +const char* const s_T2TEXT = T2TEXT; +const char* const s_T3TEXT = T3TEXT; +const char* const s_T4TEXT = T4TEXT; +const char* const s_T5TEXT = T5TEXT; +const char* const s_T6TEXT = T6TEXT; +const char* const s_CC_ZOMBIE = CC_ZOMBIE; +const char* const s_CC_SHOTGUN = CC_SHOTGUN; +const char* const s_CC_HEAVY = CC_HEAVY; +const char* const s_CC_IMP = CC_IMP; +const char* const s_CC_DEMON = CC_DEMON; +const char* const s_CC_LOST = CC_LOST; +const char* const s_CC_CACO = CC_CACO; +const char* const s_CC_HELL = CC_HELL; +const char* const s_CC_BARON = CC_BARON; +const char* const s_CC_ARACH = CC_ARACH; +const char* const s_CC_PAIN = CC_PAIN; +const char* const s_CC_REVEN = CC_REVEN; +const char* const s_CC_MANCU = CC_MANCU; +const char* const s_CC_ARCH = CC_ARCH; +const char* const s_CC_SPIDER = CC_SPIDER; +const char* const s_CC_CYBER = CC_CYBER; +const char* const s_CC_HERO = CC_HERO; +// Ty 03/30/98 - new substitutions for background textures +// during int screens +const char* const bgflatE1 = "FLOOR4_8"; // end of DOOM Episode 1 +const char* const bgflatE2 = "SFLR6_1"; // end of DOOM Episode 2 +const char* const bgflatE3 = "MFLR8_4"; // end of DOOM Episode 3 +const char* const bgflatE4 = "MFLR8_3"; // end of DOOM Episode 4 +const char* const bgflat06 = "SLIME16"; // DOOM2 after MAP06 +const char* const bgflat11 = "RROCK14"; // DOOM2 after MAP11 +const char* const bgflat20 = "RROCK07"; // DOOM2 after MAP20 +const char* const bgflat30 = "RROCK17"; // DOOM2 after MAP30 +const char* const bgflat15 = "RROCK13"; // DOOM2 going MAP15 to MAP31 +const char* const bgflat31 = "RROCK19"; // DOOM2 going MAP31 to MAP32 +const char* const bgcastcall = "BOSSBACK"; // Panel behind cast call + +const char* const startup1 = ""; // blank lines are default and are not printed +const char* const startup2 = ""; +const char* const startup3 = ""; +const char* const startup4 = ""; +const char* const startup5 = ""; + +// end d_deh.h variable declarations +// ==================================================================== + +const char* const deh_newlevel = "NEWLEVEL"; // CPhipps - const + +// DOOM shareware/registered/retail (Ultimate) names. +// CPhipps - const**const +const char * const *const mapnames[] = +{ + &s_HUSTR_E1M1, + &s_HUSTR_E1M2, + &s_HUSTR_E1M3, + &s_HUSTR_E1M4, + &s_HUSTR_E1M5, + &s_HUSTR_E1M6, + &s_HUSTR_E1M7, + &s_HUSTR_E1M8, + &s_HUSTR_E1M9, + + &s_HUSTR_E2M1, + &s_HUSTR_E2M2, + &s_HUSTR_E2M3, + &s_HUSTR_E2M4, + &s_HUSTR_E2M5, + &s_HUSTR_E2M6, + &s_HUSTR_E2M7, + &s_HUSTR_E2M8, + &s_HUSTR_E2M9, + + &s_HUSTR_E3M1, + &s_HUSTR_E3M2, + &s_HUSTR_E3M3, + &s_HUSTR_E3M4, + &s_HUSTR_E3M5, + &s_HUSTR_E3M6, + &s_HUSTR_E3M7, + &s_HUSTR_E3M8, + &s_HUSTR_E3M9, + + &s_HUSTR_E4M1, + &s_HUSTR_E4M2, + &s_HUSTR_E4M3, + &s_HUSTR_E4M4, + &s_HUSTR_E4M5, + &s_HUSTR_E4M6, + &s_HUSTR_E4M7, + &s_HUSTR_E4M8, + &s_HUSTR_E4M9, + + &deh_newlevel, // spares? Unused. + &deh_newlevel, + &deh_newlevel, + &deh_newlevel, + &deh_newlevel, + &deh_newlevel, + &deh_newlevel, + &deh_newlevel, + &deh_newlevel +}; + +// CPhipps - const**const +const char * const*const mapnames2[] = // DOOM 2 map names. +{ + &s_HUSTR_1, + &s_HUSTR_2, + &s_HUSTR_3, + &s_HUSTR_4, + &s_HUSTR_5, + &s_HUSTR_6, + &s_HUSTR_7, + &s_HUSTR_8, + &s_HUSTR_9, + &s_HUSTR_10, + &s_HUSTR_11, + + &s_HUSTR_12, + &s_HUSTR_13, + &s_HUSTR_14, + &s_HUSTR_15, + &s_HUSTR_16, + &s_HUSTR_17, + &s_HUSTR_18, + &s_HUSTR_19, + &s_HUSTR_20, + + &s_HUSTR_21, + &s_HUSTR_22, + &s_HUSTR_23, + &s_HUSTR_24, + &s_HUSTR_25, + &s_HUSTR_26, + &s_HUSTR_27, + &s_HUSTR_28, + &s_HUSTR_29, + &s_HUSTR_30, + &s_HUSTR_31, + &s_HUSTR_32, +}; + +// CPhipps - const**const +const char * const*const mapnamesp[] = // Plutonia WAD map names. +{ + &s_PHUSTR_1, + &s_PHUSTR_2, + &s_PHUSTR_3, + &s_PHUSTR_4, + &s_PHUSTR_5, + &s_PHUSTR_6, + &s_PHUSTR_7, + &s_PHUSTR_8, + &s_PHUSTR_9, + &s_PHUSTR_10, + &s_PHUSTR_11, + + &s_PHUSTR_12, + &s_PHUSTR_13, + &s_PHUSTR_14, + &s_PHUSTR_15, + &s_PHUSTR_16, + &s_PHUSTR_17, + &s_PHUSTR_18, + &s_PHUSTR_19, + &s_PHUSTR_20, + + &s_PHUSTR_21, + &s_PHUSTR_22, + &s_PHUSTR_23, + &s_PHUSTR_24, + &s_PHUSTR_25, + &s_PHUSTR_26, + &s_PHUSTR_27, + &s_PHUSTR_28, + &s_PHUSTR_29, + &s_PHUSTR_30, + &s_PHUSTR_31, + &s_PHUSTR_32, +}; + +// CPhipps - const**const +const char * const*const mapnamest[] = // TNT WAD map names. +{ + &s_THUSTR_1, + &s_THUSTR_2, + &s_THUSTR_3, + &s_THUSTR_4, + &s_THUSTR_5, + &s_THUSTR_6, + &s_THUSTR_7, + &s_THUSTR_8, + &s_THUSTR_9, + &s_THUSTR_10, + &s_THUSTR_11, + + &s_THUSTR_12, + &s_THUSTR_13, + &s_THUSTR_14, + &s_THUSTR_15, + &s_THUSTR_16, + &s_THUSTR_17, + &s_THUSTR_18, + &s_THUSTR_19, + &s_THUSTR_20, + + &s_THUSTR_21, + &s_THUSTR_22, + &s_THUSTR_23, + &s_THUSTR_24, + &s_THUSTR_25, + &s_THUSTR_26, + &s_THUSTR_27, + &s_THUSTR_28, + &s_THUSTR_29, + &s_THUSTR_30, + &s_THUSTR_31, + &s_THUSTR_32, +}; + + +#ifndef NODEHSUPPORT + +#define TRUE 1 +#define FALSE 0 + +// killough 10/98: new functions, to allow processing DEH files in-memory +// (e.g. from wads) + +typedef struct { + /* cph 2006/08/06 - + * if lump != NULL, lump is the start of the lump, + * inp is the current read pos. */ + const byte *inp, *lump; + long size; + /* else, !lump, and f is the file being read */ + FILE* f; +} DEHFILE; + +// killough 10/98: emulate IO whether input really comes from a file or not + +static char *dehfgets(char *buf, size_t n, DEHFILE *fp) +{ + if (!fp->lump) // If this is a real file, + return (fgets)(buf, n, fp->f); // return regular fgets + if (!n || !*fp->inp || fp->size<=0) // If no more characters + return NULL; + if (n==1) + fp->size--, *buf = *fp->inp++; + else + { // copy buffer + char *p = buf; + while (n>1 && *fp->inp && fp->size && + (n--, fp->size--, *p++ = *fp->inp++) != '\n') + ; + *p = 0; + } + return buf; // Return buffer pointer +} + +static int dehfeof(DEHFILE *fp) +{ + return !fp->lump ? feof(fp->f) : !*fp->inp || fp->size<=0; +} + +static int dehfgetc(DEHFILE *fp) +{ + return !fp->lump ? fgetc(fp->f) : fp->size > 0 ? + fp->size--, *fp->inp++ : EOF; +} + +// Do this for a lookup--the pointer (loaded above) is cross-referenced +// to a string key that is the same as the define above. We will use +// strdups to set these new values that we read from the file, orphaning +// the original value set above. + +// CPhipps - make strings pointed to const +typedef struct { + const char *const * ppstr; // doubly indirect pointer to string + const char * const lookup; // pointer to lookup string name +} deh_strs; + +/* CPhipps - const, static + * - removed redundant "Can't XXX in a netgame" strings + */ +static const deh_strs deh_strlookup[] = { + {&s_D_DEVSTR,"D_DEVSTR"}, + {&s_D_CDROM,"D_CDROM"}, + {&s_PRESSKEY,"PRESSKEY"}, + {&s_PRESSYN,"PRESSYN"}, + {&s_QUITMSG,"QUITMSG"}, + {&s_QSAVESPOT,"QSAVESPOT"}, + {&s_SAVEDEAD,"SAVEDEAD"}, + /* cph - disabled to prevent format string attacks in WAD files + {&s_QSPROMPT,"QSPROMPT"}, + {&s_QLPROMPT,"QLPROMPT"},*/ + {&s_NEWGAME,"NEWGAME"}, + {&s_RESTARTLEVEL,"RESTARTLEVEL"}, + {&s_NIGHTMARE,"NIGHTMARE"}, + {&s_SWSTRING,"SWSTRING"}, + {&s_MSGOFF,"MSGOFF"}, + {&s_MSGON,"MSGON"}, + {&s_NETEND,"NETEND"}, + {&s_ENDGAME,"ENDGAME"}, + {&s_DOSY,"DOSY"}, + {&s_DETAILHI,"DETAILHI"}, + {&s_DETAILLO,"DETAILLO"}, + {&s_GAMMALVL0,"GAMMALVL0"}, + {&s_GAMMALVL1,"GAMMALVL1"}, + {&s_GAMMALVL2,"GAMMALVL2"}, + {&s_GAMMALVL3,"GAMMALVL3"}, + {&s_GAMMALVL4,"GAMMALVL4"}, + {&s_EMPTYSTRING,"EMPTYSTRING"}, + {&s_GOTARMOR,"GOTARMOR"}, + {&s_GOTMEGA,"GOTMEGA"}, + {&s_GOTHTHBONUS,"GOTHTHBONUS"}, + {&s_GOTARMBONUS,"GOTARMBONUS"}, + {&s_GOTSTIM,"GOTSTIM"}, + {&s_GOTMEDINEED,"GOTMEDINEED"}, + {&s_GOTMEDIKIT,"GOTMEDIKIT"}, + {&s_GOTSUPER,"GOTSUPER"}, + {&s_GOTBLUECARD,"GOTBLUECARD"}, + {&s_GOTYELWCARD,"GOTYELWCARD"}, + {&s_GOTREDCARD,"GOTREDCARD"}, + {&s_GOTBLUESKUL,"GOTBLUESKUL"}, + {&s_GOTYELWSKUL,"GOTYELWSKUL"}, + {&s_GOTREDSKULL,"GOTREDSKULL"}, + {&s_GOTINVUL,"GOTINVUL"}, + {&s_GOTBERSERK,"GOTBERSERK"}, + {&s_GOTINVIS,"GOTINVIS"}, + {&s_GOTSUIT,"GOTSUIT"}, + {&s_GOTMAP,"GOTMAP"}, + {&s_GOTVISOR,"GOTVISOR"}, + {&s_GOTMSPHERE,"GOTMSPHERE"}, + {&s_GOTCLIP,"GOTCLIP"}, + {&s_GOTCLIPBOX,"GOTCLIPBOX"}, + {&s_GOTROCKET,"GOTROCKET"}, + {&s_GOTROCKBOX,"GOTROCKBOX"}, + {&s_GOTCELL,"GOTCELL"}, + {&s_GOTCELLBOX,"GOTCELLBOX"}, + {&s_GOTSHELLS,"GOTSHELLS"}, + {&s_GOTSHELLBOX,"GOTSHELLBOX"}, + {&s_GOTBACKPACK,"GOTBACKPACK"}, + {&s_GOTBFG9000,"GOTBFG9000"}, + {&s_GOTCHAINGUN,"GOTCHAINGUN"}, + {&s_GOTCHAINSAW,"GOTCHAINSAW"}, + {&s_GOTLAUNCHER,"GOTLAUNCHER"}, + {&s_GOTPLASMA,"GOTPLASMA"}, + {&s_GOTSHOTGUN,"GOTSHOTGUN"}, + {&s_GOTSHOTGUN2,"GOTSHOTGUN2"}, + {&s_PD_BLUEO,"PD_BLUEO"}, + {&s_PD_REDO,"PD_REDO"}, + {&s_PD_YELLOWO,"PD_YELLOWO"}, + {&s_PD_BLUEK,"PD_BLUEK"}, + {&s_PD_REDK,"PD_REDK"}, + {&s_PD_YELLOWK,"PD_YELLOWK"}, + {&s_PD_BLUEC,"PD_BLUEC"}, + {&s_PD_REDC,"PD_REDC"}, + {&s_PD_YELLOWC,"PD_YELLOWC"}, + {&s_PD_BLUES,"PD_BLUES"}, + {&s_PD_REDS,"PD_REDS"}, + {&s_PD_YELLOWS,"PD_YELLOWS"}, + {&s_PD_ANY,"PD_ANY"}, + {&s_PD_ALL3,"PD_ALL3"}, + {&s_PD_ALL6,"PD_ALL6"}, + {&s_GGSAVED,"GGSAVED"}, + {&s_HUSTR_MSGU,"HUSTR_MSGU"}, + {&s_HUSTR_E1M1,"HUSTR_E1M1"}, + {&s_HUSTR_E1M2,"HUSTR_E1M2"}, + {&s_HUSTR_E1M3,"HUSTR_E1M3"}, + {&s_HUSTR_E1M4,"HUSTR_E1M4"}, + {&s_HUSTR_E1M5,"HUSTR_E1M5"}, + {&s_HUSTR_E1M6,"HUSTR_E1M6"}, + {&s_HUSTR_E1M7,"HUSTR_E1M7"}, + {&s_HUSTR_E1M8,"HUSTR_E1M8"}, + {&s_HUSTR_E1M9,"HUSTR_E1M9"}, + {&s_HUSTR_E2M1,"HUSTR_E2M1"}, + {&s_HUSTR_E2M2,"HUSTR_E2M2"}, + {&s_HUSTR_E2M3,"HUSTR_E2M3"}, + {&s_HUSTR_E2M4,"HUSTR_E2M4"}, + {&s_HUSTR_E2M5,"HUSTR_E2M5"}, + {&s_HUSTR_E2M6,"HUSTR_E2M6"}, + {&s_HUSTR_E2M7,"HUSTR_E2M7"}, + {&s_HUSTR_E2M8,"HUSTR_E2M8"}, + {&s_HUSTR_E2M9,"HUSTR_E2M9"}, + {&s_HUSTR_E3M1,"HUSTR_E3M1"}, + {&s_HUSTR_E3M2,"HUSTR_E3M2"}, + {&s_HUSTR_E3M3,"HUSTR_E3M3"}, + {&s_HUSTR_E3M4,"HUSTR_E3M4"}, + {&s_HUSTR_E3M5,"HUSTR_E3M5"}, + {&s_HUSTR_E3M6,"HUSTR_E3M6"}, + {&s_HUSTR_E3M7,"HUSTR_E3M7"}, + {&s_HUSTR_E3M8,"HUSTR_E3M8"}, + {&s_HUSTR_E3M9,"HUSTR_E3M9"}, + {&s_HUSTR_E4M1,"HUSTR_E4M1"}, + {&s_HUSTR_E4M2,"HUSTR_E4M2"}, + {&s_HUSTR_E4M3,"HUSTR_E4M3"}, + {&s_HUSTR_E4M4,"HUSTR_E4M4"}, + {&s_HUSTR_E4M5,"HUSTR_E4M5"}, + {&s_HUSTR_E4M6,"HUSTR_E4M6"}, + {&s_HUSTR_E4M7,"HUSTR_E4M7"}, + {&s_HUSTR_E4M8,"HUSTR_E4M8"}, + {&s_HUSTR_E4M9,"HUSTR_E4M9"}, + {&s_HUSTR_1,"HUSTR_1"}, + {&s_HUSTR_2,"HUSTR_2"}, + {&s_HUSTR_3,"HUSTR_3"}, + {&s_HUSTR_4,"HUSTR_4"}, + {&s_HUSTR_5,"HUSTR_5"}, + {&s_HUSTR_6,"HUSTR_6"}, + {&s_HUSTR_7,"HUSTR_7"}, + {&s_HUSTR_8,"HUSTR_8"}, + {&s_HUSTR_9,"HUSTR_9"}, + {&s_HUSTR_10,"HUSTR_10"}, + {&s_HUSTR_11,"HUSTR_11"}, + {&s_HUSTR_12,"HUSTR_12"}, + {&s_HUSTR_13,"HUSTR_13"}, + {&s_HUSTR_14,"HUSTR_14"}, + {&s_HUSTR_15,"HUSTR_15"}, + {&s_HUSTR_16,"HUSTR_16"}, + {&s_HUSTR_17,"HUSTR_17"}, + {&s_HUSTR_18,"HUSTR_18"}, + {&s_HUSTR_19,"HUSTR_19"}, + {&s_HUSTR_20,"HUSTR_20"}, + {&s_HUSTR_21,"HUSTR_21"}, + {&s_HUSTR_22,"HUSTR_22"}, + {&s_HUSTR_23,"HUSTR_23"}, + {&s_HUSTR_24,"HUSTR_24"}, + {&s_HUSTR_25,"HUSTR_25"}, + {&s_HUSTR_26,"HUSTR_26"}, + {&s_HUSTR_27,"HUSTR_27"}, + {&s_HUSTR_28,"HUSTR_28"}, + {&s_HUSTR_29,"HUSTR_29"}, + {&s_HUSTR_30,"HUSTR_30"}, + {&s_HUSTR_31,"HUSTR_31"}, + {&s_HUSTR_32,"HUSTR_32"}, + {&s_PHUSTR_1,"PHUSTR_1"}, + {&s_PHUSTR_2,"PHUSTR_2"}, + {&s_PHUSTR_3,"PHUSTR_3"}, + {&s_PHUSTR_4,"PHUSTR_4"}, + {&s_PHUSTR_5,"PHUSTR_5"}, + {&s_PHUSTR_6,"PHUSTR_6"}, + {&s_PHUSTR_7,"PHUSTR_7"}, + {&s_PHUSTR_8,"PHUSTR_8"}, + {&s_PHUSTR_9,"PHUSTR_9"}, + {&s_PHUSTR_10,"PHUSTR_10"}, + {&s_PHUSTR_11,"PHUSTR_11"}, + {&s_PHUSTR_12,"PHUSTR_12"}, + {&s_PHUSTR_13,"PHUSTR_13"}, + {&s_PHUSTR_14,"PHUSTR_14"}, + {&s_PHUSTR_15,"PHUSTR_15"}, + {&s_PHUSTR_16,"PHUSTR_16"}, + {&s_PHUSTR_17,"PHUSTR_17"}, + {&s_PHUSTR_18,"PHUSTR_18"}, + {&s_PHUSTR_19,"PHUSTR_19"}, + {&s_PHUSTR_20,"PHUSTR_20"}, + {&s_PHUSTR_21,"PHUSTR_21"}, + {&s_PHUSTR_22,"PHUSTR_22"}, + {&s_PHUSTR_23,"PHUSTR_23"}, + {&s_PHUSTR_24,"PHUSTR_24"}, + {&s_PHUSTR_25,"PHUSTR_25"}, + {&s_PHUSTR_26,"PHUSTR_26"}, + {&s_PHUSTR_27,"PHUSTR_27"}, + {&s_PHUSTR_28,"PHUSTR_28"}, + {&s_PHUSTR_29,"PHUSTR_29"}, + {&s_PHUSTR_30,"PHUSTR_30"}, + {&s_PHUSTR_31,"PHUSTR_31"}, + {&s_PHUSTR_32,"PHUSTR_32"}, + {&s_THUSTR_1,"THUSTR_1"}, + {&s_THUSTR_2,"THUSTR_2"}, + {&s_THUSTR_3,"THUSTR_3"}, + {&s_THUSTR_4,"THUSTR_4"}, + {&s_THUSTR_5,"THUSTR_5"}, + {&s_THUSTR_6,"THUSTR_6"}, + {&s_THUSTR_7,"THUSTR_7"}, + {&s_THUSTR_8,"THUSTR_8"}, + {&s_THUSTR_9,"THUSTR_9"}, + {&s_THUSTR_10,"THUSTR_10"}, + {&s_THUSTR_11,"THUSTR_11"}, + {&s_THUSTR_12,"THUSTR_12"}, + {&s_THUSTR_13,"THUSTR_13"}, + {&s_THUSTR_14,"THUSTR_14"}, + {&s_THUSTR_15,"THUSTR_15"}, + {&s_THUSTR_16,"THUSTR_16"}, + {&s_THUSTR_17,"THUSTR_17"}, + {&s_THUSTR_18,"THUSTR_18"}, + {&s_THUSTR_19,"THUSTR_19"}, + {&s_THUSTR_20,"THUSTR_20"}, + {&s_THUSTR_21,"THUSTR_21"}, + {&s_THUSTR_22,"THUSTR_22"}, + {&s_THUSTR_23,"THUSTR_23"}, + {&s_THUSTR_24,"THUSTR_24"}, + {&s_THUSTR_25,"THUSTR_25"}, + {&s_THUSTR_26,"THUSTR_26"}, + {&s_THUSTR_27,"THUSTR_27"}, + {&s_THUSTR_28,"THUSTR_28"}, + {&s_THUSTR_29,"THUSTR_29"}, + {&s_THUSTR_30,"THUSTR_30"}, + {&s_THUSTR_31,"THUSTR_31"}, + {&s_THUSTR_32,"THUSTR_32"}, + {&s_HUSTR_CHATMACRO1,"HUSTR_CHATMACRO1"}, + {&s_HUSTR_CHATMACRO2,"HUSTR_CHATMACRO2"}, + {&s_HUSTR_CHATMACRO3,"HUSTR_CHATMACRO3"}, + {&s_HUSTR_CHATMACRO4,"HUSTR_CHATMACRO4"}, + {&s_HUSTR_CHATMACRO5,"HUSTR_CHATMACRO5"}, + {&s_HUSTR_CHATMACRO6,"HUSTR_CHATMACRO6"}, + {&s_HUSTR_CHATMACRO7,"HUSTR_CHATMACRO7"}, + {&s_HUSTR_CHATMACRO8,"HUSTR_CHATMACRO8"}, + {&s_HUSTR_CHATMACRO9,"HUSTR_CHATMACRO9"}, + {&s_HUSTR_CHATMACRO0,"HUSTR_CHATMACRO0"}, + {&s_HUSTR_TALKTOSELF1,"HUSTR_TALKTOSELF1"}, + {&s_HUSTR_TALKTOSELF2,"HUSTR_TALKTOSELF2"}, + {&s_HUSTR_TALKTOSELF3,"HUSTR_TALKTOSELF3"}, + {&s_HUSTR_TALKTOSELF4,"HUSTR_TALKTOSELF4"}, + {&s_HUSTR_TALKTOSELF5,"HUSTR_TALKTOSELF5"}, + {&s_HUSTR_MESSAGESENT,"HUSTR_MESSAGESENT"}, + {&s_HUSTR_PLRGREEN,"HUSTR_PLRGREEN"}, + {&s_HUSTR_PLRINDIGO,"HUSTR_PLRINDIGO"}, + {&s_HUSTR_PLRBROWN,"HUSTR_PLRBROWN"}, + {&s_HUSTR_PLRRED,"HUSTR_PLRRED"}, + //{c_HUSTR_KEYGREEN,"HUSTR_KEYGREEN"}, + //{c_HUSTR_KEYINDIGO,"HUSTR_KEYINDIGO"}, + //{c_HUSTR_KEYBROWN,"HUSTR_KEYBROWN"}, + //{c_HUSTR_KEYRED,"HUSTR_KEYRED"}, + {&s_AMSTR_FOLLOWON,"AMSTR_FOLLOWON"}, + {&s_AMSTR_FOLLOWOFF,"AMSTR_FOLLOWOFF"}, + {&s_AMSTR_GRIDON,"AMSTR_GRIDON"}, + {&s_AMSTR_GRIDOFF,"AMSTR_GRIDOFF"}, + {&s_AMSTR_MARKEDSPOT,"AMSTR_MARKEDSPOT"}, + {&s_AMSTR_MARKSCLEARED,"AMSTR_MARKSCLEARED"}, + {&s_STSTR_MUS,"STSTR_MUS"}, + {&s_STSTR_NOMUS,"STSTR_NOMUS"}, + {&s_STSTR_DQDON,"STSTR_DQDON"}, + {&s_STSTR_DQDOFF,"STSTR_DQDOFF"}, + {&s_STSTR_KFAADDED,"STSTR_KFAADDED"}, + {&s_STSTR_FAADDED,"STSTR_FAADDED"}, + {&s_STSTR_NCON,"STSTR_NCON"}, + {&s_STSTR_NCOFF,"STSTR_NCOFF"}, + {&s_STSTR_BEHOLD,"STSTR_BEHOLD"}, + {&s_STSTR_BEHOLDX,"STSTR_BEHOLDX"}, + {&s_STSTR_CHOPPERS,"STSTR_CHOPPERS"}, + {&s_STSTR_CLEV,"STSTR_CLEV"}, + {&s_STSTR_COMPON,"STSTR_COMPON"}, + {&s_STSTR_COMPOFF,"STSTR_COMPOFF"}, + {&s_E1TEXT,"E1TEXT"}, + {&s_E2TEXT,"E2TEXT"}, + {&s_E3TEXT,"E3TEXT"}, + {&s_E4TEXT,"E4TEXT"}, + {&s_C1TEXT,"C1TEXT"}, + {&s_C2TEXT,"C2TEXT"}, + {&s_C3TEXT,"C3TEXT"}, + {&s_C4TEXT,"C4TEXT"}, + {&s_C5TEXT,"C5TEXT"}, + {&s_C6TEXT,"C6TEXT"}, + {&s_P1TEXT,"P1TEXT"}, + {&s_P2TEXT,"P2TEXT"}, + {&s_P3TEXT,"P3TEXT"}, + {&s_P4TEXT,"P4TEXT"}, + {&s_P5TEXT,"P5TEXT"}, + {&s_P6TEXT,"P6TEXT"}, + {&s_T1TEXT,"T1TEXT"}, + {&s_T2TEXT,"T2TEXT"}, + {&s_T3TEXT,"T3TEXT"}, + {&s_T4TEXT,"T4TEXT"}, + {&s_T5TEXT,"T5TEXT"}, + {&s_T6TEXT,"T6TEXT"}, + {&s_CC_ZOMBIE,"CC_ZOMBIE"}, + {&s_CC_SHOTGUN,"CC_SHOTGUN"}, + {&s_CC_HEAVY,"CC_HEAVY"}, + {&s_CC_IMP,"CC_IMP"}, + {&s_CC_DEMON,"CC_DEMON"}, + {&s_CC_LOST,"CC_LOST"}, + {&s_CC_CACO,"CC_CACO"}, + {&s_CC_HELL,"CC_HELL"}, + {&s_CC_BARON,"CC_BARON"}, + {&s_CC_ARACH,"CC_ARACH"}, + {&s_CC_PAIN,"CC_PAIN"}, + {&s_CC_REVEN,"CC_REVEN"}, + {&s_CC_MANCU,"CC_MANCU"}, + {&s_CC_ARCH,"CC_ARCH"}, + {&s_CC_SPIDER,"CC_SPIDER"}, + {&s_CC_CYBER,"CC_CYBER"}, + {&s_CC_HERO,"CC_HERO"}, + {&bgflatE1,"BGFLATE1"}, + {&bgflatE2,"BGFLATE2"}, + {&bgflatE3,"BGFLATE3"}, + {&bgflatE4,"BGFLATE4"}, + {&bgflat06,"BGFLAT06"}, + {&bgflat11,"BGFLAT11"}, + {&bgflat20,"BGFLAT20"}, + {&bgflat30,"BGFLAT30"}, + {&bgflat15,"BGFLAT15"}, + {&bgflat31,"BGFLAT31"}, + {&bgcastcall,"BGCASTCALL"}, + // Ty 04/08/98 - added 5 general purpose startup announcement + // strings for hacker use. See m_menu.c + {&startup1,"STARTUP1"}, + {&startup2,"STARTUP2"}, + {&startup3,"STARTUP3"}, + {&startup4,"STARTUP4"}, + {&startup5,"STARTUP5"}, +}; + +static int deh_numstrlookup = sizeof(deh_strlookup)/sizeof(deh_strlookup[0]); + +// Function prototypes +static void lfstrip(char *); // strip the \r and/or \n off of a line +static void rstrip(char *); // strip trailing whitespace +static char * ptr_lstrip(char *); // point past leading whitespace +static boolean deh_GetData(char *, char *, uint_64_t *, char **, FILE *); +static boolean deh_procStringSub(char *, char *, char *, FILE *); +static char * dehReformatStr(char *); + +// Prototypes for block processing functions +// Pointers to these functions are used as the blocks are encountered. + +static void deh_procThing(DEHFILE *fpin, FILE* fpout, char *line); +static void deh_procFrame(DEHFILE *, FILE*, char *); +static void deh_procPointer(DEHFILE *, FILE*, char *); +static void deh_procSounds(DEHFILE *, FILE*, char *); +static void deh_procAmmo(DEHFILE *, FILE*, char *); +static void deh_procWeapon(DEHFILE *, FILE*, char *); +static void deh_procSprite(DEHFILE *, FILE*, char *); +static void deh_procCheat(DEHFILE *, FILE*, char *); +static void deh_procMisc(DEHFILE *, FILE*, char *); +static void deh_procText(DEHFILE *, FILE*, char *); +static void deh_procPars(DEHFILE *, FILE*, char *); +static void deh_procStrings(DEHFILE *, FILE*, char *); +static void deh_procError(DEHFILE *, FILE*, char *); +static void deh_procBexCodePointers(DEHFILE *, FILE*, char *); +static void deh_procHelperThing(DEHFILE *, FILE *, char *); // haleyjd 9/22/99 +// haleyjd: handlers to fully deprecate the DeHackEd text section +static void deh_procBexSounds(DEHFILE *, FILE *, char *); +static void deh_procBexMusic(DEHFILE *, FILE *, char *); +static void deh_procBexSprites(DEHFILE *, FILE *, char *); + +// Structure deh_block is used to hold the block names that can +// be encountered, and the routines to use to decipher them + +typedef struct +{ + const char *key; // a mnemonic block code name // CPhipps - const* + void (*const fptr)(DEHFILE *, FILE*, char *); // handler +} deh_block; + +#define DEH_BUFFERMAX 1024 // input buffer area size, hardcodedfor now +// killough 8/9/98: make DEH_BLOCKMAX self-adjusting +#define DEH_BLOCKMAX (sizeof deh_blocks/sizeof*deh_blocks) // size of array +#define DEH_MAXKEYLEN 32 // as much of any key as we'll look at +#define DEH_MOBJINFOMAX 24 // number of ints in the mobjinfo_t structure (!) + +// Put all the block header values, and the function to be called when that +// one is encountered, in this array: +static const deh_block deh_blocks[] = { // CPhipps - static const + /* 0 */ {"Thing",deh_procThing}, + /* 1 */ {"Frame",deh_procFrame}, + /* 2 */ {"Pointer",deh_procPointer}, + /* 3 */ {"Sound",deh_procSounds}, // Ty 03/16/98 corrected from "Sounds" + /* 4 */ {"Ammo",deh_procAmmo}, + /* 5 */ {"Weapon",deh_procWeapon}, + /* 6 */ {"Sprite",deh_procSprite}, + /* 7 */ {"Cheat",deh_procCheat}, + /* 8 */ {"Misc",deh_procMisc}, + /* 9 */ {"Text",deh_procText}, // -- end of standard "deh" entries, + + // begin BOOM Extensions (BEX) + + /* 10 */ {"[STRINGS]",deh_procStrings}, // new string changes + /* 11 */ {"[PARS]",deh_procPars}, // alternative block marker + /* 12 */ {"[CODEPTR]",deh_procBexCodePointers}, // bex codepointers by mnemonic + /* 13 */ {"[HELPER]", deh_procHelperThing}, // helper thing substitution haleyjd 9/22/99 + /* 14 */ {"[SPRITES]", deh_procBexSprites}, // bex style sprites + /* 15 */ {"[SOUNDS]", deh_procBexSounds}, // bex style sounds + /* 16 */ {"[MUSIC]", deh_procBexMusic}, // bex style music + /* 17 */ {"", deh_procError} // dummy to handle anything else +}; + +// flag to skip included deh-style text, used with INCLUDE NOTEXT directive +static boolean includenotext = false; + +static boolean deh_initialized = false; + +// MOBJINFO - Dehacked block name = "Thing" +// Usage: Thing nn (name) +// These are for mobjinfo_t types. Each is an integer +// within the structure, so we can use index of the string in this +// array to offset by sizeof(int) into the mobjinfo_t array at [nn] +// * things are base zero but dehacked considers them to start at #1. *** +// CPhipps - static const + +static const char *deh_mobjinfo[DEH_MOBJINFOMAX] = +{ + "ID #", // .doomednum + "Initial frame", // .spawnstate + "Hit points", // .spawnhealth + "First moving frame", // .seestate + "Alert sound", // .seesound + "Reaction time", // .reactiontime + "Attack sound", // .attacksound + "Injury frame", // .painstate + "Pain chance", // .painchance + "Pain sound", // .painsound + "Close attack frame", // .meleestate + "Far attack frame", // .missilestate + "Death frame", // .deathstate + "Exploding frame", // .xdeathstate + "Death sound", // .deathsound + "Speed", // .speed + "Width", // .radius + "Height", // .height + "Mass", // .mass + "Missile damage", // .damage + "Action sound", // .activesound + "Bits", // .flags + "Bits2", // .flags + "Respawn frame" // .raisestate +}; + +// Strings that are used to indicate flags ("Bits" in mobjinfo) +// This is an array of bit masks that are related to p_mobj.h +// values, using the smae names without the MF_ in front. +// Ty 08/27/98 new code +// +// killough 10/98: +// +// Convert array to struct to allow multiple values, make array size variable + +#define DEH_MOBJFLAGMAX (sizeof deh_mobjflags/sizeof*deh_mobjflags) + +struct deh_mobjflags_s { + const char *name; // CPhipps - const* + uint_64_t value; +}; + +// CPhipps - static const +static const struct deh_mobjflags_s deh_mobjflags[] = { + {"SPECIAL", MF_SPECIAL}, // call P_Specialthing when touched + {"SOLID", MF_SOLID}, // block movement + {"SHOOTABLE", MF_SHOOTABLE}, // can be hit + {"NOSECTOR", MF_NOSECTOR}, // invisible but touchable + {"NOBLOCKMAP", MF_NOBLOCKMAP}, // inert but displayable + {"AMBUSH", MF_AMBUSH}, // deaf monster + {"JUSTHIT", MF_JUSTHIT}, // will try to attack right back + {"JUSTATTACKED", MF_JUSTATTACKED}, // take at least 1 step before attacking + {"SPAWNCEILING", MF_SPAWNCEILING}, // initially hang from ceiling + {"NOGRAVITY", MF_NOGRAVITY}, // don't apply gravity during play + {"DROPOFF", MF_DROPOFF}, // can jump from high places + {"PICKUP", MF_PICKUP}, // will pick up items + {"NOCLIP", MF_NOCLIP}, // goes through walls + {"SLIDE", MF_SLIDE}, // keep info about sliding along walls + {"FLOAT", MF_FLOAT}, // allow movement to any height + {"TELEPORT", MF_TELEPORT}, // don't cross lines or look at heights + {"MISSILE", MF_MISSILE}, // don't hit same species, explode on block + {"DROPPED", MF_DROPPED}, // dropped, not spawned (like ammo clip) + {"SHADOW", MF_SHADOW}, // use fuzzy draw like spectres + {"NOBLOOD", MF_NOBLOOD}, // puffs instead of blood when shot + {"CORPSE", MF_CORPSE}, // so it will slide down steps when dead + {"INFLOAT", MF_INFLOAT}, // float but not to target height + {"COUNTKILL", MF_COUNTKILL}, // count toward the kills total + {"COUNTITEM", MF_COUNTITEM}, // count toward the items total + {"SKULLFLY", MF_SKULLFLY}, // special handling for flying skulls + {"NOTDMATCH", MF_NOTDMATCH}, // do not spawn in deathmatch + + // killough 10/98: TRANSLATION consists of 2 bits, not 1: + + {"TRANSLATION", MF_TRANSLATION1}, // for Boom bug-compatibility + {"TRANSLATION1", MF_TRANSLATION1}, // use translation table for color (players) + {"TRANSLATION2", MF_TRANSLATION2}, // use translation table for color (players) + {"UNUSED1", MF_TRANSLATION2}, // unused bit # 1 -- For Boom bug-compatibility + {"UNUSED2", MF_UNUSED2}, // unused bit # 2 -- For Boom compatibility + {"UNUSED3", MF_UNUSED3}, // unused bit # 3 -- For Boom compatibility + {"UNUSED4", MF_TRANSLUCENT}, // unused bit # 4 -- For Boom compatibility + {"TRANSLUCENT", MF_TRANSLUCENT}, // apply translucency to sprite (BOOM) + {"TOUCHY", MF_TOUCHY}, // dies on contact with solid objects (MBF) + {"BOUNCES", MF_BOUNCES}, // bounces off floors, ceilings and maybe walls (MBF) + {"FRIEND", MF_FRIEND}, // a friend of the player(s) (MBF) +}; + +// STATE - Dehacked block name = "Frame" and "Pointer" +// Usage: Frame nn +// Usage: Pointer nn (Frame nn) +// These are indexed separately, for lookup to the actual +// function pointers. Here we'll take whatever Dehacked gives +// us and go from there. The (Frame nn) after the pointer is the +// real place to put this value. The "Pointer" value is an xref +// that Dehacked uses and is useless to us. +// * states are base zero and have a dummy #0 (TROO) + +static const char *deh_state[] = // CPhipps - static const* +{ + "Sprite number", // .sprite (spritenum_t) // an enum + "Sprite subnumber", // .frame (long) + "Duration", // .tics (long) + "Next frame", // .nextstate (statenum_t) + // This is set in a separate "Pointer" block from Dehacked + "Codep Frame", // pointer to first use of action (actionf_t) + "Unknown 1", // .misc1 (long) + "Unknown 2" // .misc2 (long) +}; + +// SFXINFO_STRUCT - Dehacked block name = "Sounds" +// Sound effects, typically not changed (redirected, and new sfx put +// into the pwad, but not changed here. Can you tell that Gregdidn't +// know what they were for, mostly? Can you tell that I don't either? +// Mostly I just put these into the same slots as they are in the struct. +// This may not be supported in our -deh option if it doesn't make sense by then. + +// * sounds are base zero but have a dummy #0 + +static const char *deh_sfxinfo[] = // CPhipps - static const* +{ + "Offset", // pointer to a name string, changed in text + "Zero/One", // .singularity (int, one at a time flag) + "Value", // .priority + "Zero 1", // .link (sfxinfo_t*) referenced sound if linked + "Zero 2", // .pitch + "Zero 3", // .volume + "Zero 4", // .data (SAMPLE*) sound data + "Neg. One 1", // .usefulness + "Neg. One 2" // .lumpnum +}; + +// MUSICINFO is not supported in Dehacked. Ignored here. +// * music entries are base zero but have a dummy #0 + +// SPRITE - Dehacked block name = "Sprite" +// Usage = Sprite nn +// Sprite redirection by offset into the text area - unsupported by BOOM +// * sprites are base zero and dehacked uses it that way. + +// static const char *deh_sprite[] = // CPhipps - static const* +// { +// "Offset" // supposed to be the offset into the text section +// }; + +// AMMO - Dehacked block name = "Ammo" +// usage = Ammo n (name) +// Ammo information for the few types of ammo + +static const char *deh_ammo[] = // CPhipps - static const* +{ + "Max ammo", // maxammo[] + "Per ammo" // clipammo[] +}; + +// WEAPONS - Dehacked block name = "Weapon" +// Usage: Weapon nn (name) +// Basically a list of frames and what kind of ammo (see above)it uses. + +static const char *deh_weapon[] = // CPhipps - static const* +{ + "Ammo type", // .ammo + "Deselect frame", // .upstate + "Select frame", // .downstate + "Bobbing frame", // .readystate + "Shooting frame", // .atkstate + "Firing frame" // .flashstate +}; + +// CHEATS - Dehacked block name = "Cheat" +// Usage: Cheat 0 +// Always uses a zero in the dehacked file, for consistency. No meaning. +// These are just plain funky terms compared with id's +// +// killough 4/18/98: integrated into main cheat table now (see st_stuff.c) + +// MISC - Dehacked block name = "Misc" +// Usage: Misc 0 +// Always uses a zero in the dehacked file, for consistency. No meaning. + +static const char *deh_misc[] = // CPhipps - static const* +{ + "Initial Health", // initial_health + "Initial Bullets", // initial_bullets + "Max Health", // maxhealth + "Max Armor", // max_armor + "Green Armor Class", // green_armor_class + "Blue Armor Class", // blue_armor_class + "Max Soulsphere", // max_soul + "Soulsphere Health", // soul_health + "Megasphere Health", // mega_health + "God Mode Health", // god_health + "IDFA Armor", // idfa_armor + "IDFA Armor Class", // idfa_armor_class + "IDKFA Armor", // idkfa_armor + "IDKFA Armor Class", // idkfa_armor_class + "BFG Cells/Shot", // BFGCELLS + "Monsters Infight" // Unknown--not a specific number it seems, but + // the logic has to be here somewhere or + // it'd happen always +}; + +// TEXT - Dehacked block name = "Text" +// Usage: Text fromlen tolen +// Dehacked allows a bit of adjustment to the length (why?) + +// BEX extension [CODEPTR] +// Usage: Start block, then each line is: +// FRAME nnn = PointerMnemonic + +typedef struct { + actionf_t cptr; // actual pointer to the subroutine + const char *lookup; // mnemonic lookup string to be specified in BEX + // CPhipps - const* +} deh_bexptr; + +static const deh_bexptr deh_bexptrs[] = // CPhipps - static const +{ + {A_Light0, "A_Light0"}, + {A_WeaponReady, "A_WeaponReady"}, + {A_Lower, "A_Lower"}, + {A_Raise, "A_Raise"}, + {A_Punch, "A_Punch"}, + {A_ReFire, "A_ReFire"}, + {A_FirePistol, "A_FirePistol"}, + {A_Light1, "A_Light1"}, + {A_FireShotgun, "A_FireShotgun"}, + {A_Light2, "A_Light2"}, + {A_FireShotgun2, "A_FireShotgun2"}, + {A_CheckReload, "A_CheckReload"}, + {A_OpenShotgun2, "A_OpenShotgun2"}, + {A_LoadShotgun2, "A_LoadShotgun2"}, + {A_CloseShotgun2, "A_CloseShotgun2"}, + {A_FireCGun, "A_FireCGun"}, + {A_GunFlash, "A_GunFlash"}, + {A_FireMissile, "A_FireMissile"}, + {A_Saw, "A_Saw"}, + {A_FirePlasma, "A_FirePlasma"}, + {A_BFGsound, "A_BFGsound"}, + {A_FireBFG, "A_FireBFG"}, + {A_BFGSpray, "A_BFGSpray"}, + {A_Explode, "A_Explode"}, + {A_Pain, "A_Pain"}, + {A_PlayerScream, "A_PlayerScream"}, + {A_Fall, "A_Fall"}, + {A_XScream, "A_XScream"}, + {A_Look, "A_Look"}, + {A_Chase, "A_Chase"}, + {A_FaceTarget, "A_FaceTarget"}, + {A_PosAttack, "A_PosAttack"}, + {A_Scream, "A_Scream"}, + {A_SPosAttack, "A_SPosAttack"}, + {A_VileChase, "A_VileChase"}, + {A_VileStart, "A_VileStart"}, + {A_VileTarget, "A_VileTarget"}, + {A_VileAttack, "A_VileAttack"}, + {A_StartFire, "A_StartFire"}, + {A_Fire, "A_Fire"}, + {A_FireCrackle, "A_FireCrackle"}, + {A_Tracer, "A_Tracer"}, + {A_SkelWhoosh, "A_SkelWhoosh"}, + {A_SkelFist, "A_SkelFist"}, + {A_SkelMissile, "A_SkelMissile"}, + {A_FatRaise, "A_FatRaise"}, + {A_FatAttack1, "A_FatAttack1"}, + {A_FatAttack2, "A_FatAttack2"}, + {A_FatAttack3, "A_FatAttack3"}, + {A_BossDeath, "A_BossDeath"}, + {A_CPosAttack, "A_CPosAttack"}, + {A_CPosRefire, "A_CPosRefire"}, + {A_TroopAttack, "A_TroopAttack"}, + {A_SargAttack, "A_SargAttack"}, + {A_HeadAttack, "A_HeadAttack"}, + {A_BruisAttack, "A_BruisAttack"}, + {A_SkullAttack, "A_SkullAttack"}, + {A_Metal, "A_Metal"}, + {A_SpidRefire, "A_SpidRefire"}, + {A_BabyMetal, "A_BabyMetal"}, + {A_BspiAttack, "A_BspiAttack"}, + {A_Hoof, "A_Hoof"}, + {A_CyberAttack, "A_CyberAttack"}, + {A_PainAttack, "A_PainAttack"}, + {A_PainDie, "A_PainDie"}, + {A_KeenDie, "A_KeenDie"}, + {A_BrainPain, "A_BrainPain"}, + {A_BrainScream, "A_BrainScream"}, + {A_BrainDie, "A_BrainDie"}, + {A_BrainAwake, "A_BrainAwake"}, + {A_BrainSpit, "A_BrainSpit"}, + {A_SpawnSound, "A_SpawnSound"}, + {A_SpawnFly, "A_SpawnFly"}, + {A_BrainExplode, "A_BrainExplode"}, + {A_Detonate, "A_Detonate"}, // killough 8/9/98 + {A_Mushroom, "A_Mushroom"}, // killough 10/98 + {A_Die, "A_Die"}, // killough 11/98 + {A_Spawn, "A_Spawn"}, // killough 11/98 + {A_Turn, "A_Turn"}, // killough 11/98 + {A_Face, "A_Face"}, // killough 11/98 + {A_Scratch, "A_Scratch"}, // killough 11/98 + {A_PlaySound, "A_PlaySound"}, // killough 11/98 + {A_RandomJump, "A_RandomJump"}, // killough 11/98 + {A_LineEffect, "A_LineEffect"}, // killough 11/98 + + // This NULL entry must be the last in the list + {NULL, "A_NULL"}, // Ty 05/16/98 +}; + +// to hold startup code pointers from INFO.C +// CPhipps - static +static actionf_t *deh_codeptr; +static char **deh_spritenames; +static char **deh_musicnames; +static char **deh_soundnames; + +// ==================================================================== +// ProcessDehFile +// Purpose: Read and process a DEH or BEX file +// Args: filename -- name of the DEH/BEX file +// outfilename -- output file (DEHOUT.TXT), appended to here +// Returns: void +// +// killough 10/98: +// substantially modified to allow input from wad lumps instead of .deh files. + +void D_ProcessDehFile(const char *filename, const char *outfilename, int lumpnum) +{ + static FILE *fileout; // In case -dehout was used + DEHFILE infile, *filein = &infile; // killough 10/98 + char inbuffer[DEH_BUFFERMAX]; // Place to put the primary infostring + + // Initialize structures only if needed + if (!deh_initialized) + { + lprintf(LO_WARN, "D_ProcessDehFile: Initializing DEH engine...\n"); + + deh_codeptr = calloc(NUMSTATES + 1, sizeof(actionf_t)); + deh_spritenames = calloc(NUMSPRITES + 1, sizeof(char *)); + deh_musicnames = calloc(NUMMUSIC + 1, sizeof(char *)); + deh_soundnames = calloc(NUMSFX + 1, sizeof(char *)); + + // Keep a copy of the original values + for (int i = 0; i < NUMSTATES; i++) + deh_codeptr[i] = states[i].action; + for (int i = 0; i < NUMSPRITES; i++) + deh_spritenames[i] = sprnames[i]; + for(int i = 1; i < NUMMUSIC; i++) + deh_musicnames[i] = S_music[i].name; + for(int i = 1; i < NUMSFX; i++) + deh_soundnames[i] = S_sfx[i].name; + } + + // Open output file if we're writing output + if (outfilename && *outfilename && !fileout) + { + static boolean firstfile = true; // to allow append to output log + if (!strcmp(outfilename, "-")) + fileout = stdout; + else + if (!(fileout=fopen(outfilename, firstfile ? "wt" : "at"))) + { + lprintf(LO_WARN, "Could not open -dehout file %s\n... using stdout.\n", + outfilename); + fileout = stdout; + } + firstfile = false; + } + + // killough 10/98: allow DEH files to come from wad lumps + + if (filename) + { + if (!(infile.f = fopen(filename,"rt"))) + { + lprintf(LO_WARN, "-deh file %s not found\n",filename); + return; // should be checked up front anyway + } + infile.lump = NULL; + } + else // DEH file comes from lump indicated by third argument + { + infile.size = W_LumpLength(lumpnum); + infile.inp = infile.lump = W_CacheLumpNum(lumpnum); + filename = "(WAD)"; + } + + lprintf(LO_INFO, "Loading DEH file %s\n",filename); + lprintf(LO_INFO, "Lump number %d, address: %p\n",lumpnum, infile.lump); + if (fileout) fprintf(fileout,"\nLoading DEH file %s\n\n",filename); + + // loop until end of file + + while (dehfgets(inbuffer,sizeof(inbuffer),filein)) + { + unsigned i; + + lfstrip(inbuffer); + if (fileout) fprintf(fileout,"Line='%s'\n",inbuffer); + if (!*inbuffer || *inbuffer == '#' || *inbuffer == ' ') + continue; /* Blank line or comment line */ + + // -- If DEH_BLOCKMAX is set right, the processing is independently + // -- handled based on data in the deh_blocks[] structure array + + // killough 10/98: INCLUDE code rewritten to allow arbitrary nesting, + // and to greatly simplify code, fix memory leaks, other bugs + + if (!strncasecmp(inbuffer,"INCLUDE",7)) // include a file + { + // preserve state while including a file + // killough 10/98: moved to here + + char *nextfile; + boolean oldnotext = includenotext; // killough 10/98 + + // killough 10/98: exclude if inside wads (only to discourage + // the practice, since the code could otherwise handle it) + + if (infile.lump) + { + if (fileout) + fprintf(fileout, + "No files may be included from wads: %s\n",inbuffer); + continue; + } + + // check for no-text directive, used when including a DEH + // file but using the BEX format to handle strings + + if (!strncasecmp(nextfile = ptr_lstrip(inbuffer+7),"NOTEXT",6)) + includenotext = true, nextfile = ptr_lstrip(nextfile+6); + + if (fileout) + fprintf(fileout,"Branching to include file %s...\n", nextfile); + + // killough 10/98: + // Second argument must be NULL to prevent closing fileout too soon + + D_ProcessDehFile(nextfile,NULL,0); // do the included file + + includenotext = oldnotext; + if (fileout) fprintf(fileout,"...continuing with %s\n",filename); + continue; + } + + for (i=0; i= NUMSTATES) + { + if (fpout) fprintf(fpout,"Bad pointer number %d of %d\n", + indexnum, NUMSTATES); + return; // killough 10/98: fix SegViol + } + strcpy(key,"A_"); // reusing the key area to prefix the mnemonic + strcat(key,ptr_lstrip(mnemonic)); + + found = FALSE; + i= -1; // incremented to start at zero at the top of the loop + do // Ty 05/16/98 - fix loop logic to look for null ending entry + { + ++i; + if (!strcasecmp(key,deh_bexptrs[i].lookup)) + { // Ty 06/01/98 - add to states[].action for new djgcc version + states[indexnum].action = deh_bexptrs[i].cptr; // assign + if (fpout) fprintf(fpout, + " - applied %s from codeptr[%d] to states[%d]\n", + deh_bexptrs[i].lookup,i,indexnum); + found = TRUE; + } + } while (!found && (deh_bexptrs[i].cptr != NULL)); + + if (!found) + if (fpout) fprintf(fpout, + "Invalid frame pointer mnemonic '%s' at %d\n", + mnemonic, indexnum); + } + return; +} + +//--------------------------------------------------------------------------- +// To be on the safe, compatible side, we manually convert DEH bitflags +// to prboom types - POPE +//--------------------------------------------------------------------------- +static uint_64_t getConvertedDEHBits(uint_64_t bits) { + const uint_64_t bitMap[32] = { + /* cf linuxdoom-1.10 p_mobj.h */ + MF_SPECIAL, // 0 Can be picked up - When touched the thing can be picked up. + MF_SOLID, // 1 Obstacle - The thing is solid and will not let you (or others) pass through it + MF_SHOOTABLE, // 2 Shootable - Can be shot. + MF_NOSECTOR, // 3 Total Invisibility - Invisible, but can be touched + MF_NOBLOCKMAP, // 4 Don't use the blocklinks (inert but displayable) + MF_AMBUSH, // 5 Semi deaf - The thing is a deaf monster + MF_JUSTHIT, // 6 In pain - Will try to attack right back after being hit + MF_JUSTATTACKED, // 7 Steps before attack - Will take at least one step before attacking + MF_SPAWNCEILING, // 8 Hangs from ceiling - When the level starts, this thing will be at ceiling height. + MF_NOGRAVITY, // 9 No gravity - Gravity does not affect this thing + MF_DROPOFF, // 10 Travels over cliffs - Monsters normally do not walk off ledges/steps they could not walk up. With this set they can walk off any height of cliff. Usually only used for flying monsters. + MF_PICKUP, // 11 Pick up items - The thing can pick up gettable items. + MF_NOCLIP, // 12 No clipping - Thing can walk through walls. + MF_SLIDE, // 13 Slides along walls - Keep info about sliding along walls (don't really know much about this one). + MF_FLOAT, // 14 Floating - Thing can move to any height + MF_TELEPORT, // 15 Semi no clipping - Don't cross lines or look at teleport heights. (don't really know much about this one either). + MF_MISSILE, // 16 Projectiles - Behaves like a projectile, explodes when hitting something that blocks movement + MF_DROPPED, // 17 Disappearing weapon - Dropped, not spawned (like an ammo clip) I have not had much success in using this one. + MF_SHADOW, // 18 Partial invisibility - Drawn like a spectre. + MF_NOBLOOD, // 19 Puffs (vs. bleeds) - If hit will spawn bullet puffs instead of blood splats. + MF_CORPSE, // 20 Sliding helpless - Will slide down steps when dead. + MF_INFLOAT, // 21 No auto levelling - float but not to target height (?) + MF_COUNTKILL, // 22 Affects kill % - counted as a killable enemy and affects percentage kills on level summary. + MF_COUNTITEM, // 23 Affects item % - affects percentage items gathered on level summary. + MF_SKULLFLY, // 24 Running - special handling for flying skulls. + MF_NOTDMATCH, // 25 Not in deathmatch - do not spawn in deathmatch (like keys) + MF_TRANSLATION1, // 26 Color 1 (grey / red) + MF_TRANSLATION2, // 27 Color 2 (brown / red) + // Convert bit 28 to MF_TOUCHY, not (MF_TRANSLATION1|MF_TRANSLATION2) + // fixes bug #1576151 (part 1) + MF_TOUCHY, // 28 - explodes on contact (MBF) + MF_BOUNCES, // 29 - bounces off walls and floors (MBF) + MF_FRIEND, // 30 - friendly monster helps players (MBF) + MF_TRANSLUCENT // e6y: Translucency via dehacked/bex doesn't work without it + }; + int i; + uint_64_t shiftBits = bits; + uint_64_t convertedBits = 0; + for (i=0; i<32; i++) { + if (shiftBits & 0x1) convertedBits |= bitMap[i]; + shiftBits >>= 1; + } + return convertedBits; +} + +//--------------------------------------------------------------------------- +// See usage below for an explanation of this function's existence - POPE +//--------------------------------------------------------------------------- +static void setMobjInfoValue(int mobjInfoIndex, int keyIndex, uint_64_t value) { + mobjinfo_t *mi; + if (mobjInfoIndex >= NUMMOBJTYPES || mobjInfoIndex < 0) return; + mi = &mobjinfo[mobjInfoIndex]; + switch (keyIndex) { + case 0: mi->doomednum = (int)value; return; + case 1: mi->spawnstate = (int)value; return; + case 2: mi->spawnhealth = (int)value; return; + case 3: mi->seestate = (int)value; return; + case 4: mi->seesound = (int)value; return; + case 5: mi->reactiontime = (int)value; return; + case 6: mi->attacksound = (int)value; return; + case 7: mi->painstate = (int)value; return; + case 8: mi->painchance = (int)value; return; + case 9: mi->painsound = (int)value; return; + case 10: mi->meleestate = (int)value; return; + case 11: mi->missilestate = (int)value; return; + case 12: mi->deathstate = (int)value; return; + case 13: mi->xdeathstate = (int)value; return; + case 14: mi->deathsound = (int)value; return; + case 15: mi->speed = (int)value; return; + case 16: mi->radius = (int)value; return; + case 17: mi->height = (int)value; return; + case 18: mi->mass = (int)value; return; + case 19: mi->damage = (int)value; return; + case 20: mi->activesound = (int)value; return; + case 21: mi->flags = value; return; + case 22: return; // "Bits2", unused + case 23: mi->raisestate = (int)value; return; + default: return; + } +} + +// ==================================================================== +// deh_procThing +// Purpose: Handle DEH Thing block +// Args: fpin -- input file stream +// fpout -- output file stream (DEHOUT.TXT) +// line -- current line in file to process +// Returns: void +// +// Ty 8/27/98 - revised to also allow mnemonics for +// bit masks for monster attributes +// + +static void deh_procThing(DEHFILE *fpin, FILE* fpout, char *line) +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + int indexnum; + int ix; + char *strval; + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + if (fpout) fprintf(fpout,"Thing line: '%s'\n",inbuffer); + + // killough 8/98: allow hex numbers in input: + ix = sscanf(inbuffer,"%s %i",key, &indexnum); + if (fpout) fprintf(fpout,"count=%d, Thing %d\n",ix, indexnum); + + // Note that the mobjinfo[] array is base zero, but object numbers + // in the dehacked file start with one. Grumble. + --indexnum; + + // now process the stuff + // Note that for Things we can look up the key and use its offset + // in the array of key strings as an int offset in the structure + + // get a line until a blank or end of file--it's not + // blank now because it has our incoming key in it + while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + // e6y: Correction of wrong processing of Bits parameter if its value is equal to zero + // No more desync on HACX demos. + int bGetData; + + if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break; + lfstrip(inbuffer); // toss the end of line + + // killough 11/98: really bail out on blank lines (break != continue) + if (!*inbuffer) break; // bail out with blank line between sections + + // e6y: Correction of wrong processing of Bits parameter if its value is equal to zero + // No more desync on HACX demos. + bGetData = deh_GetData(inbuffer,key,&value,&strval,fpout); + if (!bGetData) + // Old code: if (!deh_GetData(inbuffer,key,&value,&strval,fpout)) // returns TRUE if ok + { + if (fpout) fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + for (ix=0; ix>32) & 0xffffffff, + (unsigned long)deh_mobjflags[iy].value & 0xffffffff, strval + ); + } + value |= deh_mobjflags[iy].value; + break; + } + if (iy >= DEH_MOBJFLAGMAX && fpout) { + fprintf(fpout, "Could not find bit mnemonic %s\n", strval); + } + } + + // Don't worry about conversion -- simply print values + if (fpout) { + fprintf(fpout, + "Bits = 0x%08lX%08lX\n", + (unsigned long)(value>>32) & 0xffffffff, + (unsigned long)value & 0xffffffff + ); + } + mobjinfo[indexnum].flags = value; // e6y + } + } + if (fpout) { + fprintf(fpout, + "Assigned 0x%08lx%08lx to %s(%d) at index %d\n", + (unsigned long)(value>>32) & 0xffffffff, + (unsigned long)value & 0xffffffff, key, indexnum, ix + ); + } + } + } + return; +} + +// ==================================================================== +// deh_procFrame +// Purpose: Handle DEH Frame block +// Args: fpin -- input file stream +// fpout -- output file stream (DEHOUT.TXT) +// line -- current line in file to process +// Returns: void +// +static void deh_procFrame(DEHFILE *fpin, FILE* fpout, char *line) +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + int indexnum; + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + + // killough 8/98: allow hex numbers in input: + sscanf(inbuffer,"%s %i",key, &indexnum); + if (fpout) fprintf(fpout,"Processing Frame at index %d: %s\n",indexnum,key); + if (indexnum < 0 || indexnum >= NUMSTATES) + if (fpout) fprintf(fpout,"Bad frame number %d of %d\n",indexnum, NUMSTATES); + + while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break; + lfstrip(inbuffer); + if (!*inbuffer) break; // killough 11/98 + if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok + { + if (fpout) fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + if (!strcasecmp(key,deh_state[0])) // Sprite number + { + if (fpout) fprintf(fpout," - sprite = %lld\n",value); + states[indexnum].sprite = (spritenum_t)value; + } + else + if (!strcasecmp(key,deh_state[1])) // Sprite subnumber + { + if (fpout) fprintf(fpout," - frame = %lld\n",value); + states[indexnum].frame = (long)value; // long + } + else + if (!strcasecmp(key,deh_state[2])) // Duration + { + if (fpout) fprintf(fpout," - tics = %lld\n",value); + states[indexnum].tics = (long)value; // long + } + else + if (!strcasecmp(key,deh_state[3])) // Next frame + { + if (fpout) fprintf(fpout," - nextstate = %lld\n",value); + states[indexnum].nextstate = (statenum_t)value; + } + else + if (!strcasecmp(key,deh_state[4])) // Codep frame (not set in Frame deh block) + { + if (fpout) fprintf(fpout," - codep, should not be set in Frame section!\n"); + /* nop */ ; + } + else + if (!strcasecmp(key,deh_state[5])) // Unknown 1 + { + if (fpout) fprintf(fpout," - misc1 = %lld\n",value); + states[indexnum].misc1 = (long)value; // long + } + else + if (!strcasecmp(key,deh_state[6])) // Unknown 2 + { + if (fpout) fprintf(fpout," - misc2 = %lld\n",value); + states[indexnum].misc2 = (long)value; // long + } + else + if (fpout) fprintf(fpout,"Invalid frame string index for '%s'\n",key); + } + return; +} + +// ==================================================================== +// deh_procPointer +// Purpose: Handle DEH Code pointer block, can use BEX [CODEPTR] instead +// Args: fpin -- input file stream +// fpout -- output file stream (DEHOUT.TXT) +// line -- current line in file to process +// Returns: void +// +static void deh_procPointer(DEHFILE *fpin, FILE* fpout, char *line) // done +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + int indexnum; + int i; // looper + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + // NOTE: different format from normal + + // killough 8/98: allow hex numbers in input, fix error case: + if (sscanf(inbuffer,"%*s %*i (%s %i)",key, &indexnum) != 2) + { + if (fpout) fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + return; + } + + if (fpout) fprintf(fpout,"Processing Pointer at index %d: %s\n",indexnum, key); + if (indexnum < 0 || indexnum >= NUMSTATES) + { + if (fpout) + fprintf(fpout,"Bad pointer number %d of %d\n",indexnum, NUMSTATES); + return; + } + + while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break; + lfstrip(inbuffer); + if (!*inbuffer) break; // killough 11/98 + if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok + { + if (fpout) fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + + if (value < 0 || value >= NUMSTATES) + { + if (fpout) + fprintf(fpout,"Bad pointer number %lld of %d\n",value, NUMSTATES); + return; + } + + if (!strcasecmp(key,deh_state[4])) // Codep frame (not set in Frame deh block) + { + states[indexnum].action = deh_codeptr[value]; + if (fpout) fprintf(fpout," - applied from codeptr[%lld] to states[%d]\n", + value,indexnum); + // Write BEX-oriented line to match: + // for (i=0;i FRAME %d = %s\n", + indexnum, &deh_bexptrs[i].lookup[2]); + break; + } + if (deh_bexptrs[i].cptr == NULL) // stop at null entry + break; + } + } + else + if (fpout) fprintf(fpout,"Invalid frame pointer index for '%s' at %lld\n", + key, value); + } + return; +} + +// ==================================================================== +// deh_procSounds +// Purpose: Handle DEH Sounds block +// Args: fpin -- input file stream +// fpout -- output file stream (DEHOUT.TXT) +// line -- current line in file to process +// Returns: void +// +static void deh_procSounds(DEHFILE *fpin, FILE* fpout, char *line) +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + int indexnum; + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + + // killough 8/98: allow hex numbers in input: + sscanf(inbuffer,"%s %i",key, &indexnum); + if (fpout) fprintf(fpout,"Processing Sounds at index %d: %s\n", + indexnum, key); + if (indexnum < 0 || indexnum >= NUMSFX) + if (fpout) fprintf(fpout,"Bad sound number %d of %d\n", + indexnum, NUMSFX); + + while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break; + lfstrip(inbuffer); + if (!*inbuffer) break; // killough 11/98 + if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok + { + if (fpout) fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + if (!strcasecmp(key,deh_sfxinfo[0])) // Offset + /* nop */ ; // we don't know what this is, I don't think + else + if (!strcasecmp(key,deh_sfxinfo[1])) // Zero/One + S_sfx[indexnum].singularity = (int)value; + else + if (!strcasecmp(key,deh_sfxinfo[2])) // Value + S_sfx[indexnum].priority = (int)value; + else + if (!strcasecmp(key,deh_sfxinfo[3])) // Zero 1 + S_sfx[indexnum].link = (sfxinfo_t *)value; + else + if (!strcasecmp(key,deh_sfxinfo[4])) // Zero 2 + S_sfx[indexnum].pitch = (int)value; + else + if (!strcasecmp(key,deh_sfxinfo[5])) // Zero 3 + S_sfx[indexnum].volume = (int)value; + else + if (!strcasecmp(key,deh_sfxinfo[6])) // Zero 4 + (void *) value;// S_sfx[indexnum].data = (void *) value; // killough 5/3/98: changed cast + else + if (!strcasecmp(key,deh_sfxinfo[7])) // Neg. One 1 + S_sfx[indexnum].usefulness = (int)value; + else + if (!strcasecmp(key,deh_sfxinfo[8])) // Neg. One 2 + S_sfx[indexnum].lumpnum = (int)value; + else + if (fpout) fprintf(fpout, + "Invalid sound string index for '%s'\n",key); + } + return; +} + +// ==================================================================== +// deh_procAmmo +// Purpose: Handle DEH Ammo block +// Args: fpin -- input file stream +// fpout -- output file stream (DEHOUT.TXT) +// line -- current line in file to process +// Returns: void +// +static void deh_procAmmo(DEHFILE *fpin, FILE* fpout, char *line) +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + int indexnum; + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + + // killough 8/98: allow hex numbers in input: + sscanf(inbuffer,"%s %i",key, &indexnum); + if (fpout) fprintf(fpout,"Processing Ammo at index %d: %s\n", + indexnum, key); + if (indexnum < 0 || indexnum >= NUMAMMO) + if (fpout) fprintf(fpout,"Bad ammo number %d of %d\n", + indexnum,NUMAMMO); + + while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break; + lfstrip(inbuffer); + if (!*inbuffer) break; // killough 11/98 + if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok + { + if (fpout) fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + if (!strcasecmp(key,deh_ammo[0])) // Max ammo + maxammo[indexnum] = (int)value; + else + if (!strcasecmp(key,deh_ammo[1])) // Per ammo + clipammo[indexnum] = (int)value; + else + if (fpout) fprintf(fpout,"Invalid ammo string index for '%s'\n",key); + } + return; +} + +// ==================================================================== +// deh_procWeapon +// Purpose: Handle DEH Weapon block +// Args: fpin -- input file stream +// fpout -- output file stream (DEHOUT.TXT) +// line -- current line in file to process +// Returns: void +// +static void deh_procWeapon(DEHFILE *fpin, FILE* fpout, char *line) +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + int indexnum; + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + + // killough 8/98: allow hex numbers in input: + sscanf(inbuffer,"%s %i",key, &indexnum); + if (fpout) fprintf(fpout,"Processing Weapon at index %d: %s\n", + indexnum, key); + if (indexnum < 0 || indexnum >= NUMWEAPONS) + if (fpout) fprintf(fpout,"Bad weapon number %d of %d\n", + indexnum, NUMAMMO); + + while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break; + lfstrip(inbuffer); + if (!*inbuffer) break; // killough 11/98 + if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok + { + if (fpout) fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + if (!strcasecmp(key,deh_weapon[0])) // Ammo type + weaponinfo[indexnum].ammo = (ammotype_t)value; + else + if (!strcasecmp(key,deh_weapon[1])) // Deselect frame + weaponinfo[indexnum].upstate = (int)value; + else + if (!strcasecmp(key,deh_weapon[2])) // Select frame + weaponinfo[indexnum].downstate = (int)value; + else + if (!strcasecmp(key,deh_weapon[3])) // Bobbing frame + weaponinfo[indexnum].readystate = (int)value; + else + if (!strcasecmp(key,deh_weapon[4])) // Shooting frame + weaponinfo[indexnum].atkstate = (int)value; + else + if (!strcasecmp(key,deh_weapon[5])) // Firing frame + weaponinfo[indexnum].flashstate = (int)value; + else + if (fpout) fprintf(fpout,"Invalid weapon string index for '%s'\n",key); + } + return; +} + +// ==================================================================== +// deh_procSprite +// Purpose: Dummy - we do not support the DEH Sprite block +// Args: fpin -- input file stream +// fpout -- output file stream (DEHOUT.TXT) +// line -- current line in file to process +// Returns: void +// +static void deh_procSprite(DEHFILE *fpin, FILE* fpout, char *line) // Not supported +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + int indexnum; + + // Too little is known about what this is supposed to do, and + // there are better ways of handling sprite renaming. Not supported. + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + + // killough 8/98: allow hex numbers in input: + sscanf(inbuffer,"%s %i",key, &indexnum); + if (fpout) fprintf(fpout, + "Ignoring Sprite offset change at index %d: %s\n",indexnum, key); + while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break; + lfstrip(inbuffer); + if (!*inbuffer) break; // killough 11/98 + // ignore line + if (fpout) fprintf(fpout,"- %s\n",inbuffer); + } + return; +} + +// ==================================================================== +// deh_procPars +// Purpose: Handle BEX extension for PAR times +// Args: fpin -- input file stream +// fpout -- output file stream (DEHOUT.TXT) +// line -- current line in file to process +// Returns: void +// +static void deh_procPars(DEHFILE *fpin, FILE* fpout, char *line) // extension +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + int indexnum; + int episode, level, partime, oldpar; + + // new item, par times + // usage: After [PARS] Par 0 section identifier, use one or more of these + // lines: + // par 3 5 120 + // par 14 230 + // The first would make the par for E3M5 be 120 seconds, and the + // second one makes the par for MAP14 be 230 seconds. The number + // of parameters on the line determines which group of par values + // is being changed. Error checking is done based on current fixed + // array sizes of[4][10] and [32] + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + + // killough 8/98: allow hex numbers in input: + sscanf(inbuffer,"%s %i",key, &indexnum); + if (fpout) fprintf(fpout, + "Processing Par value at index %d: %s\n",indexnum, key); + // indexnum is a dummy entry + while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break; + lfstrip(strlwr(inbuffer)); // lowercase it + if (!*inbuffer) break; // killough 11/98 + if (3 != sscanf(inbuffer,"par %i %i %i",&episode, &level, &partime)) + { // not 3 + if (2 != sscanf(inbuffer,"par %i %i",&level, &partime)) + { // not 2 + if (fpout) fprintf(fpout,"Invalid par time setting string: %s\n",inbuffer); + } + else + { // is 2 + // Ty 07/11/98 - wrong range check, not zero-based + if (level < 1 || level > 32) // base 0 array (but 1-based parm) + { + if (fpout) fprintf(fpout,"Invalid MAPnn value MAP%d\n",level); + } + else + { + oldpar = cpars[level-1]; + if (fpout) fprintf(fpout,"Changed par time for MAP%02d from %d to %d\n",level,oldpar,partime); + cpars[level-1] = partime; + deh_pars = TRUE; + } + } + } + else + { // is 3 + // note that though it's a [4][10] array, the "left" and "top" aren't used, + // effectively making it a base 1 array. + // Ty 07/11/98 - level was being checked against max 3 - dumb error + // Note that episode 4 does not have par times per original design + // in Ultimate DOOM so that is not supported here. + if (episode < 1 || episode > 3 || level < 1 || level > 9) + { + if (fpout) fprintf(fpout, + "Invalid ExMx values E%dM%d\n",episode, level); + } + else + { + oldpar = pars[episode][level]; + pars[episode][level] = partime; + if (fpout) fprintf(fpout, + "Changed par time for E%dM%d from %d to %d\n", + episode,level,oldpar,partime); + deh_pars = TRUE; + } + } + } + return; +} + +// ==================================================================== +// deh_procCheat +// Purpose: Handle DEH Cheat block +// Args: fpin -- input file stream +// fpout -- output file stream (DEHOUT.TXT) +// line -- current line in file to process +// Returns: void +// +static void deh_procCheat(DEHFILE *fpin, FILE* fpout, char *line) // done +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + char ch = 0; // CPhipps - `writable' null string to initialise... + char *strval = &ch; // pointer to the value area + int ix, iy; // array indices + char *p; // utility pointer + + if (fpout) fprintf(fpout,"Processing Cheat: %s\n",line); + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break; + lfstrip(inbuffer); + if (!*inbuffer) break; // killough 11/98 + if (!deh_GetData(inbuffer,key,&value,&strval,fpout)) // returns TRUE if ok + { + if (fpout) fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + // Otherwise we got a (perhaps valid) cheat name, + // so look up the key in the array + + // killough 4/18/98: use main cheat code table in st_stuff.c now + for (ix=0; cheat[ix].cheat; ix++) + if (cheat[ix].deh_cheat) // killough 4/18/98: skip non-deh + { + if (!strcasecmp(key,cheat[ix].deh_cheat)) // found the cheat, ignored case + { + // replace it but don't overflow it. Use current length as limit. + // Ty 03/13/98 - add 0xff code + // Deal with the fact that the cheats in deh files are extended + // with character 0xFF to the original cheat length, which we don't do. + for (iy=0; strval[iy]; iy++) + strval[iy] = (strval[iy]==(char)0xff) ? '\0' : strval[iy]; + + iy = ix; // killough 4/18/98 + + // Ty 03/14/98 - skip leading spaces + p = strval; + while (*p == ' ') ++p; + // Ty 03/16/98 - change to use a strdup and orphan the original + // Also has the advantage of allowing length changes. + // strncpy(cheat[iy].cheat,p,strlen(cheat[iy].cheat)); +#if 0 + { // killough 9/12/98: disable cheats which are prefixes of this one + int i; + for (i=0; cheat[i].cheat; i++) + if (cheat[i].when & not_deh && + !strncasecmp(cheat[i].cheat, + cheat[iy].cheat, + strlen(cheat[i].cheat)) && i != iy) + cheat[i].deh_modified = true; + } +#endif + cheat[iy].cheat = strdup(p); + if (fpout) fprintf(fpout, + "Assigned new cheat '%s' to cheat '%s'at index %d\n", + p, cheat[ix].deh_cheat, iy); // killough 4/18/98 + } + } + if (fpout) fprintf(fpout,"- %s\n",inbuffer); + } + return; +} + +// ==================================================================== +// deh_procMisc +// Purpose: Handle DEH Misc block +// Args: fpin -- input file stream +// fpout -- output file stream (DEHOUT.TXT) +// line -- current line in file to process +// Returns: void +// +static void deh_procMisc(DEHFILE *fpin, FILE* fpout, char *line) // done +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break; + lfstrip(inbuffer); + if (!*inbuffer) break; // killough 11/98 + if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok + { + if (fpout) fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + // Otherwise it's ok + if (fpout) fprintf(fpout,"Processing Misc item '%s'\n", key); + + if (!strcasecmp(key,deh_misc[0])) // Initial Health + initial_health = (int)value; + else + if (!strcasecmp(key,deh_misc[1])) // Initial Bullets + initial_bullets = (int)value; + else + if (!strcasecmp(key,deh_misc[2])) // Max Health + maxhealth = (int)value; + else + if (!strcasecmp(key,deh_misc[3])) // Max Armor + max_armor = (int)value; + else + if (!strcasecmp(key,deh_misc[4])) // Green Armor Class + green_armor_class = (int)value; + else + if (!strcasecmp(key,deh_misc[5])) // Blue Armor Class + blue_armor_class = (int)value; + else + if (!strcasecmp(key,deh_misc[6])) // Max Soulsphere + max_soul = (int)value; + else + if (!strcasecmp(key,deh_misc[7])) // Soulsphere Health + soul_health = (int)value; + else + if (!strcasecmp(key,deh_misc[8])) // Megasphere Health + mega_health = (int)value; + else + if (!strcasecmp(key,deh_misc[9])) // God Mode Health + god_health = (int)value; + else + if (!strcasecmp(key,deh_misc[10])) // IDFA Armor + idfa_armor = (int)value; + else + if (!strcasecmp(key,deh_misc[11])) // IDFA Armor Class + idfa_armor_class = (int)value; + else + if (!strcasecmp(key,deh_misc[12])) // IDKFA Armor + idkfa_armor = (int)value; + else + if (!strcasecmp(key,deh_misc[13])) // IDKFA Armor Class + idkfa_armor_class = (int)value; + else + if (!strcasecmp(key,deh_misc[14])) // BFG Cells/Shot + bfgcells = (int)value; + else + if (!strcasecmp(key,deh_misc[15])) { // Monsters Infight + // e6y: Dehacked support - monsters infight + if (value == 202) monsters_infight = 0; + else if (value == 221) monsters_infight = 1; + else if (fpout) fprintf(fpout, + "Invalid value for 'Monsters Infight': %i", (int)value); + + /* No such switch in DOOM - nop */ //e6y ; + } else + if (fpout) fprintf(fpout, + "Invalid misc item string index for '%s'\n",key); + } + return; +} + +// ==================================================================== +// deh_procText +// Purpose: Handle DEH Text block +// Notes: We look things up in the current information and if found +// we replace it. At the same time we write the new and +// improved BEX syntax to the log file for future use. +// Args: fpin -- input file stream +// fpout -- output file stream (DEHOUT.TXT) +// line -- current line in file to process +// Returns: void +// +static void deh_procText(DEHFILE *fpin, FILE* fpout, char *line) +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX*2]; // can't use line -- double size buffer too. + int i; // loop variable + int fromlen, tolen; // as specified on the text block line + int usedlen; // shorter of fromlen and tolen if not matched + boolean found = FALSE; // to allow early exit once found + char* line2 = NULL; // duplicate line for rerouting + + // Ty 04/11/98 - Included file may have NOTEXT skip flag set + if (includenotext) // flag to skip included deh-style text + { + if (fpout) fprintf(fpout, + "Skipped text block because of notext directive\n"); + strcpy(inbuffer,line); + while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + dehfgets(inbuffer, sizeof(inbuffer), fpin); // skip block + // Ty 05/17/98 - don't care if this fails + return; // ************** Early return + } + + // killough 8/98: allow hex numbers in input: + sscanf(line,"%s %i %i",key,&fromlen,&tolen); + if (fpout) fprintf(fpout, + "Processing Text (key=%s, from=%d, to=%d)\n", + key, fromlen, tolen); + + // killough 10/98: fix incorrect usage of feof + { + int c, totlen = 0; + while (totlen < fromlen + tolen && (c = dehfgetc(fpin)) != EOF) + if (c != '\r') + inbuffer[totlen++] = c; + inbuffer[totlen]='\0'; + } + + // if the from and to are 4, this may be a sprite rename. Check it + // against the array and process it as such if it matches. Remember + // that the original names are (and should remain) uppercase. + // Future: this will be from a separate [SPRITES] block. + if (fromlen==4 && tolen==4) + { + i=0; + while (sprnames[i]) // null terminated list in info.c //jff 3/19/98 + { //check pointer + if (!strncasecmp(sprnames[i],inbuffer,fromlen)) //not first char + { + if (fpout) fprintf(fpout, + "Changing name of sprite at index %d from %s to %*s\n", + i,sprnames[i],tolen,&inbuffer[fromlen]); + // Ty 03/18/98 - not using strdup because length is fixed + + // killough 10/98: but it's an array of pointers, so we must + // use strdup unless we redeclare sprnames and change all else + { + // CPhipps - fix constness problem + char *s; + sprnames[i] = s = strdup(sprnames[i]); + + strncpy(s,&inbuffer[fromlen],tolen); + } + found = TRUE; + break; // only one will match--quit early + } + ++i; // next array element + } + } + else + if (fromlen < 7 && tolen < 7) // lengths of music and sfx are 6 or shorter + { + usedlen = (fromlen < tolen) ? fromlen : tolen; + if (fromlen != tolen) + if (fpout) fprintf(fpout, + "Warning: Mismatched lengths from=%d, to=%d, used %d\n", + fromlen, tolen, usedlen); + // Try sound effects entries - see sounds.c + for (i=1; i 12) ? "..." : "",fromlen,tolen); + if ((size_t)fromlen <= strlen(inbuffer)) + { + line2 = strdup(&inbuffer[fromlen]); + inbuffer[fromlen] = '\0'; + } + + deh_procStringSub(NULL, inbuffer, line2, fpout); + } + free(line2); // may be NULL, ignored by free() + return; +} + +static void deh_procError(DEHFILE *fpin, FILE* fpout, char *line) +{ + char inbuffer[DEH_BUFFERMAX]; + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + if (fpout) fprintf(fpout,"Unmatched Block: '%s'\n",inbuffer); + return; +} + +// ==================================================================== +// deh_procStrings +// Purpose: Handle BEX [STRINGS] extension +// Args: fpin -- input file stream +// fpout -- output file stream (DEHOUT.TXT) +// line -- current line in file to process +// Returns: void +// +static void deh_procStrings(DEHFILE *fpin, FILE* fpout, char *line) +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + char *strval; // holds the string value of the line + static int maxstrlen = 128; // maximum string length, bumped 128 at + // a time as needed + // holds the final result of the string after concatenation + static char *holdstring = NULL; + boolean found = false; // looking for string continuation + + if (fpout) fprintf(fpout,"Processing extended string substitution\n"); + + if (!holdstring) holdstring = malloc(maxstrlen*sizeof(*holdstring)); + + *holdstring = '\0'; // empty string to start with + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + // Ty 04/24/98 - have to allow inbuffer to start with a blank for + // the continuations of C1TEXT etc. + while (!dehfeof(fpin) && *inbuffer) /* && (*inbuffer != ' ') */ + { + if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break; + if (*inbuffer == '#') continue; // skip comment lines + lfstrip(inbuffer); + if (!*inbuffer) break; // killough 11/98 + if (!*holdstring) // first one--get the key + { + if (!deh_GetData(inbuffer,key,&value,&strval,fpout)) // returns TRUE if ok + { + if (fpout) fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + } + while (strlen(holdstring) + strlen(inbuffer) > (size_t)maxstrlen) // Ty03/29/98 - fix stupid error + { + // killough 11/98: allocate enough the first time + maxstrlen += strlen(holdstring) + strlen(inbuffer) - maxstrlen; + if (fpout) fprintf(fpout, + "* increased buffer from to %d for buffer size %d\n", + maxstrlen,(int)strlen(inbuffer)); + holdstring = realloc(holdstring,maxstrlen*sizeof(*holdstring)); + } + // concatenate the whole buffer if continuation or the value iffirst + strcat(holdstring,ptr_lstrip(((*holdstring) ? inbuffer : strval))); + rstrip(holdstring); + // delete any trailing blanks past the backslash + // note that blanks before the backslash will be concatenated + // but ones at the beginning of the next line will not, allowing + // indentation in the file to read well without affecting the + // string itself. + if (holdstring[strlen(holdstring)-1] == '\\') + { + holdstring[strlen(holdstring)-1] = '\0'; + continue; // ready to concatenate + } + if (*holdstring) // didn't have a backslash, trap above would catch that + { + // go process the current string + found = deh_procStringSub(key, NULL, holdstring, fpout); // supply keyand not search string + + if (!found) + if (fpout) fprintf(fpout, + "Invalid string key '%s', substitution skipped.\n",key); + + *holdstring = '\0'; // empty string for the next one + } + } + return; +} + +// ==================================================================== +// deh_procStringSub +// Purpose: Common string parsing and handling routine for DEH and BEX +// Args: key -- place to put the mnemonic for the string if found +// lookfor -- original value string to look for +// newstring -- string to put in its place if found +// fpout -- file stream pointer for log file (DEHOUT.TXT) +// Returns: boolean: True if string found, false if not +// +boolean deh_procStringSub(char *key, char *lookfor, char *newstring, FILE *fpout) +{ + boolean found; // loop exit flag + int i; // looper + + found = false; + for (i=0;i '%s'\n",key,newstring); + + if (!key) + if (fpout) fprintf(fpout, + "Assigned '%.12s%s' to'%.12s%s' at key %s\n", + lookfor, (strlen(lookfor) > 12) ? "..." : "", + newstring, (strlen(newstring) > 12) ? "..." :"", + deh_strlookup[i].lookup); + + if (!key) // must have passed an old style string so showBEX + if (fpout) fprintf(fpout, + "*BEX FORMAT:\n%s = %s\n*END BEX\n", + deh_strlookup[i].lookup, + dehReformatStr(newstring)); + + break; + } + } + if (!found) + if (fpout) fprintf(fpout, + "Could not find '%.12s'\n",key ? key: lookfor); + + return found; +} + +//======================================================================== +// haleyjd 9/22/99 +// +// deh_procHelperThing +// +// Allows handy substitution of any thing for helper dogs. DEH patches +// are being made frequently for this purpose and it requires a complete +// rewiring of the DOG thing. I feel this is a waste of effort, and so +// have added this new [HELPER] BEX block + +static void deh_procHelperThing(DEHFILE *fpin, FILE *fpout, char *line) +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break; + lfstrip(inbuffer); + if (!*inbuffer) break; + if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok + { + if (fpout) fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + // Otherwise it's ok + if (fpout) + { + fprintf(fpout,"Processing Helper Thing item '%s'\n", key); + fprintf(fpout,"value is %i", (int)value); + } + if (!strncasecmp(key, "type", 4)) + HelperThing = (int)value; + } + return; +} + +// +// deh_procBexSprites +// +// Supports sprite name substitutions without requiring use +// of the DeHackEd Text block +// +static void deh_procBexSprites(DEHFILE *fpin, FILE *fpout, char *line) +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + char *strval; // holds the string value of the line + char candidate[5]; + int rover; + + if(fpout) + fprintf(fpout,"Processing sprite name substitution\n"); + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + + while(!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if(!dehfgets(inbuffer, sizeof(inbuffer), fpin)) + break; + if(*inbuffer == '#') + continue; // skip comment lines + lfstrip(inbuffer); + if(!*inbuffer) + break; // killough 11/98 + if(!deh_GetData(inbuffer,key,&value,&strval,fpout)) // returns TRUE if ok + { + if(fpout) + fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + // do it + memset(candidate, 0, sizeof(candidate)); + strncpy(candidate, ptr_lstrip(strval), 4); + if(strlen(candidate) != 4) + { + if(fpout) + fprintf(fpout, "Bad length for sprite name '%s'\n", + candidate); + continue; + } + + rover = 0; + while(deh_spritenames[rover]) + { + if(!strncasecmp(deh_spritenames[rover], key, 4)) + { + if(fpout) + fprintf(fpout, "Substituting '%s' for sprite '%s'\n", + candidate, deh_spritenames[rover]); + + sprnames[rover] = strdup(candidate); + break; + } + rover++; + } + } +} + +// ditto for sound names +static void deh_procBexSounds(DEHFILE *fpin, FILE *fpout, char *line) +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + char *strval; // holds the string value of the line + char candidate[7]; + int rover, len; + + if(fpout) + fprintf(fpout,"Processing sound name substitution\n"); + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + + while(!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if(!dehfgets(inbuffer, sizeof(inbuffer), fpin)) + break; + if(*inbuffer == '#') + continue; // skip comment lines + lfstrip(inbuffer); + if(!*inbuffer) + break; // killough 11/98 + if(!deh_GetData(inbuffer,key,&value,&strval,fpout)) // returns TRUE if ok + { + if(fpout) + fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + // do it + memset(candidate, 0, 7); + strncpy(candidate, ptr_lstrip(strval), 6); + len = strlen(candidate); + if(len < 1 || len > 6) + { + if(fpout) + fprintf(fpout, "Bad length for sound name '%s'\n", + candidate); + continue; + } + + rover = 1; + while(deh_soundnames[rover]) + { + if(!strncasecmp(deh_soundnames[rover], key, 6)) + { + if(fpout) + fprintf(fpout, "Substituting '%s' for sound '%s'\n", + candidate, deh_soundnames[rover]); + + S_sfx[rover].name = strdup(candidate); + break; + } + rover++; + } + } +} + +// ditto for music names +static void deh_procBexMusic(DEHFILE *fpin, FILE *fpout, char *line) +{ + char key[DEH_MAXKEYLEN]; + char inbuffer[DEH_BUFFERMAX]; + uint_64_t value; // All deh values are ints or longs + char *strval; // holds the string value of the line + char candidate[7]; + int rover, len; + + if(fpout) + fprintf(fpout,"Processing music name substitution\n"); + + strncpy(inbuffer,line,DEH_BUFFERMAX - 1); + + while(!dehfeof(fpin) && *inbuffer && (*inbuffer != ' ')) + { + if(!dehfgets(inbuffer, sizeof(inbuffer), fpin)) + break; + if(*inbuffer == '#') + continue; // skip comment lines + lfstrip(inbuffer); + if(!*inbuffer) + break; // killough 11/98 + if(!deh_GetData(inbuffer,key,&value,&strval,fpout)) // returns TRUE if ok + { + if(fpout) + fprintf(fpout,"Bad data pair in '%s'\n",inbuffer); + continue; + } + // do it + memset(candidate, 0, 7); + strncpy(candidate, ptr_lstrip(strval), 6); + len = strlen(candidate); + if(len < 1 || len > 6) + { + if(fpout) + fprintf(fpout, "Bad length for music name '%s'\n", + candidate); + continue; + } + + rover = 1; + while(deh_musicnames[rover]) + { + if(!strncasecmp(deh_musicnames[rover], key, 6)) + { + if(fpout) + fprintf(fpout, "Substituting '%s' for music '%s'\n", + candidate, deh_musicnames[rover]); + + S_music[rover].name = strdup(candidate); + break; + } + rover++; + } + } +} + +// ==================================================================== +// General utility function(s) +// ==================================================================== + +// ==================================================================== +// dehReformatStr +// Purpose: Convert a string into a continuous string with embedded +// linefeeds for "\n" sequences in the source string +// Args: string -- the string to convert +// Returns: the converted string (converted in a static buffer) +// +static char *dehReformatStr(char *string) +{ + static char *buff = NULL; // only processing the changed string, + char *s, *t; + + if (!buff) + buff = malloc(DEH_BUFFERMAX); + + s = string; // source + t = buff; // target + // let's play... + + while (*s) + { + if (*s == '\n') + ++s, *t++ = '\\', *t++ = 'n', *t++ = '\\', *t++='\n'; + else + *t++ = *s++; + } + *t = '\0'; + return buff; +} + +// ==================================================================== +// lfstrip +// Purpose: Strips CR/LF off the end of a string +// Args: s -- the string to work on +// Returns: void -- the string is modified in place +// +// killough 10/98: only strip at end of line, not entire string + +static void lfstrip(char *s) // strip the \r and/or \n off of a line +{ + char *p = s+strlen(s); + while (p > s && (*--p=='\r' || *p=='\n')) + *p = 0; +} + +// ==================================================================== +// rstrip +// Purpose: Strips trailing blanks off a string +// Args: s -- the string to work on +// Returns: void -- the string is modified in place +// +static void rstrip(char *s) // strip trailing whitespace +{ + char *p = s+strlen(s); // killough 4/4/98: same here + while (p > s && isspace(*--p)) // break on first non-whitespace + *p='\0'; +} + +// ==================================================================== +// ptr_lstrip +// Purpose: Points past leading whitespace in a string +// Args: s -- the string to work on +// Returns: char * pointing to the first nonblank character in the +// string. The original string is not changed. +// +static char *ptr_lstrip(char *p) // point past leading whitespace +{ + while (isspace(*p)) + p++; + return p; +} + +// e6y: Correction of wrong processing of Bits parameter if its value is equal to zero +// No more desync on HACX demos. +// FIXME!!! (lame) +static boolean StrToInt(char *s, long *l) +{ + return ( + (sscanf(s, " 0x%lx", l) == 1) || + (sscanf(s, " 0X%lx", l) == 1) || + (sscanf(s, " 0%lo", l) == 1) || + (sscanf(s, " %ld", l) == 1) + ); +} + +// ==================================================================== +// deh_GetData +// Purpose: Get a key and data pair from a passed string +// Args: s -- the string to be examined +// k -- a place to put the key +// l -- pointer to a long integer to store the number +// strval -- a pointer to the place in s where the number +// value comes from. Pass NULL to not use this. +// fpout -- stream pointer to output log (DEHOUT.TXT) +// Notes: Expects a key phrase, optional space, equal sign, +// optional space and a value, mostly an int but treated +// as a long just in case. The passed pointer to hold +// the key must be DEH_MAXKEYLEN in size. + +static boolean deh_GetData(char *s, char *k, uint_64_t *l, char **strval, FILE *fpout) +{ + char *t; // current char + long val; // to hold value of pair + char buffer[DEH_MAXKEYLEN]; // to hold key in progress + // e6y: Correction of wrong processing of Bits parameter if its value is equal to zero + // No more desync on HACX demos. + boolean okrc = 1; // assume good unless we have problems + int i; // iterator + + *buffer = '\0'; + val = 0; // defaults in case not otherwise set + for (i=0, t=s; *t && i < DEH_MAXKEYLEN; t++, i++) + { + if (*t == '=') break; + buffer[i] = *t; // copy it + } + buffer[--i] = '\0'; // terminate the key before the '=' + if (!*t) // end of string with no equal sign + { + okrc = FALSE; + } + else + { + if (!*++t) + { + val = 0; // in case "thiskey =" with no value + okrc = FALSE; + } + // we've incremented t + // e6y: Correction of wrong processing of Bits parameter if its value is equal to zero + // No more desync on HACX demos. + // Old code: e6y val = strtol(t,NULL,0); // killough 8/9/98: allow hex or octal input + if (!StrToInt(t,&val)) + { + val = 0; + okrc = 2; + } + } + + // go put the results in the passed pointers + *l = val; // may be a faked zero + + // if spaces between key and equal sign, strip them + strcpy(k,ptr_lstrip(buffer)); // could be a zero-length string + + if (strval != NULL) // pass NULL if you don't want this back + *strval = t; // pointer, has to be somewhere in s, + // even if pointing at the zero byte. + + return(okrc); +} +#else +void D_ProcessDehFile(const char *filename, const char *outfilename, int lumpnum) +{ + lprintf(LO_WARN, "D_ProcessDehFile: deh support has been disabled at build time!\n"); +} +#endif diff --git a/components/doom/prboom/d_deh.h b/components/doom/prboom/d_deh.h new file mode 100644 index 0000000..513e0b0 --- /dev/null +++ b/components/doom/prboom/d_deh.h @@ -0,0 +1,1113 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2006 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * Dehacked file support + * New for the TeamTNT "Boom" engine + * + * Author: Ty Halderman, TeamTNT + * + * Description: This file translates the #defined string constants + * to named variables to externalize them for deh/bex changes. + * Should be able to compile with D_FRENCH (for example) and still + * work (untested). + * + */ + +#ifndef __D_DEH__ +#define __D_DEH__ + +void D_ProcessDehFile(const char *filename, const char *outfilename, int lumpnum); + +// +// Ty 03/22/98 - note that we are keeping the english versions and +// comments in this file +// New string names all start with an extra s_ to avoid conflicts, +// but are otherwise identical to the original including uppercase. +// This is partly to keep the changes simple and partly for easier +// identification of the locations in which they're used. +// +// Printed strings for translation +// + +// +// D_Main.C +// +//#define D_DEVSTR "Development mode ON.\n" +extern const char* const s_D_DEVSTR; // = D_DEVSTR; +//#define D_CDROM "CD-ROM Version: default.cfg from c:\\doomdata\n" +extern const char* const s_D_CDROM; // = D_CDROM; + +// +// M_Menu.C +// +//#define PRESSKEY "press a key." +extern const char* const s_PRESSKEY; // = PRESSKEY; +//#define PRESSYN "press y or n." +extern const char* const s_PRESSYN; // = PRESSYN; +//#define QUITMSG "are you sure you want to\nquit this great game?" +extern const char* const s_QUITMSG; // = QUITMSG; +//#define LOADNET "you can't do load while in a net game!\n\n"PRESSKEY +extern const char* const s_LOADNET; // = LOADNET; +//#define QLOADNET "you can't quickload during a netgame!\n\n"PRESSKEY +extern const char* const s_QLOADNET; // = QLOADNET; +//#define QSAVESPOT "you haven't picked a quicksave slot yet!\n\n"PRESSKEY +extern const char* const s_QSAVESPOT; // = QSAVESPOT; +//#define SAVEDEAD "you can't save if you aren't playing!\n\n"PRESSKEY +extern const char* const s_SAVEDEAD; // = SAVEDEAD; +//#define QSPROMPT "quicksave over your game named\n\n'%s'?\n\n"PRESSYN +extern const char* const s_QSPROMPT; // = QSPROMPT; +//#define QLPROMPT "do you want to quickload the game named\n\n'%s'?\n\n"PRESSYN +extern const char* const s_QLPROMPT; // = QLPROMPT; + +/* +#define NEWGAME \ +"you can't start a new game\n"\ +"while in a network game.\n\n"PRESSKEY +*/ +extern const char* const s_NEWGAME; // = NEWGAME; + +// CPhipps - message given when asked if to restart the level +extern const char* const s_RESTARTLEVEL; + +/* +#define NIGHTMARE \ +"are you sure? this skill level\n"\ +"isn't even remotely fair.\n\n"PRESSYN +*/ +extern const char* const s_NIGHTMARE; // = NIGHTMARE; + +/* +#define SWSTRING \ +"this is the shareware version of doom.\n\n"\ +"you need to order the entire trilogy.\n\n"PRESSKEY +*/ +extern const char* const s_SWSTRING; // = SWSTRING; + +//#define MSGOFF "Messages OFF" +extern const char* const s_MSGOFF; // = MSGOFF; +//#define MSGON "Messages ON" +extern const char* const s_MSGON; // = MSGON; +//#define NETEND "you can't end a netgame!\n\n"PRESSKEY +extern const char* const s_NETEND; // = NETEND; +//#define ENDGAME "are you sure you want to end the game?\n\n"PRESSYN +extern const char* const s_ENDGAME; // = ENDGAME; + +//#define DOSY "(press y to quit)" +extern const char* const s_DOSY; // = DOSY; + +//#define DETAILHI "High detail" +extern const char* const s_DETAILHI; // = DETAILHI; +//#define DETAILLO "Low detail" +extern const char* const s_DETAILLO; // = DETAILLO; +//#define GAMMALVL0 "Gamma correction OFF" +extern const char* const s_GAMMALVL0; // = GAMMALVL0; +//#define GAMMALVL1 "Gamma correction level 1" +extern const char* const s_GAMMALVL1; // = GAMMALVL1; +//#define GAMMALVL2 "Gamma correction level 2" +extern const char* const s_GAMMALVL2; // = GAMMALVL2; +//#define GAMMALVL3 "Gamma correction level 3" +extern const char* const s_GAMMALVL3; // = GAMMALVL3; +//#define GAMMALVL4 "Gamma correction level 4" +extern const char* const s_GAMMALVL4; // = GAMMALVL4; +//#define EMPTYSTRING "empty slot" +extern const char* const s_EMPTYSTRING; // = EMPTYSTRING; + +// +// P_inter.C +// +//#define GOTARMOR "Picked up the armor." +extern const char* const s_GOTARMOR; // = GOTARMOR; +//#define GOTMEGA "Picked up the MegaArmor!" +extern const char* const s_GOTMEGA; // = GOTMEGA; +//#define GOTHTHBONUS "Picked up a health bonus." +extern const char* const s_GOTHTHBONUS; // = GOTHTHBONUS; +//#define GOTARMBONUS "Picked up an armor bonus." +extern const char* const s_GOTARMBONUS; // = GOTARMBONUS; +//#define GOTSTIM "Picked up a stimpack." +extern const char* const s_GOTSTIM; // = GOTSTIM; +//#define GOTMEDINEED "Picked up a medikit that you REALLY need!" +extern const char* const s_GOTMEDINEED; // = GOTMEDINEED; +//#define GOTMEDIKIT "Picked up a medikit." +extern const char* const s_GOTMEDIKIT; // = GOTMEDIKIT; +//#define GOTSUPER "Supercharge!" +extern const char* const s_GOTSUPER; // = GOTSUPER; + +//#define GOTBLUECARD "Picked up a blue keycard." +extern const char* const s_GOTBLUECARD; // = GOTBLUECARD; +//#define GOTYELWCARD "Picked up a yellow keycard." +extern const char* const s_GOTYELWCARD; // = GOTYELWCARD; +//#define GOTREDCARD "Picked up a red keycard." +extern const char* const s_GOTREDCARD; // = GOTREDCARD; +//#define GOTBLUESKUL "Picked up a blue skull key." +extern const char* const s_GOTBLUESKUL; // = GOTBLUESKUL; +//#define GOTYELWSKUL "Picked up a yellow skull key." +extern const char* const s_GOTYELWSKUL; // = GOTYELWSKUL; +//#define GOTREDSKULL "Picked up a red skull key." +extern const char* const s_GOTREDSKULL; // = GOTREDSKULL; + +//#define GOTINVUL "Invulnerability!" +extern const char* const s_GOTINVUL; // = GOTINVUL; +//#define GOTBERSERK "Berserk!" +extern const char* const s_GOTBERSERK; // = GOTBERSERK; +//#define GOTINVIS "Partial Invisibility" +extern const char* const s_GOTINVIS; // = GOTINVIS; +//#define GOTSUIT "Radiation Shielding Suit" +extern const char* const s_GOTSUIT; // = GOTSUIT; +//#define GOTMAP "Computer Area Map" +extern const char* const s_GOTMAP; // = GOTMAP; +//#define GOTVISOR "Light Amplification Visor" +extern const char* const s_GOTVISOR; // = GOTVISOR; +//#define GOTMSPHERE "MegaSphere!" +extern const char* const s_GOTMSPHERE; // = GOTMSPHERE; + +//#define GOTCLIP "Picked up a clip." +extern const char* const s_GOTCLIP; // = GOTCLIP; +//#define GOTCLIPBOX "Picked up a box of bullets." +extern const char* const s_GOTCLIPBOX; // = GOTCLIPBOX; +//#define GOTROCKET "Picked up a rocket." +extern const char* const s_GOTROCKET; // = GOTROCKET; +//#define GOTROCKBOX "Picked up a box of rockets." +extern const char* const s_GOTROCKBOX; // = GOTROCKBOX; +//#define GOTCELL "Picked up an energy cell." +extern const char* const s_GOTCELL; // = GOTCELL; +//#define GOTCELLBOX "Picked up an energy cell pack." +extern const char* const s_GOTCELLBOX; // = GOTCELLBOX; +//#define GOTSHELLS "Picked up 4 shotgun shells." +extern const char* const s_GOTSHELLS; // = GOTSHELLS; +//#define GOTSHELLBOX "Picked up a box of shotgun shells." +extern const char* const s_GOTSHELLBOX; // = GOTSHELLBOX; +//#define GOTBACKPACK "Picked up a backpack full of ammo!" +extern const char* const s_GOTBACKPACK; // = GOTBACKPACK; + +//#define GOTBFG9000 "You got the BFG9000! Oh, yes." +extern const char* const s_GOTBFG9000; // = GOTBFG9000; +//#define GOTCHAINGUN "You got the chaingun!" +extern const char* const s_GOTCHAINGUN; // = GOTCHAINGUN; +//#define GOTCHAINSAW "A chainsaw! Find some meat!" +extern const char* const s_GOTCHAINSAW; // = GOTCHAINSAW; +//#define GOTLAUNCHER "You got the rocket launcher!" +extern const char* const s_GOTLAUNCHER; // = GOTLAUNCHER; +//#define GOTPLASMA "You got the plasma gun!" +extern const char* const s_GOTPLASMA; // = GOTPLASMA; +//#define GOTSHOTGUN "You got the shotgun!" +extern const char* const s_GOTSHOTGUN; // = GOTSHOTGUN; +//#define GOTSHOTGUN2 "You got the super shotgun!" +extern const char* const s_GOTSHOTGUN2; // = GOTSHOTGUN2; + +// +// P_Doors.C +// +//#define PD_BLUEO "You need a blue key to activate this object" +extern const char* const s_PD_BLUEO; // = PD_BLUEO; +//#define PD_REDO "You need a red key to activate this object" +extern const char* const s_PD_REDO; // = PD_REDO; +//#define PD_YELLOWO "You need a yellow key to activate this object" +extern const char* const s_PD_YELLOWO; // = PD_YELLOWO; +//#define PD_BLUEK "You need a blue key to open this door" +extern const char* const s_PD_BLUEK; // = PD_BLUEK; +//#define PD_REDK "You need a red key to open this door" +extern const char* const s_PD_REDK; // = PD_REDK; +//#define PD_YELLOWK "You need a yellow key to open this door" +extern const char* const s_PD_YELLOWK; // = PD_YELLOWK; +//jff 02/05/98 Create messages specific to card and skull keys +//#define PD_BLUEC "You need a blue card to open this door" +extern const char* const s_PD_BLUEC; // = PD_BLUEC; +//#define PD_REDC "You need a red card to open this door" +extern const char* const s_PD_REDC; // = PD_REDC; +//#define PD_YELLOWC "You need a yellow card to open this door" +extern const char* const s_PD_YELLOWC; // = PD_YELLOWC; +//#define PD_BLUES "You need a blue skull to open this door" +extern const char* const s_PD_BLUES; // = PD_BLUES; +//#define PD_REDS "You need a red skull to open this door" +extern const char* const s_PD_REDS; // = PD_REDS; +//#define PD_YELLOWS "You need a yellow skull to open this door" +extern const char* const s_PD_YELLOWS; // = PD_YELLOWS; +//#define PD_ANY "Any key will open this door" +extern const char* const s_PD_ANY; // = PD_ANY; +//#define PD_ALL3 "You need all three keys to open this door" +extern const char* const s_PD_ALL3; // = PD_ALL3; +//#define PD_ALL6 "You need all six keys to open this door" +extern const char* const s_PD_ALL6; // = PD_ALL6; + +// +// G_game.C +// +//#define GGSAVED "game saved." +extern const char* const s_GGSAVED; // = GGSAVED; + +// +// HU_stuff.C +// +//#define HUSTR_MSGU "[Message unsent]" +extern const char* const s_HUSTR_MSGU; // = HUSTR_MSGU; + +//#define HUSTR_E1M1 "E1M1: Hangar" +extern const char* const s_HUSTR_E1M1; // = HUSTR_E1M1; +//#define HUSTR_E1M2 "E1M2: Nuclear Plant" +extern const char* const s_HUSTR_E1M2; // = HUSTR_E1M2; +//#define HUSTR_E1M3 "E1M3: Toxin Refinery" +extern const char* const s_HUSTR_E1M3; // = HUSTR_E1M3; +//#define HUSTR_E1M4 "E1M4: Command Control" +extern const char* const s_HUSTR_E1M4; // = HUSTR_E1M4; +//#define HUSTR_E1M5 "E1M5: Phobos Lab" +extern const char* const s_HUSTR_E1M5; // = HUSTR_E1M5; +//#define HUSTR_E1M6 "E1M6: Central Processing" +extern const char* const s_HUSTR_E1M6; // = HUSTR_E1M6; +//#define HUSTR_E1M7 "E1M7: Computer Station" +extern const char* const s_HUSTR_E1M7; // = HUSTR_E1M7; +//#define HUSTR_E1M8 "E1M8: Phobos Anomaly" +extern const char* const s_HUSTR_E1M8; // = HUSTR_E1M8; +//#define HUSTR_E1M9 "E1M9: Military Base" +extern const char* const s_HUSTR_E1M9; // = HUSTR_E1M9; + +//#define HUSTR_E2M1 "E2M1: Deimos Anomaly" +extern const char* const s_HUSTR_E2M1; // = HUSTR_E2M1; +//#define HUSTR_E2M2 "E2M2: Containment Area" +extern const char* const s_HUSTR_E2M2; // = HUSTR_E2M2; +//#define HUSTR_E2M3 "E2M3: Refinery" +extern const char* const s_HUSTR_E2M3; // = HUSTR_E2M3; +//#define HUSTR_E2M4 "E2M4: Deimos Lab" +extern const char* const s_HUSTR_E2M4; // = HUSTR_E2M4; +//#define HUSTR_E2M5 "E2M5: Command Center" +extern const char* const s_HUSTR_E2M5; // = HUSTR_E2M5; +//#define HUSTR_E2M6 "E2M6: Halls of the Damned" +extern const char* const s_HUSTR_E2M6; // = HUSTR_E2M6; +//#define HUSTR_E2M7 "E2M7: Spawning Vats" +extern const char* const s_HUSTR_E2M7; // = HUSTR_E2M7; +//#define HUSTR_E2M8 "E2M8: Tower of Babel" +extern const char* const s_HUSTR_E2M8; // = HUSTR_E2M8; +//#define HUSTR_E2M9 "E2M9: Fortress of Mystery" +extern const char* const s_HUSTR_E2M9; // = HUSTR_E2M9; + +//#define HUSTR_E3M1 "E3M1: Hell Keep" +extern const char* const s_HUSTR_E3M1; // = HUSTR_E3M1; +//#define HUSTR_E3M2 "E3M2: Slough of Despair" +extern const char* const s_HUSTR_E3M2; // = HUSTR_E3M2; +//#define HUSTR_E3M3 "E3M3: Pandemonium" +extern const char* const s_HUSTR_E3M3; // = HUSTR_E3M3; +//#define HUSTR_E3M4 "E3M4: House of Pain" +extern const char* const s_HUSTR_E3M4; // = HUSTR_E3M4; +//#define HUSTR_E3M5 "E3M5: Unholy Cathedral" +extern const char* const s_HUSTR_E3M5; // = HUSTR_E3M5; +//#define HUSTR_E3M6 "E3M6: Mt. Erebus" +extern const char* const s_HUSTR_E3M6; // = HUSTR_E3M6; +//#define HUSTR_E3M7 "E3M7: Limbo" +extern const char* const s_HUSTR_E3M7; // = HUSTR_E3M7; +//#define HUSTR_E3M8 "E3M8: Dis" +extern const char* const s_HUSTR_E3M8; // = HUSTR_E3M8; +//#define HUSTR_E3M9 "E3M9: Warrens" +extern const char* const s_HUSTR_E3M9; // = HUSTR_E3M9; + +//#define HUSTR_E4M1 "E4M1: Hell Beneath" +extern const char* const s_HUSTR_E4M1; // = HUSTR_E4M1; +//#define HUSTR_E4M2 "E4M2: Perfect Hatred" +extern const char* const s_HUSTR_E4M2; // = HUSTR_E4M2; +//#define HUSTR_E4M3 "E4M3: Sever The Wicked" +extern const char* const s_HUSTR_E4M3; // = HUSTR_E4M3; +//#define HUSTR_E4M4 "E4M4: Unruly Evil" +extern const char* const s_HUSTR_E4M4; // = HUSTR_E4M4; +//#define HUSTR_E4M5 "E4M5: They Will Repent" +extern const char* const s_HUSTR_E4M5; // = HUSTR_E4M5; +//#define HUSTR_E4M6 "E4M6: Against Thee Wickedly" +extern const char* const s_HUSTR_E4M6; // = HUSTR_E4M6; +//#define HUSTR_E4M7 "E4M7: And Hell Followed" +extern const char* const s_HUSTR_E4M7; // = HUSTR_E4M7; +//#define HUSTR_E4M8 "E4M8: Unto The Cruel" +extern const char* const s_HUSTR_E4M8; // = HUSTR_E4M8; +//#define HUSTR_E4M9 "E4M9: Fear" +extern const char* const s_HUSTR_E4M9; // = HUSTR_E4M9; + +//#define HUSTR_1 "level 1: entryway" +extern const char* const s_HUSTR_1; // = HUSTR_1; +//#define HUSTR_2 "level 2: underhalls" +extern const char* const s_HUSTR_2; // = HUSTR_2; +//#define HUSTR_3 "level 3: the gantlet" +extern const char* const s_HUSTR_3; // = HUSTR_3; +//#define HUSTR_4 "level 4: the focus" +extern const char* const s_HUSTR_4; // = HUSTR_4; +//#define HUSTR_5 "level 5: the waste tunnels" +extern const char* const s_HUSTR_5; // = HUSTR_5; +//#define HUSTR_6 "level 6: the crusher" +extern const char* const s_HUSTR_6; // = HUSTR_6; +//#define HUSTR_7 "level 7: dead simple" +extern const char* const s_HUSTR_7; // = HUSTR_7; +//#define HUSTR_8 "level 8: tricks and traps" +extern const char* const s_HUSTR_8; // = HUSTR_8; +//#define HUSTR_9 "level 9: the pit" +extern const char* const s_HUSTR_9; // = HUSTR_9; +//#define HUSTR_10 "level 10: refueling base" +extern const char* const s_HUSTR_10; // = HUSTR_10; +//#define HUSTR_11 "level 11: 'o' of destruction!" +extern const char* const s_HUSTR_11; // = HUSTR_11; + +//#define HUSTR_12 "level 12: the factory" +extern const char* const s_HUSTR_12; // = HUSTR_12; +//#define HUSTR_13 "level 13: downtown" +extern const char* const s_HUSTR_13; // = HUSTR_13; +//#define HUSTR_14 "level 14: the inmost dens" +extern const char* const s_HUSTR_14; // = HUSTR_14; +//#define HUSTR_15 "level 15: industrial zone" +extern const char* const s_HUSTR_15; // = HUSTR_15; +//#define HUSTR_16 "level 16: suburbs" +extern const char* const s_HUSTR_16; // = HUSTR_16; +//#define HUSTR_17 "level 17: tenements" +extern const char* const s_HUSTR_17; // = HUSTR_17; +//#define HUSTR_18 "level 18: the courtyard" +extern const char* const s_HUSTR_18; // = HUSTR_18; +//#define HUSTR_19 "level 19: the citadel" +extern const char* const s_HUSTR_19; // = HUSTR_19; +//#define HUSTR_20 "level 20: gotcha!" +extern const char* const s_HUSTR_20; // = HUSTR_20; + +//#define HUSTR_21 "level 21: nirvana" +extern const char* const s_HUSTR_21; // = HUSTR_21; +//#define HUSTR_22 "level 22: the catacombs" +extern const char* const s_HUSTR_22; // = HUSTR_22; +//#define HUSTR_23 "level 23: barrels o' fun" +extern const char* const s_HUSTR_23; // = HUSTR_23; +//#define HUSTR_24 "level 24: the chasm" +extern const char* const s_HUSTR_24; // = HUSTR_24; +//#define HUSTR_25 "level 25: bloodfalls" +extern const char* const s_HUSTR_25; // = HUSTR_25; +//#define HUSTR_26 "level 26: the abandoned mines" +extern const char* const s_HUSTR_26; // = HUSTR_26; +//#define HUSTR_27 "level 27: monster condo" +extern const char* const s_HUSTR_27; // = HUSTR_27; +//#define HUSTR_28 "level 28: the spirit world" +extern const char* const s_HUSTR_28; // = HUSTR_28; +//#define HUSTR_29 "level 29: the living end" +extern const char* const s_HUSTR_29; // = HUSTR_29; +//#define HUSTR_30 "level 30: icon of sin" +extern const char* const s_HUSTR_30; // = HUSTR_30; + +//#define HUSTR_31 "level 31: wolfenstein" +extern const char* const s_HUSTR_31; // = HUSTR_31; +//#define HUSTR_32 "level 32: grosse" +extern const char* const s_HUSTR_32; // = HUSTR_32; + +//#define PHUSTR_1 "level 1: congo" +extern const char* const s_PHUSTR_1; // = PHUSTR_1; +//#define PHUSTR_2 "level 2: well of souls" +extern const char* const s_PHUSTR_2; // = PHUSTR_2; +//#define PHUSTR_3 "level 3: aztec" +extern const char* const s_PHUSTR_3; // = PHUSTR_3; +//#define PHUSTR_4 "level 4: caged" +extern const char* const s_PHUSTR_4; // = PHUSTR_4; +//#define PHUSTR_5 "level 5: ghost town" +extern const char* const s_PHUSTR_5; // = PHUSTR_5; +//#define PHUSTR_6 "level 6: baron's lair" +extern const char* const s_PHUSTR_6; // = PHUSTR_6; +//#define PHUSTR_7 "level 7: caughtyard" +extern const char* const s_PHUSTR_7; // = PHUSTR_7; +//#define PHUSTR_8 "level 8: realm" +extern const char* const s_PHUSTR_8; // = PHUSTR_8; +//#define PHUSTR_9 "level 9: abattoire" +extern const char* const s_PHUSTR_9; // = PHUSTR_9; +//#define PHUSTR_10 "level 10: onslaught" +extern const char* const s_PHUSTR_10; // = PHUSTR_10; +//#define PHUSTR_11 "level 11: hunted" +extern const char* const s_PHUSTR_11; // = PHUSTR_11; + +//#define PHUSTR_12 "level 12: speed" +extern const char* const s_PHUSTR_12; // = PHUSTR_12; +//#define PHUSTR_13 "level 13: the crypt" +extern const char* const s_PHUSTR_13; // = PHUSTR_13; +//#define PHUSTR_14 "level 14: genesis" +extern const char* const s_PHUSTR_14; // = PHUSTR_14; +//#define PHUSTR_15 "level 15: the twilight" +extern const char* const s_PHUSTR_15; // = PHUSTR_15; +//#define PHUSTR_16 "level 16: the omen" +extern const char* const s_PHUSTR_16; // = PHUSTR_16; +//#define PHUSTR_17 "level 17: compound" +extern const char* const s_PHUSTR_17; // = PHUSTR_17; +//#define PHUSTR_18 "level 18: neurosphere" +extern const char* const s_PHUSTR_18; // = PHUSTR_18; +//#define PHUSTR_19 "level 19: nme" +extern const char* const s_PHUSTR_19; // = PHUSTR_19; +//#define PHUSTR_20 "level 20: the death domain" +extern const char* const s_PHUSTR_20; // = PHUSTR_20; + +//#define PHUSTR_21 "level 21: slayer" +extern const char* const s_PHUSTR_21; // = PHUSTR_21; +//#define PHUSTR_22 "level 22: impossible mission" +extern const char* const s_PHUSTR_22; // = PHUSTR_22; +//#define PHUSTR_23 "level 23: tombstone" +extern const char* const s_PHUSTR_23; // = PHUSTR_23; +//#define PHUSTR_24 "level 24: the final frontier" +extern const char* const s_PHUSTR_24; // = PHUSTR_24; +//#define PHUSTR_25 "level 25: the temple of darkness" +extern const char* const s_PHUSTR_25; // = PHUSTR_25; +//#define PHUSTR_26 "level 26: bunker" +extern const char* const s_PHUSTR_26; // = PHUSTR_26; +//#define PHUSTR_27 "level 27: anti-christ" +extern const char* const s_PHUSTR_27; // = PHUSTR_27; +//#define PHUSTR_28 "level 28: the sewers" +extern const char* const s_PHUSTR_28; // = PHUSTR_28; +//#define PHUSTR_29 "level 29: odyssey of noises" +extern const char* const s_PHUSTR_29; // = PHUSTR_29; +//#define PHUSTR_30 "level 30: the gateway of hell" +extern const char* const s_PHUSTR_30; // = PHUSTR_30; + +//#define PHUSTR_31 "level 31: cyberden" +extern const char* const s_PHUSTR_31; // = PHUSTR_31; +//#define PHUSTR_32 "level 32: go 2 it" +extern const char* const s_PHUSTR_32; // = PHUSTR_32; + +//#define THUSTR_1 "level 1: system control" +extern const char* const s_THUSTR_1; // = THUSTR_1; +//#define THUSTR_2 "level 2: human bbq" +extern const char* const s_THUSTR_2; // = THUSTR_2; +//#define THUSTR_3 "level 3: power control" +extern const char* const s_THUSTR_3; // = THUSTR_3; +//#define THUSTR_4 "level 4: wormhole" +extern const char* const s_THUSTR_4; // = THUSTR_4; +//#define THUSTR_5 "level 5: hanger" +extern const char* const s_THUSTR_5; // = THUSTR_5; +//#define THUSTR_6 "level 6: open season" +extern const char* const s_THUSTR_6; // = THUSTR_6; +//#define THUSTR_7 "level 7: prison" +extern const char* const s_THUSTR_7; // = THUSTR_7; +//#define THUSTR_8 "level 8: metal" +extern const char* const s_THUSTR_8; // = THUSTR_8; +//#define THUSTR_9 "level 9: stronghold" +extern const char* const s_THUSTR_9; // = THUSTR_9; +//#define THUSTR_10 "level 10: redemption" +extern const char* const s_THUSTR_10; // = THUSTR_10; +//#define THUSTR_11 "level 11: storage facility" +extern const char* const s_THUSTR_11; // = THUSTR_11; + +//#define THUSTR_12 "level 12: crater" +extern const char* const s_THUSTR_12; // = THUSTR_12; +//#define THUSTR_13 "level 13: nukage processing" +extern const char* const s_THUSTR_13; // = THUSTR_13; +//#define THUSTR_14 "level 14: steel works" +extern const char* const s_THUSTR_14; // = THUSTR_14; +//#define THUSTR_15 "level 15: dead zone" +extern const char* const s_THUSTR_15; // = THUSTR_15; +//#define THUSTR_16 "level 16: deepest reaches" +extern const char* const s_THUSTR_16; // = THUSTR_16; +//#define THUSTR_17 "level 17: processing area" +extern const char* const s_THUSTR_17; // = THUSTR_17; +//#define THUSTR_18 "level 18: mill" +extern const char* const s_THUSTR_18; // = THUSTR_18; +//#define THUSTR_19 "level 19: shipping/respawning" +extern const char* const s_THUSTR_19; // = THUSTR_19; +//#define THUSTR_20 "level 20: central processing" +extern const char* const s_THUSTR_20; // = THUSTR_20; + +//#define THUSTR_21 "level 21: administration center" +extern const char* const s_THUSTR_21; // = THUSTR_21; +//#define THUSTR_22 "level 22: habitat" +extern const char* const s_THUSTR_22; // = THUSTR_22; +//#define THUSTR_23 "level 23: lunar mining project" +extern const char* const s_THUSTR_23; // = THUSTR_23; +//#define THUSTR_24 "level 24: quarry" +extern const char* const s_THUSTR_24; // = THUSTR_24; +//#define THUSTR_25 "level 25: baron's den" +extern const char* const s_THUSTR_25; // = THUSTR_25; +//#define THUSTR_26 "level 26: ballistyx" +extern const char* const s_THUSTR_26; // = THUSTR_26; +//#define THUSTR_27 "level 27: mount pain" +extern const char* const s_THUSTR_27; // = THUSTR_27; +//#define THUSTR_28 "level 28: heck" +extern const char* const s_THUSTR_28; // = THUSTR_28; +//#define THUSTR_29 "level 29: river styx" +extern const char* const s_THUSTR_29; // = THUSTR_29; +//#define THUSTR_30 "level 30: last call" +extern const char* const s_THUSTR_30; // = THUSTR_30; + +//#define THUSTR_31 "level 31: pharaoh" +extern const char* const s_THUSTR_31; // = THUSTR_31; +//#define THUSTR_32 "level 32: caribbean" +extern const char* const s_THUSTR_32; // = THUSTR_32; + +//#define HUSTR_CHATMACRO1 "I'm ready to kick butt!" +extern const char* const s_HUSTR_CHATMACRO1; // = HUSTR_CHATMACRO1; +//#define HUSTR_CHATMACRO2 "I'm OK." +extern const char* const s_HUSTR_CHATMACRO2; // = HUSTR_CHATMACRO2; +//#define HUSTR_CHATMACRO3 "I'm not looking too good!" +extern const char* const s_HUSTR_CHATMACRO3; // = HUSTR_CHATMACRO3; +//#define HUSTR_CHATMACRO4 "Help!" +extern const char* const s_HUSTR_CHATMACRO4; // = HUSTR_CHATMACRO4; +//#define HUSTR_CHATMACRO5 "You suck!" +extern const char* const s_HUSTR_CHATMACRO5; // = HUSTR_CHATMACRO5; +//#define HUSTR_CHATMACRO6 "Next time, scumbag..." +extern const char* const s_HUSTR_CHATMACRO6; // = HUSTR_CHATMACRO6; +//#define HUSTR_CHATMACRO7 "Come here!" +extern const char* const s_HUSTR_CHATMACRO7; // = HUSTR_CHATMACRO7; +//#define HUSTR_CHATMACRO8 "I'll take care of it." +extern const char* const s_HUSTR_CHATMACRO8; // = HUSTR_CHATMACRO8; +//#define HUSTR_CHATMACRO9 "Yes" +extern const char* const s_HUSTR_CHATMACRO9; // = HUSTR_CHATMACRO9; +//#define HUSTR_CHATMACRO0 "No" +extern const char* const s_HUSTR_CHATMACRO0; // = HUSTR_CHATMACRO0; + +//#define HUSTR_TALKTOSELF1 "You mumble to yourself" +extern const char* const s_HUSTR_TALKTOSELF1; // = HUSTR_TALKTOSELF1; +//#define HUSTR_TALKTOSELF2 "Who's there?" +extern const char* const s_HUSTR_TALKTOSELF2; // = HUSTR_TALKTOSELF2; +//#define HUSTR_TALKTOSELF3 "You scare yourself" +extern const char* const s_HUSTR_TALKTOSELF3; // = HUSTR_TALKTOSELF3; +//#define HUSTR_TALKTOSELF4 "You start to rave" +extern const char* const s_HUSTR_TALKTOSELF4; // = HUSTR_TALKTOSELF4; +//#define HUSTR_TALKTOSELF5 "You've lost it..." +extern const char* const s_HUSTR_TALKTOSELF5; // = HUSTR_TALKTOSELF5; + +//#define HUSTR_MESSAGESENT "[Message Sent]" +extern const char* const s_HUSTR_MESSAGESENT; // = HUSTR_MESSAGESENT; + +// The following should NOT be changed unless it seems +// just AWFULLY necessary + +//#define HUSTR_PLRGREEN "Green: " +extern const char* const s_HUSTR_PLRGREEN; // = HUSTR_PLRGREEN; +//#define HUSTR_PLRINDIGO "Indigo: " +extern const char* const s_HUSTR_PLRINDIGO; // = HUSTR_PLRINDIGO; +//#define HUSTR_PLRBROWN "Brown: " +extern const char* const s_HUSTR_PLRBROWN; // = HUSTR_PLRBROWN; +//#define HUSTR_PLRRED "Red: " +extern const char* const s_HUSTR_PLRRED; // = HUSTR_PLRRED; + +// +// AM_map.C +// + +//#define AMSTR_FOLLOWON "Follow Mode ON" +extern const char* const s_AMSTR_FOLLOWON; // = AMSTR_FOLLOWON; +//#define AMSTR_FOLLOWOFF "Follow Mode OFF" +extern const char* const s_AMSTR_FOLLOWOFF; // = AMSTR_FOLLOWOFF; + +//#define AMSTR_GRIDON "Grid ON" +extern const char* const s_AMSTR_GRIDON; // = AMSTR_GRIDON; +//#define AMSTR_GRIDOFF "Grid OFF" +extern const char* const s_AMSTR_GRIDOFF; // = AMSTR_GRIDOFF; + +//#define AMSTR_MARKEDSPOT "Marked Spot" +extern const char* const s_AMSTR_MARKEDSPOT; // = AMSTR_MARKEDSPOT; +//#define AMSTR_MARKSCLEARED "All Marks Cleared" +extern const char* const s_AMSTR_MARKSCLEARED; // = AMSTR_MARKSCLEARED; + +// CPhipps - automap rotate & overlay +extern const char* const s_AMSTR_ROTATEON; +extern const char* const s_AMSTR_ROTATEOFF; +extern const char* const s_AMSTR_OVERLAYON; +extern const char* const s_AMSTR_OVERLAYOFF; + +// +// ST_stuff.C +// + +//#define STSTR_MUS "Music Change" +extern const char* const s_STSTR_MUS; // = STSTR_MUS; +//#define STSTR_NOMUS "IMPOSSIBLE SELECTION" +extern const char* const s_STSTR_NOMUS; // = STSTR_NOMUS; +//#define STSTR_DQDON "Degreelessness Mode On" +extern const char* const s_STSTR_DQDON; // = STSTR_DQDON; +//#define STSTR_DQDOFF "Degreelessness Mode Off" +extern const char* const s_STSTR_DQDOFF; // = STSTR_DQDOFF; + +//#define STSTR_KFAADDED "Very Happy Ammo Added" +extern const char* const s_STSTR_KFAADDED; // = STSTR_KFAADDED; +//#define STSTR_FAADDED "Ammo (no keys) Added" +extern const char* const s_STSTR_FAADDED; // = STSTR_FAADDED; + +//#define STSTR_NCON "No Clipping Mode ON" +extern const char* const s_STSTR_NCON; // = STSTR_NCON; +//#define STSTR_NCOFF "No Clipping Mode OFF" +extern const char* const s_STSTR_NCOFF; // = STSTR_NCOFF; + +//#define STSTR_BEHOLD "inVuln, Str, Inviso, Rad, Allmap, or Lite-amp" +extern const char* const s_STSTR_BEHOLD; // = STSTR_BEHOLD; +//#define STSTR_BEHOLDX "Power-up Toggled" +extern const char* const s_STSTR_BEHOLDX; // = STSTR_BEHOLDX; + +//#define STSTR_CHOPPERS "... doesn't suck - GM" +extern const char* const s_STSTR_CHOPPERS; // = STSTR_CHOPPERS; +//#define STSTR_CLEV "Changing Level..." +extern const char* const s_STSTR_CLEV; // = STSTR_CLEV; + +// +// F_Finale.C +// +/* +#define E1TEXT \ +"Once you beat the big badasses and\n"\ +"clean out the moon base you're supposed\n"\ +"to win, aren't you? Aren't you? Where's\n"\ +"your fat reward and ticket home? What\n"\ +"the hell is this? It's not supposed to\n"\ +"end this way!\n"\ +"\n" \ +"It stinks like rotten meat, but looks\n"\ +"like the lost Deimos base. Looks like\n"\ +"you're stuck on The Shores of Hell.\n"\ +"The only way out is through.\n"\ +"\n"\ +"To continue the DOOM experience, play\n"\ +"The Shores of Hell and its amazing\n"\ +"sequel, Inferno!\n" +*/ +extern const char* const s_E1TEXT; // = E1TEXT; + + +/* +#define E2TEXT \ +"You've done it! The hideous cyber-\n"\ +"demon lord that ruled the lost Deimos\n"\ +"moon base has been slain and you\n"\ +"are triumphant! But ... where are\n"\ +"you? You clamber to the edge of the\n"\ +"moon and look down to see the awful\n"\ +"truth.\n" \ +"\n"\ +"Deimos floats above Hell itself!\n"\ +"You've never heard of anyone escaping\n"\ +"from Hell, but you'll make the bastards\n"\ +"sorry they ever heard of you! Quickly,\n"\ +"you rappel down to the surface of\n"\ +"Hell.\n"\ +"\n" \ +"Now, it's on to the final chapter of\n"\ +"DOOM! -- Inferno." +*/ +extern const char* const s_E2TEXT; // = E2TEXT; + + +/* +#define E3TEXT \ +"The loathsome spiderdemon that\n"\ +"masterminded the invasion of the moon\n"\ +"bases and caused so much death has had\n"\ +"its ass kicked for all time.\n"\ +"\n"\ +"A hidden doorway opens and you enter.\n"\ +"You've proven too tough for Hell to\n"\ +"contain, and now Hell at last plays\n"\ +"fair -- for you emerge from the door\n"\ +"to see the green fields of Earth!\n"\ +"Home at last.\n" \ +"\n"\ +"You wonder what's been happening on\n"\ +"Earth while you were battling evil\n"\ +"unleashed. It's good that no Hell-\n"\ +"spawn could have come through that\n"\ +"door with you ..." +*/ +extern const char* const s_E3TEXT; // = E3TEXT; + + +/* +#define E4TEXT \ +"the spider mastermind must have sent forth\n"\ +"its legions of hellspawn before your\n"\ +"final confrontation with that terrible\n"\ +"beast from hell. but you stepped forward\n"\ +"and brought forth eternal damnation and\n"\ +"suffering upon the horde as a true hero\n"\ +"would in the face of something so evil.\n"\ +"\n"\ +"besides, someone was gonna pay for what\n"\ +"happened to daisy, your pet rabbit.\n"\ +"\n"\ +"but now, you see spread before you more\n"\ +"potential pain and gibbitude as a nation\n"\ +"of demons run amok among our cities.\n"\ +"\n"\ +"next stop, hell on earth!" +*/ +extern const char* const s_E4TEXT; // = E4TEXT; + + +// after level 6, put this: + +/* +#define C1TEXT \ +"YOU HAVE ENTERED DEEPLY INTO THE INFESTED\n" \ +"STARPORT. BUT SOMETHING IS WRONG. THE\n" \ +"MONSTERS HAVE BROUGHT THEIR OWN REALITY\n" \ +"WITH THEM, AND THE STARPORT'S TECHNOLOGY\n" \ +"IS BEING SUBVERTED BY THEIR PRESENCE.\n" \ +"\n"\ +"AHEAD, YOU SEE AN OUTPOST OF HELL, A\n" \ +"FORTIFIED ZONE. IF YOU CAN GET PAST IT,\n" \ +"YOU CAN PENETRATE INTO THE HAUNTED HEART\n" \ +"OF THE STARBASE AND FIND THE CONTROLLING\n" \ +"SWITCH WHICH HOLDS EARTH'S POPULATION\n" \ +"HOSTAGE." +*/ +extern const char* const s_C1TEXT; // = C1TEXT; + +// After level 11, put this: + +/* +#define C2TEXT \ +"YOU HAVE WON! YOUR VICTORY HAS ENABLED\n" \ +"HUMANKIND TO EVACUATE EARTH AND ESCAPE\n"\ +"THE NIGHTMARE. NOW YOU ARE THE ONLY\n"\ +"HUMAN LEFT ON THE FACE OF THE PLANET.\n"\ +"CANNIBAL MUTATIONS, CARNIVOROUS ALIENS,\n"\ +"AND EVIL SPIRITS ARE YOUR ONLY NEIGHBORS.\n"\ +"YOU SIT BACK AND WAIT FOR DEATH, CONTENT\n"\ +"THAT YOU HAVE SAVED YOUR SPECIES.\n"\ +"\n"\ +"BUT THEN, EARTH CONTROL BEAMS DOWN A\n"\ +"MESSAGE FROM SPACE: \"SENSORS HAVE LOCATED\n"\ +"THE SOURCE OF THE ALIEN INVASION. IF YOU\n"\ +"GO THERE, YOU MAY BE ABLE TO BLOCK THEIR\n"\ +"ENTRY. THE ALIEN BASE IS IN THE HEART OF\n"\ +"YOUR OWN HOME CITY, NOT FAR FROM THE\n"\ +"STARPORT.\" SLOWLY AND PAINFULLY YOU GET\n"\ +"UP AND RETURN TO THE FRAY." +*/ +extern const char* const s_C2TEXT; // = C2TEXT; + + +// After level 20, put this: + +/* +#define C3TEXT \ +"YOU ARE AT THE CORRUPT HEART OF THE CITY,\n"\ +"SURROUNDED BY THE CORPSES OF YOUR ENEMIES.\n"\ +"YOU SEE NO WAY TO DESTROY THE CREATURES'\n"\ +"ENTRYWAY ON THIS SIDE, SO YOU CLENCH YOUR\n"\ +"TEETH AND PLUNGE THROUGH IT.\n"\ +"\n"\ +"THERE MUST BE A WAY TO CLOSE IT ON THE\n"\ +"OTHER SIDE. WHAT DO YOU CARE IF YOU'VE\n"\ +"GOT TO GO THROUGH HELL TO GET TO IT?" +*/ +extern const char* const s_C3TEXT; // = C3TEXT; + + +// After level 29, put this: + +/* +#define C4TEXT \ +"THE HORRENDOUS VISAGE OF THE BIGGEST\n"\ +"DEMON YOU'VE EVER SEEN CRUMBLES BEFORE\n"\ +"YOU, AFTER YOU PUMP YOUR ROCKETS INTO\n"\ +"HIS EXPOSED BRAIN. THE MONSTER SHRIVELS\n"\ +"UP AND DIES, ITS THRASHING LIMBS\n"\ +"DEVASTATING UNTOLD MILES OF HELL'S\n"\ +"SURFACE.\n"\ +"\n"\ +"YOU'VE DONE IT. THE INVASION IS OVER.\n"\ +"EARTH IS SAVED. HELL IS A WRECK. YOU\n"\ +"WONDER WHERE BAD FOLKS WILL GO WHEN THEY\n"\ +"DIE, NOW. WIPING THE SWEAT FROM YOUR\n"\ +"FOREHEAD YOU BEGIN THE LONG TREK BACK\n"\ +"HOME. REBUILDING EARTH OUGHT TO BE A\n"\ +"LOT MORE FUN THAN RUINING IT WAS.\n" +*/ +extern const char* const s_C4TEXT; // = C4TEXT; + + + +// Before level 31, put this: + +/* +#define C5TEXT \ +"CONGRATULATIONS, YOU'VE FOUND THE SECRET\n"\ +"LEVEL! LOOKS LIKE IT'S BEEN BUILT BY\n"\ +"HUMANS, RATHER THAN DEMONS. YOU WONDER\n"\ +"WHO THE INMATES OF THIS CORNER OF HELL\n"\ +"WILL BE." +*/ +extern const char* const s_C5TEXT; // = C5TEXT; + + +// Before level 32, put this: + +/* +#define C6TEXT \ +"CONGRATULATIONS, YOU'VE FOUND THE\n"\ +"SUPER SECRET LEVEL! YOU'D BETTER\n"\ +"BLAZE THROUGH THIS ONE!\n" +*/ +extern const char* const s_C6TEXT; // = C6TEXT; + + +// after map 06 + +/* +#define P1TEXT \ +"You gloat over the steaming carcass of the\n"\ +"Guardian. With its death, you've wrested\n"\ +"the Accelerator from the stinking claws\n"\ +"of Hell. You relax and glance around the\n"\ +"room. Damn! There was supposed to be at\n"\ +"least one working prototype, but you can't\n"\ +"see it. The demons must have taken it.\n"\ +"\n"\ +"You must find the prototype, or all your\n"\ +"struggles will have been wasted. Keep\n"\ +"moving, keep fighting, keep killing.\n"\ +"Oh yes, keep living, too." +*/ +extern const char* const s_P1TEXT; // = P1TEXT; + + +// after map 11 + +/* +#define P2TEXT \ +"Even the deadly Arch-Vile labyrinth could\n"\ +"not stop you, and you've gotten to the\n"\ +"prototype Accelerator which is soon\n"\ +"efficiently and permanently deactivated.\n"\ +"\n"\ +"You're good at that kind of thing." +*/ +extern const char* const s_P2TEXT; // = P2TEXT; + + +// after map 20 + +/* +#define P3TEXT \ +"You've bashed and battered your way into\n"\ +"the heart of the devil-hive. Time for a\n"\ +"Search-and-Destroy mission, aimed at the\n"\ +"Gatekeeper, whose foul offspring is\n"\ +"cascading to Earth. Yeah, he's bad. But\n"\ +"you know who's worse!\n"\ +"\n"\ +"Grinning evilly, you check your gear, and\n"\ +"get ready to give the bastard a little Hell\n"\ +"of your own making!" +*/ +extern const char* const s_P3TEXT; // = P3TEXT; + +// after map 30 + +/* +#define P4TEXT \ +"The Gatekeeper's evil face is splattered\n"\ +"all over the place. As its tattered corpse\n"\ +"collapses, an inverted Gate forms and\n"\ +"sucks down the shards of the last\n"\ +"prototype Accelerator, not to mention the\n"\ +"few remaining demons. You're done. Hell\n"\ +"has gone back to pounding bad dead folks \n"\ +"instead of good live ones. Remember to\n"\ +"tell your grandkids to put a rocket\n"\ +"launcher in your coffin. If you go to Hell\n"\ +"when you die, you'll need it for some\n"\ +"final cleaning-up ..." +*/ +extern const char* const s_P4TEXT; // = P4TEXT; + +// before map 31 + +/* +#define P5TEXT \ +"You've found the second-hardest level we\n"\ +"got. Hope you have a saved game a level or\n"\ +"two previous. If not, be prepared to die\n"\ +"aplenty. For master marines only." +*/ +extern const char* const s_P5TEXT; // = P5TEXT; + +// before map 32 + +/* +#define P6TEXT \ +"Betcha wondered just what WAS the hardest\n"\ +"level we had ready for ya? Now you know.\n"\ +"No one gets out alive." +*/ +extern const char* const s_P6TEXT; // = P6TEXT; + + +/* +#define T1TEXT \ +"You've fought your way out of the infested\n"\ +"experimental labs. It seems that UAC has\n"\ +"once again gulped it down. With their\n"\ +"high turnover, it must be hard for poor\n"\ +"old UAC to buy corporate health insurance\n"\ +"nowadays..\n"\ +"\n"\ +"Ahead lies the military complex, now\n"\ +"swarming with diseased horrors hot to get\n"\ +"their teeth into you. With luck, the\n"\ +"complex still has some warlike ordnance\n"\ +"laying around." +*/ +extern const char* const s_T1TEXT; // = T1TEXT; + + +/* +#define T2TEXT \ +"You hear the grinding of heavy machinery\n"\ +"ahead. You sure hope they're not stamping\n"\ +"out new hellspawn, but you're ready to\n"\ +"ream out a whole herd if you have to.\n"\ +"They might be planning a blood feast, but\n"\ +"you feel about as mean as two thousand\n"\ +"maniacs packed into one mad killer.\n"\ +"\n"\ +"You don't plan to go down easy." +*/ +extern const char* const s_T2TEXT; // = T2TEXT; + + +/* +#define T3TEXT \ +"The vista opening ahead looks real damn\n"\ +"familiar. Smells familiar, too -- like\n"\ +"fried excrement. You didn't like this\n"\ +"place before, and you sure as hell ain't\n"\ +"planning to like it now. The more you\n"\ +"brood on it, the madder you get.\n"\ +"Hefting your gun, an evil grin trickles\n"\ +"onto your face. Time to take some names." +*/ +extern const char* const s_T3TEXT; // = T3TEXT; + +/* +#define T4TEXT \ +"Suddenly, all is silent, from one horizon\n"\ +"to the other. The agonizing echo of Hell\n"\ +"fades away, the nightmare sky turns to\n"\ +"blue, the heaps of monster corpses start \n"\ +"to evaporate along with the evil stench \n"\ +"that filled the air. Jeeze, maybe you've\n"\ +"done it. Have you really won?\n"\ +"\n"\ +"Something rumbles in the distance.\n"\ +"A blue light begins to glow inside the\n"\ +"ruined skull of the demon-spitter." +*/ +extern const char* const s_T4TEXT; // = T4TEXT; + + +/* +#define T5TEXT \ +"What now? Looks totally different. Kind\n"\ +"of like King Tut's condo. Well,\n"\ +"whatever's here can't be any worse\n"\ +"than usual. Can it? Or maybe it's best\n"\ +"to let sleeping gods lie.." +*/ +extern const char* const s_T5TEXT; // = T5TEXT; + + +/* +#define T6TEXT \ +"Time for a vacation. You've burst the\n"\ +"bowels of hell and by golly you're ready\n"\ +"for a break. You mutter to yourself,\n"\ +"Maybe someone else can kick Hell's ass\n"\ +"next time around. Ahead lies a quiet town,\n"\ +"with peaceful flowing water, quaint\n"\ +"buildings, and presumably no Hellspawn.\n"\ +"\n"\ +"As you step off the transport, you hear\n"\ +"the stomp of a cyberdemon's iron shoe." +*/ +extern const char* const s_T6TEXT; // = T6TEXT; + +// +// Character cast strings F_FINALE.C +// +//#define CC_ZOMBIE "ZOMBIEMAN" +extern const char* const s_CC_ZOMBIE; // = CC_ZOMBIE; +//#define CC_SHOTGUN "SHOTGUN GUY" +extern const char* const s_CC_SHOTGUN; // = CC_SHOTGUN; +//#define CC_HEAVY "HEAVY WEAPON DUDE" +extern const char* const s_CC_HEAVY; // = CC_HEAVY; +//#define CC_IMP "IMP" +extern const char* const s_CC_IMP; // = CC_IMP; +//#define CC_DEMON "DEMON" +extern const char* const s_CC_DEMON; // = CC_DEMON; +//#define CC_LOST "LOST SOUL" +extern const char* const s_CC_LOST; // = CC_LOST; +//#define CC_CACO "CACODEMON" +extern const char* const s_CC_CACO; // = CC_CACO; +//#define CC_HELL "HELL KNIGHT" +extern const char* const s_CC_HELL; // = CC_HELL; +//#define CC_BARON "BARON OF HELL" +extern const char* const s_CC_BARON; // = CC_BARON; +//#define CC_ARACH "ARACHNOTRON" +extern const char* const s_CC_ARACH; // = CC_ARACH; +//#define CC_PAIN "PAIN ELEMENTAL" +extern const char* const s_CC_PAIN; // = CC_PAIN; +//#define CC_REVEN "REVENANT" +extern const char* const s_CC_REVEN; // = CC_REVEN; +//#define CC_MANCU "MANCUBUS" +extern const char* const s_CC_MANCU; // = CC_MANCU; +//#define CC_ARCH "ARCH-VILE" +extern const char* const s_CC_ARCH; // = CC_ARCH; +//#define CC_SPIDER "THE SPIDER MASTERMIND" +extern const char* const s_CC_SPIDER; // = CC_SPIDER; +//#define CC_CYBER "THE CYBERDEMON" +extern const char* const s_CC_CYBER; // = CC_CYBER; +//#define CC_HERO "OUR HERO" +extern const char* const s_CC_HERO; // = CC_HERO; + +// Ty 03/30/98 - new substitutions for background textures during int screens +// char* bgflatE1 = "FLOOR4_8"; +extern const char* const bgflatE1; +// char* bgflatE2 = "SFLR6_1"; +extern const char* const bgflatE2; +// char* bgflatE3 = "MFLR8_4"; +extern const char* const bgflatE3; +// char* bgflatE4 = "MFLR8_3"; +extern const char* const bgflatE4; + +// char* bgflat06 = "SLIME16"; +extern const char* const bgflat06; +// char* bgflat11 = "RROCK14"; +extern const char* const bgflat11; +// char* bgflat20 = "RROCK07"; +extern const char* const bgflat20; +// char* bgflat30 = "RROCK17"; +extern const char* const bgflat30; +// char* bgflat15 = "RROCK13"; +extern const char* const bgflat15; +// char* bgflat31 = "RROCK19"; +extern const char* const bgflat31; + +// char* bgcastcall = "BOSSBACK"; // panel behind cast call +extern const char* const bgcastcall; + +// ignored if blank, general purpose startup announcements +// char* startup1 = ""; +extern const char* const startup1; +// char* startup2 = ""; +extern const char* const startup2; +// char* startup3 = ""; +extern const char* const startup3; +// char* startup4 = ""; +extern const char* const startup4; +// char* startup5 = ""; +extern const char* const startup5; + +#endif diff --git a/components/doom/prboom/d_englsh.h b/components/doom/prboom/d_englsh.h new file mode 100644 index 0000000..86e7416 --- /dev/null +++ b/components/doom/prboom/d_englsh.h @@ -0,0 +1,707 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * Printed strings for translation. + * English language support (default). + * See dstrings.h for suggestions about foreign language BEX support + * + *-----------------------------------------------------------------------------*/ + +#ifndef __D_ENGLSH__ +#define __D_ENGLSH__ + +/* d_main.c */ +#define D_DEVSTR "Development mode ON.\n" +#define D_CDROM "CD-ROM Version: default.cfg from c:\\doomdata\n" + +/* m_menu.c */ +#define PRESSKEY "press a key." +#define PRESSYN "press y or n." +#define QUITMSG "are you sure you want to\nquit this great game?" +#define LOADNET "you can't do load while in a net game!\n\n"PRESSKEY +#define QLOADNET "you can't quickload during a netgame!\n\n"PRESSKEY +#define QSAVESPOT "you haven't picked a quicksave slot yet!\n\n"PRESSKEY +#define SAVEDEAD "you can't save if you aren't playing!\n\n"PRESSKEY +#define QSPROMPT "quicksave over your game named\n\n'%s'?\n\n"PRESSYN +#define QLPROMPT "do you want to quickload the game named\n\n'%s'?\n\n"PRESSYN + +#define NEWGAME \ + "you can't start a new game\n"\ + "while in a network game.\n\n"PRESSKEY + +#define NIGHTMARE \ + "are you sure? this skill level\n"\ + "isn't even remotely fair.\n\n"PRESSYN + +#define SWSTRING \ + "this is the shareware version of doom.\n\n"\ + "you need to order the entire trilogy.\n\n"PRESSKEY + +#define MSGOFF "Messages OFF" +#define MSGON "Messages ON" +#define NETEND "you can't end a netgame!\n\n"PRESSKEY +#define ENDGAME "are you sure you want to end the game?\n\n"PRESSYN +#define RESTARTLEVEL "restart the level?\n\n"PRESSYN + +#define DOSY "(press y to quit)" + +#define DETAILHI "High detail" +#define DETAILLO "Low detail" +#define GAMMALVL0 "Gamma correction OFF" +#define GAMMALVL1 "Gamma correction level 1" +#define GAMMALVL2 "Gamma correction level 2" +#define GAMMALVL3 "Gamma correction level 3" +#define GAMMALVL4 "Gamma correction level 4" +#define EMPTYSTRING "empty slot" + +/* p_inter.c */ +#define GOTARMOR "Picked up the armor." +#define GOTMEGA "Picked up the MegaArmor!" +#define GOTHTHBONUS "Picked up a health bonus." +#define GOTARMBONUS "Picked up an armor bonus." +#define GOTSTIM "Picked up a stimpack." +#define GOTMEDINEED "Picked up a medikit that you REALLY need!" +#define GOTMEDIKIT "Picked up a medikit." +#define GOTSUPER "Supercharge!" + +#define GOTBLUECARD "Picked up a blue keycard." +#define GOTYELWCARD "Picked up a yellow keycard." +#define GOTREDCARD "Picked up a red keycard." +#define GOTBLUESKUL "Picked up a blue skull key." +#define GOTYELWSKUL "Picked up a yellow skull key." +#define GOTREDSKULL "Picked up a red skull key." + +#define GOTINVUL "Invulnerability!" +#define GOTBERSERK "Berserk!" +#define GOTINVIS "Partial Invisibility" +#define GOTSUIT "Radiation Shielding Suit" +#define GOTMAP "Computer Area Map" +#define GOTVISOR "Light Amplification Visor" +#define GOTMSPHERE "MegaSphere!" + +#define GOTCLIP "Picked up a clip." +#define GOTCLIPBOX "Picked up a box of bullets." +#define GOTROCKET "Picked up a rocket." +#define GOTROCKBOX "Picked up a box of rockets." +#define GOTCELL "Picked up an energy cell." +#define GOTCELLBOX "Picked up an energy cell pack." +#define GOTSHELLS "Picked up 4 shotgun shells." +#define GOTSHELLBOX "Picked up a box of shotgun shells." +#define GOTBACKPACK "Picked up a backpack full of ammo!" + +#define GOTBFG9000 "You got the BFG9000! Oh, yes." +#define GOTCHAINGUN "You got the chaingun!" +#define GOTCHAINSAW "A chainsaw! Find some meat!" +#define GOTLAUNCHER "You got the rocket launcher!" +#define GOTPLASMA "You got the plasma gun!" +#define GOTSHOTGUN "You got the shotgun!" +#define GOTSHOTGUN2 "You got the super shotgun!" + +/* p_doors.c */ +#define PD_BLUEO "You need a blue key to activate this object" +#define PD_REDO "You need a red key to activate this object" +#define PD_YELLOWO "You need a yellow key to activate this object" +#define PD_BLUEK "You need a blue key to open this door" +#define PD_REDK "You need a red key to open this door" +#define PD_YELLOWK "You need a yellow key to open this door" +/* jff 02/05/98 Create messages specific to card and skull keys */ +#define PD_BLUEC "You need a blue card to open this door" +#define PD_REDC "You need a red card to open this door" +#define PD_YELLOWC "You need a yellow card to open this door" +#define PD_BLUES "You need a blue skull to open this door" +#define PD_REDS "You need a red skull to open this door" +#define PD_YELLOWS "You need a yellow skull to open this door" +#define PD_ANY "Any key will open this door" +#define PD_ALL3 "You need all three keys to open this door" +#define PD_ALL6 "You need all six keys to open this door" + +/* g_game.c */ +#define GGSAVED "game saved." + +/* hu_stuff.c */ +#define HUSTR_MSGU "[Message unsent]" + +#define HUSTR_E1M1 "E1M1: Hangar" +#define HUSTR_E1M2 "E1M2: Nuclear Plant" +#define HUSTR_E1M3 "E1M3: Toxin Refinery" +#define HUSTR_E1M4 "E1M4: Command Control" +#define HUSTR_E1M5 "E1M5: Phobos Lab" +#define HUSTR_E1M6 "E1M6: Central Processing" +#define HUSTR_E1M7 "E1M7: Computer Station" +#define HUSTR_E1M8 "E1M8: Phobos Anomaly" +#define HUSTR_E1M9 "E1M9: Military Base" + +#define HUSTR_E2M1 "E2M1: Deimos Anomaly" +#define HUSTR_E2M2 "E2M2: Containment Area" +#define HUSTR_E2M3 "E2M3: Refinery" +#define HUSTR_E2M4 "E2M4: Deimos Lab" +#define HUSTR_E2M5 "E2M5: Command Center" +#define HUSTR_E2M6 "E2M6: Halls of the Damned" +#define HUSTR_E2M7 "E2M7: Spawning Vats" +#define HUSTR_E2M8 "E2M8: Tower of Babel" +#define HUSTR_E2M9 "E2M9: Fortress of Mystery" + +#define HUSTR_E3M1 "E3M1: Hell Keep" +#define HUSTR_E3M2 "E3M2: Slough of Despair" +#define HUSTR_E3M3 "E3M3: Pandemonium" +#define HUSTR_E3M4 "E3M4: House of Pain" +#define HUSTR_E3M5 "E3M5: Unholy Cathedral" +#define HUSTR_E3M6 "E3M6: Mt. Erebus" +#define HUSTR_E3M7 "E3M7: Limbo" +#define HUSTR_E3M8 "E3M8: Dis" +#define HUSTR_E3M9 "E3M9: Warrens" + +#define HUSTR_E4M1 "E4M1: Hell Beneath" +#define HUSTR_E4M2 "E4M2: Perfect Hatred" +#define HUSTR_E4M3 "E4M3: Sever The Wicked" +#define HUSTR_E4M4 "E4M4: Unruly Evil" +#define HUSTR_E4M5 "E4M5: They Will Repent" +#define HUSTR_E4M6 "E4M6: Against Thee Wickedly" +#define HUSTR_E4M7 "E4M7: And Hell Followed" +#define HUSTR_E4M8 "E4M8: Unto The Cruel" +#define HUSTR_E4M9 "E4M9: Fear" + +#define HUSTR_1 "level 1: entryway" +#define HUSTR_2 "level 2: underhalls" +#define HUSTR_3 "level 3: the gantlet" +#define HUSTR_4 "level 4: the focus" +#define HUSTR_5 "level 5: the waste tunnels" +#define HUSTR_6 "level 6: the crusher" +#define HUSTR_7 "level 7: dead simple" +#define HUSTR_8 "level 8: tricks and traps" +#define HUSTR_9 "level 9: the pit" +#define HUSTR_10 "level 10: refueling base" +#define HUSTR_11 "level 11: 'o' of destruction!" + +#define HUSTR_12 "level 12: the factory" +#define HUSTR_13 "level 13: downtown" +#define HUSTR_14 "level 14: the inmost dens" +#define HUSTR_15 "level 15: industrial zone" +#define HUSTR_16 "level 16: suburbs" +#define HUSTR_17 "level 17: tenements" +#define HUSTR_18 "level 18: the courtyard" +#define HUSTR_19 "level 19: the citadel" +#define HUSTR_20 "level 20: gotcha!" + +#define HUSTR_21 "level 21: nirvana" +#define HUSTR_22 "level 22: the catacombs" +#define HUSTR_23 "level 23: barrels o' fun" +#define HUSTR_24 "level 24: the chasm" +#define HUSTR_25 "level 25: bloodfalls" +#define HUSTR_26 "level 26: the abandoned mines" +#define HUSTR_27 "level 27: monster condo" +#define HUSTR_28 "level 28: the spirit world" +#define HUSTR_29 "level 29: the living end" +#define HUSTR_30 "level 30: icon of sin" + +#define HUSTR_31 "level 31: wolfenstein" +#define HUSTR_32 "level 32: grosse" + +#define PHUSTR_1 "level 1: congo" +#define PHUSTR_2 "level 2: well of souls" +#define PHUSTR_3 "level 3: aztec" +#define PHUSTR_4 "level 4: caged" +#define PHUSTR_5 "level 5: ghost town" +#define PHUSTR_6 "level 6: baron's lair" +#define PHUSTR_7 "level 7: caughtyard" +#define PHUSTR_8 "level 8: realm" +#define PHUSTR_9 "level 9: abattoire" +#define PHUSTR_10 "level 10: onslaught" +#define PHUSTR_11 "level 11: hunted" + +#define PHUSTR_12 "level 12: speed" +#define PHUSTR_13 "level 13: the crypt" +#define PHUSTR_14 "level 14: genesis" +#define PHUSTR_15 "level 15: the twilight" +#define PHUSTR_16 "level 16: the omen" +#define PHUSTR_17 "level 17: compound" +#define PHUSTR_18 "level 18: neurosphere" +#define PHUSTR_19 "level 19: nme" +#define PHUSTR_20 "level 20: the death domain" + +#define PHUSTR_21 "level 21: slayer" +#define PHUSTR_22 "level 22: impossible mission" +#define PHUSTR_23 "level 23: tombstone" +#define PHUSTR_24 "level 24: the final frontier" +#define PHUSTR_25 "level 25: the temple of darkness" +#define PHUSTR_26 "level 26: bunker" +#define PHUSTR_27 "level 27: anti-christ" +#define PHUSTR_28 "level 28: the sewers" +#define PHUSTR_29 "level 29: odyssey of noises" +#define PHUSTR_30 "level 30: the gateway of hell" + +#define PHUSTR_31 "level 31: cyberden" +#define PHUSTR_32 "level 32: go 2 it" + +#define THUSTR_1 "level 1: system control" +#define THUSTR_2 "level 2: human bbq" +#define THUSTR_3 "level 3: power control" +#define THUSTR_4 "level 4: wormhole" +#define THUSTR_5 "level 5: hanger" +#define THUSTR_6 "level 6: open season" +#define THUSTR_7 "level 7: prison" +#define THUSTR_8 "level 8: metal" +#define THUSTR_9 "level 9: stronghold" +#define THUSTR_10 "level 10: redemption" +#define THUSTR_11 "level 11: storage facility" + +#define THUSTR_12 "level 12: crater" +#define THUSTR_13 "level 13: nukage processing" +#define THUSTR_14 "level 14: steel works" +#define THUSTR_15 "level 15: dead zone" +#define THUSTR_16 "level 16: deepest reaches" +#define THUSTR_17 "level 17: processing area" +#define THUSTR_18 "level 18: mill" +#define THUSTR_19 "level 19: shipping/respawning" +#define THUSTR_20 "level 20: central processing" + +#define THUSTR_21 "level 21: administration center" +#define THUSTR_22 "level 22: habitat" +#define THUSTR_23 "level 23: lunar mining project" +#define THUSTR_24 "level 24: quarry" +#define THUSTR_25 "level 25: baron's den" +#define THUSTR_26 "level 26: ballistyx" +#define THUSTR_27 "level 27: mount pain" +#define THUSTR_28 "level 28: heck" +#define THUSTR_29 "level 29: river styx" +#define THUSTR_30 "level 30: last call" + +#define THUSTR_31 "level 31: pharaoh" +#define THUSTR_32 "level 32: caribbean" + +#define HUSTR_CHATMACRO1 "I'm ready to kick butt!" +#define HUSTR_CHATMACRO2 "I'm OK." +#define HUSTR_CHATMACRO3 "I'm not looking too good!" +#define HUSTR_CHATMACRO4 "Help!" +#define HUSTR_CHATMACRO5 "You suck!" +#define HUSTR_CHATMACRO6 "Next time, scumbag..." +#define HUSTR_CHATMACRO7 "Come here!" +#define HUSTR_CHATMACRO8 "I'll take care of it." +#define HUSTR_CHATMACRO9 "Yes" +#define HUSTR_CHATMACRO0 "No" + +#define HUSTR_TALKTOSELF1 "You mumble to yourself" +#define HUSTR_TALKTOSELF2 "Who's there?" +#define HUSTR_TALKTOSELF3 "You scare yourself" +#define HUSTR_TALKTOSELF4 "You start to rave" +#define HUSTR_TALKTOSELF5 "You've lost it..." + +#define HUSTR_MESSAGESENT "[Message Sent]" + +/* The following should NOT be changed unless it seems + * just AWFULLY necessary */ + +#define HUSTR_PLRGREEN "Player 1: " +#define HUSTR_PLRINDIGO "Player 2: " +#define HUSTR_PLRBROWN "Player 3: " +#define HUSTR_PLRRED "Player 4: " + +#define HUSTR_KEYGREEN 'g' +#define HUSTR_KEYINDIGO 'i' +#define HUSTR_KEYBROWN 'b' +#define HUSTR_KEYRED 'r' + +/* am_map.c */ + +#define AMSTR_FOLLOWON "Follow Mode ON" +#define AMSTR_FOLLOWOFF "Follow Mode OFF" + +#define AMSTR_GRIDON "Grid ON" +#define AMSTR_GRIDOFF "Grid OFF" + +#define AMSTR_MARKEDSPOT "Marked Spot" +#define AMSTR_MARKSCLEARED "All Marks Cleared" + +#define AMSTR_ROTATEON "Rotate Mode ON" +#define AMSTR_ROTATEOFF "Rotate Mode OFF" + +#define AMSTR_OVERLAYON "Overlay Mode ON" +#define AMSTR_OVERLAYOFF "Overlay Mode OFF" + +/* st_stuff.c */ + +#define STSTR_MUS "Music Change" +#define STSTR_NOMUS "IMPOSSIBLE SELECTION" +#define STSTR_DQDON "Degreelessness Mode On" +#define STSTR_DQDOFF "Degreelessness Mode Off" + +#define STSTR_KFAADDED "Very Happy Ammo Added" +#define STSTR_FAADDED "Ammo (no keys) Added" + +#define STSTR_NCON "No Clipping Mode ON" +#define STSTR_NCOFF "No Clipping Mode OFF" + +#define STSTR_BEHOLD "inVuln, Str, Inviso, Rad, Allmap, or Lite-amp" +#define STSTR_BEHOLDX "Power-up Toggled" + +#define STSTR_CHOPPERS "... doesn't suck - GM" +#define STSTR_CLEV "Changing Level..." + +#define STSTR_COMPON "Compatibility Mode On" /* phares */ +#define STSTR_COMPOFF "Compatibility Mode Off" /* phares */ + +/* f_finale.c */ + +#define E1TEXT \ + "Once you beat the big badasses and\n"\ + "clean out the moon base you're supposed\n"\ + "to win, aren't you? Aren't you? Where's\n"\ + "your fat reward and ticket home? What\n"\ + "the hell is this? It's not supposed to\n"\ + "end this way!\n"\ + "\n" \ + "It stinks like rotten meat, but looks\n"\ + "like the lost Deimos base. Looks like\n"\ + "you're stuck on The Shores of Hell.\n"\ + "The only way out is through.\n"\ + "\n"\ + "To continue the DOOM experience, play\n"\ + "The Shores of Hell and its amazing\n"\ + "sequel, Inferno!\n" + + +#define E2TEXT \ + "You've done it! The hideous cyber-\n"\ + "demon lord that ruled the lost Deimos\n"\ + "moon base has been slain and you\n"\ + "are triumphant! But ... where are\n"\ + "you? You clamber to the edge of the\n"\ + "moon and look down to see the awful\n"\ + "truth.\n" \ + "\n"\ + "Deimos floats above Hell itself!\n"\ + "You've never heard of anyone escaping\n"\ + "from Hell, but you'll make the bastards\n"\ + "sorry they ever heard of you! Quickly,\n"\ + "you rappel down to the surface of\n"\ + "Hell.\n"\ + "\n" \ + "Now, it's on to the final chapter of\n"\ + "DOOM! -- Inferno." + + +#define E3TEXT \ + "The loathsome spiderdemon that\n"\ + "masterminded the invasion of the moon\n"\ + "bases and caused so much death has had\n"\ + "its ass kicked for all time.\n"\ + "\n"\ + "A hidden doorway opens and you enter.\n"\ + "You've proven too tough for Hell to\n"\ + "contain, and now Hell at last plays\n"\ + "fair -- for you emerge from the door\n"\ + "to see the green fields of Earth!\n"\ + "Home at last.\n" \ + "\n"\ + "You wonder what's been happening on\n"\ + "Earth while you were battling evil\n"\ + "unleashed. It's good that no Hell-\n"\ + "spawn could have come through that\n"\ + "door with you ..." + + +#define E4TEXT \ + "the spider mastermind must have sent forth\n"\ + "its legions of hellspawn before your\n"\ + "final confrontation with that terrible\n"\ + "beast from hell. but you stepped forward\n"\ + "and brought forth eternal damnation and\n"\ + "suffering upon the horde as a true hero\n"\ + "would in the face of something so evil.\n"\ + "\n"\ + "besides, someone was gonna pay for what\n"\ + "happened to daisy, your pet rabbit.\n"\ + "\n"\ + "but now, you see spread before you more\n"\ + "potential pain and gibbitude as a nation\n"\ + "of demons run amok among our cities.\n"\ + "\n"\ + "next stop, hell on earth!" + + +/* after level 6, put this: */ + +#define C1TEXT \ + "YOU HAVE ENTERED DEEPLY INTO THE INFESTED\n" \ + "STARPORT. BUT SOMETHING IS WRONG. THE\n" \ + "MONSTERS HAVE BROUGHT THEIR OWN REALITY\n" \ + "WITH THEM, AND THE STARPORT'S TECHNOLOGY\n" \ + "IS BEING SUBVERTED BY THEIR PRESENCE.\n" \ + "\n"\ + "AHEAD, YOU SEE AN OUTPOST OF HELL, A\n" \ + "FORTIFIED ZONE. IF YOU CAN GET PAST IT,\n" \ + "YOU CAN PENETRATE INTO THE HAUNTED HEART\n" \ + "OF THE STARBASE AND FIND THE CONTROLLING\n" \ + "SWITCH WHICH HOLDS EARTH'S POPULATION\n" \ + "HOSTAGE." + +/* After level 11, put this: */ + +#define C2TEXT \ + "YOU HAVE WON! YOUR VICTORY HAS ENABLED\n" \ + "HUMANKIND TO EVACUATE EARTH AND ESCAPE\n"\ + "THE NIGHTMARE. NOW YOU ARE THE ONLY\n"\ + "HUMAN LEFT ON THE FACE OF THE PLANET.\n"\ + "CANNIBAL MUTATIONS, CARNIVOROUS ALIENS,\n"\ + "AND EVIL SPIRITS ARE YOUR ONLY NEIGHBORS.\n"\ + "YOU SIT BACK AND WAIT FOR DEATH, CONTENT\n"\ + "THAT YOU HAVE SAVED YOUR SPECIES.\n"\ + "\n"\ + "BUT THEN, EARTH CONTROL BEAMS DOWN A\n"\ + "MESSAGE FROM SPACE: \"SENSORS HAVE LOCATED\n"\ + "THE SOURCE OF THE ALIEN INVASION. IF YOU\n"\ + "GO THERE, YOU MAY BE ABLE TO BLOCK THEIR\n"\ + "ENTRY. THE ALIEN BASE IS IN THE HEART OF\n"\ + "YOUR OWN HOME CITY, NOT FAR FROM THE\n"\ + "STARPORT.\" SLOWLY AND PAINFULLY YOU GET\n"\ + "UP AND RETURN TO THE FRAY." + + +/* After level 20, put this: */ + +#define C3TEXT \ + "YOU ARE AT THE CORRUPT HEART OF THE CITY,\n"\ + "SURROUNDED BY THE CORPSES OF YOUR ENEMIES.\n"\ + "YOU SEE NO WAY TO DESTROY THE CREATURES'\n"\ + "ENTRYWAY ON THIS SIDE, SO YOU CLENCH YOUR\n"\ + "TEETH AND PLUNGE THROUGH IT.\n"\ + "\n"\ + "THERE MUST BE A WAY TO CLOSE IT ON THE\n"\ + "OTHER SIDE. WHAT DO YOU CARE IF YOU'VE\n"\ + "GOT TO GO THROUGH HELL TO GET TO IT?" + + +/* After level 29, put this: */ + +#define C4TEXT \ + "THE HORRENDOUS VISAGE OF THE BIGGEST\n"\ + "DEMON YOU'VE EVER SEEN CRUMBLES BEFORE\n"\ + "YOU, AFTER YOU PUMP YOUR ROCKETS INTO\n"\ + "HIS EXPOSED BRAIN. THE MONSTER SHRIVELS\n"\ + "UP AND DIES, ITS THRASHING LIMBS\n"\ + "DEVASTATING UNTOLD MILES OF HELL'S\n"\ + "SURFACE.\n"\ + "\n"\ + "YOU'VE DONE IT. THE INVASION IS OVER.\n"\ + "EARTH IS SAVED. HELL IS A WRECK. YOU\n"\ + "WONDER WHERE BAD FOLKS WILL GO WHEN THEY\n"\ + "DIE, NOW. WIPING THE SWEAT FROM YOUR\n"\ + "FOREHEAD YOU BEGIN THE LONG TREK BACK\n"\ + "HOME. REBUILDING EARTH OUGHT TO BE A\n"\ + "LOT MORE FUN THAN RUINING IT WAS.\n" + +/* Before level 31, put this: */ + +#define C5TEXT \ + "CONGRATULATIONS, YOU'VE FOUND THE SECRET\n"\ + "LEVEL! LOOKS LIKE IT'S BEEN BUILT BY\n"\ + "HUMANS, RATHER THAN DEMONS. YOU WONDER\n"\ + "WHO THE INMATES OF THIS CORNER OF HELL\n"\ + "WILL BE." + + +/* Before level 32, put this: */ + +#define C6TEXT \ + "CONGRATULATIONS, YOU'VE FOUND THE\n"\ + "SUPER SECRET LEVEL! YOU'D BETTER\n"\ + "BLAZE THROUGH THIS ONE!\n" + +/*** Plutonia ***/ +/* after map 06 */ + +#define P1TEXT \ + "You gloat over the steaming carcass of the\n"\ + "Guardian. With its death, you've wrested\n"\ + "the Accelerator from the stinking claws\n"\ + "of Hell. You relax and glance around the\n"\ + "room. Damn! There was supposed to be at\n"\ + "least one working prototype, but you can't\n"\ + "see it. The demons must have taken it.\n"\ + "\n"\ + "You must find the prototype, or all your\n"\ + "struggles will have been wasted. Keep\n"\ + "moving, keep fighting, keep killing.\n"\ + "Oh yes, keep living, too." + + +/* after map 11 */ + +#define P2TEXT \ + "Even the deadly Arch-Vile labyrinth could\n"\ + "not stop you, and you've gotten to the\n"\ + "prototype Accelerator which is soon\n"\ + "efficiently and permanently deactivated.\n"\ + "\n"\ + "You're good at that kind of thing." + + +/* after map 20 */ + +#define P3TEXT \ + "You've bashed and battered your way into\n"\ + "the heart of the devil-hive. Time for a\n"\ + "Search-and-Destroy mission, aimed at the\n"\ + "Gatekeeper, whose foul offspring is\n"\ + "cascading to Earth. Yeah, he's bad. But\n"\ + "you know who's worse!\n"\ + "\n"\ + "Grinning evilly, you check your gear, and\n"\ + "get ready to give the bastard a little Hell\n"\ + "of your own making!" + +/* after map 30 */ + +#define P4TEXT \ + "The Gatekeeper's evil face is splattered\n"\ + "all over the place. As its tattered corpse\n"\ + "collapses, an inverted Gate forms and\n"\ + "sucks down the shards of the last\n"\ + "prototype Accelerator, not to mention the\n"\ + "few remaining demons. You're done. Hell\n"\ + "has gone back to pounding bad dead folks \n"\ + "instead of good live ones. Remember to\n"\ + "tell your grandkids to put a rocket\n"\ + "launcher in your coffin. If you go to Hell\n"\ + "when you die, you'll need it for some\n"\ + "final cleaning-up ..." + +/* before map 31 */ + +#define P5TEXT \ + "You've found the second-hardest level we\n"\ + "got. Hope you have a saved game a level or\n"\ + "two previous. If not, be prepared to die\n"\ + "aplenty. For master marines only." + +/* before map 32 */ + +#define P6TEXT \ + "Betcha wondered just what WAS the hardest\n"\ + "level we had ready for ya? Now you know.\n"\ + "No one gets out alive." + +/*** TNT: Evilution ***/ + +#define T1TEXT \ + "You've fought your way out of the infested\n"\ + "experimental labs. It seems that UAC has\n"\ + "once again gulped it down. With their\n"\ + "high turnover, it must be hard for poor\n"\ + "old UAC to buy corporate health insurance\n"\ + "nowadays..\n"\ + "\n"\ + "Ahead lies the military complex, now\n"\ + "swarming with diseased horrors hot to get\n"\ + "their teeth into you. With luck, the\n"\ + "complex still has some warlike ordnance\n"\ + "laying around." + + +#define T2TEXT \ + "You hear the grinding of heavy machinery\n"\ + "ahead. You sure hope they're not stamping\n"\ + "out new hellspawn, but you're ready to\n"\ + "ream out a whole herd if you have to.\n"\ + "They might be planning a blood feast, but\n"\ + "you feel about as mean as two thousand\n"\ + "maniacs packed into one mad killer.\n"\ + "\n"\ + "You don't plan to go down easy." + + +#define T3TEXT \ + "The vista opening ahead looks real damn\n"\ + "familiar. Smells familiar, too -- like\n"\ + "fried excrement. You didn't like this\n"\ + "place before, and you sure as hell ain't\n"\ + "planning to like it now. The more you\n"\ + "brood on it, the madder you get.\n"\ + "Hefting your gun, an evil grin trickles\n"\ + "onto your face. Time to take some names." + +#define T4TEXT \ + "Suddenly, all is silent, from one horizon\n"\ + "to the other. The agonizing echo of Hell\n"\ + "fades away, the nightmare sky turns to\n"\ + "blue, the heaps of monster corpses start \n"\ + "to evaporate along with the evil stench \n"\ + "that filled the air. Jeeze, maybe you've\n"\ + "done it. Have you really won?\n"\ + "\n"\ + "Something rumbles in the distance.\n"\ + "A blue light begins to glow inside the\n"\ + "ruined skull of the demon-spitter." + + +#define T5TEXT \ + "What now? Looks totally different. Kind\n"\ + "of like King Tut's condo. Well,\n"\ + "whatever's here can't be any worse\n"\ + "than usual. Can it? Or maybe it's best\n"\ + "to let sleeping gods lie.." + + +#define T6TEXT \ + "Time for a vacation. You've burst the\n"\ + "bowels of hell and by golly you're ready\n"\ + "for a break. You mutter to yourself,\n"\ + "Maybe someone else can kick Hell's ass\n"\ + "next time around. Ahead lies a quiet town,\n"\ + "with peaceful flowing water, quaint\n"\ + "buildings, and presumably no Hellspawn.\n"\ + "\n"\ + "As you step off the transport, you hear\n"\ + "the stomp of a cyberdemon's iron shoe." + + + +/* + * Character cast strings F_FINALE.C + */ +#define CC_ZOMBIE "ZOMBIEMAN" +#define CC_SHOTGUN "SHOTGUN GUY" +#define CC_HEAVY "HEAVY WEAPON DUDE" +#define CC_IMP "IMP" +#define CC_DEMON "DEMON" +#define CC_LOST "LOST SOUL" +#define CC_CACO "CACODEMON" +#define CC_HELL "HELL KNIGHT" +#define CC_BARON "BARON OF HELL" +#define CC_ARACH "ARACHNOTRON" +#define CC_PAIN "PAIN ELEMENTAL" +#define CC_REVEN "REVENANT" +#define CC_MANCU "MANCUBUS" +#define CC_ARCH "ARCH-VILE" +#define CC_SPIDER "THE SPIDER MASTERMIND" +#define CC_CYBER "THE CYBERDEMON" +#define CC_HERO "OUR HERO" + + +#endif diff --git a/components/doom/prboom/d_event.h b/components/doom/prboom/d_event.h new file mode 100644 index 0000000..da5e702 --- /dev/null +++ b/components/doom/prboom/d_event.h @@ -0,0 +1,125 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * Event information structures. + * + *-----------------------------------------------------------------------------*/ + + +#ifndef __D_EVENT__ +#define __D_EVENT__ + + +#include "doomtype.h" + + +// +// Event handling. +// + +// Input event types. +typedef enum +{ + ev_keydown, + ev_keyup, + ev_mouse, + ev_joystick +} evtype_t; + +// Event structure. +typedef struct +{ + evtype_t type; + int data1; // keys / mouse/joystick buttons + int data2; // mouse/joystick x move + int data3; // mouse/joystick y move +} event_t; + + +typedef enum +{ + ga_nothing, + ga_loadlevel, + ga_newgame, + ga_loadgame, + ga_savegame, + ga_playdemo, + ga_completed, + ga_victory, + ga_worlddone, +} gameaction_t; + + + +// +// Button/action code definitions. +// +typedef enum +{ + // Press "Fire". + BT_ATTACK = 1, + + // Use button, to open doors, activate switches. + BT_USE = 2, + + // Flag: game events, not really buttons. + BT_SPECIAL = 128, + BT_SPECIALMASK = 3, + + // Flag, weapon change pending. + // If true, the next 4 bits hold weapon num. + BT_CHANGE = 4, + + // The 4bit weapon mask and shift, convenience. +//BT_WEAPONMASK = (8+16+32), + BT_WEAPONMASK = (8+16+32+64), // extended to pick up SSG // phares + BT_WEAPONSHIFT = 3, + + // Special events + BTS_LOADGAME = 0, // Loads a game + // Pause the game. + BTS_PAUSE = 1, + // Save the game at each console. + BTS_SAVEGAME = 2, + BTS_RESTARTLEVEL= 3, // Restarts the current level + + // Savegame slot numbers occupy the second byte of buttons. + BTS_SAVEMASK = (4+8+16), + BTS_SAVESHIFT = 2, + +} buttoncode_t; + + +// +// GLOBAL VARIABLES +// + +extern gameaction_t gameaction; + +#endif diff --git a/components/doom/prboom/d_items.c b/components/doom/prboom/d_items.c new file mode 100644 index 0000000..5adc28d --- /dev/null +++ b/components/doom/prboom/d_items.c @@ -0,0 +1,140 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * Something to do with weapon sprite frames. Don't ask me. + * + *----------------------------------------------------------------------------- + */ + +// We are referring to sprite numbers. +#include "doomtype.h" +#include "info.h" + +#ifdef __GNUG__ +#pragma implementation "d_items.h" +#endif +#include "d_items.h" + + +// +// PSPRITE ACTIONS for waepons. +// This struct controls the weapon animations. +// +// Each entry is: +// ammo/amunition type +// upstate +// downstate +// readystate +// atkstate, i.e. attack/fire/hit frame +// flashstate, muzzle flash +// +weaponinfo_t weaponinfo[NUMWEAPONS] = +{ + { + // fist + am_noammo, + S_PUNCHUP, + S_PUNCHDOWN, + S_PUNCH, + S_PUNCH1, + S_NULL + }, + { + // pistol + am_clip, + S_PISTOLUP, + S_PISTOLDOWN, + S_PISTOL, + S_PISTOL1, + S_PISTOLFLASH + }, + { + // shotgun + am_shell, + S_SGUNUP, + S_SGUNDOWN, + S_SGUN, + S_SGUN1, + S_SGUNFLASH1 + }, + { + // chaingun + am_clip, + S_CHAINUP, + S_CHAINDOWN, + S_CHAIN, + S_CHAIN1, + S_CHAINFLASH1 + }, + { + // missile launcher + am_misl, + S_MISSILEUP, + S_MISSILEDOWN, + S_MISSILE, + S_MISSILE1, + S_MISSILEFLASH1 + }, + { + // plasma rifle + am_cell, + S_PLASMAUP, + S_PLASMADOWN, + S_PLASMA, + S_PLASMA1, + S_PLASMAFLASH1 + }, + { + // bfg 9000 + am_cell, + S_BFGUP, + S_BFGDOWN, + S_BFG, + S_BFG1, + S_BFGFLASH1 + }, + { + // chainsaw + am_noammo, + S_SAWUP, + S_SAWDOWN, + S_SAW, + S_SAW1, + S_NULL + }, + { + // super shotgun + am_shell, + S_DSGUNUP, + S_DSGUNDOWN, + S_DSGUN, + S_DSGUN1, + S_DSGUNFLASH1 + }, +}; diff --git a/components/doom/prboom/d_items.h b/components/doom/prboom/d_items.h new file mode 100644 index 0000000..8da4df2 --- /dev/null +++ b/components/doom/prboom/d_items.h @@ -0,0 +1,59 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * Items: key cards, artifacts, weapon, ammunition. + * + *-----------------------------------------------------------------------------*/ + + +#ifndef __D_ITEMS__ +#define __D_ITEMS__ + +#include "doomdef.h" + +#ifdef __GNUG__ +#pragma interface +#endif + + +/* Weapon info: sprite frames, ammunition use. */ +typedef struct +{ + ammotype_t ammo; + int upstate; + int downstate; + int readystate; + int atkstate; + int flashstate; + +} weaponinfo_t; + +extern weaponinfo_t weaponinfo[NUMWEAPONS]; + +#endif diff --git a/components/doom/prboom/d_main.c b/components/doom/prboom/d_main.c new file mode 100644 index 0000000..d147cc6 --- /dev/null +++ b/components/doom/prboom/d_main.c @@ -0,0 +1,1070 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2004 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * DOOM main program (D_DoomMain) and game loop (D_DoomLoop), + * plus functions to determine game mode (shareware, registered), + * parse command line parameters, configure game parameters (turbo), + * and call the startup functions. + * + *----------------------------------------------------------------------------- + */ + + +#include +#include +#include +#include + +#include "doomdef.h" +#include "doomtype.h" +#include "doomstat.h" +#include "d_net.h" +#include "dstrings.h" +#include "sounds.h" +#include "z_zone.h" +#include "w_wad.h" +#include "s_sound.h" +#include "v_video.h" +#include "f_finale.h" +#include "f_wipe.h" +#include "m_argv.h" +#include "m_misc.h" +#include "m_menu.h" +#include "i_main.h" +#include "i_system.h" +#include "i_sound.h" +#include "i_video.h" +#include "g_game.h" +#include "hu_stuff.h" +#include "wi_stuff.h" +#include "st_stuff.h" +#include "am_map.h" +#include "p_setup.h" +#include "r_draw.h" +#include "r_main.h" +#include "r_fps.h" +#include "d_main.h" +#include "d_deh.h" // Ty 04/08/98 - Externalizations +#include "lprintf.h" // jff 08/03/98 - declaration of lprintf +#include "am_map.h" + +boolean devparm; // started game with -devparm + +// jff 1/24/98 add new versions of these variables to remember command line +boolean clnomonsters; // checkparm of -nomonsters +boolean clrespawnparm; // checkparm of -respawn +boolean clfastparm; // checkparm of -fast +// jff 1/24/98 end definition of command line version of play mode switches + +boolean nomonsters; // working -nomonsters +boolean respawnparm; // working -respawn +boolean fastparm; // working -fast + +boolean singletics = false; // debug flag to cancel adaptiveness + +//jff 1/22/98 parms for disabling music and sound +boolean nosfxparm; +boolean nomusicparm; + +//jff 4/18/98 +extern boolean inhelpscreens; + +skill_t startskill; +int startepisode; +int startmap; +boolean autostart; +int ffmap; + +boolean advancedemo; + +const char *basesavegame; + +static int demosequence; // killough 5/2/98: made static +static int pagetic; +static const char *pagename; // CPhipps - const + +// 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; + +//jff 4/19/98 list of standard IWAD names +const char *standard_iwads[]= +{ + "doom.wad", // Ultimate DOOM + "plutonia.wad", // Final DOOM - The Plutonia Experiment + "tnt.wad", // Final Doom - TNT: Evilution + "doom2.wad", // DOOM2 + "doom2f.wad", // DOOM2 French + "doom1.wad", // DOOM Shareware + "freedoom.wad", /* wart@kobold.org: added freedoom for Fedora Extras */ + NULL, +}; + +/* + * D_PostEvent - Event handling + * + * Called by I/O functions when an event is received. + * Try event handlers for each code area in turn. + * cph - in the true spirit of the Boom source, let the + * short ciruit operator madness begin! + */ + +void D_PostEvent(event_t *ev) +{ + /* cph - suppress all input events at game start + * FIXME: This is a lousy kludge */ + if (gametic < 3) return; + + if (M_Responder(ev)) + return; + + if (gamestate == GS_LEVEL) { + if (HU_Responder(ev)) + return; + if (ST_Responder(ev)) + return; + if (AM_Responder(ev)) + return; + } + + G_Responder(ev); +} + +// +// D_Wipe +// +// CPhipps - moved the screen wipe code from D_Display to here +// The screens to wipe between are already stored, this just does the timing +// and screen updating + +static void D_Wipe(void) +{ + boolean done; + int wipestart = I_GetTime () - 1; + + do + { + int nowtime, tics; + do + { + I_uSleep(5000); // CPhipps - don't thrash cpu in this loop + nowtime = I_GetTime(); + tics = nowtime - wipestart; + } + while (!tics); + wipestart = nowtime; + done = wipe_ScreenWipe(tics); + I_UpdateNoBlit(); + M_Drawer(); // menu is drawn even on top of wipes + I_FinishUpdate(); // page flip or blit buffer + } + while (!done); +} + +// +// D_PageDrawer +// +static void D_PageDrawer(void) +{ + // proff/nicolas 09/14/98 -- now stretchs bitmaps to fullscreen! + // CPhipps - updated for new patch drawing + // proff - added M_DrawCredits + if (pagename) + V_DrawNamePatch(0, 0, 0, pagename, CR_DEFAULT, VPT_STRETCH); + else + M_DrawCredits(); +} + +// +// D_Display +// draw current display, possibly wiping it from the previous +// + +void D_Display (void) +{ + static boolean isborderstate = false; + static boolean borderwillneedredraw = false; + static gamestate_t oldgamestate = -1; + boolean wipe = gamestate != wipegamestate; + boolean viewactive = false, isborder = false; + + if (nodrawers) // for comparative timing / profiling + return; + + if (!I_StartDisplay()) + return; + + // save the current screen if about to wipe + if (wipe) + wipe_StartScreen(); + + if (gamestate != GS_LEVEL) { // Not a level + switch (oldgamestate) { + case -1: + case GS_LEVEL: + V_SetPalette(0); // cph - use default (basic) palette + default: + break; + } + + switch (gamestate) { + case GS_INTERMISSION: + WI_Drawer(); + break; + case GS_FINALE: + F_Drawer(); + break; + case GS_DEMOSCREEN: + D_PageDrawer(); + break; + default: + break; + } + } else if (gametic != basetic) { // In a level + boolean redrawborderstuff; + + HU_Erase(); + + if (setsizeneeded) { // change the view size if needed + R_ExecuteSetViewSize(); + oldgamestate = -1; // force background redraw + } + + // Work out if the player view is visible, and if there is a border + viewactive = (!(automapmode & am_active) || (automapmode & am_overlay)) && !inhelpscreens; + isborder = viewactive ? (viewheight != SCREENHEIGHT) : (!inhelpscreens && (automapmode & am_active)); + + if (oldgamestate != GS_LEVEL) { + R_FillBackScreen (); // draw the pattern into the back screen + redrawborderstuff = isborder; + } else { + // CPhipps - + // If there is a border, and either there was no border last time, + // or the border might need refreshing, then redraw it. + redrawborderstuff = isborder && (!isborderstate || borderwillneedredraw); + // The border may need redrawing next time if the border surrounds the screen, + // and there is a menu being displayed + borderwillneedredraw = menuactive && isborder && viewactive && (viewwidth != SCREENWIDTH); + } + if (redrawborderstuff) + R_DrawViewBorder(); + + // Now do the drawing + if (viewactive) + R_RenderPlayerView (&players[displayplayer]); + if (automapmode & am_active) + AM_Drawer(); + ST_Drawer((viewheight != SCREENHEIGHT) || ((automapmode & am_active) && !(automapmode & am_overlay)), redrawborderstuff); + R_DrawViewBorder(); + HU_Drawer(); + } + + isborderstate = isborder; + oldgamestate = wipegamestate = gamestate; + + // draw pause pic + if (paused) { + // Simplified the "logic" here and no need for x-coord caching - POPE + V_DrawNamePatch((320 - V_NamePatchWidth("M_PAUSE"))/2, 4, + 0, "M_PAUSE", CR_DEFAULT, VPT_STRETCH); + } + + // menus go directly to the screen + M_Drawer(); // menu is drawn even on top of everything +#ifdef HAVE_NET + NetUpdate(); // send out any new accumulation +#else + D_BuildNewTiccmds(); +#endif + + // normal update + if (!wipe) + I_FinishUpdate (); // page flip or blit buffer + else { + // wipe update + wipe_EndScreen(); + D_Wipe(); + } + + I_EndDisplay(); + + //e6y: don't thrash cpu during pausing + if (paused) { + I_uSleep(1000); + } +} + +// +// D_DoomLoop() +// +// Not a globally visible function, +// just included for source reference, +// called by D_DoomMain, never exits. +// Manages timing and IO, +// calls all ?_Responder, ?_Ticker, and ?_Drawer, +// calls I_GetTime, I_StartFrame, and I_StartTic +// + +static void D_DoomLoop(void) +{ + for (;;) + { + WasRenderedInTryRunTics = false; + // frame syncronous IO operations + I_StartFrame (); + + if (ffmap == gamemap) ffmap = 0; + + // process one or more tics + if (singletics) + { + I_StartTic (); + G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]); + if (advancedemo) + D_DoAdvanceDemo (); + M_Ticker (); + G_Ticker (); + gametic++; + maketic++; + } + else + TryRunTics (); // will run at least one tic + + // killough 3/16/98: change consoleplayer to displayplayer + if (players[displayplayer].mo) // cph 2002/08/10 + S_UpdateSounds(players[displayplayer].mo);// move positional sounds + + // Update display, next frame, with current state. + if (!movement_smooth || !WasRenderedInTryRunTics || gamestate != wipegamestate) + D_Display(); + } +} + +// +// DEMO LOOP +// + +// +// D_PageTicker +// Handles timing for warped projection +// +void D_PageTicker(void) +{ + if (--pagetic < 0) + D_AdvanceDemo(); +} + +// +// D_AdvanceDemo +// Called after each demo or intro demosequence finishes +// +void D_AdvanceDemo (void) +{ + advancedemo = true; +} + +/* killough 11/98: functions to perform demo sequences + * cphipps 10/99: constness fixes + */ + +static void D_SetPageName(const char *name) +{ + pagename = name; +} + +static void D_DrawTitle1(const char *name) +{ + S_StartMusic(mus_intro); + pagetic = (TICRATE*170)/35; + D_SetPageName(name); +} + +static void D_DrawTitle2(const char *name) +{ + S_StartMusic(mus_dm2ttl); + D_SetPageName(name); +} + +/* killough 11/98: tabulate demo sequences + */ + +static struct +{ + void (*func)(const char *); + const char *name; +} const demostates[][4] = + { + { + {D_DrawTitle1, "TITLEPIC"}, + {D_DrawTitle1, "TITLEPIC"}, + {D_DrawTitle2, "TITLEPIC"}, + {D_DrawTitle1, "TITLEPIC"}, + }, + + { + {G_DeferedPlayDemo, "demo1"}, + {G_DeferedPlayDemo, "demo1"}, + {G_DeferedPlayDemo, "demo1"}, + {G_DeferedPlayDemo, "demo1"}, + }, + { + {D_SetPageName, NULL}, + {D_SetPageName, NULL}, + {D_SetPageName, NULL}, + {D_SetPageName, NULL}, + }, + + { + {G_DeferedPlayDemo, "demo2"}, + {G_DeferedPlayDemo, "demo2"}, + {G_DeferedPlayDemo, "demo2"}, + {G_DeferedPlayDemo, "demo2"}, + }, + + { +/* + {D_SetPageName, "HELP2"}, + {D_SetPageName, "HELP2"}, +*/ + {D_SetPageName, NULL}, + {D_SetPageName, NULL}, + {D_SetPageName, "CREDIT"}, + {D_DrawTitle1, "TITLEPIC"}, + }, + + { + {G_DeferedPlayDemo, "demo3"}, + {G_DeferedPlayDemo, "demo3"}, + {G_DeferedPlayDemo, "demo3"}, + {G_DeferedPlayDemo, "demo3"}, + }, + + { + {NULL}, + {NULL}, + {NULL}, + {D_SetPageName, "CREDIT"}, + }, + + { + {NULL}, + {NULL}, + {NULL}, + {G_DeferedPlayDemo, "demo4"}, + }, + + { + {NULL}, + {NULL}, + {NULL}, + {NULL}, + } + }; + +/* + * This cycles through the demo sequences. + * killough 11/98: made table-driven + */ + +void D_DoAdvanceDemo(void) +{ + players[consoleplayer].playerstate = PST_LIVE; /* not reborn */ + advancedemo = usergame = paused = false; + gameaction = ga_nothing; + + pagetic = TICRATE * 11; /* killough 11/98: default behavior */ + gamestate = GS_DEMOSCREEN; + + if (netgame && !demoplayback) + demosequence = 0; + else if (!demostates[++demosequence][gamemode].func) + demosequence = 0; + demostates[demosequence][gamemode].func + (demostates[demosequence][gamemode].name); +} + +// +// D_StartTitle +// +void D_StartTitle (void) +{ + gameaction = ga_nothing; + demosequence = -1; + D_AdvanceDemo(); +} + +// +// D_AddFile +// +// Add files to be loaded later by W_Init() +// +bool D_AddFile(const char *file) +{ + char relpath[PATH_MAX + 1]; + + if (numwadfiles == MAX_WAD_FILES) + I_Error("D_AddFile: Can't load more than %d WADs\n", MAX_WAD_FILES); + + snprintf(relpath, PATH_MAX, "%s/%s", I_DoomExeDir(), file); + + if (access(relpath, R_OK) == 0) + file = relpath; + else if (access(file, R_OK) != 0) { + lprintf(LO_WARN, "D_AddFile: %s not found\n", file); + return false; + } + + wadfiles[numwadfiles++] = (wadfile_info_t){ + .name = strdup(file), + .handle = NULL, + .data = NULL, + .size = 0, + }; + + return true; +} + +// +// CheckIWAD +// +// Verify a file is indeed tagged as an IWAD +// Scan its lumps for levelnames and return gamemode as indicated +// Detect missing wolf levels in DOOM II +// +// The filename to check is passed in iwadname, the gamemode detected is +// returned in gmode, hassec returns the presence of secret levels +// +// jff 4/19/98 Add routine to test IWAD for validity and determine +// the gamemode from it. Also note if DOOM II, whether secret levels exist +// CPhipps - const char* for iwadname, made static +static void CheckIWAD(wadfile_info_t *iwad,GameMode_t *gmode,boolean *hassec) +{ + int ud=0,rg=0,sw=0,cm=0,sc=0; + wadinfo_t header = {0}; + FILE *fp = NULL; + + if (!iwad) + I_Error("CheckIWAD: Can't open NULL IWAD"); + + if (iwad->data) + memcpy(&header, iwad->data, sizeof(header)); + else if ((fp = fopen(iwad->name, "rb"))) + fread(&header, sizeof(header), 1, fp); + else + I_Error("CheckIWAD: Can't open IWAD: %s", iwad->name); + + // read IWAD header + if (!strncmp(header.identification, "IWAD", 4)) + { + size_t length; + filelump_t *fileinfo; + + // read IWAD directory + header.numlumps = LONG(header.numlumps); + header.infotableofs = LONG(header.infotableofs); + length = header.numlumps; + fileinfo = calloc(sizeof(filelump_t), length); + + if (iwad->data) + memcpy(fileinfo, iwad->data + header.infotableofs, sizeof(filelump_t) * length); + else if (fseek(fp, header.infotableofs, SEEK_SET) == 0) + fread(fileinfo, sizeof(filelump_t), length, fp); + else + I_Error("CheckIWAD: Can't read IWAD: %s", iwad->name); + + // scan directory for levelname lumps + while (length--) + if (fileinfo[length].name[0] == 'E' && + fileinfo[length].name[2] == 'M' && + fileinfo[length].name[4] == 0) + { + if (fileinfo[length].name[1] == '4') + ++ud; + else if (fileinfo[length].name[1] == '3') + ++rg; + else if (fileinfo[length].name[1] == '2') + ++rg; + else if (fileinfo[length].name[1] == '1') + ++sw; + } + else if (fileinfo[length].name[0] == 'M' && + fileinfo[length].name[1] == 'A' && + fileinfo[length].name[2] == 'P' && + fileinfo[length].name[5] == 0) + { + ++cm; + if (fileinfo[length].name[3] == '3') + if (fileinfo[length].name[4] == '1' || + fileinfo[length].name[4] == '2') + ++sc; + } + + free(fileinfo); + } + else // missing IWAD tag in header + I_Error("CheckIWAD: IWAD tag %s not present", iwad->name); + + if (fp) + fclose(fp); + + // Determine game mode from levels present + // Must be a full set for whichever mode is present + // Lack of wolf-3d levels also detected here + + *gmode = indetermined; + *hassec = false; + if (cm>=30) + { + *gmode = commercial; + *hassec = sc>=2; + } + else if (ud>=9) + *gmode = retail; + else if (rg>=18) + *gmode = registered; + else if (sw>=9) + *gmode = shareware; +} + + +static void L_SetupConsoleMasks(void) { + int p; + int i; + const char *cena="ICWEFDA",*pos; //jff 9/3/98 use this for parsing console masks // CPhipps - const char*'s + + //jff 9/3/98 get mask for console output filter + if ((p = M_CheckParm ("-cout"))) { + lprintf(LO_DEBUG, "mask for console output: "); + if (++p != myargc && *myargv[p] != '-') + for (i=0,cons_output_mask=0;(size_t)iname); + + switch (gamemode) + { + case retail: + doomverstr = "The Ultimate DOOM"; + gamemission = doom; + break; + case shareware: + doomverstr = "DOOM Shareware"; + gamemission = doom; + break; + case registered: + doomverstr = "DOOM Registered"; + gamemission = doom; + break; + case commercial: // Ty 08/27/98 - fixed gamemode vs gamemission + p = strlen(iwad->name); + if (p>=7 && !strncasecmp(iwad->name+p-7,"tnt.wad",7)) { + doomverstr = "DOOM 2: TNT - Evilution"; + gamemission = pack_tnt; + } else if (p>=12 && !strncasecmp(iwad->name+p-12,"plutonia.wad",12)) { + doomverstr = "DOOM 2: Plutonia Experiment"; + gamemission = pack_plut; + } else { + doomverstr = "DOOM 2: Hell on Earth"; + gamemission = doom2; + } + break; + default: + doomverstr = "Public DOOM"; + gamemission = none; + break; + } + + /* cphipps - the main display. This shows the build date, copyright, and game type */ + lprintf(LO_ALWAYS, "%s %s (built %s), playing: %s\n" + "PrBoom is released under the GNU General Public license v2.0.\n" + "You are welcome to redistribute it under certain conditions.\n" + "It comes with ABSOLUTELY NO WARRANTY. See the file COPYING for details.\n", + PACKAGE, VERSION, version_date, doomverstr); + + // Add prboom.wad at the very beginning so it can be overriden by mods +#ifdef PRBOOMWAD + #include "prboom_wad.h" + wadfiles[numwadfiles++] = (wadfile_info_t){ + .name = "prboom.wad", + .data = prboom_wad, + .size = prboom_wad_size, + .handle = NULL, + }; +#else + if (!D_AddFile("prboom.wad")) + I_Error("PRBOOM.WAD not found\n"); +#endif + + if ((devparm = M_CheckParm("-devparm"))) + lprintf(LO_CONFIRM, "%s", s_D_DEVSTR); + + // set save path to -save parm or current dir + basesavegame = I_DoomExeDir(); + if ((p=M_CheckParm("-save")) && p 400) + scale = 400; + //jff 9/3/98 use logical output routine + lprintf (LO_CONFIRM,"turbo scale: %i%%\n",scale); + forwardmove[0] = forwardmove[0]*scale/100; + forwardmove[1] = forwardmove[1]*scale/100; + sidemove[0] = sidemove[0]*scale/100; + sidemove[1] = sidemove[1]*scale/100; + } + + // get skill / episode / map from parms + + startskill = sk_none; // jff 3/24/98 was sk_medium, just note not picked + startepisode = 1; + startmap = 1; + autostart = false; + + if ((p = M_CheckParm ("-skill")) && p < myargc-1) + { + startskill = myargv[p+1][0]-'1'; + autostart = true; + } + + if ((p = M_CheckParm ("-episode")) && p < myargc-1) + { + startepisode = myargv[p+1][0]-'0'; + startmap = 1; + autostart = true; + } + + if ((p = M_CheckParm ("-timer")) && p < myargc-1 && deathmatch) + { + int time = atoi(myargv[p+1]); + //jff 9/3/98 use logical output routine + lprintf(LO_CONFIRM,"Levels will end after %d minute%s.\n", time, time>1 ? "s" : ""); + } + + if ((p = M_CheckParm ("-warp")) || // killough 5/2/98 + (p = M_CheckParm ("-wart"))) + // Ty 08/29/98 - moved this check later so we can have -warp alone: && p < myargc-1) + { + startmap = 0; // Ty 08/29/98 - allow "-warp x" to go to first map in wad(s) + autostart = true; // Ty 08/29/98 - move outside the decision tree + if (gamemode == commercial) + { + if (p < myargc-1) + startmap = atoi(myargv[p+1]); // Ty 08/29/98 - add test if last parm + } + else // 1/25/98 killough: fix -warp xxx from crashing Doom 1 / UD + { + if (p < myargc-2) + { + startepisode = atoi(myargv[++p]); + startmap = atoi(myargv[p+1]); + } + } + } + // Ty 08/29/98 - later we'll check for startmap=0 and autostart=true + // as a special case that -warp * was used. Actually -warp with any + // non-numeric will do that but we'll only document "*" + + nomusicparm = M_CheckParm("-nosound") || M_CheckParm("-nomusic"); + nosfxparm = M_CheckParm("-nosound") || M_CheckParm("-nosfx"); + nodrawers = M_CheckParm ("-nodraw"); + noblit = M_CheckParm ("-noblit"); + + //proff 11/22/98: Added setting of viewangleoffset + if ((p = M_CheckParm("-viewangle"))) + { + viewangleoffset = atoi(myargv[p+1]); + viewangleoffset = viewangleoffset<0 ? 0 : (viewangleoffset>7 ? 7 : viewangleoffset); + viewangleoffset = (8-viewangleoffset) * ANG45; + } + + // init subsystems + + G_ReloadDefaults(); // killough 3/4/98: set defaults just loaded. + // jff 3/24/98 this sets startskill if it was -1 + +#ifndef NODEHSUPPORT + // ty 03/09/98 do dehacked stuff + // Note: do this before any other since it is expected by + // the deh patch author that this is actually part of the EXE itself + // Using -deh in BOOM, others use -dehacked. + // Ty 03/18/98 also allow .bex extension. .bex overrides if both exist. + + if ((p = M_CheckParm ("-deh"))) + { + char file[PATH_MAX+1]; // cph - localised + // the parms after p are deh/bex file names, + // until end of parms or another - preceded parm + // Ty 04/11/98 - Allow multiple -deh files in a row + + while (++p != myargc && *myargv[p] != '-') + { + AddDefaultExtension(strcpy(file, myargv[p]), ".bex"); + if (access(file, F_OK)) // nope + { + AddDefaultExtension(strcpy(file, myargv[p]), ".deh"); + if (access(file, F_OK)) // still nope + I_Error("D_DoomMainSetup: Cannot find .deh or .bex file named %s",myargv[p]); + } + // during the beta we have debug output to dehout.txt + D_ProcessDehFile(file,D_dehout(),0); + } + } + // ty 03/09/98 end of do dehacked stuff +#endif + + // add any files specified on the command line with -file wadfile + // to the wad list + if ((p = M_CheckParm ("-file"))) + { + // the parms after p are wadfile/lump names, + // until end of parms or another - preceded parm + while (++p != myargc && *myargv[p] != '-') + if (D_AddFile(myargv[p])) + modifiedgame = true; + } + + if (!(p = M_CheckParm("-playdemo")) || p >= myargc-1) { /* killough */ + if ((p = M_CheckParm ("-fastdemo")) && p < myargc-1) /* killough */ + fastdemo = true; // run at fastest speed possible + else + p = M_CheckParm ("-timedemo"); + } + + if (p && p < myargc-1) + { + char file[PATH_MAX+1]; // cph - localised + strcpy(file,myargv[p+1]); + AddDefaultExtension(file,".lmp"); // killough + D_AddFile (file); + //jff 9/3/98 use logical output routine + lprintf(LO_CONFIRM,"Playing demo %s\n",file); + if ((p = M_CheckParm ("-ffmap")) && p < myargc-1) { + ffmap = atoi(myargv[p+1]); + } + } + + // internal translucency set to config file value // phares + general_translucency = default_translucency; // phares + + lprintf(LO_INFO, "I_Init: Setting up machine state.\n"); + I_Init(); + + // CPhipps - move up netgame init + lprintf(LO_INFO, "D_InitNetGame: Checking for network game.\n"); + D_InitNetGame(); + + lprintf(LO_INFO, "W_Init: Init WADfiles.\n"); + W_Init(); // CPhipps - handling of wadfiles init changed + + lprintf(LO_INFO, "V_Init: Setting up video.\n"); + V_Init(SCREENWIDTH, SCREENHEIGHT, default_videomode); + +#ifndef NODEHSUPPORT + // e6y: option to disable automatic loading of dehacked-in-wad lump + if (!M_CheckParm ("-nodeh")) + if ((p = W_CheckNumForName("DEHACKED")) != -1) // cph - add dehacked-in-a-wad support + D_ProcessDehFile(NULL, D_dehout(), p); +#endif + + lprintf(LO_INFO, "M_Init: Init miscellaneous info.\n"); + M_Init(); + +#ifdef HAVE_NET + // CPhipps - now wait for netgame start + D_CheckNetGame(); +#endif + + lprintf(LO_INFO, "R_Init: Init DOOM refresh daemon:\n"); + R_Init(); + + lprintf(LO_INFO, "P_Init: Init Playloop state.\n"); + P_Init(); + + lprintf(LO_INFO, "S_Init: Setting up sound.\n"); + S_Init(snd_SfxVolume, snd_MusicVolume); + + lprintf(LO_INFO, "HU_Init: Setting up heads up display.\n"); + HU_Init(); + + lprintf(LO_INFO, "ST_Init: Init status bar.\n"); + ST_Init(); + + // Show hacked startup messages, if any + if (*startup1 || *startup2 || *startup3 || *startup4 || *startup5) + { + if (*startup1) lprintf(LO_INFO, "%s", startup1); + if (*startup2) lprintf(LO_INFO, "%s", startup2); + if (*startup3) lprintf(LO_INFO, "%s", startup3); + if (*startup4) lprintf(LO_INFO, "%s", startup4); + if (*startup5) lprintf(LO_INFO, "%s", startup5); + } + + lprintf(LO_INFO, "\n"); + + idmusnum = -1; //jff 3/17/98 insure idmus number is blank + + // start the apropriate game based on parms + + // killough 12/98: + // Support -loadgame with -record and reimplement -recordfrom. + + if ((slot = M_CheckParm("-recordfrom")) && (p = slot+2) < myargc) + G_RecordDemo(myargv[p]); + else + { + slot = M_CheckParm("-loadgame"); + if ((p = M_CheckParm("-record")) && ++p < myargc) + { + autostart = true; + G_RecordDemo(myargv[p]); + } + } + + if ((p = M_CheckParm ("-fastdemo")) && ++p < myargc) + { // killough + fastdemo = true; // run at fastest speed possible + timingdemo = true; // show stats after quit + G_DeferedPlayDemo(myargv[p]); + singledemo = true; // quit after one demo + } + else if ((p = M_CheckParm("-timedemo")) && ++p < myargc) + { + singletics = true; + timingdemo = true; // show stats after quit + G_DeferedPlayDemo(myargv[p]); + singledemo = true; // quit after one demo + } + else if ((p = M_CheckParm("-playdemo")) && ++p < myargc) + { + G_DeferedPlayDemo(myargv[p]); + singledemo = true; // quit after one demo + } + + if (slot && ++slot < myargc) + { + slot = atoi(myargv[slot]); // killough 3/16/98: add slot info + G_LoadGame(slot, true); // killough 5/15/98: add command flag // cph - no filename + } + else if (!singledemo) + { + if (autostart || netgame) + { + G_InitNew(startskill, startepisode, startmap); + if (demorecording) + G_BeginRecording(); + } + else + D_StartTitle(); // start up intro loop + } +} + +// +// D_DoomMain +// + +void D_DoomMain(void) +{ + D_DoomMainSetup(); // CPhipps - setup out of main execution stack + D_DoomLoop(); // never returns +} diff --git a/components/doom/prboom/d_main.h b/components/doom/prboom/d_main.h new file mode 100644 index 0000000..43d8c86 --- /dev/null +++ b/components/doom/prboom/d_main.h @@ -0,0 +1,75 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * Main startup and splash screenstuff. + * + *-----------------------------------------------------------------------------*/ + +#ifndef __D_MAIN__ +#define __D_MAIN__ + +#include "d_event.h" +#include "w_wad.h" + +#ifdef __GNUG__ +#pragma interface +#endif + +extern const char *basesavegame; // killough 2/16/98: savegame path + +//jff 1/24/98 make command line copies of play modes available +extern boolean clnomonsters; // checkparm of -nomonsters +extern boolean clrespawnparm; // checkparm of -respawn +extern boolean clfastparm; // checkparm of -fast +//jff end of external declaration of command line playmode + +extern boolean nosfxparm; +extern boolean nomusicparm; +extern int ffmap; + +// Called by IO functions when input is detected. +void D_PostEvent(event_t* ev); + +// Demo stuff +extern boolean advancedemo; +void D_AdvanceDemo(void); +void D_DoAdvanceDemo (void); + +// +// BASE LEVEL +// + +void D_Display(void); +void D_PageTicker(void); +void D_StartTitle(void); +void D_DoomMain(void); +void D_DoomMainSetup(void); +bool D_AddFile(const char *file); + +#endif diff --git a/components/doom/prboom/d_net.h b/components/doom/prboom/d_net.h new file mode 100644 index 0000000..fcdeb6c --- /dev/null +++ b/components/doom/prboom/d_net.h @@ -0,0 +1,214 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * Networking stuff. + * + *-----------------------------------------------------------------------------*/ + + +#ifndef __D_NET__ +#define __D_NET__ + +#include "d_player.h" + + +#ifdef __GNUG__ +#pragma interface +#endif + + +// +// Network play related stuff. +// There is a data struct that stores network +// communication related stuff, and another +// one that defines the actual packets to +// be transmitted. +// + +#define DOOMCOM_ID 0x12345678l + +// Max computers/players in a game. +#define MAXNETNODES 8 + + +typedef enum +{ + CMD_SEND = 1, + CMD_GET = 2 + +} command_t; + + +// +// Network packet data. +// +typedef struct +{ + // High bit is retransmit request. + unsigned checksum; + // Only valid if NCMD_RETRANSMIT. + byte retransmitfrom; + + byte starttic; + byte player; + byte numtics; + ticcmd_t cmds[BACKUPTICS]; + +} doomdata_t; + +// +// Startup packet difference +// SG: 4/12/98 +// Added so we can send more startup data to synch things like +// bobbing, recoil, etc. +// this is just mapped over the ticcmd_t array when setup packet is sent +// +// Note: the original code takes care of startskill, deathmatch, nomonsters +// respawn, startepisode, startmap +// Note: for phase 1 we need to add monsters_remember, variable_friction, +// weapon_recoil, allow_pushers, over_under, player_bobbing, +// fastparm, demo_insurance, and the rngseed +//Stick all options into bytes so we don't need to mess with bitfields +//WARNING: make sure this doesn't exceed the size of the ticcmds area! +//sizeof(ticcmd_t)*BACKUPTICS +//This is the current length of our extra stuff +// +//killough 5/2/98: this should all be replaced by calls to G_WriteOptions() +//and G_ReadOptions(), which were specifically designed to set up packets. +//By creating a separate struct and functions to read/write the options, +//you now have two functions and data to maintain instead of just one. +//If the array in g_game.c which G_WriteOptions()/G_ReadOptions() operates +//on, is too large (more than sizeof(ticcmd_t)*BACKUPTICS), it can +//either be shortened, or the net code needs to divide it up +//automatically into packets. The STARTUPLEN below is non-portable. +//There's a portable way to do it without having to know the sizes. + +#define STARTUPLEN 12 +typedef struct +{ + byte monsters_remember; + byte variable_friction; + byte weapon_recoil; + byte allow_pushers; + byte over_under; + byte player_bobbing; + byte fastparm; + byte demo_insurance; + unsigned long rngseed; + char filler[sizeof(ticcmd_t)*BACKUPTICS-STARTUPLEN]; +} startup_t; + +typedef enum { + // Leave space, so low values corresponding to normal netgame setup packets can be ignored + nm_plcolour = 3, + nm_savegamename = 4, +} netmisctype_t; + +typedef struct +{ + netmisctype_t type; + size_t len; + byte value[sizeof(ticcmd_t)*BACKUPTICS - sizeof(netmisctype_t) - sizeof(size_t)]; +} netmisc_t; + +typedef struct +{ + // Supposed to be DOOMCOM_ID? + long id; + + // DOOM executes an int to execute commands. + short intnum; + // Communication between DOOM and the driver. + // Is CMD_SEND or CMD_GET. + short command; + // Is dest for send, set by get (-1 = no packet). + short remotenode; + + // Number of bytes in doomdata to be sent + short datalength; + + // Info common to all nodes. + // Console is allways node 0. + short numnodes; + // Flag: 1 = no duplication, 2-5 = dup for slow nets. + short ticdup; + // Flag: 1 = send a backup tic in every packet. + short extratics; + // Flag: 1 = deathmatch. + short deathmatch; + // Flag: -1 = new game, 0-5 = load savegame + short savegame; + short episode; // 1-3 + short map; // 1-9 + short skill; // 1-5 + + // Info specific to this node. + short consoleplayer; + short numplayers; + + // These are related to the 3-display mode, + // in which two drones looking left and right + // were used to render two additional views + // on two additional computers. + // Probably not operational anymore. + // 1 = left, 0 = center, -1 = right + short angleoffset; + // 1 = drone + short drone; + + // The packet data to be sent. + doomdata_t data; + +} doomcom_t; + +// Create any new ticcmds and broadcast to other players. +#ifdef HAVE_NET +void NetUpdate (void); +#else +void D_BuildNewTiccmds(void); +#endif + +//? how many ticks to run? +void TryRunTics (void); + +// CPhipps - move to header file +void D_InitNetGame (void); // This does the setup +void D_CheckNetGame(void); // This waits for game start + +// CPhipps - misc info broadcast +void D_NetSendMisc(netmisctype_t type, size_t len, void* data); + +// CPhipps - ask server for a wad file we need +boolean D_NetGetWad(const char* name); + +// Netgame stuff (buffers and pointers, i.e. indices). +extern doomcom_t *doomcom; +extern doomdata_t *netbuffer; // This points inside doomcom. + +#endif diff --git a/components/doom/prboom/d_player.h b/components/doom/prboom/d_player.h new file mode 100644 index 0000000..a45abe8 --- /dev/null +++ b/components/doom/prboom/d_player.h @@ -0,0 +1,234 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * Player state structure. + * + *-----------------------------------------------------------------------------*/ + + +#ifndef __D_PLAYER__ +#define __D_PLAYER__ + + +// The player data structure depends on a number +// of other structs: items (internal inventory), +// animation states (closely tied to the sprites +// used to represent them, unfortunately). +#include "d_items.h" +#include "p_pspr.h" + +// In addition, the player is just a special +// case of the generic moving object/actor. +#include "p_mobj.h" + +// Finally, for odd reasons, the player input +// is buffered within the player data struct, +// as commands per game tick. +#include "d_ticcmd.h" + +#ifdef __GNUG__ +#pragma interface +#endif + + +// +// Player states. +// +typedef enum +{ + // Playing or camping. + PST_LIVE, + // Dead on the ground, view follows killer. + PST_DEAD, + // Ready to restart/respawn??? + PST_REBORN + +} playerstate_t; + + +// +// Player internal flags, for cheats and debug. +// +typedef enum +{ + // No clipping, walk through barriers. + CF_NOCLIP = 1, + // No damage, no health loss. + CF_GODMODE = 2, + // Not really a cheat, just a debug aid. + CF_NOMOMENTUM = 4 + +} cheat_t; + + +// +// Extended player object info: player_t +// +typedef struct player_s +{ + mobj_t* mo; + playerstate_t playerstate; + ticcmd_t cmd; + + // Determine POV, + // including viewpoint bobbing during movement. + // Focal origin above r.z + fixed_t viewz; + // Base height above floor for viewz. + fixed_t viewheight; + // Bob/squat speed. + fixed_t deltaviewheight; + // bounded/scaled total momentum. + fixed_t bob; + + /* killough 10/98: used for realistic bobbing (i.e. not simply overall speed) + * mo->momx and mo->momy represent true momenta experienced by player. + * This only represents the thrust that the player applies himself. + * This avoids anomolies with such things as Boom ice and conveyors. + */ + fixed_t momx, momy; // killough 10/98 + + // This is only used between levels, + // mo->health is used during levels. + int health; + int armorpoints; + // Armor type is 0-2. + int armortype; + + // Power ups. invinc and invis are tic counters. + int powers[NUMPOWERS]; + boolean cards[NUMCARDS]; + boolean backpack; + + // Frags, kills of other players. + int frags[MAXPLAYERS]; + weapontype_t readyweapon; + + // Is wp_nochange if not changing. + weapontype_t pendingweapon; + + boolean weaponowned[NUMWEAPONS]; + int ammo[NUMAMMO]; + int maxammo[NUMAMMO]; + + // True if button down last tic. + int attackdown; + int usedown; + + // Bit flags, for cheats and debug. + // See cheat_t, above. + int cheats; + + // Refired shots are less accurate. + int refire; + + // For intermission stats. + int killcount; + int itemcount; + int secretcount; + + // Hint messages. // CPhipps - const + const char* message; + + // For screen flashing (red or bright). + int damagecount; + int bonuscount; + + // Who did damage (NULL for floors/ceilings). + mobj_t* attacker; + + // So gun flashes light up areas. + int extralight; + + // Current PLAYPAL, ??? + // can be set to REDCOLORMAP for pain, etc. + int fixedcolormap; + + // Player skin colorshift, + // 0-3 for which color to draw player. + int colormap; + + // Overlay view sprites (gun, etc). + pspdef_t psprites[NUMPSPRITES]; + + // True if secret level has been done. + boolean didsecret; + +} player_t; + + +// +// INTERMISSION +// Structure passed e.g. to WI_Start(wb) +// +typedef struct +{ + boolean in; // whether the player is in game + + // Player stats, kills, collected items etc. + int skills; + int sitems; + int ssecret; + int stime; + int frags[4]; + int score; // current score on entry, modified on return + +} wbplayerstruct_t; + +typedef struct +{ + int epsd; // episode # (0-2) + + // if true, splash the secret level + boolean didsecret; + + // previous and next levels, origin 0 + int last; + int next; + + int maxkills; + int maxitems; + int maxsecret; + int maxfrags; + + // the par time + int partime; + + // index of this player in game + int pnum; + + wbplayerstruct_t plyr[MAXPLAYERS]; + + // CPhipps - total game time for completed levels so far + int totaltimes; + +} wbstartstruct_t; + + +#endif diff --git a/components/doom/prboom/d_server.c b/components/doom/prboom/d_server.c new file mode 100644 index 0000000..dd16483 --- /dev/null +++ b/components/doom/prboom/d_server.c @@ -0,0 +1,754 @@ +/* Emacs style mode select -*- C++ -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2004 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * Network game server code + * New for LxDoom, but drawing ideas and code fragments from the + * earlier net code + *----------------------------------------------------------------------------- + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include + +#ifdef USE_SDL_NET + #include "SDL.h" +#endif + +#include "doomtype.h" +#include "protocol.h" +#include "i_network.h" +#ifndef PRBOOM_SERVER +#include "m_fixed.h" +#endif +#include "i_system.h" +#include "m_swap.h" + +#ifndef HAVE_NET + +int main(void) +{ + fprintf(stderr, + PACKAGE "-server: You must compile with networking enabled!\n"); + exit(1); + return 1; +} + +#else + +#ifndef HAVE_GETOPT +/* The following code for getopt is from the libc-source of FreeBSD, + * it might be changed a little bit. + * Florian Schulze (florian.schulze@gmx.net) + */ + +/* + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)getopt.c 8.3 (Berkeley) 4/27/95"; +#endif +static const char rcsid[] = "$FreeBSD$"; +#endif /* LIBC_SCCS and not lint */ + +int opterr = 1, /* if error message should be printed */ + optind = 1, /* index into parent argv vector */ + optopt, /* character checked for validity */ + optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ + +#define BADCH (int)'?' +#define BADARG (int)':' +#define EMSG "" + +char *__progname="prboom_server"; + +/* + * getopt -- + * Parse argc/argv argument vector. + */ +int +getopt(nargc, nargv, ostr) + int nargc; + char * const *nargv; + const char *ostr; +{ + extern char *__progname; + static char *place = EMSG; /* option letter processing */ + char *oli; /* option letter list index */ + int ret; + + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc || *(place = nargv[optind]) != '-') { + place = EMSG; + return (-1); + } + if (place[1] && *++place == '-') { /* found "--" */ + ++optind; + place = EMSG; + return (-1); + } + } /* option letter okay? */ + if ((optopt = (int)*place++) == (int)':' || + !(oli = strchr(ostr, optopt))) { + /* + * if the user didn't specify '-' as an option, + * assume it means -1. + */ + if (optopt == (int)'-') + return (-1); + if (!*place) + ++optind; + if (opterr && *ostr != ':') + (void)fprintf(stderr, + "%s: illegal option -- %c\n", __progname, optopt); + return (BADCH); + } + if (*++oli != ':') { /* don't need argument */ + optarg = NULL; + if (!*place) + ++optind; + } + else { /* need an argument */ + if (*place) /* no white space */ + optarg = place; + else if (nargc <= ++optind) { /* no arg */ + place = EMSG; + if (*ostr == ':') + ret = BADARG; + else + ret = BADCH; + if (opterr) + (void)fprintf(stderr, + "%s: option requires an argument -- %c\n", + __progname, optopt); + return (ret); + } + else /* white space */ + optarg = nargv[optind]; + place = EMSG; + ++optind; + } + return (optopt); /* dump back option letter */ +} +#else +#include +#endif + +#define MAXPLAYERS 4 +#define BACKUPTICS 12 + +// Dummies to forfill l_udp.c unused client stuff +int M_CheckParm(const char* p) { p = NULL; return 1; } +int myargc; +char** myargv; + +void NORETURN I_Error(const char *error, ...) // killough 3/20/98: add const +{ + va_list argptr; + va_start(argptr,error); + vfprintf(stderr,error,argptr); + va_end(argptr); + exit(-1); +} + +int playerjoingame[MAXPLAYERS], playerleftgame[MAXPLAYERS]; +UDP_CHANNEL remoteaddr[MAXPLAYERS]; +enum { pc_unused, pc_connected, pc_ready, pc_confirmedready, pc_playing, pc_quit } playerstate[MAXPLAYERS]; +int displaycounter; + +boolean n_players_in_state(int n, int ps) { + int i,j; + for (i=j=0;itic); +} + + + +void read_config_file(FILE* fp, struct setup_packet_s* sp) +{ + byte* gameopt = sp->game_options; + + while (!feof(fp)) { + char def[80]; + char strparm[100]; + if (fscanf (fp, "%79s %99[^\n]\n", def, strparm) == 2) { + int v = atoi(strparm); + if (!strcmp(def,"default_skill")) { + if (verbose) printf("config file sets default_skill to %d\n",v); + sp->skill = v-1; + } else if (!strcmp(def,"default_compatibility_level")) { + if (verbose) printf("config file sets compatibility_level to %d\n",v); + if (v == -1) v = MAX_COMPATIBILITY_LEVEL-1; //e6y: -1 => maxcompat + sp->complevel = v; + } else { + int i; + for (i=0; i= MAXPLAYERS); } + +int main(int argc, char** argv) +{ +#ifndef USE_SDL_NET + int localport = 5030; +#else + Uint16 localport = 5030; +#endif + int numplayers = 2, xtratics = 0, ticdup = 1; + int exectics = 0; // gametics completed + struct setup_packet_s setupinfo = { 2, 0, 1, 1, 1, 0, best_compatibility, 0, 0}; + char**wadname = NULL; + char**wadget = NULL; + int numwads = 0; + { + int opt; + byte *gameopt = setupinfo.game_options; + + memcpy(gameopt, &def_game_options, sizeof (setupinfo.game_options)); + while ((opt = getopt(argc, argv, "c:t:x:p:e:l:adrfns:N:vw:")) != EOF) + switch (opt) { + case 'c': + { + FILE *cf = fopen(optarg,"r"); + if (!cf) { perror("fopen"); return -1; } + read_config_file(cf,&setupinfo); + fclose(cf); + } + break; + case 't': + if (optarg) ticdup = atoi(optarg); + break; + case 'x': + if (optarg) xtratics = atoi(optarg); + break; + case 'p': + if (optarg) localport = atoi(optarg); + break; + case 'e': + if (optarg) setupinfo.episode = atoi(optarg); + break; + case 'l': + if (optarg) setupinfo.level = atoi(optarg); + break; + case 'a': + setupinfo.deathmatch = 2; + break; + case 'd': + setupinfo.deathmatch = 1; + break; + case 'r': + setupinfo.game_options[6] = 1; + break; + case 'f': + setupinfo.game_options[7] = 1; + break; + case 'n': + setupinfo.game_options[8] = 1; + break; + case 's': + if (optarg) setupinfo.skill = atoi(optarg)-1; + break; + case 'N': + if (optarg) setupinfo.players = numplayers = atoi(optarg); + break; + case 'v': + verbose++; + break; + case 'w': + if (optarg) { + char *p; + wadname = realloc(wadname, ++numwads * sizeof *wadname); + wadget = realloc(wadget , numwads * sizeof *wadget ); + wadname[numwads-1] = strdup(optarg); + if ((p = strchr(wadname[numwads-1], ','))) { + *p++ = 0; wadget[numwads-1] = p; + } else wadget[numwads-1] = NULL; + } + break; + } + } + + setupinfo.ticdup = ticdup; setupinfo.extratic = xtratics; + { /* Random number seed + * Mirrors the corresponding code in G_ReadOptions */ + int rngseed = (int)time(NULL); + setupinfo.game_options[13] = rngseed & 0xff; + rngseed >>= 8; + setupinfo.game_options[12] = rngseed & 0xff; + rngseed >>= 8; + setupinfo.game_options[11] = rngseed & 0xff; + rngseed >>= 8; + setupinfo.game_options[10] = rngseed & 0xff; + } + I_InitSockets(localport); + + printf("Listening on port %d, waiting for %d players\n", localport, numplayers); + + { // no players initially + int i; + for (i=0; i2) printf("Received packet:"); + switch (packet->type) { + case PKT_INIT: + if (!ingame) { + { + int n; + struct setup_packet_s *sinfo = (void*)(packet+1); + + /* Find player number and add to the game */ + n = *(short*)(packet+1); + + if (badplayer(n) || playerstate[n] != pc_unused) + for (n=0; nyourplayer = n; + sinfo->numwads = numwads; + for (i=0; iwadnames + extrabytes, wadname[i]); + extrabytes += strlen(wadname[i]) + 1; + } + I_SendPacketTo(packet, sizeof *packet + sizeof setupinfo + extrabytes, + remoteaddr+n); + I_uSleep(10000); + I_SendPacketTo(packet, sizeof *packet + sizeof setupinfo + extrabytes, + remoteaddr+n); + } + } + } + break; + case PKT_GO: + if (!ingame) { + int from = *(byte*)(packet+1); + + if (badplayer(from) || playerstate[from] == pc_unused) break; + if (confirming) { + if (playerstate[from] != pc_confirmedready) curplayers++; + playerstate[from] = pc_confirmedready; + } else + playerstate[from] = pc_ready; + } + break; + case PKT_TICC: + { + byte tics = *(byte*)(packet+1); + int from = *(((byte*)(packet+1))+1); + + if (badplayer(from)) break; + + if (verbose>2) + printf("tics %ld - %ld from %d\n", ptic(packet), ptic(packet) + tics - 1, from); + if (ptic(packet) > remoteticfrom[from]) { + // Missed tics, so request a resend + packet_set(packet, PKT_RETRANS, remoteticfrom[from]); + I_SendPacketTo(packet, sizeof *packet, remoteaddr+from); + } else { + ticcmd_t *newtic = (void*)(((byte*)(packet+1))+2); + if (ptic(packet) + tics < remoteticfrom[from]) break; // Won't help + remoteticfrom[from] = ptic(packet); + while (tics--) + netcmds[from][remoteticfrom[from]++%BACKUPTICS] = *newtic++; + } + } + break; + case PKT_RETRANS: + { + int from = *(byte*)(packet+1); + if (badplayer(from)) break; + + if (verbose>2) printf("%d requests resend from %d\n", from, ptic(packet)); + remoteticto[from] = ptic(packet); + } + break; + case PKT_QUIT: + { + int from = *(byte*)(packet+1); + if (badplayer(from)) break; + + if (!ingame && playerstate[from] != pc_unused) { + // If we already got a PKT_GO, we have to remove this player frmo the count of ready players. And we then flag this player slot as vacant. + printf("player %d pulls out\n", from); + if (playerstate[from] == pc_confirmedready) curplayers--; + playerstate[from] = pc_unused; + } else + if (playerleftgame[from] == INT_MAX) { // In the game + playerleftgame[from] = ptic(packet); + --curplayers; + if (verbose) printf("%d quits at %d (%d left)\n", from, ptic(packet),curplayers); + if (ingame && !curplayers) exit(0); // All players have exited + } + } + // Fall through and broadcast it + case PKT_EXTRA: + BroadcastPacket(packet, len); + if (packet->type == PKT_EXTRA) { + if (verbose>2) printf("misc from %d\n", *(((byte*)(packet+1))+1)); + } + break; + case PKT_WAD: + { + int i; + int from = *(byte*)(packet+1); + char *name = 1 + (char*)(packet+1); + size_t size = sizeof(packet_header_t); + packet_header_t *reply; + + if (badplayer(from) || playerstate[from] != pc_unused) break; + + if (verbose) printf("Request for %s ", name); + for (i=0; itype); + break; + } + } + free(packet); + if (!ingame && n_players_in_state(numplayers,pc_confirmedready)) { + int i; + packet_header_t gopacket; + packet = &gopacket; + ingame=true; + printf("All players joined, beginning game.\n"); + for (i=0; i1) printf("%d new tics can be run\n", lowtic - exectics); + + if (lowtic > exectics) + exectics = lowtic; // count exec'ed tics + // Now send all tics up to lowtic + for (i=0; i1) printf("sending %d tics to %d\n", tics, i); + while (tics--) { + int j, playersthistic = 0; + byte *q = p++; + for (j=0; j remoteticto[i])) { + *p++ = j; + memcpy(p, &netcmds[j][remoteticto[i]%BACKUPTICS], sizeof(ticcmd_t)); + p += sizeof(ticcmd_t); + playersthistic++; + } + *q = playersthistic; + remoteticto[i]++; + } + I_SendPacketTo(packet, p - ((byte*)packet), remoteaddr+i); + free(packet); + } + { + if (remoteticfrom[i] == remoteticto[i]) { + backoffcounter[i] = 0; + } else if (remoteticfrom[i] > remoteticto[i]+1) { + if ((backoffcounter[i] += remoteticfrom[i] - remoteticto[i] - 1) > 35) { + packet_header_t *packet = malloc(sizeof(packet_header_t)); + packet_set(packet, PKT_BACKOFF, remoteticto[i]); + I_SendPacketTo(packet,sizeof *packet,remoteaddr+i); + backoffcounter[i] = 0; + if (verbose) printf("telling client %d to back off\n",i); + free(packet); + } + } + } + } + } + if (!((ingame ? 0xff : 0xf) & displaycounter++)) { + int i; + fprintf(stderr,"Player states: ["); + for (i=0;i +#include +#include +//#include "dosbox.h" +#include "dbopl.h" + +#ifdef _MSC_VER +#define inline __inline +#endif + +#define GCC_UNLIKELY(x) x + +#define TRUE 1 +#define FALSE 0 + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +#define OPLRATE ((double)(14318180.0 / 288.0)) + +//Try to use most precision for frequencies +//Else try to keep different waves in synch +//#define WAVE_PRECISION 1 +#ifndef WAVE_PRECISION +//Wave bits available in the top of the 32bit range +//Original adlib uses 10.10, we use 10.22 +#define WAVE_BITS 10 +#else +//Need some extra bits at the top to have room for octaves and frequency multiplier +//We support to 8 times lower rate +//128 * 15 * 8 = 15350, 2^13.9, so need 14 bits +#define WAVE_BITS 14 +#endif +#define WAVE_SH ( 32 - WAVE_BITS ) +#define WAVE_MASK ( ( 1 << WAVE_SH ) - 1 ) + +//Use the same accuracy as the waves +#define LFO_SH ( WAVE_SH - 10 ) +//LFO is controlled by our tremolo 256 sample limit +#define LFO_MAX ( 256 << ( LFO_SH ) ) + + +//Maximum amount of attenuation bits +//Envelope goes to 511, 9 bits +#if (DBOPL_WAVE == WAVE_TABLEMUL ) +//Uses the value directly +#define ENV_BITS ( 9 ) +#else +//Add 3 bits here for more accuracy and would have to be shifted up either way +#define ENV_BITS ( 9 ) +#endif +//Limits of the envelope with those bits and when the envelope goes silent +#define ENV_MIN 0 +#define ENV_EXTRA ( ENV_BITS - 9 ) +#define ENV_MAX ( 511 << ENV_EXTRA ) +#define ENV_LIMIT ( ( 12 * 256) >> ( 3 - ENV_EXTRA ) ) +#define ENV_SILENT( _X_ ) ( (_X_) >= ENV_LIMIT ) + +//Attack/decay/release rate counter shift +#define RATE_SH 24 +#define RATE_MASK ( ( 1 << RATE_SH ) - 1 ) +//Has to fit within 16bit lookuptable +#define MUL_SH 16 + +//Check some ranges +#if ENV_EXTRA > 3 +#error Too many envelope bits +#endif + +static inline void Operator__SetState(Operator *self, Bit8u s ); +static inline Bit32u Chip__ForwardNoise(Chip *self); + +// C++'s template<> sure is useful sometimes. + +static Channel* Channel__BlockTemplate(Channel *self, Chip* chip, + Bit32u samples, Bit32s* output, + SynthMode mode ); +#define BLOCK_TEMPLATE(mode) \ + static Channel* Channel__BlockTemplate_ ## mode(Channel *self, Chip* chip, \ + Bit32u samples, Bit32s* output) \ + { \ + return Channel__BlockTemplate(self, chip, samples, output, mode); \ + } + +BLOCK_TEMPLATE(sm2AM) +BLOCK_TEMPLATE(sm2FM) +BLOCK_TEMPLATE(sm3AM) +BLOCK_TEMPLATE(sm3FM) +BLOCK_TEMPLATE(sm3FMFM) +BLOCK_TEMPLATE(sm3AMFM) +BLOCK_TEMPLATE(sm3FMAM) +BLOCK_TEMPLATE(sm3AMAM) +BLOCK_TEMPLATE(sm2Percussion) +BLOCK_TEMPLATE(sm3Percussion) + +//How much to substract from the base value for the final attenuation +static const Bit8u KslCreateTable[16] = { + //0 will always be be lower than 7 * 8 + 64, 32, 24, 19, + 16, 12, 11, 10, + 8, 6, 5, 4, + 3, 2, 1, 0, +}; + +#define M(_X_) ((Bit8u)( (_X_) * 2)) +static const Bit8u FreqCreateTable[16] = { + M(0.5), M(1 ), M(2 ), M(3 ), M(4 ), M(5 ), M(6 ), M(7 ), + M(8 ), M(9 ), M(10), M(10), M(12), M(12), M(15), M(15) +}; +#undef M + +//We're not including the highest attack rate, that gets a special value +static const Bit8u AttackSamplesTable[13] = { + 69, 55, 46, 40, + 35, 29, 23, 20, + 19, 15, 11, 10, + 9 +}; +//On a real opl these values take 8 samples to reach and are based upon larger tables +static const Bit8u EnvelopeIncreaseTable[13] = { + 4, 5, 6, 7, + 8, 10, 12, 14, + 16, 20, 24, 28, + 32, +}; + +#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) +Bit16u *ExpTable=NULL; // [ 256 ]; +#endif + +#if ( DBOPL_WAVE == WAVE_HANDLER ) +//PI table used by WAVEHANDLER +Bit16u *SinTable = NULL; // [ 512 ]; +#endif + +#if ( DBOPL_WAVE > WAVE_HANDLER ) +//Layout of the waveform table in 512 entry intervals +//With overlapping waves we reduce the table to half it's size + +// | |//\\|____|WAV7|//__|/\ |____|/\/\| +// |\\//| | |WAV7| | \/| | | +// |06 |0126|17 |7 |3 |4 |4 5 |5 | + +//6 is just 0 shifted and masked + +Bit16s *WaveTable = NULL; // [ 8 * 512 ]; +//Distance into WaveTable the wave starts +static const Bit16u WaveBaseTable[8] = { + 0x000, 0x200, 0x200, 0x800, + 0xa00, 0xc00, 0x100, 0x400, + +}; +//Mask the counter with this +static const Bit16u WaveMaskTable[8] = { + 1023, 1023, 511, 511, + 1023, 1023, 512, 1023, +}; + +//Where to start the counter on at keyon +static const Bit16u WaveStartTable[8] = { + 512, 0, 0, 0, + 0, 512, 512, 256, +}; +#endif + +#if ( DBOPL_WAVE == WAVE_TABLEMUL ) +Bit16u *MulTable = NULL; // [ 384 ]; +#endif + +Bit8u *KslTable = NULL; // [ 8 * 16 ]; +Bit8u *TremoloTable = NULL; // [ TREMOLO_TABLE ]; +//Start of a channel behind the chip struct start +Bit16u *ChanOffsetTable = NULL; // [32]; +//Start of an operator behind the chip struct start +Bit16u *OpOffsetTable = NULL; // [64]; + +//The lower bits are the shift of the operator vibrato value +//The highest bit is right shifted to generate -1 or 0 for negation +//So taking the highest input value of 7 this gives 3, 7, 3, 0, -3, -7, -3, 0 +static const Bit8s VibratoTable[ 8 ] = { + 1 - 0x00, 0 - 0x00, 1 - 0x00, 30 - 0x00, + 1 - 0x80, 0 - 0x80, 1 - 0x80, 30 - 0x80 +}; + +//Shift strength for the ksl value determined by ksl strength +static const Bit8u KslShiftTable[4] = { + 31,1,2,0 +}; + +//Generate a table index and table shift value using input value from a selected rate +static void EnvelopeSelect( Bit8u val, Bit8u *index, Bit8u *shift ) { + if ( val < 13 * 4 ) { //Rate 0 - 12 + *shift = 12 - ( val >> 2 ); + *index = val & 3; + } else if ( val < 15 * 4 ) { //rate 13 - 14 + *shift = 0; + *index = val - 12 * 4; + } else { //rate 15 and up + *shift = 0; + *index = 12; + } +} + +#if ( DBOPL_WAVE == WAVE_HANDLER ) +/* + Generate the different waveforms out of the sine/exponetial table using handlers +*/ +static inline Bits MakeVolume( Bitu wave, Bitu volume ) { + Bitu total = wave + volume; + Bitu index = total & 0xff; + Bitu sig = ExpTable[ index ]; + Bitu exp = total >> 8; +#if 0 + //Check if we overflow the 31 shift limit + if ( exp >= 32 ) { + LOG_MSG( "WTF %d %d", total, exp ); + } +#endif + return (sig >> exp); +}; + +static Bits DB_FASTCALL WaveForm0( Bitu i, Bitu volume ) { + Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 + Bitu wave = SinTable[i & 511]; + return (MakeVolume( wave, volume ) ^ neg) - neg; +} +static Bits DB_FASTCALL WaveForm1( Bitu i, Bitu volume ) { + Bit32u wave = SinTable[i & 511]; + wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm2( Bitu i, Bitu volume ) { + Bitu wave = SinTable[i & 511]; + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm3( Bitu i, Bitu volume ) { + Bitu wave = SinTable[i & 255]; + wave |= ( ( (i ^ 256 ) & 256) - 1) >> ( 32 - 12 ); + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm4( Bitu i, Bitu volume ) { + //Twice as fast + i <<= 1; + Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 + Bitu wave = SinTable[i & 511]; + wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); + return (MakeVolume( wave, volume ) ^ neg) - neg; +} +static Bits DB_FASTCALL WaveForm5( Bitu i, Bitu volume ) { + //Twice as fast + i <<= 1; + Bitu wave = SinTable[i & 511]; + wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm6( Bitu i, Bitu volume ) { + Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 + return (MakeVolume( 0, volume ) ^ neg) - neg; +} +static Bits DB_FASTCALL WaveForm7( Bitu i, Bitu volume ) { + //Negative is reversed here + Bits neg = (( i >> 9) & 1) - 1; + Bitu wave = (i << 3); + //When negative the volume also runs backwards + wave = ((wave ^ neg) - neg) & 4095; + return (MakeVolume( wave, volume ) ^ neg) - neg; +} + +static const WaveHandler WaveHandlerTable[8] = { + WaveForm0, WaveForm1, WaveForm2, WaveForm3, + WaveForm4, WaveForm5, WaveForm6, WaveForm7 +}; + +#endif + +/* + Operator +*/ + +//We zero out when rate == 0 +static inline void Operator__UpdateAttack(Operator *self, const Chip* chip ) { + Bit8u rate = self->reg60 >> 4; + if ( rate ) { + Bit8u val = (rate << 2) + self->ksr; + self->attackAdd = chip->attackRates[ val ]; + self->rateZero &= ~(1 << ATTACK); + } else { + self->attackAdd = 0; + self->rateZero |= (1 << ATTACK); + } +} +static inline void Operator__UpdateDecay(Operator *self, const Chip* chip ) { + Bit8u rate = self->reg60 & 0xf; + if ( rate ) { + Bit8u val = (rate << 2) + self->ksr; + self->decayAdd = chip->linearRates[ val ]; + self->rateZero &= ~(1 << DECAY); + } else { + self->decayAdd = 0; + self->rateZero |= (1 << DECAY); + } +} +static inline void Operator__UpdateRelease(Operator *self, const Chip* chip ) { + Bit8u rate = self->reg80 & 0xf; + if ( rate ) { + Bit8u val = (rate << 2) + self->ksr; + self->releaseAdd = chip->linearRates[ val ]; + self->rateZero &= ~(1 << RELEASE); + if ( !(self->reg20 & MASK_SUSTAIN ) ) { + self->rateZero &= ~( 1 << SUSTAIN ); + } + } else { + self->rateZero |= (1 << RELEASE); + self->releaseAdd = 0; + if ( !(self->reg20 & MASK_SUSTAIN ) ) { + self->rateZero |= ( 1 << SUSTAIN ); + } + } +} + +static inline void Operator__UpdateAttenuation(Operator *self) { + Bit8u kslBase = (Bit8u)((self->chanData >> SHIFT_KSLBASE) & 0xff); + Bit32u tl = self->reg40 & 0x3f; + Bit8u kslShift = KslShiftTable[ self->reg40 >> 6 ]; + //Make sure the attenuation goes to the right bits + self->totalLevel = tl << ( ENV_BITS - 7 ); //Total level goes 2 bits below max + self->totalLevel += ( kslBase << ENV_EXTRA ) >> kslShift; +} + +static void Operator__UpdateFrequency(Operator *self) { + Bit32u freq = self->chanData & (( 1 << 10 ) - 1); + Bit32u block = (self->chanData >> 10) & 0xff; +#ifdef WAVE_PRECISION + block = 7 - block; + self->waveAdd = ( freq * self->freqMul ) >> block; +#else + self->waveAdd = ( freq << block ) * self->freqMul; +#endif + if ( self->reg20 & MASK_VIBRATO ) { + self->vibStrength = (Bit8u)(freq >> 7); + +#ifdef WAVE_PRECISION + self->vibrato = ( self->vibStrength * self->freqMul ) >> block; +#else + self->vibrato = ( self->vibStrength << block ) * self->freqMul; +#endif + } else { + self->vibStrength = 0; + self->vibrato = 0; + } +} + +static void Operator__UpdateRates(Operator *self, const Chip* chip ) { + //Mame seems to reverse this where enabling ksr actually lowers + //the rate, but pdf manuals says otherwise? + Bit8u newKsr = (Bit8u)((self->chanData >> SHIFT_KEYCODE) & 0xff); + if ( !( self->reg20 & MASK_KSR ) ) { + newKsr >>= 2; + } + if ( self->ksr == newKsr ) + return; + self->ksr = newKsr; + Operator__UpdateAttack( self, chip ); + Operator__UpdateDecay( self, chip ); + Operator__UpdateRelease( self, chip ); +} + +static inline Bit32s Operator__RateForward(Operator *self, Bit32u add ) { + Bit32s ret; + self->rateIndex += add; + /*Bit32s*/ ret = self->rateIndex >> RATE_SH; + self->rateIndex = self->rateIndex & RATE_MASK; + return ret; +} + +static Bits Operator__TemplateVolume(Operator *self, OperatorState yes) { + Bit32s vol = self->volume; + Bit32s change; + switch ( yes ) { + case OFF: + return ENV_MAX; + case ATTACK: + change = Operator__RateForward( self, self->attackAdd ); + if ( !change ) + return vol; + vol += ( (~vol) * change ) >> 3; + if ( vol < ENV_MIN ) { + self->volume = ENV_MIN; + self->rateIndex = 0; + Operator__SetState( self, DECAY ); + return ENV_MIN; + } + break; + case DECAY: + vol += Operator__RateForward( self, self->decayAdd ); + if ( GCC_UNLIKELY(vol >= self->sustainLevel) ) { + //Check if we didn't overshoot max attenuation, then just go off + if ( GCC_UNLIKELY(vol >= ENV_MAX) ) { + self->volume = ENV_MAX; + Operator__SetState( self, OFF ); + return ENV_MAX; + } + //Continue as sustain + self->rateIndex = 0; + Operator__SetState( self, SUSTAIN ); + } + break; + case SUSTAIN: + if ( self->reg20 & MASK_SUSTAIN ) { + return vol; + } + /* fallthrough */ + //In sustain phase, but not sustaining, do regular release + case RELEASE: + vol += Operator__RateForward( self, self->releaseAdd );; + if ( GCC_UNLIKELY(vol >= ENV_MAX) ) { + self->volume = ENV_MAX; + Operator__SetState( self, OFF ); + return ENV_MAX; + } + break; + } + self->volume = vol; + return vol; +} + +#define TEMPLATE_VOLUME(mode) \ + static Bits Operator__TemplateVolume ## mode(Operator *self) \ + { \ + return Operator__TemplateVolume(self, mode); \ + } + +TEMPLATE_VOLUME(OFF) +TEMPLATE_VOLUME(RELEASE) +TEMPLATE_VOLUME(SUSTAIN) +TEMPLATE_VOLUME(ATTACK) +TEMPLATE_VOLUME(DECAY) + +static const VolumeHandler VolumeHandlerTable[5] = { + &Operator__TemplateVolumeOFF, + &Operator__TemplateVolumeRELEASE, + &Operator__TemplateVolumeSUSTAIN, + &Operator__TemplateVolumeDECAY, + &Operator__TemplateVolumeATTACK, +}; + +static inline Bitu Operator__ForwardVolume(Operator *self) { + return self->currentLevel + (self->volHandler)(self); +} + + +static inline Bitu Operator__ForwardWave(Operator *self) { + self->waveIndex += self->waveCurrent; + return self->waveIndex >> WAVE_SH; +} + +static void Operator__Write20(Operator *self, const Chip* chip, Bit8u val ) { + Bit8u change = (self->reg20 ^ val ); + if ( !change ) + return; + self->reg20 = val; + //Shift the tremolo bit over the entire register, saved a branch, YES! + self->tremoloMask = (Bit8s)(val) >> 7; + self->tremoloMask &= ~(( 1 << ENV_EXTRA ) -1); + //Update specific features based on changes + if ( change & MASK_KSR ) { + Operator__UpdateRates( self, chip ); + } + //With sustain enable the volume doesn't change + if ( self->reg20 & MASK_SUSTAIN || ( !self->releaseAdd ) ) { + self->rateZero |= ( 1 << SUSTAIN ); + } else { + self->rateZero &= ~( 1 << SUSTAIN ); + } + //Frequency multiplier or vibrato changed + if ( change & (0xf | MASK_VIBRATO) ) { + self->freqMul = chip->freqMul[ val & 0xf ]; + Operator__UpdateFrequency(self); + } +} + +static void Operator__Write40(Operator *self, const Chip *chip, Bit8u val ) { + if (!(self->reg40 ^ val )) + return; + self->reg40 = val; + Operator__UpdateAttenuation( self ); +} + +static void Operator__Write60(Operator *self, const Chip* chip, Bit8u val ) { + Bit8u change = self->reg60 ^ val; + self->reg60 = val; + if ( change & 0x0f ) { + Operator__UpdateDecay( self, chip ); + } + if ( change & 0xf0 ) { + Operator__UpdateAttack( self, chip ); + } +} + +static void Operator__Write80(Operator *self, const Chip* chip, Bit8u val ) { + Bit8u change = (self->reg80 ^ val ); + Bit8u sustain; + if ( !change ) + return; + self->reg80 = val; + sustain = val >> 4; + //Turn 0xf into 0x1f + sustain |= ( sustain + 1) & 0x10; + self->sustainLevel = sustain << ( ENV_BITS - 5 ); + if ( change & 0x0f ) { + Operator__UpdateRelease( self, chip ); + } +} + +static void Operator__WriteE0(Operator *self, const Chip* chip, Bit8u val ) { + Bit8u waveForm; + if ( !(self->regE0 ^ val) ) + return; + //in opl3 mode you can always selet 7 waveforms regardless of waveformselect + waveForm = val & ( ( 0x3 & chip->waveFormMask ) | (0x7 & chip->opl3Active ) ); + self->regE0 = val; +#if( DBOPL_WAVE == WAVE_HANDLER ) + self->waveHandler = WaveHandlerTable[ waveForm ]; +#else + self->waveBase = WaveTable + WaveBaseTable[ waveForm ]; + self->waveStart = WaveStartTable[ waveForm ] << WAVE_SH; + self->waveMask = WaveMaskTable[ waveForm ]; +#endif +} + +static inline void Operator__SetState(Operator *self, Bit8u s ) { + self->state = s; + self->volHandler = VolumeHandlerTable[ s ]; +} + +static inline int Operator__Silent(Operator *self) { + if ( !ENV_SILENT( self->totalLevel + self->volume ) ) + return FALSE; + if ( !(self->rateZero & ( 1 << self->state ) ) ) + return FALSE; + return TRUE; +} + +static inline void Operator__Prepare(Operator *self, const Chip* chip ) { + self->currentLevel = self->totalLevel + (chip->tremoloValue & self->tremoloMask); + self->waveCurrent = self->waveAdd; + if ( self->vibStrength >> chip->vibratoShift ) { + Bit32s add = self->vibrato >> chip->vibratoShift; + //Sign extend over the shift value + Bit32s neg = chip->vibratoSign; + //Negate the add with -1 or 0 + add = ( add ^ neg ) - neg; + self->waveCurrent += add; + } +} + +static void Operator__KeyOn(Operator *self, Bit8u mask ) { + if ( !self->keyOn ) { + //Restart the frequency generator +#if( DBOPL_WAVE > WAVE_HANDLER ) + self->waveIndex = self->waveStart; +#else + self->waveIndex = 0; +#endif + self->rateIndex = 0; + Operator__SetState( self, ATTACK ); + } + self->keyOn |= mask; +} + +static void Operator__KeyOff(Operator *self, Bit8u mask ) { + self->keyOn &= ~mask; + if ( !self->keyOn ) { + if ( self->state != OFF ) { + Operator__SetState( self, RELEASE ); + } + } +} + +static inline Bits Operator__GetWave(Operator *self, Bitu index, Bitu vol ) { +#if( DBOPL_WAVE == WAVE_HANDLER ) + return self->waveHandler( index, vol << ( 3 - ENV_EXTRA ) ); +#elif( DBOPL_WAVE == WAVE_TABLEMUL ) + return(self->waveBase[ index & self->waveMask ] * MulTable[ vol >> ENV_EXTRA ]) >> MUL_SH; +#elif( DBOPL_WAVE == WAVE_TABLELOG ) + Bit32s wave = self->waveBase[ index & self->waveMask ]; + Bit32u total = ( wave & 0x7fff ) + vol << ( 3 - ENV_EXTRA ); + Bit32s sig = ExpTable[ total & 0xff ]; + Bit32u exp = total >> 8; + Bit32s neg = wave >> 16; + return((sig ^ neg) - neg) >> exp; +#else +#error "No valid wave routine" +#endif +} + +static inline Bits Operator__GetSample(Operator *self, Bits modulation ) { + Bitu vol = Operator__ForwardVolume(self); + if ( ENV_SILENT( vol ) ) { + //Simply forward the wave + self->waveIndex += self->waveCurrent; + return 0; + } else { + Bitu index = Operator__ForwardWave(self); + index += modulation; + return Operator__GetWave( self, index, vol ); + } +} + +static void Operator__Operator(Operator *self) { + self->chanData = 0; + self->freqMul = 0; + self->waveIndex = 0; + self->waveAdd = 0; + self->waveCurrent = 0; + self->keyOn = 0; + self->ksr = 0; + self->reg20 = 0; + self->reg40 = 0; + self->reg60 = 0; + self->reg80 = 0; + self->regE0 = 0; + Operator__SetState( self, OFF ); + self->rateZero = (1 << OFF); + self->sustainLevel = ENV_MAX; + self->currentLevel = ENV_MAX; + self->totalLevel = ENV_MAX; + self->volume = ENV_MAX; + self->releaseAdd = 0; +} + +/* + Channel +*/ + +static void Channel__Channel(Channel *self) { + Operator__Operator(&self->op[0]); + Operator__Operator(&self->op[1]); + self->old[0] = self->old[1] = 0; + self->chanData = 0; + self->regB0 = 0; + self->regC0 = 0; + self->maskLeft = -1; + self->maskRight = -1; + self->feedback = 31; + self->fourMask = 0; + self->synthHandler = Channel__BlockTemplate_sm2FM; +}; + +static inline Operator* Channel__Op( Channel *self, Bitu index ) { + return &( ( self + (index >> 1) )->op[ index & 1 ]); +} + +static void Channel__SetChanData(Channel *self, const Chip* chip, Bit32u data ) { + Bit32u change = self->chanData ^ data; + self->chanData = data; + Channel__Op( self, 0 )->chanData = data; + Channel__Op( self, 1 )->chanData = data; + //Since a frequency update triggered this, always update frequency + Operator__UpdateFrequency(Channel__Op( self, 0 )); + Operator__UpdateFrequency(Channel__Op( self, 1 )); + if ( change & ( 0xff << SHIFT_KSLBASE ) ) { + Operator__UpdateAttenuation(Channel__Op( self, 0 )); + Operator__UpdateAttenuation(Channel__Op( self, 1 )); + } + if ( change & ( 0xff << SHIFT_KEYCODE ) ) { + Operator__UpdateRates(Channel__Op( self, 0 ), chip); + Operator__UpdateRates(Channel__Op( self, 1 ), chip); + } +} + +static void Channel__UpdateFrequency(Channel *self, const Chip* chip, Bit8u fourOp ) { + //Extrace the frequency bits + Bit32u data = self->chanData & 0xffff; + Bit32u kslBase = KslTable[ data >> 6 ]; + Bit32u keyCode = ( data & 0x1c00) >> 9; + if ( chip->reg08 & 0x40 ) { + keyCode |= ( data & 0x100)>>8; /* notesel == 1 */ + } else { + keyCode |= ( data & 0x200)>>9; /* notesel == 0 */ + } + //Add the keycode and ksl into the highest bits of chanData + data |= (keyCode << SHIFT_KEYCODE) | ( kslBase << SHIFT_KSLBASE ); + Channel__SetChanData( self + 0, chip, data ); + if ( fourOp & 0x3f ) { + Channel__SetChanData( self + 1, chip, data ); + } +} + +static void Channel__WriteA0(Channel *self, const Chip* chip, Bit8u val ) { + Bit32u change; + Bit8u fourOp = chip->reg104 & chip->opl3Active & self->fourMask; + //Don't handle writes to silent fourop channels + if ( fourOp > 0x80 ) + return; + change = (self->chanData ^ val ) & 0xff; + if ( change ) { + self->chanData ^= change; + Channel__UpdateFrequency( self, chip, fourOp ); + } +} + +static void Channel__WriteB0(Channel *self, const Chip* chip, Bit8u val ) { + Bitu change; + Bit8u fourOp = chip->reg104 & chip->opl3Active & self->fourMask; + //Don't handle writes to silent fourop channels + if ( fourOp > 0x80 ) + return; + change = (self->chanData ^ ( val << 8 ) ) & 0x1f00; + if ( change ) { + self->chanData ^= change; + Channel__UpdateFrequency( self, chip, fourOp ); + } + //Check for a change in the keyon/off state + if ( !(( val ^ self->regB0) & 0x20)) + return; + self->regB0 = val; + if ( val & 0x20 ) { + Operator__KeyOn( Channel__Op(self, 0), 0x1 ); + Operator__KeyOn( Channel__Op(self, 1), 0x1 ); + if ( fourOp & 0x3f ) { + Operator__KeyOn( Channel__Op(self + 1, 0), 1 ); + Operator__KeyOn( Channel__Op(self + 1, 1), 1 ); + } + } else { + Operator__KeyOff( Channel__Op(self, 0), 0x1 ); + Operator__KeyOff( Channel__Op(self, 1), 0x1 ); + if ( fourOp & 0x3f ) { + Operator__KeyOff( Channel__Op(self + 1, 0), 1 ); + Operator__KeyOff( Channel__Op(self + 1, 1), 1 ); + } + } +} + +static void Channel__WriteC0(Channel *self, const Chip* chip, Bit8u val ) { + Bit8u synth; + Bit8u change = val ^ self->regC0; + if ( !change ) + return; + self->regC0 = val; + self->feedback = ( val >> 1 ) & 7; + if ( self->feedback ) { + //We shift the input to the right 10 bit wave index value + self->feedback = 9 - self->feedback; + } else { + self->feedback = 31; + } + //Select the new synth mode + if ( chip->opl3Active ) { + //4-op mode enabled for this channel + if ( (chip->reg104 & self->fourMask) & 0x3f ) { + Channel* chan0, *chan1; + //Check if it's the 2nd channel in a 4-op + if ( !(self->fourMask & 0x80 ) ) { + chan0 = self; + chan1 = self + 1; + } else { + chan0 = self - 1; + chan1 = self; + } + + synth = ( (chan0->regC0 & 1) << 0 )| (( chan1->regC0 & 1) << 1 ); + switch ( synth ) { + case 0: + chan0->synthHandler = Channel__BlockTemplate_sm3FMFM; + break; + case 1: + chan0->synthHandler = Channel__BlockTemplate_sm3AMFM; + break; + case 2: + chan0->synthHandler = Channel__BlockTemplate_sm3FMAM ; + break; + case 3: + chan0->synthHandler = Channel__BlockTemplate_sm3AMAM ; + break; + } + //Disable updating percussion channels + } else if ((self->fourMask & 0x40) && ( chip->regBD & 0x20) ) { + + //Regular dual op, am or fm + } else if ( val & 1 ) { + self->synthHandler = Channel__BlockTemplate_sm3AM; + } else { + self->synthHandler = Channel__BlockTemplate_sm3FM; + } + self->maskLeft = ( val & 0x10 ) ? -1 : 0; + self->maskRight = ( val & 0x20 ) ? -1 : 0; + //opl2 active + } else { + //Disable updating percussion channels + if ( (self->fourMask & 0x40) && ( chip->regBD & 0x20 ) ) { + + //Regular dual op, am or fm + } else if ( val & 1 ) { + self->synthHandler = Channel__BlockTemplate_sm2AM; + } else { + self->synthHandler = Channel__BlockTemplate_sm2FM; + } + } +} + +static void Channel__ResetC0(Channel *self, const Chip* chip ) { + Bit8u val = self->regC0; + self->regC0 ^= 0xff; + Channel__WriteC0( self, chip, val ); +}; + +static inline void Channel__GeneratePercussion(Channel *self, Chip* chip, + Bit32s* output, int opl3Mode ) { + Bit32u noiseBit, c2, c5, phaseBit, hhVol, sdVol, tcVol; + Bit32s sample; + Channel* chan = self; + + //BassDrum + Bit32s mod = (Bit32u)((self->old[0] + self->old[1])) >> self->feedback; + self->old[0] = self->old[1]; + self->old[1] = Operator__GetSample( Channel__Op(self, 0), mod ); + + //When bassdrum is in AM mode first operator is ignoed + if ( chan->regC0 & 1 ) { + mod = 0; + } else { + mod = self->old[0]; + } + sample = Operator__GetSample( Channel__Op(self, 1), mod ); + + //Precalculate stuff used by other outputs + noiseBit = Chip__ForwardNoise(chip) & 0x1; + c2 = Operator__ForwardWave(Channel__Op(self, 2)); + c5 = Operator__ForwardWave(Channel__Op(self, 5)); + phaseBit = (((c2 & 0x88) ^ ((c2<<5) & 0x80)) | ((c5 ^ (c5<<2)) & 0x20)) ? 0x02 : 0x00; + + //Hi-Hat + hhVol = Operator__ForwardVolume(Channel__Op(self, 2)); + if ( !ENV_SILENT( hhVol ) ) { + Bit32u hhIndex = (phaseBit<<8) | (0x34 << ( phaseBit ^ (noiseBit << 1 ))); + sample += Operator__GetWave( Channel__Op(self, 2), hhIndex, hhVol ); + } + //Snare Drum + sdVol = Operator__ForwardVolume( Channel__Op(self, 3) ); + if ( !ENV_SILENT( sdVol ) ) { + Bit32u sdIndex = ( 0x100 + (c2 & 0x100) ) ^ ( noiseBit << 8 ); + sample += Operator__GetWave( Channel__Op(self, 3), sdIndex, sdVol ); + } + //Tom-tom + sample += Operator__GetSample( Channel__Op(self, 4), 0 ); + + //Top-Cymbal + tcVol = Operator__ForwardVolume(Channel__Op(self, 5)); + if ( !ENV_SILENT( tcVol ) ) { + Bit32u tcIndex = (1 + phaseBit) << 8; + sample += Operator__GetWave( Channel__Op(self, 5), tcIndex, tcVol ); + } + sample <<= 1; + if ( opl3Mode ) { + output[0] += sample; + output[1] += sample; + } else { + output[0] += sample; + } +} + +Channel* Channel__BlockTemplate(Channel *self, Chip* chip, + Bit32u samples, Bit32s* output, + SynthMode mode ) { + Bitu i; + + switch( mode ) { + case sm2AM: + case sm3AM: + if ( Operator__Silent(Channel__Op(self, 0)) + && Operator__Silent(Channel__Op(self, 1))) { + self->old[0] = self->old[1] = 0; + return(self + 1); + } + break; + case sm2FM: + case sm3FM: + if ( Operator__Silent(Channel__Op(self, 1))) { + self->old[0] = self->old[1] = 0; + return (self + 1); + } + break; + case sm3FMFM: + if ( Operator__Silent(Channel__Op(self, 3))) { + self->old[0] = self->old[1] = 0; + return (self + 2); + } + break; + case sm3AMFM: + if ( Operator__Silent( Channel__Op(self, 0) ) + && Operator__Silent( Channel__Op(self, 3) )) { + self->old[0] = self->old[1] = 0; + return (self + 2); + } + break; + case sm3FMAM: + if ( Operator__Silent( Channel__Op(self, 1)) + && Operator__Silent( Channel__Op(self, 3))) { + self->old[0] = self->old[1] = 0; + return (self + 2); + } + break; + case sm3AMAM: + if ( Operator__Silent( Channel__Op(self, 0) ) + && Operator__Silent( Channel__Op(self, 2) ) + && Operator__Silent( Channel__Op(self, 3) )) { + self->old[0] = self->old[1] = 0; + return (self + 2); + } + break; + + default: + abort(); + } + //Init the operators with the the current vibrato and tremolo values + Operator__Prepare( Channel__Op( self, 0 ), chip ); + Operator__Prepare( Channel__Op( self, 1 ), chip ); + if ( mode > sm4Start ) { + Operator__Prepare( Channel__Op( self, 2 ), chip ); + Operator__Prepare( Channel__Op( self, 3 ), chip ); + } + if ( mode > sm6Start ) { + Operator__Prepare( Channel__Op( self, 4 ), chip ); + Operator__Prepare( Channel__Op( self, 5 ), chip ); + } + for ( i = 0; i < samples; i++ ) { + Bit32s mod, sample, out0; + Bits next; + //Early out for percussion handlers + if ( mode == sm2Percussion ) { + Channel__GeneratePercussion( self, chip, output + i, FALSE ); + continue; //Prevent some unitialized value bitching + } else if ( mode == sm3Percussion ) { + Channel__GeneratePercussion( self, chip, output + i * 2, TRUE ); + continue; //Prevent some unitialized value bitching + } + + //Do unsigned shift so we can shift out all bits but still stay in 10 bit range otherwise + mod = (Bit32u)((self->old[0] + self->old[1])) >> self->feedback; + self->old[0] = self->old[1]; + self->old[1] = Operator__GetSample( Channel__Op(self, 0), mod ); + sample = 0; + out0 = self->old[0]; + if ( mode == sm2AM || mode == sm3AM ) { + sample = out0 + Operator__GetSample( Channel__Op(self, 1), 0 ); + } else if ( mode == sm2FM || mode == sm3FM ) { + sample = Operator__GetSample( Channel__Op(self, 1), out0 ); + } else if ( mode == sm3FMFM ) { + next = Operator__GetSample( Channel__Op(self, 1), out0 ); + next = Operator__GetSample( Channel__Op(self, 2), next ); + sample = Operator__GetSample( Channel__Op(self, 3), next ); + } else if ( mode == sm3AMFM ) { + sample = out0; + next = Operator__GetSample( Channel__Op(self, 1), 0 ); + next = Operator__GetSample( Channel__Op(self, 2), next ); + sample += Operator__GetSample( Channel__Op(self, 3), next ); + } else if ( mode == sm3FMAM ) { + sample = Operator__GetSample( Channel__Op(self, 1), out0 ); + next = Operator__GetSample( Channel__Op(self, 2), 0 ); + sample += Operator__GetSample( Channel__Op(self, 3), next ); + } else if ( mode == sm3AMAM ) { + sample = out0; + next = Operator__GetSample( Channel__Op(self, 1), 0 ); + sample += Operator__GetSample( Channel__Op(self, 2), next ); + sample += Operator__GetSample( Channel__Op(self, 3), 0 ); + } + switch( mode ) { + case sm2AM: + case sm2FM: + output[ i ] += sample; + break; + case sm3AM: + case sm3FM: + case sm3FMFM: + case sm3AMFM: + case sm3FMAM: + case sm3AMAM: + output[ i * 2 + 0 ] += sample & self->maskLeft; + output[ i * 2 + 1 ] += sample & self->maskRight; + break; + default: + abort(); + } + } + switch( mode ) { + case sm2AM: + case sm2FM: + case sm3AM: + case sm3FM: + return ( self + 1 ); + case sm3FMFM: + case sm3AMFM: + case sm3FMAM: + case sm3AMAM: + return ( self + 2 ); + case sm2Percussion: + case sm3Percussion: + return( self + 3 ); + default: + abort(); + } + return 0; +} + +/* + Chip +*/ + +void Chip__Chip(Chip *self) { + int i; + + for (i=0; i<18; ++i) { + Channel__Channel(&self->chan[i]); + } + + self->reg08 = 0; + self->reg04 = 0; + self->regBD = 0; + self->reg104 = 0; + self->opl3Active = 0; +} + +static inline Bit32u Chip__ForwardNoise(Chip *self) { + Bitu count; + self->noiseCounter += self->noiseAdd; + count = self->noiseCounter >> LFO_SH; + self->noiseCounter &= WAVE_MASK; + for ( ; count > 0; --count ) { + //Noise calculation from mame + self->noiseValue ^= ( 0x800302 ) & ( 0 - (self->noiseValue & 1 ) ); + self->noiseValue >>= 1; + } + return self->noiseValue; +} + +static inline Bit32u Chip__ForwardLFO(Chip *self, Bit32u samples ) { + Bit32u todo, count; + //Current vibrato value, runs 4x slower than tremolo + self->vibratoSign = ( VibratoTable[ self->vibratoIndex >> 2] ) >> 7; + self->vibratoShift = ( VibratoTable[ self->vibratoIndex >> 2] & 7) + self->vibratoStrength; + self->tremoloValue = TremoloTable[ self->tremoloIndex ] >> self->tremoloStrength; + + //Check hom many samples there can be done before the value changes + todo = LFO_MAX - self->lfoCounter; + count = (todo + self->lfoAdd - 1) / self->lfoAdd; + if ( count > samples ) { + count = samples; + self->lfoCounter += count * self->lfoAdd; + } else { + self->lfoCounter += count * self->lfoAdd; + self->lfoCounter &= (LFO_MAX - 1); + //Maximum of 7 vibrato value * 4 + self->vibratoIndex = ( self->vibratoIndex + 1 ) & 31; + //Clip tremolo to the the table size + if ( self->tremoloIndex + 1 < TREMOLO_TABLE ) + ++self->tremoloIndex; + else + self->tremoloIndex = 0; + } + return count; +} + + +static void Chip__WriteBD(Chip *self, Bit8u val ) { + Bit8u change = self->regBD ^ val; + if ( !change ) + return; + self->regBD = val; + //TODO could do this with shift and xor? + self->vibratoStrength = (val & 0x40) ? 0x00 : 0x01; + self->tremoloStrength = (val & 0x80) ? 0x00 : 0x02; + if ( val & 0x20 ) { + //Drum was just enabled, make sure channel 6 has the right synth + if ( change & 0x20 ) { + if ( self->opl3Active ) { + self->chan[6].synthHandler + = Channel__BlockTemplate_sm3Percussion; + } else { + self->chan[6].synthHandler + = Channel__BlockTemplate_sm2Percussion; + } + } + //Bass Drum + if ( val & 0x10 ) { + Operator__KeyOn( &self->chan[6].op[0], 0x2 ); + Operator__KeyOn( &self->chan[6].op[1], 0x2 ); + } else { + Operator__KeyOff( &self->chan[6].op[0], 0x2 ); + Operator__KeyOff( &self->chan[6].op[1], 0x2 ); + } + //Hi-Hat + if ( val & 0x1 ) { + Operator__KeyOn( &self->chan[7].op[0], 0x2 ); + } else { + Operator__KeyOff( &self->chan[7].op[0], 0x2 ); + } + //Snare + if ( val & 0x8 ) { + Operator__KeyOn( &self->chan[7].op[1], 0x2 ); + } else { + Operator__KeyOff( &self->chan[7].op[1], 0x2 ); + } + //Tom-Tom + if ( val & 0x4 ) { + Operator__KeyOn( &self->chan[8].op[0], 0x2 ); + } else { + Operator__KeyOff( &self->chan[8].op[0], 0x2 ); + } + //Top Cymbal + if ( val & 0x2 ) { + Operator__KeyOn( &self->chan[8].op[1], 0x2 ); + } else { + Operator__KeyOff( &self->chan[8].op[1], 0x2 ); + } + //Toggle keyoffs when we turn off the percussion + } else if ( change & 0x20 ) { + //Trigger a reset to setup the original synth handler + Channel__ResetC0( &self->chan[6], self ); + Operator__KeyOff( &self->chan[6].op[0], 0x2 ); + Operator__KeyOff( &self->chan[6].op[1], 0x2 ); + Operator__KeyOff( &self->chan[7].op[0], 0x2 ); + Operator__KeyOff( &self->chan[7].op[1], 0x2 ); + Operator__KeyOff( &self->chan[8].op[0], 0x2 ); + Operator__KeyOff( &self->chan[8].op[1], 0x2 ); + } +} + + +#define REGOP( _FUNC_ ) \ + index = ( ( reg >> 3) & 0x20 ) | ( reg & 0x1f ); \ + if ( OpOffsetTable[ index ] ) { \ + Operator* regOp = (Operator*)( ((char *)self ) + OpOffsetTable[ index ] ); \ + Operator__ ## _FUNC_ (regOp, self, val); \ + } + +#define REGCHAN( _FUNC_ ) \ + index = ( ( reg >> 4) & 0x10 ) | ( reg & 0xf ); \ + if ( ChanOffsetTable[ index ] ) { \ + Channel* regChan = (Channel*)( ((char *)self ) + ChanOffsetTable[ index ] ); \ + Channel__ ## _FUNC_ (regChan, self, val); \ + } + +void Chip__WriteReg(Chip *self, Bit32u reg, Bit8u val ) { + Bitu index; + switch ( (reg & 0xf0) >> 4 ) { + case 0x00 >> 4: + if ( reg == 0x01 ) { + self->waveFormMask = ( val & 0x20 ) ? 0x7 : 0x0; + } else if ( reg == 0x104 ) { + //Only detect changes in lowest 6 bits + if ( !((self->reg104 ^ val) & 0x3f) ) + return; + //Always keep the highest bit enabled, for checking > 0x80 + self->reg104 = 0x80 | ( val & 0x3f ); + } else if ( reg == 0x105 ) { + int i; + + //MAME says the real opl3 doesn't reset anything on opl3 disable/enable till the next write in another register + if ( !((self->opl3Active ^ val) & 1 ) ) + return; + self->opl3Active = ( val & 1 ) ? 0xff : 0; + //Update the 0xc0 register for all channels to signal the switch to mono/stereo handlers + for ( i = 0; i < 18;i++ ) { + Channel__ResetC0( &self->chan[i], self ); + } + } else if ( reg == 0x08 ) { + self->reg08 = val; + } + case 0x10 >> 4: + break; + case 0x20 >> 4: + case 0x30 >> 4: + REGOP( Write20 ); + break; + case 0x40 >> 4: + case 0x50 >> 4: + REGOP( Write40 ); + break; + case 0x60 >> 4: + case 0x70 >> 4: + REGOP( Write60 ); + break; + case 0x80 >> 4: + case 0x90 >> 4: + REGOP( Write80 ); + break; + case 0xa0 >> 4: + REGCHAN( WriteA0 ); + break; + case 0xb0 >> 4: + if ( reg == 0xbd ) { + Chip__WriteBD( self, val ); + } else { + REGCHAN( WriteB0 ); + } + break; + case 0xc0 >> 4: + REGCHAN( WriteC0 ); + case 0xd0 >> 4: + break; + case 0xe0 >> 4: + case 0xf0 >> 4: + REGOP( WriteE0 ); + break; + } +} + +Bit32u Chip__WriteAddr(Chip *self, Bit32u port, Bit8u val ) { + switch ( port & 3 ) { + case 0: + return val; + case 2: + if ( self->opl3Active || (val == 0x05) ) + return 0x100 | val; + else + return val; + } + return 0; +} + +void Chip__GenerateBlock2(Chip *self, Bitu total, Bit32s* output ) { + while ( total > 0 ) { + Channel *ch; + int count; + + Bit32u samples = Chip__ForwardLFO( self, total ); + memset(output, 0, sizeof(Bit32s) * samples); + count = 0; + for ( ch = self->chan; ch < self->chan + 9; ) { + count++; + ch = (ch->synthHandler)( ch, self, samples, output ); + } + total -= samples; + output += samples; + } +} + +void Chip__GenerateBlock3(Chip *self, Bitu total, Bit32s* output ) { + while ( total > 0 ) { + int count; + Channel *ch; + + Bit32u samples = Chip__ForwardLFO( self, total ); + memset(output, 0, sizeof(Bit32s) * samples *2); + count = 0; + for ( ch = self->chan; ch < self->chan + 18; ) { + count++; + ch = (ch->synthHandler)( ch, self, samples, output ); + } + total -= samples; + output += samples * 2; + } +} + +void Chip__Setup(Chip *self, Bit32u rate ) { + double original = OPLRATE; + Bit32u i; +// double original = rate; + double scale = original / (double)rate; + + //Noise counter is run at the same precision as general waves + self->noiseAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) ); + self->noiseCounter = 0; + self->noiseValue = 1; //Make sure it triggers the noise xor the first time + //The low frequency oscillation counter + //Every time his overflows vibrato and tremoloindex are increased + self->lfoAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) ); + self->lfoCounter = 0; + self->vibratoIndex = 0; + self->tremoloIndex = 0; + + //With higher octave this gets shifted up + //-1 since the freqCreateTable = *2 + { +#ifdef WAVE_PRECISION + double freqScale = ( 1 << 7 ) * scale * ( 1 << ( WAVE_SH - 1 - 10)); + for ( i = 0; i < 16; i++ ) { + self->freqMul[i] = (Bit32u)( 0.5 + freqScale * FreqCreateTable[ i ] ); + } +#else + Bit32u freqScale = (Bit32u)( 0.5 + scale * ( 1 << ( WAVE_SH - 1 - 10))); + for ( i = 0; i < 16; i++ ) { + self->freqMul[i] = freqScale * FreqCreateTable[ i ]; + } +#endif + } + + //-3 since the real envelope takes 8 steps to reach the single value we supply + for ( i = 0; i < 76; i++ ) { + Bit8u index, shift; + EnvelopeSelect( (Bit8u) i, &index, &shift ); + self->linearRates[i] = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH + ENV_EXTRA - shift - 3 ))); + } + //Generate the best matching attack rate + for ( i = 0; i < 62; i++ ) { + Bit8u index, shift; + Bit32s original, guessAdd, bestAdd, bestDiff; + Bit32u passes; + EnvelopeSelect( (Bit8u) i, &index, &shift ); + //Original amount of samples the attack would take + original = (Bit32u)( (AttackSamplesTable[ index ] << shift) / scale); + + guessAdd = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH - shift - 3 ))); + bestAdd = guessAdd; + bestDiff = 1 << 30; + + for ( passes = 0; passes < 16; passes ++ ) { + Bit32s diff, lDiff; + Bit32s volume = ENV_MAX; + Bit32s samples = 0; + Bit32u count = 0; + while ( volume > 0 && samples < original * 2 ) { + Bit32s change; + count += guessAdd; + change = count >> RATE_SH; + count &= RATE_MASK; + if ( GCC_UNLIKELY(change) ) { // less than 1 % + volume += ( ~volume * change ) >> 3; + } + samples++; + + } + diff = original - samples; + lDiff = labs( diff ); + //Init last on first pass + if ( lDiff < bestDiff ) { + bestDiff = lDiff; + bestAdd = guessAdd; + if ( !bestDiff ) + break; + } + //Below our target + if ( diff < 0 ) { + //Better than the last time + Bit32s mul = ((original - diff) << 12) / original; + guessAdd = ((guessAdd * mul) >> 12); + guessAdd++; + } else if ( diff > 0 ) { + Bit32s mul = ((original - diff) << 12) / original; + guessAdd = (guessAdd * mul) >> 12; + guessAdd--; + } + } + self->attackRates[i] = bestAdd; + } + for ( i = 62; i < 76; i++ ) { + //This should provide instant volume maximizing + self->attackRates[i] = 8 << RATE_SH; + } + //Setup the channels with the correct four op flags + //Channels are accessed through a table so they appear linear here + self->chan[ 0].fourMask = 0x00 | ( 1 << 0 ); + self->chan[ 1].fourMask = 0x80 | ( 1 << 0 ); + self->chan[ 2].fourMask = 0x00 | ( 1 << 1 ); + self->chan[ 3].fourMask = 0x80 | ( 1 << 1 ); + self->chan[ 4].fourMask = 0x00 | ( 1 << 2 ); + self->chan[ 5].fourMask = 0x80 | ( 1 << 2 ); + + self->chan[ 9].fourMask = 0x00 | ( 1 << 3 ); + self->chan[10].fourMask = 0x80 | ( 1 << 3 ); + self->chan[11].fourMask = 0x00 | ( 1 << 4 ); + self->chan[12].fourMask = 0x80 | ( 1 << 4 ); + self->chan[13].fourMask = 0x00 | ( 1 << 5 ); + self->chan[14].fourMask = 0x80 | ( 1 << 5 ); + + //mark the percussion channels + self->chan[ 6].fourMask = 0x40; + self->chan[ 7].fourMask = 0x40; + self->chan[ 8].fourMask = 0x40; + + //Clear Everything in opl3 mode + Chip__WriteReg( self, 0x105, 0x1 ); + for ( i = 0; i < 512; i++ ) { + if ( i == 0x105 ) + continue; + Chip__WriteReg( self, i, 0xff ); + Chip__WriteReg( self, i, 0x0 ); + } + Chip__WriteReg( self, 0x105, 0x0 ); + //Clear everything in opl2 mode + for ( i = 0; i < 255; i++ ) { + Chip__WriteReg( self, i, 0xff ); + Chip__WriteReg( self, i, 0x0 ); + } +} + +static int doneTables = FALSE; +void DBOPL_InitTables( void ) { + int i, oct; + Chip *chip = NULL; + + if ( doneTables ) + return; + doneTables = TRUE; +#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) + //Exponential volume table, same as the real adlib + for ( i = 0; i < 256; i++ ) { + //Save them in reverse + ExpTable[i] = (int)( 0.5 + ( pow(2.0, ( 255 - i) * ( 1.0 /256 ) )-1) * 1024 ); + ExpTable[i] += 1024; //or remove the -1 oh well :) + //Preshift to the left once so the final volume can shift to the right + ExpTable[i] *= 2; + } +#endif +#if ( DBOPL_WAVE == WAVE_HANDLER ) + //Add 0.5 for the trunc rounding of the integer cast + //Do a PI sinetable instead of the original 0.5 PI + for ( i = 0; i < 512; i++ ) { + SinTable[i] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 ); + } +#endif +#if ( DBOPL_WAVE == WAVE_TABLEMUL ) + //Multiplication based tables + for ( i = 0; i < 384; i++ ) { + int s = i * 8; + //TODO maybe keep some of the precision errors of the original table? + double val = ( 0.5 + ( pow(2.0, -1.0 + ( 255 - s) * ( 1.0 /256 ) )) * ( 1 << MUL_SH )); + MulTable[i] = (Bit16u)(val); + } + + //Sine Wave Base + for ( i = 0; i < 512; i++ ) { + WaveTable[ 0x0200 + i ] = (Bit16s)(sin( (i + 0.5) * (PI / 512.0) ) * 4084); + WaveTable[ 0x0000 + i ] = -WaveTable[ 0x200 + i ]; + } + //Exponential wave + for ( i = 0; i < 256; i++ ) { + WaveTable[ 0x700 + i ] = (Bit16s)( 0.5 + ( pow(2.0, -1.0 + ( 255 - i * 8) * ( 1.0 /256 ) ) ) * 4085 ); + WaveTable[ 0x6ff - i ] = -WaveTable[ 0x700 + i ]; + } +#endif +#if ( DBOPL_WAVE == WAVE_TABLELOG ) + //Sine Wave Base + for ( i = 0; i < 512; i++ ) { + WaveTable[ 0x0200 + i ] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 ); + WaveTable[ 0x0000 + i ] = ((Bit16s)0x8000) | WaveTable[ 0x200 + i]; + } + //Exponential wave + for ( i = 0; i < 256; i++ ) { + WaveTable[ 0x700 + i ] = i * 8; + WaveTable[ 0x6ff - i ] = ((Bit16s)0x8000) | i * 8; + } +#endif + + // | |//\\|____|WAV7|//__|/\ |____|/\/\| + // |\\//| | |WAV7| | \/| | | + // |06 |0126|27 |7 |3 |4 |4 5 |5 | + +#if (( DBOPL_WAVE == WAVE_TABLELOG ) || ( DBOPL_WAVE == WAVE_TABLEMUL )) + for ( i = 0; i < 256; i++ ) { + //Fill silence gaps + WaveTable[ 0x400 + i ] = WaveTable[0]; + WaveTable[ 0x500 + i ] = WaveTable[0]; + WaveTable[ 0x900 + i ] = WaveTable[0]; + WaveTable[ 0xc00 + i ] = WaveTable[0]; + WaveTable[ 0xd00 + i ] = WaveTable[0]; + //Replicate sines in other pieces + WaveTable[ 0x800 + i ] = WaveTable[ 0x200 + i ]; + //double speed sines + WaveTable[ 0xa00 + i ] = WaveTable[ 0x200 + i * 2 ]; + WaveTable[ 0xb00 + i ] = WaveTable[ 0x000 + i * 2 ]; + WaveTable[ 0xe00 + i ] = WaveTable[ 0x200 + i * 2 ]; + WaveTable[ 0xf00 + i ] = WaveTable[ 0x200 + i * 2 ]; + } +#endif + + //Create the ksl table + for ( oct = 0; oct < 8; oct++ ) { + int base = oct * 8; + for ( i = 0; i < 16; i++ ) { + int val = base - KslCreateTable[i]; + if ( val < 0 ) + val = 0; + //*4 for the final range to match attenuation range + KslTable[ oct * 16 + i ] = val * 4; + } + } + //Create the Tremolo table, just increase and decrease a triangle wave + for ( i = 0; i < TREMOLO_TABLE / 2; i++ ) { + Bit8u val = i << ENV_EXTRA; + TremoloTable[i] = val; + TremoloTable[TREMOLO_TABLE - 1 - i] = val; + } + //Create a table with offsets of the channels from the start of the chip + for ( i = 0; i < 32; i++ ) { + Bitu blah; + Bitu index = i & 0xf; + if ( index >= 9 ) { + ChanOffsetTable[i] = 0; + continue; + } + //Make sure the four op channels follow eachother + if ( index < 6 ) { + index = (index % 3) * 2 + ( index / 3 ); + } + //Add back the bits for highest ones + if ( i >= 16 ) + index += 9; + blah = (Bitu) ( &(chip->chan[ index ]) ); + ChanOffsetTable[i] = (Bit16u) blah; + } + //Same for operators + for ( i = 0; i < 64; i++ ) { + Bitu chNum, opNum, blah; + Channel* chan = NULL; + if ( i % 8 >= 6 || ( (i / 8) % 4 == 3 ) ) { + OpOffsetTable[i] = 0; + continue; + } + chNum = (i / 8) * 3 + (i % 8) % 3; + //Make sure we use 16 and up for the 2nd range to match the chanoffset gap + if ( chNum >= 12 ) + chNum += 16 - 12; + opNum = ( i % 8 ) / 3; + blah = (Bitu) ( &(chan->op[opNum]) ); + OpOffsetTable[i] = (Bit16u) (ChanOffsetTable[ chNum ] + blah); + } +#if 0 + //Stupid checks if table's are correct + for ( Bitu i = 0; i < 18; i++ ) { + Bit32u find = (Bit16u)( &(chip->chan[ i ]) ); + for ( Bitu c = 0; c < 32; c++ ) { + if ( ChanOffsetTable[c] == find ) { + find = 0; + break; + } + } + if ( find ) { + find = find; + } + } + for ( Bitu i = 0; i < 36; i++ ) { + Bit32u find = (Bit16u)( &(chip->chan[ i / 2 ].op[i % 2]) ); + for ( Bitu c = 0; c < 64; c++ ) { + if ( OpOffsetTable[c] == find ) { + find = 0; + break; + } + } + if ( find ) { + find = find; + } + } +#endif +} + + diff --git a/components/doom/prboom/dbopl.h b/components/doom/prboom/dbopl.h new file mode 100644 index 0000000..cc31850 --- /dev/null +++ b/components/doom/prboom/dbopl.h @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2002-2010 The DOSBox Team + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +//#include "inttypes.h" //NSM +#include +#include + +#ifdef _MSC_VER + #if _MSC_VER < 1300 + #include + typedef INT_PTR intptr_t; + #endif +#endif + +//Use 8 handlers based on a small logatirmic wavetabe and an exponential table for volume +#define WAVE_HANDLER 10 +//Use a logarithmic wavetable with an exponential table for volume +#define WAVE_TABLELOG 11 +//Use a linear wavetable with a multiply table for volume +#define WAVE_TABLEMUL 12 + +//Select the type of wave generator routine +#define DBOPL_WAVE WAVE_TABLEMUL + +#define TREMOLO_TABLE 52 + +typedef struct _Chip Chip; +typedef struct _Operator Operator; +typedef struct _Channel Channel; + +typedef uintptr_t Bitu; +typedef intptr_t Bits; +typedef uint32_t Bit32u; +typedef int32_t Bit32s; +typedef uint16_t Bit16u; +typedef int16_t Bit16s; +typedef uint8_t Bit8u; +typedef int8_t Bit8s; + +#if (DBOPL_WAVE == WAVE_HANDLER) +typedef Bits ( DB_FASTCALL *WaveHandler) ( Bitu i, Bitu volume ); +#endif + +#define DB_FASTCALL + +typedef Bits (*VolumeHandler)(Operator *self); +typedef Channel* (*SynthHandler)(Channel *self, Chip* chip, Bit32u samples, Bit32s* output ); + +//Different synth modes that can generate blocks of data +typedef enum { + sm2AM, + sm2FM, + sm3AM, + sm3FM, + sm4Start, + sm3FMFM, + sm3AMFM, + sm3FMAM, + sm3AMAM, + sm6Start, + sm2Percussion, + sm3Percussion, +} SynthMode; + +//Shifts for the values contained in chandata variable +enum { + SHIFT_KSLBASE = 16, + SHIFT_KEYCODE = 24, +}; + +//Masks for operator 20 values +enum { + MASK_KSR = 0x10, + MASK_SUSTAIN = 0x20, + MASK_VIBRATO = 0x40, + MASK_TREMOLO = 0x80, +}; + +typedef enum { + OFF, + RELEASE, + SUSTAIN, + DECAY, + ATTACK, +} OperatorState; + +struct _Operator { + VolumeHandler volHandler; + +#if (DBOPL_WAVE == WAVE_HANDLER) + WaveHandler waveHandler; //Routine that generate a wave +#else + Bit16s* waveBase; + Bit32u waveMask; + Bit32u waveStart; +#endif + Bit32u waveIndex; //WAVE_BITS shifted counter of the frequency index + Bit32u waveAdd; //The base frequency without vibrato + Bit32u waveCurrent; //waveAdd + vibratao + + Bit32u chanData; //Frequency/octave and derived data coming from whatever channel controls this + Bit32u freqMul; //Scale channel frequency with this, TODO maybe remove? + Bit32u vibrato; //Scaled up vibrato strength + Bit32s sustainLevel; //When stopping at sustain level stop here + Bit32s totalLevel; //totalLevel is added to every generated volume + Bit32u currentLevel; //totalLevel + tremolo + Bit32s volume; //The currently active volume + + Bit32u attackAdd; //Timers for the different states of the envelope + Bit32u decayAdd; + Bit32u releaseAdd; + Bit32u rateIndex; //Current position of the evenlope + + Bit8u rateZero; //Bits for the different states of the envelope having no changes + Bit8u keyOn; //Bitmask of different values that can generate keyon + //Registers, also used to check for changes + Bit8u reg20, reg40, reg60, reg80, regE0; + //Active part of the envelope we're in + Bit8u state; + //0xff when tremolo is enabled + Bit8u tremoloMask; + //Strength of the vibrato + Bit8u vibStrength; + //Keep track of the calculated KSR so we can check for changes + Bit8u ksr; +}; + +struct _Channel { + Operator op[2]; + SynthHandler synthHandler; + Bit32u chanData; //Frequency/octave and derived values + Bit32s old[2]; //Old data for feedback + + Bit8u feedback; //Feedback shift + Bit8u regB0; //Register values to check for changes + Bit8u regC0; + //This should correspond with reg104, bit 6 indicates a Percussion channel, bit 7 indicates a silent channel + Bit8u fourMask; + Bit8s maskLeft; //Sign extended values for both channel's panning + Bit8s maskRight; + +}; + +struct _Chip { + //This is used as the base counter for vibrato and tremolo + Bit32u lfoCounter; + Bit32u lfoAdd; + + + Bit32u noiseCounter; + Bit32u noiseAdd; + Bit32u noiseValue; + + //Frequency scales for the different multiplications + Bit32u freqMul[16]; + //Rates for decay and release for rate of this chip + Bit32u linearRates[76]; + //Best match attack rates for the rate of this chip + Bit32u attackRates[76]; + + //18 channels with 2 operators each + Channel chan[18]; + + Bit8u reg104; + Bit8u reg08; + Bit8u reg04; + Bit8u regBD; + Bit8u vibratoIndex; + Bit8u tremoloIndex; + Bit8s vibratoSign; + Bit8u vibratoShift; + Bit8u tremoloValue; + Bit8u vibratoStrength; + Bit8u tremoloStrength; + //Mask for allowed wave forms + Bit8u waveFormMask; + //0 or -1 when enabled + Bit8s opl3Active; + +}; + +/* +struct Handler : public Adlib::Handler { + DBOPL::Chip chip; + virtual Bit32u WriteAddr( Bit32u port, Bit8u val ); + virtual void WriteReg( Bit32u addr, Bit8u val ); + virtual void Generate( MixerChannel* chan, Bitu samples ); + virtual void Init( Bitu rate ); +}; +*/ + +#ifdef __cplusplus +extern "C" void Chip__Setup(Chip *self, Bit32u rate ); +extern "C" void DBOPL_InitTables( void ); +extern "C" void Chip__Chip(Chip *self); +extern "C" void Chip__WriteReg(Chip *self, Bit32u reg, Bit8u val ); +extern "C" void Chip__GenerateBlock2(Chip *self, Bitu total, Bit32s* output ); +#else +void Chip__Setup(Chip *self, Bit32u rate ); +void DBOPL_InitTables( void ); +void Chip__Chip(Chip *self); +void Chip__WriteReg(Chip *self, Bit32u reg, Bit8u val ); +void Chip__GenerateBlock2(Chip *self, Bitu total, Bit32s* output ); + +#endif + diff --git a/components/doom/prboom/docs/DMSPEC16.txt b/components/doom/prboom/docs/DMSPEC16.txt new file mode 100644 index 0000000..e5094fa --- /dev/null +++ b/components/doom/prboom/docs/DMSPEC16.txt @@ -0,0 +1,3810 @@ +From MSFell@aol.com Thu Dec 15 1994, upload as DMSPEC16.ZIP + + +------------------------------------------------------------------------------ + + T H E U N O F F I C I A L +================= =============== =============== ================ +\\ . . . . . . .\\ //. . . . . . .\\ //. . . . . . .\\ \\. . .\\// . .// +||. . ._____. . .|| ||. . ._____. . .|| ||. . ._____. . .|| || . . .\/ . ..|| +|| . .|| ||. . || || . .|| ||. . || || . .|| ||. . || ||. . . . . . .|| +||. . || || . .|| ||. . || || . .|| ||. . || || . .|| || . | . . . ..|| +|| . .|| ||. _-|| ||-_ .|| ||. . || || . .|| ||. _-|| ||-_.|\ . . . .|| +||. . || ||-' || || `-|| || . .|| ||. . || ||-' || || `|\_ . .|..|| +|| . _|| || || || || ||_ . || || . _|| || || || |\ `-_/| .|| +||_-' || .|/ || || \|. || `-_|| ||_-' || .|/ || || | \ / -_.|| +|| ||_-' || || `-_|| || || ||_-' || || | \ / | '|| +|| `' || || `' || || `' || || | \ / | || +|| .===' `===. .==='.`===. .===' /==. | \/ | || +|| .==' \_|-_ `===. .===' _|_ `===. .===' _-|/ `== \/ | || +|| .==' _-' `-_ `=' _-' `-_ `=' _-' `-_ /| \/ | || +|| .==' _-' `-__\._-' `-_./__-' `' |. /| | || +||.==' _-' `' | /==.|| +==' _-' S P E C S \/ `== +\ _-' `-_ / + `'' ``' + Release v1.666 - December 15th, 1994 + Written by: Matthew S Fell (msfell@aol.com) + + "The poets talk about love, ...but what I talk about is DOOM, + because in the end, DOOM is all that counts." + - Alex Machine/George Stark/Stephen King, _The Dark Half_ +------------------------------------------------------------------------------ + + + + + + + + +---------- +DISCLAIMER +---------- + + These specs are to aid in informing the public about the games +DOOM and DOOM 2, by id Software. In no way should this promote your +killing yourself, killing others, or killing in any other fashion. +Additionally, the author does not claim ANY responsibility +regarding ANY illegal activity concerning this file, or indirectly related +to this file. The information contained in this file only reflects +id Software indirectly, and questioning id Software regarding any +information in this file is not recommended. + +---------------- +COPYRIGHT NOTICE +---------------- + +This article is Copyright 1994 by Matt Fell. All rights reserved. +You are granted the following rights: + +I. To make copies of this work in original form, so long as + (a) the copies are exact and complete; + (b) the copies include the copyright notice and these paragraphs + in their entirety; + (c) the copies give obvious credit to the author, Matt Fell; + (d) the copies are in electronic form. +II. To distribute this work, or copies made under the provisions + above, so long as + (a) this is the original work and not a derivative form; + (b) you do not charge a fee for copying or for distribution; + (c) you ensure that the distributed form includes the copyright + notice, this paragraph, the disclaimer of warranty in + their entirety and credit to the author; + (d) the distributed form is not in an electronic magazine or + within computer software (prior explicit permission may be + obtained from the author); + (e) the distributed form is the NEWEST version of the article to + the best of the knowledge of the distributor; + (f) the distributed form is electronic. + + You may not distribute this work by any non-electronic media, +including but not limited to books, newsletters, magazines, manuals, +catalogs, and speech. You may not distribute this work in electronic +magazines or within computer software without prior written explicit +permission. These rights are temporary and revocable upon written, oral, +or other notice by the author. This copyright notice shall be governed +by the laws of the state of Ohio. + If you would like additional rights beyond those granted above, +write to the author at "msfell@aol.com" on the Internet. + + +-------- +CONTENTS +-------- + +[1] Introduction + [1-1] id Software's Copyright + [1-2] What's New +[2] The Basics + [2-1] Pwads + [2-2] DOOM version information + [2-3] Terminology conventions +[3] List of DOOM.WAD Directory Entries +[4] The Levels + [4-1] ExMy or MAPxy + [4-2] THINGS + [4-2-1] Thing Types + [4-2-2] Thing Sizes + [4-2-3] Thing Options + [4-3] LINEDEFS + [4-3-1] Linedef Flags + [4-3-2] Linedef Types + [4-4] SIDEDEFS + [4-5] VERTEXES + [4-6] SEGS + [4-7] SSECTORS + [4-8] NODES + [4-9] SECTORS + [4-9-1] Special Sector Types + [4-10] REJECT + [4-11] BLOCKMAP +[5] Graphics + [5-1] Picture Format +[6] Flats (Floor and Ceiling Textures) + [6-1] Animated Floors, see [8-4-1] +[7] Sounds and Music + [7-1] PC Speaker Sound Effects + [7-2] Soundcard Sound Effects + [7-3] Music + [7-4] GENMIDI + [7-5] DMXGUS +[8] Miscellaneous Lumps + [8-1] PLAYPAL + [8-2] COLORMAP + [8-3] ENDOOM + [8-4] TEXTURE1 and TEXTURE2 + [8-4-1] Animated Walls + [8-4-2] The SKY Textures + [8-5] PNAMES + [8-6] DEMOs + [8-6-1] Level changes from 1.2 to 1.666 DOOM.WAD +[9] Savegame Files + +[10] The DOOM.EXE File + [10-1] Version 1.2 DOOM.EXE Data Segment Overview + [10-1] Version 1.666 DOOM.EXE Data Segment Overview + [10-3] Detail on some EXE Data Structures + +APPENDICES + +[A-1] Backus-Naur Form definitions of wad elements +[A-2] Engine limits +[A-3] DOOM.WAD changes and errors +[A-3] A BLOCKMAP algorithm +[A-4] Other helpful documents +[A-5] Acknowledgments + + +------------------------- +CHAPTER [1]: Introduction +------------------------- + + DOOM is simply an all-time great game. A big factor in its success +and durability is the plethora of user-created add-ons. id Software +tacitly encouraged them by including the -FILE parameter, and by having +a data format that is both straightforward and easy to understand. +DOOM is basically two files, DOOM.EXE and DOOM.WAD. DOOM.EXE is the +"engine" which does the display and controls the game, and DOOM.WAD has +ALL of the graphics, sound, and map/level data that the engine uses. +The -FILE parameter allows small or large external "WAD" files to be +incorporated, changing any number of those graphics, sounds, and maps. + DOOM 2 has many things in common with DOOM. It uses the same EXE file +as version 1.666 of DOOM, and the WAD file format is the same. It's just +the contents of the WAD file that are different; there are more enemies! +more pictures! more weapons! more stuff!! + + This document explains in great detail nearly all aspects of the doom +WAD file format. And a new chapter (10) documents the location of data +within DOOM.EXE itself, so that various unusual game-play changes can +be made. This information has been updated to apply to DOOM 2 as well +as DOOM 1. + The specs were originally conceived as an aid to programmers making +DOOM utilities, especially map-editors. Coincidentally, there might also +be information useful to advanced level designers and players. + The material herein is somewhat technical and it is not recommended for +beginners, unless they are determined. There are some other very useful +documents in existence; I list the ones I know of in Appendix [A-3]. + + +[1-1]: id Software's Copyright and the Shareware Version +======================================================== + + My comments and statements are by no means official, and the excerpts +below are just the parts that I think are relevant to these specs. Please +read the LICENSE.DOC and README.EXE that came with DOOM. + + The LICENSE.DOC says: + + "You shall not: rent, lease, sell, distribute for money or other + consideration, modify, translate, disassemble, decompile, reverse + engineer, or create derivative works based upon the Software. + Notwithstanding the foregoing, you may create a map editor, modify + maps and make your own maps (collectively referenced as the + "Permitted Derivative Works") for the Software. You may not sell + or distribute any Permitted Derivative Works but you may exchange + the Permitted Derivative Works at no charge amongst other end-users. + In order to commercially distribute any such map editor or data + utility you must first sign ID's Data Utility License and ID + reserves the right to deny authorization to commercial distribute + the any such map editor or data utility. You may request a copy of + the Data Editor License from ID" + + "(except for backup purposes) You may not otherwise reproduce, copy + or disclose to others, in whole or in any part, the Software." + + The README says: + + "id Software respectfully requests that you do not modify the levels + for the shareware version of DOOM. We feel that the distribution of + new levels that work with the shareware version of DOOM will lessen a + potential user's incentive to purchase the registered version. + + "If you would like to work with modified levels of DOOM, we encourage + you to purchase the registered version of the game." + + If you are making add-ons, plan on them not working on the shareware +game, and plan on including statements about the trademarks and copyrights +that id Software owns, as well as disclaimers that they won't support your +add-on product, nor will they support DOOM after it has been modified. + + +[1-2]: What's New +================= + + Each new version of these specs renders the previous version obsolete. + This document has grown considerably in size, and to fight that trend, +I'll not discuss it any more. + It has now been five months since the specs were updated. I won't talk +about that either. I'll just apologize for not releasing updates in late +May and July like I should have. Those updates would have been numbered +1.4 and 1.5, so perhaps that's why this is version 1.666. + Here's some of the new or revised sections since the 1.3 specs: + + - DOOM 2 info, especially in [4-2-1] and [4-3-2] + - lots of info on the DOOM.EXE file in [10] + - BNF style definitions in [A-1] + - DOOM engine limits in [A-2] + - the DEMO format [8-6] + - the ENDOOM lump [8-3] + - comprehensive list of WAD lumps in [3] + + - many parts rewritten for clarity + - changes in terminology to reflect id's where possible, and to be + more consistent throughout + - reformatted again, errors and typos corrected + + +------------------- +CHAPTER [2]: Basics +------------------- + + The starting point is the concept of "WAD". It is not an acronym, it +just means a collection of data. Throughout this document, "WAD" or "wad" +will mean a file with a .WAD extension that contains data for the doom +engine to use. + A WAD file has three parts: + + (1) a twelve-byte header + (2) one or more "lumps" + (3) a directory or "info table" that contains the names, offsets, and + sizes of all the lumps in the WAD + + The header consists of three four-byte parts: + + (a) an ASCII string which must be either "IWAD" or "PWAD" + (b) a 4-byte (long) integer which is the number of lumps in the wad + (c) a long integer which is the file offset to the start of + the directory + + The directory has one 16-byte entry for every lump. Each entry consists +of three parts: + + (a) a long integer, the file offset to the start of the lump + (b) a long integer, the size of the lump in bytes + (c) an 8-byte ASCII string, the name of the lump, padded with zeros. + For example, the "DEMO1" entry in hexadecimal would be + (44 45 4D 4F 31 00 00 00) + + A "lump" is just data, in one of several different formats. Some +contain sound data, some contain graphics data, some contain level +structure data, etc. These specs are mostly concerned with delineating +the formats of the various lump types. There are 10 different types of +map/level lump formats, each has a section in chapter [4] (sections 2-11). +There are 13 other types of lump formats, listed below with the section +where the format is explained, and the actual lump names in parentheses. +Also, Appendix [A-1] has definitions of the structures of all these +WAD elements. + + [8-1] palettes (PLAYPAL) + [8-2] colormaps (COLORMAP) + [8-3] dos exit text (ENDOOM) + [8-6] demos (DEMO1, DEMO2, and DEMO3) + [8-4] texture composition list (TEXTURE1 and TEXTURE2) + [8-5] wall patch "number for name" indexing list (PNAMES) + [7-4] midi mapping (GENMIDI) + [7-5] Gravis UltraSound patch mappings (DMXGUS) + [7-1] PC speaker sound effects (DP*) + [7-2] Soundcard sound effects (DS*) + [7-3] songs (D_*) + [6] flats (lumpnames between F_START and F_END) + [5] all other graphics (all other lumps) + + The "marker" and "label" lump names like "S_START" and "E1M1" (or +"MAP01") do not actually refer to lumps - they have zero length. They +merely serve to mark the beginning or end of a set of related lumps. + + It is possible to include other directory entries and lumps in a wad +file, e.g. an entry called CLOWNS could point to a lump that includes the +level creator's name, date of completion, and the latitude and longitude +of the Holy Grail. None of these non-standard entries will be used by +DOOM, nor will they cause it problems. + + +[2-1]: Pwads +============ + + There are two types of wad files. The original DOOM.WAD and DOOM2.WAD +files are "IWAD"s, or "Internal wads", meaning they contain all of the +data necessary to play. The other type is the "PWAD" file, "Patch wad", +an external file which has the same structure, but with far fewer entries +in the directory. The data in a pwad is substituted for the original data +in the DOOM.WAD, thus allowing for much easier distribution of new levels. +Only those resources listed in the pwad's directory are changed, +everything else is loaded from the IWAD. All external wads should have +the "PWAD" indicator, as id has requested. + A typical pwad might contain new data for a single level, in which +case it would contain the 10 lumps and 11 directory entries necessary +to define the level (as described in chapter [4]). + A pwad file may contain more than one level or parts of levels, in +addition to replacement graphics, sounds, etc. (as of version 1.666, +sprites and flats do NOT work from pwads - see chapter [5] for more). +In fact, there is apparently no limit to how many entries may be in a +pwad. The original doom levels are pretty complicated, and they are +from 50-200 kilobytes each in size, uncompressed. + Pwad files need to have the extension .WAD to work. Many of them have +descriptive names, e.g. if J.R.R. Tolkien made a new level, he might call +it GONDOLIN.WAD - to use this level, a player would type + + DOOM -FILE GONDOLIN.WAD + +at the command line, along with any other parameters. More than one +external file can be added, thus in general: + + DOOM -FILE [pwad_filename] [pwad_filename] [pwad_filename] ... + + If there are duplicate entries amongst the directories of all the +wads being "added", the pwads listed LAST take precedence. + When the game loads, a "modified game" message will appear if there +are any pwads involved, reminding the player that id Software will not +give technical support or answer questions regarding modified levels. + With DOOM version 1.666, there is also the @responsefile option for +listing command line parameters and -file specifications. See the DOOM +README or the latest FAQ for more information. Also, there are numerous +"front-end" utilities that make it easier to play pwads, e.g. load several +external files at once, warp to certain levels, specify options, etc. + + +[2-2]: DOOM versions +==================== + +Version Date Time Is + +1.0 10dec93 01:00 first release (aka DOOM Operating System 0.99) +1.1 16dec93 01:10 slightly different from 1.0, newer dos extender +1.2 17feb94 01:20 modem play added! +1.3 - - unauthorized beta release +1.4 28jun94 01:04 shareware beta +1.5 ??jul94 ? shareware beta +1.6 03aug94 01:06 shareware beta +1.666 01sep94 16:42 registered full upgrade! +1.666 ? ? DOOM 2! + + The important releases as of this writing are 1.2 and 1.666. Hopefully, +everyone will move up to 1.666 soon; it has many important improvements +over 1.2. The 1.4, 1.5, and 1.6 shareware betas contained increasing +amounts of the stuff that's now in 1.666, but there's no information +here about what exactly those changes were. One, I didn't keep track, +and two, they're not really important. + See appendix [A-3] for some miscellany about what has changed from +version to version. + + +[2-3]: Terminology conventions +============================== + + Throughout this document, I will use the following conventions for +numbers and variable types: + +(1) Most numbers will be decimal. Hexadecimal numbers will usually be + labeled thus: 0xffff or $ffff. But sometimes I'll say "hex ...". + And in tablature form, a column heading "HEX" indicates all the + numbers in that column are hexadecimal. +(2) "byte" is of course the generic, 8 bits. It will usually mean one + 8-bit component of a larger data type, or an 8-bit ASCII + character, or some such. As a number, it is an unsigned 8-bit + integer (0..255). +(3) "short" is a signed 16-bit integer (-32768..32767), stored in + lo-hi format. +(4) "ushort" or "unsigned short" is an unsigned 16-bit integer (0..65535). +(5) "integer" or "long" is a signed 32-bit integer. If you don't read + this first, my use of the word "integer" might not be immediately + apparent. +(6) "string8" or "8-byte string" is an ASCII string with length between + 1 and 8 characters inclusive. If its length is less than 8, the + remaining bytes are zeros. +(7) The first byte of a file or any data structure, for addressing and + offset purposes, is byte #0, not byte #1. +(8) Some abbreviations I use: E1, E2, and E3 refer to episodes 1, 2, and + 3 respectively. "The EXE" means the file DOOM.EXE. +(666) Any reference to this number is purely intentional. + + +----------------------------------------------- +CHAPTER [3]: List of DOOM.WAD Directory Entries +----------------------------------------------- + + There are over 2000 entries in the DOOM.WAD directory. Most of them +can be easily described in groups, and so are not explicitly mentioned +in this list. This includes the sprites (see [4-2-1] for sprite names +and [5] for the sprite lump naming system), the wall patches ([8-4] and +[8-5] have more info), the flats (chapter [6]), the sounds and songs +(chapter [7]), and the map data lumps (chapter [4]). All the others +are listed here. + There have been several changes from version to version. The "Ver" +column indicates in which doom versions the lump exists: + +___ no indication means it is in every version. Most are like this. +1.1 it was in 1.0 and 1.1, but not in 1.2 and later. It is obsolete. +1.2 it is not in 1.1 and earlier, only in 1.2 and up. +1.6 it is not in 1.2 and earlier, only in 1.666 and up. +r it is only in the registered version, not the shareware. +1 it is only in DOOM 1, it is not in DOOM 2. +2 it is only in DOOM 2, it is not in DOOM 1. + + In the lump names, x (and y and e) indicates variable ASCII +characters, and * can be replaced by an ASCII string (up to the +8-byte lumpname limit). + +LumpName Ver Description +-------- --- ----------- +PLAYPAL fourteen 256 color palettes. See [8-1]. +COLORMAP maps colors in the palette down to darker ones. [8-2]. +ENDOOM text message displayed when you exit to DOS. [8-3]. +DEMOx x=1-3, are the demos. [8-6]. +ExMy subsequent entries define a single level's data. [4]. +MAPxy 2 like ExMy, but for DOOM 2. +TEXTURE1 list of wall texture names and their composition data, + used in the SIDEDEF portion of each level. [8-4]. +TEXTURE2 r more wall texture compositions. +PNAMES lists all lump names used as wall patches. [8-5]. +GENMIDI General Midi standard instrument data. [7-3]. +DMXGUS Gravis Ultra Sound instrument patches. [7-4]. + +D_ExMy music for a doom 1 level. [7-2]. +D_INTER music played on the summary screen between levels. +D_INTRO music played when the game starts. +D_INTROA 1.2 more introductory music. +D_VICTOR music played on the victory text-screen after an episode. +D_BUNNY r music for while a certain rabbit has his story told... +D_* 2 music for a doom 2 level. + +DP_* vary PC speaker sound effects. [7-1]. +DS_* vary Soundcard sound effects. [7-1]. + + All the remaining entries in the directory, except the flats between +F_START and F_END, and the "markers" like S_START, refer to lumps which +are pictures, in the doom/wad graphic format described in chapter [5]. +The flats are also pictures, but in a format described in chapter [6]. + The next seven are full screen (320 by 200 pixel) pictures. After +that, ST* are status-bar pictures, WI* are for the screens between +levels, and M_* are for menus. + +HELP1 Ad-screen says Register!, with some screen shots. +HELP2 Actual help, all the controls explained. +TITLEPIC Maybe this is the title screen? Gee, I dunno... +CREDIT People at id Software who created this great game. +VICTORY2 r Screen shown after a victorious end to episode 2. +PFUB1 r A nice little rabbit minding his own peas and queues... +PFUB2 r ...a hint of what's waiting in Doom 2. + +ENDx r x=0-6, big red "THE END" gets shot up. +AMMNUMx x=0-9. Small grey digits for ammo count (15/200 etc). +STxBARy 1.1 x=M or A, y= L or R. Status bar used to be in pieces. +STCHAT 1.1 Status bar used to have a "chat" box. +STRSNUMx 1.1 x=0-9. Small red digits. +STWEAPx 1.1 x=0-5. COOL little weapon icons. Why'd they drop them? +STFRAGS 1.1 Tiny "FRAG" to be placed on top of part of status bar. +STBAR 1.2 Status Bar as used in deathmatches. +STGNUMx x=0-9. Small grey digits used on the "Arms" panel. +STTNUMx x=0-9. Big red digits used for Armor, Health, etc. +STTMINUS 1.6 Big red "-" used for negative frags. +STYSNUMx x=0-9. Small yellow digits used on the "Arms" panel. +STTPRCNT Big red % used in Armor and Health. +STKEYSx x=0-5. Blue/Yellow/Red Keycards and Skullkeys. +STDISK Disk, used at bottom right corner during disk accesses. +STCDROM 1.6 CD, used during CD-ROM accesses. +STARMS "Arms" panel which replaces "Frags" in non-deathmatch. +STCFNxxx xxx=033-095, also 121. Small red ASCII characters. +STFBx x=0-3. Green/black/brown/red squares, for ST player faces. +STPBx x=0-3. Squares with bottoms, for inter-level screens. +STFSTxy x=0-4, y=0-2. Player face. x=0 is 100% health...x=4 is + very low health. y=0 is glancing right, y=2 left. +STFTLx0 x=0-4. Face looking left, player hurt from that direction. +STFTRx0 x=0-4. Face looking right. +STFOUCHx x=0-4. Face looking surprised (hurt bad). +STFEVLx x=0-4. Face with a grin (when pick up new weapons). +STFKILLx x=0-4. Face with a grimace (when killing foes). +STFGOD0 Face with yellow eyes (invulnerable). +STFDEAD0 Dead face. +BRDR_* Tiny pictures used as a border between a less-than-full + screen view and the "outside" marbleized zone. TL is + top left, BR bottom right, you can guess the rest. +WIBONUS 1.1 Medium sized red text "BONUS" +WISCORE 1.1 "SCORE" +WIMSTPx 1.1 x=0-3. Red text "ONE" to "FOUR". +WIMSTBx 1.1 x=0-3. Grey text "ONE" to "FOUR". +WIMINUS 1.6 Small red "-" used for negative frags. +WIMAPx x=0-2. 320x200 maps used on inter-level screens for e1,2,3. +WIAe0x0y patches used to animate inter-level maps. +WIURH0 "YOU ARE HERE" with an arrow pointing left. +WIURH1 "YOU ARE HERE" with an arrow pointing right. +WISPLAT Splat mark that indicates a completed level. +WIOSTK "KILLS" +WIOSTI "ITEMS" +WIF "FINISHED" +WIMSTT "TOTAL" +WIOSTS "SCRT" +WIOSTF "F." +WITIME "TIME" +WIPAR "PAR" +WIMSTAR "YOU" +WIPCNT "%" +WINUMx x=0-9. Medium sized red digits. +WICOLON ":" +WISUCKS "SUCKS" +WIFRGS "FRAGS" +WILVxy x=0-2, y=0-8. E(x+1)M(y+1) level names in grey/white letters. +WIPx x=1-4. Red "P1" - "P4", for multiplayer summaries. +WIBPx x=1-4. Grey "P1" - "P4" +WIKILRS Small red "KILLERS" going sideways up, for deathmatches. +WIVCTMS Small red "VICTIMS" for the top of the deathmatch chart. +WISCRT2 "SECRET" +WIENTER "ENTERING" +M_DOOM The DOOM logo +M_RDTHIS Big red "Read This!" +M_OPTION "Options" +M_QUITG "Quit Game" +M_NGAME "New Game" +M_SKULL1 The skull indicator with eyes lit. +M_SKULL2 The skull indicator with eyes unlit. +M_THERMO The marker on e.g. the Sfx volume "thermometer". +M_THERMR The right end of the thermometer. +M_THERML The left end. +M_THERMM The middle, repeated over and over. +M_ENDGAM "End Game" +M_PAUSE "Pause" +M_MESSG "Messages:" +M_MSGON "on" +M_MSGOFF "off" +M_EPISOD "Which Epsiode?" +M_EPI1 "Knee-Deep In The Dead" +M_EPI2 "The Shores Of Hell" +M_EPI3 "Inferno" +M_HURT "Hurt me plenty." +M_JKILL "I'm too young to die." +M_ROUGH "Hey, not too rough." +M_SKILL "Choose Skill Level:" +M_NEWG "NEW GAME" (title of New Game menu) +M_ULTRA "Ultra-Violence." +M_NMARE 1.2 "Nightmare!" +M_SVOL "Sound Volume" +M_OPTTTL "OPTIONS" (title of Options menu) +M_SAVEG "Save Game" +M_LOADG "Load Game" +M_DISP "Display" +M_MSENS "Mouse sensitivity" +M_GDHIGH "high" +M_GDLOW "low" +M_DETAIL "Graphic Detail:" +M_DISOPT "DISPLAY OPTIONS" +M_SCRNSZ "Screen Size" +M_SGTTL "SAVE GAME" +M_LGTTL "LOAD GAME" +M_SFXVOL "Sfx Volume" +M_MUSVOL "Music Volume" +M_LSLEFT Load/save box, left part +M_LSCNTR Load/save box, center part (repeated) +M_LSRGHT Load/save box, right part + + The following entries are markers that do not point to a lump; they +have zero size: + +S_START marks the start of the item/monster "sprite" section. + See chapter [5] for the naming convention used here. +S_END is immediately after the last sprite. +P_START marks the beginning of the wall patches. +P1_START before the first of the shareware wall patches. +P1_END after the last of the shareware wall patches. +P2_START r registered wall patches. +P2_END r registered wall patches. +P_END marks the end of the wall patches. +F_START marks the beginning of the flats (floor textures). +F1_START shareware flats. +F1_END shareware flats. +F2_START r registered flats. +F2_END r registered flats. +F_END marks the end of the flats. + + +----------------------- +CHAPTER [4]: The Levels +----------------------- + + Each level has eleven directory entries and ten lumps: E[x]M[y] (or +MAPxy in a DOOM 2 wad), THINGS, LINEDEFS, SIDEDEFS, VERTEXES, SEGS, +SSECTORS, NODES, SECTORS, REJECT, and BLOCKMAP. + In the DOOM.WAD file, all of these entries are present for every level. +In a pwad external file, they don't all need to be present. Whichever +entries are in a pwad will be substituted for the originals. For example, +a pwad with just two entries, E3M6 and THINGS, would use all the walls +and such from the original E3M6, but could have a completely different +set of THINGS. + + +[4-1]: ExMy or MAPxy +==================== + + DOOM 1 levels have an ExMy label in a wad's directory. x is a single +(ASCII) digit 1-3 for the episode number and y is 1-9 for the mission +number. + DOOM 2 levels have a MAPxy label in a wad's directory. xy can range +from (ASCII) 01 to 32, for the level number. + This label just indicates that the lump names following it are part +of the designated level. The label does not actually point to a lump, +and the size field in the directory is 0. The assignment of lumps to +this level stops with either the next ExMy or MAPxy entry, or with a +non-map entry like TEXTURE1. + Without these labels, there would be no way to differentiate amongst +the many lumps named "THINGS", "LINEDEFS", etc. + + +[4-2]: THINGS +============= + + "Things" in DOOM are player start positions, monsters, weapons, keys, +barrels, etc. The size of each THINGS lump will be a multiple of ten, +since each thing requires ten bytes to describe it, in five +fields: + +(1) X position of thing (at level's inception) +(2) Y position of thing +(3) Angle the thing faces. On the automap, 0 is east, 90 is north, 180 + is west, 270 is south. This value is only used for monsters, player + starts, deathmatch starts, and teleporter landing spots. Other + things look the same from all directions. Values are rounded to + the nearest 45 degree angle, so if the value is 80, it will + actually face 90 - north. +(4) Type of thing, see next subsection, [4-2-1] +(5) Thing options, see [4-2-3] + + +[4-2-1]: Thing Types +-------------------- + + Short 4 of 5, occupying bytes 6-7 of each thing record, specifies its +kind. The table below summarizes the different types. They are listed +in functional groups. You can easily get a numerical-order list by +extracting this table and SORTing it. + +Dec/Hex The thing's number in decimal and hexadecimal. This is the + number used in the THINGS lump on a level (ExMy or MAPxx). +V Version of DOOM needed to use this object: + no mark indicates all versions have this object +r requires registered DOOM or DOOM 2 +2 requires DOOM 2 +Spr The sprite name associated with this thing. This is the first + four letters of the lumps that are pictures of this thing. +seq. The sequence of frames displayed. "-" means it displays nothing. + Unanimated things will have just an "a" here, e.g. a backpack's + only picture can be found in the wad under BPAKA0. Animated + things will show the order that their frames are displayed + (they cycle back after the last one). So the blue key + alternates between BKEYA0 and BKEYB0. The soulsphere uses + SOULA0-SOULB0-C0-D0-C0-B0 then repeats. Thing 15, a dead + player, is PLAYN0. ++ Monsters and players and barrels. They can be hurt, and they + have a more complicated sprite arrangement. See chapter [5]. +CAPITAL Monsters, counts toward the KILL ratio at the end of a level. +# An obstacle, players and monsters can't move through it. +^ Hangs from the ceiling, or floats (if a monster). +$ A regular item that players may get. +! An artifact item; counts toward the ITEM ratio at level's end. + Note that 2025, the radiation suit, was an ITEM in version + 1.2, but it is not an ITEM in version 1.666 on. Also note + that 2022 and 2024, invulnerability and invisibility, do not + respawn in -altdeath games. + +Dec. Hex V Spr seq. Thing is: + + -1 ffff ---- - (nothing) + 0 0000 ---- - (nothing) + 1 0001 PLAY + Player 1 start (Player 1 start needed on ALL +levels) + 2 0002 PLAY + Player 2 start (Player starts 2-4 are needed in) + 3 0003 PLAY + Player 3 start (cooperative mode multiplayer games) + 4 0004 PLAY + Player 4 start + 11 000b ---- - Deathmatch start positions. Should have >= 4/level + 14 000e ---- - Teleport landing. Where players/monsters land when + 14 they teleport to the SECTOR containing this thing + +3004 0bbc POSS + # FORMER HUMAN: regular pistol-shooting zombieman + 84 0054 2 SSWV + # WOLFENSTEIN SS: guest appearance by Wolf3D blue guy + 9 0009 SPOS + # FORMER HUMAN SERGEANT: black armor, shotgunners + 65 0041 2 CPOS + # HEAVY WEAPON DUDE: red armor, chaingunners +3001 0bb9 TROO + # IMP: brown, hurl fireballs +3002 0bba SARG + # DEMON: pink, muscular bull-like chewers + 58 003a SARG + # SPECTRE: invisible version of the DEMON +3006 0bbe r SKUL + ^# LOST SOUL: flying flaming skulls, they really bite +3005 0bbd r HEAD + ^# CACODEMON: red one-eyed floating heads. Behold... + 69 0045 2 BOS2 + # HELL KNIGHT: grey-not-pink BARON, weaker +3003 0bbb BOSS + # BARON OF HELL: cloven hooved minotaur boss + 68 0044 2 BSPI + # ARACHNOTRON: baby SPIDER, shoots green plasma + 71 0047 2 PAIN + ^# PAIN ELEMENTAL: shoots LOST SOULS, deserves its +name + 66 0042 2 SKEL + # REVENANT: Fast skeletal dude shoots homing missles + 67 0043 2 FATT + # MANCUBUS: Big, slow brown guy shoots barrage of +fire + 64 0040 2 VILE + # ARCH-VILE: Super-fire attack, ressurects the dead! + 7 0007 r SPID + # SPIDER MASTERMIND: giant walking brain boss + 16 0010 r CYBR + # CYBER-DEMON: robo-boss, rocket launcher + + 88 0058 2 BBRN + # BOSS BRAIN: Horrifying visage of the ultimate demon + 89 0059 2 - - Boss Shooter: Shoots spinning skull-blocks + 87 0057 2 - - Spawn Spot: Where Todd McFarlane's guys appear + +2005 07d5 CSAW a $ Chainsaw +2001 07d1 SHOT a $ Shotgun + 82 0052 2 SGN2 a $ Double-barreled shotgun +2002 07d2 MGUN a $ Chaingun, gatling gun, mini-gun, whatever +2003 07d3 LAUN a $ Rocket launcher +2004 07d4 r PLAS a $ Plasma gun +2006 07d6 r BFUG a $ Bfg9000 +2007 07d7 CLIP a $ Ammo clip +2008 07d8 SHEL a $ Shotgun shells +2010 07da ROCK a $ A rocket +2047 07ff r CELL a $ Cell charge +2048 0800 AMMO a $ Box of Ammo +2049 0801 SBOX a $ Box of Shells +2046 07fe BROK a $ Box of Rockets + 17 0011 r CELP a $ Cell charge pack + 8 0008 BPAK a $ Backpack: doubles maximum ammo capacities + +2011 07db STIM a $ Stimpak +2012 07dc MEDI a $ Medikit +2014 07de BON1 abcdcb ! Health Potion +1% health +2015 07df BON2 abcdcb ! Spirit Armor +1% armor +2018 07e2 ARM1 ab $ Green armor 100% +2019 07e3 ARM2 ab $ Blue armor 200% + 83 0053 2 MEGA abcd ! Megasphere: 200% health, 200% armor +2013 07dd SOUL abcdcb ! Soulsphere, Supercharge, +100% health +2022 07e6 r PINV abcd ! Invulnerability +2023 07e7 r PSTR a ! Berserk Strength and 100% health +2024 07e8 PINS abcd ! Invisibility +2025 07e9 SUIT a (!)Radiation suit - see notes on ! above +2026 07ea PMAP abcdcb ! Computer map +2045 07fd PVIS ab ! Lite Amplification goggles + + 5 0005 BKEY ab $ Blue keycard + 40 0028 r BSKU ab $ Blue skullkey + 13 000d RKEY ab $ Red keycard + 38 0026 r RSKU ab $ Red skullkey + 6 0006 YKEY ab $ Yellow keycard + 39 0027 r YSKU ab $ Yellow skullkey + +2035 07f3 BAR1 ab+ # Barrel; not an obstacle after blown up + (BEXP sprite) + 72 0048 2 KEEN a+ # A guest appearance by Billy + + 48 0030 ELEC a # Tall, techno pillar + 30 001e r COL1 a # Tall green pillar + 32 0020 r COL3 a # Tall red pillar + 31 001f r COL2 a # Short green pillar + 36 0024 r COL5 ab # Short green pillar with beating heart + 33 0021 r COL4 a # Short red pillar + 37 0025 r COL6 a # Short red pillar with skull + 47 002f r SMIT a # Stalagmite: small brown pointy stump + 43 002b r TRE1 a # Burnt tree: gray tree + 54 0036 r TRE2 a # Large brown tree + +2028 07ec COLU a # Floor lamp + 85 0055 2 TLMP abcd # Tall techno floor lamp + 86 0056 2 TLP2 abcd # Short techno floor lamp + 34 0022 CAND a Candle + 35 0023 CBRA a # Candelabra + 44 002c r TBLU abcd # Tall blue firestick + 45 002d r TGRE abcd # Tall green firestick + 46 002e TRED abcd # Tall red firestick + 55 0037 r SMBT abcd # Short blue firestick + 56 0038 r SMGT abcd # Short green firestick + 57 0039 r SMRT abcd # Short red firestick + 70 0046 2 FCAN abc # Burning barrel + + 41 0029 r CEYE abcb # Evil Eye: floating eye in symbol, over candle + 42 002a r FSKU abc # Floating Skull: flaming skull-rock + + 49 0031 r GOR1 abcb ^# Hanging victim, twitching + 63 003f r GOR1 abcb ^ Hanging victim, twitching + 50 0032 r GOR2 a ^# Hanging victim, arms out + 59 003b r GOR2 a ^ Hanging victim, arms out + 52 0034 r GOR4 a ^# Hanging pair of legs + 60 003c r GOR4 a ^ Hanging pair of legs + 51 0033 r GOR3 a ^# Hanging victim, 1-legged + 61 003d r GOR3 a ^ Hanging victim, 1-legged + 53 0035 r GOR5 a ^# Hanging leg + 62 003e r GOR5 a ^ Hanging leg + 73 0049 2 HDB1 a ^# Hanging victim, guts removed + 74 004a 2 HDB2 a ^# Hanging victim, guts and brain removed + 75 004b 2 HDB3 a ^# Hanging torso, looking down + 76 004c 2 HDB4 a ^# Hanging torso, open skull + 77 004d 2 HDB5 a ^# Hanging torso, looking up + 78 004e 2 HDB6 a ^# Hanging torso, brain removed + + 25 0019 r POL1 a # Impaled human + 26 001a r POL6 ab # Twitching impaled human + 27 001b r POL4 a # Skull on a pole + 28 001c r POL2 a # 5 skulls shish kebob + 29 001d r POL3 ab # Pile of skulls and candles + 10 000a PLAY w Bloody mess (an exploded player) + 12 000c PLAY w Bloody mess, this thing is exactly the same as 10 + 24 0018 POL5 a Pool of blood and flesh + 79 004f 2 POB1 a Pool of blood + 80 0050 2 POB2 a Pool of blood + 81 0051 2 BRS1 a Pool of brains + 15 000f PLAY n Dead player + 18 0012 POSS l Dead former human + 19 0013 SPOS l Dead former sergeant + 20 0014 TROO m Dead imp + 21 0015 SARG n Dead demon + 22 0016 r HEAD l Dead cacodemon + 23 0017 r SKUL k Dead lost soul, invisible + (they blow up when killed) + + +[4-2-2]: Thing Sizes +-------------------- + + The list below gives the radius, height, mass, speed, and toughness +of all the monsters in DOOM 1 and 2. Almost all non-monster things only +differ in their "radius", dependent on whether they are obstacles or not. +For collision purposes, things are NOT circular. They occupy a square +whose side equals slightly more than 2 times the radius. This square +does not turn, it is always aligned with the x and y axes of a level. +Consider a simple collision detection in a coordinate plane: + + IF (ABS(x1-x2) =< (r1+r2)) AND (ABS(y1-y2) =< (r1+r2)) THEN *collision* + + This will result in square objects centered on their (x,y) positions, +and that is the behavior that DOOM objects exhibit. + I don't know why the horizontal size is "slightly more" than 2 times +the radius, but it is. A player cannot enter a corridor of width 32, but +can enter a corridor of width 33. Experiments have shown that no monster +can enter a corridor that is exactly (2*radius) wide. It must be bigger. +Moving up to the next multiple of 8 is a good idea, if not 16 or 32. + Monsters CAN enter sectors that are exactly "Height" high. But obstacles +are infinitely high for collision purposes. A player on a very high ledge +might not be able to jump off, because of an obstacle right next to him, +even though it is far below him. + Height is also used when under a crushing ceiling, and to determine +if an object can move from one sector to another. The space between the +highest floor and the lowest ceiling must be "Height" or greater for the +object to fit. + Toughness indicates how much punishment a monster can take until it +dies. Bullets do 10 damage, Shotgun shells 70 (7 pellets, each is 10), +Plasma 20, Rockets 100, and the BFG does 1000 for a direct hit. There's +more info on this stuff in the DOOM FAQ. + +Dec. Hex Radius Height Mass Tough Speed Sprite name or class of things: + +- - 16 56 100 (100) - PLAY +3004 0bbc 20 56 100 20 8 POSS + 84 0054 20 56 100 50 8 SSWV + 9 0009 20 56 100 30 8 SPOS + 65 0041 20 56 100 70 8 CPOS +3001 0bb9 20 56 100 60 8 TROO +3002 0bba 30 56 400 150 10 SARG + 58 003a 30 56 400 150 10 SARG (Inviso model) +3006 0bbe 16 56 50 100 8 SKUL +3005 0bbd 31 56 400 400 8 HEAD + 69 0045 24 64 1000 500 8 BOS2 +3003 0bbb 24 64 1000 1000 8 BOSS + 68 0044 64 64 600 500 12 BSPI + 71 0047 31 56 400 400 8 PAIN + 66 0042 20 56 500 300 10 SKEL + 67 0043 48 64 1000 600 8 FATT + 64 0040 20 56 500 700 15 VILE + 7 0007 128 100 1000 3000 12 SPID + 16 0010 40 110 1000 4000 16 CYBR + 88 0058 16 16 6666 250 0 BBRN + 72 0048 16 72 6666 100 0 KEEN +2035 07f3 10 42 100 20 0 BAR1 + - - 20 16 - - - most non-obstacles (e.g. gettables) + - - 16 16 - - - most obstacles + 54 0036 32 16 - - - large brown tree + + +[4-2-3]: Thing Options +---------------------- + + Short 5 of 5, occupying bytes 8-9 of each thing record, control a +few options, according to which bits are set: + +bit 0 the THING is present at skill 1 and 2 +bit 1 the THING is present at skill 3 (hurt me plenty) +bit 2 the THING is present at skill 4 and 5 (ultra-violence, nightmare) +bit 3 indicates a deaf guard. +bit 4 means the THING only appears in multiplayer mode. + +bits 5-15 have no effect. + + The skill settings are most used with the monsters, of course...the +most common skill level settings are hex 07/0f (on all skills), 06/0e +(on skill 3-4-5), and 04/0c (only on skill 4-5). Unusual skill settings +are perfectly allowable, e.g. hex 05 for a thing which is present on +skill 1, 2, 4, and 5, but not skill 3. + "deaf guard" only has meaning for monsters, who will not attack until +they see a player if they are deaf. Otherwise, they will activate when +they hear gunshots, etc. (including the punch!). Sound does not travel +through solid walls (walls that are solid at the time of the noise). +Also, lines can be set so that sound does not pass through them (see +[4-3-1] bit 6). This option is also known as the "ambush" option (or +flag, or attribute). + + +[4-3]: LINEDEFS +=============== + + Each linedef represents a line from one of the VERTEXES to another, +and each linedef's record is 14 bytes, containing 7 fields: + +(1) from the VERTEX with this number (the first vertex is 0). +(2) to the VERTEX with this number (31 is the 32nd vertex). +(3) flags, see [4-3-1] below. +(4) types, see [4-3-2] below. +(5) is a "tag" or "trigger" number which ties this line's effect type + to all SECTORS that have the same tag number (in their last + field). +(6) number of the "right" SIDEDEF for this linedef. +(7) "left" SIDEDEF, if this line adjoins 2 SECTORS. Otherwise, it is + equal to -1 (FFFF hex). + + "right" and "left" are based on the direction of the linedef as +indicated by the "from" and "to", or "start" and "end", VERTEXES. +This sketch should make it clear: + + left side right side + start -----------------> end <----------------- start + right side left side + + IMPORTANT: All lines must have a right side. If it is a one-sided +line, then it must go the proper direction, so its single side is +facing the sector it is part of. DOOM will crash on a level that has +a line with no right side. + + +[4-3-1]: Linedef Flags +---------------------- + + The third field of each linedef controls some attributes of +that line. These attributes (aka flags) are indicated by bits. If the +bit is set (equal to 1), the condition is true for that line. If the +bit is not set (equal to 0), the condition is not true. Note that the +"unpegged" flags cannot be independently set for the two SIDEDEFs of +a line. Here's a list of the flags, followed by a discussion of each: + +bit Condition + +0 Impassible +1 Block Monsters +2 Two-sided +3 Upper Unpegged +4 Lower Unpegged +5 Secret +6 Block Sound +7 Not on Map +8 Already on Map +9-15 unused + + 0 (Impassible) - Players and monsters cannot cross this line. Note that +if there is no sector on the other side, they can't go through the line +anyway, regardless of the flags. + + 1 (Block Monsters) - Monsters cannot cross this line. + + 2 (Two-sided) - The linedef's two sidedefs can have "-" as a texture, +which in this case means "transparent". If this flag is not set, the +sidedefs can't be transparent: if "-" is viewed, it will result in the +"hall of mirrors" effect. However, a linedef CAN have two non-transparent +sidedefs, even if this flag is not set, as long as it is between two +sectors. + Another side effect of this flag is that if it is set, then gunfire +(pistol, shotgun, chaingun) can go through it. If this flag is not set, +gunfire cannot go through the line. Projectiles (rockets, plasma etc.) +are not restricted this way. They can go through as long as there's a +sector on the other side (and the sector heights allow it). + Finally, monsters can see through and attack through two-sided lines, +despite any of the line's other flag settings and textures (once again, +provided sector heights and the REJECT [4-10] allow it). + + 3 (Upper unpegged) - The upper texture is pasted onto the wall from +the top down instead of from the bottom up like usual. + Upper textures normally have the bottom of the wall texture to be +drawn lined up with the bottom of the "upper" space in which it is +to be drawn (sidedef Y offsets then apply [4-4]). This can result +in the upper texture being misaligned with a neighboring "middle" +texture. To help solve this problem, common at "windows", this flag +can be set. + If the upper texture is unpegged, it is drawn with the wall texture's +top row at the ceiling, just like middle and lower textures are usually +drawn. This can help realign the upper texture with a neighbor. + + The article TEXTURES, cited in appendix [A-4] gives a great deal +more explanation on the "unpegged" flags and how to use them. + + 4 (Lower unpegged) - Lower and middle textures are drawn from the +bottom up, instead of from the top down like usual. + This is also commonly used on lower textures under "windows". It is +also used on doorjambs, because when the door opens, the sector ceiling +is rising, so the "sides" (the doorjambs), which are middle textures, +will be drawn from the ever-changing ceiling height down, and thus will +appear to be "moving". Unpegging them will make them be drawn from the +floor up, and since the floor height doesn't change when a door opens, +then will not move. + There's one slight difference with lower textures being unpegged - +they are not necessarily drawn with the bottom of the wall texture placed +at the bottom of the wall. The height of the facing sector and the height +of the wall texture are taken into account. So if the sector is 160 high, +and the wall texture is 128 high, then lower unpegged will cause the 32nd +row of the wall texture to be at the floor, NOT the 128th row. This of +course excludes sidedef Y offsets, which are applied AFTER unpegged +flags do their stuff. + + 5 (Secret) - On the automap, this line appears in red like a normal +solid wall that has nothing on the other side. This is useful in +protecting secret doors and such. Note that if the sector on the other +side of this "secret" line has its floor height HIGHER than the sector +on the facing side of the secret line, then the map will show the lines +beyond and thus give up the secret. + Also note that this flag has NOTHING to do with the SECRETS ratio on +inter-level screens. That's done with special sector 9 (see [4-9-1]). + + 6 (Block Sound) - For purposes of monsters hearing sounds and thus +becoming alerted. Every time a player fires a weapon, the "sound" of +it travels from sector to sector, alerting all non-deaf monsters in +each new sector. But the sound will die when it hits a second line +with this flag. The sound can cross one such line, but not two. All +possible routes for the sound to take are taken, so it can get to +some out-of-the-way places. Another thing that blocks sound, instantly, +is incompatible sector heights. Sound can go from a sector with 0/72 +floor/ceiling heights to one with 64/192, but the sound CANNOT go +from a 0/128 sector to an adjacent 128/256 sector. + + 7 (Not on Map) - The line is not shown on the automap, even if the +computer all-map power up is acquired. + + 8 (Already on Map) - When the level is begun, this line is already +on the automap, even though it hasn't been seen (in the display) yet. +Normally lines only get mapped once part of the line has been seen in +the display window. + + Automap line colors: Red lines indicate the line is one-sided, that +there is a sector on only one side (or the line is marked secret). +Brown lines are between two sectors with different floor heights but +the same ceiling height. Yellow lines are between two sectors with +different ceiling heights and the same or different floor heights. +Gray lines are as-yet-unseen lines revealed by the computer all-map. +Without the all-map, lines between sectors with identical floor and +ceiling heights don't show up. With it, they are gray. + + +[4-3-2]: Linedef Types +---------------------- + + The in field 4 of 7 of a linedef can control various special +effects like doors opening, floors moving, etc. Some of them must be +activated by "using" them, like switches, and some of them are activated +when they are walked over. There are a huge number of ways to use these +effects, but it's all done by using one of a hundred or so line function +types. + The most common way they work is this: a player walks across a line +or activates (presses the spacebar or the use key) right in front of +a line. That line has a function type that is non-zero. It also has +a tag number. Then ALL sectors on the level with the same tag number, +that are not already engaged in some action, undergo the effects that +the linedef type number dictates. Note that the tag numbers are NOT the +sector numbers, nor the linedef numbers. A tag number is in a lindef's +5th field, and a sector's last field. + +Explanations of all the abbreviations in the table: + +Val The value of the linedef "type" field (#4). If you want them + in numerical order, use SORT or something. +* This line function only works in 1.666 and up +Class The category of the effect +Act Activation. How the linedef's effect is activated. +n does NOT require a tag number (see note 5 below) +W walk-over activation +S switch ("use" - default config is spacebar) +G gunfire (pistol, shotgun, chaingun) cross or hit line +1 the line may be activated once only +R potentially repeatable activation +& affected sectors "locked out" from further changes. See notes 9/10. +m Monster actions can activate the line's effect +Sound The type of noise made by moving sectors +Speed How quickly a floor moves up/down etc. +Tm Time - how long it "rests"; doors "rest" when they've gone as + high as they're going to go, lifts "rest" at the bottom, etc. +Chg Change - some of them cause a floor texture change and/or special + sector change. See note 11 below. +T Trigger model, see note 11 below. +N Numeric model, see note 11 below. +X Floor texture is transferred, and Sector type 0. +P Special Sector types 4, 5, 7, 9, 11, 16 transfer. +Effect What happens to the affected sector(s). +open The ceiling goes (up) to LEC-4. +close The ceiling goes (down) to the floor level. +up Will move up at specified speed if the destination is above. + If the destination is below, it arrives there instantly. +down Will move down at specified speed if the destination is below. + If the destination is above, it arrives there instantly. +open/ The door can be activated while moving. If it's open or opening, + close it closes. If it's closed or closing, it opens, then pauses, + then closes. +open, The door can only be activated if it is in the closed state. + close It opens, pauses, then closes. +lift The floor goes down to LIF, rests, then back up to original height. +L lowest +H highest +C ceiling +F floor +E adjacent sectors, excluding the affected sector +I adjacent sectors, including the affected sector +nh next-higher, i.e. LEF that is higher than source. + + More notes and longer discussions related to these terms: + +1. "Adjacent" is any sector that shares a LINEDEF with the tagged sector +(sectors are adjacent to themselves). +2. All S activations and the teleporters only work from the RIGHT side +of the linedef. +3. For teleporters, if more than 1 sector is tagged the same and each +has a teleport landing THING, then the lowest numbered sector is the +destination. +4. Floors that raise up an absolute height (up 24, 32) will go up INTO +ceilings, so using the WR and SR types of these in levels is unwise. +5. A few of the linedef types don't require tag numbers: the end-level +switches, the scrolling wall type 48 (0x30), and the "manual" doors which +operate on the sector facing the left side of the linedef with the manual +door line type. +666. Here's the terms id uses for different types of activations: + Manual: nSR and nS1 doors + Trigger: W1 + Retrigger: WR + Switch: S1 + Button: SR + Impact: G + Effect: line 48 is the only one +7. The "moving floors" go up to a maximum of HIF and go down to a minimum +of LIF. Why they sometimes go up first and sometimes down is still a +mystery to me. +8. The "crushing ceilings" go from their original ceiling height down +to (floor + 8), then back up. While crushing a creature, their downward +speed slows considerably. "Fast hurt" does about 120% total damage per +crush, and "slow hurt" grabs you and does somewhere around 1000-2000% +total damage per crush. +9. The & symbol indicates that a sector cannot be affected any more by +other line types after it has performed this effect, even if it has +finished. These are the floor-texture-changers and... (keep reading) +10. Moving floors and crushing ceilings also "lock out" further changes +to the sectors affected, EXCEPT for restarting the moving floor or +crushing ceiling. If a line triggers a type 6 crushing ceiling in a +sector, then it is stopped, then ANY other line with a "crush" type that +is tagged to the same sector will cause the type 6 crusher to start +again, with its original maximum and minimum ceiling heights. +11. Some line types cause floor textures and/or some special sector types +(see [4-9-1]) to transfer to the tagged sector. The tagged sectors' floor +and/or special sector (SS) type will change to match that of the "model" +sector. The TRIGGER model gets the info from the sector that the +activating line's right side faces. The NUMERIC model gets the info +from the sector on the other side of the lowest numbered two-sided +linedef that has one side in the tagged sector. + All of these "change" line types transfer the floor texture. Also, +they all can pass a special sector trait of "0" or "nothing", i.e. if +the destination is an acid-floor or "damaging" sector, then any of these +lines can erase the damaging effect. Lines 59, 93, 37, 84, and 9 (see +note 12 for more specifics on line type 9) also have the ability to +transfer the "secret" trait of SS 9, and the damaging traits of SS +4, 5, 7, 11, and 16. None of the "blinking light" effects of SSs can be +transferred. SS 4 "blinks" and causes damage, but only the damaging part +can be transferred. SS 11 also turns off god-mode and causes a level END +when health <11%, this characteristic is part of SS 11, and cannot be +isolated via fancy transfers. +12. Line type 9 is a special one. The definitive example is the chainsaw +pillar on E1M2. Take the lowest-numbered linedef that has a sidedef in +the tagged sector. If that linedef is one-sided, nothing happens. If it +is 2-sided, then the tagged sector's floor will move down to match the +2nd sector's floor height (or it will jump instantly up if it was below, +like other floors that are supposed to move "down"). + If this 2nd sector CONTAINS the tagged sector, i.e. all the linedefs +with a sidedef in the tagged sector have their other sidedef in the 2nd +sector, then this 2nd sector is the "donut". This donut's floor will +move "up" to match the floor height of the sector on the other side of +the DONUT's lowest-numbered linedef, excluding those linedefs that are +shared with the "donut hole" central sector. Also, the donut will undergo +a floor texture change and special sector type change to match the +"outside". The donut sector does not have to be completely surrounded +by another sector (i.e. it can have 1-sided linedefs), but if its +lowest-numbered linedef is not 2-sided, a minor glitch results: the donut +and the donut-hole both move to a strange height, and the donut changes +floor texture to TLITE6_6 - the last flat in the directory. + Note that if the donut hole and the donut are both going to move, the +donut hole is going to move to match the height that the donut is "going +to". In other words, the whole thing will be at a single height when +they're done, and this is the height of the "outside" sector that borders +the donut. + Whew! +13. Line types 30 and 96, "up ShortestLowerTexture" means that affected +sector(s) floors go up a number of units equal to the height of the +shortest "lower" texture facing out from the sector(s). +14. STAIRS. Any sector tagged to a stair-raiser line will go up 8. Now +find the lowest-numbered 2-sided linedef whose RIGHT side faces this +sector (the first step). The sector on the other side of the lindedef +becomes the next step, and its floor height changes to be 8 above the +previous step (it raises up if it was lower, or it changes instantly if +it was higher). This process continues as long as there are 2-sided +lines with right sides facing each successive step. A couple things +will stop the stairway: + (a) no 2-sided linedef whose right side faces the current step + (b) a sector with a different floor texture + (c) a sector that has already been moved by this stairway (this stops + ouroboros stairways that circle around to repeat themselves) + (d) "locked-out" sectors that can't change their floor height anymore + The component steps of a stairway can have any shape or size. + The turbo stairs (100, 127) work just like regular stairs except that +each step goes up 16 not 8, and rising steps can crush things between +themselves and the ceiling. +15. Line types 78 and 85 do NOTHING as of version 1.666. + + + +Val Class Act Sound Speed Tm Chg Effect + +SPECIAL (Continuous effect, doesn't need triggereing) + + 48 Spec n-- - - - - Scrolling wall + +LOCAL DOORS ("MANUAL" DOORS) + + 1 mDoor nSRm door med 4 - open/close + 26 mDoor nSR door med 4 - open/close BLUE KEY + 28 mDoor nSR door med 4 - open/close RED KEY + 27 mDoor nSR door med 4 - open/close YELLOW KEY + 31 mDoor nS1 door med - - open + 32 mDoor nS1 door med - - open BLUE KEY + 33 mDoor nS1 door med - - open RED KEY + 34 mDoor nS1 door med - - open YELLOW KEY + 46 mDoor nGR door med - - open +117 * mDoor nSR blaze turbo 4 - open/close +118 * mDoor nS1 blaze turbo - - open + +REMOTE DOORS + + 4 rDoor W1 door med 4 - open,close + 29 rDoor S1 door med 4 - open,close + 90 rDoor WR door med 4 - open,close + 63 rDoor SR door med 4 - open,close + 2 rDoor W1 door med - - open +103 rDoor S1 door med - - open + 86 rDoor WR door med - - open + 61 rDoor SR door med - - open + 3 rDoor W1 door med - - close + 50 rDoor S1 door med - - close + 75 rDoor WR door med - - close + 42 rDoor SR door med - - close + 16 rDoor W1 door med 30 - close, then opens + 76 rDoor WR door med 30 - close, then opens +108 * rDoor W1 blaze turbo 4 - open,close +111 * rDoor WR blaze turbo 4 - open,close +105 * rDoor S1 blaze turbo 4 - open,close +114 * rDoor SR blaze turbo 4 - open,close +109 * rDoor W1 blaze turbo - - open +112 * rDoor S1 blaze turbo - - open +106 * rDoor WR blaze turbo - - open +115 * rDoor SR blaze turbo - - open +110 * rDoor W1 blaze turbo - - close +113 * rDoor S1 blaze turbo - - close +107 * rDoor WR blaze turbo - - close +116 * rDoor SR blaze turbo - - close +133 * rDoor S1 blaze turbo - - open BLUE KEY + 99 * rDoor SR blaze turbo - - open BLUE KEY +135 * rDoor S1 blaze turbo - - open RED KEY +134 * rDoor SR blaze turbo - - open RED KEY +137 * rDoor S1 blaze turbo - - open YELLOW KEY +136 * rDoor SR blaze turbo - - open YELLOW KEY + +CEILINGS + + 40 Ceil W1 mover slow - - up to HEC + 41 Ceil S1 mover slow - - down to floor + 43 Ceil SR mover slow - - down to floor + 44 Ceil W1 mover slow - - down to floor + 8 + 49 Ceil S1 mover slow - - down to floor + 8 + 72 Ceil WR mover slow - - down to floor + 8 + +LIFTS + + 10 Lift W1 lift fast 3 - lift + 21 Lift S1 lift fast 3 - lift + 88 Lift WRm lift fast 3 - lift + 62 Lift SR lift fast 3 - lift +121 * Lift W1 lift turbo 3 - lift +122 * Lift S1 lift turbo 3 - lift +120 * Lift WR lift turbo 3 - lift +123 * Lift SR lift turbo 3 - lift + +FLOORS + +119 * Floor W1 mover slow - - up to nhEF +128 * Floor WR mover slow - - up to nhEF + 18 Floor S1 mover slow - - up to nhEF + 69 Floor SR mover slow - - up to nhEF + 22 Floor W1& mover slow - TX up to nhEF + 95 Floor WR& mover slow - TX up to nhEF + 20 Floor S1& mover slow - TX up to nhEF + 68 Floor SR& mover slow - TX up to nhEF + 47 Floor G1& mover slow - TX up to nhEF + 5 Floor W1 mover slow - - up to LIC + 91 Floor WR mover slow - - up to LIC +101 Floor S1 mover slow - - up to LIC + 64 Floor SR mover slow - - up to LIC + 24 Floor G1 mover slow - - up to LIC +130 * Floor W1 mover turbo - - up to nhEF +131 * Floor S1 mover turbo - - up to nhEF +129 * Floor WR mover turbo - - up to nhEF +132 * Floor SR mover turbo - - up to nhEF + 56 Floor W1& mover slow - - up to LIC - 8, CRUSH + 94 Floor WR& mover slow - - up to LIC - 8, CRUSH + 55 Floor S1 mover slow - - up to LIC - 8, CRUSH + 65 Floor SR mover slow - - up to LIC - 8, CRUSH + 58 Floor W1 mover slow - - up 24 + 92 Floor WR mover slow - - up 24 + 15 Floor S1& mover slow - TX up 24 + 66 Floor SR& mover slow - TX up 24 + 59 Floor W1& mover slow - TXP up 24 + 93 Floor WR& mover slow - TXP up 24 + 14 Floor S1& mover slow - TX up 32 + 67 Floor SR& mover slow - TX up 32 +140 * Floor S1 mover med - - up 512 + 30 Floor W1 mover slow - - up ShortestLowerTexture + 96 Floor WR mover slow - - up ShortestLowerTexture + 38 Floor W1 mover slow - - down to LEF + 23 Floor S1 mover slow - - down to LEF + 82 Floor WR mover slow - - down to LEF + 60 Floor SR mover slow - - down to LEF + 37 Floor W1 mover slow - NXP down to LEF + 84 Floor WR mover slow - NXP down to LEF + 19 Floor W1 mover slow - - down to HEF +102 Floor S1 mover slow - - down to HEF + 83 Floor WR mover slow - - down to HEF + 45 Floor SR mover slow - - down to HEF + 36 Floor W1 mover fast - - down to HEF + 8 + 71 Floor S1 mover fast - - down to HEF + 8 + 98 Floor WR mover fast - - down to HEF + 8 + 70 Floor SR mover fast - - down to HEF + 8 + 9 Floor S1 mover slow - NXP donut (see note 12 above) + +STAIRS + + 8 Stair W1 mover slow - - stairs + 7 Stair S1 mover slow - - stairs +100 * Stair W1 mover turbo - - stairs (each up 16 not 8) + crush +127 * Stair S1 mover turbo - - stairs (each up 16 not 8) + crush + +MOVING FLOORS + + 53 MvFlr W1& lift slow 3 - start moving floor + 54 MvFlr W1& - - - - stop moving floor + 87 MvFlr WR& lift slow 3 - start moving floor + 89 MvFlr WR& - - - - stop moving floor + +CRUSHING CEILINGS + + 6 Crush W1& crush med 0 - start crushing, fast hurt + 25 Crush W1& crush med 0 - start crushing, slow hurt + 73 Crush WR& crush slow 0 - start crushing, slow hurt + 77 Crush WR& crush med 0 - start crushing, fast hurt + 57 Crush W1& - - - - stop crush + 74 Crush WR& - - - - stop crush +141 * Crush W1& none? slow 0 - start crushing, slow hurt "Silent" + +EXIT LEVEL + + 11 Exit nS- clunk - - - End level, go to next level + 51 Exit nS- clunk - - - End level, go to secret level + 52 Exit nW- clunk - - - End level, go to next level +124 * Exit nW- clunk - - - End level, go to secret level + +TELEPORT + + 39 Telpt W1m tport - - - Teleport + 97 Telpt WRm tport - - - Teleport +125 * Telpt W1m tport - - - Teleport monsters only +126 * Telpt WRm tport - - - Teleport monsters only + +LIGHT + + 35 Light W1 - - - - 0 +104 Light W1 - - - - LE (light level) + 12 Light W1 - - - - HE (light level) + 13 Light W1 - - - - 255 + 79 Light WR - - - - 0 + 80 Light WR - - - - HE (light level) + 81 Light WR - - - - 255 + 17 Light W1 - - - - Light blinks (see [4-9-1] type 3) +138 * Light SR clunk - - - 255 +139 * Light SR clunk - - - 0 + + +[4-4]: SIDEDEFS +=============== + + A sidedef is a definition of what wall texture(s) to draw along a +LINEDEF, and a group of sidedefs outline the space of a SECTOR. + There will be one sidedef for a line that borders only one sector +(and it must be the RIGHT side, as noted in [4-3]). It is not +necessary to define what the doom player would see from the other +side of that line because the doom player can't go there. The doom +player can only go where there is a sector. + Each sidedef's record is 30 bytes, comprising 2 fields, then +3 <8-byte string> fields, then a final field: + +(1) X offset for pasting the appropriate wall texture onto the wall's + "space": positive offset moves into the texture, so the left + portion gets cut off (# of columns off left side = offset). + Negative offset moves texture farther right, in the wall's space. +(2) Y offset: analogous to the X, for vertical. +(3) "upper" texture name: the part above the juncture with a lower + ceiling of an adjacent sector. +(4) "lower" texture name: the part below a juncture with a higher + floored adjacent sector. +(5) "middle" texture name: the regular part of the wall. Also known as + "normal" or "full" texture. +(6) SECTOR that this sidedef faces or helps to surround. + + The texture names are from the TEXTURE1/2 resources. The names of +wall patches in the directory (between P_START and P_END) are not +directly used, they are referenced through the PNAMES lump. + Simple sidedefs have no upper or lower texture, and so they will have +"-" instead of a texture name. Also, two-sided lines can be transparent, +in which case "-" means transparent (no texture). + If the wall is wider than the texture to be pasted onto it, then the +texture is tiled horizontally. A 64-wide texture will be pasted at 0, +64, 128, etc., unless an X-offset changes this. + If the wall is taller than the texture, than the texture is tiled +vertically, with one very important difference: it starts new tiles +ONLY at 128, 256, 384, etc. So if the texture is less than 128 high, +there will be junk filling the undefined areas, and it looks ugly. +This is sometimes called the "Tutti Frutti" effect. + + There are some transparent textures which can be used as middle textures +on 2-sided sidedefs (between sectors). These textures need to be composed +of a single patch (see [8-4]), and note that on a very tall wall, they +will NOT be tiled. Only one will be placed, at the spot determined by +the "lower unpegged" flag being on/off and the sidedef's y offset. And +if a transparent texture is used as an upper or lower texture, then +the good old "Tutti Frutti" effect will have its way. + Also note that animated wall textures (see [8-4-1]) do NOT animate +if they are the "middle" texture on a 2-sided line. So much for the +lava waterfall with the hidden room at its base...hmm, maybe not... + + +[4-5]: VERTEXES +=============== + + These are the beginning and end points for LINEDEFS and SEGS. Each +vertice's record is 4 bytes in 2 fields: + +(1) X coordinate +(2) Y coordinate + + On the automap within the game, with the grid on (press 'G'), the +lines are 128 apart (0x80), two lines = 256 (0x100). + A note on the coordinates: the coordinate system used for the vertices +and the heights of the sectors corresponds to pixels, for purposes of +texture-mapping. So a sector that's 128 high, or a multiple of 128, is +pretty typical, since many wall textures are 128 pixels high. + And yes, the correct spelling of the plural of "vertex" is "vertices". + + +[4-6]: SEGS +=========== + + The SEGS are stored in a sequential order determined by the SSECTORS, +which are part of the NODES recursive tree. + Each seg is 12 bytes in 6 fields: + +(1) start of seg is VERTEX with this number +(2) end VERTEX +(3) angle: 0= east, 16384=north, -16384=south, -32768=west. + In hex, it's 0000=east, 4000=north, 8000=west, c000=south. + This is also know as BAMS for Binary Angle Measurement. +(4) LINEDEF that this seg goes along +(5) direction: 0 if the seg goes the same direction as the linedef it + is on, 1 if the seg goes the opposite direction. This is the + same as (0 if the seg is on the RIGHT side of the linedef) or + (1 if the seg is on the LEFT side of the linedef). +(6) offset: distance along the linedef to the start of this seg (the + vertex in field 1). The offset is in the same direction as the + seg. If field 5 is 0, then the distance is from the "start" + vertex of the linedef to the "start" vertex of the seg. If field + 5 is 1, then the offset is from the "end" vertex of the linedef + to the "start" vertex of the seg. So if the seg begins at one of + the two endpoints of the linedef, this offset will be 0. + + For diagonal segs, the offset distance can be obtained from the +formula DISTANCE = SQR((x2 - x1)^2 + (y2 - y1)^2). The angle can be +calculated from the inverse tangent of the dx and dy in the vertices, +multiplied to convert PI/2 radians (90 degrees) to 16384. And since +most arctan functions return a value between -(pi/2) and (pi/2), +you'll have to do some tweaking based on the sign of (x2-x1), to +account for segs that go "west". + + +[4-7]: SSECTORS +=============== + + SSECTOR stands for sub-sector. These divide up all the SECTORS into +convex polygons. They are then referenced through the NODES resources. +There will be (number of nodes + 1) ssectors. + Each ssector is 4 bytes in 2 fields: + +(1) This many SEGS are in this SSECTOR... +(2) ...starting with this SEG number + + The segs in ssector 0 should be segs 0 through x, then ssector 1 +contains segs x+1 through y, ssector 2 containg segs y+1 to z, etc. + + +[4-8]: NODES +============ + + A detailed explanation of the nodes follows this list of a node's +structure in the wad file. + Each node is 28 bytes in 14 fields: + +(1) X coordinate of partition line's start +(2) Y coordinate of partition line's start +(3) DX, change in X to end of partition line +(4) DY, change in Y to end of partition line + + If (1) to (4) equaled 64, 128, -64, -64, the partition line would +go from (64,128) to (0,64). + +(5) Y upper bound for right bounding-box.\ +(6) Y lower bound All SEGS in right child of node +(7) X lower bound must be within this box. +(8) X upper bound / +(9) Y upper bound for left bounding box. \ +(10) Y lower bound All SEGS in left child of node +(11) X lower bound must be within this box. +(12) X upper bound / +(13) a NODE or SSECTOR number for the right child. If bit 15 of this + is set, then the rest of the number represents the + child SSECTOR. If not, the child is a recursed node. +(14) a NODE or SSECTOR number for the left child. + + The NODES lump is by far the most difficult to understand of all the +data structures in DOOM. A new level won't display right without a valid +set of precalculated nodes, ssectors, and segs. + Here I will explain what the nodes are for, and how they can be +generated automatically from the set of linedefs, sidedefs, and +vertices. I am NOT including any code or a pseudo-code algorithm, like +I do for the BLOCKMAP (appendix [A-3]). This is for reasons of space, +and more importantly, the fact that I haven't written any such +algorithm myself. If there's to be some "node code" published here, it +will have to be donated by someone, well-commented, well-organized, in +pseudo-code, and 100% effective! So the odds are long against it. + + The NODES are branches in a binary space partition (BSP) that divides +up the level and is used to determine which walls are in front of others, +a process know as hidden-surface removal. The SSECTORS (sub-sectors) and +SEGS (segments) lumps are necessary parts of the structure. + A BSP tree is normally used in 3d space, but DOOM uses a simplified +2d version of the scheme. Basically, the idea is to keep dividing the +map into smaller spaces until each of the smallest spaces contains only +wall segments which cannot possibly occlude (block from view) other +walls in its own space. The smallest, undivided spaces will become +SSECTORS. Each wall segment is part or all of a linedef (and thus a +straight line), and becomes a SEG. All of the divisions are kept track +of in a binary tree structure, which is used to greatly speed the +rendering process (drawing what is seen). How does this binary tree +lead to faster rendering? I have no idea. + Only the SECTORS need to be divided. The parts of the levels that are +"outside" sectors are ignored. Also, only the walls need to be kept +track of. The sides of any created ssectors which are not parts of +linedefs do not become segs. + Some sectors do not require any dividing. Consider a square sector. +All the walls are orthogonal to the floor (the walls are all straight +up and down), so from any viewpoint inside the square, none of its +four walls can possibly block the view of any of the others. Now +imagine a sector shaped like this drawing: + ++--------------.------+ The * is the viewpoint, looking ->, east. The +| . | diagonal wall marked @ @ can't be seen at all, +| /\ |@ and the vertical wall marked @@@ is partially +| *-> / @\ |@ occluded by the other diagonal wall. This sector +| / @\|@ needs to be divided. Suppose the diagonal wall ++---------/ is extended, as shown by the two dots (..): + +now each of the two resulting sub-sectors are sufficient, because while +in either one, no wall that is part of that sub-sector blocks any other. + In general, being a convex polygon is the goal of a ssector. Convex +means a line connecting any two points that are inside the polygon will +be completely contained in the polygon. All triangles and rectangles are +convex, but not all quadrilaterals. In doom's simple Euclidean space, +convex also means that all the interior angles of the polygon are less +than or equal to 180 degrees. + Now, an additional complication arises because of the two-sided +linedef. Its two sides are in different sectors, so they will end up +in different ssectors too. Thus every two-sided linedef becomes two segs +(or more), or you could say that every sidedef becomes a seg. Creating +segs from sidedefs is a good idea, because the seg can then be associated +with a sector. Two segs that aren't part of the same sector cannot +possibly be in the same ssector, so further division is required of any +set of segs that aren't all from the same sector. + Whenever a division needs to be made, a SEG is picked, somewhat +arbitrarily, which along with its imaginary extensions, forms a "knife" +that divides the remaining space in two (thus binary). This seg is the +partition line of a node, and the remaining spaces on either side of +the partition line become the right and left CHILDREN of the node. All +partition lines have a direction, and the space on the "right" side of +the partition is the right child of the node; the space on the "left" +is the left child (there's a cute sketch in [4-3]: LINEDEFS that shows +how right and left relate to the start and end of a line). Note that if +there does not exist a seg in the remaining space which can serve as a +partition line, then there is no need for a further partition, i.e. +it's a ssector and a "leaf" on the node tree. + If the "knife" passes through any lines/segs (but not at vertices), +they are split at the intersection, with one part going to each child. +A two-sided linedef, which is two segs, when split results in four segs. +A two sider that lies along an extension of the partition line has each +of its two segs go to opposite sides of the partition line. This is the +eventual fate of ALL segs on two-sided linedefs. + As the partition lines are picked and the nodes created, a strict +ordering must be maintained. The node tree is created from the "top" +down. After the first division is made, then the left child is divided, +then its left child, and so on, until a node's child is a ssector. The +n you move back up the tree one branch, and divide the right child, then +its left, etc. ALWAYS left first, on the way down. + Since there will be splits along the way, there is no way to know +ahead of time how many nodes and ssectors there will be at the end. +And the top of the tree, the node that is created first, is given the +highest number. So as nodes and ssectors are created, they are simply +numbered in order from 0 on up, and when it's all done (nothing's left +but ssectors), then ALL the numbers, for nodes and ssectors, are +reversed. If there's 485 nodes, then 485 becomes 0 and 0 becomes 485. + Here is another fabulous drawing which will explain everything. ++ is a vertex, - and | indicate linedefs, the . . indicates an +extension of a partition line. The <, >, and ^ symbols indicate the +directions of partition lines. All the space within the drawing is +actual level space, i.e. sectors. + + +-----+-------+-------+ 0 (5) + | | | | / \ ==> / \ + | e |^ f |^ g | 1 4 (4) (1) + | |4 |5 | / \ / \ / \ / \ ++---- + . . +-------+-------+ 2 3 e 5 (3) (2) 2 (0) +| | < 0 | / \ / \ / \ / \ / \ / \ +| a | b | a b c d f g 6 5 4 3 1 0 +| |^ | +| . . |2. . . . . +---------+ The order in which How the elements are +| | |1 > the node tree's numbered when it's +| c |^ d | elements get made. finished. +| |3 | 0 = node (5) = node ++-----+-----------+ a = ssector 6 = ssector + + 1. Make segs from all the linedefs. There are 5 two-sided lines here. + 2. Pick the vertex at 0 and go west (left). This is the first + partition line. Note the . . extension line. + 3. Pick the vertex at 1, going east. The backwards extension . . cuts + the line 3>2>, and the unlabeled left edge line. The left edge + was one seg, it becomes two. The 3>2> line was two segs, it + becomes four. New vertices are created at the intersection + points to do this. + 4. Pick the (newly created) vertex at 2. Now the REMAINING spaces on + both sides of the partition line are suitable for ssectors. The + left one is first, it becomes a, the right b. Note that ssector + a has 3 segs, and ssector b has 5 segs. The . . imaginary lines + are NOT segs. + 5. Back up the tree, and take 1's right branch. Pick 3. Once again, we + can make 2 ssectors, c and d, 3 segs each. Back up the tree to 0. + 6. Pick 4. Now the left side is a ssector, it becomes e. But the right + side is not, it needs one more node. Pick 5, make f and g. + 7. All done, so reverse all the ordination of the nodes and the + ssectors. Ssector 0's segs become segs 0-2, ssector 1's segs + become segs 3-7, etc. The segs are written sequentially according + to the ssector numbering. + + If we want to create an algorithm to do the nodes automatically, it +needs to be able to pick partition lines automatically. From studying +the original maps, it appears that they usually chose a linedef which +divides the child's space roughly in "half". This is restricted by the +availability of a seg in a good location, with a good angle. Also, the +"half" refers to the total number of ssectors in any particular child, +which we have no way of knowing when we start! Optimization methods are +probably used, or maybe brute force, trying every candidate seg until +the "best" one is found. + What is the best possible choice for a partition line? Well, there +are apparently two goals when creating a BSP tree, which are partially +exclusive. One is to have a balanced tree, i.e. for any node, there are +about the same total number of sub-nodes on either side of it. The other +goal is to minimize the number of "splits" necessary, in this case, the +number of seg splits needed, along with the accompanying new vertices +and extra segs. Only a very primitive and specially constructed set of +linedefs could avoid having any splits, so they are inevitable. It's +just that with some choices of partition lines, there end up being +fewer splits. For example, + ++--------------+ If a and b are chosen as partition lines, +| | there will be four extra vertices needed, ++---+ +---+ < A and this shape becomes five ssectors and + |^ ^| 16 segs. If A and B are chosen, however, ++---+a b+---+ < B there are no extra vertices, and only three +| | ssectors and 12 segs. ++--------------+ + + I've read that for a "small" number of polygons (less than 1000?), +which is what we're dealing with in a doom level, one should definitely +try to minimize splits, and not worry about balancing the tree. I can't +say for sure, but it does appear that the original levels strive for +this. Their trees are not totally unbalanced, but there are some parts +where many successive nodes each have a node and a ssector as children +(this is unbalanced). And there are a lot of examples to prove that the +number of extra segs and vertices they create is very low compared to +what it could be. I think the algorithm that id Software used tried to +optimize both, but with fewer splits being more important. + + +[4-9]: SECTORS +============== + + A SECTOR is a horizontal (east-west and north-south) area of the map +where a floor height and ceiling height is defined. It can have any +shape. Any change in floor or ceiling height or texture requires a +new sector (and therefore separating linedefs and sidedefs). If you +didn't already know, this is where you find out that DOOM is in many +respects still a two-dimensional world, because there can only be ONE +floor height in each sector. No buildings with two floors, one above +the other, although fairly convincing illusions are possible. + Each sector's record is 26 bytes, comprising 2 fields, then +2 <8-byte string> fields, then 3 fields: + +(1) Floor is at this height for this sector +(2) Ceiling height +(3) name of the flat used for the floor texture, from the directory. +(4) name of the flat used for the ceiling texture. + All the flats in the directory between F_START and F_END work + as either floors or ceilings. +(5) lightlevel of this sector: 0 = total dark, 255 (0xff) = maximum + light. There are actually only 32 brightnesses possible (see + COLORMAP [8-2]), so 0-7 are the same, ..., 248-255 are the same. +(6) special sector: see [4-9-1] immediately below. +(7) a "tag" number corresponding to LINEDEF(s) with the same tag + number. When that linedef is activated, something will usually + happen to this sector - its floor will rise, the lights will + go out, etc. See [4-3-2] for the list of linedef effects. + + +[4-9-1]: Special Sector Types +----------------------------- + + Bytes 22-23 of each Sector record are a which determines +some area-effects called special sectors. + Light changes are automatic. The brightness level will alternate +between the light value specified for the special sector, and the lowest +value amongst adjacent sectors (two sectors are adjacent if a linedef +has a sidedef facing each sector). If there is no lower light value, +or no adjacent sectors, then the "blink" sectors will instead alternate +between 0 light and their own specified light level. The "oscillate" +special (8) does nothing if there is no lower light level. + "blink off" means the light is at the specified level most of the time, +and changes to the lower value for just a moment. "blink on" means the +light is usually at the lower value, and changes to the sector's value +for just a moment. Every "synchronized" blinking sector on the level +will change at the same time, whereas the unsynchonized blinking sectors +change independently. "oscillate" means the light level goes smoothly +from the lower to the higher and back; it takes about 2 seconds to go +from maximum to minimum and back (255 down to 0 back up to 255). + The damaging sector types only affect players, monsters suffer no ill +effects from them whatsoever. Players will only take damage when they +are standing on the floor of the damaging sector. "-10/20%" means that +the player takes 20% damage at the end of every second that they are in +the sector, except at skill 1, they will take 10% damage. If the player +has armor, then the damage is split between health and armor. + +Dec Hex Class Condition or effect + + 0 00 - Normal, no special characteristic. + 1 01 Light random off + 2 02 Light blink 0.5 second + 3 03 Light blink 1.0 second + 4 04 Both -10/20% health AND light blink 0.5 second + 5 05 Damage -5/10% health + 7 07 Damage -2/5% health + 8 08 Light oscillates + 9 09 Secret a player must stand in this sector to get credit for + finding this secret. This is for the SECRETS ratio + on inter-level screens. +10 0a Door 30 seconds after level start, ceiling closes like a door. +11 0b End -10/20% health. If a player's health is lowered to less + than 11% while standing here, then the level ends! Play + proceeds to the next level. If it is a final level + (levels 8 in DOOM 1, level 30 in DOOM 2), the game ends! +12 0c Light blink 0.5 second, synchronized +13 0d Light blink 1.0 second, synchronized +14 0e Door 300 seconds after level start, ceiling opens like a door. +16 10 Damage -10/20% health + + The following value can only be used in versions 1.666 and up, it will +cause an error and exit to DOS in version 1.2 and earlier: + +17 11 Light flickers on and off randomly + + All other values cause an error and exit to DOS. This includes these +two values which were developed and are quoted by id as being available, +but are not actually implemented in DOOM.EXE (as of version 1.666): + + 6 06 - crushing ceiling +15 0f - ammo creator + + What a shame! The "ammo creator" sounds especially interesting! + + +[4-10]: REJECT +============== + + The REJECT lump is used to help speed play on large levels. It can +also be used for some special effects like monsters in plain sight who +CANNOT attack or see players. + The size of a REJECT in bytes is (number of SECTORS ^ 2) / 8, rounded +up. It is an array of bits, with each bit controlling whether monsters +in a given sector can detect and/or attack players in another sector. + Make a table of sectors vs. sectors, like this: + + sector that the player is in + 0 1 2 3 4 + +--------------- +sector 0 | 0 1 0 0 0 +that 1 | 1 0 1 1 0 +the 2 | 0 1 0 1 0 +monster 3 | 0 1 1 1 0 +is in 4 | 0 0 1 0 0 + + A 1 means the monster cannot become activated by seeing a player, nor +can it attack the player. A 0 means there is no restriction. All non- +deaf monsters still become activated by weapon sounds that they hear +(including the bare fist!). And activated monsters will still pursue +the player, but they will not attack if their current sector vs. sector +bit is "1". So a REJECT that's set to all 1s gives a bunch of pacifist +monsters who will follow the player around and look menacing, but never +actually attack. + How the table turns into the REJECT resource: + Reading left-to-right, then top-to-bottom, like a page, the first bit +in the table becomes bit 0 of byte 0, the 2nd bit is bit 1 of byte 0, +the 9th bit is bit 0 of byte 1, etc. So if the above table represented +a level with only 5 sectors, its REJECT would be 4 bytes: + +10100010 00101001 01000111 xxxxxxx0 (hex A2 29 47 00, decimal 162 41 71 0) + + In other words, the REJECT is a long string of bits which are read +from least significant bit to most significant bit, according to the +lo-hi storage scheme used in a certain "x86" family of CPUs. + Usually, if a monster in sector A can't detect a player in sector B, +then the reverse is true too, thus if sector8/sector5 is set, then +sector5/sector8 will be set also. Same sector prohibitions, e.g. 0/0, +3/3, etc. are only useful for special effects (pacifist monsters), or +for tiny sectors that monsters can't get to anyway. + + The REJECT array was designed to speed the monster-detection process. +If a sector pair is prohibited, the game engine doesn't even bother +checking line-of-sight feasibility for the monster to "see" the player +and consider attacking. When a level has hundreds of monsters and +hundreds of sectors, a good REJECT can prevent the drastic slowdowns +that might otherwise occur (even fast CPUs can fall victim to this +phenomenon). + + +[4-11]: BLOCKMAP +================ + + The BLOCKMAP is a pre-calculated structure that the game engine uses +to simplify collision-detection between moving things and walls. If a +level doesn't have a blockmap, it will display fine, but everybody walks +through walls, and no one can hurt anyone else. + A concise definition of the BLOCKMAP is in appendix [A-1]. This is +the full explanation of it. + The whole level is cut into "blocks", each is a 128 (hex 80) wide +square (the grid lines in the automap correspond to these blocks). The +BLOCKMAP is a collection of lists, one list for each block, which say +what LINEDEFS are wholly or partially in that block (i.e. part of the +line passes through the block). When the game engine needs to check +for an object/wall collision (to prevent a player from walking through +a wall or to explode a rocket when it hits a wall, etc.), it just looks +up the blocklist for the block that the object is in. This tells it +which linedefs it needs to check for collisions. Most blocks will have +few if any lines in them, so there will be a substantial savings in +processor time if it only checks a couple linedefs per object instead +of a thousand or so linedefs per object - it would have to check every +single linedef on the level if not for these blocklists. + The blocks are also used for object/object collisions, but that is +not visible in the WAD format. During play, each block is also given a +dynamic "thing list", which tells what THINGS are currently in that +block. Again, this negates the need to check every moving object vs. +every other object for collisions - only a few need be tested. + The BLOCKMAP is composed of three parts: the header, the offsets, and +the blocklists. + The 8-byte header contains 4 short integers: + +(1) X coordinate of block-grid origin +(2) Y coordinate of block-grid origin +(3) # of columns (blocks in X direction) +(4) # of rows (blocks in Y direction) + + The block-grid origin is the bottom-left corner of the bottom-left +(southwest) block. id's blockmap builder this origin point at 8 less +than the minimum values of x and y achieved in any vertex on the level. + The number of columns and rows needs to be sufficient to contain +every linedef in the level. If there are linedefs outside the blockmap, +it will not be able to prevent monsters or players from crossing those +linedefs, which can cause problems, including the hall of mirrors effect. + + There are N blocks, N = (number of columns * number of rows). Each +block has a blocklist and an offset to that blocklist. Immediately +following the 8-byte header are N unsigned short integers. The first +is the offset in short-ints NOT bytes, from the start of the BLOCKMAP +lump to the start of the first blocklist. The last offset points to +blocklist (N-1), the last blocklist. Note that all these offsets are +UNSIGNED, so they can point to a location 65535 shorts (131070 bytes) +into the BLOCKMAP. If they were signed, they could only go up to 32767. + The blocks are numbered going east (right) first, then north (up). +Block 0 is at the southwest corner (row 0, column 0). Block 1 is at +row 0, column 1. If there are 37 columns, then block 38 is at row 1, +column 0, etc. + + After the offsets come the blocklists. Each blocklist starts with +a short-int 0 (0x0000) and ends with a short-int -1 (0xffff). In between +are the numbers of every linedef which has any portion whatsoever in the +128 x 128 coordinate area of that block. If the block-grid origin is at +(0,0), then the first column is X = 0 to 127 inclusive, the second column +is X = 128 to 255 inclusive, etc. So a vertical line with X = 128 which +might seem to be on the border of two columns of blocks is actually only +in the easternmost/rightmost column. Likewise for the rows. + The first linedef in the LINEDEFS lump is linedef number 0, and so on. +An "empty" block's blocklist only has the two shorts 0 and -1. A non- +empty block might have this as its blocklist: 0 330 331 333 -1. This +means that linedefs 330, 331, and 333 have some part of them pass through +this block. A block that has linedef 0 in it will go: 0 0 .. etc .. -1. + + There is an upper limit to how big a BLOCKMAP can be. Even empty +blocklists require at least 3 shorts - the 0, the -1, and the offset to +the blocklist. The offsets are unsigned shorts, which would imply a +maximum value of short #65535 ( = byte 131070) for the start of the last +blocklist. At a little over 6 bytes per blocklist, that would be a maximum +of around 21000 blocks (145 by 145 blocks, 18560 in coordinates). But the +actual limit is less. Experiments suggest that the maximum total size of +all the blocklists, not counting the offsets, is 65535 bytes. This limits +a minimalist level to around 120 blocks square (15360 in coordinates), +or a realistically complex level to around 100 blocks square (12800 in +coordinates). + + +--------------------- +CHAPTER [5]: Graphics +--------------------- + + The great majority of the entries in the directory reference lumps +that are in a special picture format. The same format is used for the +sprites (monsters, items), the wall patches, and various miscellaneous +pictures for the status bar, menu text, inter-level map, etc. Every +one of these picture lumps contains exactly one picture. The flats +(floor and ceiling pictures) are NOT in this format, they are raw data; +see chapter [6]. + A great many of these lumps are used in sprites. A "sprite" is the +picture or pictures necessary to display any of the THINGS. Some of +them are simple, a single lump like SUITA0. Most of the monsters have +50 or more lumps. + The first four letters of these lumps are the sprite's "name". TROO +is for imps, BKEY is for the blue key, and so on. See [4-2-1] for a list +of them all. The fifth letter in the lump is an indication of what "frame" +it is, for animation sequences. The letters correspond to numbers, ASCII +"A" equalling 0, "B" is 1, ... "Z" is 25, etc. The highest needed by a +DOOM 1 sprite is W=22, but some of the DOOM 2 monsters need a few more +frames. + The "0" in the lump name is for "rotations" or "rot"s. All the +static objects like torches and barrels and dead bodies look the same +from any angle. This is because they have a "rot=0 lump" as DOOM itself +might say. Monsters and projectiles look different from different +angles. This is done with rots 1-8. This diagram shows how rot 1 is for +the front and they go counter-clockwise (looking from above) to 8: + + 3 + 4 | 2 + \|/ + 5--*----> 1 Thing is facing this direction + /|\ + 6 | 8 + 7 + + Many things have sets of lumps like this: TROOA1, TROOA2A8, TROOA3A7, +TROOA4A6, TROOA5, TROOB1, etc. This means that for frame 0 (A), the +pairs of rots/angles (2 and 8), (3 and 7), and (4 and 6) are mirror- +images. In the long run, this saves space in the wad file, and from the +designer's point of view, it's 37% fewer pictures to have to draw. + If a sprite's frame has a non-zero rot, it needs to have ALL 8 of +them. Also note that no more than two rots can be squeezed into one +lump's name. Some other two-rot lumps with a different format are +shown in the SPIDA1D1, SPIDA2D2, etc. lumps. + + IMPORTANT: Sprite lumps and flats cannot be added or replaced via pwads +unless they ALL are. That is, ALL sprites' lumps must be located in a +single wad file, and ALL flats' lumps must be in a single wad file. Wall +patches CAN be used in external wads, because the PNAMES lump gives a +number to every pname, and is used as a quick-index list to load in +wall patches. + Version 1.666 was rumored to be able to include sprites in pwads (in +fact the README says it can), but it can't. + + +[5-1]: Picture Format +===================== + + Each picture has three sections. First, an 8-byte header composed of +four short-integers. Then a number of long-integer pointers. Then the +picture's pixel/color data. See [A-1] for concise BNF style definitions, +here is a meatier explanation of the format: + +(A) The header's four fields are: + + (1) Width. The number of columns of picture data. + (2) Height. The number of rows. + (3) Left offset. The number of pixels to the left of the center; + where the first column gets drawn. + (4) Top offset. The number of pixels above the origin; + where the top row is. + + The width and height define a rectangular space or limits for drawing +a picture within. To be "centered", (3) is usually about half of the +total width. If the picture had 30 columns, and (3) was 10, then it +would be off-center to the right, especially when the player is standing +right in front of it, looking at it. If a picture has 30 rows, and (4) +is 60, it will appear to "float" like a blue soul-sphere. If (4) equals +the number of rows, it will appear to rest on the ground. If (4) is less +than that for an object, the bottom part of the picture looks awkward. + With walls patches, (3) is always (columns/2)-1, and (4) is always +(rows)-5. This is because the walls are drawn consistently within their +own space (There are two integers in each SIDEDEF which can offset the +starting position for drawing a wall's texture within the wall space). + + Finally, if (3) and (4) are NEGATIVE integers, then they are the +absolute coordinates from the top-left corner of the screen, to begin +drawing the picture, assuming the VIEW is full-screen (i.e., the full +320x200). This is only done with the picture of the player's current +weapon - fist, chainsaw, bfg9000, etc. The game engine scales the +picture down appropriatelyif the view is less than full-screen. + +(B) After the header, there are N = field (1) = = (# of columns) +4-byte integers. These are pointers to the data for each COLUMN. +The value of the pointer represents the offset in bytes from the first +byte of the picture lump. + +(C) Each column is composed of some number of BYTES (NOT integers), +arranged in "posts": + + The first byte is the row to begin drawing this post at. 0 means +whatever height the header (4) upwards-offset describes, larger numbers +move correspondingly down. + The second byte is how many colored pixels (non-transparent) to draw, +going downwards. + Then follow (# of pixels) + 2 bytes, which define what color each +pixel is, using the game palette. The first and last bytes AREN'T drawn, +and I don't know why they are there. Probably just leftovers from the +creation process on the NeXT machines. Only the middle (# of pixels in +this post) are drawn, starting at the row specified in the first byte +of the post. + After the last byte of a post, either the column ends, or there is +another post, which will start as stated above. + 255 (0xFF) ends the column, so a column that starts this way is a null +column, all "transparent". Draw the next column. + + +----------------------------------------------- +CHAPTER [6]: Flats (Floor and Ceiling Textures) +----------------------------------------------- + + All the lumpnames for flats are in the directory between the F_START +and F_END entries. Calling them flats is a good way to avoid confusion +with wall textures. There is no look-up or meta-structure in flats as +there is in walls textures. Each flat is 4096 raw bytes, making a square +64 by 64 pixels. This is pasted onto a floor or ceiling with the same +orientation as the automap would imply, i.e. the first byte is the color +at the NW corner, the 64th byte (byte 63, 0x3f) is the NE corner, etc. + The blocks in the automap grid are 128 by 128, so four flats will fit +in each block. Note that there is no way to offset the placement of flats, +as can be done with wall textures. They are pasted according to grid lines +64 apart, reckoned from the coordinate (0,0). This allows flats to flow +smoothly even across jagged boundaries between sectors with the same +floor or ceiling height. + + As discussed in chapter [5], replacement and/or new-name flats don't +work right from pwad files unless they are all in the same wad. + Theoretically, you can change all the flats want by constructing a +new DOOM.WAD or ALLFLATS.WAD pwad, but you have to make sure no floor +or ceiling uses an entry name which isn't in your F_ section. And you +have to include these four entries for DOOM 1 use, although you can +change their actual contents (pictures): FLOOR4_8, SFLR6_1, MFLR8_4, +and FLOOR7_2. The first three are needed as backgrounds for the episode +end texts. The last is what is shown "outside" the border of the display +window if the display is not full-screen. + + +[6-1]: Animated Flats +--------------------- + + See Chapter [8-4-1] for a discussion of how the animated walls and +flats work. Unfortunately, the fact that the flats all need to be +lumped together in one wad file means that its not possible to change +the animations via a pwad file, unless it contains ALL the flats, which +amounts to several hundred k. Plus it is illegal to distribute the +original data, so to pass around modifications, either all the flats +must be all-new, or a utility must be used to construct a FLATS.WAD +on an end-user's hard drive, using the original DOOM.WAD plus the +additions. (Note: Bernd Kreimeier, listed in [A-5], has written a +utility that does just this. It is called DMADDS11). + + +----------------------------- +CHAPTER [7]: Sounds and Music +----------------------------- + + +[7-1]: PC Speaker Sound Effects +=============================== + + DP* entries in the directory refer to lumps that are sound data for +systems using the PC speaker. + It's a quick and simple format. First is a that's always 0, +then a that's the number of bytes of sound data, then follow +that many bytes worth of sound data. That is, the lump's bytes will be +0, 0, N, 0, then N bytes of data. The DP* lumps range in size from around +10 bytes to around 150 bytes, and the data seem to range from 0 to 96 +(0x00 to 0x60). The numbers obviously indicate frequency, but beyond +that I don't know the exact correlation in Hz, nor the time duration +of each byte worth of data. Feel free to figure this out and tell me. + + +[7-2]: Soundcard Sound Effects +============================== + + DS* entries in the directory refer to lumps that are sound data for +systems using soundcards. + This data is in a RAW format for 8-bit 11 KHz mono sound - first is +an 8-byte header composed of 4 unsigned short integers: + +(1) 3 (means what?) +(2) 11025 (the sample rate, samples per second) +(3) N (the number of samples) +(4) 0 + + Each sample is a single byte, since they are 8-bit samples. The +maximum number of samples is 65535, so at 11 KHz, a little less than +6 seconds is the longest possible sound effect. + + +[7-3]: Music +============ + + D_* entries is the directory refer to lumps that are music. This +music is in the MUS file format, which goes like this: + +offset type contents + +0 ASCII "MUS" and CTRL-Z (hex 4d 55 53 1a) +4 # of bytes of music data +6 # of bytes of header data (offset to start of music) +8 number of primary channels +10 number of secondary channels +12 number of instrument patches +14 0 +16 s instrument patch numbers +X to end ? Music data + + X is the header size (the second short). Drum patch numbers (greater +than 128) are 28 less than the numbers listed in the DMXGUS lump. + What the exact format of the music data is, I don't know. + + +[7-4]: GENMIDI +============== + + This has something to do with General MIDI, obviously. This lump +has 3 sections: an 8-byte header (the ASCII text "#OPL_II#"), then +150 36-byte records (1 for each instrument), then 150 32-byte strings +(names of instruments, padded with zeros). Perhaps the 36 bytes contain +waveform information for the General MIDI standard instruments (this +guess is based on exactly one glance at a dump of the byte values, +so don't put too much faith in it). + + +[7-5]: DMXGUS +============= + + This lump is used to do instrument patch mappings on the Gravis +Ultra-Sound soundcard. It's in a very simple format - ASCII text! +Here's the start and end of the DMXGUS lump from DOOM 1 version 1.2, +which is 200 lines, of which the first 10 are comments: + +#Purpose: Different size patch libraries for different memory sizes. +# The libraries are built in such a way as to leave 8K+32bytes +# after the patches are loaded for digital audio. +# +#Revision History: 06/22/93 - Fixed problem with 512K patch library +# 07/26/93 - patch names changed in various releases +# +# +#Explanation of Columns: Patch # 256K 512K 768K 1024K Patch Name +# +0, 2, 1, 1, 1, acpiano +1, 2, 1, 1, 1, britepno +2, 2, 1, 1, 1, synpiano +. +. +. +213, 128, 128, 128, 128, castinet +214, 128, 128, 128, 128, surdo1 +215, 128, 128, 128, 128, surdo2 + + +-------------------------------- +CHAPTER [8]: Miscellaneous Lumps +-------------------------------- + + +[8-1]: PLAYPAL +============== + + There are 14 palettes here, each is 768 bytes = 256 rgb triples. +That is, the first three bytes of a palette are the red, green, and +blue portions of color 0. And so on. Note that the values use the +full range (0..255), while standard VGA digital-analog converters +use values 0-63. + The first palette, palette 0, is used for most situations. + Palettes 10-12 are used (briefly) when an item is picked up, the +more items that are picked up in quick succession, the brighter it +gets, palette 12 being the brightest. + Palette 13 is used while wearing a radiation suit. + Palettes 3, 2, then 0 again are used after getting berserk strength. + If the player is hurt, then the palette shifts up to number X, then +comes "down" one every second or so, to palette 2, then palette 0 +(normal) again. What X is depends on how badly the player got hurt: +Over 100% damage (add health loss and armor loss), X=8. 93%, X=7. 81%, +X=6. 55%, X=5. 35%, X=4. 16%, X=2. These are just rough estimates +based on a single test session long ago. Why bother tracking down +the exact division points? + + Unknown: what palettes 1 and 9 are for. + + +[8-2]: COLORMAP +=============== + + This contains 34 sets of 256 bytes, which "map" the colors "down" in +brightness. Brightness varies from sector to sector. At very low +brightness, almost all the colors are mapped to black, the darkest gray, +etc. At the highest brightness levels, most colors are mapped to their +own values, i.e. they don't change. + In each set of 256 bytes, byte 0 will have the number of the palette +color to which original color 0 gets mapped. + The colormaps are numbered 0-33. Colormaps 0-31 are for the different +brightness levels, 0 being the brightest (light level 248-255), 31 being +the darkest (light level 0-7). Light level is the fifth field of each +SECTOR record, see [4-9]. + Colormap 32 is used for every pixel in the display window (but not +the status bar), regardless of sector brightness, when the player is +under the effect of the "Invulnerability" power-up. This colormap is +all whites and greys. + Colormap 33 is all black for some reason. + While the light-amplification goggles power-up is in effect, everything +in the display uses colormap 0, regardless of sector brightness. + + +[8-3]: ENDOOM +============= + + When you finally have to leave DOOM, you exit to dos, and a colorful +box of text appears. This is it. It is 4000 bytes, which are simply +stored in the screen memory area for 80x25 16-color text mode. Thus +it follows the same format as screen memory does: each character on +the screen takes up two bytes. The second byte of each pair is from +the (extended) ASCII character set, while the first byte of each pair +is the color attribute for that character. The color attribute can +be explained thus: + + bit 7 6 5 4 3 2 1 0 + +-----+---+---+---+---+---+---+---+ + | | . . | . . . | + |Blink| Background| Foreground | + | | . . | . . . | + +-----+---+---+---+---+---+---+---+ + + So the foreground color can be from 0-15, the background color can +be from 0-7, and the "blink" attribute is either on or off. All this +very low-level info can be found in many ancient PC programming books, +but otherwise it might be hard to locate... + + +[8-4]: TEXTURE1 and TEXTURE2 +============================ + + These are lists of wall texture names used in SIDEDEFS lumps. Each +wall texture is composed of one or more wall patches, whose names are +listed in the PNAMES lump. But in a texture, the wall patches are not +referred to by name, rather by the index number indicating what position +they occupy in the PNAMES lump. + The TEXTURE2 lump is only present in the registered DOOM.WAD. The +TEXTURE1 lump is identical in DOOM.WAD and the shareware DOOM1.WAD, and +it only refers to pname numbers up to 163, because the shareware wad +only has the first 163 wall patches, not all 350. + + A TEXTURE lump starts with a 4-byte long integer N which is the number +of textures defined in it. Following it are N long integers which are the +offsets in bytes from the beginning of the TEXTURE lump to the start of +each texture's definition. + Then there are N texture definitions, which have the following format. +The first (texture name) field is an 8-byte string (less than 8 byte +names are padded with zeros), the rest of the fields are 2-byte short +integers: + +(1) The name of the texture, used in SIDEDEFS, e.g. "FIREWALL". +(2) always 0. +(3) always 0. +(4) total width of texture +(5) total height of texture + + The fourth and fifth fields define a "space" (usually 128 by 128 + or 64 by 72 or etc...) in which individual wall patches are placed + to form the overall picture. To tile vertically on a very tall wall + without exhibiting the "Tutti Frutti" effect, a texture must have + height 128, the maximum. There is no maximum width. + +(6) always 0. +(7) always 0. +(8) Number of 5-field (5 ) patch descriptors that follow. This +means that each texture entry has variable length. Many entries have just +1 patch, the most used in DOOM in a single texture is 64. + + Patch descriptor: + + (a) x offset from top-left corner of texture space defined in fields + 4 and 5 to start placement of this patch + (b) y offset + (c) number (0...) of the entry in the PNAMES lump that contains the + lump name from the directory, of the wall patch to use... + (d) always 1, is for something called "stepdir"... + (e) always 0, is for "colormap"... + + Each texture's entry ends after the last of its patch descriptors. + Note that patches can have transparent parts, since they are in the +same picture format as everything else. Thus there can be (and are) +transparent wall textures. These should only be used on a border between +two sectors, to avoid "hall of mirrors" problems. + Also, textures intended for use as the "middle" texture of a 2-sided +SIDEDEF (e.g. transparent textures) should only be composed of a single +patch. A limitation in the game engine will cause the "medusa" effect +if there is more than 1 patch in any middle texture that is visible in +the display window. This effect causes the computer to slow to a crawl +and make play impossible until the offending wall is out of view. + + +[8-4-1]: Animated Walls +----------------------- + + Some of the walls and floors are animated. In the case of wall +textures, it is possible to substantially customize these animations. +Flats' animations can theoretically also be modified, but since flats +don't work from pwads, that can make the effort very difficult. + The game engine sets up a number of wall animation cycles based on +what entries it finds in the TEXTURE lumps. It also sets up flat +animations based on what lumps exist between F_START and F_END. +Versions before 1.666 can have up to 9 animated walls and 5 animated +flats. Version 1.666 (DOOM 1 or 2) can have 13 walls and 9 floors +animate. + For wall animations, the entries in the columns "First" and "Last" +below, and all the entries between them (in the order that they occur +in the TEXTURE lump) are linked. If one of them is listed as a texture +on a sidedef, that sidedef will change texture to the next in the cycle +about 3 times a second, going back to after . Flats work +similarly, except the order is dictated by the wad directory. If both +of the and texture/flat names are not present, no problem. +Then that potential cycle is unused. But if is present, and + either is not present or is listed BEFORE , then an +error occurs while the DOOM operating system sets up, and it aborts. + Note that much longer sequences are possible! The entries between + and can be almost anything; they need not be the same +in number as in the original, nor do they have to follow the same +naming pattern. Thus one could set up SLADRIP1, TRON2, TRON3, TRON4, +..., TRON67, SLADRIP3 for a 69-frame animated wall! + The "Ver" column indicates what version of DOOM is required. "All" +indicates all versions have it. The "r" signifies that the shareware +DOOM1.WAD does not contain the necessary picture lumps. The "2" means +that only DOOM 2 has the necessary picture lumps, but version 1.666 of +DOOM.EXE for DOOM 1 also has the capability to use these animation-cycle +names (for pwad designers). + +First Last Ver Normal # of frames + +BLODGR1 BLODGR4 r 4 +BLODRIP1 BLODRIP4 r 4 +FIREBLU1 FIREBLU2 r 2 +FIRELAV3 FIRELAVA r 2 (3 patches are in DOOM.WAD, 1 is unused) +FIREMAG1 FIREMAG3 r 3 +FIREWALA FIREWALL r 3 +GSTFONT1 GSTFONT3 r 3 +ROCKRED1 ROCKRED3 r 3 +SLADRIP1 SLADRIP3 All 3 + +BFALL1 BFALL4 2 4 +SFALL1 SFALL4 2 4 +WFALL1 WFALL4 2 4 +DBRAIN1 DBRAIN4 2 4 + +(floor/ceiling animations): + +NUKAGE1 NUKAGE3 All 3 +FWATER1 FWATER4 r 4 +SWATER1 SWATER4 - 4 (SWATER lumps aren't in any DOOM.WAD) +LAVA1 LAVA4 r 4 +BLOOD1 BLOOD3 r 3 + +RROCK05 RROCK08 2 4 +SLIME01 SLIME04 2 4 +SLIME05 SLIME08 2 4 +SLIME09 SLIME12 2 4 + + +[8-4-2]: The SKY Textures +------------------------- + + The SKY1, SKY2, and SKY3 textures are rather special in that they are +used as sky backgrounds when the player is out in the open. They can +also be used on regular walls, but they usually aren't, because then +they just look like a painting. The "background" effect is done by +the game engine. There is a special flat, F_SKY1, which is used to +indicate that a floor or ceiling is "transparent" to the SKY beyond. +The picture data in the F_SKY1 flat is not even used. + Upper textures between F_SKY1 ceilinged sectors do not have the +specified texture (if any) drawn. Instead, they are "sky". Likewise +with lower textures between F_SKY1 floored sectors, but it doesn't +work as well, because if the player's viewpoint is below the top of +a lower-texture-sky (i.e. if any part of it is in the upper half of +the display), it causes a hall-of-mirrors effect. + SKY textures as sky backgrounds are mirror-images of what they look +like on walls. + The SKY textures are always placed with their tops at the top of the +view window. Since they cannot be more than 128 high, just like any +other texture, a rather ugly "seam" in the sky is sometimes visible +if the player can see too far "down". + SKY textures do move horizontally, though, to give a realistic +effect. Doing a complete 360 degree turn will scroll by a 256-wide +SKY four times. A 1024-wide SKY will exactly circumscribe the horizon. +The 0 column of the SKY texture will be at due north (as on the automap), +the 256 column is at west, 512 is south, and 768 is east. So the middle +part of a 256-wide SKY is visible at NW, SW, SE, and NE. + + SKY textures can be composed of several patches, just like regular +textures, but trying to animate the sky doesn't work. DOOM.EXE can be +changed so that SKY2 is the start of an animation cycle, and indeed +on a wall it will animate, but the sky background does not. This is +perhaps related to the way that "middle" textures of sidedefs do not +animate. + + +[8-5]: PNAMES +============= + + This is a list of all the names of lumps that are going to be used +as wall patches. DOOM assigns numbers to these names, in the order +that they are listed. The numbers are then used in TEXTURE1 and TEXTURE2 +entries to refer to wall patch lumps. In a texture composition entry, +0 means the first pname, 1 is the second, ... + + The first four bytes of the PNAMES lump is a long integer N. + Then follow N pnames, each of which occupies 8 bytes. Pnames less than +8 bytes long are padded with zeros. These names duplicate an entry in +the directory (but are not case sensitive - lowercase letters will match +uppercase letters and vice versa). Unlike sprites and floors, wall +patches can be loaded from external pwads. Whichever pwad was listed +last on the command line and contains a lump with the same name as the +one in the PNAMES lump (which itself could be from a pwad) is the pwad +that is used to get the picture data for that wall patch. + + +[8-6]: DEMOs +============ + + If you start DOOM and do nothing, after a few seconds, it automatically +shows a demo of play on some level. Also, external demos can be recorded +and played back by using the command line parameters explained in the +README and/or the DOOM FAQ. All external demos have a .LMP extension +which the DOOM OS attaches; you only type the [demoname] without the +.LMP extension. + The DOOM.WAD lumps DEMO1, DEMO2, and DEMO3 are in exactly the same +format as these external .LMP files. Strictly speaking, the "demo" +format should not be called the "LMP" format, because any external +file without a wadfile header, i.e. it is just raw data, is a "lump" +and deserves the .LMP extension. + + A DOOM demo has three parts: + + (1) header - 7 or 13 bytes + (2) data recording player moves - 4 bytes per player per gametic + (3) quit byte - equals 128 (0x80) + +(1) There are two different kinds of header depending on the version of +DOOM used to record the demo. Versions up to 1.2 use a 7-byte header: + + byte range purpose + +0 0-4 skill level. 0="I'm too young to die", 4="Nightmare!" +1 1-3 episode. +2 1-9 mission/map. +3 0-1 player 1 is present if this is 1. +4 0-1 player 2. +5 0-1 player 3. +6 0-1 player 4. + + Versions after 1.2 use a 13-byte header: + +byte range purpose + +0 104-106 version. 104=1.4 beta, 105=1.5 beta, 106=1.6 beta or 1.666 +1 0-4 skill level. 0="I'm too young to die", 4="Nightmare!" +2 1-3 episode. In DOOM 2 this is always 1. +3 1-32 mission/map/level. In DOOM 1, it's 1-9. In DOOM 2, it's 1-32. +4 0-2 mode. 0=single or cooperative, 1=deathmatch, 2=altdeath +5 0- respawn. 0=no respawn parameter, (any other value)=respawn. +6 0- fast. 0=no fast parameter, (any other value)=fast. +7 0- nomonsters. 0=monsters exist, (any other value)=nomonsters. +8 0-3 viewpoint. 0=player 1's status bar, ..., 3=player 4. +9 0-1 player 1 is present if this is 1. +10 0x0a 0-1 player 2. +11 0x0b 0-1 player 3. +12 0x0c 0-1 player 4. + +(2) The player-move data is recorded in 4-byte chunks. Every 1/35 of a +second is a gametic, and for every gametic, there is one 4-byte chunk +per player. So the time duration of a demo (in seconds) is approximately +equal to its length in bytes divided by (140 * number_of_players). + + The four bytes recording each player's actions are: + + (a) Forward/Backward Movement. + (b) Strafe Right/Left Movement. + (c) Turn Left/Right. + (d) other actions - use/activate, fire, change weapons. + + The first three are signed bytes (i.e. of type ). + + (a) Ranges from -127 to 127, negative numbers are backward movement, + positive numbers are forward movement. Without the -turbo option + above 100, values outside -50..50 cannot be achieved. With a + keyboard or joystick, these are the regular values: + + Move forward: 25 (0x19) with Speed on: 50 (0x32) + Move backward: -25 (0xE7) with Speed on: -50 (0xCE) + + Fancy mouse use can achieve any number in the range. + + (b) Ranges from -127 to 127, negative numbers are left-strafe movement, + positive numbers are right-strafe movement. The keyboard values are: + + Strafe right: 24 (0x18) with Speed on: 50 (0x32) + Strafe left: -24 (0xE8) with Speed on: -50 (0xCE) + + (c) Ranges from -127 to 127, negative numbers are right turns, positive + numbers are left turns. The keyboard values vary from version to + version, but are all in the range -5..5, and that's with Speed on. + + Using the mouse can achieve much higher numbers here. I doubt if + the maximums of 127 and -127 can actually be achieved in play, + though. + + (d) the bits of this byte indicate what actions the player is engaged in: + + bit 0 Fire current weapon + bit 1 Use (a switch, open a door, etc.) + bit 2 Change weapon to the one indicated in bits 3-5: + + bits 5-3 = 000 Fist or Chainsaw + 001 Pistol + 010 Shotgun + 011 Chaingun + 100 Rocket Launcher + 101 Plasma Rifle + 110 BFG 9000 + 111 Super Shotgun (DOOM 2 only) + + bit 6 unused + bit 7 indicates a special action which alters the meanings + of the other bits: + + bits 1-0 = 01 pause or unpause + = 10 save game in slot # recorded in bits 4 to 2 + (slot number can thus be 0 to 7 but + should NOT be 6 or 7 or else!) + + There might be other special actions. The save game action happens +during replay of the demo, so be careful when playing demos if you +have important savegames! One or more of them could conceivably get +overwritten. + +(3) The last byte of a demo has the value 128 (0x80) + + +[8-6-1]: Level changes from 1.2 to 1.666 DOOM.WAD +================================================= + + Many people have experienced the error "Demo from a different game +version", because DOOM versions will only play back demos that were +recorded with the same version number. Theoretically, though, ANY +version can be converted to ANY other version. Versions up to 1.2 +don't even require any modification, and versions 1.4 and up just +require that their first byte be changed. To change between the two +families (pre-1.4 and post-1.2) would just require extra header bytes +be added/shaved. + But there are serious complications to converting demos. There have +been some minor changes and bug-fixes to the levels from version to +version. Since the demos only record player actions, they have NOTHING +about the level in them, so they depend on the level used for playback +to be the same as the level used for recording. Some kinds of differences +in the playback level can cause the demo to become "unsynchronized" +with the level, and players will seem to have gone crazy. For example, +if a deathmatch start-position is at a different location, when a +demo-player is spawned there, they will try to open doors that don't +exist at the new location, shoot at people who aren't there, etc. +The entire playback is ruined from that point on. Some examples of +changes that don't matter are different floor and wall textures, light +levels, and linedef "unpegged" flags. But most changes DO matter. + Note that changes like (nomonsters) vs. (monsters), (respawn) vs. +(regular), (altdeath) vs. (regular deathmatch) will very quickly lead +to unsynchronized goofball players. + + + +--------------------------- +CHAPTER [9]: Savegame Files +--------------------------- + + +------------------------------- +CHAPTER [10]: The DOOM.EXE File +------------------------------- + + Via pwads, a great many characteristics of the DOOM environment can +be changed: maps, pictures, sounds, etc. But there are also a lot of +neat things that can be done by patching the DOOM.EXE file itself. +There is a large collection of data at the end of the EXE file, and by +patching some bytes, we can turn literal values into variables. For +example, the player has a 16-unit "radius" which prevents him from +entering very small passageways. The player's radius can be made 1 and +his "height" 1, so he can enter mouse-sized crawlspaces. There are a +lot more exciting examples, such as invisible monsters, cyber-demons +that look like players, super-fast shotguns, and a hundred others, but +I won't describe all of that here. See appendix [A-4] for some EXE +utilities and documents. Here I will simply give the data that has +been figured out to date. + I freely mix hex and decimal numbers below. Hopefully you can tell from +the context. All of the stuff below applies to registered version 1.2, +and some of it applies to version 1.666 also. This chapter has not yet +been completely updated for 1.666, but it soon will be. + +[10-1]: Version 1.2 DOOM.EXE Data Segment Overview +================================================== + + The data begins at 0x6f414 (455700) and continues to the end of the +file, 0x8db27 (580391). Here's an overview of the sections: + +start length what + +6f414 3d30 TEXT STRINGS +73412 1a34 various unknowns, probably to do with I/O, sound, mouse, etc. +74bf8 10000 looks like hard-coded math tables, for speed? +84bf8 148 misc. +84d40 82 gamma correction messages +84dc2 280 "are you sure you want to quit" messages +85042 3a2 MENUS (new game, load game, etc.) +853e4 140 ? +85524 36c configuration options and defaults, like in DEFAULT.CFG +85890 174 ? +85a04 60 ? +85a64 54 ? +85ab8 c4 ? +85b7c 20 max ammo at start, and ammo per thing +85b9c c0 ammo type and frame #s for the weapons +85c5c 188 ANIMATED WALLS and FLOORS +85de4 258 SWITCH-WALLS +8603c c0 ? +860fc d4 ? +861d0 500 5 colormaps for use with the gamma correction setting 0-4 +866e4 fc ? +867e0 40 pointers to chatmacros, "Green:", etc. +86820 88 pointers to level names, used on Automap +868a8 d8 splat mark coordinates for end-level screen +86980 5a8 wimap patch animations for end-level screen +86f28 224 SONG NAMES list of pointers +8714c 8b8 SOUND TABLE +87a04 1a4 SPRITE NAMES list of pointers +87ba8 3800 STATE TABLE +8b3a8 20 ? +8b3c8 2368 THING TABLE +8d730 3fd ? + +[10-2]: Version 1.666 DOOM.EXE Data Segment Overview +==================================================== + + +[10-3]: Detail on some EXE Data Structures +========================================== + + More detail on some of the data follows. The "names" of each section +are the hexadecimal offsets to the start of that data, in the registered +versions 1.2 and 1.666 of DOOM.EXE. 1.2 offsets are to the left of the +asterisk, 1.666 to the right. "Integer" means a 4-byte integer +in hi-lo format, unless otherwise noted (e.g. "2-byte short integer"). + +6f414 *** 82a14 + + START OF DATA. Several times I'll refer to "pointers". All of these +pointers are integers. Add the values of these pointers to $6f414 or +$82a14 depending on the version, and you'll get the location of what's +being pointed to. + Note: there's also at least one other kind of pointer in here, with +larger values, that point to a location in the code, NOT the data. I call +these "code-pointers" for now. I know it's a lame term. + +6f414 *** a2228 + + TEXT STRINGS. They all start on 4-byte boundaries, i.e. at xxxx0/4/8/c. +$00 ends the string. Then the next one starts at the next boundary, so a 4 +byte string is followed by $00, then 3 bytes of random junk, then the next +string. + +73140 + + I think this is the last string, "TZ" + +73144 + + Misc. stuff I haven't investigated. Some of it has to do with sound card +stuff and mice and joysticks, because at 7384c is "DMXGUS.INI" and at 74ba8 +are pointers which point to the strings "None", "PC_Speaker", "Adlib", etc. + +74bf8 + + 64k of precisely ordered numbers, which leads me to believe they are +pre-calculated math tables, to speed up some floating point operations +used in the screen draw routine. Any other guesses? + +84bfc + + 3 pointers to the episode 1/2/3 end texts, "Once you beat...", "You've +done it...", and "The loathsome Spiderdemon is dead..." + +84c24 + + pointer to the string "doom.wad" + +84c74 + + pointer to the string "default.cfg" + +84c78 + + 8 integers: 1, 25, 50, 24, 40, 640, 1280, 320 + +84c98 + + 2 code-pointers + +84ccc + + 29 integers, with values like 90 and 135 and 180. Angles? + +84d40 + + "Gamma correction OFF", 00s, "Gamma correction level 1", ... 4. Each +occupies $1a bytes. + +84dc2 + + 8 text messages used to confirm quitting, each uses $50 bytes + +85042 + + MENUS. I know this controls to some extent which menu pictures are used +for which menu, but I haven't figured it all out yet. + +853e4 + + 14 ints: 42, 22, 23, 24, 28, 29, 31, 40, zeros + +8541c + + 256 bytes, values from 00-ff, no two the same, "random" order. + +85524 + + The configuration options. Each is 5 integers: a pointer to a string, +like "mouse_sensitivity", a code-pointer, the default value for that +option, a 0 or 1 (1 for all the "key_" options), and a 0. It would be +pretty dense to do anything with this, I think. + +85890 + + About 117 integers, with a definite structure, but I can't figure it +out, and changing/experimenting seems to do nothing. + +85a64 + + 21 sets of 4 bytes: 0, 0, 1, 0, 320, 168, "33", 0, 1, $(b2 26 26 2e), +$(ff 63 fd ff), a pointer that points to the $(b2...), 0, 1, "ema", 0, 0, +1, 0, 1, "xma". All these are unchanged from version 0.99 through 1.2, +except the pointer obviously. + +85ab8 + + Ints: 0, -1, -1, 0, 0, 0, 0, 4, 7, 10, 12, 14, 15, 15, 0, 0, 112, 96, 64, +176, then 16 that are members of this set {-65536, -47000, 0, 47000, 65536}, +then 4, 5, 6, 7, 0, 1, 2, 3, 8, 3, 1, 5, 7 + +85b7c *** 95714 + + AMMO AMOUNTS. 8 integers: 200, 50, 300, 50, 10, 4, 20, 1. The first four +are the maximum initial capacity for ammo, shells, cells, and rockets. The +backpack doubles these amounts. The second four are how many ammo in a +clip, shells, rockets/rocket, and cells/cell item. Boxes have 5x as much. + +859bc *** 95734 + + AMMO TABLE. 8 sets of 6 integers (9 sets in 1.666): + + version 1.2 version 1.666 + +Punch 5 4 3 2 5 0 Punch 5 4 3 2 5 0 +Pistol 0 12 11 10 13 17 Pistol 0 12 11 10 13 17 +Shotgun 1 20 19 18 21 30 Shotgun 1 20 19 18 21 30 +Chaingun 0 34 33 32 35 38 Chaingun 0 51 50 49 52 55 +Laucher 3 42 41 40 43 46 Laucher 3 59 58 57 60 63 +Plasma 2 59 58 57 60 62 Plasma 2 76 75 74 77 79 +BFG 2 66 65 64 67 71 BFG 2 83 82 81 84 88 +Chainsaw 5 53 52 50 54 0 Chainsaw 5 70 69 67 71 0 + Super-Shotgun 1 34 33 32 35 47 + + The first number of each set is the ammo type. Type 5 never runs out. +The next three numbers are 3 state #s (see the STATE TABLE below) for the +pictures displayed when moving while holding that weapon. You know, the +"bobbing weapon" effect? Fifth is the first state of the "shoot" sequence +for that weapon, and last is the first state of the "firing" sequence. The +"firing" pictures are the ones that are lit up, fire coming out, etc. + +85c5c *** 9580c + + ANIMATED WALLS and FLOORS. Each is 26 bytes: an integer, a 8-byte string, +$00, a 8-byte string, $00, and a final integer. + +0 NUKAGE3 NUKAGE1 8 +0 FWATER4 FWATER1 8 +0 SWATER4 SWATER1 8 +0 LAVA4 LAVA1 8 +0 BLOOD4 BLOOD1 8 + <---- v1.666 has four more: 0 RROCK08 RROCK05 8 +1 BLODGR4 BLODGR1 8 0 SLIME04 SLIME01 8 +1 SLADRIP4 SLADRIP1 8 0 SLIME08 SLIME05 8 +1 BLODRIP4 BLODRIP1 8 0 SLIME12 SLIME09 8 +1 FIREWALL FIREWALA 8 +1 GSTFONT3 GSTFONT1 8 +1 FIRELAVA FIRELAV3 8 +1 FIREBLU2 FIREBLU1 8 +1 ROCKRED3 ROCKRED1 8 + <---- V1.666 has four more: 1 BFALL4 BFALL1 8 + 1 SFALL4 SFALL1 8 + 1 WFALL4 WFALL1 8 + 1 DBRAIN4 DBRAIN1 8 + + Obviously the 0/1 means floor or wall. The first string is the name of +the animation cycle's LAST listed texture, the second string is the FIRST +listed texture. The cycle includes them and all entries between them in +whichever wad file is in effect (It doesn't have to be DOOM.WAD, a pwad +with new TEXTURE1 and 2 resources works quite nicely). The final 8 +doesn't seem to mean much. + +85dc8 + + A -1 then a bunch of zeros, maybe space for another animation cycle? + +85de4 *** 95a64 + + SWITCH WALL NAMES. Each is 20 bytes: an 8-byte string, 00, another +string, 00, and a 2-byte short integer. There are 28 switch names here +in v1.2 and 39 switch names in v1.666. When a switch is pulled, the game +checks to see if the wall texture is on this list. If it is, it changes +the wall texture to the corresponding alternate texture. The +is 1, 2, or 3. 1 means it's in all versions, 2 means only registered +DOOM 1 and DOOM 2, 3 means DOOM 2 only. + +86028 + + 20 zeros, again, room for one more? + +8603c *** + + 48 integers: 3 0 2 1 3 0 2 0 3 1 2 0 0 0 0 0 + 2 0 2 1 0 0 0 0 3 1 3 0 0 0 0 0 + 2 0 3 1 2 1 3 1 2 1 3 0 0 0 0 0 + +860fc *** + + 50 integers, all are either 50 or -50. + +861d0 *** + + 5 sets of 256 bytes, each is a COLORMAP, for the gamma correction +settings OFF, 1, 2, 3, 4. + +866d0 *** + + 5 integers: 1, 0, -1, 0, 0 + +866e4 *** + + 13 sets of 5 - 10 bytes, each set terminated by a $FF + +8675e *** + + $74 $20 + +86760 *** + + 13 pointers to the stuff at 866e4. An integer '0' between each pointer. + +867c8 *** + + 6 integers: -1, -1, 0, -1, 0, 1 + +867e0 *** + + 10 pointers to the 10 default chatmacros, then 4 pointers, to "Green:", +"Indigo:", "Brown:", "Red:" + +86820 *** + + AUTOMAP LEVEL NAMES. 27 pointers to the level names used on the automap. + +8689c *** + + The ascii letters "gibr" - the keys for sending messages in multiplayer. + +868a8 *** + + SPLAT MARK COORDINATES. At what screen coordinates to place the WISPLAT +picture on the end-level screen, for th 27 levels. 54 integers, 27 pairs. +e1m1 x, e1m1 y, ..., e3m9 y. + +86980, 86bb0, 86da8 *** + + END-LEVEL MAP ANIMATIONS. Each is 14 integers. The first one is (0, 11, +3, 224, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0). The first number is 0 for all the +ones on maps 0 and 2 (episodes 1 and 3), and it's 2 for map 1. The 11 is +always 11 except the last one of map 2 is 8. The 3 means 3 pictures are +involved in the animation, e.g WIA00100, WIA00101, and WIA00102. 224 and 104 +are the x and y coordinates. The sixth number is not 0 for map 1 - it's +from 1 to 8. This controls the way the Tower of Mystery "appears". All the +other numbers are always 0. + +86ef8 *** + + Three integers, how many animations for WIMAP0, 1, 2 respectively. + +86f04 *** + + Three pointers, to the starts of the animations for WIMAP0, 1, 2 +respectively. + +8714c *** + + SOUND TABLE. 61 and 1/2 sounds are listed here. Each is 9 integers: a +pointer to the string which is the sound's "name", then a 0 or 1, then +a value ranging from 32 to 128, then 0, -1, -1, 0, 0, 0. The names are +"pistol", "shotgn", ... "hoof", "metal", "chgun". Prefix DS or DP and you +get the entries in DOOM.WAD for the sound data. The "chgun" is the 1/2 - +there's no "DSCHGUN" in doom.wad, and the entry in this table is incomplete +anyway, lacking the all-important 0, -1, -1, 0, 0, 0 ending :-). There seem +to be a few glitches in the way the sounds were fit into the whole scheme, +this is just one of them. + +879ec *** + + pointer to start of SOUND TABLE. + +879f0 *** + + Integer = 150. 150 whats? + +87a04 *** + + SPRITE NAME POINTERS. 105 pointers to the strings "TROO", "SHTG", ..., +"SMRT". + +87ba8 *** 9834c + + STATE TABLE. 512 entries in v1.2, 967 entries in v1.666. Each entry +is 28 bytes in 7 integers: + +(1) sprite number 0-..., lookup in sprite name pointers list. +(2) sprite frame, 0="A" in a sprite lump, 1="B", etc. +(3) duration, how many gametics this state is displayed until + it looks for the next. -1 (0xffffffff) is forever. +(4) a "code pointer" which indicates what action(s) accompany + the displaying of this state. +(5) next state in sequence. 0 means no next state, sequence is done. +(6) always 0, has no effect. +(7) always 0, has no effect. + + +8b3a8 *** + + Two integers: 1, 0, then 6 code-pointers. + +8b3c8 *** 9ed10 + + THING TABLE. 103 entries in v1.2 which are each 88 bytes = 22 integers. +136 entries in v1.666, which are each 92 bytes = 23 integers. + +(1) Thing number, as used in maps. See [4-2-1]. Some of them are + equal to -1, e.g. the players' entry, and all projectiles. +(2) "Spawn" state. State number (from STATE TABLE) for when this + thing first appears. +(3) Health. Inanimates can't be killed, so it doesn't apply to them. +(4) "Moving" state. First state # of monsters pursuing, etc. +(5) "See player" sound. For monsters who become activated. Also for + projectiles' first sound. Note that sounds are 1-..., not 0-... + 0 indicates no sound. +(6) Reaction Time. Lower is faster. +(7) "Attack" sound. +(8) "Pain" state. +(9) Painchance. The chance out of 256 that a monster will be disrupted + when it gets hurt. Otherwise, they keep attacking. +(10) "Pain" sound. +(11) "Close attack" state. +(12) "Distance attack" state. +(13) "Death" state, or "explode" for projectiles. +(14) "Explosive death" state, only some monsters can be "mushed". +(15) "Death" sound, or "explode" for projectiles. +(16) Speed of movement. Projectiles' speed are * 65536. +(17) Horizontal size (radius) * 65536 +(18) Height * 65536 +(19) Mass +(20) Missile damage. Also, the Lost Soul has a 3 here, for it's attack. +(21) "Act" sound, for wandering monsters. +(22) Flags, see below +(23) "Respawn" state, for monsters being ressurected. VERSION 1.666 ONLY + + Flags. 0 = condition is false. 1 = condition is true. + + bit flagname effect on thing + + 0 Special it is a gettable thing (ammo, health, etc.) + 1 Solid creatures can't pass through (but projectiles can) + 2 Shootable can be hurt (note barrels have this set) + 3 NoSector totally invisible + 4 NoBlockmap + 5 + 6 (InPain) ? + 7 + 8 SpawnCeiling hung from ceiling + 9 NoGravity floating monsters and not-on-ground things + 10 Dropoff doesn't automatically hug floor if "jump" off ledge + 11 Pickup can pick up gettable items + 12 (NoClip) walks through walls + 13 + 14 Float floating monsters + 15 (Semi-NoClip) climb tall steps + 16 Missile projectiles + 17 (Disappearing ? + Weapon) + 18 Shadow semi-invisible like Spectres + 19 NoBlood uses PUFF instead of BLUD when hurt (e.g. barrels) + 20 (SlideHelpless) ? + 21 + 22 CountKill Monster: counts toward KILLS ratio on inter-level + 23 CountItem Artifact: counts toward ITEMS on inter-level screen + 24 (Running) ? + 25 NotDMatch this thing doesn't get spawned in deathmatch modes + 26 Color0 \ 00 = green stays green 01 = change to dark greys + 27 Color1 / 10 = change to browns 11 = change to dark reds + 28- unused + +8d730 *** n/a + + Misc junk I can't figure out. + +8db27 *** a7b99 + + End of DOOM.EXE + + +------------------------------------------------------------ +APPENDIX [A-1]: Backus-Naur Form definitions of WAD elements +------------------------------------------------------------ + + The descriptions below use a modified Backus-Naur Form (BNF) notation. +Each entry looks like + + := description ;type or comment (optional) + description cont'd. ;type or comment (optional) + + Descriptions composed of more than one sequential keyword or element +are usually listed with one element per line. This is for clarity and also +allows each succesive element to be assigned different types without extra +lines. + + := ; + + is a shorthand for + + := + := + + The description is one or more of the following predefined types, +and/or previously or subsequently defined keywords. + + is an unsigned 8-bit integer (0 to 255). + is a signed 8-bit integer (-128 to 127). + is an unsigned 16-bit integer in lo-hi format (0 to 65535) + is a signed 16-bit integer (-32768 to 32767). + is a signed 32-bit integer (-2147483648 to 2147483647). + is an ASCII string of from 1 to 8 bytes. If its length is + less than 8 bytes, the remainder are zeros (hex 00). + + Any of these may be followed by a range: means a byte +restricted to the range 1 to 99 inclusive. A single number means that +value is literally included: inserts that 8-bit value. + + { } are used to enclose a group of elements. + + | is used to separate choices - exactly one of the choices applies. + + [ ] are used following an element or group of elements to indicate +an array. Usually a literal value or a keyword will be used to denote +how many members the array has. [666] means that the element + is repeated 666 times in sequence. { } [zeus] +means that whatever the value of is, there are that many pairs +of and . [1..16] indicates the value may be from +1 to 16 inclusive, and [...] indicates an indefinite number. + + A literal string "ABCD" may appear, in which case those ASCII characters +are directly inserted. + +------ + + := "PWAD"|"IWAD" + + + + + + := ;number of lumps in WAD file + := ;file offset to directory start + + := [numlumps] + := ;see different kinds below + + := { | } [numlumps] + := ; + ; + ; + + := |